|  | @@ -1,487 +0,0 @@
 | 
	
		
			
				|  |  | -import { Dict, compareNumbers, isAuthorisedAsync } from "../util";
 | 
	
		
			
				|  |  | -import { Message, TextChannel, RichEmbed, MessageReaction, User, Collector, Collection, SnowflakeUtil } from "discord.js";
 | 
	
		
			
				|  |  | -import yaml from "yaml";
 | 
	
		
			
				|  |  | -import { getRepository, getManager } from "typeorm";
 | 
	
		
			
				|  |  | -import { Contest } from "@shared/db/entity/Contest";
 | 
	
		
			
				|  |  | -import emoji_regex from "emoji-regex";
 | 
	
		
			
				|  |  | -import { client } from "../client";
 | 
	
		
			
				|  |  | -import { scheduleJob } from "node-schedule";
 | 
	
		
			
				|  |  | -import { ContestEntry } from "@shared/db/entity/ContestEntry";
 | 
	
		
			
				|  |  | -import { ContestVote } from "@shared/db/entity/ContestVote";
 | 
	
		
			
				|  |  | -import { CommandSet, Command, Action, ActionType } from "src/model/command";
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -const CHANNEL_ID_PATTERN = /<#(\d+)>/;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -interface ActiveContest {
 | 
	
		
			
				|  |  | -    id: number;
 | 
	
		
			
				|  |  | -    voteReaction: string;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -interface ContestCreationOptions {
 | 
	
		
			
				|  |  | -    in?: string;
 | 
	
		
			
				|  |  | -    duration?: string;
 | 
	
		
			
				|  |  | -    announce_winners?: boolean;
 | 
	
		
			
				|  |  | -    vote_reaction?: string;
 | 
	
		
			
				|  |  | -    max_winners?: number;
 | 
	
		
			
				|  |  | -    unique_winners?: boolean
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -const CONTEST_DEFAULTS: ContestCreationOptions = {
 | 
	
		
			
				|  |  | -    duration: "1d",
 | 
	
		
			
				|  |  | -    announce_winners: false,
 | 
	
		
			
				|  |  | -    vote_reaction: "❤",
 | 
	
		
			
				|  |  | -    max_winners: 1,
 | 
	
		
			
				|  |  | -    unique_winners: true
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -const DURATION_REGEX_STR = `(\\d+) ?(${Object.keys(DURATION_MULTIPLIERS).join("|")})`;
 | 
	
		
			
				|  |  | -const DURATION_REGEX = new RegExp(DURATION_REGEX_STR, "i");
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -function parseDuration(duration: string): number | undefined {
 | 
	
		
			
				|  |  | -    let match = DURATION_REGEX.exec(duration);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    if (match.length == 0)
 | 
	
		
			
				|  |  | -        return undefined;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    let num = match[1];
 | 
	
		
			
				|  |  | -    let unit = match[2];
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    return +num * DURATION_MULTIPLIERS[unit];
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -type ContestEntryWithMessage = ContestEntry & { message: Message };
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -function numberToOrdered(num: number) {
 | 
	
		
			
				|  |  | -    const prefixes = ["st", "nd", "rd"];
 | 
	
		
			
				|  |  | -    let s = num % 10;
 | 
	
		
			
				|  |  | -    return 0 < s && s <= prefixes.length ? `${num}${prefixes[s - 1]}` : `${num}th`;
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -@CommandSet
 | 
	
		
			
				|  |  | -/*export*/ class ContestCommands {
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    activeContests: Dict<ActiveContest> = {};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    diffEntryVotes(votes: ContestVote[], currentUsers: Collection<string, User>) {
 | 
	
		
			
				|  |  | -        let votedUsersIds = new Set(votes.map(v => v.userId));
 | 
	
		
			
				|  |  | -        let currentUsersIds = new Set(currentUsers.keys());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        for (let currentUserId of currentUsersIds)
 | 
	
		
			
				|  |  | -            if (votedUsersIds.has(currentUserId))
 | 
	
		
			
				|  |  | -                votedUsersIds.delete(currentUserId);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        for (let votedUserId of votedUsersIds)
 | 
	
		
			
				|  |  | -            if (currentUsersIds.has(votedUserId))
 | 
	
		
			
				|  |  | -                currentUsersIds.delete(votedUserId);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        return [currentUsersIds, votedUsersIds];
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    async updateContestStatus(contest: Contest) {
 | 
	
		
			
				|  |  | -        let voteRepo = getRepository(ContestVote);
 | 
	
		
			
				|  |  | -        let entryRepo = getRepository(ContestEntry);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let channel = client.channels.get(contest.channel) as TextChannel;
 | 
	
		
			
				|  |  | -        if (!channel) {
 | 
	
		
			
				|  |  | -            console.log(`Channel ${contest.channel} has been deleted! Removing contest ${contest.id}...`);
 | 
	
		
			
				|  |  | -            await this.removeContest(contest.id);
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let newestEntry: Date = null;
 | 
	
		
			
				|  |  | -        let contestEntryMessageIds = new Set<string>(contest.entries.map(e => e.msgId));
 | 
	
		
			
				|  |  | -        for (let entry of contest.entries) {
 | 
	
		
			
				|  |  | -            try {
 | 
	
		
			
				|  |  | -                let msg = await channel.fetchMessage(entry.msgId);
 | 
	
		
			
				|  |  | -                let existingVotes = await voteRepo.find({ where: { contest: contest, contestEntry: entry } });
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                let voteReaction = msg.reactions.find(r => r.emoji.toString() == contest.voteReaction);
 | 
	
		
			
				|  |  | -                if (!voteReaction) {
 | 
	
		
			
				|  |  | -                    await voteRepo.remove(existingVotes);
 | 
	
		
			
				|  |  | -                    continue;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                let users = await voteReaction.fetchUsers();
 | 
	
		
			
				|  |  | -                let [newVotes, removedVotes] = this.diffEntryVotes(existingVotes, users);
 | 
	
		
			
				|  |  | -                voteRepo.remove(existingVotes.filter(v => removedVotes.has(v.userId)));
 | 
	
		
			
				|  |  | -                let newVoteEntries = [...newVotes].map(i => voteRepo.create({
 | 
	
		
			
				|  |  | -                    userId: i,
 | 
	
		
			
				|  |  | -                    contest: contest,
 | 
	
		
			
				|  |  | -                    contestEntry: entry
 | 
	
		
			
				|  |  | -                }));
 | 
	
		
			
				|  |  | -                await voteRepo.save(newVoteEntries);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                entry.votes = [
 | 
	
		
			
				|  |  | -                    ...newVoteEntries,
 | 
	
		
			
				|  |  | -                    ...existingVotes.filter(v => !removedVotes.has(v.userId))
 | 
	
		
			
				|  |  | -                ];
 | 
	
		
			
				|  |  | -                await entryRepo.save(entry);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                if (!newestEntry || msg.createdAt > newestEntry)
 | 
	
		
			
				|  |  | -                    newestEntry = msg.createdAt;
 | 
	
		
			
				|  |  | -            } catch (err) {
 | 
	
		
			
				|  |  | -                console.log(`Failed to update entry ${entry.msgId} for contest ${contest.id} because ${err}!`);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                await voteRepo.delete({ contestEntry: entry });
 | 
	
		
			
				|  |  | -                await entryRepo.delete({ msgId: entry.msgId });
 | 
	
		
			
				|  |  | -                contestEntryMessageIds.delete(entry.msgId);
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let newEntries = (await channel.fetchMessages({
 | 
	
		
			
				|  |  | -            after: SnowflakeUtil.generate(newestEntry || contest.startDate)
 | 
	
		
			
				|  |  | -        })).filter(m => m.attachments.size != 0 && !contestEntryMessageIds.has(m.id));
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        for (let [_, msg] of newEntries)
 | 
	
		
			
				|  |  | -            await this.registerEntry(msg, contest);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (contest.endDate < new Date()) {
 | 
	
		
			
				|  |  | -            await this.stopContest(contest.id);
 | 
	
		
			
				|  |  | -        } else {
 | 
	
		
			
				|  |  | -            scheduleJob(contest.endDate, this.stopContest.bind(this, contest.id));
 | 
	
		
			
				|  |  | -            this.activeContests[channel.id] = {
 | 
	
		
			
				|  |  | -                id: contest.id,
 | 
	
		
			
				|  |  | -                voteReaction: contest.voteReaction
 | 
	
		
			
				|  |  | -            };
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    async registerEntry(msg: Message, contest: Contest) {
 | 
	
		
			
				|  |  | -        let entryRepo = getRepository(ContestEntry);
 | 
	
		
			
				|  |  | -        let voteRepo = getRepository(ContestVote);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let entry = entryRepo.create({
 | 
	
		
			
				|  |  | -            msgId: msg.id,
 | 
	
		
			
				|  |  | -            contest: contest
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        await entryRepo.save(entry);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let voteReaction = msg.reactions.find(r => r.emoji.toString() == contest.voteReaction);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (!voteReaction)
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let votedUsers = await voteReaction.fetchUsers();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        await voteRepo.save(votedUsers.map(u => voteRepo.create({
 | 
	
		
			
				|  |  | -            userId: u.id,
 | 
	
		
			
				|  |  | -            contest: contest,
 | 
	
		
			
				|  |  | -            contestEntry: entry
 | 
	
		
			
				|  |  | -        })));
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    async removeContest(contestId: number) {
 | 
	
		
			
				|  |  | -        await getManager().transaction(async em => {
 | 
	
		
			
				|  |  | -            let contestRepo = em.getRepository(Contest);
 | 
	
		
			
				|  |  | -            let contestEntryRepo = em.getRepository(ContestEntry);
 | 
	
		
			
				|  |  | -            let contestVoteRepo = em.getRepository(ContestVote);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            let contest = contestRepo.create({
 | 
	
		
			
				|  |  | -                id: contestId
 | 
	
		
			
				|  |  | -            });
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            await contestRepo.delete({
 | 
	
		
			
				|  |  | -                id: contestId
 | 
	
		
			
				|  |  | -            });
 | 
	
		
			
				|  |  | -            await contestVoteRepo.delete({
 | 
	
		
			
				|  |  | -                contest: contest
 | 
	
		
			
				|  |  | -            });
 | 
	
		
			
				|  |  | -            await contestEntryRepo.delete({
 | 
	
		
			
				|  |  | -                contest: contest
 | 
	
		
			
				|  |  | -            });
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    async pickValidEntries(channel: TextChannel, contestEntries: ContestEntry[], max: number, unique = true) {
 | 
	
		
			
				|  |  | -        let addedUsers = new Set<string>();
 | 
	
		
			
				|  |  | -        let result: ContestEntryWithMessage[] = [];
 | 
	
		
			
				|  |  | -        let maxResults = Math.min(max, contestEntries.length);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        for (let entry of contestEntries) {
 | 
	
		
			
				|  |  | -            try {
 | 
	
		
			
				|  |  | -                let msg = await channel.fetchMessage(entry.msgId);
 | 
	
		
			
				|  |  | -                if (unique && addedUsers.has(msg.author.id))
 | 
	
		
			
				|  |  | -                    continue;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                result.push({ ...entry, message: msg });
 | 
	
		
			
				|  |  | -                addedUsers.add(msg.author.id);
 | 
	
		
			
				|  |  | -                if (result.length == maxResults)
 | 
	
		
			
				|  |  | -                    break;
 | 
	
		
			
				|  |  | -            } catch (err) { }
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        return result;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    async printResults(contest: Contest, channel: TextChannel) {
 | 
	
		
			
				|  |  | -        let entryRepo = getRepository(ContestEntry);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let entries = await entryRepo.find({
 | 
	
		
			
				|  |  | -            where: { contest: contest },
 | 
	
		
			
				|  |  | -            relations: ["votes"]
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (entries.length == 0) {
 | 
	
		
			
				|  |  | -            // Hmmm... maybe rich embeds?
 | 
	
		
			
				|  |  | -            await channel.send("No entries were sent into this contest! Therefore I declare myself a winner!");
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let winningEntries = await this.pickValidEntries(channel, entries.sort(compareNumbers(o => o.votes.length)), contest.maxWinners, contest.uniqueWinners);
 | 
	
		
			
				|  |  | -        let totalVotes = entries.reduce((p, c) => p + c.votes.length, 0);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let embed = new RichEmbed({
 | 
	
		
			
				|  |  | -            title: "🎆 Contest results 🎆",
 | 
	
		
			
				|  |  | -            color: 0x3b8dc4,
 | 
	
		
			
				|  |  | -            timestamp: new Date(),
 | 
	
		
			
				|  |  | -            description: `The contest has ended!\nCollected ${totalVotes} votes.\nHere are the results:`,
 | 
	
		
			
				|  |  | -            fields: winningEntries.map((e, i) => ({
 | 
	
		
			
				|  |  | -                name: `${numberToOrdered(i + 1)} place (${e.votes.length} votes)`,
 | 
	
		
			
				|  |  | -                value: `${e.message.author.toString()} ([View entry](${e.message.url}))`
 | 
	
		
			
				|  |  | -            }))
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        await channel.send(embed);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    async stopContest(contestId: number) {
 | 
	
		
			
				|  |  | -        let repo = getRepository(Contest);
 | 
	
		
			
				|  |  | -        let contest = await repo.findOne(contestId);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let channel = client.channels.get(contest.channel) as TextChannel;
 | 
	
		
			
				|  |  | -        if (!channel) {
 | 
	
		
			
				|  |  | -            // TODO: Don't remove; instead report in web manager
 | 
	
		
			
				|  |  | -            console.log(`Channel ${contest.channel} has been deleted! Removing contest ${contest.id}...`);
 | 
	
		
			
				|  |  | -            await this.removeContest(contestId);
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        await channel.send(`Current contest has ended! Thank you for your participation!`);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (contest.announceWinners)
 | 
	
		
			
				|  |  | -            await this.printResults(contest, channel);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        await repo.update(contestId, { active: false });
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    async createContest(msg: Message, info: ContestCreationOptions) {
 | 
	
		
			
				|  |  | -        if (info.in) {
 | 
	
		
			
				|  |  | -            let matches = CHANNEL_ID_PATTERN.exec(info.in);
 | 
	
		
			
				|  |  | -            if (matches.length == 0) {
 | 
	
		
			
				|  |  | -                await msg.channel.send(`${msg.author.toString()} I can't see such a channel!`);
 | 
	
		
			
				|  |  | -                return;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            let channelId = matches[1];
 | 
	
		
			
				|  |  | -            if (!msg.guild.channels.exists("id", channelId)) {
 | 
	
		
			
				|  |  | -                await msg.channel.send(`${msg.author.toString()} This channel is not in the current guild!`);
 | 
	
		
			
				|  |  | -                return;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            info.in = channelId;
 | 
	
		
			
				|  |  | -        } else
 | 
	
		
			
				|  |  | -            info.in = msg.channel.id;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (info.max_winners < 1) {
 | 
	
		
			
				|  |  | -            await msg.channel.send(`${msg.author.toString()} The contest must have at least one possible winner!`);
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        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, active: true }
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        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,
 | 
	
		
			
				|  |  | -            voteReaction: info.vote_reaction,
 | 
	
		
			
				|  |  | -            maxWinners: info.max_winners,
 | 
	
		
			
				|  |  | -            uniqueWinners: info.unique_winners,
 | 
	
		
			
				|  |  | -            active: true
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        await repo.save(contest);
 | 
	
		
			
				|  |  | -        await msg.channel.send(`${msg.author.toString()} Started contest (ID: ${contest.id})`);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        scheduleJob(contest.endDate, this.stopContest.bind(this, contest.id));
 | 
	
		
			
				|  |  | -        this.activeContests[contest.channel] = {
 | 
	
		
			
				|  |  | -            id: contest.id,
 | 
	
		
			
				|  |  | -            voteReaction: contest.voteReaction
 | 
	
		
			
				|  |  | -        };
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    async onReact(reaction: MessageReaction, user: User) {
 | 
	
		
			
				|  |  | -        if (user.bot)
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let channel = reaction.message.channel;
 | 
	
		
			
				|  |  | -        let activeContest = this.activeContests[channel.id];
 | 
	
		
			
				|  |  | -        if (!activeContest || reaction.emoji.toString() != activeContest.voteReaction)
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let entryRepo = getRepository(ContestEntry);
 | 
	
		
			
				|  |  | -        let voteRepo = getRepository(ContestVote);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let entry = await entryRepo.findOne({
 | 
	
		
			
				|  |  | -            where: { msgId: reaction.message.id }
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -        if (!entry)
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let vote = await voteRepo.findOne({
 | 
	
		
			
				|  |  | -            where: { userId: user.id, contestId: activeContest.id }
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -        if (!vote)
 | 
	
		
			
				|  |  | -            vote = voteRepo.create({ userId: user.id, contestId: activeContest.id });
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        vote.contestEntry = entry;
 | 
	
		
			
				|  |  | -        await voteRepo.save(vote);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    @Action(ActionType.MESSAGE)
 | 
	
		
			
				|  |  | -    async addEntry(actionsDone: boolean, m: Message, content: string) {
 | 
	
		
			
				|  |  | -        if (m.attachments.size == 0)
 | 
	
		
			
				|  |  | -            return false;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let channel = m.channel;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let contestRepo = getRepository(Contest);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let contest = await contestRepo.findOne({
 | 
	
		
			
				|  |  | -            where: {
 | 
	
		
			
				|  |  | -                channel: channel.id,
 | 
	
		
			
				|  |  | -                active: true
 | 
	
		
			
				|  |  | -            },
 | 
	
		
			
				|  |  | -            select: ["id", "voteReaction"]
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (!contest)
 | 
	
		
			
				|  |  | -            return false;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        await this.registerEntry(m, contest);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // Don't prevent further actions
 | 
	
		
			
				|  |  | -        return false;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    async onStart() {
 | 
	
		
			
				|  |  | -        let contestRepo = getRepository(Contest);
 | 
	
		
			
				|  |  | -        let contests = await contestRepo.find({
 | 
	
		
			
				|  |  | -            where: { active: true },
 | 
	
		
			
				|  |  | -            relations: ["entries"]
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        for (let contest of contests)
 | 
	
		
			
				|  |  | -            await this.updateContestStatus(contest);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        client.on("messageReactionAdd", this.onReact);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    @Command({ pattern: "create contest", auth: true })
 | 
	
		
			
				|  |  | -    async startContest(m: Message) {
 | 
	
		
			
				|  |  | -        if (!await isAuthorisedAsync(m.member)) {
 | 
	
		
			
				|  |  | -            m.channel.send(`${m.author.toString()} You're not authorised to create contests!`);
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let message = m.content.trim().substr(client.user.toString().length).trim().substr("create contest".length).trim();
 | 
	
		
			
				|  |  | -        let contestData: ContestCreationOptions = { ...CONTEST_DEFAULTS, ...yaml.parse(message) };
 | 
	
		
			
				|  |  | -        await this.createContest(m, contestData);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    @Command({ pattern: "contests" })
 | 
	
		
			
				|  |  | -    async listContests(m: Message) {
 | 
	
		
			
				|  |  | -        let repo = getRepository(Contest);
 | 
	
		
			
				|  |  | -        let contests = await repo.find({ where: { active: true } });
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let contestsData = contests.map(c => ({
 | 
	
		
			
				|  |  | -            contest: c,
 | 
	
		
			
				|  |  | -            channel: client.channels.get(c.channel) as TextChannel
 | 
	
		
			
				|  |  | -        })).filter(c => c.channel);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (contestsData.length == 0)
 | 
	
		
			
				|  |  | -            await m.channel.send(`${m.author.toString()} There are no currently running contests!`);
 | 
	
		
			
				|  |  | -        else
 | 
	
		
			
				|  |  | -            await m.channel.send(`${m.author.toString()} Currently there are contests active in the following channels:\n${contestsData.map((c, i) => `${i + 1}. ${c.channel.toString()}`).join("\n")}`);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    @Command({ pattern: /end contest( (\d*))?/, auth: true })
 | 
	
		
			
				|  |  | -    async endContest(m: Message, contents: string, matches: RegExpMatchArray) {
 | 
	
		
			
				|  |  | -        if (!await isAuthorisedAsync(m.member)) {
 | 
	
		
			
				|  |  | -            m.channel.send(`${m.author.toString()} You're not authorised to create contests!`);
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let repo = getRepository(Contest);
 | 
	
		
			
				|  |  | -        let contestId = +matches[1];
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let contest = await repo.findOne({
 | 
	
		
			
				|  |  | -            where: { id: contestId },
 | 
	
		
			
				|  |  | -            select: ["id", "active"]
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (!contest) {
 | 
	
		
			
				|  |  | -            await m.channel.send(`${m.author.toString()} Can't find contest with ID ${contestId}`);
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (!contest.active) {
 | 
	
		
			
				|  |  | -            await m.channel.send(`${m.author.toString()} The contest with ID ${contestId} is already inactive!`);
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        await this.stopContest(contest.id);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    @Command({ pattern: "announce winners", auth: true })
 | 
	
		
			
				|  |  | -    async announceWinners(m: Message) {
 | 
	
		
			
				|  |  | -        let repo = getRepository(Contest);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        let contest = await repo.findOne({
 | 
	
		
			
				|  |  | -            where: { channel: m.channel.id },
 | 
	
		
			
				|  |  | -            order: { endDate: "DESC" }
 | 
	
		
			
				|  |  | -        });
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (!contest) {
 | 
	
		
			
				|  |  | -            await m.channel.send(`${m.author.toString()} There have never been any contests!`);
 | 
	
		
			
				|  |  | -            return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (contest.active) {
 | 
	
		
			
				|  |  | -            await this.stopContest(contest.id);
 | 
	
		
			
				|  |  | -            if (contest.announceWinners)
 | 
	
		
			
				|  |  | -                return;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -        await this.printResults(contest, m.channel as TextChannel);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -};
 |