diff --git a/README.md b/README.md index fae4612..68f4d72 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Devvit.addMenuItem({ appearance: 'success', text: 'Note added!', }); - } + }, }); export default Devvit; diff --git a/package.json b/package.json index 31d4cf4..807cac1 100644 --- a/package.json +++ b/package.json @@ -1,49 +1,49 @@ { - "name": "toolbox-devvit", - "version": "0.3.0", - "description": "Helpers for working with /r/toolbox data from Devvit community apps.", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "files": [ - "dist/*" - ], - "scripts": { - "fmt": "dprint fmt", - "build": "tsc", - "test": "ava", - "coverage": "nyc ava", - "docs": "typedoc src/index.ts", - "prepublishOnly": "tsc && ava" - }, - "repository": "https://github.com/toolbox-team/toolbox-devvit.git", - "author": "eritbh ", - "license": "MIT", - "bugs": { - "url": "https://github.com/toolbox-team/toolbox-devvit/issues" - }, - "homepage": "https://github.com/toolbox-team/toolbox-devvit#readme", - "devDependencies": { - "@ava/typescript": "^4.0.0", - "@devvit/public-api": "^0.10.0", - "@types/node": "^13.7.4", - "ava": "^5.3.0", - "dprint": "^0.40.2", - "eslint": "^6.8.0", - "nyc": "^15.0.0", - "tsx": "^3.12.7", - "typedoc": "^0.24.8", - "typescript": "^5.1.3" - }, - "dependencies": { - "pako": "^1.0.11" - }, - "ava": { - "extensions": { - "ts": "module" - }, - "nodeArguments": [ - "--loader=tsx", - "--no-warnings" - ] - } + "name": "toolbox-devvit", + "version": "0.3.0", + "description": "Helpers for working with /r/toolbox data from Devvit community apps.", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist/*" + ], + "scripts": { + "fmt": "dprint fmt", + "build": "tsc", + "test": "ava", + "coverage": "nyc ava", + "docs": "typedoc src/index.ts", + "prepublishOnly": "tsc && ava" + }, + "repository": "https://github.com/toolbox-team/toolbox-devvit.git", + "author": "eritbh ", + "license": "MIT", + "bugs": { + "url": "https://github.com/toolbox-team/toolbox-devvit/issues" + }, + "homepage": "https://github.com/toolbox-team/toolbox-devvit#readme", + "devDependencies": { + "@ava/typescript": "^4.0.0", + "@devvit/public-api": "^0.10.0", + "@types/node": "^13.7.4", + "ava": "^5.3.0", + "dprint": "^0.40.2", + "eslint": "^6.8.0", + "nyc": "^15.0.0", + "tsx": "^3.12.7", + "typedoc": "^0.24.8", + "typescript": "^5.1.3" + }, + "dependencies": { + "pako": "^1.0.11" + }, + "ava": { + "extensions": { + "ts": "module" + }, + "nodeArguments": [ + "--loader=tsx", + "--no-warnings" + ] + } } diff --git a/src/classes/ToolboxClient.ts b/src/classes/ToolboxClient.ts index 4e80470..6827155 100644 --- a/src/classes/ToolboxClient.ts +++ b/src/classes/ToolboxClient.ts @@ -1,6 +1,6 @@ import {RedditAPIClient} from '@devvit/public-api'; -import {Usernotes} from './Usernotes'; import {Usernote, UsernoteInit} from '../types/Usernote'; +import {Usernotes} from './Usernotes'; /** The name of the wiki page where Toolbox stores usernotes. */ const TB_USERNOTES_PAGE = 'usernotes'; diff --git a/src/classes/Usernotes.test.ts b/src/classes/Usernotes.test.ts index 14f50ce..e7e5b74 100644 --- a/src/classes/Usernotes.test.ts +++ b/src/classes/Usernotes.test.ts @@ -1,7 +1,7 @@ import test from 'ava'; -import {Usernotes} from './Usernotes'; import {compressBlob, decompressBlob} from '../helpers/usernotes'; import type {RawUsernotes} from '../types/RawUsernotes'; +import {Usernotes} from './Usernotes'; test.todo('constructor'); @@ -61,17 +61,20 @@ test('get: read and merge existing entries for lowercased usernames', t => { text: 'test 1', }, { - text: 'test 0' + text: 'test 0', }, ], 'notes from both spellings of the username should be returned in order'); // expect the entry under the lowercased username to be gone when saving const newUsersData = decompressBlob(usernotes.toJSON().blob); - t.false('someuser' in newUsersData, 'lowercased spelling of the username should be removed from usernotes object'); -}) + t.false( + 'someuser' in newUsersData, + 'lowercased spelling of the username should be removed from usernotes object', + ); +}); test.todo('add'); test.todo('toJSON'); -test.todo('toString') +test.todo('toString'); diff --git a/src/classes/Usernotes.ts b/src/classes/Usernotes.ts index cde4a04..9e96e60 100644 --- a/src/classes/Usernotes.ts +++ b/src/classes/Usernotes.ts @@ -1,16 +1,13 @@ import { - RawUsernotes, - RawUsernotesConstants, -} from '../types/RawUsernotes'; -import {Usernote} from '../types/Usernote'; -import { - LATEST_KNOWN_USERNOTES_SCHEMA, compressBlob, decompressBlob, expandPermalink, - squashPermalink, + LATEST_KNOWN_USERNOTES_SCHEMA, migrateUsernotesToLatestSchema, + squashPermalink, } from '../helpers/usernotes'; +import {RawUsernotes, RawUsernotesConstants} from '../types/RawUsernotes'; +import {Usernote} from '../types/Usernote'; // TODO: nothing here handles username case correctly; go back and check the // toolbox implementation of that for correctness later and write test @@ -43,8 +40,12 @@ export class Usernotes { timestamp: new Date(rawNote.t * 1000), text: rawNote.n!, moderatorUsername: data.constants.users[rawNote.m!]!, - contextPermalink: rawNote.l == null ? undefined : expandPermalink(rawNote.l), - noteType: rawNote.w == null ? undefined : data.constants.warnings[rawNote.w] ?? undefined, + contextPermalink: rawNote.l == null + ? undefined + : expandPermalink(rawNote.l), + noteType: rawNote.w == null + ? undefined + : data.constants.warnings[rawNote.w] ?? undefined, }); } } @@ -159,7 +160,9 @@ export class Usernotes { n: note.text, m: modIndex, w: typeKeyIndex, - l: note.contextPermalink == null ? undefined : squashPermalink(note.contextPermalink), + l: note.contextPermalink == null + ? undefined + : squashPermalink(note.contextPermalink), }); } } diff --git a/src/helpers/usernotes.test.ts b/src/helpers/usernotes.test.ts index d1a408e..0cf451e 100644 --- a/src/helpers/usernotes.test.ts +++ b/src/helpers/usernotes.test.ts @@ -1,75 +1,106 @@ import test from 'ava'; import { - squashPermalink, expandPermalink, migrateUsernotesToLatestSchema, + squashPermalink, } from './usernotes'; test('squashPermalink', t => { - for (const [arg, expected] of Object.entries({ - // Comment links -> l,POSTID,COMMENTID - 'https://www.reddit.com/r/subreddit/comments/123abc/some_link_slug/456def': 'l,123abc,456def', - 'https://www.reddit.com/r/subreddit/comments/123abc/some_link_slug/456def/?trailing#garbage': 'l,123abc,456def', - 'https://www.reddit.com/comments/123abc/some_link_slug/456def': 'l,123abc,456def', - 'https://www.reddit.com/comments/123abc/some_link_slug/456def/?trailing#garbage': 'l,123abc,456def', - 'https://new.reddit.com/r/subreddit/comments/123abc/some_link_slug/456def': 'l,123abc,456def', - 'https://new.reddit.com/r/subreddit/comments/123abc/some_link_slug/456def/?trailing#garbage': 'l,123abc,456def', - 'https://new.reddit.com/comments/123abc/some_link_slug/456def': 'l,123abc,456def', - 'https://new.reddit.com/comments/123abc/some_link_slug/456def/?trailing#garbage': 'l,123abc,456def', - 'https://old.reddit.com/r/subreddit/comments/123abc/some_link_slug/456def': 'l,123abc,456def', - 'https://old.reddit.com/r/subreddit/comments/123abc/some_link_slug/456def/?trailing#garbage': 'l,123abc,456def', - 'https://old.reddit.com/comments/123abc/some_link_slug/456def': 'l,123abc,456def', - 'https://old.reddit.com/comments/123abc/some_link_slug/456def/?trailing#garbage': 'l,123abc,456def', - // Submission links -> l,POSTID - 'https://www.reddit.com/r/subreddit/comments/123abc': 'l,123abc', - 'https://www.reddit.com/r/subreddit/comments/123abc/some_link_slug/?trailing#garbage': 'l,123abc', - 'https://www.reddit.com/comments/123abc': 'l,123abc', - 'https://www.reddit.com/comments/123abc/some_link_slug/?trailing#garbage': 'l,123abc', - 'https://new.reddit.com/r/subreddit/comments/123abc': 'l,123abc', - 'https://new.reddit.com/r/subreddit/comments/123abc/some_link_slug/?trailing#garbage': 'l,123abc', - 'https://new.reddit.com/comments/123abc': 'l,123abc', - 'https://new.reddit.com/comments/123abc/some_link_slug/?trailing#garbage': 'l,123abc', - 'https://old.reddit.com/r/subreddit/comments/123abc': 'l,123abc', - 'https://old.reddit.com/r/subreddit/comments/123abc/some_link_slug/?trailing#garbage': 'l,123abc', - 'https://old.reddit.com/comments/123abc': 'l,123abc', - 'https://old.reddit.com/comments/123abc/some_link_slug/?trailing#garbage': 'l,123abc', - 'https://redd.it/123abc': 'l,123abc', - 'https://redd.it/123abc/?trailing#garbage': 'l,123abc', - // Old modmail links -> m,MESSAGEID - 'https://www.reddit.com/r/subreddit/message/messages/123abc': 'm,123abc', - 'https://www.reddit.com/r/subreddit/message/messages/123abc/?trailing#garbage': 'm,123abc', - 'https://www.reddit.com/message/messages/123abc': 'm,123abc', - 'https://www.reddit.com/message/messages/123abc/?trailing#garbage': 'm,123abc', - 'https://new.reddit.com/r/subreddit/message/messages/123abc': 'm,123abc', - 'https://new.reddit.com/r/subreddit/message/messages/123abc/?trailing#garbage': 'm,123abc', - 'https://new.reddit.com/message/messages/123abc': 'm,123abc', - 'https://new.reddit.com/message/messages/123abc/?trailing#garbage': 'm,123abc', - 'https://old.reddit.com/r/subreddit/message/messages/123abc': 'm,123abc', - 'https://old.reddit.com/r/subreddit/message/messages/123abc/?trailing#garbage': 'm,123abc', - 'https://old.reddit.com/message/messages/123abc': 'm,123abc', - 'https://old.reddit.com/message/messages/123abc/?trailing#garbage': 'm,123abc', - // Everything else is passed through as-is, including new modmail - 'https://mod.reddit.com/mail/all/123abc': 'https://mod.reddit.com/mail/all/123abc', - 'literally anything else': 'literally anything else', - })) { + for ( + const [arg, expected] of Object.entries({ + // Comment links -> l,POSTID,COMMENTID + 'https://www.reddit.com/r/subreddit/comments/123abc/some_link_slug/456def': + 'l,123abc,456def', + 'https://www.reddit.com/r/subreddit/comments/123abc/some_link_slug/456def/?trailing#garbage': + 'l,123abc,456def', + 'https://www.reddit.com/comments/123abc/some_link_slug/456def': + 'l,123abc,456def', + 'https://www.reddit.com/comments/123abc/some_link_slug/456def/?trailing#garbage': + 'l,123abc,456def', + 'https://new.reddit.com/r/subreddit/comments/123abc/some_link_slug/456def': + 'l,123abc,456def', + 'https://new.reddit.com/r/subreddit/comments/123abc/some_link_slug/456def/?trailing#garbage': + 'l,123abc,456def', + 'https://new.reddit.com/comments/123abc/some_link_slug/456def': + 'l,123abc,456def', + 'https://new.reddit.com/comments/123abc/some_link_slug/456def/?trailing#garbage': + 'l,123abc,456def', + 'https://old.reddit.com/r/subreddit/comments/123abc/some_link_slug/456def': + 'l,123abc,456def', + 'https://old.reddit.com/r/subreddit/comments/123abc/some_link_slug/456def/?trailing#garbage': + 'l,123abc,456def', + 'https://old.reddit.com/comments/123abc/some_link_slug/456def': + 'l,123abc,456def', + 'https://old.reddit.com/comments/123abc/some_link_slug/456def/?trailing#garbage': + 'l,123abc,456def', + // Submission links -> l,POSTID + 'https://www.reddit.com/r/subreddit/comments/123abc': 'l,123abc', + 'https://www.reddit.com/r/subreddit/comments/123abc/some_link_slug/?trailing#garbage': + 'l,123abc', + 'https://www.reddit.com/comments/123abc': 'l,123abc', + 'https://www.reddit.com/comments/123abc/some_link_slug/?trailing#garbage': + 'l,123abc', + 'https://new.reddit.com/r/subreddit/comments/123abc': 'l,123abc', + 'https://new.reddit.com/r/subreddit/comments/123abc/some_link_slug/?trailing#garbage': + 'l,123abc', + 'https://new.reddit.com/comments/123abc': 'l,123abc', + 'https://new.reddit.com/comments/123abc/some_link_slug/?trailing#garbage': + 'l,123abc', + 'https://old.reddit.com/r/subreddit/comments/123abc': 'l,123abc', + 'https://old.reddit.com/r/subreddit/comments/123abc/some_link_slug/?trailing#garbage': + 'l,123abc', + 'https://old.reddit.com/comments/123abc': 'l,123abc', + 'https://old.reddit.com/comments/123abc/some_link_slug/?trailing#garbage': + 'l,123abc', + 'https://redd.it/123abc': 'l,123abc', + 'https://redd.it/123abc/?trailing#garbage': 'l,123abc', + // Old modmail links -> m,MESSAGEID + 'https://www.reddit.com/r/subreddit/message/messages/123abc': 'm,123abc', + 'https://www.reddit.com/r/subreddit/message/messages/123abc/?trailing#garbage': + 'm,123abc', + 'https://www.reddit.com/message/messages/123abc': 'm,123abc', + 'https://www.reddit.com/message/messages/123abc/?trailing#garbage': + 'm,123abc', + 'https://new.reddit.com/r/subreddit/message/messages/123abc': 'm,123abc', + 'https://new.reddit.com/r/subreddit/message/messages/123abc/?trailing#garbage': + 'm,123abc', + 'https://new.reddit.com/message/messages/123abc': 'm,123abc', + 'https://new.reddit.com/message/messages/123abc/?trailing#garbage': + 'm,123abc', + 'https://old.reddit.com/r/subreddit/message/messages/123abc': 'm,123abc', + 'https://old.reddit.com/r/subreddit/message/messages/123abc/?trailing#garbage': + 'm,123abc', + 'https://old.reddit.com/message/messages/123abc': 'm,123abc', + 'https://old.reddit.com/message/messages/123abc/?trailing#garbage': + 'm,123abc', + // Everything else is passed through as-is, including new modmail + 'https://mod.reddit.com/mail/all/123abc': + 'https://mod.reddit.com/mail/all/123abc', + 'literally anything else': 'literally anything else', + }) + ) { t.is(squashPermalink(arg), expected); } }); test('expandPermalink', t => { - for (const [arg, expected] of Object.entries({ - // Squashed submission links - 'l,123abc': 'https://www.reddit.com/comments/123abc', - // Squashed comment links - 'l,123abc,456def': 'https://www.reddit.com/comments/123abc/_/456def', - // Squashed old modmail links - 'm,123abc': 'https://www.reddit.com/message/messages/123abc', - // Everything else is passed through as-is - 'https://www.reddit.com/r/subreddit/comments/123abc/': 'https://www.reddit.com/r/subreddit/comments/123abc/', - 'https://mod.reddit.com/mail/all/123abc': 'https://mod.reddit.com/mail/all/123abc', - 'literally anything else': 'literally anything else', - })) { + for ( + const [arg, expected] of Object.entries({ + // Squashed submission links + 'l,123abc': 'https://www.reddit.com/comments/123abc', + // Squashed comment links + 'l,123abc,456def': 'https://www.reddit.com/comments/123abc/_/456def', + // Squashed old modmail links + 'm,123abc': 'https://www.reddit.com/message/messages/123abc', + // Everything else is passed through as-is + 'https://www.reddit.com/r/subreddit/comments/123abc/': + 'https://www.reddit.com/r/subreddit/comments/123abc/', + 'https://mod.reddit.com/mail/all/123abc': + 'https://mod.reddit.com/mail/all/123abc', + 'literally anything else': 'literally anything else', + }) + ) { t.is(expandPermalink(arg), expected); } }); diff --git a/src/helpers/usernotes.ts b/src/helpers/usernotes.ts index 4fedc00..4404fd7 100644 --- a/src/helpers/usernotes.ts +++ b/src/helpers/usernotes.ts @@ -29,7 +29,11 @@ export const EARLIEST_KNOWN_USERNOTES_SCHEMA = 4; */ export function squashPermalink (permalink: string): string { let match: RegExpMatchArray | null; - if ((match = permalink.match(/(?:\/comments|^https?:\/\/redd\.it)\/([a-z0-9]+)(?:\/[^/]*(?:\/([a-z0-9]+)?)?)?/))) { + if ( + (match = permalink.match( + /(?:\/comments|^https?:\/\/redd\.it)\/([a-z0-9]+)(?:\/[^/]*(?:\/([a-z0-9]+)?)?)?/, + )) + ) { // Submission pages let squashed = `l,${match[1]}`; if (match[2]) { @@ -58,7 +62,9 @@ export function squashPermalink (permalink: string): string { export function expandPermalink (shortenedLink: string): string { if (shortenedLink.startsWith('l,')) { // Expand to a submission or comment permalink - return `https://www.reddit.com/comments/${shortenedLink.substr(2).split(',').join('/_/')}`; + return `https://www.reddit.com/comments/${ + shortenedLink.substr(2).split(',').join('/_/') + }`; } else if (shortenedLink.startsWith('m,')) { // Expand to a message permalink return `https://www.reddit.com/message/messages/${shortenedLink.substr(2)}`; @@ -85,7 +91,11 @@ export function compressBlob (value: T): RawUsernotesBlob { * @returns The original JSON value. */ export function decompressBlob (blob: RawUsernotesBlob): T { - return JSON.parse(pako.inflate(Buffer.from(blob, 'base64').toString('binary'), {to: 'string'})); + return JSON.parse( + pako.inflate(Buffer.from(blob, 'base64').toString('binary'), { + to: 'string', + }), + ); } /** @@ -98,10 +108,14 @@ export function decompressBlob (blob: RawUsernotesBlob): T { */ export function migrateUsernotesToLatestSchema (data: any): RawUsernotes { if (data.ver < EARLIEST_KNOWN_USERNOTES_SCHEMA) { - throw new TypeError(`Unknown schema version ${data.ver} (earliest known version is ${EARLIEST_KNOWN_USERNOTES_SCHEMA})`); + throw new TypeError( + `Unknown schema version ${data.ver} (earliest known version is ${EARLIEST_KNOWN_USERNOTES_SCHEMA})`, + ); } if (data.ver > LATEST_KNOWN_USERNOTES_SCHEMA) { - throw new TypeError(`Unknown schema version ${data.ver} (latest known version is ${LATEST_KNOWN_USERNOTES_SCHEMA})`); + throw new TypeError( + `Unknown schema version ${data.ver} (latest known version is ${LATEST_KNOWN_USERNOTES_SCHEMA})`, + ); } // eslint-disable-next-line default-case @@ -110,7 +124,9 @@ export function migrateUsernotesToLatestSchema (data: any): RawUsernotes { // Timestamps need to be converted from milliseconds to seconds for (const user of Object.values(data.users as RawUsernotesUsers)) { for (const note of user.ns) { - if (note.t) note.t /= 1000; + if (note.t) { + note.t /= 1000; + } } } // fallthrough to immediately bump to next version diff --git a/src/index.ts b/src/index.ts index 8e77393..e08e844 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ +export * from './classes/ToolboxClient'; +export * from './classes/Usernotes'; export * from './helpers/usernotes'; export * from './types/RawUsernotes'; export * from './types/Usernote'; -export * from './classes/Usernotes'; -export * from './classes/ToolboxClient'; // export * from './subConfig'; diff --git a/src/subConfig.ts b/src/subConfig.ts index 79410eb..cb84ff6 100644 --- a/src/subConfig.ts +++ b/src/subConfig.ts @@ -4,59 +4,59 @@ export const LATEST_KNOWN_CONFIG_SCHEMA = 1; export const EARLIEST_KNOWN_CONFIG_SCHEMA = 1; export interface RawSubredditConfig { - /** The schema version of the data */ - ver: number; - /** Settings for domain tags */ - domainTags: DomainTag[]; - /** Default settings for banning users via the mod button */ - banMacros: { - /** The default mod-only ban note */ - banNote: string; - /** The default message sent to banned users */ - banMessage: string; - }; - /** Settings for removal reasons */ - removalReasons: { - header: string; - footer: string; - pmsubject: string; - logreason: unknown; - logsub: unknown; - logtitle: unknown; - bantitle: unknown; - getfrom: unknown; - reasons: RemovalReason[]; - }; - modMacros: ModMacro[]; - usernoteColors: UsernoteType[]; + /** The schema version of the data */ + ver: number; + /** Settings for domain tags */ + domainTags: DomainTag[]; + /** Default settings for banning users via the mod button */ + banMacros: { + /** The default mod-only ban note */ + banNote: string; + /** The default message sent to banned users */ + banMessage: string; + }; + /** Settings for removal reasons */ + removalReasons: { + header: string; + footer: string; + pmsubject: string; + logreason: unknown; + logsub: unknown; + logtitle: unknown; + bantitle: unknown; + getfrom: unknown; + reasons: RemovalReason[]; + }; + modMacros: ModMacro[]; + usernoteColors: UsernoteType[]; } export interface DomainTag { - /** The domain to tag, e.g. "example.com" */ - name: string; - /** A CSS color value */ - color: string; + /** The domain to tag, e.g. "example.com" */ + name: string; + /** A CSS color value */ + color: string; } export interface RemovalReason { - title: string; - text: string; - flairText: string; - cssClass: string; + title: string; + text: string; + flairText: string; + cssClass: string; } export interface ModMacro { - title: string; - text: string; - distinguish: boolean; - ban: boolean; - mute: boolean; - remove: boolean; - appprove: boolean; - lockthread: boolean; - sticky: boolean; - archivemodmail: boolean; - highlightmodmail: boolean; + title: string; + text: string; + distinguish: boolean; + ban: boolean; + mute: boolean; + remove: boolean; + appprove: boolean; + lockthread: boolean; + sticky: boolean; + archivemodmail: boolean; + highlightmodmail: boolean; } /** A single usernote type */ diff --git a/tsconfig.json b/tsconfig.json index 82e5d04..86835e2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,23 +1,23 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "commonjs", - "moduleResolution": "node", - "resolveJsonModule": true, - "esModuleInterop": true, - "outDir": "./dist", - "declaration": true, - "noImplicitAny": false, - "strictNullChecks": true, - "strict": true, - - // devvit types do not like being observed in this context - "skipLibCheck": true - }, - "include": [ - "src" - ], - "exclude": [ - "src/**/*.test.ts" - ] -} +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "moduleResolution": "node", + "resolveJsonModule": true, + "esModuleInterop": true, + "outDir": "./dist", + "declaration": true, + "noImplicitAny": false, + "strictNullChecks": true, + "strict": true, + + // devvit types do not like being observed in this context + "skipLibCheck": true + }, + "include": [ + "src" + ], + "exclude": [ + "src/**/*.test.ts" + ] +}