diff --git a/package.json b/package.json index 5566300968bf18cd7cef48723e7aad801484f95e..5b7c30c2662ba06aa283958bb291ef33682f8dce 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,9 @@ }, "homepage": "https://github.com/Half-Shot/matrix-appservice-discord#readme", "dependencies": { + "@types/bluebird": "^3.0.37", "@types/node": "^7.0.5", + "@types/sqlite3": "^2.2.32", "bluebird": "^3.4.7", "discord.js": "^11.0.0", "js-yaml": "^3.8.1", @@ -37,6 +39,7 @@ "matrix-appservice-bridge": "^1.3.5", "mime": "^1.3.4", "npmlog": "^4.0.2", + "sqlite3": "^3.1.8", "tslint": "^4.4.2", "typescript": "^2.1.6" }, diff --git a/src/dbschema/dbschema.ts b/src/dbschema/dbschema.ts new file mode 100644 index 0000000000000000000000000000000000000000..e8b7cfdc625be7662f5e0cad1a910c78efd9025c --- /dev/null +++ b/src/dbschema/dbschema.ts @@ -0,0 +1,5 @@ +import { DiscordStore } from "../discordstore"; +export interface IDbSchema { + description: string, + run(store: DiscordStore): Promise<null>; +} diff --git a/src/dbschema/v1.ts b/src/dbschema/v1.ts new file mode 100644 index 0000000000000000000000000000000000000000..474544f7cfa27b50984e0711a479046c81c731bc --- /dev/null +++ b/src/dbschema/v1.ts @@ -0,0 +1,19 @@ +import {IDbSchema} from "./dbschema"; +import {DiscordStore} from "../discordstore"; +export class Schema implements IDbSchema { + public description = "Schema, Client Auth Table"; + public run(store: DiscordStore): Promise<null> { + return store.create_table(` + CREATE TABLE schema ( + version INTEGER UNIQUE NOT NULL + );`, "schema").then(() => { + return store.db.runAsync("INSERT INTO schema VALUES (0);"); + }).then(() => { + return store.create_table(` + CREATE TABLE user_tokens ( + userId TEXT UNIQUE NOT NULL, + token TEXT UNIQUE NOT NULL + );`, "user_tokens"); + }) + } +} diff --git a/src/discordas.ts b/src/discordas.ts index 1b5807922f2be99e8333ae361d5b6d772e7a6267..7d913fcb1f6cf94f9c3b1f8cedacada77a437fa4 100644 --- a/src/discordas.ts +++ b/src/discordas.ts @@ -5,6 +5,7 @@ import * as fs from "fs"; import { DiscordBridgeConfig } from "./config"; import { DiscordBot } from "./discordbot"; import { MatrixRoomHandler } from "./matrixroomhandler"; +import { DiscordStore } from "./discordstore"; const cli = new Cli({ bridgeConfig: { @@ -47,26 +48,21 @@ function run (port: number, config: DiscordBridgeConfig) { token: registration.as_token, url: config.bridge.homeserverUrl, }); - const discordbot = new DiscordBot(config); + const discordstore = new DiscordStore("discord.db"); + const discordbot = new DiscordBot(config, discordstore); const roomhandler = new MatrixRoomHandler(discordbot, config, botUserId); const bridge = new Bridge({ clientFactory, controller: { // onUserQuery: userQuery, - onAliasQuery: (alias, aliasLocalpart) => { - return roomhandler.OnAliasQuery(alias, aliasLocalpart); - }, + onAliasQuery: roomhandler.OnAliasQuery.bind(roomhandler), onEvent: roomhandler.OnEvent.bind(roomhandler), onAliasQueried: roomhandler.OnAliasQueried.bind(roomhandler), thirdPartyLookup: roomhandler.ThirdPartyLookup, - // onLog: function (line, isError) { - // if(isError) { - // if(line.indexOf("M_USER_IN_USE") === -1) {//QUIET! - // log.warn("matrix-appservice-bridge", line); - // } - // } - // } + onLog: (line, isError) => { + log.verbose("matrix-appservice-bridge", line); + } }, domain: config.bridge.domain, homeserverUrl: config.bridge.homeserverUrl, @@ -74,9 +70,12 @@ function run (port: number, config: DiscordBridgeConfig) { }); roomhandler.setBridge(bridge); discordbot.setBridge(bridge); - + log.info("discordas", "Initing bridge."); log.info("AppServ", "Started listening on port %s at %s", port, new Date().toUTCString() ); bridge.run(port, config); - discordbot.run(); - + log.info("discordas", "Initing store."); + discordstore.init().then(() => { + log.info("discordas", "Initing bot."); + return discordbot.run(); + }); } diff --git a/src/discordstore.ts b/src/discordstore.ts new file mode 100644 index 0000000000000000000000000000000000000000..83d981799427fccf16ef07326016789f5b114f4a --- /dev/null +++ b/src/discordstore.ts @@ -0,0 +1,121 @@ +import * as SQLite3 from "sqlite3"; +import * as log from "npmlog"; +import * as Bluebird from "bluebird"; +import { IDbSchema } from "./dbschema/dbschema"; + +const CURRENT_SCHEMA = 1; +/** + * Stores data for specific users and data not specific to rooms. + */ +export class DiscordStore { + /** + * @param {string} filepath Location of the SQLite database file. + */ + public db: any; + private version: number; + constructor (filepath) { + this.db = new SQLite3.Database(filepath, (err) => { + if (err) { + log.error("DiscordStore", "Error opening database, %s"); + throw new Error("Couldn't open database. The appservice won't be able to continue."); + } + }); + this.db = Bluebird.promisifyAll(this.db); + this.version = null; + } + + /** + * Checks the database has all the tables needed. + */ + public init () { + log.info("DiscordStore", "Starting DB Init"); + let oldVersion; + let version; + return this.getSchemaVersion().then( (v) => { + oldVersion = v; + version = v; + let promises = []; + while (version < CURRENT_SCHEMA) { + version++; + const schemaClass = require(`./dbschema/v${version}.js`).Schema; + const schema = (new schemaClass() as IDbSchema); + log.info("DiscordStore", `Updating database to v${version}, ${schema.description}`); + promises.push(schema.run(this).then(() => { + log.info("DiscordStore", "Updated database v%s", version); + })); + this.version = version; + } + return Promise.all(promises); + }).then( () => { + return this.setSchemaVersion(oldVersion, version).then( () => { + log.info("DiscordStore", "Updated database to the latest schema"); + }); + }).catch( (err) => { + log.error("DiscordStore", "Couldn't update database to the latest version! Bailing"); + throw err; + }); + } + + public create_table (statement, tablename) { + return this.db.runAsync(statement).then(() => { + log.info("DiscordStore", "Created table ", tablename); + }).catch((err) => { + throw new Error(`Error creating '${tablename}': ${err}`); + }); + } + + public close () { + this.db.close(); + } + + public set_user_token(userId: string, token: string) { + log.silly("SQL", "set_user_token => %s", userId); + return this.db.runAsync( + `REPLACE INTO user_tokens (userId,token) VALUES ($id,$token);` + , { + $id: userId, + $token: token + }).catch(err => { + log.error("TwitDB", "Error storing user token %s", err); + throw err; + }); + } + + public get_user_token(userId: string): Promise<string> { + log.silly("SQL", "get_user_token => %s", userId); + return this.db.getAsync( + ` + SELECT token + FROM user_tokens + WHERE user_tokens.userId = $id; + ` + , { + $id: userId + }).then(row => { + return row !== undefined ? row.token : null; + }).catch( err => { + log.error("TwitDB", "Error getting user token %s", err.Error); + throw err; + }); + } + + private getSchemaVersion ( ) { + log.silly("DiscordStore", "_get_schema_version"); + return this.db.getAsync(`SELECT version FROM schema`).then((row) => { + return row === undefined ? 0 : row.version; + }).catch( () => { + return 0; + }); + } + + private setSchemaVersion (oldVer: number, ver: number) { + log.silly("DiscordStore", "_set_schema_version => %s", ver); + return this.db.getAsync( + ` + UPDATE schema + SET version = $ver + WHERE version = $old_ver + `, {$ver: ver, $old_ver: oldVer}, + ); + } +}