enable noImplicitAny (#209)

This commit is contained in:
Jess Porter 2022-02-02 12:43:05 +00:00 committed by GitHub
parent fcf53d15b8
commit f70d97e4d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 78 additions and 64 deletions

View File

@ -16,7 +16,9 @@
"test:manual": "NODE_ENV=harness ts-node test/integration/manualLaunchScript.ts"
},
"devDependencies": {
"@types/config": "0.0.41",
"@types/crypto-js": "^4.0.2",
"@types/html-to-text": "^8.0.1",
"@types/jsdom": "^16.2.11",
"@types/mocha": "^9.0.0",
"@types/node": "^16.7.10",
@ -31,7 +33,6 @@
},
"dependencies": {
"config": "^3.3.6",
"escape-html": "^1.0.3",
"express": "^4.17",
"html-to-text": "^8.0.0",
"js-yaml": "^4.1.0",

View File

@ -17,7 +17,7 @@ limitations under the License.
export const ERROR_KIND_PERMISSION = "permission";
export const ERROR_KIND_FATAL = "fatal";
const TRIGGER_INTERVALS = {
const TRIGGER_INTERVALS: { [key: string]: number } = {
[ERROR_KIND_PERMISSION]: 3 * 60 * 60 * 1000, // 3 hours
[ERROR_KIND_FATAL]: 15 * 60 * 1000, // 15 minutes
};

View File

@ -16,8 +16,7 @@ limitations under the License.
import { LogLevel, LogService, TextualMessageEventContent } from "matrix-bot-sdk";
import config from "./config";
import { replaceRoomIdsWithPills } from "./utils";
import * as htmlEscape from "escape-html";
import { htmlEscape, replaceRoomIdsWithPills } from "./utils";
const levelToFn = {
[LogLevel.DEBUG.toString()]: LogService.debug,

View File

@ -40,7 +40,7 @@ import { ProtectionSettingValidationError } from "./protections/ProtectionSettin
import { UnlistedUserRedactionQueue } from "./queues/UnlistedUserRedactionQueue";
import { Healthz } from "./health/healthz";
import { EventRedactionQueue, RedactUserInRoom } from "./queues/EventRedactionQueue";
import * as htmlEscape from "escape-html";
import { htmlEscape } from "./utils";
import { ReportManager } from "./report/ReportManager";
import { WebAPIs } from "./webapis/WebAPIs";
import RuleServer from "./models/RuleServer";
@ -84,7 +84,7 @@ export class Mjolnir {
* @param {boolean} options.autojoinOnlyIfManager Whether to only accept an invitation by a user present in the `managementRoom`.
* @param {string} options.acceptInvitesFromGroup A group of users to accept invites from, ignores invites form users not in this group.
*/
private static addJoinOnInviteListener(mjolnir: Mjolnir, client: MatrixClient, options) {
private static addJoinOnInviteListener(mjolnir: Mjolnir, client: MatrixClient, options: { [key: string]: any }) {
client.on("room.invite", async (roomId: string, inviteEvent: any) => {
const membershipEvent = new MembershipEvent(inviteEvent);
@ -271,7 +271,7 @@ export class Mjolnir {
await logMessage(LogLevel.DEBUG, "Mjolnir@startup", "Loading protected rooms...");
await this.resyncJoinedRooms(false);
try {
const data: Object | null = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE);
const data: { rooms?: string[] } | null = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE);
if (data && data['rooms']) {
for (const roomId of data['rooms']) {
this.protectedRooms[roomId] = Permalinks.forRoom(roomId);
@ -328,15 +328,15 @@ export class Mjolnir {
if (unprotectedIdx >= 0) this.knownUnprotectedRooms.splice(unprotectedIdx, 1);
this.explicitlyProtectedRoomIds.push(roomId);
let additionalProtectedRooms;
let additionalProtectedRooms: { rooms?: string[] } | null = null;
try {
additionalProtectedRooms = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE);
} catch (e) {
LogService.warn("Mjolnir", extractRequestError(e));
}
if (!additionalProtectedRooms || !additionalProtectedRooms['rooms']) additionalProtectedRooms = { rooms: [] };
additionalProtectedRooms.rooms.push(roomId);
await this.client.setAccountData(PROTECTED_ROOMS_EVENT_TYPE, additionalProtectedRooms);
const rooms = (additionalProtectedRooms?.rooms ?? []);
rooms.push(roomId);
await this.client.setAccountData(PROTECTED_ROOMS_EVENT_TYPE, { rooms: rooms });
await this.syncLists(config.verboseLogging);
}
@ -346,14 +346,13 @@ export class Mjolnir {
const idx = this.explicitlyProtectedRoomIds.indexOf(roomId);
if (idx >= 0) this.explicitlyProtectedRoomIds.splice(idx, 1);
let additionalProtectedRooms;
let additionalProtectedRooms: { rooms?: string[] } | null = null;
try {
additionalProtectedRooms = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE);
} catch (e) {
LogService.warn("Mjolnir", extractRequestError(e));
}
if (!additionalProtectedRooms || !additionalProtectedRooms['rooms']) additionalProtectedRooms = { rooms: [] };
additionalProtectedRooms.rooms = additionalProtectedRooms.rooms.filter(r => r !== roomId);
additionalProtectedRooms = { rooms: additionalProtectedRooms?.rooms?.filter(r => r !== roomId) ?? [] };
await this.client.setAccountData(PROTECTED_ROOMS_EVENT_TYPE, additionalProtectedRooms);
}
@ -379,7 +378,7 @@ export class Mjolnir {
private async getEnabledProtections() {
let enabled: string[] = [];
try {
const protections: Object | null = await this.client.getAccountData(ENABLED_PROTECTIONS_EVENT_TYPE);
const protections: { enabled: string[] } | null = await this.client.getAccountData(ENABLED_PROTECTIONS_EVENT_TYPE);
if (protections && protections['enabled']) {
for (const protection of protections['enabled']) {
enabled.push(protection);
@ -555,8 +554,8 @@ export class Mjolnir {
this.applyUnprotectedRooms();
try {
const accountData: Object | null = await this.client.getAccountData(WARN_UNPROTECTED_ROOM_EVENT_PREFIX + roomId);
if (accountData && accountData['warned']) return; // already warned
const accountData: { warned: boolean } | null = await this.client.getAccountData(WARN_UNPROTECTED_ROOM_EVENT_PREFIX + roomId);
if (accountData && accountData.warned) return; // already warned
} catch (e) {
// Ignore - probably haven't warned about it yet
}
@ -575,14 +574,14 @@ export class Mjolnir {
const banLists: BanList[] = [];
const joinedRooms = await this.client.getJoinedRooms();
let watchedListsEvent = {};
let watchedListsEvent: { references?: string[] } | null = null;
try {
watchedListsEvent = await this.client.getAccountData(WATCHED_LISTS_EVENT_TYPE);
} catch (e) {
// ignore - not important
}
for (const roomRef of (watchedListsEvent['references'] || [])) {
for (const roomRef of (watchedListsEvent?.references || [])) {
const permalink = Permalinks.parseUrl(roomRef);
if (!permalink.roomIdOrAlias) continue;
@ -852,7 +851,7 @@ export class Mjolnir {
} else if (ruleKind === RULE_ROOM) {
ruleKind = 'room';
}
html += `<li>${change.changeType} ${htmlEscape(ruleKind)} (<code>${htmlEscape(rule.recommendation)}</code>): <code>${htmlEscape(rule.entity)}</code> (${htmlEscape(rule.reason)})</li>`;
html += `<li>${change.changeType} ${htmlEscape(ruleKind)} (<code>${htmlEscape(rule.recommendation ?? "")}</code>): <code>${htmlEscape(rule.entity)}</code> (${htmlEscape(rule.reason)})</li>`;
text += `* ${change.changeType} ${ruleKind} (${rule.recommendation}): ${rule.entity} (${rule.reason})\n`;
}

View File

@ -16,7 +16,7 @@ limitations under the License.
import { Mjolnir } from "../Mjolnir";
import { RichReply } from "matrix-bot-sdk";
import * as htmlEscape from "escape-html";
import { htmlEscape } from "../utils";
// !mjolnir move <alias> <new room ID>
export async function execMoveAliasCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {

View File

@ -19,7 +19,7 @@ import { execStatusCommand } from "./StatusCommand";
import { execBanCommand, execUnbanCommand } from "./UnbanBanCommand";
import { execDumpRulesCommand } from "./DumpRulesCommand";
import { extractRequestError, LogService, RichReply } from "matrix-bot-sdk";
import * as htmlEscape from "escape-html";
import { htmlEscape } from "../utils";
import { execSyncCommand } from "./SyncCommand";
import { execPermissionCheckCommand } from "./PermissionCheckCommand";
import { execCreateListCommand } from "./CreateBanListCommand";
@ -40,7 +40,7 @@ import { execKickCommand } from "./KickCommand";
export const COMMAND_PREFIX = "!mjolnir";
export async function handleCommand(roomId: string, event: any, mjolnir: Mjolnir) {
export async function handleCommand(roomId: string, event: { content: { body: string } }, mjolnir: Mjolnir) {
const cmd = event['content']['body'];
const parts = cmd.trim().split(' ').filter(p => p.trim().length > 0);

View File

@ -23,7 +23,7 @@ export async function execCreateListCommand(roomId: string, event: any, mjolnir:
const shortcode = parts[3];
const aliasLocalpart = parts[4];
const powerLevels = {
const powerLevels: { [key: string]: any } = {
"ban": 50,
"events": {
"m.room.name": 100,
@ -38,12 +38,11 @@ export async function execCreateListCommand(roomId: string, event: any, mjolnir:
"redact": 50,
"state_default": 50,
"users": {
// populated in a moment
[await mjolnir.client.getUserId()]: 100,
[event["sender"]]: 50
},
"users_default": 0,
};
powerLevels['users'][await mjolnir.client.getUserId()] = 100;
powerLevels['users'][event['sender']] = 50;
const listRoomId = await mjolnir.client.createRoom({
preset: "public_chat",

View File

@ -16,7 +16,7 @@ limitations under the License.
import { Mjolnir } from "../Mjolnir";
import { RichReply } from "matrix-bot-sdk";
import * as htmlEscape from "escape-html";
import { htmlEscape } from "../utils";
// !mjolnir rules
export async function execDumpRulesCommand(roomId: string, event: any, mjolnir: Mjolnir) {

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import * as htmlEscape from "escape-html";
import { htmlEscape } from "../utils";
import { Mjolnir } from "../Mjolnir";
import { extractRequestError, LogService, RichReply } from "matrix-bot-sdk";
import { PROTECTIONS } from "../protections/protections";

View File

@ -31,9 +31,9 @@ interface Arguments {
// Exported for tests
export async function parseArguments(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]): Promise<Arguments|null> {
let defaultShortcode = null;
let defaultShortcode: string | null = null;
try {
const data: Object = await mjolnir.client.getAccountData(DEFAULT_LIST_EVENT_TYPE);
const data: { shortcode: string } = await mjolnir.client.getAccountData(DEFAULT_LIST_EVENT_TYPE);
defaultShortcode = data['shortcode'];
} catch (e) {
LogService.warn("UnbanBanCommand", "Non-fatal error getting default ban list");

View File

@ -91,7 +91,7 @@ class BanList extends EventEmitter {
* @param roomRef A sharable/clickable matrix URL that refers to the room.
* @param client A matrix client that is used to read the state of the room when `updateList` is called.
*/
constructor(public readonly roomId: string, public readonly roomRef, private client: MatrixClient) {
constructor(public readonly roomId: string, public readonly roomRef: string, private client: MatrixClient) {
super();
}

View File

@ -30,12 +30,11 @@ export class BasicFlooding implements IProtection {
private lastEvents: { [roomId: string]: { [userId: string]: { originServerTs: number, eventId: string }[] } } = {};
private recentlyBanned: string[] = [];
maxPerMinute = new NumberProtectionSetting(DEFAULT_MAX_PER_MINUTE);
settings = {};
settings = {
maxPerMinute: new NumberProtectionSetting(DEFAULT_MAX_PER_MINUTE)
};
constructor() {
this.settings['maxPerMinute'] = this.maxPerMinute;
}
constructor() { }
public get name(): string {
return 'BasicFloodingProtection';
@ -62,7 +61,7 @@ export class BasicFlooding implements IProtection {
messageCount++;
}
if (messageCount >= this.maxPerMinute.value) {
if (messageCount >= this.settings.maxPerMinute.value) {
await logMessage(LogLevel.WARN, "BasicFlooding", `Banning ${event['sender']} in ${roomId} for flooding (${messageCount} messages in the last minute)`, roomId);
if (!config.noop) {
await mjolnir.client.banUser(event['sender'], roomId, "spam");
@ -88,8 +87,8 @@ export class BasicFlooding implements IProtection {
}
// Trim the oldest messages off the user's history if it's getting large
if (forUser.length > this.maxPerMinute.value * 2) {
forUser.splice(0, forUser.length - (this.maxPerMinute.value * 2) - 1);
if (forUser.length > this.settings.maxPerMinute.value * 2) {
forUser.splice(0, forUser.length - (this.settings.maxPerMinute.value * 2) - 1);
}
}
}

View File

@ -76,13 +76,13 @@ export function isListSetting(object: any): object is AbstractProtectionListSett
export class StringProtectionSetting extends AbstractProtectionSetting<string, string> {
value = "";
fromString = (data) => data;
validate = (data) => true;
fromString = (data: string): string => data;
validate = (data: string): boolean => true;
}
export class StringListProtectionSetting extends AbstractProtectionListSetting<string, string[]> {
value: string[] = [];
fromString = (data) => data;
validate = (data) => true;
fromString = (data: string): string => data;
validate = (data: string): boolean => true;
addValue(data: string): string[] {
return [...this.value, data];
}
@ -107,11 +107,11 @@ export class NumberProtectionSetting extends AbstractProtectionSetting<number, n
this.max = max;
}
fromString(data) {
fromString(data: string) {
let number = Number(data);
return isNaN(number) ? undefined : number;
}
validate(data) {
validate(data: number) {
return (!isNaN(data)
&& (this.min === undefined || this.min <= data)
&& (this.max === undefined || data <= this.max))

View File

@ -17,7 +17,7 @@ limitations under the License.
import { PowerLevelAction } from "matrix-bot-sdk/lib/models/PowerLevelAction";
import { LogService, UserID } from "matrix-bot-sdk";
import { htmlToText } from "html-to-text";
import * as htmlEscape from "escape-html";
import { htmlEscape } from "../utils";
import { JSDOM } from 'jsdom';
import { Mjolnir } from "../Mjolnir";
@ -235,9 +235,9 @@ export class ReportManager {
"m.relationship": {
"rel_type": "m.reference",
"event_id": relation.event_id,
}
},
[ABUSE_ACTION_CONFIRMATION_KEY]: confirmationReport
};
confirmation[ABUSE_ACTION_CONFIRMATION_KEY] = confirmationReport;
let requestConfirmationEventId = await this.mjolnir.client.sendMessage(this.mjolnir.managementRoomId, confirmation);
await this.mjolnir.client.sendEvent(this.mjolnir.managementRoomId, "m.reaction", {
@ -693,7 +693,7 @@ class DisplayManager {
// Ignore.
}
let eventContent;
let eventContent: { msg: string} | { html: string } | { text: string };
try {
if (event["type"] === "m.room.encrypted") {
eventContent = { msg: "<encrypted content>" };
@ -707,6 +707,8 @@ class DisplayManager {
} else {
eventContent = { text: this.limitLength(JSON.stringify(event["content"], null, 2), MAX_EVENT_CONTENT_LENGTH, MAX_NEWLINES) };
}
} else {
eventContent = { msg: "Malformed event, cannot read content." };
}
} catch (ex) {
eventContent = { msg: `<Cannot extract event. Please verify that Mjölnir has been invited to room ${roomAliasOrId} and made room moderator or administrator>.` };
@ -716,12 +718,12 @@ class DisplayManager {
let reporterDisplayName: string, accusedDisplayName: string;
try {
reporterDisplayName = await this.owner.mjolnir.client.getUserProfile(reporterId)["displayname"] || reporterId;
reporterDisplayName = (await this.owner.mjolnir.client.getUserProfile(reporterId))["displayname"] || reporterId;
} catch (ex) {
reporterDisplayName = "<Error: Cannot extract reporter display name>";
}
try {
accusedDisplayName = await this.owner.mjolnir.client.getUserProfile(accusedId)["displayname"] || accusedId;
accusedDisplayName = (await this.owner.mjolnir.client.getUserProfile(accusedId))["displayname"] || accusedId;
} catch (ex) {
accusedDisplayName = "<Error: Cannot extract accused display name>";
}
@ -832,8 +834,8 @@ class DisplayManager {
}
// ...insert HTML content
for (let [key, value] of [
['event-content', eventContent],
for (let {key, value} of [
{ key: 'event-content', value: eventContent },
]) {
let node = document.getElementById(key);
if (node) {
@ -842,7 +844,7 @@ class DisplayManager {
} else if ("text" in value) {
node.textContent = value.text;
} else if ("html" in value) {
node.innerHTML = value.html;
node.innerHTML = value.html
}
}
}
@ -868,8 +870,8 @@ class DisplayManager {
body: htmlToText(document.body.outerHTML, { wordwrap: false }),
format: "org.matrix.custom.html",
formatted_body: document.body.outerHTML,
[ABUSE_REPORT_KEY]: report
};
notice[ABUSE_REPORT_KEY] = report;
let noticeEventId = await this.owner.mjolnir.client.sendMessage(this.owner.mjolnir.managementRoomId, notice);
if (kind !== Kind.ERROR) {

View File

@ -29,9 +29,17 @@ import {
} from "matrix-bot-sdk";
import { logMessage } from "./LogProxy";
import config from "./config";
import * as htmlEscape from "escape-html";
import { ClientRequest, IncomingMessage } from "http";
export function htmlEscape(input: string): string {
return input.replace(/["&<>]/g, (char: string) => ({
['"'.charCodeAt(0)]: "&quot;",
["&".charCodeAt(0)]: "&amp;",
["<".charCodeAt(0)]: "&lt;",
[">".charCodeAt(0)]: "&gt;"
})[char.charCodeAt(0)]);
}
export function setToArray<T>(set: Set<T>): T[] {
const arr: T[] = [];
for (const v of set) {
@ -225,11 +233,13 @@ function patchMatrixClientForConciseExceptions() {
return;
}
let originalRequestFn = getRequestFn();
setRequestFn((params, cb) => {
setRequestFn((params: { [k: string]: any }, cb: any) => {
// Store an error early, to maintain *some* semblance of stack.
// We'll only throw the error if there is one.
let error = new Error("STACK CAPTURE");
originalRequestFn(params, function conciseExceptionRequestFn(err, response, resBody) {
originalRequestFn(params, function conciseExceptionRequestFn(
err: { [key: string]: any }, response: { [key: string]: any }, resBody: string
) {
if (!err && (response?.statusCode < 200 || response?.statusCode >= 300)) {
// Normally, converting HTTP Errors into rejections is done by the caller
// of `requestFn` within matrix-bot-sdk. However, this always ends up rejecting
@ -332,7 +342,7 @@ function patchMatrixClientForRetry() {
return;
}
let originalRequestFn = getRequestFn();
setRequestFn(async (params, cb) => {
setRequestFn(async (params: { [k: string]: any }, cb: any) => {
let attempt = 1;
numberOfConcurrentRequests += 1;
if (TRACE_CONCURRENT_REQUESTS) {
@ -342,7 +352,9 @@ function patchMatrixClientForRetry() {
while (true) {
try {
let result: any[] = await new Promise((resolve, reject) => {
originalRequestFn(params, function requestFnWithRetry(err, response, resBody) {
originalRequestFn(params, function requestFnWithRetry(
err: { [key: string]: any }, response: { [key: string]: any }, resBody: string
) {
// Note: There is no data race on `attempt` as we `await` before continuing
// to the next iteration of the loop.
if (attempt < MAX_REQUEST_ATTEMPTS && err?.body?.errcode === 'M_LIMIT_EXCEEDED') {

View File

@ -107,16 +107,19 @@ export class WebAPIs {
{
// -- Create a client on behalf of the reporter.
// We'll use it to confirm the authenticity of the report.
let accessToken;
let accessToken: string | undefined = undefined;
// Authentication mechanism 1: Request header.
let authorization = request.get('Authorization');
if (authorization) {
[, accessToken] = AUTHORIZATION.exec(authorization)!;
} else {
} else if (typeof(request.query["access_token"]) === 'string') {
// Authentication mechanism 2: Access token as query parameter.
accessToken = request.query["access_token"];
} else {
response.status(401).send("Missing access token");
return;
}
// Create a client dedicated to this report.

View File

@ -9,7 +9,7 @@
"noImplicitReturns": true,
"noUnusedLocals": true,
"target": "es2015",
"noImplicitAny": false,
"noImplicitAny": true,
"sourceMap": true,
"strictNullChecks": true,
"outDir": "./lib",