Ver Fonte

Implementi initial version of contest info collector

ghorsington há 5 anos atrás
pai
commit
97553af01e
1 ficheiros alterados com 202 adições e 14 exclusões
  1. 202 14
      bot/src/commands/contest.ts

+ 202 - 14
bot/src/commands/contest.ts

@@ -1,22 +1,149 @@
 import { ICommand } from "./command";
 import { Dict, compareNumbers } from "../util";
-import { ReactionCollector, Message, TextChannel, RichEmbed } from "discord.js";
+import { ReactionCollector, Message, TextChannel, RichEmbed, MessageReaction, User, Collector, Collection, SnowflakeUtil } from "discord.js";
 import yaml from "yaml";
 import { getRepository, getManager } from "typeorm";
 import { Contest } from "@db/entity/Contest";
-import { isNumber } from "util";
 import emoji_regex from "emoji-regex";
 import { client } from "../client";
 import { scheduleJob } from "node-schedule";
-import { ContestEntry } from "../../../db/lib/src/entity/ContestEntry";
-import { ContestVote } from "../../../db/lib/src/entity/ContestVote";
+import { ContestEntry } from "@db/entity/ContestEntry";
+import { ContestVote } from "@db/entity/ContestVote";
 
 const CHANNEL_ID_PATTERN = /<#(\d+)>/;
 
-let pendingReactors: Dict<ReactionCollector> = {};
+let activeContests: Dict<ActiveContest> = {};
 
 async function init() {
+    let contestRepo = getRepository(Contest);
 
+    let contests = await contestRepo.find({
+        where: { active: true },
+        relations: ["entries"]
+    });
+
+    let now = new Date();
+
+    for (let contest of contests)
+        await updateContestStatus(contest);
+
+    client.on("messageReactionAdd", onReact);
+}
+
+function 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];
+}
+
+type ContestEntryMessage = {
+    msgId?: string;
+    timestamp?: Date;
+};
+
+//TODO: Convert into a transaction
+async function 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 removeContest(contest.id);
+        return;
+    }
+
+    let entries = contest.entries.reduce((p, c) => {
+        p[c.msgId] = c;
+        return p;
+    }, <Dict<ContestEntry>>{});
+
+    let newestEntry: Date = null;
+
+    for (let entry of contest.entries) {
+        try {
+            let msg = await channel.fetchMessage(entry.msgId);
+            let voteReaction = msg.reactions.get(contest.voteReaction);
+            let users = await voteReaction.fetchUsers();
+            let existingVotes = await voteRepo.find({ where: { contest: contest } });
+
+            let [newVotes, removedVotes] = 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}!`);
+            delete entries[entry.msgId];
+        }
+
+        let newEntries = (await channel.fetchMessages({
+            after: SnowflakeUtil.generate(newestEntry || contest.startDate)
+        })).filter(m => m.attachments.size != 0);
+
+        for(let [_, msg] of newEntries)
+            await registerEntry(msg, contest);
+    }
+
+    if(contest.endDate < new Date()){
+        contest.active = false;
+
+        if(contest.announceWinners)
+            await printResults(contest, channel);
+    }
+}
+
+async function 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);
+
+    let votedUsers = await voteReaction.fetchUsers();
+
+    await voteRepo.save(votedUsers.map(u => voteRepo.create({
+        userId: u.id,
+        contest: contest,
+        contestEntry: entry
+    })));
+}
+
+interface ActiveContest {
+    id: number;
+    voteReaction: string;
 }
 
 interface ContestCreationOptions {
@@ -86,20 +213,20 @@ type ContestEntryWithMessage = ContestEntry & { message: Message };
 
 async function pickValidEntries(channel: TextChannel, contestEntries: ContestEntry[], max: number, unique = true) {
     let addedUsers = new Set<string>();
-    let result : ContestEntryWithMessage[] = [];
+    let result: ContestEntryWithMessage[] = [];
     let maxResults = Math.min(max, contestEntries.length);
 
-    for(let entry of contestEntries) {
-        try{
+    for (let entry of contestEntries) {
+        try {
             let msg = await channel.fetchMessage(entry.msgId);
-            if(unique && addedUsers.has(msg.author.id))
+            if (unique && addedUsers.has(msg.author.id))
                 continue;
 
-            result.push({...entry, message: msg});
+            result.push({ ...entry, message: msg });
             addedUsers.add(msg.author.id);
-            if(result.length == maxResults)
+            if (result.length == maxResults)
                 break;
-        } catch(err) {}
+        } catch (err) { }
     }
 
     return result;
@@ -182,7 +309,7 @@ async function createContest(msg: Message, info: ContestCreationOptions) {
     } else
         info.in = msg.channel.id;
 
-    if(info.max_winners < 1) {
+    if (info.max_winners < 1) {
         await msg.channel.send(`${msg.author.toString()} The contest must have at least one possible winner!`);
         return;
     }
@@ -227,11 +354,72 @@ async function createContest(msg: Message, info: ContestCreationOptions) {
 }
 
 async function onMessage(actionsDone: boolean, m: Message, content: string) {
-    let repo = getRepository(Contest);
+    if (m.attachments.size == 0)
+        return false;
+
+    let channel = m.channel;
+
+    let contestRepo = getRepository(Contest);
+    let entryRepo = getRepository(ContestEntry);
+
+    let contest = await contestRepo.findOne({
+        where: {
+            channel: channel.id,
+            active: true
+        },
+        select: ["id", "voteReaction"]
+    });
+
+    if (!contest)
+        return false;
 
+    await registerEntry(m);
+
+    // Don't prevent further actions
     return false;
 }
 
+async function onReact(reaction: MessageReaction, user: User) {
+    let channel = reaction.message.channel;
+
+    let activeContest = activeContests[channel.id];
+    if (!activeContest)
+        return;
+
+    if (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,
+            contest: {
+                id: activeContest.id
+            }
+        }
+    });
+
+    if (!vote)
+        vote = voteRepo.create({
+            userId: user.id,
+            contest: {
+                id: activeContest.id
+            }
+        });
+
+    vote.contestEntry = entry;
+    await voteRepo.save(vote);
+}
+
 export default <ICommand>{
     commands: [
         {