From 9f2d89551dd756b49bf1ded0f33fbc32f5167c48 Mon Sep 17 00:00:00 2001 From: Siddharth VP Date: Wed, 22 Mar 2023 15:46:59 +0530 Subject: [PATCH] Add back queryAuthors() method Now using the wikiwho.wmflabs.org endpoint. --- CHANGELOG.md | 2 +- README.md | 2 + src/page.ts | 103 +++++++++++++++++- tests/suppl.bot.test.js | 13 +++ .../docs/11-integration-with-other-apis.md | 13 +++ 5 files changed, 131 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f2d6c3..28de0a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Only breaking changes, deprecations and the like are documented in this change l #### 2.0.0 -- mwn#queryAuthors() and `queryAuthors()` on page objects are removed. They relied on the WikiWho API which is now defunct. +- mwn#queryAuthors() which had been deprecated has now been removed. Instead, please use `queryAuthors()` method on page objects. #### 0.11.0 diff --git a/README.md b/README.md index a028328..90ff064 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ Mwn works with both JavaScript and TypeScript. It is created with a design philo This library provides TypeScript type definitions for all its functions, as well as for MediaWiki API request objects (MW core + several extensions). API responses are also typed for the common operations. +In addition to the MediaWiki Action API, methods are provided to talk to the Wikimedia Pageviews API, the ORES API, and WikiWho API. + This library uses mocha and chai for tests, and has [extensive test coverage](https://coveralls.io/github/siddharthvp/mwn?branch=master). Testing is automated using a CI workflow on GitHub Actions. To install, run `npm install mwn`. diff --git a/src/page.ts b/src/page.ts index 2a250b5..63530c9 100644 --- a/src/page.ts +++ b/src/page.ts @@ -148,7 +148,13 @@ export interface MwnPage extends MwnTitle { * @param options */ pageViews(options?: PageViewOptions): Promise; - + /** + * Query the top contributors to the article using the WikiWho API. + * This API has a throttling of 2000 requests a day. + * Supported for EN, DE, ES, EU, TR Wikipedias only + * @see https://wikiwho.wmflabs.org/ + */ + queryAuthors(): Promise; edit(transform: (rev: { content: string; timestamp: string }) => string | ApiEditPageParams): Promise; save(text: string, summary?: string, options?: ApiEditPageParams): Promise; newSection(header: string, message: string, additionalParams?: ApiEditPageParams): Promise; @@ -549,6 +555,91 @@ export default function (bot: mwn): MwnPageStatic { }); } + /** @inheritDoc */ + async queryAuthors(): Promise { + let langcodematch = bot.options.apiUrl.match(/([^/]*?)\.wikipedia\.org/); + if (!langcodematch || !langcodematch[1]) { + throw new Error('WikiWho API is not supported for bot API URL. Re-check.'); + } + + let json; + try { + json = await bot + .rawRequest({ + url: `https://wikiwho.wmflabs.org/${ + langcodematch[1] + }/api/v1.0.0-beta/latest_rev_content/${encodeURIComponent(this.toString())}/?editor=true`, + headers: { + 'User-Agent': bot.options.userAgent, + }, + }) + .then((response) => response.data); + } catch (err) { + throw new Error(err && err.response && err.response.data && err.response.data.Error); + } + + const tokens = Object.values(json.revisions[0])[0].tokens; + + let data: AuthorshipData = { + totalBytes: 0, + users: [], + }; + let userdata: { + [editor: string]: { + name?: string; + bytes: number; + percent?: number; + }; + } = {}; + + for (let token of tokens) { + data.totalBytes += token.str.length; + let editor = token['editor']; + if (!userdata[editor]) { + userdata[editor] = { bytes: 0 }; + } + userdata[editor].bytes += token.str.length; + if (editor.startsWith('0|')) { + // IP + userdata[editor].name = editor.slice(2); + } + } + + Object.entries(userdata).map(([userid, { bytes }]) => { + userdata[userid].percent = bytes / data.totalBytes; + if (userdata[userid].percent < 0.02) { + delete userdata[userid]; + } + }); + + await bot + .request({ + action: 'query', + list: 'users', + ususerids: Object.keys(userdata).filter((us) => !us.startsWith('0|')), // don't lookup IPs + }) + .then((json) => { + json.query.users.forEach((us: any) => { + userdata[us.userid].name = us.name; + }); + }); + + data.users = Object.entries(userdata) + .map(([userid, { bytes, name, percent }]) => { + return { + id: Number(userid), + name: name, + bytes: bytes, + percent: percent, + }; + }) + .sort((a, b) => { + return a.bytes < b.bytes ? 1 : -1; + }); + + return data; + } + /**** Post operations *****/ // Defined in bot.js @@ -600,3 +691,13 @@ export interface PageViewData { agent: string; views: number; } + +export interface AuthorshipData { + totalBytes: number; + users: Array<{ + id: number; + name: string; + bytes: number; + percent: number; + }>; +} diff --git a/tests/suppl.bot.test.js b/tests/suppl.bot.test.js index d49b4f0..4155693 100644 --- a/tests/suppl.bot.test.js +++ b/tests/suppl.bot.test.js @@ -58,4 +58,17 @@ describe('supplementary functions', function () { expect(views[0]).to.be.an('object').with.property('timestamp').that.equals('2021010100'); expect(views[1]).to.be.an('object').with.property('timestamp').that.equals('2021020100'); }); + + it('wikiwho', async function () { + this.timeout(10000); + await bot.getSiteInfo(); + let page = new bot.page('Dairy in India'); + let data = await page.queryAuthors(); + expect(data).to.be.an('object').with.property('totalBytes').that.is.a('number'); + expect(data).to.have.property('users').that.is.instanceOf(Array).of.length.greaterThan(1); + expect(data.users[0]).to.be.an('object').with.property('id').that.is.a('number'); + expect(data.users[0]).to.have.property('name').that.is.a('string'); + expect(data.users[0]).to.have.property('percent').that.is.a('number'); + expect(data.users[0]).to.have.property('bytes').that.is.a('number'); + }); }); diff --git a/website/docs/11-integration-with-other-apis.md b/website/docs/11-integration-with-other-apis.md index 958b4f8..def2ef8 100644 --- a/website/docs/11-integration-with-other-apis.md +++ b/website/docs/11-integration-with-other-apis.md @@ -28,6 +28,19 @@ const pageViewData = await page.pageViews({ The [PageViewOptions](https://mwn.toolforge.org/docs/api/interfaces/pageviewoptions.html) argument is optional. Return type is Promise<PageViewData[]>. +### WikiWho + +See + +Fetch the list of top contributors to an article. Available for limited number of Wikipedias. + +```js +const page = new bot.page('Lorem ipsum'); +const contributorData = await page.queryAuthors(); +``` + +Return type is Promise<AuthorshipData>. + ### EventStreams Methods for [EventStreams](https://wikitech.wikimedia.org/wiki/Event_Platform/EventStreams) access have been removed in v2. Consider using the dedicated library instead.