import { z } from "zod"; /** * AMC request body value type */ type AMCRequestBodyValue = string | number | boolean | null | undefined | Record | AMCRequestBodyValue[]; /** * AMC request body type definition */ interface AMCRequestBody { moduleName?: string; action?: string; event?: string; [key: string]: AMCRequestBodyValue; } /** * AMC response base schema */ declare const baseSchema: z.ZodObject<{ status: z.ZodString; body: z.ZodOptional; message: z.ZodOptional; }>; /** * AMC response schema */ declare const amcResponseSchema: z.ZodType & Record>; /** * AMC response type */ type AMCResponse = z.infer; /** * Successful AMC response */ interface AMCSuccessResponse { status: "ok"; body: string; [key: string]: unknown; } /** * Type guard to check if AMC response is successful * @param response - AMC response * @returns true if response is successful */ declare function isSuccessResponse(response: AMCResponse): response is AMCSuccessResponse; /** * Base error class for the Wikidot library * All custom errors inherit from this class */ declare abstract class WikidotError extends Error { /** Error name */ readonly name: string; /** * @param message - Error message */ constructor(message: string); } /** * Unexpected error * Represents internal inconsistencies or bugs */ declare class UnexpectedError extends WikidotError {} /** * Base error for Ajax Module Connector related issues */ declare class AMCError extends WikidotError {} /** * HTTP status code error * Thrown when an AMC request fails with an HTTP error */ declare class AMCHttpError extends AMCError { /** HTTP status code */ readonly statusCode: number; /** * @param message - Error message * @param statusCode - HTTP status code */ constructor(message: string, statusCode: number); } /** * Wikidot status code error * Thrown when AMC response status is not ok */ declare class WikidotStatusError extends AMCError { /** Wikidot status code string */ readonly statusCode: string; /** Raw AMC response, when available (e.g. to inspect form_errors payloads) */ readonly response?: AMCResponse; /** * @param message - Error message * @param statusCode - Status code (e.g., 'not_ok', 'try_again') * @param response - Raw AMC response (optional, keeps existing call sites intact) */ constructor(message: string, statusCode: string, response?: AMCResponse); } /** * Form validation error * Thrown when AMC response status is 'form_errors' or 'form_error'. * Absorbs the payload key differences across modules (formErrors / errors / message) * behind a single `errors` accessor. */ declare class FormErrorsError extends WikidotStatusError { /** Field-name -> message record, regardless of the underlying payload key */ get errors(): Record; } /** * Response data error * Thrown when response parsing fails */ declare class ResponseDataError extends AMCError {} /** * Base error for session related issues */ declare class SessionError extends WikidotError {} /** * Session creation failure error * Thrown when a login attempt fails */ declare class SessionCreateError extends SessionError {} /** * Login required error * Thrown when an authenticated operation is attempted without login */ declare class LoginRequiredError extends SessionError { constructor(message?: string); } /** * Resource not found error * Thrown when the requested resource does not exist */ declare class NotFoundException extends WikidotError {} /** * Resource already exists error * Thrown when attempting to create a resource that already exists */ declare class TargetExistsError extends WikidotError {} /** * Target state error * Thrown when a resource is in an inoperable state (e.g., locked) */ declare class TargetError extends WikidotError {} /** * Access denied error * Thrown when an operation is denied due to insufficient permissions */ declare class ForbiddenError extends WikidotError {} /** * HTML element not found error * Thrown when a required element is not found during parsing */ declare class NoElementError extends WikidotError {} /** * Module providing logging functionality * * This module configures and provides loggers used throughout the library. * By default, it does not output anything, allowing application-side log control. */ /** * Log level */ type LogLevel = "debug" | "info" | "warn" | "error"; /** * Logger handler */ type LogHandler = (level: LogLevel, name: string, message: string, ...args: unknown[]) => void; /** * NullHandler: Outputs nothing (default) */ declare const nullHandler: LogHandler; /** * ConsoleHandler: Outputs to console */ declare const consoleHandler: LogHandler; /** * Logger class */ declare class Logger { private readonly name; private handler; private level; constructor(name: string, handler?: LogHandler, level?: LogLevel); /** * Set handler */ setHandler(handler: LogHandler): void; /** * Set log level */ setLevel(level: LogLevel): void; /** * Check if the specified level should be logged */ private shouldLog; /** * Output log */ private log; debug(message: string, ...args: unknown[]): void; info(message: string, ...args: unknown[]): void; warn(message: string, ...args: unknown[]): void; error(message: string, ...args: unknown[]): void; } /** * Get logger * @param name - Logger name (default: "wikidot") * @returns Logger instance */ declare function getLogger(name?: string): Logger; /** * Setup console output handler * @param logger - Logger to configure * @param level - Log level (default: "warn") */ declare function setupConsoleHandler(logger: Logger, level?: LogLevel): void; /** * Default logger used throughout the package */ declare const logger: Logger; /** DateTime type (ISO 8601 format) */ type DateTimeString = string; /** User ID or username */ type UserIdentifier = number | string; /** Site UNIX name */ type SiteUnixName = string; /** Page fullname (category:name) */ type PageFullname = string; import { Result, ResultAsync } from "neverthrow"; /** Synchronous Result type alias */ type WikidotResult = Result; /** Asynchronous Result type alias */ type WikidotResultAsync = ResultAsync; /** Create success Result */ declare const wdOk: (value: T) => WikidotResult; /** Create error Result */ declare const wdErr: (error: E) => WikidotResult; /** Create success ResultAsync */ declare const wdOkAsync: (value: T) => WikidotResultAsync; /** Create error ResultAsync */ declare const wdErrAsync: (error: E) => WikidotResultAsync; /** Convert from Promise */ declare const fromPromise: (promise: Promise, errorMapper: (error: unknown) => WikidotError) => WikidotResultAsync; /** Combine multiple ResultAsync */ declare const combineResults: (results: WikidotResultAsync[]) => WikidotResultAsync; /** * Helpers for building AMC request body fragments that match Wikidot's frontend * serialization rules (see `.local/memory/260728_wikidot-ajax-modules/10_transport.md` * in the sibling wikidot.py repo for the wire-format research this is based on). * * Wikidot's `OZONE.utils.formToArray` (and the hand-built request bodies that mirror it) * omit unchecked checkboxes/flags entirely instead of sending `false` — a naive * `{enabled: false}` body is silently read as "no change", not "turn this off". */ /** Value types accepted by {@link omitFalsy}: `false`/`null`/`undefined` mean "omit this key". */ type OmittableBodyValue = string | number | boolean | null | undefined; /** * Build a request-body fragment from key/value pairs, dropping any key whose value is * `false`, `null`, or `undefined` instead of serializing it. * @param input - Key/value pairs, where `false`/`null`/`undefined` mean "omit this key" * @returns A new object with omitted keys removed */ declare function omitFalsy>(input: T): Partial>; /** * Encode a checkbox-style boolean the way `OZONE.utils.formToArray` does: * checked -> `"on"`, unchecked -> omitted. * * The returned `undefined` is safe to spread directly into an `AMCRequestBody` (the AMC * client already skips `undefined` values when serializing), or to pass through * {@link omitFalsy} alongside other fields. * @param value - Checkbox state * @returns `"on"` if checked, `undefined` if not */ declare function checkbox(value: boolean | null | undefined): "on" | undefined; /** * Encode a flag-style boolean the way individually hand-built request bodies do * (`if (checked) { p.foo = true; }`): checked -> `"true"`, unchecked -> omitted. * @param value - Flag state * @returns `"true"` if set, `undefined` if not */ declare function flag(value: boolean | null | undefined): "true" | undefined; /** * JSON-encode a value for params that Wikidot expects as a JSON string * (e.g. `categories`, `options`, `addresses`). * @param value - Value to encode * @returns JSON string, or `undefined` for null/undefined so omitFalsy drops the key */ declare function jsonParam(value: unknown): string | undefined; /** * Ajax Module Connector configuration */ interface AMCConfig { /** Request timeout (milliseconds) */ timeout: number; /** Maximum retry count */ retryLimit: number; /** Base retry interval (milliseconds) */ retryInterval: number; /** Maximum backoff (milliseconds) */ maxBackoff: number; /** Backoff factor */ backoffFactor: number; /** Maximum concurrent requests */ semaphoreLimit: number; /** Batch size for amcRequestWithRetry */ retryBatchSize: number; /** Maximum retry attempts for amcRequestWithRetry */ retryMaxRetries: number; } /** Default AMC configuration */ declare const DEFAULT_AMC_CONFIG: AMCConfig; /** * Class for managing AMC request headers */ declare class AMCHeader { private cookies; private contentType; private userAgent; private referer; /** * @param options - Header initialization options */ constructor(options?: { contentType?: string; userAgent?: string; referer?: string; }); /** * Set a cookie * @param name - Cookie name * @param value - Cookie value */ setCookie(name: string, value: string): void; /** * Delete a cookie * @param name - Cookie name */ deleteCookie(name: string): void; /** * Get a cookie * @param name - Cookie name * @returns Cookie value, undefined if not exists */ getCookie(name: string): string | undefined; /** * Get HTTP headers object * @returns Headers dictionary */ getHeaders(): Record; } /** * Mask sensitive information (for logging) * @param body - Request body to mask * @returns Masked body */ declare function maskSensitiveData(body: AMCRequestBody): Record; /** * Ensure an AMC response carries a `body` field, throwing `ResponseDataError` if not. * * An unknown `moduleName` doesn't error at the AMC layer — Wikidot just returns * `{"status":"ok"}` with no `body` — so a typo in a module path would otherwise silently * produce an empty parse result instead of a clear failure. Callers that need `body` * should route through this instead of reading `response.body` directly. * @param response - AMC response (or undefined, e.g. a missing array element) * @param moduleName - Module name/path (or action/event) used in the request, for the error message * @returns The response body * @throws ResponseDataError if `response` or `response.body` is missing */ declare function requireBody(response: AMCResponse | undefined, moduleName: string): string; /** * AMC request options */ interface AMCRequestOptions { /** Site name (default: www) */ siteName?: string; /** SSL support (auto-detected if omitted) */ sslSupported?: boolean; /** Include errors in results instead of throwing (default: false) */ returnExceptions?: boolean; } /** * Ajax Module Connector client * Manages requests to Wikidot AMC endpoint */ declare class AMCClient { /** ky instance */ private readonly ky; /** Concurrent request limiter */ private readonly limit; /** Header manager */ readonly header: AMCHeader; /** Configuration */ readonly config: AMCConfig; /** Base domain */ readonly domain: string; /** SSL support status cache */ private sslCache; /** * @param config - AMC configuration (uses defaults if omitted) * @param domain - Base domain (default: wikidot.com) */ constructor(config?: Partial, domain?: string); /** * Check site existence and SSL support status * @param siteName - Site name * @returns SSL support status (true: HTTPS, false: HTTP) */ checkSiteSSL(siteName: string): WikidotResultAsync; /** * Execute AMC request * @param bodies - Request body array * @param siteName - Site name (default: www) * @param sslSupported - SSL support (auto-detected if omitted) * @returns Response array */ request(bodies: AMCRequestBody[], siteName?: string, sslSupported?: boolean): WikidotResultAsync; /** * Execute AMC request (with options) * @param bodies - Request body array * @param options - Request options * @returns Response array (includes errors if returnExceptions is true) */ requestWithOptions(bodies: AMCRequestBody[], options?: AMCRequestOptions): WikidotResultAsync<(AMCResponse | WikidotError)[]>; /** * Internal method to execute a single request * @param body - Request body * @param url - Request URL * @returns Response */ private singleRequest; /** * Upload a file to a page via the multipart upload endpoint * * UNVERIFIED AGAINST A LIVE WIKIDOT INSTANCE. This is a separate wire * path from `request()`: it posts multipart/form-data to * `/default--flow/files__UploadTarget` (not ajax-module-connector.php) * and gets back an HTML fragment, not the usual AMC JSON envelope, so it * cannot go through `request()`'s JSON parsing. The endpoint, parameters * (`action=FileAction`, `event=uploadFile`, `page_id`, * `source=multiflash`, `multikey?`, file under the `userfile` field), * and response shape were all determined by reading Wikidot's * client-side JS, not by observing a real upload -- see 30_plan.md D8 * and 32_tasks.md Task 3-5b in the sibling wikidot.py repo's memory * directory. Confirm against a real site before relying on this for * anything important. * @param options - pageId: target page. filename: file name as it will * appear on the page. content: file content. siteName: target site * (default: this client's configured site). siteSslSupported: whether * the site supports SSL. multikey: multi-file upload session key, * required by FileAction/multiUploadComplete when uploading more than * one file in the same batch * @returns Parsed response fields among "status" / "message" / "filename" */ uploadFile(options: { pageId: number; filename: string; content: Uint8Array | Blob; siteName?: string; siteSslSupported?: boolean; multikey?: string; }): WikidotResultAsync>; } /** * Client reference interface * Used to avoid direct dependency on Client type */ interface ClientRef { /** * Call before operations that require login * @returns Error if not logged in */ requireLogin(): { isErr(): boolean; error?: Error; }; /** * Whether logged in */ isLoggedIn(): boolean; } /** * Minimal AMCHeader interface */ interface AMCHeaderRef { getHeaders(): Record; setCookie(name: string, value: string): void; deleteCookie(name: string): void; } /** * Minimal AMCClient interface */ interface AMCClientRef { header: AMCHeaderRef; request(bodies: AMCRequestBody[]): WikidotResultAsync; } /** * Client context required for authentication * Used to avoid auth.ts directly depending on client.ts */ interface AuthClientContext { amcClient: AMCClientRef; } /** * Site reference interface * Used to avoid direct dependency on Site type */ interface SiteRef { /** * Site ID */ readonly id: number; /** * UNIX name (e.g., scp-jp) */ readonly unixName: string; /** * Domain */ readonly domain: string; /** * SSL support flag */ readonly sslSupported: boolean; /** * Client reference */ readonly client: ClientRef; /** * Execute AMC request */ amcRequest(bodies: AMCRequestBody[]): WikidotResultAsync; amcRequest(bodies: AMCRequestBody[], options: { returnExceptions: true; }): WikidotResultAsync<(AMCResponse | WikidotError)[]>; /** * Execute single AMC request */ amcRequestSingle(body: AMCRequestBody): WikidotResultAsync; /** * Execute AMC request with partial failure tolerance */ amcRequestWithRetry(bodies: AMCRequestBody[], options?: { batchSize?: number; maxRetries?: number; }): WikidotResultAsync<(AMCResponse | null)[]>; } /** * Forum category reference interface */ interface ForumCategoryRef { /** * Category ID */ readonly id: number; /** * Category title */ readonly title: string; /** * Site reference */ readonly site: SiteRef; } /** * Forum thread reference interface * Used to avoid direct dependency on ForumThread type */ interface ForumThreadRef { /** * Thread ID */ readonly id: number; /** * Thread title */ readonly title: string; /** * Site reference */ readonly site: SiteRef; /** * Category reference (if exists) */ readonly category?: ForumCategoryRef | null; } /** * Forum post reference interface * Used to avoid direct dependency on ForumPost type */ interface ForumPostRef { /** * Post ID */ readonly id: number; /** * Post title */ readonly title: string; /** * Thread reference */ readonly thread: ForumThreadRef; } /** * Page reference interface * Used to avoid direct dependency on Page type */ interface PageRef { /** * Page ID (set after retrieval) */ readonly id: number | null; /** * Fullname (category:name format) */ readonly fullname: string; /** * Page name */ readonly name: string; /** * Category */ readonly category: string; /** * Site reference */ readonly site: SiteRef; } /** * Login to Wikidot with username and password * @param client - Client context (object with AMCClient) * @param username - Username * @param password - Password * @returns void on success, SessionCreateError on failure */ declare function login(client: AuthClientContext, username: string, password: string): WikidotResultAsync; /** * Logout from Wikidot * @param client - Client context (object with AMCClient) * @returns void on success */ declare function logout(client: AuthClientContext): WikidotResultAsync; /** * User type */ type UserType = "user" | "deleted" | "anonymous" | "guest" | "wikidot"; /** * User base interface */ interface AbstractUser { /** Client */ readonly client: ClientRef; /** User ID */ readonly id: number; /** Username */ readonly name: string; /** UNIX format username */ readonly unixName: string | null; /** Avatar URL */ readonly avatarUrl: string | null; /** IP address (only for anonymous users) */ readonly ip: string | null; /** User type */ readonly userType: UserType; /** Check user type */ isUser(): boolean; isDeletedUser(): boolean; isAnonymousUser(): boolean; isGuestUser(): boolean; isWikidotUser(): boolean; } /** * User collection */ declare class UserCollection extends Array { constructor(users?: (User | null)[]); /** * Find by username * @param name - Username * @returns User or undefined */ findByName(name: string): User | undefined; /** * Find by user ID * @param id - User ID * @returns User or undefined */ findById(id: number): User | undefined; /** * Return only non-null users * @returns Array of non-null users */ filterNonNull(): User[]; } /** * Option keys accepted by userinfo/UserChangesListModule's `options` JSON. * Unlike the page-history version (history/PageHistoryModule), there is no "tags" key. */ declare const RECENT_CHANGES_OPTION_KEYS: readonly ["all", "source", "title", "move", "files", "new", "meta"]; /** Valid key in {@link RECENT_CHANGES_OPTION_KEYS} */ type RecentChangesOptionKey = (typeof RECENT_CHANGES_OPTION_KEYS)[number]; /** Filter flags for userinfo/UserChangesListModule */ type RecentChangesOptions = Partial>; /** Data backing a {@link UserChange} */ interface UserChangeData { client: Client; siteTitle: string; siteUrl: string; pageFullname: string; pageTitle: string; revisionNo: number; changedAt: Date; flags: string[]; } /** * A row of a user's recent page edits (userinfo/UserChangesListModule) * * Nearly identical in structure to SiteChange (page/site-change.ts's * changes/SiteChangesListModule row), with a site column added since this view * spans every site the user belongs to (measured 2026-07-29, see the sibling * wikidot.py repo's `.local/memory/260728_wikidot-ajax-modules/70_account.md`, * "一覧モジュールの行マークアップ"). */ declare class UserChange { readonly client: Client; /** Title of the site the change occurred on (td.site > a) */ readonly siteTitle: string; /** URL of the site the change occurred on (td.site > a href) */ readonly siteUrl: string; /** Page name, taken from the path of td.title > a href (e.g. "component:scp-173") */ readonly pageFullname: string; readonly pageTitle: string; /** Revision number (td.revision-no); 0 for a page-creation row shown as "(new)" */ readonly revisionNo: number; readonly changedAt: Date; /** "N"=new, "S"=source change, "T"=title change, "R"=rename, "M"=move, "F"=file, "A"=delete */ readonly flags: string[]; constructor(data: UserChangeData); toString(): string; } /** Data backing a {@link RecentPost} */ interface RecentPostData { client: Client; title: string; url: string; createdAt: Date; content: string; postId?: number | null; siteTitle?: string | null; siteUrl?: string | null; threadTitle?: string | null; threadUrl?: string | null; } /** * A row of a user's recent forum posts (userinfo/UserRecentPostsListModule) * * Each row is `div.post#post-`, with `div.long > div.head > div.title > a` * (title/link), `div.info > span.odate` (date), the text nodes "on site" / * "in discussion:" in `div.info` each followed by a link (site / thread), and * `div.content` (post text). Measured 2026-07-29 and 2026-09-28. */ declare class RecentPost { readonly client: Client; /** Post/thread title (div.title > a) */ readonly title: string; /** Link to the post (div.title > a href) */ readonly url: string; readonly createdAt: Date; /** Post text (div.content) */ readonly content: string; /** Post ID (from the row's `post-` id attribute) */ readonly postId: number | null; /** Title of the site the post belongs to (the link after "on site") */ readonly siteTitle: string | null; /** URL of the site the post belongs to */ readonly siteUrl: string | null; /** Title of the thread the post belongs to (the link after "in discussion:") */ readonly threadTitle: string | null; /** URL of the thread the post belongs to */ readonly threadUrl: string | null; constructor(data: RecentPostData); toString(): string; } /** Data backing a {@link UserProfile} */ interface UserProfileData { client: Client; fields: Record; realName: string | null; website: string | null; memberSince: Date | null; accountType: string | null; karmaLevel: string | null; } /** * The Profile tab of user:info (userinfo/UserInfoProfileModule) * * Built from the `dt`/`dd` pairs of `div.profile-box dl`. Which rows appear * varies by user, so every named field may be null; `fields` holds every row. */ declare class UserProfile { readonly client: Client; /** Every row, keyed by the dt text without its trailing ":" (value: whitespace-normalized dd text) */ readonly fields: Record; /** "Real name" row */ readonly realName: string | null; /** "Website" row (the link's href, or the text if there is no link) */ readonly website: string | null; /** "Wikidot user since" row */ readonly memberSince: Date | null; /** "Account type" row (e.g. "free") */ readonly accountType: string | null; /** First word of the "Karma level" row (e.g. "high") */ readonly karmaLevel: string | null; constructor(data: UserProfileData); toString(): string; } /** Data backing a {@link UserSiteEntry} */ interface UserSiteEntryData { client: Client; title: string; url: string; subtitle: string | null; description: string | null; } /** * A site listed on the Member of / Admin of / Moderator of tabs of user:info * (`dl > dt > a` for the site, `dt > small > em` for the subtitle, and the * following `dd > small` for the description) */ declare class UserSiteEntry { readonly client: Client; /** Site name (dt > a) */ readonly title: string; /** Site URL (dt > a href) */ readonly url: string; /** Site subtitle (dt > small > em) */ readonly subtitle: string | null; /** Site description (dd > small); null when missing or empty */ readonly description: string | null; constructor(data: UserSiteEntryData); toString(): string; } /** * User data */ interface UserData { id: number; name: string; displayName?: string | null; avatarUrl?: string | null; unixName?: string; } /** * Regular user */ declare class User implements AbstractUser { readonly client: ClientRef; /** User ID */ readonly id: number; /** Username */ readonly name: string; /** Display name */ readonly displayName: string | null; /** Avatar URL */ readonly avatarUrl: string | null; /** UNIX format username */ readonly unixName: string; /** IP address (null for regular users) */ readonly ip: string | null; /** User type */ readonly userType: UserType; constructor(client: ClientRef, data: UserData); /** * Get user from username * @param client - Client * @param name - Username * @returns User (null if not found) */ static fromName(client: ClientRef, name: string): WikidotResultAsync; /** * Get users from multiple usernames * @param client - Client * @param names - Array of usernames * @returns User collection */ static fromNames(client: ClientRef, names: string[]): WikidotResultAsync; /** * Run a user:info fetch, passing WikidotErrors through and wrapping anything else */ private fetchUserInfo; /** * Get this user's recent page edits, across all sites (the "Recent contributions" tab). * * Wraps userinfo/UserChangesListModule, fetching pages until exhausted or * `limit` is reached. * @param options - Filter flags. Keys must be a subset of RECENT_CHANGES_OPTION_KEYS; unlike * history/PageHistoryModule's options, there is no "tags" key here * @param limit - Maximum number of entries to retrieve. If omitted, retrieves all * @returns List of change history (in descending order by date) */ getChanges(options?: RecentChangesOptions, limit?: number): WikidotResultAsync; /** * Get this user's recent forum posts, across all sites (the "Recent posts and comments" tab). * * Wraps userinfo/UserRecentPostsListModule (20 posts per page), fetching pages * until exhausted or `limit` is reached. * @param limit - Maximum number of entries to retrieve. If omitted, retrieves all * @returns List of recent posts (in descending order by date) */ getPosts(limit?: number): WikidotResultAsync; /** * Get this user's profile (the "Profile" tab, userinfo/UserInfoProfileModule) * @returns The profile */ getProfile(): WikidotResultAsync; /** * Get the sites this user is a member of (the "Member of" tab) * @returns Listed sites (empty if none) */ getMemberOf(): WikidotResultAsync; /** * Get the sites this user administers (the "Admin of" tab) * @returns Listed sites (empty if none) */ getAdminOf(): WikidotResultAsync; /** * Get the sites this user moderates (the "Moderator of" tab) * @returns Listed sites (empty if none) */ getModeratorOf(): WikidotResultAsync; private fetchSites; isUser(): boolean; isDeletedUser(): boolean; isAnonymousUser(): boolean; isGuestUser(): boolean; isWikidotUser(): boolean; toString(): string; } /** * Anonymous user */ declare class AnonymousUser implements AbstractUser { readonly client: ClientRef; /** User ID (0 for anonymous) */ readonly id: number; /** Username ("Anonymous" for anonymous users) */ readonly name: string; /** UNIX format username */ readonly unixName: string; /** Avatar URL (null for anonymous) */ readonly avatarUrl: string | null; /** IP address */ readonly ip: string; /** User type */ readonly userType: UserType; constructor(client: ClientRef, ip: string); isUser(): boolean; isDeletedUser(): boolean; isAnonymousUser(): boolean; isGuestUser(): boolean; isWikidotUser(): boolean; toString(): string; } /** * Deleted user */ declare class DeletedUser implements AbstractUser { readonly client: ClientRef; /** Deleted user ID */ readonly id: number; /** Username ("account deleted" for deleted users) */ readonly name: string; /** UNIX format username */ readonly unixName: string; /** Avatar URL (null for deleted) */ readonly avatarUrl: string | null; /** IP address (null for deleted) */ readonly ip: string | null; /** User type */ readonly userType: UserType; constructor(client: ClientRef, id: number); isUser(): boolean; isDeletedUser(): boolean; isAnonymousUser(): boolean; isGuestUser(): boolean; isWikidotUser(): boolean; toString(): string; } /** * Guest user */ declare class GuestUser implements AbstractUser { readonly client: ClientRef; /** User ID (0 for guest) */ readonly id: number; /** Guest name */ readonly name: string; /** UNIX format username (null for guest) */ readonly unixName: string | null; /** Avatar URL (may be Gravatar) */ readonly avatarUrl: string | null; /** IP address (null for guest) */ readonly ip: string | null; /** User type */ readonly userType: UserType; constructor(client: ClientRef, name: string, avatarUrl?: string | null); isUser(): boolean; isDeletedUser(): boolean; isAnonymousUser(): boolean; isGuestUser(): boolean; isWikidotUser(): boolean; toString(): string; } /** * Wikidot system user */ declare class WikidotUser implements AbstractUser { readonly client: ClientRef; /** User ID (0 for Wikidot system) */ readonly id: number; /** Username */ readonly name: string; /** UNIX format username */ readonly unixName: string; /** Avatar URL (null for system user) */ readonly avatarUrl: string | null; /** IP address (null for system user) */ readonly ip: string | null; /** User type */ readonly userType: UserType; constructor(client: ClientRef); isUser(): boolean; isDeletedUser(): boolean; isAnonymousUser(): boolean; isGuestUser(): boolean; isWikidotUser(): boolean; toString(): string; } /** * Private message data */ interface PrivateMessageData { client: Client; id: number; sender: AbstractUser; recipient: AbstractUser; subject: string; body: string; createdAt: Date; } /** * Private message */ declare class PrivateMessage { readonly client: Client; readonly id: number; readonly sender: AbstractUser; readonly recipient: AbstractUser; readonly subject: string; readonly body: string; readonly createdAt: Date; constructor(data: PrivateMessageData); /** * Get message by message ID * @param client - Client instance * @param messageId - Message ID * @returns Private message */ static fromId(client: Client, messageId: number): WikidotResultAsync; /** * Send private message * @param client - Client instance * @param recipient - Recipient * @param subject - Subject * @param body - Body */ static send(client: Client, recipient: User, subject: string, body: string): WikidotResultAsync; /** * Save a private message draft * @param client - Client instance * @param subject - Draft subject * @param body - Draft body * @param recipient - Intended recipient. May be omitted (the real form allows a draft with no recipient selected yet) */ static saveDraft(client: Client, subject: string, body: string, recipient?: User): WikidotResultAsync; /** * Check whether the account is allowed to send a private message to a user. * * Wraps DashboardMessageAction/checkCan. Does not return a boolean: only the * generic no_permission rejection path was confirmed during the investigation, * so other rejection reasons (e.g. the recipient's receive-messages setting) may * surface as a plain WikidotStatusError instead of ForbiddenError. Treat a * successful (isOk) result as "allowed". * @param client - Client instance * @param user - Prospective recipient */ static checkCanSend(client: Client, user: AbstractUser): WikidotResultAsync; /** * Render a preview of a private message without sending it. Wraps the * dashboard/messages/DMPreviewModule module. * @param client - Client instance * @param subject - Message subject * @param body - Message body * @param recipient - Intended recipient * @returns Rendered HTML preview */ static preview(client: Client, subject: string, body: string, recipient?: User): WikidotResultAsync; /** * Fetch the pre-filled "new message" form HTML for replying to a message. * * Wraps the dashboard/messages/DMNewMessageModule module with replyMessageId * set, which renders the form with the original sender pre-filled as recipient * (toUserId/toUserName in the rendered HTML). Returns the raw body since the * investigation captured the request/response shape but not the specific * markup used to extract those pre-filled values. * @param client - Client instance * @param replyMessageId - ID of the message being replied to * @returns Raw rendered HTML body */ static fetchReplyFormHtml(client: Client, replyMessageId: number): WikidotResultAsync; /** * Mark this message as read */ markAsRead(): WikidotResultAsync; /** * Mark this message as unread */ markAsUnread(): WikidotResultAsync; /** * Delete this message */ delete(): WikidotResultAsync; toString(): string; } /** * A message that could not be fetched, with the ID it was requested by */ interface PrivateMessageFetchFailure { /** Requested message ID */ id: number; /** The error that made this message unfetchable (transport or parse failure) */ error: WikidotError; } /** * Private message collection */ declare class PrivateMessageCollection extends Array { readonly client: Client; /** * Messages that could not be fetched when this collection was built. * A single unfetchable message no longer fails the whole fetch: it is * skipped and reported here instead, so callers processing the successful * messages must check this to know the fetch was partial. */ readonly failures: PrivateMessageFetchFailure[]; constructor(client: Client, messages?: PrivateMessage[], failures?: PrivateMessageFetchFailure[]); /** * Find by ID */ findById(id: number): PrivateMessage | undefined; /** * Get messages from list of message IDs. * * Partial-success contract: a message that fails to fetch (per-request * transport error, missing body, or unparseable markup) is skipped and * reported in the returned collection's `failures`, in request order, so * one broken message cannot block the rest. Successful messages keep the * input ID order. If every message fails, the result is still Ok with an * empty collection and all IDs in `failures`. Only systemic failures * (login missing, SSL/site resolution) return Err. */ static fromIds(client: Client, messageIds: number[]): WikidotResultAsync; /** * Internal method to get messages from module */ protected static acquireFromModule(client: Client, moduleName: string): WikidotResultAsync; /** * Mark messages as read. * * Wraps DashboardMessageAction/setAsReaded (the misspelling is Wikidot's own * event name on the wire and is intentionally kept as-is here; only this method * name uses correct spelling). * @param client - Client instance * @param messageIds - IDs of the messages to mark as read */ static markAsRead(client: Client, messageIds: number[]): WikidotResultAsync; /** * Mark messages as unread. Wraps DashboardMessageAction/setAsUnreaded. * @param client - Client instance * @param messageIds - IDs of the messages to mark as unread */ static markAsUnread(client: Client, messageIds: number[]): WikidotResultAsync; /** * Delete messages. Wraps DashboardMessageAction/removeMessages. * @param client - Client instance * @param messageIds - IDs of the messages to delete */ static removeMessages(client: Client, messageIds: number[]): WikidotResultAsync; } /** * Inbox */ declare class PrivateMessageInbox extends PrivateMessageCollection { /** * Get all messages in inbox */ static acquire(client: Client): WikidotResultAsync; } /** * Sent box */ declare class PrivateMessageSentBox extends PrivateMessageCollection { /** * Get all messages in sent box */ static acquire(client: Client): WikidotResultAsync; } /** * Fetch a page of the account's pending site invitations (raw HTML). * Wraps dashboard/messages/DMInvitationsModule. * @param client - Client instance * @param page - Page number * @returns Raw rendered HTML body */ declare function getInvitationsHtml2(client: Client, page?: number): WikidotResultAsync; /** * Fetch the detail HTML of a single site invitation. * Wraps dashboard/messages/DMViewInvitationModule. * @param client - Client instance * @param item - Invitation ID * @returns Raw rendered HTML body */ declare function getInvitationDetailHtml2(client: Client, item: number): WikidotResultAsync; /** Data backing a {@link SiteJoinApplication} */ interface SiteJoinApplicationData { client: Client; itemId: number; fromSite: string; subject: string; preview: string; submittedAt: Date; } /** * A row of the account's own outgoing site-join applications * (dashboard/messages/DMApplicationsModule) * * Distinct from SiteApplication (site-application.ts), which is a site admin's * view of incoming applications to their own site. * * Row markup was measured 2026-07-29 (see the sibling wikidot.py repo's * `.local/memory/260728_wikidot-ajax-modules/70_account.md`, "一覧モジュールの * 行マークアップ" / "行の属性"): each row is `tr.message` with * `data-href="#/applications/"` (the same pattern as the inbox/sent * `tr.message[data-href]`), plus `span.from` / `span.subject` / * `span.preview` / `span.date > span.odate`. The row does not carry a siteId, * so this object cannot drive `DashboardSitesAction/removeApplication` (which * takes a siteId, not this itemId) directly; use * `SiteAccessor.removeApplication(siteId)` with a siteId obtained elsewhere if * you need to withdraw an application. Extracting a siteId from * `DMViewApplicationModule`'s response was not attempted since that * response's structure is unmeasured. */ declare class SiteJoinApplication { readonly client: Client; /** Application ID (tail of tr.message's data-href), usable as the `item` parameter of DMViewApplicationModule */ readonly itemId: number; /** Text of span.from (the site the application was submitted to) */ readonly fromSite: string; /** Text of span.subject */ readonly subject: string; /** Text of span.preview */ readonly preview: string; /** Submission date and time (span.date > span.odate) */ readonly submittedAt: Date; constructor(data: SiteJoinApplicationData); /** * Fetch the detail HTML of this application (DMViewApplicationModule) */ fetchDetailHtml(): WikidotResultAsync; toString(): string; } /** * Get all of the account's pending outgoing site join applications. * * Wraps dashboard/messages/DMApplicationsModule, fetching all pages. * @param client - Client instance * @returns All pending applications */ declare function getApplications2(client: Client): WikidotResultAsync; /** * Fetch the detail HTML of a single site join application. * Wraps dashboard/messages/DMViewApplicationModule. * @param client - Client instance * @param item - Application ID * @returns Raw rendered HTML body */ declare function getApplicationDetailHtml2(client: Client, item: number): WikidotResultAsync; /** Data backing a {@link Contact} */ interface ContactData { client: Client; user: AbstractUser; } /** * A row of the account's contact list (dashboard/messages/DMContactsModule) * * Row markup was measured 2026-07-29 (see the sibling wikidot.py repo's * `.local/memory/260728_wikidot-ajax-modules/70_account.md`, "一覧モジュールの * 行マークアップ"): each row is a `tr` with * `td > span.printuser.avatarhover > a > img.small` and a delete button * (`td > a.awesome.red.small`). The user is parsed via the existing printuser * parser rather than the delete button, since removal only needs the user ID * (ContactsAction/removeContact), which the printuser element already carries. */ declare class Contact { readonly client: Client; /** The contact */ readonly user: AbstractUser; constructor(data: ContactData); toString(): string; /** * Remove this user from the account's contact list */ remove(): WikidotResultAsync; } /** * Get the account's contact list. * * Wraps dashboard/messages/DMContactsModule (single request; this module is not * paginated). * @param client - Client instance * @returns All contacts */ declare function getContacts2(client: Client): WikidotResultAsync; /** * Fetch the contact picker used when composing a new message (raw HTML). * Wraps dashboard/messages/DMContactsListModule. * @param client - Client instance * @returns Raw rendered HTML body */ declare function getContactsListHtml2(client: Client): WikidotResultAsync; /** * Add a user to the account's contact list. Wraps ContactsAction/addContact. * @param client - Client instance * @param user - User to add */ declare function addContact2(client: Client, user: AbstractUser): WikidotResultAsync; /** * Remove a user from the account's contact list. Wraps ContactsAction/removeContact. * @param client - Client instance * @param user - User to remove */ declare function removeContact2(client: Client, user: AbstractUser): WikidotResultAsync; /** * Add a user to the account's contact list from their profile page. * * Wraps userinfo/UserAddToContactsModule, a second (module-render-as-action) path * to add a contact distinct from ContactsAction/addContact, used from a user's * profile ("user:info") page rather than the messages dashboard. * @param client - Client instance * @param user - User to add * @returns Raw rendered HTML body */ declare function addContactViaProfile2(client: Client, user: AbstractUser): WikidotResultAsync; /** * Private message operations accessor * * @example * ```typescript * // Get inbox * const inboxResult = await client.privateMessage.inbox(); * if (!inboxResult.isOk()) { * throw new Error('Failed to get inbox'); * } * const inbox = inboxResult.value; * ``` */ declare class PrivateMessageAccessor { readonly client: Client; constructor(client: Client); /** * Get message by message ID * * @param id - Message ID * @returns Message object wrapped in Result type */ get(id: number): WikidotResultAsync; /** * Get messages from multiple message IDs * @param ids - Array of message IDs * @returns Message collection */ getMessages(ids: number[]): WikidotResultAsync; /** * Get inbox message list * @returns Inbox */ inbox(): WikidotResultAsync; /** * Get sent box message list * @returns Sent box */ sentBox(): WikidotResultAsync; /** * Send a private message * @param recipient - Recipient * @param subject - Subject * @param body - Body */ send(recipient: User, subject: string, body: string): WikidotResultAsync; /** * Mark messages as read * @param messageIds - IDs of the messages to mark as read */ markAsRead(messageIds: number[]): WikidotResultAsync; /** * Mark messages as unread * @param messageIds - IDs of the messages to mark as unread */ markAsUnread(messageIds: number[]): WikidotResultAsync; /** * Delete messages * @param messageIds - IDs of the messages to delete */ removeMessages(messageIds: number[]): WikidotResultAsync; /** * Save a private message draft * @param subject - Draft subject * @param body - Draft body * @param recipient - Intended recipient */ saveDraft(subject: string, body: string, recipient?: User): WikidotResultAsync; /** * Check whether the account is allowed to message a user * @param user - Prospective recipient */ checkCanSend(user: AbstractUser): WikidotResultAsync; /** * Render a preview of a private message without sending it * @param subject - Message subject * @param body - Message body * @param recipient - Intended recipient * @returns Rendered HTML preview */ preview(subject: string, body: string, recipient?: User): WikidotResultAsync; /** * Fetch the pre-filled "new message" form HTML for replying to a message * @param replyMessageId - ID of the message being replied to * @returns Raw rendered HTML body */ fetchReplyFormHtml(replyMessageId: number): WikidotResultAsync; /** * Fetch a page of the account's pending site invitations (raw HTML) * @param page - Page number */ getInvitationsHtml(page?: number): WikidotResultAsync; /** * Fetch the detail HTML of a single site invitation * @param item - Invitation ID */ getInvitationDetailHtml(item: number): WikidotResultAsync; /** * Get all of the account's pending outgoing site join applications */ getApplications(): WikidotResultAsync; /** * Fetch the detail HTML of a single site join application * @param item - Application ID */ getApplicationDetailHtml(item: number): WikidotResultAsync; /** * Get the account's contact list */ getContacts(): WikidotResultAsync; /** * Fetch the contact picker used when composing a new message (raw HTML) */ getContactsListHtml(): WikidotResultAsync; /** * Add a user to the account's contact list * @param user - User to add */ addContact(user: AbstractUser): WikidotResultAsync; /** * Remove a user from the account's contact list * @param user - User to remove */ removeContact(user: AbstractUser): WikidotResultAsync; /** * Add a user to the account's contact list from their profile page * @param user - User to add * @returns Raw rendered HTML body */ addContactViaProfile(user: AbstractUser): WikidotResultAsync; } /** `template` values accepted by NewSiteAction/createSite (form(new-site-form)) */ type NewSiteTemplate = "standard-template" | "blog-template" | "blank-template" | "default-template" | "notebooks"; /** `privacy` values accepted by NewSiteAction/createSite (form(new-site-form)) */ type NewSitePrivacy = "open" | "closed" | "private"; /** Options accepted by DashboardSites.create */ interface CreateSiteOptions { /** Site title */ name: string; /** Site UNIX name (used in the domain, e.g. "foo" -> foo.wikidot.com) */ unixname: string; /** Site subtitle */ subtitle?: string; /** Site language code */ language?: string; /** Starting content template */ template?: NewSiteTemplate; /** Site visibility */ privacy?: NewSitePrivacy; /** * Whether to accept the Terms of Service. The real form requires this checked to * submit; passing false reproduces the unchecked (omitted) wire state */ tos?: boolean; } /** Data backing a {@link DashboardSite} */ interface DashboardSiteData { client: Client; siteId: number; title: string; url: string; unixName: string; tagline: string; activity: string; role: string; deleted: boolean; } /** * A row of the account's site dashboard listing (dashboard/sites/DSListModule) * * Represents the current account's relationship to one site (any role, or a site * it once belonged to but is now deleted). Distinct from `Site`, which represents * a site's own state independent of any particular account. * * Row markup was measured 2026-07-29 (see the sibling wikidot.py repo's * `.local/memory/260728_wikidot-ajax-modules/70_account.md`, "一覧モジュールの * 行マークアップ"): each row is `div.site`, with `div.name > a` (title), * `div.url` (site URL), and a `div.data` block holding `span.activity`, * `span.site-id`, `span.unix-name`, `span.tagline`, `span.deleted`, * `span.occupation`. The measurement captured the DOM skeleton but not the exact * value encoding of every field, so `activity` and `role` are kept as the raw * observed text rather than a guessed enum/unit. */ declare class DashboardSite { readonly client: Client; /** Site ID (span.site-id) */ readonly siteId: number; /** Site title (text of div.name > a) */ readonly title: string; /** Site URL (text of div.url) */ readonly url: string; /** Site UNIX name (span.unix-name) */ readonly unixName: string; /** Site tagline/subtitle (span.tagline) */ readonly tagline: string; /** Raw text of span.activity; exact meaning/unit was not confirmed */ readonly activity: string; /** * Raw text of span.occupation. Observed values are expected to align with the * hash-tab identifiers used elsewhere on this page ("master_admin" / "admin" / * "moderator" / "member"), but this correspondence was not independently * confirmed */ readonly role: string; /** Whether span.deleted is present in this row's div.data block */ readonly deleted: boolean; constructor(data: DashboardSiteData); /** * Restore this site (must currently be deleted) * @param confirmSiteName - Site name, required as a typed confirmation */ restore(confirmSiteName: string): WikidotResultAsync; /** * Resign the account's admin role on this site */ resignAsAdmin(): WikidotResultAsync; /** * Resign the account's moderator role on this site */ resignAsModerator(): WikidotResultAsync; /** * Leave this site (account must be a plain member) */ signOffAsMember(): WikidotResultAsync; /** * Set this site's file storage limit. Unmeasured: see * DashboardSites.setStorageLimit for details * @param rawFields - Raw form fields to send as-is */ setStorageLimit(rawFields: Record): WikidotResultAsync; toString(): string; /** * Retrieve every site the account belongs to (all roles) plus deleted sites * * Wraps dashboard/sites/DSListModule, which renders the full list in one * response (the real UI filters by role/deleted client-side via DOM attributes * rather than separate requests). * @param client - Client instance * @returns All rows of the account's site dashboard */ static acquireAll(client: Client): WikidotResultAsync; } /** * Static namespace grouping the account's site-dashboard operations * (DashboardSitesAction / NewSiteAction / dashboard/sites/*). * * Access through `client.site` (create/listHtml/acceptInvitation/...) rather than * calling these directly. */ declare const DashboardSites: { /** * Create a new site (form(new-site-form) -> NewSiteAction/createSite) * @param client - Client instance * @param options - New site options * @returns UNIX name of the created site (the "siteUnixName" response field) */ create(client: Client, options: CreateSiteOptions): WikidotResultAsync; /** * Retrieve every site the account belongs to (all roles) plus deleted sites * @param client - Client instance * @returns All rows of the account's site dashboard */ listSites(client: Client): WikidotResultAsync; /** * Accept a pending site invitation * @param client - Client instance * @param invitationId - Invitation ID (as listed by dashboard/messages/DMInvitationsModule) */ acceptInvitation(client: Client, invitationId: number): WikidotResultAsync; /** * Discard a pending site invitation without accepting it * @param client - Client instance * @param invitationId - Invitation ID */ throwAwayInvitation(client: Client, invitationId: number): WikidotResultAsync; /** * Withdraw a pending membership application the account submitted to a site * @param client - Client instance * @param siteId - ID of the site the application was submitted to */ removeApplication(client: Client, siteId: number): WikidotResultAsync; /** * Restore a deleted site the account administers * @param client - Client instance * @param siteId - ID of the deleted site * @param confirmSiteName - Site name, required by form(ds-restore-site-form) as a typed confirmation * (mirrors the real UI's "type the site name" guard) */ restoreSite(client: Client, siteId: number, confirmSiteName: string): WikidotResultAsync; /** * Resign the account's admin role on a site (form(ds-admin-resign-form)) * @param client - Client instance * @param siteId - Site ID */ resignAsAdmin(client: Client, siteId: number): WikidotResultAsync; /** * Resign the account's moderator role on a site (form(ds-moderator-resign-form)) * @param client - Client instance * @param siteId - Site ID */ resignAsModerator(client: Client, siteId: number): WikidotResultAsync; /** * Leave a site the account is a plain member of (form(ds-member-signoff-form)) * @param client - Client instance * @param siteId - Site ID */ signOffAsMember(client: Client, siteId: number): WikidotResultAsync; /** * Set a site's file storage limit (form(limit-site-)). * * Unmeasured: dashboard/sites/DSListModule did not render this form for the * investigation account (no Pro site available), so the field names of * limit-site- could not be captured. Pass the exact field names/values as * sent by the real form. * @param client - Client instance * @param siteId - Site ID * @param rawFields - Raw form fields to send as-is */ setStorageLimit(client: Client, siteId: number, rawFields: Record): WikidotResultAsync; }; import { Element } from "domhandler"; /** * Forum post revision data */ interface ForumPostRevisionData { post: ForumPostRef; id: number; revNo: number; createdBy: AbstractUser; createdAt: Date; } /** * Forum post revision (version in edit history) */ declare class ForumPostRevision { /** Post this revision belongs to */ readonly post: ForumPostRef; /** Revision ID */ readonly id: number; /** Revision number (0 = initial version) */ readonly revNo: number; /** Revision creator */ readonly createdBy: AbstractUser; /** Revision creation date */ readonly createdAt: Date; /** HTML content (internal cache) */ private _html; constructor(data: ForumPostRevisionData); /** * Whether HTML content has been acquired */ isHtmlAcquired(): boolean; /** * Get HTML content (cached) */ get html(): string | null; /** * Set HTML content */ set html(value: string | null); /** * Get revision HTML content * @returns HTML string */ getHtml(): WikidotResultAsync; toString(): string; } /** * Forum post revision collection */ declare class ForumPostRevisionCollection extends Array { readonly post: ForumPostRef; constructor(post: ForumPostRef, revisions?: ForumPostRevision[]); /** * Find by ID * @param id - Revision ID * @returns Revision (undefined if not found) */ findById(id: number): ForumPostRevision | undefined; /** * Find by revision number * @param revNo - Revision number * @returns Revision (undefined if not found) */ findByRevNo(revNo: number): ForumPostRevision | undefined; /** * Get HTML for all revisions * @returns Array of HTML strings */ getHtmls(): WikidotResultAsync; /** * Parse revisions from HTML (internal method) */ private static _parse; /** * Get all revisions for a post * @param post - Forum post reference * @returns Revision collection */ static acquireAll(post: ForumPostRef): WikidotResultAsync; /** * Get revisions for multiple posts * @param posts - Array of forum post references * @param withHtml - Whether to also fetch HTML content for each revision * @returns Map of post ID to revision collection */ static acquireAllForPosts(posts: ForumPostRef[], withHtml?: boolean): WikidotResultAsync>; } /** * Forum post data */ interface ForumPostData { thread: ForumThreadRef; id: number; title: string; text: string; element: Element; createdBy: AbstractUser; createdAt: Date; editedBy?: AbstractUser | null; editedAt?: Date | null; parentId?: number | null; } /** * Forum post */ declare class ForumPost { readonly thread: ForumThreadRef; readonly id: number; title: string; readonly text: string; readonly element: Element; readonly createdBy: AbstractUser; readonly createdAt: Date; readonly editedBy: AbstractUser | null; readonly editedAt: Date | null; private _parentId; private _source; private _revisions; constructor(data: ForumPostData); /** * Parent post ID */ get parentId(): number | null; /** * Get source code (cached) */ get source(): string | null; /** * Set source code */ set source(value: string | null); /** * Get source code (Wikidot syntax) */ getSource(): WikidotResultAsync; /** * Edit post * @param source - New source (Wikidot syntax) * @param title - New title (keeps current title if omitted) */ edit(source: string, title?: string): WikidotResultAsync; /** * Delete the post. Destructive and irreversible. * @param confirm - Must be explicitly true to proceed (safety gate for a * destructive operation) * @throws {Error} If confirm is not true */ delete(confirm: boolean): WikidotResultAsync; /** * Whether the post has been edited (has revisions) */ get hasRevisions(): boolean; /** * Get post revisions (edit history) * @returns Revision collection */ getRevisions(): WikidotResultAsync; toString(): string; } /** * Forum post collection */ declare class ForumPostCollection extends Array { readonly thread: ForumThreadRef; constructor(thread: ForumThreadRef, posts?: ForumPost[]); /** * Find by ID * @param id - Post ID * @returns Post (undefined if not found) */ findById(id: number): ForumPost | undefined; /** * Parse posts from HTML (internal method) */ private static _parse; /** * Get all posts in a thread */ static acquireAllInThread(thread: ForumThreadRef): WikidotResultAsync; /** * Get all posts from multiple threads */ static acquireAllInThreads(threads: ForumThreadRef[]): WikidotResultAsync>; /** * Internal method to acquire post sources in bulk */ static acquirePostSources(thread: ForumThreadRef, posts: ForumPost[]): WikidotResultAsync; /** * Get sources for all posts in the collection * @returns Result containing the collection (for method chaining) */ getPostSources(): WikidotResultAsync; } /** * Forum thread data */ interface ForumThreadData { site: Site; id: number; title: string; description: string; createdBy: AbstractUser | null; createdAt: Date; postCount: number; category?: ForumCategory | null; } /** * Forum thread */ declare class ForumThread { readonly site: Site; readonly id: number; title: string; description: string; readonly createdBy: AbstractUser | null; readonly createdAt: Date; postCount: number; category: ForumCategory | null; private _posts; constructor(data: ForumThreadData); /** * Get thread URL */ getUrl(): string; /** * Get post list */ getPosts(): WikidotResultAsync; /** * Reply to thread */ reply(source: string, title?: string, parentPostId?: number | null): WikidotResultAsync; /** * Update the thread's title and/or description. * * Sends both fields on every call (`ForumAction/saveThreadMeta` resubmits * the whole `thread-meta-form`, it does not patch a single field), so * omitted arguments default to the thread's current locally-known * title/description instead of blanking them. * @param title - New title. Keeps the current title if omitted * @param description - New description (1000 character limit). Keeps the * current description if omitted */ saveMeta(title?: string, description?: string): WikidotResultAsync; /** * Pin or unpin the thread within its category * @param sticky - true to pin, false to unpin */ setSticky(sticky: boolean): WikidotResultAsync; /** * Lock or unlock the thread (locked threads reject new posts) * @param block - true to lock, false to unlock */ setBlock(block: boolean): WikidotResultAsync; /** * Move the thread to a different forum category * @param category - Destination category */ move(category: ForumCategory): WikidotResultAsync; /** * Start watching the thread (email notification on new posts) */ watch(): WikidotResultAsync; toString(): string; /** * Create a page's discussion (comment) thread if it does not already have one. * @param site - Site the page belongs to * @param pageId - Numeric page ID (not the page's unix name) * @returns The created thread, or `null` if the response carried no numeric * `thread_id` (the key observed on pseudo-scp-jp, 2026-10-08) */ static createForPage(site: Site, pageId: number): WikidotResultAsync; /** * Get thread by ID */ static getFromId(site: Site, threadId: number, category?: ForumCategory | null): WikidotResultAsync; } /** * Forum thread collection */ declare class ForumThreadCollection extends Array { readonly site: Site; constructor(site: Site, threads?: ForumThread[]); /** * Find by ID */ findById(id: number): ForumThread | undefined; /** * Get all threads in category */ static acquireAllInCategory(category: ForumCategory): WikidotResultAsync; /** * Get a single thread by thread ID * @param site - Site instance * @param threadId - Thread ID */ static fromId(site: Site, threadId: number): WikidotResultAsync; /** * Get threads by thread IDs */ static acquireFromThreadIds(site: Site, threadIds: number[], category?: ForumCategory | null): WikidotResultAsync; } /** * Forum category data */ interface ForumCategoryData { site: Site; id: number; title: string; description: string; threadsCount: number; postsCount: number; } /** * Forum category */ declare class ForumCategory { readonly site: Site; readonly id: number; readonly title: string; readonly description: string; readonly threadsCount: number; readonly postsCount: number; private _threads; constructor(data: ForumCategoryData); /** * Get thread list */ getThreads(): WikidotResultAsync; /** * Reload thread list */ reloadThreads(): WikidotResultAsync; /** * Create thread */ createThread(title: string, description: string, source: string): WikidotResultAsync; /** * Render a preview of a new thread without creating it. * * Mirrors the fields `createThread` submits (`new-thread-form`), so a * preview and a subsequent `createThread` call with the same arguments * render consistently. * @param title - Thread title * @param description - Thread description * @param source - Thread body (Wikidot syntax) * @returns Rendered preview HTML */ previewThread(title: string, description: string, source: string): WikidotResultAsync; toString(): string; } /** * Forum category collection */ declare class ForumCategoryCollection extends Array { readonly site: Site; constructor(site: Site, categories?: ForumCategory[]); /** * Find by ID */ findById(id: number): ForumCategory | undefined; /** * Get all categories for a site */ static acquireAll(site: Site): WikidotResultAsync; } /** * QuickModule module name */ type QuickModuleName = "MemberLookupQModule" | "UserLookupQModule" | "PageLookupQModule"; /** * QuickModule user information */ interface QMCUser { id: number; name: string; } /** * QuickModule page information */ interface QMCPage { title: string; unixName: string; } /** * Search site members * @param siteId - Site ID * @param query - Search query (partial username) * @returns List of matching users */ declare function memberLookup(siteId: number, query: string): WikidotResultAsync; /** * Search users across all Wikidot * @param siteId - Site ID (any site ID works) * @param query - Search query (partial username) * @returns List of matching users */ declare function userLookup(siteId: number, query: string): WikidotResultAsync; /** * Search pages within site * @param siteId - Site ID * @param query - Search query (partial page name) * @returns List of matching pages */ declare function pageLookup(siteId: number, query: string): WikidotResultAsync; /** * QuickModule API (maintained for backwards compatibility) * @deprecated Use individual functions (memberLookup, userLookup, pageLookup) instead */ declare const QuickModule: { memberLookup: typeof memberLookup; userLookup: typeof userLookup; pageLookup: typeof pageLookup; }; /** * Site membership application data */ interface SiteApplicationData { site: Site; user: AbstractUser; text: string; } /** * Site membership application */ declare class SiteApplication { readonly site: Site; readonly user: AbstractUser; readonly text: string; constructor(data: SiteApplicationData); /** * Get all pending membership applications * @param site - Target site */ static acquireAll(site: Site): WikidotResultAsync; /** * Internal method to process application */ private process; /** * Accept membership application */ accept(): WikidotResultAsync; /** * Decline membership application */ decline(): WikidotResultAsync; toString(): string; } /** * A single blocked-user entry. * * **Row markup is not directly measured** (no admin-panel HTML sample was captured for * this module; only the client-side JS handlers were). Row parsing reuses the * `span.printuser` convention validated elsewhere in this codebase (e.g. SiteMember.parse, * ForumPostRevision), since `managesite_blocks_ManageSiteUserBlocksModule.js` confirms each * row's "unblock" control passes the user ID via `deleteBlock(event, userId, ...)` -- the * same onclick-argument shape as other admin listings in this project. Verify against a live * site before relying on `reason` parsing. */ interface UserBlockData { site: Site; user: AbstractUser; reason: string; } declare class UserBlock { readonly site: Site; readonly user: AbstractUser; readonly reason: string; constructor(data: UserBlockData); /** Parse blocked-user entries from list HTML */ static parseAll(site: Site, html: string): UserBlock[]; } /** * A single blocked-IP entry. * * **Row markup is not directly measured** (same caveat as {@link UserBlock}). `blockId` is * extracted from the `deleteBlock(event, blockId, ...)` onclick handler * (`managesite_blocks_ManageSiteIpBlocksModule.js` confirms this argument is a block ID, not * the IP itself -- asymmetric with UserBlock, whose equivalent argument is a user ID; see * member-accessor.ts's `unblockUser`/`unblockIp` docs). */ interface IpBlockData { site: Site; blockId: number; ip: string; reason: string; } declare class IpBlock { readonly site: Site; readonly blockId: number; readonly ip: string; readonly reason: string; constructor(data: IpBlockData); /** Parse blocked-IP entries from list HTML */ static parseAll(site: Site, html: string): IpBlock[]; } /** * Site member data */ interface SiteMemberData { site: Site; user: AbstractUser; joinedAt: Date | null; } /** * Site member */ declare class SiteMember { readonly site: Site; readonly user: AbstractUser; readonly joinedAt: Date | null; constructor(data: SiteMemberData); /** * Parse member information from HTML. * * Not private: reused by MemberAccessor (member-accessor.ts) for the * admin-panel member/moderator/admin listings. Confirmed 2026-07-29: the * admin panel's `ManageSiteMembersListModule` table has a 3rd * options-dropdown `td` alongside name/join-date, and a leading `th`-only * header row (`Name` / `Member since` / blank) -- `$(tds[0])` on an empty * `tds` (the header row) yields an empty selection rather than throwing, * so `userElem.length === 0` already skips it; the `tds.length >= 2` join * -date check below already tolerates the extra 3rd column too. */ static parse(site: Site, html: string): SiteMember[]; /** * Get site member list * @param site - Target site * @param group - Group ("admins", "moderators", or empty string for all members) */ static getMembers(site: Site, group?: "admins" | "moderators" | ""): WikidotResultAsync; /** * Internal method to change group */ private changeGroup; /** * Promote to moderator */ toModerator(): WikidotResultAsync; /** * Remove moderator privileges */ removeModerator(): WikidotResultAsync; /** * Promote to admin */ toAdmin(): WikidotResultAsync; /** * Remove admin privileges */ removeAdmin(): WikidotResultAsync; toString(): string; } /** Accepts a user object, or a raw user id, wherever a member is referenced */ type UserOrId = User | number; /** A single result row from `users/UserSearchModule` */ interface UserSearchResult { id: number; name: string; } /** * Site member operations accessor. Access through `Site.member`. * * Groups administrative operations that require site-admin permissions: * removing/promoting/demoting members, moderator permissions, invitations, * membership-application handling (delegated to SiteApplication, unchanged), * members auto-watching, abuse-flag clearing, and user/IP blocks (see * site-block.ts for the underlying parsing). */ declare class MemberAccessor { readonly site: Site; constructor(site: Site); /** * Get all members * @returns Member list */ getAll(): WikidotResultAsync; /** * Get moderator list * @returns Moderator list */ getModerators(): WikidotResultAsync; /** * Get admin list * @returns Admin list */ getAdmins(): WikidotResultAsync; /** * Get pending membership applications * @returns Application list */ getApplications(): WikidotResultAsync; /** * Search members * @param query - Search query (part of username) * @returns Matched user list (QMCUser format) */ lookup(query: string): WikidotResultAsync; /** * Invite user to site * @param user - User to invite * @param text - Invitation message */ invite(user: User, text: string): WikidotResultAsync; /** * Internal helper: fetch a paginated `managesite/members/*` listing. * * Row markup for `ManageSiteMembersListModule` was confirmed live * 2026-07-29 (see 40_admin-managesite.md): 1st `td` is `span.printuser`, * 2nd is `span.odate` (join date), 3rd is a Bootstrap options dropdown; a * leading `th`-only header row is skipped by `SiteMember.parse`. * `ManageSiteModeratorsModule` / `ManageSiteAdminsModule` were **not** * confirmed (the test site had no moderators/admins to render), but are * assumed to share this shape since Wikidot's server templates * consistently render member rows through the shared * `WIKIDOT.render.printuser` partial. * * **Pagination is unconfirmed.** The test site's 6 members did not * render a `div.pager` at all, so whether the admin panel uses the same * `div.pager` markup as the public `membership/MembersListModule` (vs. * Bootstrap-style pagination) could not be verified -- Wikidot's own * client (`loadMemberList`) just re-requests with `page` and does not * itself track a total page count. The `page` parameter is confirmed * correct either way; single-page results (the common case) are * unaffected. Re-verify against a site with enough members to paginate * before relying on multi-page results. */ private getPaginated; /** * Get the admin-panel view of all site members. * * Uses `managesite/members/ManageSiteMembersListModule`, distinct from * `getAll()` (public `membership/MembersListModule`): this is the view * rendered inside `_admin`, requires site-admin permissions, and is what * Wikidot's own client re-fetches after remove/promote/demote actions to * refresh the panel. */ getMembersAdminView(): WikidotResultAsync; /** Get the admin-panel view of site moderators */ getModeratorsAdminView(): WikidotResultAsync; /** Get the admin-panel view of site administrators */ getAdminsAdminView(): WikidotResultAsync; /** Internal: fire-and-forget action helper, mirroring settings-accessor's simpleAction */ private simpleAction; /** * Remove a member from the site. Destructive. * @param user - Member to remove (or their user ID) * @param options.ban - **`ban: true` removes the member and blocks them * in one call** (Wikidot's own client calls this combined flow "remove * and ban" -- it is not merely a removal reason). Sent as `ban="yes"` * when true; omitted entirely when false. If you only want to remove * membership without blocking, leave this false */ remove(user: UserOrId, options?: { ban?: boolean; }): WikidotResultAsync; /** * Transfer site master-admin ownership to another admin. Destructive from * the caller's perspective: the caller loses master status. Uses `userId` * (camelCase) -- unlike every other `ManageSiteMembershipAction` event in * this class (which use `user_id`), Wikidot's own client sends this one * parameter name in camelCase for `changeMaster` specifically. * @param user - New master admin (must already be a site admin; or their user ID) */ changeMaster(user: UserOrId): WikidotResultAsync; /** * Fetch the raw `sm-mod-perms-form` HTML for a moderator. * * **未実測**: the test site used for this research had no moderators, so * `sm-mod-perms-form`'s field names could not be captured. Returns the raw * rendered HTML body instead of a typed structure -- inspect it yourself * and pass the exact field names to `saveModeratorPermissions`. * @param moderatorId - Moderator's user ID (obtainable from getModeratorsAdminView()) */ getModeratorPermissionsForm(moderatorId: number): WikidotResultAsync; /** * Save moderator permissions (`saveModeratorPermissions`). * * **未実測**: `sm-mod-perms-form`'s field names are unknown (see * `getModeratorPermissionsForm`). Pass the exact field names/values * Wikidot's form uses; this method does not validate, transform, or * default them (unlike the typed `site.settings.*` methods). * @param fields - Raw form fields to submit verbatim */ saveModeratorPermissions(fields: Record): WikidotResultAsync; /** * Search for users to invite (`users/UserSearchModule`). * * Confirmed live 2026-07-29 (see 40_admin-managesite.md): `userIds` is a * JSON array of IDs, but `userNames` is **not** a parallel array -- it's * an object keyed by the user ID *as a string* * (`{"3396310": "ukwhatn", ...}`). There is also no `count` key (the * initial research's notes were wrong on both points); use * `result.length` if a count is needed. * @param query - Search query (part of a username) */ searchUsers(query: string): WikidotResultAsync; /** * Send email invitations to join the site. * @param addresses - `[email, name, isContact]` tuples. Encoded as * `addresses=[[email, name, isContact], ...]` (JSON), matching Wikidot's own client * @param message - Message included with the invitation */ sendEmailInvitations(addresses: [email: string, name: string, isContact: boolean][], message?: string): WikidotResultAsync; /** Delete a pending email invitation */ deleteEmailInvitation(invitationId: number): WikidotResultAsync; /** Resend a pending email invitation */ resendEmailInvitation(invitationId: number, message?: string): WikidotResultAsync; /** * Allow/disallow regular members to invite others via email. * @param enabled - Sent as the literal string "true"/"false" (always * present, not omitted when false -- matches `enableLetUsersInvite(bool)`'s * plain-boolean notation, the same always-sent convention as * `site.settings.saveOpenId`'s `enableOpenID`) */ setLetUsersInvite(enabled: boolean): WikidotResultAsync; /** * Invite a user to become a site admin. * @param user - User to invite (or their user ID) * @returns The invited user's ID, if returned by Wikidot */ inviteAdmin(user: UserOrId): WikidotResultAsync; /** Get the list of blocked users */ getBlockedUsers(): WikidotResultAsync; /** Get the list of blocked IP addresses/ranges */ getBlockedIps(): WikidotResultAsync; /** * Block a user from the site. * @param user - User to block (or their user ID) * @param reason - Block reason (200 characters max per Wikidot's form) */ blockUser(user: UserOrId, reason?: string): WikidotResultAsync; /** * Remove a user block. * @param user - Blocked user (or their user ID). `deleteBlock`'s `userId` * really is a user ID (confirmed 2026-07-29 from * `managesite_blocks_ManageSiteUserBlocksModule.js`) -- do not confuse * with `unblockIp`'s `blockId`, which is a block ID */ unblockUser(user: UserOrId): WikidotResultAsync; /** * Block one or more IP addresses/ranges. * @param ips - IP addresses/ranges, one per line (matches the `ips` textarea) * @param reason - Block reason (200 characters max per Wikidot's form) */ blockIp(ips: string, reason?: string): WikidotResultAsync; /** * Remove an IP block. * @param blockId - Block ID (from getBlockedIps()). `deleteIpBlock`'s * `blockId` is a block ID, not an IP or user ID -- asymmetric with * `unblockUser`'s `userId`, do not confuse the two */ unblockIp(blockId: number): WikidotResultAsync; /** Clear abuse flags reported against a user */ clearUserFlags(user: UserOrId): WikidotResultAsync; /** Clear abuse flags reported against a page */ clearPageFlags(path: string): WikidotResultAsync; /** * Clear abuse flags reported against an anonymous (IP) address. * @param address - IP address * @param proxy - Sent as `proxy="yes"` when true (matching the `?("yes")` * notation in 40_admin-managesite.md, the same value Wikidot uses for * `remove()`'s `ban`); omitted when false */ clearAnonymousFlags(address: string, proxy?: boolean): WikidotResultAsync; /** * Configure members' automatic watching of new pages. * @param options.watchAll - Watch all categories automatically * @param options.selectedCategories - Category IDs to watch when watchAll * is false. Sent as `selected_categories[]=&...` (the AMC client * auto-expands array values into bracket notation) */ setMembersWatching(options?: { watchAll?: boolean; selectedCategories?: number[]; }): WikidotResultAsync; /** * Configure automatic link-blocking by karma level. * @param karmaLevel - Karma threshold (0-5) * @param blockLink - Whether to actually block links below the threshold */ setBlockLink(karmaLevel: number, blockLink?: boolean): WikidotResultAsync; } import * as cheerio3 from "cheerio"; import { AnyNode } from "domhandler"; /** Edit mode accepted by edit/PageEditModule. "section" requires `section` * to be set; save() then takes rangeStart/rangeEnd instead. */ type EditMode = "page" | "section" | "append"; /** Options for constructing a PageEditSession */ interface PageEditSessionOptions { site: Site; fullname: string; pageId?: number | null; mode?: EditMode; section?: number | null; forceLock?: boolean; } /** Options for PageEditSession.save() */ interface SaveOptions { title?: string; source?: string; comment?: string; andContinue?: boolean; rangeStart?: number; rangeEnd?: number; tags?: string; parentPage?: string; dontNotifyWatchers?: boolean; } /** Options for PageEditSession.checkDraftExists() */ interface CheckDraftExistsOptions { title?: string; source?: string; comment?: string; } /** Options for PageEditSession.preview() */ interface PreviewOptions { title?: string; source?: string; pageUnixName?: string; rangeStart?: number; rangeEnd?: number; } /** Options for PageEditSession.diff() */ interface DiffOptions { title?: string; source?: string; rangeStart?: number; rangeEnd?: number; } /** * A single Wikidot page edit session * * Acquiring the lock (via `open()`) talks to `edit/PageEditModule`. From * that point on the lock is held until either `save()` succeeds or the * session is released (explicitly via `release()`, or automatically by * `withEditLock()` if `save()` never succeeded). */ declare class PageEditSession { readonly site: Site; readonly fullname: string; readonly pageId: number | null; readonly mode: EditMode; readonly section: number | null; readonly forceLock: boolean; private _lockId; private _lockSecret; private _revisionId; private _timeLeft; private _isExistingPage; private _saved; private _locked; /** * @param options - Session options. `mode: "section"` requires `section` to be set */ constructor(options: PageEditSessionOptions); /** Lock ID, set once the session is open */ get lockId(): string | null; /** Lock secret, set once the session is open */ get lockSecret(): string | null; /** Revision ID submitted with save/synchronize requests */ get revisionId(): string; /** Remaining lock time in seconds, last refreshed by open/synchronize/forceLockIntercept/recreateExpiredLock */ get timeLeft(): number | null; /** Whether the page already existed when the lock was acquired */ get isExistingPage(): boolean; /** Whether save() has completed successfully */ get saved(): boolean; /** Whether the lock is currently held */ get isOpen(): boolean; private requireOpen; private lockParams; /** * Acquire the edit lock (edit/PageEditModule) */ open(): WikidotResultAsync; /** * Save the page (WikiPageAction/savePage) * * `andContinue` keeps the lock/editor open; Wikidot returns a fresh * `revisionId` in the response in that case. `rangeStart`/`rangeEnd` are * only meaningful when mode is "section". `tags`/`parentPage` are only * used when creating a page via a /tags/... or /parentPage/... URL. */ save(options?: SaveOptions): WikidotResultAsync>; /** * Keep the edit lock alive (WikiPageAction/synchronize) * * Call periodically while the editor stays open without saving. * @param sinceLastInput - Seconds elapsed since the last user input */ synchronize(sinceLastInput?: number): WikidotResultAsync>; /** * Check whether a draft already exists for this lock (WikiPageAction/checkDraftExists) * * The wire format echoes `form(edit-page-form)` back to the server * alongside the lock identifiers; the exact reason the server wants the * in-progress content for this check (as opposed to just the lock id) is * not documented upstream, so the parameters sent here are an * implementation judgment call rather than a confirmed wire contract. */ checkDraftExists(options?: CheckDraftExistsOptions): WikidotResultAsync; /** * Forcibly take over another user's lock (WikiPageAction/forceLockIntercept) * * On success this session's lockId/lockSecret are updated in place. */ forceLockIntercept(): WikidotResultAsync>; /** * Recreate an expired lock (WikiPageAction/recreateExpiredLock) * * On success this session's lockId/lockSecret are updated in place. */ recreateExpiredLock(): WikidotResultAsync>; /** * Release the edit lock (WikiPageAction/removePageEditLock) * * Safe to call on a session that was never opened (no-op in that case). * Failures are logged, not thrown, so calling this from `withEditLock()` * never masks whatever error triggered the release. * @param leaveDraft - Whether to keep the in-progress content as a draft instead of discarding it */ release(leaveDraft?: boolean): WikidotResultAsync; /** * Render a preview of in-progress content (edit/PagePreviewModule) */ preview(options?: PreviewOptions): WikidotResultAsync<{ body: string; title: string; }>; /** * Render a diff of in-progress content against the base revision (edit/PageEditDiffModule) */ diff(options?: DiffOptions): WikidotResultAsync; } import * as cheerio from "cheerio"; /** * Page file data */ interface PageFileData { page: Page; id: number; name: string; url: string; mimeType: string; size: number; } /** * Page attachment file */ declare class PageFile { readonly page: Page; readonly id: number; name: string; readonly url: string; readonly mimeType: string; readonly size: number; constructor(data: PageFileData); toString(): string; /** * Get the rendered rename form for this file (files/FileRenameWinModule) */ getRenameForm(): WikidotResultAsync; /** * Get the rendered move form for this file (files/FileMoveWinModule) */ getMoveForm(): WikidotResultAsync; /** * Get the rendered detail view for this file (files/FileInformationWinModule) */ getInfo(): WikidotResultAsync; /** * Rename this file (FileAction/renameFile) * @param newName - New file name * @param options - force: whether to overwrite if a file with the new name already exists * @remarks Failure status codes: "file_exists" (response carries `body`) * / "name_error" (response carries `message`) */ rename(newName: string, options?: { force?: boolean; }): WikidotResultAsync; /** * Move this file to another page (FileAction/moveFile) * @param destinationPageName - Fullname of the destination page * @param options - force: whether to overwrite if a file with the same * name already exists on the destination page * @remarks Failure status codes: "file_exists" / "no_destination" / * "no_destination_permission". After a successful move this object's * `page` reference still points at the source page; re-fetch the file * from the destination page if an up-to-date PageFile is needed */ move(destinationPageName: string, options?: { force?: boolean; }): WikidotResultAsync; /** * Delete this file (FileAction/deleteFile) * @param options - confirm: must be explicitly set to true. This is a * destructive, irreversible operation */ delete(options?: { confirm?: boolean; }): WikidotResultAsync; } /** * Page file collection */ declare class PageFileCollection extends Array { readonly page: Page; constructor(page: Page, files?: PageFile[]); /** * Find by ID */ findById(id: number): PageFile | undefined; /** * Find by name */ findByName(name: string): PageFile | undefined; /** * Convert size string to bytes (public for batch operations) */ static parseSize(sizeText: string): number; /** * Parse file information from HTML response * Internal helper for acquire() and PageCollection.acquirePageFiles() */ static _parseFromHtml(page: Page, $: cheerio.CheerioAPI): PageFile[]; /** * Get list of files attached to page */ static acquire(page: Page): WikidotResultAsync; /** * Check whether a file with the given name exists on the page (FileAction/checkFileExists) */ static checkExists(page: Page, filename: string): WikidotResultAsync; /** * Get the rendered file upload form for a page (files/FileUploadModule) * * Returns the HTML form only; the actual upload goes through the * separate multipart endpoint (see upload()), not this module. */ static getUploadForm(page: Page): WikidotResultAsync; /** * Get the rendered site-wide file manager view (files/manager/FileManagerModule) * * The exact parameter set for a manager view scoped beyond a single page * was not captured during wire-format research; this sends `pageId` as * the one documented per-page parameter shape and returns the raw body. */ static getManager(page: Page): WikidotResultAsync; /** * Upload a file to a page (multipart, /default--flow/files__UploadTarget) * * UNVERIFIED AGAINST A LIVE WIKIDOT INSTANCE -- see * AMCClient.uploadFile's docstring for what is and isn't confirmed. * Does not go through site.amcRequest(): this endpoint returns an HTML * fragment rather than the AMC JSON envelope, so it uses a dedicated * client method instead. * @param page - Page to attach the file to * @param filename - File name as it will appear on the page * @param content - File content * @param options - multikey: multi-file upload session key, required * when uploading more than one file so Wikidot can group them for * multiUploadComplete() */ static upload(page: Page, filename: string, content: Uint8Array | Blob, options?: { multikey?: string; }): WikidotResultAsync>; /** * Notify Wikidot that a batch of multipart uploads has finished (FileAction/multiUploadComplete) */ static multiUploadComplete(page: Page, multikey: string, filenames: string[]): WikidotResultAsync; } /** * Page meta tag data */ interface PageMetaData { page: PageRef; name: string; content: string; } /** * Page meta tag */ declare class PageMeta { readonly page: PageRef; readonly name: string; content: string; constructor(data: PageMetaData); /** * Update meta tag value * @param content - New value */ update(content: string): WikidotResultAsync; /** * Delete meta tag */ delete(): WikidotResultAsync; toString(): string; } /** * Page meta tag collection */ declare class PageMetaCollection extends Array { readonly page: PageRef; constructor(page: PageRef, metas?: PageMeta[]); /** * Find by name * @param name - Meta tag name * @returns Meta tag (undefined if not found) */ findByName(name: string): PageMeta | undefined; /** * Get page meta tags * @param page - Page reference * @returns Meta tag collection */ static acquire(page: PageRef): WikidotResultAsync; /** * Set meta tag * @param page - Page reference * @param name - Meta tag name * @param content - Meta tag value */ static setMeta(page: PageRef, name: string, content: string, options?: { allPages?: boolean; }): WikidotResultAsync; /** * Delete meta tag * @param page - Page reference * @param name - Meta tag name */ static deleteMeta(page: PageRef, name: string, options?: { allPages?: boolean; }): WikidotResultAsync; } /** * Page source data */ interface PageSourceData { page: Page; wikiText: string; } /** * Page source code (Wikidot syntax) */ declare class PageSource { /** Page this source belongs to */ readonly page: Page; /** Source code (Wikidot syntax) */ readonly wikiText: string; constructor(data: PageSourceData); toString(): string; } type HistoryOptionKey = "all" | "source" | "title" | "move" | "tags" | "files" | "meta"; /** * Page revision data */ interface PageRevisionData { page: Page; id: number; revNo: number; createdBy: AbstractUser; createdAt: Date; comment: string; } /** * Page revision (version in edit history) */ declare class PageRevision { /** Page this revision belongs to */ readonly page: Page; /** Revision ID */ readonly id: number; /** Revision number */ readonly revNo: number; /** Revision creator */ readonly createdBy: AbstractUser; /** Revision creation date */ readonly createdAt: Date; /** Edit comment */ readonly comment: string; /** Source code (internal cache) */ private _source; /** HTML display (internal cache) */ private _html; constructor(data: PageRevisionData); /** * Whether source code has been acquired */ isSourceAcquired(): boolean; /** * Whether HTML display has been acquired */ isHtmlAcquired(): boolean; /** * Get source code (cached) */ get source(): PageSource | null; /** * Set source code */ set source(value: PageSource | null); /** * Get HTML display (cached) */ get html(): string | null; /** * Set HTML display */ set html(value: string | null); /** * Get revision source (REV-001) * @returns Source string */ getSource(): WikidotResultAsync; /** * Get revision HTML (REV-002) * @returns HTML string */ getHtml(): WikidotResultAsync; /** * Revert the page to this revision (WikiPageAction/revert) * * @param options - force: force the revert despite an active edit lock held by someone else * @returns Raw response data. On a lock conflict (force not set, or set * but still refused), the response carries `locks` + `body` describing * the conflicting lock instead of completing the revert; the caller is * responsible for inspecting this rather than an exception being * thrown, since a lock conflict is reported as `status: "ok"` with * these extra keys */ revert(options?: { force?: boolean; }): WikidotResultAsync>; toString(): string; } /** * Page revision collection */ declare class PageRevisionCollection extends Array { readonly page: Page | null; constructor(page: Page | null, revisions?: PageRevision[]); /** * Find by ID * @param id - Revision ID * @returns Revision (undefined if not found) */ findById(id: number): PageRevision | undefined; /** * Get sources for all revisions * @returns Array of source strings */ getSources(): WikidotResultAsync; /** * Get HTML for all revisions * @returns Array of HTML strings */ getHtmls(): WikidotResultAsync; /** * Get an HTML diff between two revisions (history/PageDiffModule) */ static getDiff(page: Page, fromRevisionId: number, toRevisionId: number, showType?: string): WikidotResultAsync; /** * Get a page's revision history with server-side filtering (history/PageRevisionListModule) * * Unlike the eager full-history fetch behind `Page.getRevisions()` * (`PageCollection.acquirePageRevisions`, which always requests * `{all: true}` with a huge perpage to populate the whole collection), * this exposes the change-type filter and pagination Wikidot's own * history view uses. * @param options - options: change-type filter flags, defaults to * `{all: true}`. perpage: items per page (10/20/50/100/200 match * Wikidot's own choices). pageNo: 1-indexed page number */ static acquire(page: Page, options?: { options?: Partial>; perpage?: 20 | 50 | 100 | 200; pageNo?: number; }): WikidotResultAsync; /** * Get a page's whole revision history, failing unless the list is provably complete * * Fails with ResponseDataError when any history row could not be parsed, * when revision numbers are not contiguous from 0 or 1, or when the latest * revision number is below `page.revisionsCount` (a truncated list). A * latest revision above `revisionsCount` is accepted, since the page may * have been edited after `page` was fetched. * @param page - Page with `id` and `revisionsCount` set */ static acquireComplete(page: Page): WikidotResultAsync; } /** * Page vote data */ interface PageVoteData { page: Page; user: AbstractUser; value: number; } /** * Page vote (rating) */ declare class PageVote { /** Page this vote belongs to */ readonly page: Page; /** User who voted */ readonly user: AbstractUser; /** Vote value (+1/-1 or numeric) */ readonly value: number; constructor(data: PageVoteData); toString(): string; } /** * Page vote collection */ declare class PageVoteCollection extends Array { readonly page: Page; constructor(page: Page, votes?: PageVote[]); /** * Find by user * @param user - User to search for * @returns Vote (undefined if not found) */ findByUser(user: AbstractUser): PageVote | undefined; } /** * Page search query parameters */ interface SearchPagesQueryParams { /** Page type (e.g., 'normal', 'admin') */ pagetype?: string; /** Category name */ category?: string; /** Tags to search (AND condition) */ tags?: string | string[]; /** Parent page name */ parent?: string; /** Linked page name */ linkTo?: string; /** Created date condition */ createdAt?: string; /** Updated date condition */ updatedAt?: string; /** Creator */ createdBy?: AbstractUser | string; /** Rating condition */ rating?: string; /** Vote count condition */ votes?: string; /** Page name condition */ name?: string; /** Fullname (exact match) */ fullname?: string; /** Range specification */ range?: string; /** Sort order (e.g., 'created_at desc') */ order?: string; /** Start offset */ offset?: number; /** Result limit */ limit?: number; /** Items per page */ perPage?: number; /** Separate display */ separate?: string; /** Wrapper display */ wrapper?: string; } /** * Default items per page */ declare const DEFAULT_PER_PAGE = 250; /** * Default module body fields */ declare const DEFAULT_MODULE_BODY: readonly ["fullname", "category", "name", "title", "created_at", "created_by_linked", "updated_at", "updated_by_linked", "commented_at", "commented_by_linked", "parent_fullname", "comments", "size", "children", "rating_votes", "rating", "rating_percent", "revisions", "tags", "_tags"]; /** * Page search query */ declare class SearchPagesQuery { /** Page type */ pagetype: string; /** Category */ category: string; /** Tags */ tags: string | string[] | null; /** Parent page */ parent: string | null; /** Link target */ linkTo: string | null; /** Created date condition */ createdAt: string | null; /** Updated date condition */ updatedAt: string | null; /** Creator */ createdBy: AbstractUser | string | null; /** Rating condition */ rating: string | null; /** Vote count condition */ votes: string | null; /** Page name condition */ name: string | null; /** Fullname condition */ fullname: string | null; /** Range */ range: string | null; /** Sort order */ order: string; /** Offset */ offset: number; /** Result limit */ limit: number | null; /** Items per page */ perPage: number; /** Separate display */ separate: string; /** Wrapper display */ wrapper: string; constructor(params?: SearchPagesQueryParams); /** * Convert to dictionary format */ asDict(): Record; } /** * Page data */ interface PageData { site: Site; fullname: string; name: string; category: string; title: string; childrenCount: number; commentsCount: number; size: number; rating: number; votesCount: number; ratingPercent: number | null; revisionsCount: number; parentFullname: string | null; tags: string[]; createdBy: AbstractUser | null; createdAt: Date; updatedBy: AbstractUser | null; updatedAt: Date; commentedBy: AbstractUser | null; commentedAt: Date | null; } /** * Wikidot page */ declare class Page { readonly site: Site; readonly fullname: string; readonly name: string; readonly category: string; title: string; childrenCount: number; commentsCount: number; size: number; rating: number; votesCount: number; ratingPercent: number | null; revisionsCount: number; parentFullname: string | null; tags: string[]; readonly createdBy: AbstractUser | null; readonly createdAt: Date; updatedBy: AbstractUser | null; updatedAt: Date; commentedBy: AbstractUser | null; commentedAt: Date | null; private _id; private _source; private _revisions; private _votes; _files: PageFileCollection | null; constructor(data: PageData); /** * Get page URL */ getUrl(): string; /** * Whether page ID has been acquired */ isIdAcquired(): boolean; /** * Get page ID */ get id(): number | null; /** * Set page ID */ set id(value: number | null); /** * Get source code */ get source(): PageSource | null; /** * Set source code */ set source(value: PageSource | null); /** * Get revision history */ get revisions(): PageRevisionCollection | null; /** * Set revision history */ set revisions(value: PageRevisionCollection | null); /** * Get the whole revision history, failing unless it is provably complete * (see PageRevisionCollection.acquireComplete). Always fetches; the * result is not cached in `revisions`. */ getCompleteRevisions(): WikidotResultAsync; /** * Get vote information */ get votes(): PageVoteCollection | null; /** * Set vote information */ set votes(value: PageVoteCollection | null); /** * Get latest revision */ get latestRevision(): PageRevision | undefined; /** * Ensure page ID is available (auto-acquire if not yet acquired) * @param operation - Operation name (for error message) * @throws If ID acquisition fails */ private ensureId; /** * Delete page */ destroy(): WikidotResultAsync; /** * Save tags */ commitTags(): WikidotResultAsync; /** * Set parent page * @param parentFullname - Parent page fullname (null to remove) */ setParent(parentFullname: string | null): WikidotResultAsync; /** * Vote on page * @param value - Vote value * @returns New rating */ vote(value: number): WikidotResultAsync; /** * Cancel vote * @returns New rating */ cancelVote(): WikidotResultAsync; /** * Edit the page * @param options - Edit options */ edit(options: { title?: string; source?: string; comment?: string; forceEdit?: boolean; }): WikidotResultAsync; /** * Rename the page * @param newFullname - New fullname * @param options - fixdeps: backlink page IDs (see getRenameBacklinks()) * whose links should be updated along with the rename. force: force the * rename despite an active edit lock held by someone else */ rename(newFullname: string, options?: { fixdeps?: number[]; force?: boolean; }): WikidotResultAsync; /** * Get list of files attached to the page */ getFiles(): WikidotResultAsync; /** * Get the discussion thread for the page * * Wikidot creates the discussion thread when ForumCommentsListModule is * first requested, so this returns a thread even for a page that had none. */ getDiscussion(): WikidotResultAsync; /** * Get the list of meta tags for the page * @returns Meta tag collection */ getMetas(): WikidotResultAsync; /** * Set a meta tag * @param name - Meta tag name * @param content - Meta tag value * @param options - allPages: apply to all pages sharing the template */ setMeta(name: string, content: string, options?: { allPages?: boolean; }): WikidotResultAsync; /** * Delete a meta tag * @param name - Meta tag name * @param options - allPages: apply to all pages sharing the template */ deleteMeta(name: string, options?: { allPages?: boolean; }): WikidotResultAsync; /** * Get page source (auto-acquire if not yet acquired) * @returns Page source */ getSource(): WikidotResultAsync; /** * Get revision history (auto-acquire if not yet acquired) * @returns Revision collection */ getRevisions(): WikidotResultAsync; /** * Get vote information (auto-acquire if not yet acquired) * @returns Vote collection */ getVotes(): WikidotResultAsync; /** * Open a manual edit session for this page * * Unlike `edit()`/`site.page.create()`, which perform a single * create-or-edit round trip, this returns an unopened `PageEditSession` * for callers that need access to intermediate operations while the * lock is held (preview, diff, synchronize, section/append mode, * andContinue, ...). Use `withEditLock()` so the lock is always * released unless save() succeeds. */ openEditor(options?: { mode?: EditMode; section?: number | null; forceLock?: boolean; }): PageEditSession; /** * Get this page's source for use as a page-creation template (edit/TemplateSourceModule) * * Distinct from getSource(): this is the wire endpoint Wikidot uses when * populating a new page's editor from a template page selection, not * general source retrieval. */ getTemplateSource(): WikidotResultAsync; /** * Get the rendered page-block form (pageblock/PageBlockModule) */ getBlockForm(): WikidotResultAsync; /** * Set or clear this page's edit-block flag (WikiPageAction/saveBlock) * * A blocked page cannot be edited by non-moderator users. * @param block - Whether to block editing (default true) */ setBlock(block?: boolean): WikidotResultAsync; /** * Get pages linking to this page (backlinks/BacklinksModule) * * Returns the raw HTML rather than a parsed list; the exact markup was * not captured during wire-format research (same caveat as * getRenameBacklinks(), which uses a different, rename-specific module). */ getBacklinks(): WikidotResultAsync; /** * Watch this page for changes (WatchAction/watchPage) */ watch(): WikidotResultAsync; /** * Get the list of users watching this page (watch/WhoWatchesModule) */ getWatchers(): WikidotResultAsync; /** * Get the rendered tags editing form (pagetags/PageTagsModule) */ getTagsForm(): WikidotResultAsync; /** * Update tags via the quick-tag button UI (WikiPageAction/updateTagsByButton) * * Distinct from commitTags(): that saves the current `tags` property in * full via WikiPageAction/saveTags. This instead mirrors clicking one of * Wikidot's own quick-tag buttons, which sends whatever tag string the * button computed client-side rather than the page's current tag list. * @param tags - Space-separated tag string to apply */ updateTagsByButton(tags: string): WikidotResultAsync; /** * Get the rendered parent-page selection form (parent/ParentPageModule) */ getParentForm(): WikidotResultAsync; /** * Get pages linking to this page, for building rename()'s fixdeps (rename/RenameBacklinksModule) * * Returns the rendered HTML listing backlink pages with checkboxes; the * IDs of the ones a caller wants fixed up are what rename()'s `fixdeps` * expects. The exact list markup was not captured during wire-format * research (only the request/response shape is documented), so this * returns the raw body rather than a parsed ID list. */ getRenameBacklinks(): WikidotResultAsync; toString(): string; } /** * Page collection */ declare class PageCollection extends Array { readonly site: Site; constructor(site: Site, pages?: Page[]); /** * Find by fullname * @param fullname - Page fullname * @returns Page (undefined if not found) */ findByFullname(fullname: string): Page | undefined; /** * Acquire page IDs in bulk */ getPageIds(): WikidotResultAsync; /** * Acquire page sources in bulk */ getPageSources(): WikidotResultAsync; /** * Acquire page revisions in bulk */ getPageRevisions(): WikidotResultAsync; /** * Acquire page votes in bulk */ getPageVotes(): WikidotResultAsync; /** * Acquire page files in bulk */ getPageFiles(): WikidotResultAsync; /** * Internal method to acquire page files in bulk */ static acquirePageFiles(site: Site, pages: Page[]): WikidotResultAsync; /** * Internal method to acquire page IDs in bulk */ static acquirePageIds(site: Site, pages: Page[]): WikidotResultAsync; /** * Internal method to acquire page sources in bulk */ static acquirePageSources(site: Site, pages: Page[]): WikidotResultAsync; /** * Internal method to acquire page revisions in bulk */ static acquirePageRevisions(site: Site, pages: Page[]): WikidotResultAsync; /** * Internal method to acquire page votes in bulk */ static acquirePageVotes(site: Site, pages: Page[]): WikidotResultAsync; /** * Parse ListPagesModule response */ static parse(site: Site, htmlBody: cheerio3.CheerioAPI, _parseUser: (element: cheerio3.Cheerio) => AbstractUser): PageCollection; /** * Search pages */ static searchPages(site: Site, parseUser: (element: cheerio3.Cheerio) => AbstractUser, query?: SearchPagesQuery | null): WikidotResultAsync; /** * Create or edit a page */ static createOrEdit(site: Site, fullname: string, options?: { pageId?: number | null; title?: string; source?: string; comment?: string; forceEdit?: boolean; raiseOnExists?: boolean; }): WikidotResultAsync; } /** * Site change history data */ interface SiteChangeData { site: Site; pageFullname: string; pageTitle: string; revisionNo: number; changedBy: AbstractUser | null; changedAt: Date | null; flags: string[]; comment: string; } /** * Site change history */ declare class SiteChange { readonly site: Site; readonly pageFullname: string; readonly pageTitle: string; readonly revisionNo: number; readonly changedBy: AbstractUser | null; readonly changedAt: Date | null; readonly flags: string[]; readonly comment: string; constructor(data: SiteChangeData); /** * Get page URL */ getPageUrl(): string; toString(): string; } /** * Site change history collection */ declare class SiteChangeCollection extends Array { readonly site: Site; constructor(site: Site, changes?: SiteChange[]); /** * Get recent change history * @param site - Site instance * @param options - Options * @returns Change history collection */ static acquire(site: Site, options?: { perPage?: number; page?: number; limit?: number; }): WikidotResultAsync; } /** * Single page operations accessor */ declare class PageAccessor { readonly site: Site; constructor(site: Site); /** * Get page by UNIX name * @param unixName - Page UNIX name (e.g., 'scp-173') * @returns Page (null if not found) */ get(unixName: string): WikidotResultAsync; /** * Create a page * @param fullname - Page fullname (e.g., 'scp-173') * @param options - Creation options * @returns void */ create(fullname: string, options?: { title?: string; source?: string; comment?: string; forceEdit?: boolean; }): WikidotResultAsync; } /** * Page list operations accessor */ declare class PagesAccessor { readonly site: Site; constructor(site: Site); /** * Search pages matching conditions * @param params - Search conditions * @returns Page collection */ search(params?: SearchPagesQueryParams): WikidotResultAsync; /** * Get all pages * @returns Page collection */ all(): WikidotResultAsync; /** * Get recent changes * @param options - Options * @param options.perPage - Items per page (default: 20) * @param options.page - Page number (default: 1) * @returns Change history collection */ getRecentChanges(options?: { perPage?: number; page?: number; }): WikidotResultAsync; } /** * Encoders/decoders for Wikidot's compact permission and rating strings * * Manage Site's category objects hold permissions as a single semicolon-separated * string (e.g. `v:armo;c:m;...`), forum permissions in a similar but distinct * format, and rating configuration as a fixed 4-character code (e.g. `drvM`). * These are typed here instead of being passed around as raw strings so callers * get validation and IDE support; see 30_plan.md D4 in the sibling wikidot.py * repo's memory directory for the design rationale (in particular: unknown * symbols are preserved through a decode/encode round trip rather than dropped, * since at least one forum permission symbol ("s") is defined in Wikidot's * client JS but has an unconfirmed purpose). This is a direct port of * wikidot.py's `module/site_permissions.py`. */ /** Actor that can be granted a permission */ type Actor = "anonymous" | "registered" | "member" | "author"; type PagePermField = "view" | "create" | "edit" | "move" | "delete" | "uploadFiles" | "renameFiles" | "replaceFiles" | "showOptions"; /** Constructor data for {@link PagePermissions} */ interface PagePermissionsData { view?: Iterable; create?: Iterable; edit?: Iterable; move?: Iterable; delete?: Iterable; uploadFiles?: Iterable; renameFiles?: Iterable; replaceFiles?: Iterable; showOptions?: Iterable; /** * Raw "letter:users" segments this library did not recognize, preserved * verbatim so a decode -> encode round trip never loses data */ unknown?: readonly string[]; } /** Decoded form of a category's `permissions` string */ declare class PagePermissions { readonly view: ReadonlySet; readonly create: ReadonlySet; readonly edit: ReadonlySet; readonly move: ReadonlySet; readonly delete: ReadonlySet; readonly uploadFiles: ReadonlySet; readonly renameFiles: ReadonlySet; readonly replaceFiles: ReadonlySet; readonly showOptions: ReadonlySet; readonly unknown: readonly string[]; constructor(data?: PagePermissionsData); /** * Encode back into Wikidot's `permissions` string format * @returns e.g. "v:armo;c:m;e:m;m:m;d:m;a:m;r:m;z:m;o:rm" */ encode(): string; /** * Decode a category's `permissions` string * @param s - Raw string, e.g. "v:armo;c:m;e:m;m:m;d:m;a:m;r:m;z:m;o:rm" */ static decode(s: string): PagePermissions; /** * Return a copy with only the specified fields replaced, leaving the rest * (including `unknown`) unchanged */ withUpdates(updates: Partial>>): PagePermissions; /** * Check the anonymous subset registered subset member containment convention. * * Wikidot's Manage Site UI enforces this relationship client-side (granting * anonymous access implies registered and member access, and so on), but it * is unconfirmed whether the server enforces it too. This library does not * auto-correct the containment (doing so could silently grant permissions * the caller did not ask for); call this explicitly if you want to check * before saving. * @returns Human-readable description of each violated field. Empty if * everything is consistent */ validate(): string[]; } /** Constructor data for {@link ForumPermissions} */ interface ForumPermissionsData { createThreads?: Iterable; addPosts?: Iterable; editPosts?: Iterable; /** Raw "letter:users" segments this library did not recognize (e.g. "s") */ unknown?: readonly string[]; } /** * Decoded form of `ManageSiteForumAction/saveForumPermissions`'s per-category * `permissions` string (a separate encoding from PagePermissions despite the * similar shape) */ declare class ForumPermissions { readonly createThreads: ReadonlySet; readonly addPosts: ReadonlySet; readonly editPosts: ReadonlySet; readonly unknown: readonly string[]; constructor(data?: ForumPermissionsData); /** Encode back into the forum `permissions` string format */ encode(): string; /** Decode a forum category's `permissions` string */ static decode(s: string): ForumPermissions; } /** Constructor data for {@link RatingSettings} */ interface RatingSettingsData { enabled: boolean; voters: "registered" | "member"; anonymous: boolean; kind: "plusOnly" | "plusMinus" | "stars"; } /** Decoded form of a category's 4-character `rating` code (e.g. "drvM") */ declare class RatingSettings { readonly enabled: boolean; readonly voters: "registered" | "member"; readonly anonymous: boolean; readonly kind: "plusOnly" | "plusMinus" | "stars"; constructor(data: RatingSettingsData); /** * Encode back into Wikidot's 4-character `rating` code * @returns e.g. "drvM" */ encode(): string; /** * Decode a category's `rating` code * @param s - 4-character code, e.g. "drvM" * @throws {Error} If `s` is not a recognized 4-character code. Unlike * PagePermissions/ForumPermissions, no unrecognized-but-real variant of this * code was found during the survey (each of the 4 positions has exactly 2 * documented values), so this throws instead of silently guessing at an * unknown format */ static decode(s: string): RatingSettings; } /** * Known `license_id` values for a category. * * Values and the exact option text come from a live read-only fetch of * `managesite/ManageSiteLicenseModule` (`select#sm-license-lic`), recorded * in the sibling wikidot.py repo's 35_form-fields.md "license_id の値 * (実測)" table. All 15 values are confirmed, including the * previously-grouped NonCommercial variants (5/6/7 and 15/16/17). */ declare const SiteLicense: { /** "Other" (custom license). Requires `licenseOther` to be set. */ readonly OTHER: 1; /** "Creative Commons Attribution Share Alike 2.5" */ readonly CC_ATTRIBUTION_SHAREALIKE_2_5: 2; /** "Creative Commons Attribution 2.5" */ readonly CC_ATTRIBUTION_2_5: 3; /** "Creative Commons Attribution No Derivatives 2.5" */ readonly CC_ATTRIBUTION_NO_DERIVATIVES_2_5: 4; /** "Creative Commons Attribution Non-commercial 2.5" */ readonly CC_ATTRIBUTION_NONCOMMERCIAL_2_5: 5; /** "Creative Commons Attribution Non-commercial Share Alike 2.5" */ readonly CC_ATTRIBUTION_NONCOMMERCIAL_SHAREALIKE_2_5: 6; /** "Creative Commons Attribution Non-commercial No Derivatives 2.5" */ readonly CC_ATTRIBUTION_NONCOMMERCIAL_NO_DERIVATIVES_2_5: 7; /** "GNU Free Documentation License 1.2" */ readonly GFDL_1_2: 8; /** "Standard copyright (not recommended)" */ readonly STANDARD_COPYRIGHT: 11; /** "Creative Commons Attribution-ShareAlike 3.0 License (recommended)" */ readonly CC_ATTRIBUTION_SHAREALIKE_3_0: 12; /** "Creative Commons Attribution 3.0 License" */ readonly CC_ATTRIBUTION_3_0: 13; /** "Creative Commons Attribution-NoDerivs 3.0 License" */ readonly CC_ATTRIBUTION_NO_DERIVATIVES_3_0: 14; /** "Creative Commons Attribution-NonCommercial 3.0 License" */ readonly CC_ATTRIBUTION_NONCOMMERCIAL_3_0: 15; /** "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License" */ readonly CC_ATTRIBUTION_NONCOMMERCIAL_SHAREALIKE_3_0: 16; /** "Creative Commons Attribution-NonCommercial-NoDerivs 3.0 License" */ readonly CC_ATTRIBUTION_NONCOMMERCIAL_NO_DERIVATIVES_3_0: 17; }; type SiteLicense = (typeof SiteLicense)[keyof typeof SiteLicense]; /** Raw category object shape as returned by Wikidot (snake_case wire format) */ type RawSiteCategory = Record & { category_id: number; site_id: number; name: string; }; /** Constructor data for {@link SiteCategory} */ interface SiteCategoryData { categoryId: number; siteId: number; name: string; themeDefault: boolean; /** * Normally an int theme id. Wikidot's own client sends the empty string * here (not 0) when `themeExternalUrl` is used instead (see * 40_admin-managesite.md "Appearance"). */ themeId: number | string; layoutDefault: boolean; layoutId: number; themeExternalUrl: string; permissionsDefault: boolean; /** undefined/null when permissionsDefault is true (inherits site default) */ permissions: PagePermissions | null; licenseDefault: boolean; licenseId: number | null; licenseOther: string; navDefault: boolean; topBarPageName: string | null; sideBarPageName: string | null; templateId: number | null; /** null means "use site default" */ perPageDiscussion: boolean | null; perPageDiscussionDefault: boolean; rating: RatingSettings | null; autonumerate: boolean; pageTitleTemplate: string | null; enablePingbackOut: boolean; enablePingbackIn: boolean; /** * Original response object, kept so toRaw() can round-trip fields this * library does not (yet) know about instead of dropping them */ raw: RawSiteCategory; } /** A single category object from a site's Manage Site `categories` array */ declare class SiteCategory { categoryId: number; siteId: number; name: string; themeDefault: boolean; themeId: number | string; layoutDefault: boolean; layoutId: number; themeExternalUrl: string; permissionsDefault: boolean; permissions: PagePermissions | null; licenseDefault: boolean; licenseId: number | null; licenseOther: string; navDefault: boolean; topBarPageName: string | null; sideBarPageName: string | null; templateId: number | null; perPageDiscussion: boolean | null; perPageDiscussionDefault: boolean; rating: RatingSettings | null; autonumerate: boolean; pageTitleTemplate: string | null; enablePingbackOut: boolean; enablePingbackIn: boolean; private readonly raw; constructor(data: SiteCategoryData); /** * Parse a single category object from a `categories` array element * @param data - Raw category object as returned by Wikidot */ static fromRaw(data: RawSiteCategory): SiteCategory; /** * Rebuild a category object for sending back to Wikidot. * Starts from the original raw object (so unknown fields survive the round * trip) and overwrites only the fields this class models. */ toRaw(): RawSiteCategory; /** * Update the specified page-permission fields, leaving the rest unchanged. * Also clears `permissionsDefault` (this category now has its own explicit * permissions instead of inheriting the site default). */ setPermissions(updates: Partial>>): void; } /** * The full `categories` array for a site, keyed by category name. * * Never cached (see 30_plan.md D3): a new collection is fetched every time * `SettingsAccessor.updateCategories` is called, so concurrent changes by * other admins are not clobbered by a stale save. */ declare class SiteCategoryCollection { readonly site: Site; readonly categories: SiteCategory[]; constructor(site: Site, categories: SiteCategory[]); /** * Look up a category by name * @throws {Error} If no category with that name exists */ get(name: string): SiteCategory; /** Get all category names */ names(): string[]; get length(): number; /** * Fetch the current `categories` array by rendering a Manage Site module * @param site - Site to fetch categories for * @param moduleName - Any Manage Site module documented to embed the full * `categories` array in its response (e.g. * "managesite/ManageSitePermissionsModule") */ static fetch(site: Site, moduleName: string): WikidotResultAsync; /** * Send the full `categories` array back to Wikidot * @param action - AMC action (e.g. "ManageSiteAction") * @param event - AMC event (e.g. "savePermissions") */ save(action: string, event: string): WikidotResultAsync; } /** Accepts a user object, or a raw user id, wherever a viewer/recipient list is needed */ type UserOrId2 = AbstractUser | number; /** * Current values of the site's General settings form (`sm-general-form`). * * Any field is undefined when the corresponding form element could not be * found in the rendered HTML; this library does not guess a value in that * case. */ interface GeneralSettings { name: string | undefined; subtitle: string | undefined; language: string | undefined; description: string | undefined; defaultPage: string | undefined; welcomePage: string | undefined; } /** Current values of the site's Domain settings */ interface DomainSettings { domain: string | undefined; domainDefault: boolean | undefined; redirects: string[] | undefined; } /** * Current values of the site's Access policy settings (`sm-private-form`). * * `viewers` (extra allowed users for a private site) is intentionally * absent: it is not part of this form, see `saveAccessPolicy`'s docstring * for why it cannot be read back. */ interface AccessPolicySettings { privacy: "open" | "closed" | "private" | undefined; byApply: boolean | undefined; byDomain: string | undefined; byPassword: boolean | undefined; password: string | undefined; allowHotlink: boolean | undefined; landingPage: string | undefined; hideNav: boolean | undefined; } /** Accessor for Manage Site (`_admin`) settings. Access through `Site.settings`. */ declare class SettingsAccessor { readonly site: Site; constructor(site: Site); /** * Fetch the current `categories` array, mutate it, and save it back. * * This is the single place that performs the categories read-modify-write * cycle; every method below that touches `categories` (Permissions / * License / Navigation / Templates / PageRate / PerPageDiscussion / * Appearance) is a thin wrapper over this. The array is always re-fetched * here (never cached), because a partial-update API does not exist and * holding a stale snapshot risks reverting another admin's concurrent * change. * @param moduleName - Manage Site module to fetch the `categories` array * from before mutating (e.g. "managesite/ManageSitePermissionsModule"). * Matches the module that would render the area being changed -- it is * unconfirmed whether every categories-rendering module echoes back the * same full 24-field schema, so this fetches from the module for the area * actually being saved rather than a single fixed one * @param action - AMC action for the save request (e.g. "ManageSiteAction") * @param event - AMC event for the save request (e.g. "savePermissions") * @param mutator - Called with the freshly fetched collection; mutate * categories in place */ updateCategories(moduleName: string, action: string, event: string, mutator: (categories: SiteCategoryCollection) => void): WikidotResultAsync; /** * Set explicit page permissions for a category. Clears the category's * `permissionsDefault` flag (it stops inheriting the site default). */ setPagePermissions(categoryName: string, permissions: PagePermissions): WikidotResultAsync; /** Make a category inherit the site's default page permissions */ useDefaultPagePermissions(categoryName: string): WikidotResultAsync; /** * Set an explicit license for a category * @param other - Free-text license description. Required when `license` is * `SiteLicense.OTHER` * @throws {Error} If `license` is `SiteLicense.OTHER` and `other` is empty */ setLicense(categoryName: string, license: SiteLicense, other?: string): WikidotResultAsync; /** Make a category inherit the site's default license */ useDefaultLicense(categoryName: string): WikidotResultAsync; /** Set explicit top/side navigation pages for a category */ setNavigation(categoryName: string, topBarPageName: string, sideBarPageName: string): WikidotResultAsync; /** Make a category inherit the site's default navigation */ useDefaultNavigation(categoryName: string): WikidotResultAsync; /** Set (or clear, with null) the page template for a category */ setTemplate(categoryName: string, templateId: number | null): WikidotResultAsync; /** Set the rating (vote) configuration for a category */ setPageRateSettings(categoryName: string, rating: RatingSettings): WikidotResultAsync; /** * Enable/disable the per-page discussion thread for a category * @param enabled - true/false to force on/off, or null to use the site default */ setPerPageDiscussion(categoryName: string, enabled: boolean | null): WikidotResultAsync; /** Apply a built-in theme to a category */ setAppearanceTheme(categoryName: string, themeId: number): WikidotResultAsync; /** * Apply an external theme URL to a category. Wikidot represents "external * theme" by sending `theme_id` as the empty string instead of an int (see * SiteCategory.themeId). */ setAppearanceExternalTheme(categoryName: string, themeExternalUrl: string): WikidotResultAsync; /** Make a category inherit the site's default appearance */ useDefaultAppearance(categoryName: string): WikidotResultAsync; /** * Fetch the site's current General settings. * * Renders `managesite/ManageSiteGeneralModule` and reads the current * values out of `sm-general-form` by its documented field names. */ getGeneral(): WikidotResultAsync; /** * Save the site's title/subtitle/language/description/entry pages. * * `saveGeneral` resubmits the whole form rather than a diff, so this * fetches the current settings (`getGeneral`) first and only overrides * the fields the caller passed explicitly. Undefined keeps the current * value; pass "" to clear a field. * @param options.name - Site title. Undefined keeps the current title. An * empty string (or a current title this library could not read) raises * FormErrorsError, since the title is required * @returns New unix name, only returned when the site's unix name changed * as a result * @throws {FormErrorsError} (in the returned Result's error channel) when * validation fails (e.g. an empty title) */ saveGeneral(options?: { name?: string; subtitle?: string; language?: string; description?: string; defaultPage?: string; welcomePage?: string; }): WikidotResultAsync; /** * Fetch the site's current Domain settings. * * Renders `managesite/ManageSiteDomainModule`. Unlike General/Access * policy these fields are read by element id, not `name` (Wikidot's own * JS reads them the same way). */ getDomain(): WikidotResultAsync; /** * Save the site's custom domain and redirect domains. * * Fetches the current settings (`getDomain`) first; undefined keeps the * current value for each option. * @param options.redirects - Additional domains that redirect to this * site. Undefined keeps the current redirect list; pass [] to clear it. * At most 10 (Wikidot's own client also allows empty entries through, * which show up as consecutive ";" in the joined string; this method does * not filter them out to match observed behavior) * @returns New domain, only returned when it changed * @throws {Error} If more than 10 redirects are given */ saveDomain(options?: { domain?: string; redirects?: string[]; domainDefault?: boolean; }): WikidotResultAsync; /** * Fetch the site's current Access policy settings. * * Renders `managesite/ManageSiteAccessPolicyModule` and reads * `sm-private-form`. Does not include `viewers` -- see `saveAccessPolicy` * for why that field cannot be read back. */ getAccessPolicy(): WikidotResultAsync; /** * Save the site's access policy (privacy level, apply/password/domain * gating, extra viewers, hotlinking, landing page, nav visibility). * * Fetches the current settings (`getAccessPolicy`) first; undefined keeps * the current value for each option (except `viewers`, see below). * @param privacy - Undefined keeps the current value. Throws if it cannot * be determined (this library will not guess between open / closed / * private, since a wrong guess could expose a private site) * @param options.password - Undefined keeps the current value. **Note**: * it is unconfirmed whether Wikidot actually echoes the real current * password back in this form (services commonly blank password fields * for security) -- if you are changing another field on a * password-gated site, pass the password explicitly rather than relying * on this * @param options.viewers - Extra users allowed to view a private site. * **Not** part of `sm-private-form` -- Wikidot assembles it client-side * via an autocomplete widget with no static representation of the * current selection, so it cannot be read back the way the other fields * can. Undefined omits the `viewers` parameter from the request entirely * (rather than sending an empty string, which would actively clear it), * but this is *not* the same guarantee as the other fields' "keeps the * current value": if the site has extra viewers configured, pass them * explicitly to preserve them * @throws {Error} If `privacy` is undefined and the current value could * not be determined */ saveAccessPolicy(privacy?: "open" | "closed" | "private", options?: { byApply?: boolean; byDomain?: string; byPassword?: boolean; password?: string; allowHotlink?: boolean; landingPage?: string; hideNav?: boolean; viewers?: UserOrId2[]; }): WikidotResultAsync; /** Save the site's custom footer */ saveCustomFooter(source: string, use?: boolean): WikidotResultAsync; /** Save the site's edit-toolbar visibility preference */ saveToolbarsPreference(options: { toolbarTop?: boolean; toolbarBottom?: boolean; promote?: boolean; }): WikidotResultAsync; /** Save the site's Google Analytics key */ saveGoogleAnalytics(key: string, use?: boolean): WikidotResultAsync; /** * Enable page auto-numbering for a category * @param override - Wikidot responds with status "non_numeric" and asks * for confirmation when the category has pages with non-numeric names; * pass override=true to confirm and proceed. This method does not * auto-retry on "non_numeric" -- inspect the returned Result's error and * re-call with override=true if needed */ addAutonumeration(categoryName: string, override?: boolean): WikidotResultAsync; /** Disable page auto-numbering for a category */ removeAutonumeration(categoryName: string): WikidotResultAsync; /** Set the auto-numbering title format for a category */ setAutonumerateTitleFormat(categoryName: string, titleFormat: string): WikidotResultAsync; /** Enable outgoing pingbacks for a category (see addAutonumeration for `override`) */ addPingbacks(categoryName: string, override?: boolean): WikidotResultAsync; /** Disable outgoing pingbacks for a category */ removePingbacks(categoryName: string): WikidotResultAsync; /** Enable/disable pingbacks site-wide */ setGlobalPingback(enabled?: boolean): WikidotResultAsync; /** Save the site's public API access settings */ saveApiSettings(options: { enabled?: boolean; read1?: boolean; read2?: boolean; write1?: boolean; write2?: boolean; }): WikidotResultAsync; /** * Save the site-wide OpenID configuration. Only the site-wide form * (`sm-openid-form-0`) is modeled; the per-page OpenID forms are * dynamically added/removed in the UI and out of scope for this method. * @param enabled - Whether OpenID login is enabled. Sent as the literal * string "true"/"false" (unlike most other boolean settings here, this * one is not omitted when false) */ saveOpenId(enabled: boolean, options?: { identityUrl?: string; serverUrl?: string; }): WikidotResultAsync; /** Request a site backup */ requestBackup(options?: { backupSources?: boolean; backupFiles?: boolean; backupType?: "tar" | "zip"; }): WikidotResultAsync; /** * Delete a site backup. Destructive and irreversible. * @param confirm - Must be explicitly true to proceed (safety gate for a * destructive operation) * @throws {Error} If confirm is not true */ deleteBackup(confirm: boolean, options?: { backupSources?: boolean; backupFiles?: boolean; backupType?: "tar" | "zip"; }): WikidotResultAsync; /** Delete the site's favicon */ deleteFavicon(): WikidotResultAsync; /** Set the site's favicon from a URL */ setFaviconFromUri(uri: string): WikidotResultAsync; /** Delete the site's iOS home screen icon */ deleteIosIcon(): WikidotResultAsync; /** Set the site's iOS home screen icon from a URL */ setIosIconFromUri(uri: string): WikidotResultAsync; /** Delete the site's Windows tile icon */ deleteWindowsIcon(): WikidotResultAsync; /** Set the site's Windows tile icon from a URL */ setWindowsIconFromUri(uri: string): WikidotResultAsync; /** * Set the site's Windows tile background color. * * The AMC event name is `windowsIconBackroundColor` (missing the "g" in * "Background") -- this is a typo in Wikidot's own JS, kept verbatim here * since the server only recognizes the exact name. */ setWindowsIconBackgroundColor(color: string): WikidotResultAsync; /** Render a newsletter preview */ previewNewsletter(title: string, content: string): WikidotResultAsync<{ title: string; content: string; }>; /** Send a newsletter to site members */ sendNewsletter(title: string, content: string, options?: { admins?: boolean; moderators?: boolean; members?: boolean; others?: UserOrId2[]; }): WikidotResultAsync; /** Shared "fire a single AMC action, discard the response body" helper */ private simpleAction; } /** The 8 change-type flags accepted by changes/SiteChangesListModule's * `options` JSON (distinct from history/PageRevisionListModule's 7 -- this * one adds "new" and lacks nothing page history has). */ type RecentChangesOptionKey2 = "all" | "source" | "title" | "tags" | "move" | "files" | "new" | "meta"; /** * Accessor for site-wide tooling views (Site Tools, Wanted/Orphaned Pages, * Drafts, category page lists, filtered recent changes) * * Access through `Site.tools`. */ declare class ToolsAccessor { readonly site: Site; constructor(site: Site); /** * Get the rendered Site Tools overview page (sitetools/SiteToolsModule) */ getOverview(): WikidotResultAsync; /** * Get the Wanted Pages list (sitetools/WantedPagesModule) * @param options - page: pagination page number. embed: whether to render in embedded (no-chrome) mode */ getWantedPages(options?: { page?: number; embed?: boolean; }): WikidotResultAsync; /** * Get the Orphaned Pages list (sitetools/OrphanedPagesModule) */ getOrphanedPages(): WikidotResultAsync; /** * Get the drafts list, scoped to Site Tools (list/ListDraftsModule) */ getDrafts(): WikidotResultAsync; /** * Get the category list for manage:listpages (list/WikiCategoriesModule) * * Per 50_page.md, the outer page-list body on manage:listpages is static * HTML shipped with the page itself, not fetched via this module; this * wraps the same module for programmatic access. Use `expandCategory()` * to get an individual category's page list. */ getCategories(): WikidotResultAsync; /** * Get the page list for a single category (list/WikiCategoriesPageListModule) * @param categoryId - Category ID to expand * @param options - includeHidden: whether to include hidden pages */ expandCategory(categoryId: number, options?: { includeHidden?: boolean; }): WikidotResultAsync; /** * Get recent changes with server-side filtering (changes/SiteChangesListModule) * * Unlike `SiteChangeCollection.acquire()` (page/site-change.ts, which * parses a different table layout and has no category/page filtering), * this mirrors the sibling wikidot.py repo's `site.tools.get_recent_changes` * and exposes the categoryId/pageId filtering and change-type options * flags Wikidot's own system:recent-changes view uses. * @param options - categoryId: restrict to a single category (all * categories when omitted). pageId: restrict to a single page. options: * change-type filter flags, defaults to `{all: true}`. perpage: items * per page. pageNo: 1-indexed page number */ getRecentChanges(options?: { categoryId?: number; pageId?: number; options?: Partial>; perpage?: 10 | 20 | 50 | 100 | 200; pageNo?: number; }): WikidotResultAsync; } /** * Site data */ interface SiteData { id: number; title: string; unixName: string; domain: string; sslSupported: boolean; } /** * Site class */ declare class Site { readonly client: Client; /** Site ID */ readonly id: number; /** Site title */ readonly title: string; /** UNIX name (e.g., scp-jp) */ readonly unixName: string; /** Domain */ readonly domain: string; /** SSL support flag */ readonly sslSupported: boolean; /** Page accessor */ private _page; /** Pages accessor */ private _pages; /** Forum accessor */ private _forum; /** Member accessor */ private _member; /** Settings accessor */ private _settings; /** Tools accessor */ private _tools; constructor(client: Client, data: SiteData); /** * Get page accessor */ get page(): PageAccessor; /** * Get pages accessor */ get pages(): PagesAccessor; /** * Get forum accessor */ get forum(): ForumAccessor; /** * Get member accessor */ get member(): MemberAccessor; /** * Get settings accessor */ get settings(): SettingsAccessor; /** * Get tools accessor */ get tools(): ToolsAccessor; /** * Get base URL of the site */ getBaseUrl(): string; /** * Execute AMC request to this site * @param bodies - Request body array * @param options - Request options * @returns AMC response array */ amcRequest(bodies: AMCRequestBody[]): WikidotResultAsync; amcRequest(bodies: AMCRequestBody[], options: { returnExceptions: true; }): WikidotResultAsync<(AMCResponse | WikidotError)[]>; /** * Execute AMC request with partial failure tolerance. * Requests are split into batches and failed requests are retried. * @param bodies - Request body array * @param options - Optional batch size and max retries * @returns AMC response array (null for permanently failed requests) */ amcRequestWithRetry(bodies: AMCRequestBody[], options?: { batchSize?: number; maxRetries?: number; }): WikidotResultAsync<(AMCResponse | null)[]>; /** * Execute a single AMC request * @param body - Request body * @returns AMC response */ amcRequestSingle(body: AMCRequestBody): WikidotResultAsync; /** * Get site from UNIX name * @param client - Client * @param unixName - Site UNIX name (e.g., 'scp-jp') * @returns Site */ static fromUnixName(client: Client, unixName: string): WikidotResultAsync; toString(): string; } /** Raw forum group object shape as returned by Wikidot (snake_case wire format) */ type RawForumLayoutGroup = Record & { name?: string; description?: string; visible?: boolean; }; /** * A single forum group entry from `saveForumLayout`'s `groups` array. * * Compares by reference (the default for a TS class), not by field values: * `ForumLayout` looks up a group's index with `indexOf`, and two distinct * groups can legitimately share the same name/description/visible values. */ declare class ForumLayoutGroup { name: string; description: string; visible: boolean; /** * Original response object (holds `group_id` and any other field this * library does not model), kept so `toRaw()` round-trips fields it does * not know about instead of dropping them. Empty for a group created * locally via `ForumLayout.addGroup` (Wikidot assigns `group_id` on save) */ private readonly raw; constructor(data: { name: string; description?: string; visible?: boolean; raw?: RawForumLayoutGroup; }); /** Parse a single `groups` array element */ static fromRaw(data: RawForumLayoutGroup): ForumLayoutGroup; /** Rebuild a `groups` array element for sending back to Wikidot */ toRaw(): RawForumLayoutGroup; } /** Raw forum category object shape as returned by Wikidot (snake_case wire format) */ type RawForumLayoutCategory = Record & { name?: string; description?: string; max_nest_level?: number | null; category_id?: number; number_threads?: number; }; /** * A single forum category entry from `saveForumLayout`'s `categories` array. * Compares by reference, like ForumLayoutGroup. */ declare class ForumLayoutCategory { name: string; description: string; /** 0-10, or null to inherit the forum's site-wide default */ maxNestLevel: number | null; /** undefined for a category created locally that Wikidot has not assigned an ID to yet */ categoryId: number | undefined; /** * Thread count as last reported by Wikidot. Read-only local info, not sent * back on save (kept only so callers can guard against removing a * non-empty category) */ readonly numberThreads: number | undefined; private readonly raw; constructor(data: { name: string; description?: string; maxNestLevel?: number | null; categoryId?: number; numberThreads?: number; raw?: RawForumLayoutCategory; }); /** Parse a single `categories[groupIndex]` array element */ static fromRaw(data: RawForumLayoutCategory): ForumLayoutCategory; /** Rebuild a `categories[groupIndex]` array element for sending back */ toRaw(): RawForumLayoutCategory; } /** * The full forum group/category layout for a site (`saveForumLayout`'s * read-modify-write cycle). * * `categories` is Wikidot's own two-dimensional shape: `categories[i]` holds * the ForumLayoutCategory list belonging to `groups[i]` (same index). * Adding/removing a group keeps both arrays in sync so this invariant is * never violated by calling code. * * `defaultNesting` is informational only (as returned alongside the layout * by `managesite/ManageSiteGetForumLayoutModule`); changing it goes through * `setForumDefaultNesting` / `ManageSiteForumAction/saveForumDefaultNesting` * instead, a separate event from `saveForumLayout` -- this class does not * send it back. * * Never cached: call `ForumLayout.fetch` again for the latest state before * editing, consistent with `SiteCategoryCollection` (30_plan.md D3). */ declare class ForumLayout { readonly site: Site; groups: ForumLayoutGroup[]; categories: ForumLayoutCategory[][]; readonly defaultNesting: number | null; private deletedGroups; private deletedCategoryIds; constructor(site: Site, groups: ForumLayoutGroup[], categories: ForumLayoutCategory[][], defaultNesting: number | null); /** Fetch the current forum layout */ static fetch(site: Site): WikidotResultAsync; /** Look up a group's index by reference, throwing if it isn't part of this layout */ private groupIndex; /** * Add a new (empty) group to the layout * @returns The newly created group (append categories to it via `addCategory`) */ addGroup(name: string, description?: string, visible?: boolean): ForumLayoutGroup; /** * Add a new category to a group in this layout * @param group - Must be a group already in this layout (from `fetch` or `addGroup`) */ addCategory(group: ForumLayoutGroup, name: string, description?: string, maxNestLevel?: number | null): ForumLayoutCategory; /** * Remove a group and every category in it. Destructive and irreversible. * * Wikidot's own UI refuses to delete a non-empty group client-side; * whether the server re-validates this is unconfirmed, so this method * does not attempt the same check -- pass confirm=true to acknowledge you * want the group (and its categories) gone regardless. * @throws {Error} If confirm is not true, or group does not belong to this layout */ removeGroup(group: ForumLayoutGroup, confirm: boolean): void; /** * Remove a single category from a group. Destructive and irreversible. * * Wikidot's own UI refuses to delete a category that still has threads * client-side (see `category.numberThreads`); whether the server * re-validates this is unconfirmed, so pass confirm=true to acknowledge * you want it gone regardless. * @throws {Error} If confirm is not true, or group/category do not belong to this layout */ removeCategory(group: ForumLayoutGroup, category: ForumLayoutCategory, confirm: boolean): void; /** * Send the layout back to Wikidot (`ManageSiteForumAction/saveForumLayout`). * * Sends `groups`, `categories`, `deleted_groups`, `deleted_categories` * every time (Wikidot's own client always builds and submits all four, * whether or not anything was deleted this round). Clears the * pending-deletion lists on success. */ save(): WikidotResultAsync; } /** * Raw forum category object shape as returned by * `managesite/ManageSiteForumPermissionsModule` (13 fields; confirmed by a * live read-only fetch, 2026-07-29). A *different* shape from * {@link RawForumLayoutCategory} (`managesite/ManageSiteGetForumLayoutModule`): * the two modules describe the same underlying forum categories but return * different field sets (this module has `number_posts` / `permissions_default` * / `sort_index` / `site_id` / `per_page_discussion`; the layout module has * `posts` instead of `number_posts` and lacks the other four). Always fetch * from the module matching the event being saved, exactly like the page * `categories` (30_plan.md D3) -- do not mix fields from the two shapes. */ type RawForumCategoryPermissions = Record & { category_id: number; group_id?: number; name?: string; description?: string; number_posts?: number; number_threads?: number; last_post_id?: number | null; permissions_default?: boolean; permissions?: string | null; max_nest_level?: number | null; sort_index?: number | null; site_id?: number; per_page_discussion?: boolean | null; }; /** * A single forum category object from `managesite/ManageSiteForumPermissionsModule`'s * `categories` array. Compares by reference, like ForumLayoutGroup/ForumLayoutCategory. */ declare class ForumCategoryPermissions { readonly categoryId: number; groupId: number | undefined; name: string; description: string; numberPosts: number; numberThreads: number; lastPostId: number | null; /** Whether this category inherits the site-wide default permissions */ permissionsDefault: boolean; /** * null means "inherit the site-wide default permissions" * (40_admin-managesite.md: フォーラムカテゴリの permissions が null の場合は * 「サイト既定を使う」); also see `permissionsDefault` */ permissions: ForumPermissions | null; /** 0-10, or null to inherit the forum's site-wide default */ maxNestLevel: number | null; sortIndex: number | null; siteId: number | undefined; perPageDiscussion: boolean | null; /** * Original response object, kept so `toRaw()` round-trips fields this * library does not (yet) know about instead of dropping them (same * rationale as `SiteCategory`'s `raw`, D3) */ private readonly raw; constructor(data: { categoryId: number; groupId?: number; name?: string; description?: string; numberPosts?: number; numberThreads?: number; lastPostId?: number | null; permissionsDefault?: boolean; permissions?: ForumPermissions | null; maxNestLevel?: number | null; sortIndex?: number | null; siteId?: number; perPageDiscussion?: boolean | null; raw?: RawForumCategoryPermissions; }); /** Parse a single `categories` array element */ static fromRaw(data: RawForumCategoryPermissions): ForumCategoryPermissions; /** Rebuild a `categories` array element for sending back to Wikidot */ toRaw(): RawForumCategoryPermissions; /** * Set this category's forum permissions * @param permissions - New permissions, or null to inherit the site-wide * default (also sets `permissionsDefault` accordingly) */ setPermissions(permissions: ForumPermissions | null): void; } /** * The full `categories` array from `managesite/ManageSiteForumPermissionsModule`. * * Never cached (30_plan.md D3): fetch again before each edit so a stale * snapshot doesn't clobber another admin's concurrent change when saved. */ declare class ForumCategoryPermissionsCollection { readonly site: Site; readonly categories: ForumCategoryPermissions[]; constructor(site: Site, categories: ForumCategoryPermissions[]); /** * Look up a category by ID * @throws {Error} If no category with that ID exists */ get(categoryId: number): ForumCategoryPermissions; get length(): number; /** Fetch the current forum category permissions */ static fetch(site: Site): WikidotResultAsync; /** * Send the full `categories` array back to Wikidot * (`ManageSiteForumAction/saveForumPermissions`) * @param defaultPermissions - Site-wide default forum permissions to also * set. Wikidot's own client reads this from a variable populated at * page-render time, not from any confirmed AMC response field (see * 40_admin-managesite.md "実測(2026-07-29)"), so this library cannot * fetch-and-preserve the current value the way it does for `categories` * -- the key is only sent when the caller explicitly provides a value * here, leaving the site default untouched otherwise */ save(defaultPermissions?: ForumPermissions): WikidotResultAsync; } /** * Forum operations accessor */ declare class ForumAccessor { readonly site: Site; constructor(site: Site); /** * Get forum category list * @returns Category list */ getCategories(): WikidotResultAsync; /** * Get thread * @param threadId - Thread ID * @returns Thread */ getThread(threadId: number): WikidotResultAsync; /** * Get multiple threads * @param threadIds - Array of thread IDs * @returns Thread collection */ getThreads(threadIds: number[]): WikidotResultAsync; /** Enable the forum for this site (`ManageSiteForumAction/activateForum`) */ activate(): WikidotResultAsync; /** * Set the forum's site-wide default reply nesting depth * @param maxNestLevel - 0-10 (0 = flat) */ setDefaultNesting(maxNestLevel: number): WikidotResultAsync; /** * Fetch the current forum group/category layout for editing */ getLayout(): WikidotResultAsync; /** * Fetch the current forum category permissions, mutate them, and save * them back. * * See `forum-admin.ts`'s `updateForumPermissions` for why this must be a * fetch-mutate-save cycle rather than accepting a hand-built override * list (`ManageSiteForumAction/saveForumPermissions` sends the module's * entire fetched `categories` array). * @param mutator - Called with the freshly fetched collection; mutate * categories in place (e.g. `collection.get(categoryId).setPermissions(...)`) * @param defaultPermissions - Site-wide default forum permissions to * also set. Only sent when explicitly provided -- see * `ForumCategoryPermissionsCollection.save`'s docs for why this can't * be fetched and preserved automatically */ updatePermissions(mutator: (categories: ForumCategoryPermissionsCollection) => void, defaultPermissions?: ForumPermissions): WikidotResultAsync; /** * Create a page's discussion (comment) thread if it does not already have one * @param pageId - Numeric page ID (not the page's unix name) * @returns See `ForumThread.createForPage` for why this can be null */ createPageDiscussionThread(pageId: number): WikidotResultAsync; } /** * Site operations accessor */ declare class SiteAccessor { readonly client: Client; constructor(client: Client); /** * Get site by UNIX name * * @param unixName - Site UNIX name (e.g., 'scp-jp') * @returns Site object wrapped in Result type * * @example * ```typescript * const siteResult = await client.site.get('scp-jp'); * if (!siteResult.isOk()) { * throw new Error('Failed to get site'); * } * const site = siteResult.value; * ``` */ get(unixName: string): WikidotResultAsync; /** * Create a new site * @param options - New site options * @returns UNIX name of the created site */ create(options: CreateSiteOptions): WikidotResultAsync; /** * Retrieve every site the account belongs to (all roles) plus deleted sites * @returns All rows of the account's site dashboard */ listSites(): WikidotResultAsync; /** * Accept a pending site invitation * @param invitationId - Invitation ID */ acceptInvitation(invitationId: number): WikidotResultAsync; /** * Discard a pending site invitation without accepting it * @param invitationId - Invitation ID */ throwAwayInvitation(invitationId: number): WikidotResultAsync; /** * Withdraw a pending membership application the account submitted to a site * @param siteId - ID of the site the application was submitted to */ removeApplication(siteId: number): WikidotResultAsync; /** * Restore a deleted site the account administers * @param siteId - ID of the deleted site * @param confirmSiteName - Site name, required as a typed confirmation */ restoreSite(siteId: number, confirmSiteName: string): WikidotResultAsync; /** * Resign the account's admin role on a site * @param siteId - Site ID */ resignAsAdmin(siteId: number): WikidotResultAsync; /** * Resign the account's moderator role on a site * @param siteId - Site ID */ resignAsModerator(siteId: number): WikidotResultAsync; /** * Leave a site the account is a plain member of * @param siteId - Site ID */ signOffAsMember(siteId: number): WikidotResultAsync; /** * Set a site's file storage limit. * * Unmeasured: the form fields of limit-site- could not be captured * during the investigation (no Pro site available). Pass the exact field * names/values as sent by the real form. * @param siteId - Site ID * @param rawFields - Raw form fields to send as-is */ setStorageLimit(siteId: number, rawFields: Record): WikidotResultAsync; } /** * User retrieval options */ interface GetUserOptions { /** Throw error if user not found (default: false) */ raiseWhenNotFound?: boolean; } /** * User operations accessor */ declare class UserAccessor { readonly client: Client; constructor(client: Client); /** * Get user by username * * @param name - Username * @param options - Retrieval options * @returns User wrapped in Result type (null if not found, error if raiseWhenNotFound is true) * * @example * ```typescript * const userResult = await client.user.get('username'); * if (!userResult.isOk()) { * throw new Error('Failed to get user'); * } * const user = userResult.value; * ``` */ get(name: string, options?: GetUserOptions): WikidotResultAsync; /** * Get users from multiple usernames * @param names - Array of usernames * @param options - Retrieval options * @returns User collection (null for non-existent users, error if raiseWhenNotFound is true) */ getMany(names: string[], options?: GetUserOptions): WikidotResultAsync; } /** * Client creation options */ interface ClientOptions { /** Wikidot username */ username?: string; /** Wikidot password */ password?: string; /** Base domain (default: wikidot.com) */ domain?: string; /** AMC configuration override */ amcConfig?: Partial; } /** * Wikidot client * Main entry point of the library */ declare class Client { /** AMC client */ readonly amcClient: AMCClient; /** Base domain */ readonly domain: string; /** User operations accessor */ readonly user: UserAccessor; /** Site operations accessor */ readonly site: SiteAccessor; /** Private message operations accessor */ readonly privateMessage: PrivateMessageAccessor; /** Account (Dashboard) settings/profile operations accessor */ readonly account: AccountAccessor; /** Username of the logged-in user */ private _username; /** Logged-in user */ private _me; /** * Private constructor * Use the create method to create an instance */ private constructor(); /** * Get the username of the logged-in user */ get username(): string | null; /** * Get the logged-in user * Returns null if not logged in */ get me(): User | null; /** * Create a client * * @param options - Client options * @returns Client instance wrapped in Result type * * @example * ```typescript * import { Client } from '@ukwhatn/wikidot'; * * // Create a client * const clientResult = await Client.create({ * username: 'your_username', * password: 'your_password', * }); * * // Result type requires isOk() check before accessing .value * if (!clientResult.isOk()) { * throw new Error('Failed to create client'); * } * const client = clientResult.value; * * // Now you can access client.site, etc. * const siteResult = await client.site.get('scp-jp'); * ``` */ static create(options?: ClientOptions): WikidotResultAsync; /** * Create an unauthenticated client * @param options - Client options (excluding credentials) * @returns Client instance */ static createAnonymous(options?: Omit): Client; /** * Check login status * @returns true if logged in */ isLoggedIn(): boolean; /** * Require login * Returns LoginRequiredError if not logged in * @returns void on success */ requireLogin(): WikidotResult; /** * Close the client * Attempts to logout if a session exists */ close(): WikidotResultAsync; } /** * Account-level (Dashboard) settings/profile operations accessor * * Associated with a client instance, provides access to the sub-accessors covering * the /account/settings and /account/recent dashboards. Access through the * `client.account` property. * * @example * ```typescript * const result = await client.account.settings.setLanguage('ja'); * ``` */ declare class AccountAccessor { readonly client: Client; readonly settings: AccountSettings; readonly profile: AccountProfile; readonly recent: AccountRecentActivity; constructor(client: Client); } /** `from` values accepted by DashboardSettingsAction/saveReceiveMessages */ type PrivateMessageReceiveFrom = "a" | "mf" | "f" | "n"; /** * Operations on `DashboardSettingsAction` and `dashboard/settings/*` * * Covers account-wide settings: password, email, language, private message receive * preferences and block list, toolbars, digest/newsletter/invitation subscriptions, * API key, and Facebook link. Access through `client.account.settings`. * * `saveReceiveMessages` / `blockUser` / `deleteBlock` live under this action * namespace even though they are about private messages: Wikidot groups all of * DashboardSettingsAction here regardless of topic, so this class owns them instead * of PrivateMessageAccessor. */ declare class AccountSettings { private readonly client; constructor(client: Client); private request; private fetchModule; /** * Set who is allowed to send you private messages * @param from - "a" = all registered users, "mf" = co-members + contacts, "f" = contacts only, "n" = nobody */ setReceiveMessages(from: PrivateMessageReceiveFrom): WikidotResultAsync; /** * Add a user to the private message block list * @param user - User to block */ blockUser(user: AbstractUser): WikidotResultAsync; /** * Remove a user from the private message block list * @param user - User to unblock */ unblockUser(user: AbstractUser): WikidotResultAsync; /** * Start the two-step email change flow (step 1). Wikidot sends a confirmation code * to the new address; complete the change with confirmEmailChange(evercode) * @param email - New email address */ startEmailChange(email: string): WikidotResultAsync; /** * Complete the two-step email change flow (step 2) * @param evercode - Confirmation code received by email */ confirmEmailChange(evercode: string): WikidotResultAsync; /** * Change the account password * @param oldPassword - Current password * @param newPassword - New password (sent twice as new_password1/new_password2, matching form(change-password-form)) */ changePassword(oldPassword: string, newPassword: string): WikidotResultAsync; /** * Set the account UI language * @param language - Language code (e.g. "en", "ja") */ setLanguage(language: string): WikidotResultAsync; /** * Set whether to receive the site activity digest email * @param receive - true to subscribe, false to unsubscribe */ setReceiveDigest(receive: boolean): WikidotResultAsync; /** * Set whether to receive the Wikidot newsletter * @param receive - true to subscribe, false to unsubscribe */ setReceiveNewsletter(receive: boolean): WikidotResultAsync; /** * Set whether to receive site invitations * @param receive - true to allow invitations, false to block them */ setReceiveInvitations(receive: boolean): WikidotResultAsync; /** * Set which editor toolbars are shown. * * This is DashboardSettingsAction/saveToolbarsPref (account-wide editor * preference). Do not confuse it with ManageSiteAction/saveToolbarsPref, a * same-named event on a per-site settings action. * @param top - Show the top toolbar * @param bottom - Show the bottom toolbar */ setToolbars(top?: boolean, bottom?: boolean): WikidotResultAsync; /** * Regenerate the account's API key * @param readOnly - true to generate a read-only key. Any other value (including omission) generates a * read-write key; only the literal "r" means read-only on the wire * @returns The newly generated API key */ generateApiKey(readOnly?: boolean): WikidotResultAsync; /** * Link the account with a Facebook account * @param fbUser - Facebook user object, sent using bracket notation (e.g. fbUser[id]=..., fbUser[name]=...) * @returns The "fbaccount" field of the response */ connectFacebook(fbUser: Record): WikidotResultAsync>; /** * Unlink the account from Facebook */ disconnectFacebook(): WikidotResultAsync; /** Fetch the raw HTML of dashboard/settings/DSAccountModule (`#/account`) */ getAccountHtml(): WikidotResultAsync; /** Fetch the raw HTML of dashboard/settings/DSAboutModule (`#/about`) */ getAboutHtml(): WikidotResultAsync; /** Fetch the raw HTML of dashboard/settings/DSForumSignatureModule (`#/forumsignature`) */ getForumSignatureHtml(): WikidotResultAsync; /** Fetch the raw HTML of dashboard/settings/DSToolbarsModule (`#/toolbars`) */ getToolbarsHtml(): WikidotResultAsync; /** Fetch the raw HTML of dashboard/settings/DSNewsletterModule (`#/newsletter`) */ getNewsletterHtml(): WikidotResultAsync; /** * Fetch the raw HTML of dashboard/settings/DSMessagesModule (`#/messages`). * * This is also the module used to re-render the PM settings tab after * setReceiveMessages()/blockUser()/unblockUser(). Do not confuse with * dashboard/messages/DMSettingsModule (the `/account/messages#/settings` tab), * which is a different module that renders the same UI from the messages hub and * returned an empty body when measured. */ getMessagesHtml(): WikidotResultAsync; /** Fetch the raw HTML of dashboard/settings/DSInvitationsModule (`#/invitations`) */ getInvitationsHtml(): WikidotResultAsync; /** Fetch the raw HTML of dashboard/settings/DSFacebookModule (`#/facebook`) */ getFacebookHtml(): WikidotResultAsync; /** * Fetch the raw HTML of dashboard/settings/DSVisibilityModule (`#/visibility`). * * Unmeasured: in the investigation this module returned status "no_permission" * for a non-Pro account, so its availability on free accounts is unconfirmed. */ getVisibilityHtml(): WikidotResultAsync; /** Fetch the raw HTML of dashboard/settings/DSApiModule (`#/api`) */ getApiHtml(): WikidotResultAsync; } /** * Fields accepted by AccountProfile.saveAbout (form(dp-about-form)) */ interface SaveAboutFields { realName?: string; gender?: "m" | "f"; birthdayDay?: string; birthdayMonth?: string; birthdayYear?: string; /** * Free-text bio. The web form caps this at 200 characters; this method does not * enforce that client-side, so an over-length value surfaces as a FormErrorsError * from the server */ about?: string; website?: string; imAim?: string; imGaduGadu?: string; imGoogleTalk?: string; imIcq?: string; imJabber?: string; imMsn?: string; imYahoo?: string; location?: string; } /** * Operations on `DashboardProfileAction` * * Covers the public profile: display name, "about" bio, forum signature, profile * visibility, and avatar. Access through `client.account.profile`. */ declare class AccountProfile { private readonly client; constructor(client: Client); private request; /** * Change the account's display (screen) name * @param screenName - New display name */ changeScreenName(screenName: string): WikidotResultAsync; /** * Save the "about" section of the profile (form(dp-about-form)) * @param fields - About-page fields. Unset fields are omitted (matches an unfilled form field) */ saveAbout(fields?: SaveAboutFields): WikidotResultAsync; /** * Save the forum post signature. The web form caps this at 400 characters; this * method does not enforce that client-side, so an over-length value surfaces as a * FormErrorsError from the server * @param source - Signature source (Wikidot markup) */ saveForumSignature(source: string): WikidotResultAsync; /** * Render a preview of a forum signature without saving it * @param source - Signature source to preview * @returns Rendered HTML preview */ previewForumSignature(source: string): WikidotResultAsync; /** * Save profile visibility settings (form(ap-provilev-form)). * * Unmeasured: dashboard/settings/DSVisibilityModule returned status * "no_permission" for a non-Pro account during the investigation, so the field * names of ap-provilev-form (note: "provilev" is the site's own typo, not * corrected here) could not be captured. Pass the exact field names/values as * sent by the real form. * @param rawFields - Raw form fields to send as-is */ saveProfileVisibility(rawFields: Record): WikidotResultAsync; /** * Delete the account's avatar image */ deleteAvatar(): WikidotResultAsync; /** * Set the account's avatar from an image URL * @param uri - URL of the image to use as the new avatar * @returns Response containing "status", "im48", "im16" (avatar URLs at each size) */ uploadAvatarFromUri(uri: string): WikidotResultAsync<{ status: unknown; im48: unknown; im16: unknown; }>; } /** * Operations on the `/account/recent` dashboard tab * * Covers the logged-in account's own recent page edits and forum posts, across all * sites it belongs to. Access through `client.account.recent`. */ declare class AccountRecentActivity { private readonly client; private changesUserIdCache; private postsUserIdCache; constructor(client: Client); /** * Internal helper to fetch a hidden `userId` field from a shell module. * * `www.wikidot.com` pages do not expose `WIKIREQUEST.info.userId` (unlike a * site's own pages), and userinfo/UserChangesListModule / * userinfo/UserRecentPostsListModule respond with status "not_ok" and an empty * body if `userId` is omitted. The real UI reads it from a hidden input on the * shell module that renders the tab (userinfo/UserChangesModule / * userinfo/UserRecentPostsModule) before requesting the list module. * @param moduleName - Shell module to fetch * @param elementId - id of the hidden input holding the user ID * @returns The account's own user ID */ private fetchHiddenUserId; private changesUserId; private postsUserId; /** * Get the account's own recent page edits, across all sites. * * Wraps userinfo/UserChangesListModule, fetching pages until exhausted or * `limit` is reached. * @param options - Filter flags. Keys must be a subset of RECENT_CHANGES_OPTION_KEYS; unlike * history/PageHistoryModule's options, there is no "tags" key here * @param limit - Maximum number of entries to retrieve. If omitted, retrieves all * @returns List of change history (in descending order by date) */ getChanges(options?: RecentChangesOptions, limit?: number): WikidotResultAsync; /** * Get the account's own recent forum posts, across all sites. * * Wraps userinfo/UserRecentPostsListModule, fetching pages until exhausted or * `limit` is reached. * @param limit - Maximum number of entries to retrieve. If omitted, retrieves all * @returns List of recent posts (in descending order by date) */ getPosts(limit?: number): WikidotResultAsync; } /** * Extract the source text from a Wikidot source module response * * Converts ` ` entities to ASCII spaces while keeping literal * U+00A0 characters in the source as they are. * * @param body - HTML body of viewsource/ViewSourceModule or history/PageSourceModule * @returns Text of the `div.page-source` element, or null if the element is not found */ declare function parsePageSource(body: string): string | null; import * as cheerio4 from "cheerio"; import { AnyNode as AnyNode2 } from "domhandler"; /** * Parse printuser element and return user object * * @param client - Wikidot client * @param elem - Element to parse (element with printuser class) * @returns Parsed user object */ declare function parseUser2(client: ClientRef, elem: cheerio4.Cheerio): AbstractUser; /** * Parse date from odate element * * @param elem - Element to parse (odate element) * @returns Parsed Date, or null on parse failure */ declare function parseOdate(elem: cheerio4.Cheerio): Date | null; export { AMCClient, AMCClientRef, AMCConfig, AMCError, AMCHeader, AMCHeaderRef, AMCHttpError, AMCRequestBody, AMCRequestOptions, AMCResponse, AMCSuccessResponse, AbstractUser, AccessPolicySettings, AccountAccessor, AccountProfile, AccountRecentActivity, AccountSettings, Actor, AnonymousUser, AuthClientContext, Client, ClientOptions, ClientRef, Contact, ContactData, CreateSiteOptions, DEFAULT_AMC_CONFIG, DEFAULT_MODULE_BODY, DEFAULT_PER_PAGE, DashboardSite, DashboardSiteData, DashboardSites, DateTimeString, DeletedUser, DomainSettings, ForbiddenError, FormErrorsError, ForumAccessor, ForumCategory, ForumCategoryCollection, ForumCategoryData, ForumCategoryRef, ForumPermissions, ForumPermissionsData, ForumPost, ForumPostCollection, ForumPostData, ForumPostRef, ForumPostRevision, ForumPostRevisionCollection, ForumPostRevisionData, ForumThread, ForumThreadCollection, ForumThreadData, ForumThreadRef, GeneralSettings, GetUserOptions, GuestUser, LogHandler, LogLevel, Logger, LoginRequiredError, MemberAccessor, NewSitePrivacy, NewSiteTemplate, NoElementError, NotFoundException, OmittableBodyValue, Page, PageAccessor, PageCollection, PageData, PageFile, PageFileCollection, PageFileData, PageFullname, PageMeta, PageMetaCollection, PageMetaData, PagePermissions, PagePermissionsData, PageRef, PageRevision, PageRevisionCollection, PageRevisionData, PageSource, PageSourceData, PageVote, PageVoteCollection, PageVoteData, PagesAccessor, PrivateMessage, PrivateMessageAccessor, PrivateMessageCollection, PrivateMessageData, PrivateMessageFetchFailure, PrivateMessageInbox, PrivateMessageReceiveFrom, PrivateMessageSentBox, QMCPage, QMCUser, QuickModule, QuickModuleName, RECENT_CHANGES_OPTION_KEYS, RatingSettings, RatingSettingsData, RecentChangesOptionKey, RecentChangesOptions, RecentPost, RecentPostData, ResponseDataError, SaveAboutFields, SearchPagesQuery, SearchPagesQueryParams, SessionCreateError, SessionError, SettingsAccessor, Site, SiteAccessor, SiteApplication, SiteApplicationData, SiteCategory, SiteCategoryCollection, SiteChange, SiteChangeCollection, SiteChangeData, SiteData, SiteJoinApplication, SiteJoinApplicationData, SiteLicense, SiteMember, SiteMemberData, SiteRef, SiteUnixName, TargetError, TargetExistsError, UnexpectedError, User, UserAccessor, UserChange, UserChangeData, UserCollection, UserData, UserIdentifier, UserProfile, UserProfileData, UserSiteEntry, UserSiteEntryData, UserType, WikidotError, WikidotResult, WikidotResultAsync, WikidotStatusError, WikidotUser, addContact2 as addContact, addContactViaProfile2 as addContactViaProfile, amcResponseSchema, checkbox, combineResults, consoleHandler, flag, fromPromise, getApplicationDetailHtml2 as getApplicationDetailHtml, getApplications2 as getApplications, getContacts2 as getContacts, getContactsListHtml2 as getContactsListHtml, getInvitationDetailHtml2 as getInvitationDetailHtml, getInvitationsHtml2 as getInvitationsHtml, getLogger, isSuccessResponse, jsonParam, logger, login, logout, maskSensitiveData, memberLookup, nullHandler, omitFalsy, pageLookup, parseOdate, parsePageSource, parseUser2 as parseUser, removeContact2 as removeContact, requireBody, setupConsoleHandler, userLookup, wdErr, wdErrAsync, wdOk, wdOkAsync };