0
0
Fork 0
misskey-tools/src/backend/services/worker.ts
2023-02-25 17:13:07 +09:00

129 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import cron from 'node-cron';
import { deleteUser } from '../functions/users.js';
import { MiUser, updateScore } from '../functions/update-score.js';
import { updateRating } from '../functions/update-rating.js';
import { Users } from '../models/index.js';
import {sendNoteAlert, sendNotificationAlert} from './send-alert.js';
import {api, MisskeyError, TimedOutError} from './misskey.js';
import * as Store from '../store.js';
import { User } from '../models/entities/user.js';
import {groupBy} from '../utils/group-by.js';
import {clearLog, printLog} from '../store.js';
import {errorToString} from '../functions/error-to-string.js';
import {Acct, toAcct} from '../models/acct.js';
import {Count} from '../models/count.js';
import {format} from '../../common/functions/format.js';
import {delay} from '../utils/delay.js';
const ERROR_CODES_USER_REMOVED = ['NO_SUCH_USER', 'AUTHENTICATION_FAILED', 'YOUR_ACCOUNT_SUSPENDED'];
// TODO: Redisで持つようにしたい
const userScoreCache = new Map<Acct, Count>();
export default (): void => {
cron.schedule('0 0 0 * * *', work);
};
export const work = async () => {
Store.dispatch({ nowCalculating: true });
clearLog();
printLog('Started.');
try {
const users = await Users.find();
const groupedUsers = groupBy(users, u => u.host);
printLog(`${users.length} アカウントのレート計算を開始します。`);
await calculateAllRating(groupedUsers);
Store.dispatch({ nowCalculating: false });
printLog(`${users.length} アカウントのアラート送信を開始します。`);
await sendAllAlerts(groupedUsers);
printLog('ミス廃アラートワーカーは正常に完了しました。');
} catch (e) {
printLog('ミス廃アラートワーカーが異常終了しました。', 'error');
printLog(e instanceof Error ? errorToString(e) : JSON.stringify(e, null, ' '), 'error');
} finally {
Store.dispatch({ nowCalculating: false });
}
};
const calculateAllRating = async (groupedUsers: [string, User[]][]) => {
return await Promise.all(groupedUsers.map(kv => calculateRating(...kv)));
};
const calculateRating = async (host: string, users: User[]) => {
for (const user of users) {
let miUser: MiUser;
try {
miUser = await api<MiUser>(user.host, 'i', {}, user.token);
} catch (e) {
if (!(e instanceof Error)) {
printLog('バグエラーオブジェクトはErrorを継承していないといけない', 'error');
} else if (e instanceof MisskeyError) {
if (ERROR_CODES_USER_REMOVED.includes(e.error.code)) {
// ユーザーが削除されている場合、レコードからも消してとりやめ
printLog(`アカウント ${toAcct(user)} は削除されているか、凍結されているか、トークンを失効しています。そのため、本システムからアカウントを削除します。`, 'warn');
await deleteUser(user.username, user.host);
} else {
printLog(`Misskey エラー: ${JSON.stringify(e.error)}`, 'error');
}
} else if (e instanceof TimedOutError) {
printLog(`サーバー ${user.host} との接続に失敗したため、このサーバーのレート計算を中断します。`, 'error');
return;
} else {
// おそらく通信エラー
printLog(`不明なエラーが発生しました。\n${errorToString(e)}`, 'error');
}
continue;
}
userScoreCache.set(toAcct(user), miUser);
await updateRating(user, miUser);
}
printLog(`${host} ユーザー(${users.length}人) のレート計算が完了しました。`);
};
const sendAllAlerts = async (groupedUsers: [string, User[]][]) => {
return await Promise.all(groupedUsers.map(kv => sendAlerts(...kv)));
};
const sendAlerts = async (host: string, users: User[]) => {
const models = users
.map(user => {
const count = userScoreCache.get(toAcct(user));
if (count == null) return null;
return {
user,
count,
message: format(user, count),
};
})
.filter(u => u != null) as {user: User, count: Count, message: string}[];
// 何もしない
for (const {user, count} of models.filter(m => m.user.alertMode === 'nothing')) {
await updateScore(user, count);
}
// 通知
for (const {user, count, message} of models.filter(m => m.user.alertMode === 'notification' || m.user.alertMode === 'both')) {
await sendNotificationAlert(message, user);
if (user.alertMode === 'notification') {
await updateScore(user, count);
}
}
// アラート
for (const {user, count, message} of models.filter(m => m.user.alertMode === 'note' || m.user.alertMode === 'both')) {
await sendNoteAlert(message, user);
await Promise.all([
updateScore(user, count),
delay(1000),
]);
}
printLog(`${host} ユーザー(${users.length}人) へのアラート送信が完了しました。`);
};