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.
This commit is contained in:
Dandelion Mané 2020-06-13 12:09:18 -07:00
parent 6e6d749902
commit dd76595ac2
2 changed files with 91 additions and 0 deletions

View File

@ -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(),
};
}

View File

@ -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<DiscordConfig> {
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<void> {
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<WeightedGraph> {
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<ReferenceDetector> {
// 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<SqliteMirrorRepository> {
const path = pathJoin(ctx.cacheDirectory(), "discordMirror.db");
const db = await new Database(path);
return new SqliteMirrorRepository(db, guild);
}