Przeglądaj źródła

Finalize contests functionality

ghorsington 5 lat temu
rodzic
commit
1bbb56ae04
2 zmienionych plików z 67 dodań i 56 usunięć
  1. 63 55
      bot/src/commands/contest.ts
  2. 4 1
      db/src/entity/ContestVote.ts

+ 63 - 55
bot/src/commands/contest.ts

@@ -16,14 +16,11 @@ 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);
 
@@ -52,73 +49,68 @@ type ContestEntryMessage = {
 
 //TODO: Convert into a transaction
 async function updateContestStatus(contest: Contest) {
-    let contestRepo = getRepository(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;
-
+    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 voteReaction = msg.reactions.get(contest.voteReaction);
-            let users = await voteReaction.fetchUsers();
-            let existingVotes = await voteRepo.find({ where: { contest: contest } });
+            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] = 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];
+            
+            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);
+    })).filter(m => m.attachments.size != 0 && !contestEntryMessageIds.has(m.id));
 
     for (let [_, msg] of newEntries)
         await registerEntry(msg, contest);
 
     if (contest.endDate < new Date()) {
-        if (contest.announceWinners)
-            await printResults(contest, channel);
-        await contestRepo.update(contest.id, { active: false });
+        await stopContest(contest.id);
     } else {
         scheduleJob(contest.endDate, stopContest.bind(null, contest.id));
-        activeContests[contest.id] = {
+        activeContests[channel.id] = {
             id: contest.id,
             voteReaction: contest.voteReaction
         };
@@ -138,6 +130,9 @@ async function registerEntry(msg: Message, contest: Contest) {
 
     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({
@@ -164,7 +159,7 @@ interface ContestCreationOptions {
 const CONTEST_DEFAULTS: ContestCreationOptions = {
     duration: "1d",
     announce_winners: false,
-    vote_reaction: "❤",
+    vote_reaction: "❤",
     max_winners: 1,
     unique_winners: true
 };
@@ -263,16 +258,16 @@ async function printResults(contest: Contest, channel: TextChannel) {
 
     let embed = new RichEmbed({
         title: "🎆 Contest results 🎆",
-        color: 0xff3b8dc4,
+        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`,
-            value: `${e.message.toString()} ([View entry](${e.message.url}))`
+            name: `${numberToOrdered(i + 1)} place (${e.votes.length} votes)`,
+            value: `${e.message.author.toString()} ([View entry](${e.message.url}))`
         }))
     });
 
-    await channel.sendEmbed(embed);
+    await channel.send(embed);
 }
 
 async function stopContest(contestId: number) {
@@ -292,9 +287,7 @@ async function stopContest(contestId: number) {
     if (contest.announceWinners)
         await printResults(contest, channel);
 
-    await repo.update(contestId, {
-        active: false
-    });
+    await repo.update(contestId, { active: false });
 }
 
 async function createContest(msg: Message, info: ContestCreationOptions) {
@@ -357,7 +350,7 @@ async function createContest(msg: Message, info: ContestCreationOptions) {
     await msg.channel.send(`${msg.author.toString()} Started contest (ID: ${contest.id})`);
 
     scheduleJob(contest.endDate, stopContest.bind(null, contest.id));
-    activeContests[contest.id] = {
+    activeContests[contest.channel] = {
         id: contest.id,
         voteReaction: contest.voteReaction
     };
@@ -389,13 +382,12 @@ async function onMessage(actionsDone: boolean, m: Message, content: string) {
 }
 
 async function onReact(reaction: MessageReaction, user: User) {
-    let channel = reaction.message.channel;
-
-    let activeContest = activeContests[channel.id];
-    if (!activeContest)
+    if(user.bot)
         return;
 
-    if (reaction.emoji.toString() != activeContest.voteReaction)
+    let channel = reaction.message.channel;
+    let activeContest = activeContests[channel.id];
+    if (!activeContest || reaction.emoji.toString() != activeContest.voteReaction)
         return;
 
     let entryRepo = getRepository(ContestEntry);
@@ -404,26 +396,14 @@ async function onReact(reaction: MessageReaction, user: User) {
     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
-            }
-        }
+        where: { userId: user.id, contestId: activeContest.id }
     });
-
     if (!vote)
-        vote = voteRepo.create({
-            userId: user.id,
-            contest: {
-                id: activeContest.id
-            }
-        });
+        vote = voteRepo.create({ userId: user.id, contestId: activeContest.id });
 
     vote.contestEntry = entry;
     await voteRepo.save(vote);
@@ -447,7 +427,18 @@ export default <ICommand>{
         {
             pattern: "contests",
             action: async (m) => {
-                await m.channel.send("Heck");
+                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")}`);
             }
         },
         {
@@ -481,8 +472,25 @@ export default <ICommand>{
         },
         {
             pattern: "announce winners",
-            action: async (m, contents, matches) => {
-                await m.channel.send("Heck");
+            action: async (m, contents) => {
+                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 stopContest(contest.id);
+                    if(contest.announceWinners)
+                        return;
+                }
+                await printResults(contest, m.channel as TextChannel);
             }
         }
     ],

+ 4 - 1
db/src/entity/ContestVote.ts

@@ -1,6 +1,6 @@
 import { Contest } from "./Contest";
 import { ContestEntry } from "./ContestEntry";
-import { Entity, PrimaryColumn, ManyToOne } from "typeorm";
+import { Entity, PrimaryColumn, ManyToOne, Column } from "typeorm";
 
 @Entity()
 export class ContestVote {
@@ -8,6 +8,9 @@ export class ContestVote {
     @PrimaryColumn()
     userId: string;
 
+    @PrimaryColumn()
+    contestId: number;
+
     @ManyToOne(type => Contest, contest => contest.votes, { primary: true })
     contest: Contest;