Browse Source

Base implementation of contest create cmd

ghorsington 5 years ago
parent
commit
dee5dcdf50
4 changed files with 173 additions and 2 deletions
  1. 4 1
      bot/package.json
  2. 146 0
      bot/src/commands/contest.ts
  3. 20 1
      bot/src/util.ts
  4. 3 0
      db/src/entity/Contest.ts

+ 4 - 1
bot/package.json

@@ -33,10 +33,12 @@
       "@types/sha1": "^1.1.2",
       "@types/turndown": "^5.0.0",
       "@types/xml2js": "^0.4.4",
+      "@types/yaml": "^1.0.2",
       "axios": "^0.19.0",
       "cheerio": "^1.0.0-rc.3",
       "discord.js": "^11.4.2",
       "dotenv": "^8.0.0",
+      "emoji-regex": "^8.0.0",
       "html2bbcode": "^1.2.6",
       "interval-promise": "^1.2.0",
       "jimp": "^0.5.4",
@@ -54,7 +56,8 @@
       "turndown": "^5.0.1",
       "typeorm": "0.2.18",
       "typescript": "^3.5.2",
-      "uws": "^99.0.0"
+      "uws": "^99.0.0",
+      "yaml": "^1.6.0"
    },
    "devDependencies": {
       "@types/node": "^8.0.29",

+ 146 - 0
bot/src/commands/contest.ts

@@ -0,0 +1,146 @@
+import { ICommand } from "./command";
+import { Dict, NeighBuilder } from "src/util";
+import { ReactionCollector, Message } from "discord.js";
+import yaml from "yaml";
+import { client } from "src/client";
+import { getRepository } from "typeorm";
+import { Contest } from "@db/entity/Contest";
+import { isNumber } from "util";
+import emoji_regex from "emoji-regex";
+
+
+const CHANNEL_ID_PATTERN = /<#(\d+)>/;
+
+let pendingReactors : Dict<ReactionCollector> = {};
+
+async function init() {
+
+}
+
+interface ContestCreationOptions {
+    in?: string;
+    duration?: string;
+    announce_winners?: boolean;
+    vote_reaction?: string;
+}
+
+const CONTEST_DEFAULTS : ContestCreationOptions = {
+    duration: "1d",
+    announce_winners: false,
+    vote_reaction: "❤️"
+};
+
+const DURATION_MULTIPLIERS : Dict<number> = {
+    "s" : 1000,
+    "min": 60 * 1000,
+    "h": 60 * 60 * 1000,
+    "d": 24 * 60 * 60 * 1000,
+    "mon": 30 * 24 * 60 * 60 * 1000,
+    "y": 365 * 24 * 60 * 60 * 1000
+};
+
+function parseDuration(duration: string) : number | undefined {
+    let numNB = new NeighBuilder();
+    
+    let chIndex = 0;
+    for(; chIndex < duration.length; chIndex++) {
+        let c = duration.charAt(chIndex);
+        if(isNumber(c))
+            numNB.append(c);
+        else
+            break;
+    }
+
+    let unit = duration.substr(chIndex).trim().toLowerCase();
+
+    if(!(unit in DURATION_MULTIPLIERS))
+        return undefined;
+
+    return +numNB * DURATION_MULTIPLIERS[unit];
+}
+
+async function createContest(msg: Message, info: ContestCreationOptions) {
+    let dur = parseDuration(info.duration);
+    if(!dur) {
+        await msg.channel.send(`${msg.author.toString()} Duration format is invalid!`);
+        return;
+    }
+
+    if(!msg.guild.emojis.find(e => e.toString() == info.vote_reaction) && !emoji_regex().exec(info.vote_reaction)) {
+        await msg.channel.send(`${msg.author.toString()} The vote emote must be accessible by everyone on the server!`);
+        return;
+    }
+
+    let repo = getRepository(Contest);
+
+    let contest = await repo.findOne({
+        where: { channel: info.in }
+    });
+
+    if(contest) {
+        await msg.channel.send(`${msg.author.toString()} The channel already has a contest running!`);
+        return;
+    }
+
+    contest = repo.create({
+        channel: info.in,
+        startDate: new Date(),
+        endDate: new Date(Date.now() + dur),
+        announceWinners: info.announce_winners
+    });
+
+    await repo.save(contest);
+    await msg.channel.send(`${msg.author.toString()} Started contest (ID: ${contest.id})`);
+}
+
+export default <ICommand>{
+    commands: [
+        {
+            //\s+in:\s*([^\s]+)\s+duration:\s(\d+ ?(s|d|m|y))\s+announce_winners:\s*(true|false)\s+vote_reaction:\s*([^\s])
+            pattern: "create contest",
+            action: async (m, contents) => {
+                let message = contents.substr("create contest".length);
+                let contestData : ContestCreationOptions = { ...CONTEST_DEFAULTS, ...yaml.parse(message) };
+
+                if(!contestData.in)
+                    contestData.in = m.channel.id;
+                else {
+                    let matches = CHANNEL_ID_PATTERN.exec(contestData.in);
+                    if(matches.length == 0) {
+                        await m.channel.send(`${m.author.toString()} I can't see such a channel!`);
+                        return;
+                    }
+                    
+                    let channelId = matches[1];
+                    if(!m.guild.channels.exists("id", channelId)) {
+                        await m.channel.send(`${m.author.toString()} This channel is not in the current guild!`);
+                        return;
+                    }
+
+                    contestData.in = channelId;
+                }
+
+                await createContest(m, contestData);
+            }
+        },
+        {
+            pattern: "contests",
+            action: async (m) => {
+                await m.channel.send("Heck");
+            }
+        },
+        {
+            pattern: /end contest( (\d*))?/,
+            action: async (m, contents, matches) => {
+                await m.channel.send("Heck");
+            }
+        },
+        {
+            pattern: "announce winners",
+            action: async (m, contents, matches) => {
+                await m.channel.send("Heck");
+            }
+        }
+    ],
+    onStart: init,
+};

+ 20 - 1
bot/src/util.ts

@@ -45,4 +45,23 @@ export async function isAuthorisedAsync(member : GuildMember) {
     return false;
 }
 
-export type Dict<TVal> = { [key: string]: TVal };
+export type Dict<TVal> = { [key: string]: TVal };
+
+export class NeighBuilder {
+    private data: any[];
+
+    constructor() {}
+
+    append(...data: any[]) {
+        this.data.concat(data);
+    }
+
+    appendLine(...data: any[]){
+        this.append(data);
+        this.data.push("\n");
+    }
+
+    toString() {
+        return this.data.reduce((prev, cur) => cur + `${prev}`, "");
+    }
+}

+ 3 - 0
db/src/entity/Contest.ts

@@ -20,6 +20,9 @@ export class Contest {
     @Column()
     announceWinners: boolean;
 
+    @Column()
+    voteReaction: string;
+
     @OneToMany(type => ContestEntry, entry => entry.contest)
     entries: ContestEntry[];