From dd76595ac2464f885a3c827741f8191d25a0d1a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dandelion=20Man=C3=A9?= Date: Sat, 13 Jun 2020 12:09:18 -0700 Subject: [PATCH] Setup Discord cli2 plugin This adds a cliPlugin to the experimental Discord plugin, along the lines of the cli 2 plugins for GitHub and Discourse. Test plan: I set up a Cred instance for SourceCred including our Discord config, and successfully loaded Cred for it. Not easy to give full reproduction instructions, not least because it requires a private Discord bot token. --- src/cli2/bundledPlugins.js | 2 + src/plugins/experimental-discord/cliPlugin.js | 89 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/plugins/experimental-discord/cliPlugin.js diff --git a/src/cli2/bundledPlugins.js b/src/cli2/bundledPlugins.js index 646158d..4048aa2 100644 --- a/src/cli2/bundledPlugins.js +++ b/src/cli2/bundledPlugins.js @@ -3,6 +3,7 @@ import type {CliPlugin} from "./cliPlugin"; import {GithubCliPlugin} from "../plugins/github/cliPlugin"; import {DiscourseCliPlugin} from "../plugins/discourse/cliPlugin"; +import {DiscordCliPlugin} from "../plugins/experimental-discord/cliPlugin"; /** * Returns an object mapping owner-name pairs to CLI plugin @@ -12,5 +13,6 @@ export function bundledPlugins(): {[pluginId: string]: CliPlugin} { return { "sourcecred/github": new GithubCliPlugin(), "sourcecred/discourse": new DiscourseCliPlugin(), + "sourcecred/discord": new DiscordCliPlugin(), }; } diff --git a/src/plugins/experimental-discord/cliPlugin.js b/src/plugins/experimental-discord/cliPlugin.js new file mode 100644 index 0000000..9381bfa --- /dev/null +++ b/src/plugins/experimental-discord/cliPlugin.js @@ -0,0 +1,89 @@ +// @flow + +import Database from "better-sqlite3"; + +import type {CliPlugin, PluginDirectoryContext} from "../../cli2/cliPlugin"; +import type {PluginDeclaration} from "../../analysis/pluginDeclaration"; +import {parseConfig, type DiscordConfig, type DiscordToken} from "./config"; +import {declaration} from "./declaration"; +import {join as pathJoin} from "path"; +import fs from "fs-extra"; +import {type TaskReporter} from "../../util/taskReporter"; +import {Fetcher} from "./fetcher"; +import {Mirror} from "./mirror"; +import type {ReferenceDetector} from "../../core/references/referenceDetector"; +import type {WeightedGraph} from "../../core/weightedGraph"; +import {weightsForDeclaration} from "../../analysis/pluginDeclaration"; +import {createGraph} from "./createGraph"; +import * as Model from "./models"; +import {SqliteMirrorRepository} from "./mirrorRepository"; + +async function loadConfig( + dirContext: PluginDirectoryContext +): Promise { + const dirname = dirContext.configDirectory(); + const path = pathJoin(dirname, "config.json"); + const contents = await fs.readFile(path); + return Promise.resolve(parseConfig(JSON.parse(contents))); +} + +const TOKEN_ENV_VAR_NAME = "SOURCECRED_DISCORD_TOKEN"; + +function getTokenFromEnv(): DiscordToken { + const rawToken = process.env[TOKEN_ENV_VAR_NAME]; + if (rawToken == null) { + throw new Error(`No Discord token provided: set ${TOKEN_ENV_VAR_NAME}`); + } + return rawToken; +} + +export class DiscordCliPlugin implements CliPlugin { + declaration(): PluginDeclaration { + return declaration; + } + + async load( + ctx: PluginDirectoryContext, + reporter: TaskReporter + ): Promise { + const {guildId} = await loadConfig(ctx); + const token = getTokenFromEnv(); + const fetcher = new Fetcher({token}); + const repo = await repository(ctx, guildId); + const mirror = new Mirror(repo, fetcher, guildId); + await mirror.update(reporter); + } + + async graph( + ctx: PluginDirectoryContext, + rd: ReferenceDetector + ): Promise { + const _ = rd; // TODO(#1808): not yet used + const {guildId, reactionWeights} = await loadConfig(ctx); + const repo = await repository(ctx, guildId); + const declarationWeights = weightsForDeclaration(declaration); + return await createGraph( + guildId, + repo, + declarationWeights, + reactionWeights + ); + } + + async referenceDetector( + _unused_ctx: PluginDirectoryContext + ): Promise { + // TODO: Implement Discord reference detection + // (low priority bc ppl rarely hardlink to Discord messages) + return {addressFromUrl: () => undefined}; + } +} + +async function repository( + ctx: PluginDirectoryContext, + guild: Model.Snowflake +): Promise { + const path = pathJoin(ctx.cacheDirectory(), "discordMirror.db"); + const db = await new Database(path); + return new SqliteMirrorRepository(db, guild); +}