src/Suppress/userLog.js
import { load } from 'cheerio';
import { MediaWikiApi } from 'wiki-saikou';
import clientLogin from '../utils/clientLogin.js';
import config from '../utils/config.js';
import { getTimeData, editTimeData } from '../utils/lastTime.js';
import splitAndJoin from '../utils/splitAndJoin.js';
const zhapi = new MediaWikiApi(config.zh.api, {
headers: { 'user-agent': config.useragent },
}),
cmapi = new MediaWikiApi(config.cm.api, {
headers: { 'user-agent': config.useragent },
});
async function queryLogs(api, leaction, leend, lestart = undefined) {
const result = [];
const eol = Symbol();
let lecontinue = undefined;
while (lecontinue !== eol) {
const { data } = await api.post({
list: 'logevents',
leprop: leaction === 'avatar/delete' ? 'ids|comment' : 'title',
leaction,
lestart,
leend,
lelimit: 'max',
...leaction === 'avatar/delete' && { leuser: '星海-adminbot' },
lecontinue,
}, {
retry: 15,
});
lecontinue = data.continue ? data.continue.lecontinue : eol;
result.push(...data.query.logevents);
}
return leaction === 'avatar/delete'
? [...new Set(result.filter(({ comment, suppressed }) => !suppressed && comment === '被隐藏的用户').map(({ logid }) => logid))]
: [...new Set(result.map(({ title }) => title))];
}
async function queryPages(apprefix, apnamespace) {
const { data: { query: { allpages } } } = await zhapi.post({
list: 'allpages',
apprefix,
apnamespace,
}, {
retry: 15,
});
const user = apprefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const prefixRegex = new RegExp(`^User(?: talk)?:(${user}|${user}/.*)$`);
return allpages
.map((page) => page.title)
.filter((title) => prefixRegex.test(title));
}
async function hidePages(user) {
const pagelist = await Promise.all([
queryPages(user, '2'),
queryPages(user, '3'),
]).then((result) => result.flat());
await Promise.all(pagelist.map(async (title) => {
let retry = 0;
while (retry < 30) {
const { response: { data: htmlString } } = await zhapi.request.get('/index.php', {
query: {
title,
action: 'delete',
},
});
if (htmlString.includes('<title>无法删除页面')) {
console.warn('The page could not be deleted. It may have already been deleted by someone else.');
break;
}
const $ = load(htmlString);
const wpEditToken = $('[name="wpEditToken"]').get(0)?.attribs.value;
if (wpEditToken) {
const { response: { data } } = await zhapi.request.post('/index.php', {
title,
action: 'delete',
wpDeleteReasonList: 'other',
wpReason: '被隐藏的用户',
wpSuppress: '',
wpConfirmB: '删除页面',
wpEditToken,
});
if (data.includes('<title>操作完成')) {
console.log('Successful deleted the page');
break;
}
}
console.warn(`删除页面失败。重试第 ${retry} 次...`);
retry++;
}
}));
}
async function hideAbuseLog(afluser) {
let retry = 0;
while (retry < 30) {
const ids = await (async () => {
const result = [];
const eol = Symbol();
let aflstart = undefined;
while (aflstart !== eol) {
const { data } = await zhapi.post({
list: 'abuselog',
afluser,
afllimit: 'max',
aflprop: 'ids|hidden',
aflstart,
}, {
retry: 15,
});
aflstart = data.continue ? data.continue.aflstart : eol;
result.push(...data.query.abuselog);
}
return result.filter(({ hidden }) => !hidden).map(({ id }) => id);
})();
if (!ids.length) {
break;
}
console.log(`Retry: ${retry}, ids: ${ids.length}`);
await Promise.all(ids.map(async (id) => {
await zhapi.request.post('/index.php', {
title: 'Special:滥用日志',
hide: id,
wpdropdownreason: 'other',
wpreason: '被隐藏的用户',
wphidden: true,
wpEditToken: await zhapi.token('csrf'),
}).then(() => console.log(`Try to hide ${id}`));
}));
retry++;
}
}
async function deleteAvatar(user) {
let retry = 0;
while (retry < 15) {
const { response: { data } } = await cmapi.request.post('/index.php', {
title: 'Special:查看头像',
'delete': 'true',
user,
reason: '被隐藏的用户',
});
if (data.includes('该用户没有头像。')) {
console.log('Successful deleted the avatar!');
break;
}
retry++;
if (retry === 10) {
console.warn('Failed to delete the avatar!');
}
}
}
(async () => {
console.log(`Start time: ${new Date().toISOString()}`);
await Promise.all([
clientLogin(zhapi, config.cm.sbot.account, config.password),
clientLogin(cmapi, config.cm.sbot.account, config.password),
]);
const lastTime = await getTimeData('hide-user-log');
const leend = lastTime['hide-user-log'],
lestart = new Date().toISOString();
const userlist = await Promise.all([
queryLogs(zhapi, 'suppress/block', leend, lestart),
queryLogs(zhapi, 'suppress/reblock', leend, lestart),
]).then((result) => result.flat().map((title) => title.replace('User:', '')));
await Promise.all(userlist.map(async (user) => {
await Promise.all([
hidePages(user),
hideAbuseLog(user),
deleteAvatar(user),
]);
}));
const idlist = await Promise.all([
Promise.all(userlist.map(async (user) => {
const result = [];
const eol = Symbol();
let lecontinue = undefined;
while (lecontinue !== eol) {
const { data } = await cmapi.post({
list: 'logevents',
leprop: 'ids',
leuser: user.replace('User:', ''),
lelimit: 'max',
lecontinue,
}, {
retry: 15,
});
lecontinue = data.continue ? data.continue.lecontinue : eol;
result.push(...data.query.logevents);
}
return result
.filter(({ suppressed }) => !suppressed)
.map(({ logid }) => logid);
})),
queryLogs(cmapi, 'avatar/delete', new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString()),
]).then((result) => result.flat(Infinity));
const idslist = splitAndJoin(idlist, 500);
await Promise.all(idslist.map(async (ids) => {
await cmapi.postWithToken('csrf', {
action: 'revisiondelete',
type: 'logging',
ids,
hide: 'content|user|comment',
suppress: 'yes',
reason: 'test',
tags: 'Bot',
}, {
retry: 50,
noCache: true,
}).then(({ data }) => {
data.revisiondelete.items = data.revisiondelete.items
.map(({ status, id, action, type }) => ({ status, id, action, type }));
console.log(JSON.stringify(data));
});
}));
await editTimeData(lastTime, 'hide-user-log', lestart);
console.log(`End time: ${new Date().toISOString()}`);
})();