Spaces:
Running
Running
File size: 4,888 Bytes
5c2ed06 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
/**
* Code for using Google's Perspective API for filters.
* @author mia-pi-git
*/
import { ProcessManager, Net, Repl } from '../../lib';
import { Config } from '../config-loader';
import { toID } from '../../sim/dex-data';
// 20m. this is mostly here so we can use Monitor.slow()
const PM_TIMEOUT = 20 * 60 * 1000;
export const ATTRIBUTES = {
"SEVERE_TOXICITY": {},
"TOXICITY": {},
"IDENTITY_ATTACK": {},
"INSULT": {},
"PROFANITY": {},
"THREAT": {},
"SEXUALLY_EXPLICIT": {},
"FLIRTATION": {},
};
export interface PerspectiveRequest {
languages: string[];
requestedAttributes: AnyObject;
comment: { text: string };
}
function time() {
return Math.floor(Math.floor(Date.now() / 1000) / 60);
}
export class Limiter {
readonly max: number;
lastTick = time();
count = 0;
constructor(max: number) {
this.max = max;
}
shouldRequest() {
const now = time();
if (this.lastTick !== now) {
this.count = 0;
this.lastTick = now;
}
this.count++;
return this.count < this.max;
}
}
function isCommon(message: string) {
message = message.toLowerCase().replace(/\?!\., ;:/g, '');
return ['gg', 'wp', 'ggwp', 'gl', 'hf', 'glhf', 'hello'].includes(message);
}
let throttleTime: number | null = null;
export const limiter = new Limiter(800);
export const PM = new ProcessManager.QueryProcessManager<string, Record<string, number> | null>(module, async text => {
if (isCommon(text) || !limiter.shouldRequest()) return null;
if (throttleTime && ((Date.now() - throttleTime) < 10000)) {
return null;
}
if (throttleTime) throttleTime = null;
const requestData: PerspectiveRequest = {
// todo - support 'es', 'it', 'pt', 'fr' - use user.language? room.settings.language...?
languages: ['en'],
requestedAttributes: ATTRIBUTES,
comment: { text },
};
try {
const raw = await Net(`https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze`).post({
query: {
key: Config.perspectiveKey,
},
body: JSON.stringify(requestData),
headers: {
'Content-Type': "application/json",
},
timeout: 10 * 1000, // 10s
});
if (!raw) return null;
const data = JSON.parse(raw);
if (data.error) throw new Error(data.message);
const result: { [k: string]: number } = {};
for (const k in data.attributeScores) {
const score = data.attributeScores[k];
result[k] = score.summaryScore.value;
}
return result;
} catch (e: any) {
// eslint-disable-next-line require-atomic-updates
throttleTime = Date.now();
if (e.message.startsWith('Request timeout') || e.statusCode === 429) {
// request timeout: just ignore this. error on their end not ours.
// 429: too many requests, we already freeze for 10s above so. not much more we can do
return null;
}
Monitor.crashlog(e, 'A Perspective API request', { request: JSON.stringify(requestData) });
return null;
}
}, PM_TIMEOUT);
// main module check necessary since this gets required in other non-parent processes sometimes
// when that happens we do not want to take over or set up or anything
if (require.main === module) {
// This is a child process!
global.Config = Config;
global.Monitor = {
crashlog(error: Error, source = 'A remote Artemis child process', details: AnyObject | null = null) {
const repr = JSON.stringify([error.name, error.message, source, details]);
process.send!(`THROW\n@!!@${repr}\n${error.stack}`);
},
slow(text: string) {
process.send!(`CALLBACK\nSLOW\n${text}`);
},
} as any;
global.toID = toID;
process.on('uncaughtException', err => {
if (Config.crashguard) {
Monitor.crashlog(err, 'A remote Artemis child process');
}
});
// eslint-disable-next-line no-eval
Repl.start(`abusemonitor-remote-${process.pid}`, cmd => eval(cmd));
} else if (!process.send) {
PM.spawn(Config.remoteartemisprocesses || 1);
}
export class RemoteClassifier {
static readonly PM = PM;
static readonly ATTRIBUTES = ATTRIBUTES;
classify(text: string) {
if (!Config.perspectiveKey) return Promise.resolve(null);
return PM.query(text);
}
async suggestScore(text: string, data: Record<string, number>) {
if (!Config.perspectiveKey) return Promise.resolve(null);
const body: AnyObject = {
comment: { text },
attributeScores: {},
};
for (const k in data) {
body.attributeScores[k] = { summaryScore: { value: data[k] } };
}
try {
const raw = await Net(`https://commentanalyzer.googleapis.com/v1alpha1/comments:suggestscore`).post({
query: {
key: Config.perspectiveKey,
},
body: JSON.stringify(body),
headers: {
'Content-Type': "application/json",
},
timeout: 10 * 1000, // 10s
});
return JSON.parse(raw);
} catch (e: any) {
return { error: e.message };
}
}
destroy() {
return PM.destroy();
}
respawn() {
return PM.respawn();
}
spawn(number: number) {
PM.spawn(number);
}
getActiveProcesses() {
return PM.processes.length;
}
}
|