Comparing version 0.8.3 to 0.9.0
@@ -21,2 +21,3 @@ /** | ||
*/ | ||
/// <reference types="node" /> | ||
/** | ||
@@ -32,162 +33,22 @@ * Attributions: | ||
*/ | ||
import { AxiosRequestConfig } from 'axios'; | ||
import { AxiosRequestConfig, AxiosResponse } from 'axios'; | ||
import tough = require('tough-cookie'); | ||
import OAuth = require('oauth-1.0a'); | ||
import { log, updateLoggingConfig } from './log'; | ||
import { MwnError, MwnErrorConfig } from "./error"; | ||
import type { Link, CategoryLink, FileLink, PageLink, Template, TemplateConfig, Section } from "./wikitext"; | ||
import type { revisionprop, logprop } from './page'; | ||
import type { LogEvent, UserContribution } from "./user"; | ||
import type { recentchangeProps } from "./eventstream"; | ||
import type { timeUnit } from "./date"; | ||
import type { ApiDeleteParams, ApiEditPageParams, ApiMoveParams, ApiParseParams, ApiPurgeParams, ApiQueryAllPagesParams, ApiQueryCategoryMembersParams, ApiQuerySearchParams, ApiRollbackParams, ApiUndeleteParams, ApiUploadParams, ApiEmailUserParams, ApiQueryRevisionsParams, ApiQueryLogEventsParams, ApiQueryBacklinkspropParams, ApiQueryUserContribsParams, ApiBlockParams, ApiUnblockParams } from "./api_params"; | ||
import { link, template, table } from './static_utils'; | ||
import { MwnDate } from './date'; | ||
import { MwnTitle } from './title'; | ||
import { MwnPage } from './page'; | ||
import { MwnWikitext } from './wikitext'; | ||
import { MwnUser } from './user'; | ||
import { MwnCategory } from './category'; | ||
import { MwnFile } from './file'; | ||
import { MwnStream } from './eventstream'; | ||
export { MwnDate, MwnTitle, MwnPage, MwnFile, MwnCategory, MwnWikitext, MwnUser, MwnStream }; | ||
import type { ApiDeleteParams, ApiEditPageParams, ApiMoveParams, ApiParseParams, ApiPurgeParams, ApiQueryAllPagesParams, ApiQueryCategoryMembersParams, ApiQuerySearchParams, ApiQueryUserInfoParams, ApiRollbackParams, ApiUndeleteParams, ApiUploadParams } from "./api_params"; | ||
import { sleep } from './utils'; | ||
export interface RawRequestParams extends AxiosRequestConfig { | ||
retryNumber?: number; | ||
} | ||
export interface MwnTitle { | ||
title: string; | ||
namespace: number; | ||
fragment: string; | ||
getNamespaceId(): number; | ||
getMain(): string; | ||
getMainText(): string; | ||
getPrefixedDb(): string; | ||
getPrefixedText(): string; | ||
getFragment(): string | null; | ||
isTalkPage(): boolean; | ||
getTalkPage(): MwnTitle | null; | ||
getSubjectPage(): MwnTitle | null; | ||
canHaveTalkPage(): boolean; | ||
getExtension(): string | null; | ||
getDotExtension(): string; | ||
toString(): string; | ||
toText(): string; | ||
} | ||
export interface MwnPage extends MwnTitle { | ||
data: any; | ||
getTalkPage(): MwnPage; | ||
getSubjectPage(): MwnPage; | ||
text(): Promise<string>; | ||
categories(): Promise<{ | ||
sortkey: string; | ||
category: string; | ||
hidden: boolean; | ||
}[]>; | ||
templates(): Promise<{ | ||
ns: number; | ||
title: string; | ||
exists: boolean; | ||
}[]>; | ||
links(): Promise<{ | ||
ns: number; | ||
title: string; | ||
exists: boolean; | ||
}[]>; | ||
backlinks(): Promise<string[]>; | ||
transclusions(): Promise<string[]>; | ||
images(): Promise<string[]>; | ||
externallinks(): Promise<string[]>; | ||
subpages(options?: ApiQueryAllPagesParams): Promise<string[]>; | ||
isRedirect(): Promise<boolean>; | ||
getRedirectTarget(): Promise<string>; | ||
isRedirect(): Promise<boolean>; | ||
getRedirectTarget(): Promise<string>; | ||
getCreator(): Promise<string>; | ||
getDeletingAdmin(): Promise<string>; | ||
getDescription(customOptions?: any): Promise<string>; | ||
history(props: revisionprop[] | revisionprop, limit: number, customOptions?: ApiQueryRevisionsParams): Promise<object[]>; | ||
logs(props: logprop | logprop[], limit?: number, type?: string, customOptions?: ApiQueryLogEventsParams): Promise<object[]>; | ||
edit(transform: ((rev: { | ||
content: string; | ||
timestamp: string; | ||
}) => string | object)): Promise<any>; | ||
save(text: string, summary?: string, options?: ApiEditPageParams): Promise<any>; | ||
newSection(header: string, message: string, additionalParams?: ApiEditPageParams): Promise<any>; | ||
move(target: string, summary: string, options?: ApiMoveParams): Promise<any>; | ||
delete(summary: string, options?: ApiDeleteParams): Promise<any>; | ||
undelete(summary: string, options?: ApiUndeleteParams): Promise<any>; | ||
purge(options?: ApiPurgeParams): Promise<any>; | ||
} | ||
export interface MwnFile extends MwnPage { | ||
getName(): string; | ||
getNameText(): string; | ||
usages(options?: ApiQueryBacklinkspropParams): Promise<{ | ||
pageid: number; | ||
title: string; | ||
redirect: boolean; | ||
}[]>; | ||
download(localname: string): void; | ||
} | ||
export interface MwnCategory extends MwnPage { | ||
members(options?: ApiQueryCategoryMembersParams): Promise<{ | ||
pageid: number; | ||
ns: number; | ||
title: string; | ||
}[]>; | ||
pages(options?: ApiQueryCategoryMembersParams): Promise<{ | ||
pageid: number; | ||
ns: number; | ||
title: string; | ||
}[]>; | ||
subcats(options?: ApiQueryCategoryMembersParams): Promise<{ | ||
pageid: number; | ||
ns: number; | ||
title: string; | ||
}[]>; | ||
files(options?: ApiQueryCategoryMembersParams): Promise<{ | ||
pageid: number; | ||
ns: number; | ||
title: string; | ||
}[]>; | ||
} | ||
export interface MwnStream { | ||
addListener(filter: ((data: any) => boolean) | any, action: (data: any) => void): void; | ||
} | ||
export interface MwnUser { | ||
username: string; | ||
userpage: MwnPage; | ||
talkpage: MwnPage; | ||
contribs(options?: ApiQueryUserContribsParams): Promise<UserContribution[]>; | ||
contribsGen(options?: ApiQueryUserContribsParams): AsyncGenerator<UserContribution>; | ||
logs(options?: ApiQueryLogEventsParams): Promise<LogEvent[]>; | ||
logsGen(options?: ApiQueryLogEventsParams): AsyncGenerator<LogEvent>; | ||
info(props?: string | string[]): Promise<any>; | ||
globalinfo(props?: ("groups" | "rights" | "merged" | "unattached" | "editcount")[]): Promise<any>; | ||
sendMessage(header: string, message: string): Promise<any>; | ||
email(subject: string, message: string, options?: ApiEmailUserParams): Promise<any>; | ||
block(options: ApiBlockParams): Promise<any>; | ||
unblock(options: ApiUnblockParams): Promise<any>; | ||
} | ||
export interface MwnWikitext { | ||
text: string; | ||
links: Array<PageLink>; | ||
templates: Array<Template>; | ||
files: Array<FileLink>; | ||
categories: Array<CategoryLink>; | ||
sections: Array<Section>; | ||
parseLinks(): void; | ||
parseTemplates(config: TemplateConfig): Template[]; | ||
removeEntity(entity: Link | Template): void; | ||
parseSections(): Section[]; | ||
unbind(prefix: string, postfix: string): void; | ||
rebind(): string; | ||
getText(): string; | ||
apiParse(options: ApiParseParams): Promise<string>; | ||
} | ||
export interface MwnDate extends Date { | ||
isValid(): boolean; | ||
isBefore(date: Date | MwnDate): boolean; | ||
isAfter(date: Date | MwnDate): boolean; | ||
getUTCMonthName(): string; | ||
getUTCMonthNameAbbrev(): string; | ||
getMonthName(): string; | ||
getMonthNameAbbrev(): string; | ||
getUTCDayName(): string; | ||
getUTCDayNameAbbrev(): string; | ||
getDayName(): string; | ||
getDayNameAbbrev(): string; | ||
add(number: number, unit: timeUnit): MwnDate; | ||
subtract(number: number, unit: timeUnit): MwnDate; | ||
format(formatstr: string, zone?: number | 'utc' | 'system'): string; | ||
calendar(zone?: number | 'utc' | 'system'): string; | ||
} | ||
export interface MwnOptions { | ||
@@ -217,3 +78,2 @@ silent?: boolean; | ||
suppressInvalidDateWarning?: boolean; | ||
semlog?: object; | ||
} | ||
@@ -231,3 +91,3 @@ declare type editConfigType = { | ||
}; | ||
export declare type ApiResponse = any; | ||
export declare type ApiResponse = Record<string, any>; | ||
export interface ApiPage { | ||
@@ -270,3 +130,6 @@ title: string; | ||
/** | ||
* Bot instance's edit token. | ||
* Bot instance's edit token. Initially set as an invalid token string | ||
* so that the badtoken handling logic is invoked if the token is | ||
* not set before a query is sent. | ||
* @type {string} | ||
*/ | ||
@@ -279,9 +142,27 @@ csrfToken: string; | ||
readonly defaultOptions: MwnOptions; | ||
/** | ||
* Actual, current options of the bot instance | ||
* Mix of the default options, the custom options and later changes | ||
* @type {Object} | ||
*/ | ||
options: MwnOptions; | ||
/** | ||
* Cookie jar for the bot instance - holds session and login cookies | ||
* @type {tough.CookieJar} | ||
*/ | ||
cookieJar: tough.CookieJar; | ||
static requestDefaults: RawRequestParams; | ||
/** | ||
* Request options for the axios library. | ||
* Change the defaults using setRequestOptions() | ||
* @type {Object} | ||
*/ | ||
requestOptions: RawRequestParams; | ||
/** | ||
* Emergency shutoff config | ||
* @type {{hook: NodeJS.Timeout, state: boolean}} | ||
*/ | ||
shutoff: { | ||
state: boolean; | ||
hook: ReturnType<typeof setInterval>; | ||
hook: NodeJS.Timeout; | ||
}; | ||
@@ -291,98 +172,8 @@ hasApiHighLimit: boolean; | ||
usingOAuth: boolean; | ||
title: { | ||
new (title: string, namespace?: number): MwnTitle; | ||
idNameMap: { | ||
[namespaceId: number]: string; | ||
}; | ||
nameIdMap: { | ||
[namespaceName: string]: number; | ||
}; | ||
legaltitlechars: string; | ||
caseSensitiveNamespaces: Array<number>; | ||
processNamespaceData(json: { | ||
query: { | ||
general: { | ||
legaltitlechars: string; | ||
}; | ||
namespaces: { | ||
name: string; | ||
id: number; | ||
canonical: boolean; | ||
case: string; | ||
}[]; | ||
namespacealiases: { | ||
alias: string; | ||
id: number; | ||
}[]; | ||
}; | ||
}): void; | ||
checkData(): void; | ||
newFromText(title: string, namespace?: number): MwnTitle | null; | ||
makeTitle(namespace: number, title: string): MwnTitle | null; | ||
isTalkNamespace(namespaceId: number): boolean; | ||
phpCharToUpper(chr: string): string; | ||
}; | ||
page: { | ||
new (title: MwnTitle | string, namespace?: number): MwnPage; | ||
}; | ||
file: { | ||
new (title: MwnTitle | string): MwnFile; | ||
}; | ||
category: { | ||
new (title: MwnTitle | string): MwnCategory; | ||
}; | ||
stream: { | ||
new (streams: string | string[], config: { | ||
since?: Date | MwnDate | string; | ||
onopen?: (() => void); | ||
onerror?: ((evt: MessageEvent) => void); | ||
}): MwnStream; | ||
recentchange(filter: Partial<recentchangeProps> | ((data: recentchangeProps) => boolean), action: ((data: recentchangeProps) => void)): MwnStream; | ||
}; | ||
date: { | ||
new (...args: any[]): MwnDate; | ||
loadLocaleData(data: any): void; | ||
getMonthName(monthNum: number): string; | ||
getMonthNameAbbrev(monthNum: number): string; | ||
getDayName(dayNum: number): string; | ||
getDayNameAbbrev(dayNum: number): string; | ||
}; | ||
wikitext: { | ||
new (text: string): MwnWikitext; | ||
parseTemplates(wikitext: string, config: TemplateConfig): Template[]; | ||
parseTable(text: string): { | ||
[column: string]: string; | ||
}[]; | ||
parseSections(text: string): Section[]; | ||
}; | ||
user: { | ||
new (username: string): MwnUser; | ||
}; | ||
static Error: typeof MwnError; | ||
static log: (data: any) => void; | ||
static link: (target: string | MwnTitle, displaytext?: string) => string; | ||
static template: (title: string | MwnTitle, parameters?: { | ||
[parameter: string]: string; | ||
}) => string; | ||
static table: { | ||
new (config?: { | ||
plain?: boolean; | ||
sortable?: boolean; | ||
style?: string; | ||
multiline?: boolean; | ||
}): { | ||
text: string; | ||
multiline: boolean; | ||
_makecell(cell: string | { | ||
[attribute: string]: string; | ||
}, isHeader?: boolean): string; | ||
addHeaders(headers: (string | { | ||
[attribute: string]: string; | ||
})[]): void; | ||
addRow(fields: string[], attributes?: { | ||
[attribute: string]: string; | ||
}): void; | ||
getText(): string; | ||
}; | ||
}; | ||
static log: typeof log; | ||
static setLoggingConfig: typeof updateLoggingConfig; | ||
static link: typeof link; | ||
static template: typeof template; | ||
static table: typeof table; | ||
static util: { | ||
@@ -397,4 +188,35 @@ escapeRegExp: (str: string) => string; | ||
}; | ||
/***************** CONSTRUCTOR ********************/ | ||
/** | ||
* Title class associated with the bot instance | ||
*/ | ||
title: import("./title").MwnTitleStatic; | ||
/** | ||
* Page class associated with the bot instance | ||
*/ | ||
page: import("./page").MwnPageStatic; | ||
/** | ||
* Category class associated with the bot instance | ||
*/ | ||
category: import("./category").MwnCategoryStatic; | ||
/** | ||
* File class associated with the bot instance | ||
*/ | ||
file: import("./file").MwnFileStatic; | ||
/** | ||
* User class associated with the bot instance | ||
*/ | ||
user: import("./user").MwnUserStatic; | ||
/** | ||
* Wikitext class associated with the bot instance | ||
*/ | ||
wikitext: import("./wikitext").MwnWikitextStatic; | ||
/** | ||
* Stream class associated with the bot instance | ||
*/ | ||
stream: import("./eventstream").MwnStreamStatic; | ||
/** | ||
* Date class associated with the bot instance | ||
*/ | ||
date: import("./date").MwnDateStatic; | ||
/** | ||
* Constructs a new bot instance | ||
@@ -446,3 +268,3 @@ * It is advised to create one bot instance for every API to use | ||
*/ | ||
_usingOAuth(): boolean; | ||
private _usingOAuth; | ||
/** | ||
@@ -456,3 +278,3 @@ * Initialize OAuth instance | ||
*/ | ||
makeOAuthHeader(params: OAuth.RequestOptions): OAuth.Header; | ||
private makeOAuthHeader; | ||
/************ CORE REQUESTS ***************/ | ||
@@ -465,3 +287,3 @@ /** | ||
*/ | ||
rawRequest(requestOptions: RawRequestParams): Promise<any>; | ||
rawRequest(requestOptions: RawRequestParams): Promise<AxiosResponse>; | ||
/** | ||
@@ -474,3 +296,3 @@ * Executes a request with the ability to use custom parameters and custom | ||
*/ | ||
request(params: ApiParams, customRequestOptions?: RawRequestParams): Promise<any>; | ||
request(params: ApiParams, customRequestOptions?: RawRequestParams): Promise<ApiResponse>; | ||
private dieWithError; | ||
@@ -494,2 +316,8 @@ /************** CORE FUNCTIONS *******************/ | ||
/** | ||
* Get basic info about the logged-in user | ||
* @param [options] | ||
* @returns {Promise} | ||
*/ | ||
userinfo(options?: ApiQueryUserInfoParams): Promise<any>; | ||
/** | ||
* Gets namespace-related information for use in title nested class. | ||
@@ -889,3 +717,3 @@ * This need not be used if login() is being used. This is for cases | ||
*/ | ||
sleep(duration: number): Promise<void>; | ||
sleep: typeof sleep; | ||
/** | ||
@@ -895,7 +723,6 @@ * Returns a promise rejected with an error object | ||
* @param {string} errorCode | ||
* @returns {Promise<mwn.Error>} | ||
* @returns {Promise} | ||
*/ | ||
rejectWithErrorCode(errorCode: string): Promise<MwnError>; | ||
rejectWithError(errorConfig: MwnErrorConfig): Promise<MwnError>; | ||
rejectWithErrorCode(errorCode: string): Promise<never>; | ||
rejectWithError(errorConfig: MwnErrorConfig): Promise<never>; | ||
} | ||
export {}; |
@@ -0,1 +1,19 @@ | ||
import type { mwn, MwnPage, MwnTitle } from './bot'; | ||
import { ApiQueryCategoryMembersParams } from "./api_params"; | ||
declare type ApiPageInfo = { | ||
pageid: number; | ||
ns: number; | ||
title: string; | ||
}; | ||
export interface MwnCategoryStatic { | ||
new (title: MwnTitle | string): MwnCategory; | ||
} | ||
export interface MwnCategory extends MwnPage { | ||
members(options?: ApiQueryCategoryMembersParams): Promise<ApiPageInfo[]>; | ||
membersGen(options?: ApiQueryCategoryMembersParams): AsyncGenerator<ApiPageInfo>; | ||
pages(options?: ApiQueryCategoryMembersParams): Promise<ApiPageInfo[]>; | ||
subcats(options?: ApiQueryCategoryMembersParams): Promise<ApiPageInfo[]>; | ||
files(options?: ApiQueryCategoryMembersParams): Promise<ApiPageInfo[]>; | ||
} | ||
export default function (bot: mwn): MwnCategoryStatic; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
module.exports = function (bot) { | ||
function default_1(bot) { | ||
class Category extends bot.page { | ||
@@ -31,2 +31,16 @@ /** | ||
} | ||
async *membersGen(options) { | ||
let continuedQuery = bot.continuedQueryGen({ | ||
"action": "query", | ||
"list": "categorymembers", | ||
"cmtitle": "Category:" + this.title, | ||
"cmlimit": "max", | ||
...options | ||
}); | ||
for await (let json of continuedQuery) { | ||
for (let result of json.query.categorymembers) { | ||
yield result; | ||
} | ||
} | ||
} | ||
/** | ||
@@ -61,2 +75,3 @@ * Get all pages in the category - does not include subcategories or files | ||
return Category; | ||
}; | ||
} | ||
exports.default = default_1; |
@@ -0,1 +1,37 @@ | ||
import type { mwn } from "./bot"; | ||
/** | ||
* Wrapper around the native JS Date() for ease of | ||
* handling dates, as well as a constructor that | ||
* can parse MediaWiki dating formats. | ||
*/ | ||
export interface MwnDateStatic { | ||
new (...args: any[]): MwnDate; | ||
getMonthName(monthNum: number): string; | ||
getMonthNameAbbrev(monthNum: number): string; | ||
getDayName(dayNum: number): string; | ||
getDayNameAbbrev(dayNum: number): string; | ||
} | ||
export interface MwnDate extends Date { | ||
isValid(): boolean; | ||
isBefore(date: Date | MwnDate): boolean; | ||
isAfter(date: Date | MwnDate): boolean; | ||
getUTCMonthName(): string; | ||
getUTCMonthNameAbbrev(): string; | ||
getMonthName(): string; | ||
getMonthNameAbbrev(): string; | ||
getUTCDayName(): string; | ||
getUTCDayNameAbbrev(): string; | ||
getDayName(): string; | ||
getDayNameAbbrev(): string; | ||
add(number: number, unit: timeUnit): MwnDate; | ||
subtract(number: number, unit: timeUnit): MwnDate; | ||
format(formatstr: string, zone?: number | 'utc' | 'system'): string; | ||
calendar(zone?: number | 'utc' | 'system'): string; | ||
} | ||
/** | ||
* Wrapper around the native JS Date() for ease of | ||
* handling dates, as well as a constructor that | ||
* can parse MediaWiki dating formats. | ||
*/ | ||
export default function (bot: mwn): MwnDateStatic; | ||
declare const unitMap: { | ||
@@ -2,0 +38,0 @@ seconds: string; |
@@ -8,4 +8,4 @@ "use strict"; | ||
*/ | ||
module.exports = function (bot) { | ||
class MwnDate extends Date { | ||
function default_1(bot) { | ||
class XDate extends Date { | ||
/** | ||
@@ -51,24 +51,24 @@ * Create a date object. MediaWiki timestamp format is also acceptable, | ||
getUTCMonthName() { | ||
return MwnDate.localeData.months[this.getUTCMonth()]; | ||
return XDate.localeData.months[this.getUTCMonth()]; | ||
} | ||
getUTCMonthNameAbbrev() { | ||
return MwnDate.localeData.monthsShort[this.getUTCMonth()]; | ||
return XDate.localeData.monthsShort[this.getUTCMonth()]; | ||
} | ||
getMonthName() { | ||
return MwnDate.localeData.months[this.getMonth()]; | ||
return XDate.localeData.months[this.getMonth()]; | ||
} | ||
getMonthNameAbbrev() { | ||
return MwnDate.localeData.monthsShort[this.getMonth()]; | ||
return XDate.localeData.monthsShort[this.getMonth()]; | ||
} | ||
getUTCDayName() { | ||
return MwnDate.localeData.days[this.getUTCDay()]; | ||
return XDate.localeData.days[this.getUTCDay()]; | ||
} | ||
getUTCDayNameAbbrev() { | ||
return MwnDate.localeData.daysShort[this.getUTCDay()]; | ||
return XDate.localeData.daysShort[this.getUTCDay()]; | ||
} | ||
getDayName() { | ||
return MwnDate.localeData.days[this.getDay()]; | ||
return XDate.localeData.days[this.getDay()]; | ||
} | ||
getDayNameAbbrev() { | ||
return MwnDate.localeData.daysShort[this.getDay()]; | ||
return XDate.localeData.daysShort[this.getDay()]; | ||
} | ||
@@ -81,3 +81,3 @@ /** | ||
* @throws {Error} if invalid or unsupported unit is given | ||
* @returns {MwnDate} | ||
* @returns {XDate} | ||
*/ | ||
@@ -100,3 +100,3 @@ add(number, unit) { | ||
* @throws {Error} if invalid or unsupported unit is given | ||
* @returns {MwnDate} | ||
* @returns {XDate} | ||
*/ | ||
@@ -121,7 +121,7 @@ subtract(number, unit) { | ||
if (!zone || zone === 'utc') { | ||
udate = new MwnDate(this.getTime()).add(this.getTimezoneOffset(), 'minutes'); | ||
udate = new XDate(this.getTime()).add(this.getTimezoneOffset(), 'minutes'); | ||
} | ||
else if (typeof zone === 'number') { | ||
// convert to utc, then add the utc offset given | ||
udate = new MwnDate(this.getTime()).add(this.getTimezoneOffset() + zone, 'minutes'); | ||
udate = new XDate(this.getTime()).add(this.getTimezoneOffset() + zone, 'minutes'); | ||
} | ||
@@ -171,19 +171,15 @@ const pad = function (num) { | ||
case dateDiff === 0: | ||
return this.format(MwnDate.localeData.relativeTimes.thisDay, zone); | ||
return this.format(XDate.localeData.relativeTimes.thisDay, zone); | ||
case dateDiff === 1: | ||
return this.format(MwnDate.localeData.relativeTimes.prevDay, zone); | ||
return this.format(XDate.localeData.relativeTimes.prevDay, zone); | ||
case dateDiff > 0 && dateDiff < 7: | ||
return this.format(MwnDate.localeData.relativeTimes.pastWeek, zone); | ||
return this.format(XDate.localeData.relativeTimes.pastWeek, zone); | ||
case dateDiff === -1: | ||
return this.format(MwnDate.localeData.relativeTimes.nextDay, zone); | ||
return this.format(XDate.localeData.relativeTimes.nextDay, zone); | ||
case dateDiff < 0 && dateDiff > -7: | ||
return this.format(MwnDate.localeData.relativeTimes.thisWeek, zone); | ||
return this.format(XDate.localeData.relativeTimes.thisWeek, zone); | ||
default: | ||
return this.format(MwnDate.localeData.relativeTimes.other, zone); | ||
return this.format(XDate.localeData.relativeTimes.other, zone); | ||
} | ||
} | ||
// TODO: allow easier i18n | ||
static loadLocaleData(data) { | ||
MwnDate.localeData = data; | ||
} | ||
/** | ||
@@ -193,3 +189,3 @@ * Get month name from month number (1-indexed) | ||
static getMonthName(monthNum) { | ||
return MwnDate.localeData.months[monthNum - 1]; | ||
return XDate.localeData.months[monthNum - 1]; | ||
} | ||
@@ -200,3 +196,3 @@ /** | ||
static getMonthNameAbbrev(monthNum) { | ||
return MwnDate.localeData.monthsShort[monthNum - 1]; | ||
return XDate.localeData.monthsShort[monthNum - 1]; | ||
} | ||
@@ -207,3 +203,3 @@ /** | ||
static getDayName(dayNum) { | ||
return MwnDate.localeData.days[dayNum - 1]; | ||
return XDate.localeData.days[dayNum - 1]; | ||
} | ||
@@ -214,6 +210,6 @@ /** | ||
static getDayNameAbbrev(dayNum) { | ||
return MwnDate.localeData.daysShort[dayNum - 1]; | ||
return XDate.localeData.daysShort[dayNum - 1]; | ||
} | ||
} | ||
MwnDate.localeData = { | ||
XDate.localeData = { | ||
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], | ||
@@ -233,7 +229,7 @@ monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], | ||
// Tweak set* methods (setHours, setUTCMinutes, etc) so that they | ||
// return the modified MwnDate object rather than the seconds-since-epoch | ||
// return the modified XDate object rather than the seconds-since-epoch | ||
// representation which is what JS Date() gives | ||
Object.getOwnPropertyNames(Date.prototype).filter(f => f.startsWith('set')).forEach(func => { | ||
let proxy = MwnDate.prototype[func]; | ||
MwnDate.prototype[func] = function (...args) { | ||
let proxy = XDate.prototype[func]; | ||
XDate.prototype[func] = function (...args) { | ||
proxy.call(this, ...args); | ||
@@ -243,4 +239,5 @@ return this; | ||
}); | ||
return MwnDate; | ||
}; | ||
return XDate; | ||
} | ||
exports.default = default_1; | ||
// mapping time units with getter/setter function names for add and subtract | ||
@@ -247,0 +244,0 @@ const unitMap = { |
/// <reference types="node" /> | ||
import type { RawRequestParams } from "./bot"; | ||
export declare type MwnErrorConfig = { | ||
@@ -6,3 +7,3 @@ code: string; | ||
response?: Record<string, unknown>; | ||
request?: Record<string, unknown>; | ||
request?: RawRequestParams; | ||
disableRetry?: boolean; | ||
@@ -14,3 +15,3 @@ }; | ||
*/ | ||
constructor(config: MwnErrorConfig); | ||
constructor(config: Error | MwnErrorConfig); | ||
static MissingPage: { | ||
@@ -17,0 +18,0 @@ new (config?: Partial<MwnErrorConfig>): { |
@@ -9,2 +9,5 @@ "use strict"; | ||
constructor(config) { | ||
if (config instanceof Error) { | ||
return config; | ||
} | ||
// If it's an mwn internal error, don't put the error code (begins with "mwn") | ||
@@ -11,0 +14,0 @@ // in the error message |
@@ -0,1 +1,13 @@ | ||
import type { mwn as Mwn, MwnDate } from './bot'; | ||
export interface MwnStreamStatic { | ||
new (streams: string | string[], config: { | ||
since?: Date | MwnDate | string; | ||
onopen?: (() => void); | ||
onerror?: ((evt: MessageEvent) => void); | ||
}): MwnStream; | ||
recentchange(filter: Partial<recentchangeProps> | ((data: recentchangeProps) => boolean), action: ((data: recentchangeProps) => void)): MwnStream; | ||
} | ||
export interface MwnStream { | ||
addListener(filter: ((data: any) => boolean) | any, action: (data: any) => void): void; | ||
} | ||
export declare type recentchangeProps = { | ||
@@ -10,1 +22,2 @@ wiki: string; | ||
}; | ||
export default function (bot: Mwn, mwn: typeof Mwn): MwnStreamStatic; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const EventSource = require("eventsource"); | ||
module.exports = function (bot, mwn) { | ||
function default_1(bot, mwn) { | ||
class EventStream extends EventSource { | ||
@@ -66,2 +66,3 @@ /** | ||
return EventStream; | ||
}; | ||
} | ||
exports.default = default_1; |
@@ -1,1 +0,16 @@ | ||
export {}; | ||
import type { mwn, MwnPage, MwnTitle } from './bot'; | ||
import { ApiQueryBacklinkspropParams } from "./api_params"; | ||
export interface MwnFileStatic { | ||
new (title: MwnTitle | string): MwnFile; | ||
} | ||
export interface MwnFile extends MwnPage { | ||
getName(): string; | ||
getNameText(): string; | ||
usages(options?: ApiQueryBacklinkspropParams): Promise<{ | ||
pageid: number; | ||
title: string; | ||
redirect: boolean; | ||
}[]>; | ||
download(localname: string): void; | ||
} | ||
export default function (bot: mwn): MwnFileStatic; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
module.exports = function (bot) { | ||
function default_1(bot) { | ||
class File extends bot.page { | ||
@@ -62,2 +62,3 @@ /** | ||
return File; | ||
}; | ||
} | ||
exports.default = default_1; |
@@ -0,2 +1,51 @@ | ||
import type { mwn, MwnTitle } from './bot'; | ||
import type { ApiDeleteParams, ApiEditPageParams, ApiMoveParams, ApiPurgeParams, ApiQueryAllPagesParams, ApiQueryLogEventsParams, ApiQueryRevisionsParams, ApiUndeleteParams } from "./api_params"; | ||
export declare type revisionprop = "content" | "timestamp" | "user" | "comment" | "parsedcomment" | "ids" | "flags" | "size" | "tags" | "userid" | "contentmodel"; | ||
export declare type logprop = "type" | "user" | "comment" | "details" | "timestamp" | "title" | "parsedcomment" | "ids" | "tags" | "userid"; | ||
export interface MwnPageStatic { | ||
new (title: MwnTitle | string, namespace?: number): MwnPage; | ||
} | ||
export interface MwnPage extends MwnTitle { | ||
data: any; | ||
getTalkPage(): MwnPage; | ||
getSubjectPage(): MwnPage; | ||
text(): Promise<string>; | ||
categories(): Promise<{ | ||
sortkey: string; | ||
category: string; | ||
hidden: boolean; | ||
}[]>; | ||
templates(): Promise<{ | ||
ns: number; | ||
title: string; | ||
exists: boolean; | ||
}[]>; | ||
links(): Promise<{ | ||
ns: number; | ||
title: string; | ||
exists: boolean; | ||
}[]>; | ||
backlinks(): Promise<string[]>; | ||
transclusions(): Promise<string[]>; | ||
images(): Promise<string[]>; | ||
externallinks(): Promise<string[]>; | ||
subpages(options?: ApiQueryAllPagesParams): Promise<string[]>; | ||
isRedirect(): Promise<boolean>; | ||
getRedirectTarget(): Promise<string>; | ||
getCreator(): Promise<string>; | ||
getDeletingAdmin(): Promise<string>; | ||
getDescription(customOptions?: any): Promise<string>; | ||
history(props: revisionprop[] | revisionprop, limit: number, customOptions?: ApiQueryRevisionsParams): Promise<object[]>; | ||
logs(props: logprop | logprop[], limit?: number, type?: string, customOptions?: ApiQueryLogEventsParams): Promise<object[]>; | ||
edit(transform: ((rev: { | ||
content: string; | ||
timestamp: string; | ||
}) => string | object)): Promise<any>; | ||
save(text: string, summary?: string, options?: ApiEditPageParams): Promise<any>; | ||
newSection(header: string, message: string, additionalParams?: ApiEditPageParams): Promise<any>; | ||
move(target: string, summary: string, options?: ApiMoveParams): Promise<any>; | ||
delete(summary: string, options?: ApiDeleteParams): Promise<any>; | ||
undelete(summary: string, options?: ApiUndeleteParams): Promise<any>; | ||
purge(options?: ApiPurgeParams): Promise<any>; | ||
} | ||
export default function (bot: mwn): MwnPageStatic; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const error_1 = require("./error"); | ||
module.exports = function (bot) { | ||
function default_1(bot) { | ||
class Page extends bot.title { | ||
@@ -90,4 +90,4 @@ constructor(title, namespace) { | ||
}).then(jsons => { | ||
var pages = jsons.reduce((pages, json) => pages.concat(json.query.pages), []); | ||
var page = pages[0]; | ||
let pages = jsons.reduce((pages, json) => pages.concat(json.query.pages), []); | ||
let page = pages[0]; | ||
if (page.missing) { | ||
@@ -111,4 +111,4 @@ return Promise.reject(new error_1.MwnError.MissingPage()); | ||
}).then(jsons => { | ||
var pages = jsons.reduce((pages, json) => pages.concat(json.query.pages), []); | ||
var page = pages[0]; | ||
let pages = jsons.reduce((pages, json) => pages.concat(json.query.pages), []); | ||
let page = pages[0]; | ||
if (page.missing) { | ||
@@ -174,3 +174,3 @@ return Promise.reject(new error_1.MwnError.MissingPage()); | ||
if (this.data.text) { | ||
var target = /^\s*#redirect \[\[(.*?)\]\]/.exec(this.data.text); | ||
let target = /^\s*#redirect \[\[(.*?)\]\]/.exec(this.data.text); | ||
if (!target) { | ||
@@ -186,3 +186,3 @@ return Promise.resolve(this.toText()); | ||
}).then(data => { | ||
var page = data.query.pages[0]; | ||
let page = data.query.pages[0]; | ||
if (page.missing) { | ||
@@ -207,3 +207,3 @@ return Promise.reject(new error_1.MwnError.MissingPage()); | ||
}).then(data => { | ||
var page = data.query.pages[0]; | ||
let page = data.query.pages[0]; | ||
if (page.missing) { | ||
@@ -227,3 +227,3 @@ return Promise.reject(new error_1.MwnError.MissingPage()); | ||
}).then(data => { | ||
var logs = data.query.logevents; | ||
let logs = data.query.logevents; | ||
if (logs.length === 0) { | ||
@@ -248,3 +248,3 @@ return null; | ||
}).then(data => { | ||
var page = data.query.pages[0]; | ||
let page = data.query.pages[0]; | ||
if (page.missing) { | ||
@@ -275,3 +275,3 @@ return Promise.reject(new error_1.MwnError.MissingPage()); | ||
}).then(data => { | ||
var page = data.query.pages[0]; | ||
let page = data.query.pages[0]; | ||
if (page.missing) { | ||
@@ -310,3 +310,3 @@ return Promise.reject(new error_1.MwnError.MissingPage()); | ||
logs(props, limit, type, customOptions) { | ||
var logtypeObj = {}; | ||
let logtypeObj = {}; | ||
if (type) { | ||
@@ -357,2 +357,3 @@ if (type.includes('/')) { | ||
return Page; | ||
}; | ||
} | ||
exports.default = default_1; |
@@ -5,111 +5,121 @@ /** | ||
import type { MwnTitle } from "./bot"; | ||
declare function rawurlencode(str: string): string; | ||
declare function isIPv4Address(address: string, allowBlock?: boolean): boolean; | ||
declare function isIPv6Address(address: string, allowBlock?: boolean): boolean; | ||
declare const _default: { | ||
/** | ||
* Get wikitext for a new link | ||
* @param target | ||
* @param [displaytext] | ||
*/ | ||
export declare function link(target: string | MwnTitle, displaytext?: string): string; | ||
/** | ||
* Get wikitext for a template usage | ||
* @param title | ||
* @param [parameters={}] - template parameters as object | ||
*/ | ||
export declare function template(title: string | MwnTitle, parameters?: { | ||
[parameter: string]: string; | ||
}): string; | ||
export declare class table { | ||
text: string; | ||
multiline: boolean; | ||
/** | ||
* Get wikitext for a new link | ||
* @param target | ||
* @param [displaytext] | ||
* @param {Object} [config={}] | ||
* @config {boolean} plain - plain table without borders (default: false) | ||
* @config {boolean} sortable - make columns sortable (default: true) | ||
* @config {string} style - style attribute | ||
* @config {boolean} multiline - put each cell of the table on a new line, | ||
* this causes no visual changes, but the wikitext representation is different. | ||
* This is more reliable. (default: true) | ||
*/ | ||
link: (target: string | MwnTitle, displaytext?: string) => string; | ||
constructor(config?: { | ||
plain?: boolean; | ||
sortable?: boolean; | ||
style?: string; | ||
multiline?: boolean; | ||
}); | ||
_makecell(cell: string | { | ||
[attribute: string]: string; | ||
}, isHeader?: boolean): string; | ||
/** | ||
* Get wikitext for a template usage | ||
* @param title | ||
* @param [parameters={}] - template parameters as object | ||
* Add the headers | ||
* @param headers - array of header items | ||
*/ | ||
template: (title: string | MwnTitle, parameters?: { | ||
[parameter: string]: string; | ||
}) => string; | ||
table: { | ||
new (config?: { | ||
plain?: boolean; | ||
sortable?: boolean; | ||
style?: string; | ||
multiline?: boolean; | ||
}): { | ||
text: string; | ||
multiline: boolean; | ||
_makecell(cell: string | { | ||
[attribute: string]: string; | ||
}, isHeader?: boolean): string; | ||
/** | ||
* Add the headers | ||
* @param headers - array of header items | ||
*/ | ||
addHeaders(headers: (string | { | ||
[attribute: string]: string; | ||
})[]): void; | ||
/** | ||
* Add a row to the table | ||
* @param fields - array of items on the row, | ||
* @param attributes - row attributes | ||
*/ | ||
addRow(fields: string[], attributes?: { | ||
[attribute: string]: string; | ||
}): void; | ||
/** Returns the final table wikitext */ | ||
getText(): string; | ||
}; | ||
}; | ||
util: { | ||
/** | ||
* Escape string for safe inclusion in regular expression. | ||
* The following characters are escaped: | ||
* \ { } ( ) | . ? * + - ^ $ [ ] | ||
* @param {string} str String to escape | ||
* @return {string} Escaped string | ||
*/ | ||
escapeRegExp: (str: string) => string; | ||
/** | ||
* Escape a string for HTML. Converts special characters to HTML entities. | ||
* | ||
* Util.escapeHtml( '< > \' & "' ); | ||
* // Returns < > ' & " | ||
* | ||
* @param {string} s - The string to escape | ||
* @return {string} HTML | ||
*/ | ||
escapeHtml: (s: string) => string; | ||
/** | ||
* Encode the string like PHP's rawurlencode | ||
* | ||
* @param {string} str String to be encoded. | ||
* @return {string} Encoded string | ||
*/ | ||
rawurlencode: typeof rawurlencode; | ||
/** | ||
* Encode page titles for use in a URL like mw.util.wikiUrlencode() | ||
* | ||
* We want / and : to be included as literal characters in our title URLs | ||
* as they otherwise fatally break the title. The others are decoded because | ||
* we can, it's prettier and matches behaviour of `wfUrlencode` in PHP. | ||
* | ||
* @param {string} str String to be encoded. | ||
* @return {string} Encoded string | ||
*/ | ||
wikiUrlencode: (str: string) => string; | ||
/** | ||
* Check if string is an IPv4 address | ||
* @param {string} address | ||
* @param {boolean} [allowBlock=false] | ||
* @return {boolean} | ||
*/ | ||
isIPv4Address: typeof isIPv4Address; | ||
/** | ||
* Check if the string is an IPv6 address | ||
* @param {string} address | ||
* @param {boolean} [allowBlock=false] | ||
* @return {boolean} | ||
*/ | ||
isIPv6Address: typeof isIPv6Address; | ||
/** | ||
* Check whether a string is an IP address | ||
* @param {string} address String to check | ||
* @param {boolean} [allowBlock=false] True if a block of IPs should be allowed | ||
* @return {boolean} | ||
*/ | ||
isIPAddress: (address: string, allowBlock?: boolean) => boolean; | ||
}; | ||
addHeaders(headers: (string | { | ||
[attribute: string]: string; | ||
})[]): void; | ||
/** | ||
* Add a row to the table | ||
* @param fields - array of items on the row, | ||
* @param attributes - row attributes | ||
*/ | ||
addRow(fields: string[], attributes?: { | ||
[attribute: string]: string; | ||
}): void; | ||
/** Returns the final table wikitext */ | ||
getText(): string; | ||
} | ||
/** | ||
* Encode the string like PHP's rawurlencode | ||
* | ||
* @param {string} str String to be encoded. | ||
* @return {string} Encoded string | ||
*/ | ||
declare function rawurlencode(str: string): string; | ||
/** | ||
* Check if string is an IPv4 address | ||
* @param {string} address | ||
* @param {boolean} [allowBlock=false] | ||
* @return {boolean} | ||
*/ | ||
declare function isIPv4Address(address: string, allowBlock?: boolean): boolean; | ||
/** | ||
* Check if the string is an IPv6 address | ||
* @param {string} address | ||
* @param {boolean} [allowBlock=false] | ||
* @return {boolean} | ||
*/ | ||
declare function isIPv6Address(address: string, allowBlock?: boolean): boolean; | ||
/** | ||
* Escape string for safe inclusion in regular expression. | ||
* The following characters are escaped: | ||
* \ { } ( ) | . ? * + - ^ $ [ ] | ||
* @param {string} str String to escape | ||
* @return {string} Escaped string | ||
*/ | ||
declare function escapeRegExp(str: string): string; | ||
/** | ||
* Escape a string for HTML. Converts special characters to HTML entities. | ||
* | ||
* Util.escapeHtml( '< > \' & "' ); | ||
* // Returns < > ' & " | ||
* | ||
* @param {string} s - The string to escape | ||
* @return {string} HTML | ||
*/ | ||
declare function escapeHtml(s: string): string; | ||
/** | ||
* Encode page titles for use in a URL like mw.util.wikiUrlencode() | ||
* | ||
* We want / and : to be included as literal characters in our title URLs | ||
* as they otherwise fatally break the title. The others are decoded because | ||
* we can, it's prettier and matches behaviour of `wfUrlencode` in PHP. | ||
* | ||
* @param {string} str String to be encoded. | ||
* @return {string} Encoded string | ||
*/ | ||
declare function wikiUrlencode(str: string): string; | ||
/** | ||
* Check whether a string is an IP address | ||
* @param {string} address String to check | ||
* @param {boolean} [allowBlock=false] True if a block of IPs should be allowed | ||
* @return {boolean} | ||
*/ | ||
declare function isIPAddress(address: string, allowBlock?: boolean): boolean; | ||
export declare const util: { | ||
escapeRegExp: typeof escapeRegExp; | ||
escapeHtml: typeof escapeHtml; | ||
rawurlencode: typeof rawurlencode; | ||
wikiUrlencode: typeof wikiUrlencode; | ||
isIPv4Address: typeof isIPv4Address; | ||
isIPv6Address: typeof isIPv6Address; | ||
isIPAddress: typeof isIPAddress; | ||
}; | ||
export default _default; | ||
export {}; |
@@ -6,5 +6,133 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/* | ||
* Definitions of some private functions used | ||
exports.util = exports.table = exports.template = exports.link = void 0; | ||
/** | ||
* Get wikitext for a new link | ||
* @param target | ||
* @param [displaytext] | ||
*/ | ||
function link(target, displaytext) { | ||
if (typeof target === 'string') { | ||
return '[[' + target + (displaytext ? '|' + displaytext : '') + ']]'; | ||
} | ||
return '[[' + target.toText() + | ||
(target.fragment ? '#' + target.fragment : '') + | ||
(displaytext ? '|' + displaytext : '') + | ||
']]'; | ||
} | ||
exports.link = link; | ||
/** | ||
* Get wikitext for a template usage | ||
* @param title | ||
* @param [parameters={}] - template parameters as object | ||
*/ | ||
function template(title, parameters = {}) { | ||
if (typeof title !== 'string') { | ||
if (title.namespace === 10) { | ||
title = title.getMainText(); // skip namespace name for templates | ||
} | ||
else if (title.namespace === 0) { | ||
title = ':' + title.toText(); // prefix colon for mainspace | ||
} | ||
else { | ||
title = title.toText(); | ||
} | ||
} | ||
return '{{' + title + | ||
Object.entries(parameters).map(([key, val]) => { | ||
if (!val) { // skip parameter if no value provided | ||
return ''; | ||
} | ||
return '|' + key + '=' + val; | ||
}).join('') + | ||
'}}'; | ||
} | ||
exports.template = template; | ||
class table { | ||
/** | ||
* @param {Object} [config={}] | ||
* @config {boolean} plain - plain table without borders (default: false) | ||
* @config {boolean} sortable - make columns sortable (default: true) | ||
* @config {string} style - style attribute | ||
* @config {boolean} multiline - put each cell of the table on a new line, | ||
* this causes no visual changes, but the wikitext representation is different. | ||
* This is more reliable. (default: true) | ||
*/ | ||
constructor(config = {}) { | ||
let classes = []; | ||
if (!config.plain) { | ||
classes.push('wikitable'); | ||
} | ||
if (config.sortable !== false) { | ||
classes.push('sortable'); | ||
} | ||
if (config.multiline !== false) { | ||
this.multiline = true; | ||
} | ||
this.text = `{|`; | ||
if (classes.length) { | ||
this.text += ` class="${classes.join(' ')}"`; | ||
} | ||
if (config.style) { | ||
this.text += ` style="${config.style}"`; | ||
} | ||
this.text += '\n'; | ||
} | ||
_makecell(cell, isHeader) { | ||
// typeof null is also object! | ||
if (cell && typeof cell === 'object') { | ||
let text = isHeader ? `scope="col"` : ``; | ||
for (let [key, value] of Object.entries(cell)) { | ||
if (key === 'label') { | ||
continue; | ||
} | ||
text += ` ${key}="${value}"`; | ||
} | ||
text += ` | ${cell.label}`; | ||
return text; | ||
} | ||
return String(cell); | ||
} | ||
/** | ||
* Add the headers | ||
* @param headers - array of header items | ||
*/ | ||
addHeaders(headers) { | ||
this.text += `|-\n`; // row separator | ||
if (this.multiline) { | ||
this.text += headers.map(e => `! ${this._makecell(e, true)} \n`).join(''); | ||
} | ||
else { | ||
this.text += `! ` + headers.map(e => this._makecell(e, true)).join(' !! ') + '\n'; | ||
} | ||
} | ||
/** | ||
* Add a row to the table | ||
* @param fields - array of items on the row, | ||
* @param attributes - row attributes | ||
*/ | ||
addRow(fields, attributes = {}) { | ||
let attributetext = ''; | ||
Object.entries(attributes).forEach(([key, value]) => { | ||
attributetext += ` ${key}="${value}"`; | ||
}); | ||
this.text += `|-${attributetext}\n`; // row separator | ||
if (this.multiline) { | ||
this.text += fields.map(e => `| ${this._makecell(e)} \n`).join(''); | ||
} | ||
else { | ||
this.text += `| ` + fields.map(f => this._makecell(f)).join(' || ') + '\n'; | ||
} | ||
} | ||
/** Returns the final table wikitext */ | ||
getText() { | ||
return this.text + `|}`; // add the table closing tag and return | ||
} | ||
} | ||
exports.table = table; | ||
/** | ||
* Encode the string like PHP's rawurlencode | ||
* | ||
* @param {string} str String to be encoded. | ||
* @return {string} Encoded string | ||
*/ | ||
function rawurlencode(str) { | ||
@@ -15,2 +143,8 @@ return encodeURIComponent(String(str)) | ||
} | ||
/** | ||
* Check if string is an IPv4 address | ||
* @param {string} address | ||
* @param {boolean} [allowBlock=false] | ||
* @return {boolean} | ||
*/ | ||
function isIPv4Address(address, allowBlock) { | ||
@@ -24,2 +158,8 @@ let block, RE_IP_BYTE = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])', RE_IP_ADD = '(?:' + RE_IP_BYTE + '\\.){3}' + RE_IP_BYTE; | ||
} | ||
/** | ||
* Check if the string is an IPv6 address | ||
* @param {string} address | ||
* @param {boolean} [allowBlock=false] | ||
* @return {boolean} | ||
*/ | ||
function isIPv6Address(address, allowBlock) { | ||
@@ -60,219 +200,74 @@ let block, RE_IPV6_ADD; | ||
} | ||
exports.default = { | ||
/** | ||
* Get wikitext for a new link | ||
* @param target | ||
* @param [displaytext] | ||
*/ | ||
link: function (target, displaytext) { | ||
if (typeof target === 'string') { | ||
return '[[' + target + (displaytext ? '|' + displaytext : '') + ']]'; | ||
/** | ||
* Escape string for safe inclusion in regular expression. | ||
* The following characters are escaped: | ||
* \ { } ( ) | . ? * + - ^ $ [ ] | ||
* @param {string} str String to escape | ||
* @return {string} Escaped string | ||
*/ | ||
function escapeRegExp(str) { | ||
// eslint-disable-next-line no-useless-escape | ||
return str.replace(/([\\{}()|.?*+\-^$\[\]])/g, '\\$1'); | ||
} | ||
/** | ||
* Escape a string for HTML. Converts special characters to HTML entities. | ||
* | ||
* Util.escapeHtml( '< > \' & "' ); | ||
* // Returns < > ' & " | ||
* | ||
* @param {string} s - The string to escape | ||
* @return {string} HTML | ||
*/ | ||
function escapeHtml(s) { | ||
return s.replace(/['"<>&]/g, function escapeCallback(s) { | ||
switch (s) { | ||
case '\'': | ||
return '''; | ||
case '"': | ||
return '"'; | ||
case '<': | ||
return '<'; | ||
case '>': | ||
return '>'; | ||
case '&': | ||
return '&'; | ||
} | ||
return '[[' + target.toText() + | ||
(target.fragment ? '#' + target.fragment : '') + | ||
(displaytext ? '|' + displaytext : '') + | ||
']]'; | ||
}, | ||
/** | ||
* Get wikitext for a template usage | ||
* @param title | ||
* @param [parameters={}] - template parameters as object | ||
*/ | ||
template: function (title, parameters = {}) { | ||
if (typeof title !== 'string') { | ||
if (title.namespace === 10) { | ||
title = title.getMainText(); // skip namespace name for templates | ||
} | ||
else if (title.namespace === 0) { | ||
title = ':' + title.toText(); // prefix colon for mainspace | ||
} | ||
else { | ||
title = title.toText(); | ||
} | ||
} | ||
return '{{' + title + | ||
Object.entries(parameters).map(([key, val]) => { | ||
if (!val) { // skip parameter if no value provided | ||
return ''; | ||
} | ||
return '|' + key + '=' + val; | ||
}).join('') + | ||
'}}'; | ||
}, | ||
table: class table { | ||
/** | ||
* @param {Object} [config={}] | ||
* @config {boolean} plain - plain table without borders (default: false) | ||
* @config {boolean} sortable - make columns sortable (default: true) | ||
* @config {string} style - style attribute | ||
* @config {boolean} multiline - put each cell of the table on a new line, | ||
* this causes no visual changes, but the wikitext representation is different. | ||
* This is more reliable. (default: true) | ||
*/ | ||
constructor(config = {}) { | ||
let classes = []; | ||
if (!config.plain) { | ||
classes.push('wikitable'); | ||
} | ||
if (config.sortable !== false) { | ||
classes.push('sortable'); | ||
} | ||
if (config.multiline !== false) { | ||
this.multiline = true; | ||
} | ||
this.text = `{|`; | ||
if (classes.length) { | ||
this.text += ` class="${classes.join(' ')}"`; | ||
} | ||
if (config.style) { | ||
this.text += ` style="${config.style}"`; | ||
} | ||
this.text += '\n'; | ||
} | ||
_makecell(cell, isHeader) { | ||
// typeof null is also object! | ||
if (cell && typeof cell === 'object') { | ||
let text = isHeader ? `scope="col"` : ``; | ||
for (let [key, value] of Object.entries(cell)) { | ||
if (key === 'label') { | ||
continue; | ||
} | ||
text += ` ${key}="${value}"`; | ||
} | ||
text += ` | ${cell.label}`; | ||
return text; | ||
} | ||
return String(cell); | ||
} | ||
/** | ||
* Add the headers | ||
* @param headers - array of header items | ||
*/ | ||
addHeaders(headers) { | ||
this.text += `|-\n`; // row separator | ||
if (this.multiline) { | ||
this.text += headers.map(e => `! ${this._makecell(e, true)} \n`).join(''); | ||
} | ||
else { | ||
this.text += `! ` + headers.map(e => this._makecell(e, true)).join(' !! ') + '\n'; | ||
} | ||
} | ||
/** | ||
* Add a row to the table | ||
* @param fields - array of items on the row, | ||
* @param attributes - row attributes | ||
*/ | ||
addRow(fields, attributes = {}) { | ||
let attributetext = ''; | ||
Object.entries(attributes).forEach(([key, value]) => { | ||
attributetext += ` ${key}="${value}"`; | ||
}); | ||
this.text += `|-${attributetext}\n`; // row separator | ||
if (this.multiline) { | ||
this.text += fields.map(e => `| ${this._makecell(e)} \n`).join(''); | ||
} | ||
else { | ||
this.text += `| ` + fields.map(f => this._makecell(f)).join(' || ') + '\n'; | ||
} | ||
} | ||
/** Returns the final table wikitext */ | ||
getText() { | ||
return this.text + `|}`; // add the table closing tag and return | ||
} | ||
}, | ||
util: { | ||
/** | ||
* Escape string for safe inclusion in regular expression. | ||
* The following characters are escaped: | ||
* \ { } ( ) | . ? * + - ^ $ [ ] | ||
* @param {string} str String to escape | ||
* @return {string} Escaped string | ||
*/ | ||
escapeRegExp: function (str) { | ||
// eslint-disable-next-line no-useless-escape | ||
return str.replace(/([\\{}()|.?*+\-^$\[\]])/g, '\\$1'); | ||
}, | ||
/** | ||
* Escape a string for HTML. Converts special characters to HTML entities. | ||
* | ||
* Util.escapeHtml( '< > \' & "' ); | ||
* // Returns < > ' & " | ||
* | ||
* @param {string} s - The string to escape | ||
* @return {string} HTML | ||
*/ | ||
escapeHtml: function (s) { | ||
return s.replace(/['"<>&]/g, function escapeCallback(s) { | ||
switch (s) { | ||
case '\'': | ||
return '''; | ||
case '"': | ||
return '"'; | ||
case '<': | ||
return '<'; | ||
case '>': | ||
return '>'; | ||
case '&': | ||
return '&'; | ||
} | ||
}); | ||
}, | ||
/** | ||
* Encode the string like PHP's rawurlencode | ||
* | ||
* @param {string} str String to be encoded. | ||
* @return {string} Encoded string | ||
*/ | ||
rawurlencode: rawurlencode, | ||
/** | ||
* Encode page titles for use in a URL like mw.util.wikiUrlencode() | ||
* | ||
* We want / and : to be included as literal characters in our title URLs | ||
* as they otherwise fatally break the title. The others are decoded because | ||
* we can, it's prettier and matches behaviour of `wfUrlencode` in PHP. | ||
* | ||
* @param {string} str String to be encoded. | ||
* @return {string} Encoded string | ||
*/ | ||
wikiUrlencode: function (str) { | ||
return rawurlencode(str) | ||
.replace(/%20/g, '_') | ||
// wfUrlencode replacements | ||
.replace(/%3B/g, ';') | ||
.replace(/%40/g, '@') | ||
.replace(/%24/g, '$') | ||
.replace(/%21/g, '!') | ||
.replace(/%2A/g, '*') | ||
.replace(/%28/g, '(') | ||
.replace(/%29/g, ')') | ||
.replace(/%2C/g, ',') | ||
.replace(/%2F/g, '/') | ||
.replace(/%7E/g, '~') | ||
.replace(/%3A/g, ':'); | ||
}, | ||
/** | ||
* Check if string is an IPv4 address | ||
* @param {string} address | ||
* @param {boolean} [allowBlock=false] | ||
* @return {boolean} | ||
*/ | ||
isIPv4Address: isIPv4Address, | ||
/** | ||
* Check if the string is an IPv6 address | ||
* @param {string} address | ||
* @param {boolean} [allowBlock=false] | ||
* @return {boolean} | ||
*/ | ||
isIPv6Address: isIPv6Address, | ||
/** | ||
* Check whether a string is an IP address | ||
* @param {string} address String to check | ||
* @param {boolean} [allowBlock=false] True if a block of IPs should be allowed | ||
* @return {boolean} | ||
*/ | ||
isIPAddress: function (address, allowBlock) { | ||
return isIPv4Address(address, allowBlock) || | ||
isIPv6Address(address, allowBlock); | ||
} | ||
} | ||
}; | ||
}); | ||
} | ||
/** | ||
* Encode page titles for use in a URL like mw.util.wikiUrlencode() | ||
* | ||
* We want / and : to be included as literal characters in our title URLs | ||
* as they otherwise fatally break the title. The others are decoded because | ||
* we can, it's prettier and matches behaviour of `wfUrlencode` in PHP. | ||
* | ||
* @param {string} str String to be encoded. | ||
* @return {string} Encoded string | ||
*/ | ||
function wikiUrlencode(str) { | ||
return rawurlencode(str) | ||
.replace(/%20/g, '_') | ||
// wfUrlencode replacements | ||
.replace(/%3B/g, ';') | ||
.replace(/%40/g, '@') | ||
.replace(/%24/g, '$') | ||
.replace(/%21/g, '!') | ||
.replace(/%2A/g, '*') | ||
.replace(/%28/g, '(') | ||
.replace(/%29/g, ')') | ||
.replace(/%2C/g, ',') | ||
.replace(/%2F/g, '/') | ||
.replace(/%7E/g, '~') | ||
.replace(/%3A/g, ':'); | ||
} | ||
/** | ||
* Check whether a string is an IP address | ||
* @param {string} address String to check | ||
* @param {boolean} [allowBlock=false] True if a block of IPs should be allowed | ||
* @return {boolean} | ||
*/ | ||
function isIPAddress(address, allowBlock) { | ||
return isIPv4Address(address, allowBlock) || | ||
isIPv6Address(address, allowBlock); | ||
} | ||
exports.util = { escapeRegExp, escapeHtml, rawurlencode, wikiUrlencode, isIPv4Address, isIPv6Address, isIPAddress }; |
@@ -10,2 +10,55 @@ /** | ||
*/ | ||
export {}; | ||
import type { mwn } from "./bot"; | ||
export interface MwnTitleStatic { | ||
new (title: string, namespace?: number): MwnTitle; | ||
idNameMap: { | ||
[namespaceId: number]: string; | ||
}; | ||
nameIdMap: { | ||
[namespaceName: string]: number; | ||
}; | ||
legaltitlechars: string; | ||
caseSensitiveNamespaces: Array<number>; | ||
processNamespaceData(json: { | ||
query: { | ||
general: { | ||
legaltitlechars: string; | ||
}; | ||
namespaces: { | ||
name: string; | ||
id: number; | ||
canonical: boolean; | ||
case: string; | ||
}[]; | ||
namespacealiases: { | ||
alias: string; | ||
id: number; | ||
}[]; | ||
}; | ||
}): void; | ||
checkData(): void; | ||
newFromText(title: string, namespace?: number): MwnTitle | null; | ||
makeTitle(namespace: number, title: string): MwnTitle | null; | ||
isTalkNamespace(namespaceId: number): boolean; | ||
phpCharToUpper(chr: string): string; | ||
} | ||
export interface MwnTitle { | ||
title: string; | ||
namespace: number; | ||
fragment: string; | ||
getNamespaceId(): number; | ||
getMain(): string; | ||
getMainText(): string; | ||
getPrefixedDb(): string; | ||
getPrefixedText(): string; | ||
getFragment(): string | null; | ||
isTalkPage(): boolean; | ||
getTalkPage(): MwnTitle | null; | ||
getSubjectPage(): MwnTitle | null; | ||
canHaveTalkPage(): boolean; | ||
getExtension(): string | null; | ||
getDotExtension(): string; | ||
toString(): string; | ||
toText(): string; | ||
} | ||
export default function (bot: mwn): MwnTitleStatic; |
@@ -12,3 +12,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
module.exports = function (bot) { | ||
function default_1(bot) { | ||
var NS_MAIN = 0; | ||
@@ -1175,2 +1175,3 @@ var NS_TALK = 1; | ||
return Title; | ||
}; | ||
} | ||
exports.default = default_1; |
@@ -0,1 +1,21 @@ | ||
import type { mwn, MwnPage } from "./bot"; | ||
import type { ApiBlockParams, ApiEmailUserParams, ApiQueryLogEventsParams, ApiQueryUserContribsParams, ApiUnblockParams } from "./api_params"; | ||
export interface MwnUserStatic { | ||
new (username: string): MwnUser; | ||
} | ||
export interface MwnUser { | ||
username: string; | ||
userpage: MwnPage; | ||
talkpage: MwnPage; | ||
contribs(options?: ApiQueryUserContribsParams): Promise<UserContribution[]>; | ||
contribsGen(options?: ApiQueryUserContribsParams): AsyncGenerator<UserContribution>; | ||
logs(options?: ApiQueryLogEventsParams): Promise<LogEvent[]>; | ||
logsGen(options?: ApiQueryLogEventsParams): AsyncGenerator<LogEvent>; | ||
info(props?: string | string[]): Promise<any>; | ||
globalinfo(props?: ("groups" | "rights" | "merged" | "unattached" | "editcount")[]): Promise<any>; | ||
sendMessage(header: string, message: string): Promise<any>; | ||
email(subject: string, message: string, options?: ApiEmailUserParams): Promise<any>; | ||
block(options: ApiBlockParams): Promise<any>; | ||
unblock(options: ApiUnblockParams): Promise<any>; | ||
} | ||
export declare type UserContribution = { | ||
@@ -29,1 +49,2 @@ userid: number; | ||
}; | ||
export default function (bot: mwn): MwnUserStatic; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
module.exports = function (bot) { | ||
function default_1(bot) { | ||
class User { | ||
@@ -153,2 +153,3 @@ /** | ||
return User; | ||
}; | ||
} | ||
exports.default = default_1; |
@@ -15,3 +15,28 @@ /** | ||
*/ | ||
import type { MwnTitle } from "./bot"; | ||
import type { mwn, MwnTitle } from "./bot"; | ||
import type { ApiParseParams } from "./api_params"; | ||
export interface MwnWikitextStatic { | ||
new (text: string): MwnWikitext; | ||
parseTemplates(wikitext: string, config: TemplateConfig): Template[]; | ||
parseTable(text: string): { | ||
[column: string]: string; | ||
}[]; | ||
parseSections(text: string): Section[]; | ||
} | ||
export interface MwnWikitext { | ||
text: string; | ||
links: Array<PageLink>; | ||
templates: Array<Template>; | ||
files: Array<FileLink>; | ||
categories: Array<CategoryLink>; | ||
sections: Array<Section>; | ||
parseLinks(): void; | ||
parseTemplates(config: TemplateConfig): Template[]; | ||
removeEntity(entity: Link | Template): void; | ||
parseSections(): Section[]; | ||
unbind(prefix: string, postfix: string): void; | ||
rebind(): string; | ||
getText(): string; | ||
apiParse(options: ApiParseParams): Promise<string>; | ||
} | ||
export interface Link { | ||
@@ -74,1 +99,2 @@ wikitext: string; | ||
} | ||
export default function (bot: mwn): MwnWikitextStatic; |
@@ -68,3 +68,3 @@ "use strict"; | ||
exports.Parameter = Parameter; | ||
module.exports = function (bot) { | ||
function default_1(bot) { | ||
class Wikitext { | ||
@@ -509,2 +509,3 @@ constructor(wikitext) { | ||
return Wikitext; | ||
}; | ||
} | ||
exports.default = default_1; |
{ | ||
"name": "mwn", | ||
"version": "0.8.3", | ||
"version": "0.9.0", | ||
"description": "MediaWiki bot framework for Node.js", | ||
@@ -10,6 +10,7 @@ "main": "./build/bot.js", | ||
"build": "npx tsc", | ||
"test-readonly": "cd tests && npx mocha bot.test.js batchOperations.bot.test.js category.test.js date.test.js file.test.js page.test.js title.test.js wikitext.test.js", | ||
"test-readonly": "cd tests && npx mocha bot.test.js batchOperations.bot.test.js category.test.js date.test.js file.test.js log.test.js login.bot.test.js oauth.test.js page.test.js static_utils.test.js suppl.bot.test.js title.test.js user.test.js wikitext.test.js", | ||
"test": "nyc --reporter=lcov --reporter=text mocha tests/", | ||
"coverage": "nyc report --reporter=text-lcov | coveralls", | ||
"test-ts": "ts-mocha -p tsconfig.json tests/ts/*" | ||
"test-ts": "npx ts-mocha -p tsconfig.json tests/ts/*", | ||
"docs": "npx typedoc src/bot.ts --out docs --ignoreCompilerErrors" | ||
}, | ||
@@ -34,8 +35,9 @@ "repository": "git+https://github.com/siddharthvp/mwn.git", | ||
"@types/tough-cookie": "^4.0.0", | ||
"axios": "^0.19.2", | ||
"axios": "^0.21.1", | ||
"axios-cookiejar-support": "^1.0.0", | ||
"chalk": "^1.1.3", | ||
"eventsource": "^1.0.7", | ||
"form-data": "^3.0.0", | ||
"oauth-1.0a": "^2.2.6", | ||
"semlog": "^0.6.10", | ||
"prettyjson": "^1.1.3", | ||
"tough-cookie": "^4.0.0" | ||
@@ -56,8 +58,9 @@ }, | ||
"eslint-plugin-standard": "^4.0.1", | ||
"mocha": "^7.1.1", | ||
"mocha": "^8.2.1", | ||
"nyc": "^15.1.0", | ||
"ts-mocha": "^8.0.0", | ||
"ts-node": "^9.0.0", | ||
"typedoc": "^0.19.2", | ||
"typescript": "^4.0.3" | ||
} | ||
} |
191
README.md
# mwn | ||
![Node.js CI](https://github.com/siddharthvp/mwn/workflows/Node.js%20CI/badge.svg) | ||
![CodeQL](https://github.com/siddharthvp/mwn/workflows/CodeQL/badge.svg) | ||
[![NPM version](https://img.shields.io/npm/v/mwn.svg)](https://www.npmjs.com/package/mwn) | ||
[![Coverage Status](https://coveralls.io/repos/github/siddharthvp/mwn/badge.svg?branch=master)](https://coveralls.io/github/siddharthvp/mwn?branch=master) | ||
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) | ||
**mwn** is a modern MediaWiki bot framework in NodeJS, orginally adapted from [mwbot](https://github.com/Fannon/mwbot). | ||
**Mwn** is a modern and comprehensive MediaWiki bot framework for Node.js, originally adapted from [mwbot](https://github.com/Fannon/mwbot). | ||
Development status: Unstable. Versioning: while mwn is in version 0, changes may be made to the public interface with a change in the minor version number. | ||
Mwn works with both JavaScript and TypeScript. It is created with a design philosophy of allowing bot developers to easily and quickly write bot code, without having to deal with the MediaWiki API complications and idiosyncrasies such as logins, tokens, maxlag, query continuations and error handling. Making raw API calls is also supported for complete flexibility where needed. The [axios](https://www.npmjs.com/package/axios) library is used for HTTP requests. | ||
Documentation given below is incomplete. There are a number of additional classes such as `bot.title`, `bot.wikitext`, `bot.page`, etc that provide useful functionality but aren't documented. You can learn about these by looking through the source code. | ||
Mwn uses promises, which you can use with async-await. To handle query continuations, mwn uses asynchronous generators. All methods with names ending in `Gen` are generators. | ||
Amongst the major highlights are `batchOperation` and `seriesBatchOperation` which allow you run a large number of tasks with control over concurrency and sleep time between tasks. Failing actions can be automatically retried. | ||
Mwn uses [JSON with formatversion 2](https://www.mediawiki.org/wiki/API:JSON_version_2#Using_the_new_JSON_results_format) by default. Use of the legacy formatversion is not recommended. Note that [Special:ApiSandbox](https://en.wikipedia.org/wiki/Special:ApiSandbox) uses formatversion=1 by default, so if you're testing API calls using ApiSandbox be sure to set the correct formatversion there, otherwise the output will be formatted differently. | ||
Versioning: while mwn is in version 0, changes may be made to the public interface with a change in the minor version number. | ||
Complete API documentation is available **[here](https://tools-static.wmflabs.org/mwn/docs/classes/_bot_.mwn.html)** ([alternative link](https://mwn.toolforge.org/docs/classes/_bot_.mwn.html)). In addition to the MediaWiki Action API, the library also provides methods to talk to the Wikimedia EventStreams API, the ORES API and WikiWho API. | ||
Amongst the major highlights are `batchOperation` and `seriesBatchOperation` which allow you run a large number of tasks with control over concurrency and sleep time between tasks. Failing actions are automatically retried. | ||
This library uses mocha for tests and has extensive test coverage covering all commonly used code paths. Testing is automated using a CI workflow on Github Actions. | ||
### Setup | ||
@@ -26,25 +37,34 @@ | ||
#### mwn uses JSON with formatversion 2 by default; formatversion 2 is an [improved JSON output format](https://www.mediawiki.org/wiki/API:JSON_version_2#Using_the_new_JSON_results_format) introduced in MediaWiki in 2015. | ||
#### Node.js compatibility | ||
Mwn is written in TypeScript v4. The repository contains JavaScript files compiled to CommonJS module system for ES2018 target, which corresponds to Node 10.x. | ||
#### Node version | ||
mwn is written in TypeScript v4. The repository contains JavaScript files compiled for ES2018 target, which corresponds to Node 10.x. If your bot is hosted on [Toolforge](https://tools.wmflabs.org/), note that the system version of node there is v8.11.1. But you can install the latest version node to your home directory, using: | ||
If your bot is hosted on [Toolforge](https://tools.wmflabs.org/), note that the system version of node there is v8.11.1. You can install a more recent version of node to your home directory, using: | ||
```sh | ||
npm install npm@latest # update npm first to the latest version | ||
npm install n | ||
npm install npm@latest # update npm first to the latest version | ||
npm install n # install a node package manager | ||
export N_PREFIX=~ | ||
./node_modules/n/bin/n latest | ||
./node_modules/n/bin/n lts # get the latest LTS version of node | ||
export PATH=~/bin:$PATH | ||
``` | ||
Check that your `.profile` or `.bashrc` file includes the line `PATH="$HOME/bin:$PATH"`, so that the path includes your home directory every time you open the shell. | ||
#### MediaWiki version | ||
mwn is written for and tested on the latest version of MediaWiki used on WMF wikis. | ||
If you're using mwn for a Toolforge webservice, use the Kubernetes backend which provides node v10. Mwn is not supported for the legacy Grid Engine backend since it uses node v8.11.1. The [toolforge-node-app-base](https://github.com/siddharthvp/toolforge-node-app-base) template repository can quickly get you started with a basic web tool boilerplate. | ||
#### MediaWiki compatibility | ||
Mwn is written for and tested on the latest version of MediaWiki used on WMF wikis. | ||
#### Set up a bot password or OAuth credentials | ||
mwn supports authentication via both BotPasswords and via OAuth. Use of OAuth is recommended as it does away the need for separate API requests for logging in, and is also a bit more secure. | ||
Mwn supports authentication via both [BotPasswords](https://www.mediawiki.org/wiki/Manual:Bot_passwords) and [OAuth](https://www.mediawiki.org/wiki/OAuth/Owner-only_consumers). Use of OAuth is recommended as it does away the need for separate API requests for logging in, and is also more secure. | ||
Bot passwords may be a bit easier to set up. To generate one, go to the wiki's [Special:BotPasswords](https://en.wikipedia.org/wiki/Special:BotPasswords) page. | ||
Bot passwords, however, are a bit easier to set up. To generate one, go to the wiki's [Special:BotPasswords](https://en.wikipedia.org/wiki/Special:BotPasswords) page. | ||
**Maxlag**: The default [maxlag parameter](https://www.mediawiki.org/wiki/Manual:Maxlag_parameter) used by mwn is 5 seconds. Requests failing due to maxlag will be automatically retried after pausing for a duration specified by `maxlagPause` (default 5 seconds). A maximum of `maxRetries` will take place (default 3). | ||
**Token handling**: `bot.getCsrfToken()` fetches a CSRF token required for most write operations. The token, once retrieved, is stored in the bot state so that it can be reused any number of times. If an API request fails due to an expired or missing token, the request is automatically retried after fetching a new token. | ||
**Retries**: Mwn automatically retries failing requests `bot.options.maxRetries` times (default: 3). This is useful in case of connectivity resets and the like. As for errors raised by the API itself, note that MediaWiki generally handles these at the response level rather than the protocol level (they still emit a 200 OK response). Mwn will attempt retries for these errors based on the error code. For instance, if the error is `readonly` or `maxlag` , retry is done after a delay. If it's `assertuserfailed` or `assertbotfailed` (indicates a session loss), mwn will try to log in again and then retry. If it's `badtoken`, retry is done after fetching a fresh edit token. | ||
If you're migrating from mwbot, note that: | ||
@@ -54,3 +74,3 @@ - `edit` in mwbot is different from `edit` in mwn. You want to use `save` instead. | ||
### Documentation | ||
### Getting started | ||
@@ -72,80 +92,63 @@ Importing mwn: | ||
```js | ||
const bot = new mwn({ | ||
apiUrl: 'https://en.wikipedia.org/w/api.php', | ||
username: 'YourBotUsername', | ||
password: 'YourBotPassword' | ||
}); | ||
await bot.login(); | ||
``` | ||
Or to use OAuth: | ||
```js | ||
const bot = new mwn({ | ||
apiUrl: 'https://en.wikipedia.org/w/api.php', | ||
const bot = await mwn.init({ | ||
apiUrl: 'https://en.wikipedia.org/w/api.php', | ||
// Can be skipped if the bot doesn't need to sign in | ||
username: 'YourBotUsername', | ||
password: 'YourBotPassword', | ||
// Instead of username and password, you can use OAuth to authenticate: | ||
oauth_consumer_token: "16_DIGIT_ALPHANUMERIC_KEY", | ||
oauth_consumer_secret: "20_DIGIT_ALPHANUMERIC_KEY", | ||
oauth_access_token: "16_DIGIT_ALPHANUMERIC_KEY", | ||
oauth_access_secret: "20_DIGIT_ALPHANUMERIC_KEY" | ||
}); | ||
bot.initOAuth(); // does not involve an API call | ||
// Any errors in authentication will surface when the first actual API call is made | ||
``` | ||
oauth_access_secret: "20_DIGIT_ALPHANUMERIC_KEY", | ||
A more complete constructor syntax: | ||
```js | ||
const bot = new mwn({ | ||
apiUrl: 'https://en.wikipedia.org/w/api.php', | ||
username: 'YourBotUsername', | ||
password: 'YourBotPassword', | ||
// Set your user agent (required for WMF wikis, see https://meta.wikimedia.org/wiki/User-Agent_policy): | ||
userAgent: 'myCoolToolName 1.0 ([[link to bot user page or tool documentation]])', | ||
userAgent: 'myCoolToolName 1.0 ([[link to bot user page or tool documentation]])', | ||
defaultParams: { | ||
assert: 'user' // API parameter to ensure we're logged in | ||
} | ||
// Set default parameters to be sent to be included in every API request | ||
defaultParams: { | ||
assert: 'user' // ensure we're logged in | ||
} | ||
}); | ||
``` | ||
This creates a bot instance, signs in and fetches tokens needed for editing. | ||
Set default parameters to be sent to be included in every API request: | ||
You can also create a bot instance synchronously (without using await): | ||
```js | ||
bot.setDefaultParams({ | ||
assert: 'bot', | ||
maxlag: 4 // mwn default is 5 | ||
const bot = new mwn({ | ||
...options | ||
}); | ||
``` | ||
Set bot options. The default values for each is specified below: | ||
This creates a bot instance which is not signed in. Then use `bot.login()`, `bot.loginGetToken()`, `bot.initOAuth()` or `bot.getTokensAndSiteInfo()`. Note that `bot.initOAuth()` does not involve an API call. Any error in authentication will surface when the first API call is made. | ||
The bot options can also be set using `setOptions` rather than through the constructor: | ||
```js | ||
bot.setOptions({ | ||
silent: false, // suppress messages (except error messages) | ||
maxlagPause: 5000, // pause for 5000 milliseconds (5 seconds) on maxlag error. | ||
maxlagMaxRetries: 3, // attempt to retry a request failing due to maxlag upto 3 times | ||
apiUrl: null // set the API URL, can also be set by a bot.setApiUrl | ||
retryPause: 5000, // pause for 5000 milliseconds (5 seconds) on maxlag error. | ||
maxRetries: 3, // attempt to retry a failing requests upto 3 times | ||
}); | ||
``` | ||
**Maxlag**: The default [maxlag parameter](https://www.mediawiki.org/wiki/Manual:Maxlag_parameter) used by mwn is 5 seconds. Requests failing due to maxlag will be automatically retried after pausing for a duration specified by `maxlagPause` (default 5 seconds). A maximum of `maxlagMaxRetries` will take place (default 3). | ||
### Direct API calls | ||
The `request` method is for directly querying the API. See [mw:API](https://www.mediawiki.org/wiki/API:Main_page) for options. You can create and test your queries in [Special:ApiSandbox](https://www.mediawiki.org/wiki/Special:ApiSandbox). Be sure to set formatversion: 2 in the options for format=json! | ||
Fetch an CSRF token required for most write operations. | ||
Example: get all images used on the article Foo | ||
```js | ||
bot.getCsrfToken(); | ||
bot.request({ | ||
"action": "query", | ||
"prop": "images", | ||
"titles": "Foo" | ||
}).then(data => { | ||
return data.query.pages[0].images.map(im => im.title); | ||
}); | ||
``` | ||
The token, once obtained is stored in the bot state so that it can be reused any number of times. | ||
If an action fails due to an expired or missing token, the action will be automatically retried after fetching a new token. | ||
Mwn provides a great number of convenience methods so that you can avoid writing raw API calls, see the sections below. | ||
For convenience, you can log in and get the edit token together as: | ||
### Editing pages | ||
Edit a page. On edit conflicts, a retry is automatically attempted once. | ||
```js | ||
bot.loginGetToken(); | ||
``` | ||
If your bot doesn't need to log in, you can simply set the API url using: | ||
```js | ||
bot.setApiUrl('https://en.wikipedia.org/w/api.php'); | ||
``` | ||
Set your user agent (required for [WMF wikis](https://meta.wikimedia.org/wiki/User-Agent_policy)): | ||
```js | ||
bot.setUserAgent('myCoolToolName v1.0 ([[w:en:User:Example]])/mwn'); | ||
``` | ||
Edit a page. Edit conflicts are raised as errors. | ||
```js | ||
bot.edit('Page title', rev => { | ||
@@ -166,16 +169,15 @@ // rev.content gives the revision text | ||
Save a page with the given content without loading it first. Simpler verion of `edit`. Does not offer any edit conflict detection. | ||
Some more functions associated with editing pages: | ||
```js | ||
// Save a page with the given content without loading it first. Simpler verion of `edit`. Does not offer any edit conflict detection. | ||
bot.save('Page title', 'Page content', 'Edit summary'); | ||
``` | ||
Create a new page. | ||
```js | ||
// Create a new page. | ||
bot.create('Page title', 'Page content', 'Edit summary'); | ||
``` | ||
Post a new section to a talk page: | ||
```js | ||
// Post a new section to a talk page: | ||
bot.newSection('Page title', 'New section header', 'Section content', additionalOptions); | ||
``` | ||
### Other basic operations | ||
Read the contents of a page: | ||
@@ -205,7 +207,2 @@ ```js | ||
Restore all deleted versions: | ||
```js | ||
bot.undelete('Page title', 'log summary', additionalOptions); | ||
``` | ||
Move a page along with its subpages: | ||
@@ -234,18 +231,2 @@ ```js | ||
#### Direct calls | ||
#### request(query) | ||
Directly query the API. See [mw:API](https://www.mediawiki.org/wiki/API:Main_page) for options. You can create and test your queries in the [`API sandbox`](https://www.mediawiki.org/wiki/Special:ApiSandbox). Be sure to set formatversion: 2 in the options for format=json! | ||
Example: get all images used on the article Foo | ||
```js | ||
bot.request({ | ||
"action": "query", | ||
"prop": "images", | ||
"titles": "Foo" | ||
}).then(data => { | ||
return data.query.pages[0].images.map(im => im.title); | ||
}); | ||
``` | ||
#### Bulk processing methods | ||
@@ -255,5 +236,2 @@ | ||
Send an API query, and continue re-sending it with the continue parameters received in the response, until there are no more results (or till `maxCalls` limit is reached). The return value is a promise resolved with the array of responses to individual API calls. | ||
```js | ||
bot.continousQuery(apiQueryObject, maxCalls=10) | ||
``` | ||
@@ -267,3 +245,3 @@ Example: get a list of all active users on the wiki using `continuedQuery` (using [API:Allusers](https://www.mediawiki.org/wiki/API:Allusers)): | ||
"aulimit": "max" | ||
}, 40).then(jsons => { | ||
}, /* max number of calls */ 40).then(jsons => { | ||
return jsons.reduce((activeusers, json) => { | ||
@@ -313,6 +291,7 @@ return activeusers.concat(json.query.allusers.map(user => user.name)); | ||
##### batchOperation(pageList, workerFunction, concurrency) | ||
#### Batch operations | ||
Perform asynchronous tasks (involving API usage) over a number of pages (or other arbitrary items). `batchOperation` uses a default concurrency of 5. Customise this according to how expensive the API operation is. Higher concurrency limits could lead to more frequent API errors. | ||
The `workerFunction` must return a promise. | ||
Usage: `batchOperation(pageList, workerFunction, concurrency)` The `workerFunction` must return a promise. | ||
```js | ||
@@ -324,3 +303,3 @@ bot.batchOperation(pageList, (page, idx) => { | ||
// return a promise in the end | ||
}, 5, 2); // set the concurrency as the third parameter, number of retries as 4th parameter | ||
}, /* concurrency */ 5, /* retries */ 2); | ||
``` | ||
@@ -345,2 +324,2 @@ | ||
**mwn** is released under GNU Lesser General Public License (LGPL) v3.0, since it borrows quite a bit of code from MediaWiki core (GPL v2). LGPL is a more permissive variant of the more popular GNU GPL. Unlike GPL, LPGL _allows_ the work to be used as a library in software not released under GPL-compatible licenses, and even in proprietary software. However, any derivatives of this library should also be released under LGPL or another GPL-compatible license. | ||
Mwn is released under [GNU Lesser General Public License](https://en.wikipedia.org/wiki/GNU_Lesser_General_Public_License) (LGPL) v3.0, since it borrows quite a bit of code from MediaWiki core (GPL v2). LGPL is a more permissive variant of the more popular GNU GPL. Unlike GPL, LPGL _allows_ the work to be used as a library in software not released under GPL-compatible licenses, and even in proprietary software. However, any derivatives of this library should also be released under LGPL or another GPL-compatible license. |
@@ -1,6 +0,24 @@ | ||
import type {mwn, MwnCategory, MwnTitle} from './bot'; | ||
import type {mwn, MwnPage, MwnTitle} from './bot'; | ||
import {ApiQueryCategoryMembersParams} from "./api_params"; | ||
module.exports = function (bot: mwn) { | ||
type ApiPageInfo = { | ||
pageid: number; | ||
ns: number; | ||
title: string; | ||
}; | ||
export interface MwnCategoryStatic { | ||
new (title: MwnTitle | string): MwnCategory; | ||
} | ||
export interface MwnCategory extends MwnPage { | ||
members(options?: ApiQueryCategoryMembersParams): Promise<ApiPageInfo[]>; | ||
membersGen(options?: ApiQueryCategoryMembersParams): AsyncGenerator<ApiPageInfo>; | ||
pages(options?: ApiQueryCategoryMembersParams): Promise<ApiPageInfo[]>; | ||
subcats(options?: ApiQueryCategoryMembersParams): Promise<ApiPageInfo[]>; | ||
files(options?: ApiQueryCategoryMembersParams): Promise<ApiPageInfo[]>; | ||
} | ||
export default function (bot: mwn) { | ||
class Category extends bot.page implements MwnCategory { | ||
@@ -27,3 +45,3 @@ | ||
*/ | ||
members(options?: ApiQueryCategoryMembersParams): Promise<{pageid: number, ns: number, title: string}[]> { | ||
members(options?: ApiQueryCategoryMembersParams): Promise<ApiPageInfo[]> { | ||
return bot.request({ | ||
@@ -38,2 +56,18 @@ "action": "query", | ||
async *membersGen(options?: ApiQueryCategoryMembersParams): AsyncGenerator<ApiPageInfo> { | ||
let continuedQuery = bot.continuedQueryGen({ | ||
"action": "query", | ||
"list": "categorymembers", | ||
"cmtitle": "Category:" + this.title, | ||
"cmlimit": "max", | ||
...options | ||
}); | ||
for await (let json of continuedQuery) { | ||
for (let result of json.query.categorymembers) { | ||
yield result; | ||
} | ||
} | ||
} | ||
/** | ||
@@ -45,3 +79,3 @@ * Get all pages in the category - does not include subcategories or files | ||
*/ | ||
pages(options?: ApiQueryCategoryMembersParams): Promise<{pageid: number, ns: number, title: string}[]> { | ||
pages(options?: ApiQueryCategoryMembersParams): Promise<ApiPageInfo[]> { | ||
return this.members({"cmtype": ["page"], ...options}); | ||
@@ -56,3 +90,3 @@ } | ||
*/ | ||
subcats(options?: ApiQueryCategoryMembersParams): Promise<{pageid: number, ns: number, title: string}[]> { | ||
subcats(options?: ApiQueryCategoryMembersParams): Promise<ApiPageInfo[]> { | ||
return this.members({"cmtype": ["subcat"], ...options}); | ||
@@ -67,3 +101,3 @@ } | ||
*/ | ||
files(options?: ApiQueryCategoryMembersParams): Promise<{pageid: number, ns: number, title: string}[]> { | ||
files(options?: ApiQueryCategoryMembersParams): Promise<ApiPageInfo[]> { | ||
return this.members({"cmtype": ["file"], ...options}); | ||
@@ -74,4 +108,4 @@ } | ||
return Category; | ||
return Category as MwnCategoryStatic; | ||
} |
106
src/date.ts
@@ -1,2 +0,2 @@ | ||
import type {mwn, MwnDate as XDate} from "./bot"; | ||
import type {mwn} from "./bot"; | ||
@@ -9,6 +9,38 @@ /** | ||
module.exports = function (bot: mwn) { | ||
export interface MwnDateStatic { | ||
new (...args: any[]): MwnDate; | ||
getMonthName(monthNum: number): string; | ||
getMonthNameAbbrev(monthNum: number): string; | ||
getDayName(dayNum: number): string; | ||
getDayNameAbbrev(dayNum: number): string; | ||
} | ||
class MwnDate extends Date implements XDate { | ||
export interface MwnDate extends Date { | ||
isValid(): boolean; | ||
isBefore(date: Date | MwnDate): boolean; | ||
isAfter(date: Date | MwnDate): boolean; | ||
getUTCMonthName(): string; | ||
getUTCMonthNameAbbrev(): string; | ||
getMonthName(): string; | ||
getMonthNameAbbrev(): string; | ||
getUTCDayName(): string; | ||
getUTCDayNameAbbrev(): string; | ||
getDayName(): string; | ||
getDayNameAbbrev(): string; | ||
add(number: number, unit: timeUnit): MwnDate; | ||
subtract(number: number, unit: timeUnit): MwnDate; | ||
format(formatstr: string, zone?: number | 'utc' | 'system'): string; | ||
calendar(zone?: number | 'utc' | 'system'): string; | ||
} | ||
/** | ||
* Wrapper around the native JS Date() for ease of | ||
* handling dates, as well as a constructor that | ||
* can parse MediaWiki dating formats. | ||
*/ | ||
export default function (bot: mwn) { | ||
class XDate extends Date implements MwnDate { | ||
/** | ||
@@ -59,31 +91,31 @@ * Create a date object. MediaWiki timestamp format is also acceptable, | ||
getUTCMonthName(): string { | ||
return MwnDate.localeData.months[this.getUTCMonth()]; | ||
return XDate.localeData.months[this.getUTCMonth()]; | ||
} | ||
getUTCMonthNameAbbrev(): string { | ||
return MwnDate.localeData.monthsShort[this.getUTCMonth()]; | ||
return XDate.localeData.monthsShort[this.getUTCMonth()]; | ||
} | ||
getMonthName(): string { | ||
return MwnDate.localeData.months[this.getMonth()]; | ||
return XDate.localeData.months[this.getMonth()]; | ||
} | ||
getMonthNameAbbrev(): string { | ||
return MwnDate.localeData.monthsShort[this.getMonth()]; | ||
return XDate.localeData.monthsShort[this.getMonth()]; | ||
} | ||
getUTCDayName(): string { | ||
return MwnDate.localeData.days[this.getUTCDay()]; | ||
return XDate.localeData.days[this.getUTCDay()]; | ||
} | ||
getUTCDayNameAbbrev(): string { | ||
return MwnDate.localeData.daysShort[this.getUTCDay()]; | ||
return XDate.localeData.daysShort[this.getUTCDay()]; | ||
} | ||
getDayName(): string { | ||
return MwnDate.localeData.days[this.getDay()]; | ||
return XDate.localeData.days[this.getDay()]; | ||
} | ||
getDayNameAbbrev(): string { | ||
return MwnDate.localeData.daysShort[this.getDay()]; | ||
return XDate.localeData.daysShort[this.getDay()]; | ||
} | ||
@@ -97,5 +129,5 @@ | ||
* @throws {Error} if invalid or unsupported unit is given | ||
* @returns {MwnDate} | ||
* @returns {XDate} | ||
*/ | ||
add(number: number, unit: timeUnit): MwnDate { | ||
add(number: number, unit: timeUnit): XDate { | ||
// @ts-ignore | ||
@@ -117,5 +149,5 @@ let unitNorm = unitMap[unit] || unitMap[unit + 's']; // so that both singular and plural forms work | ||
* @throws {Error} if invalid or unsupported unit is given | ||
* @returns {MwnDate} | ||
* @returns {XDate} | ||
*/ | ||
subtract(number: number, unit: timeUnit): MwnDate { | ||
subtract(number: number, unit: timeUnit): XDate { | ||
return this.add(-number, unit); | ||
@@ -136,9 +168,9 @@ } | ||
} | ||
let udate: MwnDate = this; | ||
let udate: XDate = this; | ||
// create a new date object that will contain the date to display as system time | ||
if (!zone || zone === 'utc') { | ||
udate = new MwnDate(this.getTime()).add(this.getTimezoneOffset(), 'minutes'); | ||
udate = new XDate(this.getTime()).add(this.getTimezoneOffset(), 'minutes'); | ||
} else if (typeof zone === 'number') { | ||
// convert to utc, then add the utc offset given | ||
udate = new MwnDate(this.getTime()).add(this.getTimezoneOffset() + zone, 'minutes'); | ||
udate = new XDate(this.getTime()).add(this.getTimezoneOffset() + zone, 'minutes'); | ||
} | ||
@@ -193,13 +225,13 @@ | ||
case dateDiff === 0: | ||
return this.format(MwnDate.localeData.relativeTimes.thisDay, zone); | ||
return this.format(XDate.localeData.relativeTimes.thisDay, zone); | ||
case dateDiff === 1: | ||
return this.format(MwnDate.localeData.relativeTimes.prevDay, zone); | ||
return this.format(XDate.localeData.relativeTimes.prevDay, zone); | ||
case dateDiff > 0 && dateDiff < 7: | ||
return this.format(MwnDate.localeData.relativeTimes.pastWeek, zone); | ||
return this.format(XDate.localeData.relativeTimes.pastWeek, zone); | ||
case dateDiff === -1: | ||
return this.format(MwnDate.localeData.relativeTimes.nextDay, zone); | ||
return this.format(XDate.localeData.relativeTimes.nextDay, zone); | ||
case dateDiff < 0 && dateDiff > -7: | ||
return this.format(MwnDate.localeData.relativeTimes.thisWeek, zone); | ||
return this.format(XDate.localeData.relativeTimes.thisWeek, zone); | ||
default: | ||
return this.format(MwnDate.localeData.relativeTimes.other, zone); | ||
return this.format(XDate.localeData.relativeTimes.other, zone); | ||
} | ||
@@ -223,7 +255,2 @@ } | ||
// TODO: allow easier i18n | ||
static loadLocaleData(data) { | ||
MwnDate.localeData = data; | ||
} | ||
/** | ||
@@ -233,3 +260,3 @@ * Get month name from month number (1-indexed) | ||
static getMonthName(monthNum: number): string { | ||
return MwnDate.localeData.months[monthNum - 1]; | ||
return XDate.localeData.months[monthNum - 1]; | ||
} | ||
@@ -241,3 +268,3 @@ | ||
static getMonthNameAbbrev(monthNum: number): string { | ||
return MwnDate.localeData.monthsShort[monthNum - 1]; | ||
return XDate.localeData.monthsShort[monthNum - 1]; | ||
} | ||
@@ -249,3 +276,3 @@ | ||
static getDayName(dayNum: number): string { | ||
return MwnDate.localeData.days[dayNum - 1]; | ||
return XDate.localeData.days[dayNum - 1]; | ||
} | ||
@@ -257,3 +284,3 @@ | ||
static getDayNameAbbrev(dayNum: number): string { | ||
return MwnDate.localeData.daysShort[dayNum - 1]; | ||
return XDate.localeData.daysShort[dayNum - 1]; | ||
} | ||
@@ -264,7 +291,7 @@ | ||
// Tweak set* methods (setHours, setUTCMinutes, etc) so that they | ||
// return the modified MwnDate object rather than the seconds-since-epoch | ||
// return the modified XDate object rather than the seconds-since-epoch | ||
// representation which is what JS Date() gives | ||
Object.getOwnPropertyNames(Date.prototype).filter(f => f.startsWith('set')).forEach(func => { | ||
let proxy = MwnDate.prototype[func]; | ||
MwnDate.prototype[func] = function(...args) { | ||
let proxy = XDate.prototype[func]; | ||
XDate.prototype[func] = function(...args) { | ||
proxy.call(this, ...args); | ||
@@ -275,5 +302,5 @@ return this; | ||
return MwnDate; | ||
return XDate as MwnDateStatic; | ||
}; | ||
} | ||
@@ -288,5 +315,6 @@ // mapping time units with getter/setter function names for add and subtract | ||
years: 'FullYear' | ||
} | ||
}; | ||
// plural or singular keys of unitMap | ||
export type timeUnit = keyof typeof unitMap | 'second' | 'minute' | 'hour' | 'day' | 'month' | 'year' | ||
@@ -0,1 +1,3 @@ | ||
import type {RawRequestParams} from "./bot"; | ||
export type MwnErrorConfig = { | ||
@@ -5,3 +7,3 @@ code: string, | ||
response?: Record<string, unknown>, | ||
request?: Record<string, unknown>, | ||
request?: RawRequestParams, | ||
disableRetry?: boolean | ||
@@ -14,3 +16,6 @@ } | ||
*/ | ||
constructor(config: MwnErrorConfig) { | ||
constructor(config: Error | MwnErrorConfig) { | ||
if (config instanceof Error) { | ||
return config; | ||
} | ||
// If it's an mwn internal error, don't put the error code (begins with "mwn") | ||
@@ -17,0 +22,0 @@ // in the error message |
@@ -1,5 +0,16 @@ | ||
import EventSource = require('eventsource'); | ||
import type {mwn as Mwn, MwnDate, MwnStream} from './bot'; | ||
import type {mwn as Mwn, MwnDate} from './bot'; | ||
export interface MwnStreamStatic { | ||
new (streams: string | string[], config: { | ||
since?: Date | MwnDate | string; | ||
onopen?: (() => void); | ||
onerror?: ((evt: MessageEvent) => void); | ||
}): MwnStream; | ||
recentchange(filter: Partial<recentchangeProps> | ((data: recentchangeProps) => boolean), action: ((data: recentchangeProps) => void)): MwnStream; | ||
} | ||
export interface MwnStream { | ||
addListener(filter: ((data: any) => boolean) | any, action: (data: any) => void): void; | ||
} | ||
export type recentchangeProps = { | ||
@@ -15,3 +26,3 @@ wiki: string, | ||
module.exports = function (bot: Mwn, mwn: typeof Mwn) { | ||
export default function (bot: Mwn, mwn: typeof Mwn) { | ||
@@ -83,3 +94,3 @@ class EventStream extends EventSource implements MwnStream { | ||
static recentchange(filter: Partial<recentchangeProps> | ((data: recentchangeProps) => boolean), | ||
action: ((data: recentchangeProps) => void)): EventStream { | ||
action: ((data: recentchangeProps) => void)): EventStream { | ||
let stream = new EventStream('recentchange'); | ||
@@ -92,3 +103,3 @@ stream.addListener(filter, action); | ||
return EventStream; | ||
}; | ||
return EventStream as MwnStreamStatic; | ||
} |
@@ -1,6 +0,20 @@ | ||
import type {mwn, MwnFile, MwnTitle} from './bot'; | ||
import type {mwn, MwnPage, MwnTitle} from './bot'; | ||
import {ApiQueryBacklinkspropParams} from "./api_params"; | ||
module.exports = function (bot: mwn) { | ||
export interface MwnFileStatic { | ||
new (title: MwnTitle | string): MwnFile; | ||
} | ||
export interface MwnFile extends MwnPage { | ||
getName(): string; | ||
getNameText(): string; | ||
usages(options?: ApiQueryBacklinkspropParams): Promise<{ | ||
pageid: number; | ||
title: string; | ||
redirect: boolean; | ||
}[]>; | ||
download(localname: string): void; | ||
} | ||
export default function (bot: mwn) { | ||
class File extends bot.page implements MwnFile { | ||
@@ -72,4 +86,4 @@ | ||
return File; | ||
return File as MwnFileStatic; | ||
}; | ||
} |
import {MwnError} from "./error"; | ||
import type {mwn, MwnTitle, MwnPage} from './bot' | ||
import type {mwn, MwnTitle} from './bot'; | ||
import type { | ||
@@ -12,9 +12,57 @@ ApiDeleteParams, | ||
export type revisionprop = "content" | "timestamp" | "user" | "comment" | "parsedcomment" | "ids" | "flags" | | ||
"size" | "tags" | "userid" | "contentmodel" | ||
export type logprop = "type" | "user" | "comment" | "details" | "timestamp" | "title" | "parsedcomment" | ||
| "ids" | "tags" | "userid" | ||
export type revisionprop = "content" | "timestamp" | "user" | "comment" | "parsedcomment" | | ||
"ids" | "flags" | "size" | "tags" | "userid" | "contentmodel"; | ||
export type logprop = "type" | "user" | "comment" | "details" | "timestamp" | "title" | | ||
"parsedcomment" | "ids" | "tags" | "userid"; | ||
module.exports = function (bot: mwn) { | ||
export interface MwnPageStatic { | ||
new (title: MwnTitle | string, namespace?: number): MwnPage; | ||
} | ||
export interface MwnPage extends MwnTitle { | ||
data: any; | ||
getTalkPage(): MwnPage; | ||
getSubjectPage(): MwnPage; | ||
text(): Promise<string>; | ||
categories(): Promise<{ | ||
sortkey: string; | ||
category: string; | ||
hidden: boolean; | ||
}[]>; | ||
templates(): Promise<{ | ||
ns: number; | ||
title: string; | ||
exists: boolean; | ||
}[]>; | ||
links(): Promise<{ | ||
ns: number; | ||
title: string; | ||
exists: boolean; | ||
}[]>; | ||
backlinks(): Promise<string[]>; | ||
transclusions(): Promise<string[]>; | ||
images(): Promise<string[]>; | ||
externallinks(): Promise<string[]>; | ||
subpages(options?: ApiQueryAllPagesParams): Promise<string[]>; | ||
isRedirect(): Promise<boolean>; | ||
getRedirectTarget(): Promise<string>; | ||
getCreator(): Promise<string>; | ||
getDeletingAdmin(): Promise<string>; | ||
getDescription(customOptions?: any): Promise<string>; | ||
history(props: revisionprop[] | revisionprop, limit: number, customOptions?: ApiQueryRevisionsParams): Promise<object[]>; | ||
logs(props: logprop | logprop[], limit?: number, type?: string, customOptions?: ApiQueryLogEventsParams): Promise<object[]>; | ||
edit(transform: ((rev: { | ||
content: string; | ||
timestamp: string; | ||
}) => string | object)): Promise<any>; | ||
save(text: string, summary?: string, options?: ApiEditPageParams): Promise<any>; | ||
newSection(header: string, message: string, additionalParams?: ApiEditPageParams): Promise<any>; | ||
move(target: string, summary: string, options?: ApiMoveParams): Promise<any>; | ||
delete(summary: string, options?: ApiDeleteParams): Promise<any>; | ||
undelete(summary: string, options?: ApiUndeleteParams): Promise<any>; | ||
purge(options?: ApiPurgeParams): Promise<any>; | ||
} | ||
export default function(bot: mwn): MwnPageStatic { | ||
class Page extends bot.title implements MwnPage { | ||
@@ -115,4 +163,4 @@ data: any | ||
}).then(jsons => { | ||
var pages = jsons.reduce((pages, json) => pages.concat(json.query.pages), []); | ||
var page = pages[0]; | ||
let pages = jsons.reduce((pages, json) => pages.concat(json.query.pages), []); | ||
let page = pages[0]; | ||
if (page.missing) { | ||
@@ -137,4 +185,4 @@ return Promise.reject(new MwnError.MissingPage()); | ||
}).then(jsons => { | ||
var pages = jsons.reduce((pages, json) => pages.concat(json.query.pages), []); | ||
var page = pages[0]; | ||
let pages = jsons.reduce((pages, json) => pages.concat(json.query.pages), []); | ||
let page = pages[0]; | ||
if (page.missing) { | ||
@@ -206,3 +254,3 @@ return Promise.reject(new MwnError.MissingPage()); | ||
if (this.data.text) { | ||
var target = /^\s*#redirect \[\[(.*?)\]\]/.exec(this.data.text); | ||
let target = /^\s*#redirect \[\[(.*?)\]\]/.exec(this.data.text); | ||
if (!target) { | ||
@@ -218,3 +266,3 @@ return Promise.resolve(this.toText()); | ||
}).then(data => { | ||
var page = data.query.pages[0]; | ||
let page = data.query.pages[0]; | ||
if (page.missing) { | ||
@@ -241,3 +289,3 @@ return Promise.reject(new MwnError.MissingPage()); | ||
}).then(data => { | ||
var page = data.query.pages[0]; | ||
let page = data.query.pages[0]; | ||
if (page.missing) { | ||
@@ -262,3 +310,3 @@ return Promise.reject(new MwnError.MissingPage()); | ||
}).then(data => { | ||
var logs = data.query.logevents; | ||
let logs = data.query.logevents; | ||
if (logs.length === 0) { | ||
@@ -284,3 +332,3 @@ return null; | ||
}).then(data => { | ||
var page = data.query.pages[0]; | ||
let page = data.query.pages[0]; | ||
if (page.missing) { | ||
@@ -303,3 +351,3 @@ return Promise.reject(new MwnError.MissingPage()); | ||
*/ | ||
history(props: revisionprop[] | revisionprop, limit: number = 50, customOptions?: ApiQueryRevisionsParams): Promise<object[]> { | ||
history(props: revisionprop[] | revisionprop, limit = 50, customOptions?: ApiQueryRevisionsParams): Promise<object[]> { | ||
return bot.request({ | ||
@@ -313,3 +361,3 @@ "action": "query", | ||
}).then(data => { | ||
var page = data.query.pages[0]; | ||
let page = data.query.pages[0]; | ||
if (page.missing) { | ||
@@ -350,3 +398,3 @@ return Promise.reject(new MwnError.MissingPage()); | ||
logs(props: logprop | logprop[], limit?: number, type?: string, customOptions?: ApiQueryLogEventsParams) { | ||
var logtypeObj: any = {}; | ||
let logtypeObj: any = {}; | ||
if (type) { | ||
@@ -407,4 +455,4 @@ if (type.includes('/')) { | ||
return Page; | ||
return Page as MwnPageStatic; | ||
}; | ||
} |
@@ -7,7 +7,140 @@ /** | ||
/* | ||
* Definitions of some private functions used | ||
/** | ||
* Get wikitext for a new link | ||
* @param target | ||
* @param [displaytext] | ||
*/ | ||
export function link(target: string | MwnTitle, displaytext?: string): string { | ||
if (typeof target === 'string') { | ||
return '[[' + target + (displaytext ? '|' + displaytext : '') + ']]'; | ||
} | ||
return '[[' + target.toText() + | ||
(target.fragment ? '#' + target.fragment : '') + | ||
(displaytext ? '|' + displaytext : '') + | ||
']]'; | ||
} | ||
function rawurlencode(str: string ) { | ||
/** | ||
* Get wikitext for a template usage | ||
* @param title | ||
* @param [parameters={}] - template parameters as object | ||
*/ | ||
export function template(title: string | MwnTitle, parameters: {[parameter: string]: string} = {}): string { | ||
if (typeof title !== 'string') { | ||
if (title.namespace === 10) { | ||
title = title.getMainText(); // skip namespace name for templates | ||
} else if (title.namespace === 0) { | ||
title = ':' + title.toText(); // prefix colon for mainspace | ||
} else { | ||
title = title.toText(); | ||
} | ||
} | ||
return '{{' + title + | ||
Object.entries(parameters).map(([key, val]) => { | ||
if (!val) { // skip parameter if no value provided | ||
return ''; | ||
} | ||
return '|' + key + '=' + val; | ||
}).join('') + | ||
'}}'; | ||
} | ||
export class table { | ||
text: string | ||
multiline: boolean | ||
/** | ||
* @param {Object} [config={}] | ||
* @config {boolean} plain - plain table without borders (default: false) | ||
* @config {boolean} sortable - make columns sortable (default: true) | ||
* @config {string} style - style attribute | ||
* @config {boolean} multiline - put each cell of the table on a new line, | ||
* this causes no visual changes, but the wikitext representation is different. | ||
* This is more reliable. (default: true) | ||
*/ | ||
constructor(config: { | ||
plain?: boolean | ||
sortable?: boolean | ||
style?: string | ||
multiline?: boolean | ||
} = {}) { | ||
let classes = []; | ||
if (!config.plain) { | ||
classes.push('wikitable'); | ||
} | ||
if (config.sortable !== false) { | ||
classes.push('sortable'); | ||
} | ||
if (config.multiline !== false) { | ||
this.multiline = true; | ||
} | ||
this.text = `{|`; | ||
if (classes.length) { | ||
this.text += ` class="${classes.join(' ')}"`; | ||
} | ||
if (config.style) { | ||
this.text += ` style="${config.style}"`; | ||
} | ||
this.text += '\n'; | ||
} | ||
_makecell(cell: string | {[attribute: string]: string}, isHeader?: boolean): string { | ||
// typeof null is also object! | ||
if (cell && typeof cell === 'object') { | ||
let text = isHeader ? `scope="col"` : ``; | ||
for (let [key, value] of Object.entries(cell)) { | ||
if (key === 'label') { | ||
continue; | ||
} | ||
text += ` ${key}="${value}"`; | ||
} | ||
text += ` | ${cell.label}`; | ||
return text; | ||
} | ||
return String(cell); | ||
} | ||
/** | ||
* Add the headers | ||
* @param headers - array of header items | ||
*/ | ||
addHeaders(headers: (string | {[attribute: string]: string})[]): void { | ||
this.text += `|-\n`; // row separator | ||
if (this.multiline) { | ||
this.text += headers.map(e => `! ${this._makecell(e, true)} \n`).join(''); | ||
} else { | ||
this.text += `! ` + headers.map(e => this._makecell(e, true)).join(' !! ') + '\n'; | ||
} | ||
} | ||
/** | ||
* Add a row to the table | ||
* @param fields - array of items on the row, | ||
* @param attributes - row attributes | ||
*/ | ||
addRow(fields: string[], attributes: {[attribute: string]: string} = {}): void { | ||
let attributetext = ''; | ||
Object.entries(attributes).forEach(([key, value]) => { | ||
attributetext += ` ${key}="${value}"`; | ||
}); | ||
this.text += `|-${attributetext}\n`; // row separator | ||
if (this.multiline) { | ||
this.text += fields.map(e => `| ${this._makecell(e)} \n`).join(''); | ||
} else { | ||
this.text += `| ` + fields.map(f => this._makecell(f)).join(' || ') + '\n'; | ||
} | ||
} | ||
/** Returns the final table wikitext */ | ||
getText(): string { | ||
return this.text + `|}`; // add the table closing tag and return | ||
} | ||
} | ||
/** | ||
* Encode the string like PHP's rawurlencode | ||
* | ||
* @param {string} str String to be encoded. | ||
* @return {string} Encoded string | ||
*/ | ||
function rawurlencode(str: string ): string { | ||
return encodeURIComponent( String( str ) ) | ||
@@ -18,3 +151,9 @@ .replace( /!/g, '%21' ).replace( /'/g, '%27' ).replace( /\(/g, '%28' ) | ||
function isIPv4Address( address: string, allowBlock?: boolean ) { | ||
/** | ||
* Check if string is an IPv4 address | ||
* @param {string} address | ||
* @param {boolean} [allowBlock=false] | ||
* @return {boolean} | ||
*/ | ||
function isIPv4Address( address: string, allowBlock?: boolean ): boolean { | ||
let block, | ||
@@ -30,3 +169,9 @@ RE_IP_BYTE = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])', | ||
function isIPv6Address( address: string, allowBlock?: boolean ) { | ||
/** | ||
* Check if the string is an IPv6 address | ||
* @param {string} address | ||
* @param {boolean} [allowBlock=false] | ||
* @return {boolean} | ||
*/ | ||
function isIPv6Address( address: string, allowBlock?: boolean ): boolean { | ||
let block, RE_IPV6_ADD; | ||
@@ -39,15 +184,15 @@ if ( typeof address !== 'string' ) { | ||
'(?:' + // starts with "::" (including "::") | ||
':(?::|(?::' + | ||
'[0-9A-Fa-f]{1,4}' + | ||
'){1,7})' + | ||
'|' + // ends with "::" (except "::") | ||
'[0-9A-Fa-f]{1,4}' + | ||
'(?::' + | ||
'[0-9A-Fa-f]{1,4}' + | ||
'){0,6}::' + | ||
'|' + // contains no "::" | ||
'[0-9A-Fa-f]{1,4}' + | ||
'(?::' + | ||
'[0-9A-Fa-f]{1,4}' + | ||
'){7}' + | ||
':(?::|(?::' + | ||
'[0-9A-Fa-f]{1,4}' + | ||
'){1,7})' + | ||
'|' + // ends with "::" (except "::") | ||
'[0-9A-Fa-f]{1,4}' + | ||
'(?::' + | ||
'[0-9A-Fa-f]{1,4}' + | ||
'){0,6}::' + | ||
'|' + // contains no "::" | ||
'[0-9A-Fa-f]{1,4}' + | ||
'(?::' + | ||
'[0-9A-Fa-f]{1,4}' + | ||
'){7}' + | ||
')'; | ||
@@ -61,3 +206,3 @@ if ( new RegExp( '^' + RE_IPV6_ADD + block + '$' ).test( address ) ) { | ||
'(?:::?' + | ||
'[0-9A-Fa-f]{1,4}' + | ||
'[0-9A-Fa-f]{1,4}' + | ||
'){1,6}'; | ||
@@ -71,239 +216,78 @@ return ( | ||
export default { | ||
/** | ||
* Escape string for safe inclusion in regular expression. | ||
* The following characters are escaped: | ||
* \ { } ( ) | . ? * + - ^ $ [ ] | ||
* @param {string} str String to escape | ||
* @return {string} Escaped string | ||
*/ | ||
function escapeRegExp( str: string ): string { | ||
// eslint-disable-next-line no-useless-escape | ||
return str.replace( /([\\{}()|.?*+\-^$\[\]])/g, '\\$1' ); | ||
} | ||
/** | ||
* Get wikitext for a new link | ||
* @param target | ||
* @param [displaytext] | ||
*/ | ||
link: function(target: string | MwnTitle, displaytext?: string) { | ||
if (typeof target === 'string') { | ||
return '[[' + target + (displaytext ? '|' + displaytext : '') + ']]'; | ||
/** | ||
* Escape a string for HTML. Converts special characters to HTML entities. | ||
* | ||
* Util.escapeHtml( '< > \' & "' ); | ||
* // Returns < > ' & " | ||
* | ||
* @param {string} s - The string to escape | ||
* @return {string} HTML | ||
*/ | ||
function escapeHtml( s: string ): string { | ||
return s.replace( /['"<>&]/g, function escapeCallback( s ) { | ||
switch ( s ) { | ||
case '\'': | ||
return '''; | ||
case '"': | ||
return '"'; | ||
case '<': | ||
return '<'; | ||
case '>': | ||
return '>'; | ||
case '&': | ||
return '&'; | ||
} | ||
return '[[' + target.toText() + | ||
(target.fragment ? '#' + target.fragment : '') + | ||
(displaytext ? '|' + displaytext : '') + | ||
']]'; | ||
}, | ||
}); | ||
} | ||
/** | ||
* Get wikitext for a template usage | ||
* @param title | ||
* @param [parameters={}] - template parameters as object | ||
*/ | ||
template: function(title: string | MwnTitle, parameters: {[parameter: string]: string} = {}) { | ||
if (typeof title !== 'string') { | ||
if (title.namespace === 10) { | ||
title = title.getMainText(); // skip namespace name for templates | ||
} else if (title.namespace === 0) { | ||
title = ':' + title.toText(); // prefix colon for mainspace | ||
} else { | ||
title = title.toText(); | ||
} | ||
} | ||
return '{{' + title + | ||
Object.entries(parameters).map(([key, val]) => { | ||
if (!val) { // skip parameter if no value provided | ||
return ''; | ||
} | ||
return '|' + key + '=' + val; | ||
}).join('') + | ||
'}}'; | ||
}, | ||
/** | ||
* Encode page titles for use in a URL like mw.util.wikiUrlencode() | ||
* | ||
* We want / and : to be included as literal characters in our title URLs | ||
* as they otherwise fatally break the title. The others are decoded because | ||
* we can, it's prettier and matches behaviour of `wfUrlencode` in PHP. | ||
* | ||
* @param {string} str String to be encoded. | ||
* @return {string} Encoded string | ||
*/ | ||
function wikiUrlencode( str: string ): string { | ||
return rawurlencode( str ) | ||
.replace( /%20/g, '_' ) | ||
// wfUrlencode replacements | ||
.replace( /%3B/g, ';' ) | ||
.replace( /%40/g, '@' ) | ||
.replace( /%24/g, '$' ) | ||
.replace( /%21/g, '!' ) | ||
.replace( /%2A/g, '*' ) | ||
.replace( /%28/g, '(' ) | ||
.replace( /%29/g, ')' ) | ||
.replace( /%2C/g, ',' ) | ||
.replace( /%2F/g, '/' ) | ||
.replace( /%7E/g, '~' ) | ||
.replace( /%3A/g, ':' ); | ||
} | ||
table: class table { | ||
text: string | ||
multiline: boolean | ||
/** | ||
* Check whether a string is an IP address | ||
* @param {string} address String to check | ||
* @param {boolean} [allowBlock=false] True if a block of IPs should be allowed | ||
* @return {boolean} | ||
*/ | ||
function isIPAddress( address: string, allowBlock?: boolean ): boolean { | ||
return isIPv4Address( address, allowBlock ) || | ||
isIPv6Address( address, allowBlock ); | ||
} | ||
/** | ||
* @param {Object} [config={}] | ||
* @config {boolean} plain - plain table without borders (default: false) | ||
* @config {boolean} sortable - make columns sortable (default: true) | ||
* @config {string} style - style attribute | ||
* @config {boolean} multiline - put each cell of the table on a new line, | ||
* this causes no visual changes, but the wikitext representation is different. | ||
* This is more reliable. (default: true) | ||
*/ | ||
constructor(config: { | ||
plain?: boolean | ||
sortable?: boolean | ||
style?: string | ||
multiline?: boolean | ||
} = {}) { | ||
let classes = []; | ||
if (!config.plain) { | ||
classes.push('wikitable'); | ||
} | ||
if (config.sortable !== false) { | ||
classes.push('sortable'); | ||
} | ||
if (config.multiline !== false) { | ||
this.multiline = true; | ||
} | ||
this.text = `{|`; | ||
if (classes.length) { | ||
this.text += ` class="${classes.join(' ')}"`; | ||
} | ||
if (config.style) { | ||
this.text += ` style="${config.style}"`; | ||
} | ||
this.text += '\n'; | ||
} | ||
_makecell(cell: string | {[attribute: string]: string}, isHeader?: boolean): string { | ||
// typeof null is also object! | ||
if (cell && typeof cell === 'object') { | ||
let text = isHeader ? `scope="col"` : ``; | ||
for (let [key, value] of Object.entries(cell)) { | ||
if (key === 'label') { | ||
continue; | ||
} | ||
text += ` ${key}="${value}"`; | ||
} | ||
text += ` | ${cell.label}`; | ||
return text; | ||
} | ||
return String(cell); | ||
} | ||
/** | ||
* Add the headers | ||
* @param headers - array of header items | ||
*/ | ||
addHeaders(headers: (string | {[attribute: string]: string})[]) { | ||
this.text += `|-\n`; // row separator | ||
if (this.multiline) { | ||
this.text += headers.map(e => `! ${this._makecell(e, true)} \n`).join(''); | ||
} else { | ||
this.text += `! ` + headers.map(e => this._makecell(e, true)).join(' !! ') + '\n'; | ||
} | ||
} | ||
/** | ||
* Add a row to the table | ||
* @param fields - array of items on the row, | ||
* @param attributes - row attributes | ||
*/ | ||
addRow(fields: string[], attributes: {[attribute: string]: string} = {}) { | ||
let attributetext = ''; | ||
Object.entries(attributes).forEach(([key, value]) => { | ||
attributetext += ` ${key}="${value}"`; | ||
}); | ||
this.text += `|-${attributetext}\n`; // row separator | ||
if (this.multiline) { | ||
this.text += fields.map(e => `| ${this._makecell(e)} \n`).join(''); | ||
} else { | ||
this.text += `| ` + fields.map(f => this._makecell(f)).join(' || ') + '\n'; | ||
} | ||
} | ||
/** Returns the final table wikitext */ | ||
getText(): string { | ||
return this.text + `|}`; // add the table closing tag and return | ||
} | ||
}, | ||
util: { | ||
/** | ||
* Escape string for safe inclusion in regular expression. | ||
* The following characters are escaped: | ||
* \ { } ( ) | . ? * + - ^ $ [ ] | ||
* @param {string} str String to escape | ||
* @return {string} Escaped string | ||
*/ | ||
escapeRegExp: function( str: string ): string { | ||
// eslint-disable-next-line no-useless-escape | ||
return str.replace( /([\\{}()|.?*+\-^$\[\]])/g, '\\$1' ); | ||
}, | ||
/** | ||
* Escape a string for HTML. Converts special characters to HTML entities. | ||
* | ||
* Util.escapeHtml( '< > \' & "' ); | ||
* // Returns < > ' & " | ||
* | ||
* @param {string} s - The string to escape | ||
* @return {string} HTML | ||
*/ | ||
escapeHtml: function( s: string ): string { | ||
return s.replace( /['"<>&]/g, function escapeCallback( s ) { | ||
switch ( s ) { | ||
case '\'': | ||
return '''; | ||
case '"': | ||
return '"'; | ||
case '<': | ||
return '<'; | ||
case '>': | ||
return '>'; | ||
case '&': | ||
return '&'; | ||
} | ||
}); | ||
}, | ||
/** | ||
* Encode the string like PHP's rawurlencode | ||
* | ||
* @param {string} str String to be encoded. | ||
* @return {string} Encoded string | ||
*/ | ||
rawurlencode: rawurlencode, | ||
/** | ||
* Encode page titles for use in a URL like mw.util.wikiUrlencode() | ||
* | ||
* We want / and : to be included as literal characters in our title URLs | ||
* as they otherwise fatally break the title. The others are decoded because | ||
* we can, it's prettier and matches behaviour of `wfUrlencode` in PHP. | ||
* | ||
* @param {string} str String to be encoded. | ||
* @return {string} Encoded string | ||
*/ | ||
wikiUrlencode: function( str: string ): string { | ||
return rawurlencode( str ) | ||
.replace( /%20/g, '_' ) | ||
// wfUrlencode replacements | ||
.replace( /%3B/g, ';' ) | ||
.replace( /%40/g, '@' ) | ||
.replace( /%24/g, '$' ) | ||
.replace( /%21/g, '!' ) | ||
.replace( /%2A/g, '*' ) | ||
.replace( /%28/g, '(' ) | ||
.replace( /%29/g, ')' ) | ||
.replace( /%2C/g, ',' ) | ||
.replace( /%2F/g, '/' ) | ||
.replace( /%7E/g, '~' ) | ||
.replace( /%3A/g, ':' ); | ||
}, | ||
/** | ||
* Check if string is an IPv4 address | ||
* @param {string} address | ||
* @param {boolean} [allowBlock=false] | ||
* @return {boolean} | ||
*/ | ||
isIPv4Address: isIPv4Address, | ||
/** | ||
* Check if the string is an IPv6 address | ||
* @param {string} address | ||
* @param {boolean} [allowBlock=false] | ||
* @return {boolean} | ||
*/ | ||
isIPv6Address: isIPv6Address, | ||
/** | ||
* Check whether a string is an IP address | ||
* @param {string} address String to check | ||
* @param {boolean} [allowBlock=false] True if a block of IPs should be allowed | ||
* @return {boolean} | ||
*/ | ||
isIPAddress: function( address: string, allowBlock?: boolean ) { | ||
return isIPv4Address( address, allowBlock ) || | ||
isIPv6Address( address, allowBlock ); | ||
} | ||
} | ||
}; | ||
export const util = { escapeRegExp, escapeHtml, rawurlencode, wikiUrlencode, isIPv4Address, isIPv6Address, isIPAddress }; |
@@ -11,6 +11,59 @@ /** | ||
import type {mwn, MwnTitle} from "./bot"; | ||
import type {mwn} from "./bot"; | ||
module.exports = function (bot: mwn) { | ||
export interface MwnTitleStatic { | ||
new (title: string, namespace?: number): MwnTitle; | ||
idNameMap: { | ||
[namespaceId: number]: string; | ||
}; | ||
nameIdMap: { | ||
[namespaceName: string]: number; | ||
}; | ||
legaltitlechars: string; | ||
caseSensitiveNamespaces: Array<number>; | ||
processNamespaceData(json: { | ||
query: { | ||
general: { | ||
legaltitlechars: string; | ||
}; | ||
namespaces: { | ||
name: string; | ||
id: number; | ||
canonical: boolean; | ||
case: string; | ||
}[]; | ||
namespacealiases: { | ||
alias: string; | ||
id: number; | ||
}[]; | ||
}; | ||
}): void; | ||
checkData(): void; | ||
newFromText(title: string, namespace?: number): MwnTitle | null; | ||
makeTitle(namespace: number, title: string): MwnTitle | null; | ||
isTalkNamespace(namespaceId: number): boolean; | ||
phpCharToUpper(chr: string): string; | ||
} | ||
export interface MwnTitle { | ||
title: string; | ||
namespace: number; | ||
fragment: string; | ||
getNamespaceId(): number; | ||
getMain(): string; | ||
getMainText(): string; | ||
getPrefixedDb(): string; | ||
getPrefixedText(): string; | ||
getFragment(): string | null; | ||
isTalkPage(): boolean; | ||
getTalkPage(): MwnTitle | null; | ||
getSubjectPage(): MwnTitle | null; | ||
canHaveTalkPage(): boolean; | ||
getExtension(): string | null; | ||
getDotExtension(): string; | ||
toString(): string; | ||
toText(): string; | ||
} | ||
export default function (bot: mwn) { | ||
var NS_MAIN = 0; | ||
@@ -1261,4 +1314,4 @@ var NS_TALK = 1; | ||
return Title; | ||
return Title as MwnTitleStatic; | ||
}; | ||
} |
@@ -1,2 +0,2 @@ | ||
import type {mwn, MwnPage, MwnUser} from "./bot"; | ||
import type {mwn, MwnPage} from "./bot"; | ||
import type { | ||
@@ -10,2 +10,23 @@ ApiBlockParams, | ||
export interface MwnUserStatic { | ||
new (username: string): MwnUser; | ||
} | ||
export interface MwnUser { | ||
username: string; | ||
userpage: MwnPage; | ||
talkpage: MwnPage; | ||
contribs(options?: ApiQueryUserContribsParams): Promise<UserContribution[]>; | ||
contribsGen(options?: ApiQueryUserContribsParams): AsyncGenerator<UserContribution>; | ||
logs(options?: ApiQueryLogEventsParams): Promise<LogEvent[]>; | ||
logsGen(options?: ApiQueryLogEventsParams): AsyncGenerator<LogEvent>; | ||
info(props?: string | string[]): Promise<any>; | ||
globalinfo(props?: ("groups" | "rights" | "merged" | "unattached" | "editcount")[]): Promise<any>; | ||
sendMessage(header: string, message: string): Promise<any>; | ||
email(subject: string, message: string, options?: ApiEmailUserParams): Promise<any>; | ||
block(options: ApiBlockParams): Promise<any>; | ||
unblock(options: ApiUnblockParams): Promise<any>; | ||
} | ||
export type UserContribution = { | ||
@@ -41,3 +62,3 @@ userid: number | ||
module.exports = function(bot: mwn) { | ||
export default function(bot: mwn) { | ||
@@ -210,4 +231,4 @@ class User implements MwnUser { | ||
return User; | ||
return User as MwnUserStatic; | ||
}; | ||
} |
@@ -16,5 +16,30 @@ /** | ||
import type {mwn, MwnTitle, MwnWikitext} from "./bot"; | ||
import type {mwn, MwnTitle} from "./bot"; | ||
import type {ApiParseParams} from "./api_params"; | ||
export interface MwnWikitextStatic { | ||
new (text: string): MwnWikitext; | ||
parseTemplates(wikitext: string, config: TemplateConfig): Template[]; | ||
parseTable(text: string): { | ||
[column: string]: string; | ||
}[]; | ||
parseSections(text: string): Section[]; | ||
} | ||
export interface MwnWikitext { | ||
text: string; | ||
links: Array<PageLink>; | ||
templates: Array<Template>; | ||
files: Array<FileLink>; | ||
categories: Array<CategoryLink>; | ||
sections: Array<Section>; | ||
parseLinks(): void; | ||
parseTemplates(config: TemplateConfig): Template[]; | ||
removeEntity(entity: Link | Template): void; | ||
parseSections(): Section[]; | ||
unbind(prefix: string, postfix: string): void; | ||
rebind(): string; | ||
getText(): string; | ||
apiParse(options: ApiParseParams): Promise<string>; | ||
} | ||
export interface Link { | ||
@@ -110,3 +135,3 @@ wikitext: string | ||
module.exports = function (bot: mwn) { | ||
export default function (bot: mwn) { | ||
@@ -617,4 +642,4 @@ class Wikitext implements MwnWikitext { | ||
return Wikitext; | ||
return Wikitext as MwnWikitextStatic; | ||
}; | ||
} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
638495
46
16175
11
18
315
+ Addedchalk@^1.1.3
+ Addedprettyjson@^1.1.3
+ Addedaxios@0.21.4(transitive)
+ Addedfollow-redirects@1.15.9(transitive)
- Removedsemlog@^0.6.10
- Removedaxios@0.19.2(transitive)
- Removedfollow-redirects@1.5.10(transitive)
- Removedsemlog@0.6.10(transitive)
Updatedaxios@^0.21.1