From 3d46b3fbccd7d49d4a53d51e9e84178a586df243 Mon Sep 17 00:00:00 2001 From: Liesel Downes Date: Thu, 29 Jun 2023 22:14:49 +0930 Subject: [PATCH] Check division commands and import command --- src/commands/Help.ts | 2 +- src/commands/Members.ts | 27 +++++--- src/commands/Whip.ts | 114 +++++++++++++++++++++++++++++++- src/entity/Division.ts | 2 +- src/reddit/Subreddit.ts | 7 +- src/reddit/Thread.ts | 28 ++++++++ src/reddit/VoteComment.ts | 10 ++- src/utilities/ImportUsername.ts | 3 + 8 files changed, 177 insertions(+), 16 deletions(-) create mode 100644 src/utilities/ImportUsername.ts diff --git a/src/commands/Help.ts b/src/commands/Help.ts index 00a545c..b26a9c0 100644 --- a/src/commands/Help.ts +++ b/src/commands/Help.ts @@ -26,6 +26,6 @@ export class HelpCommand extends Subcommand { } public async chatInputImportFormula(interaction: Subcommand.ChatInputCommandInteraction) { - await interaction.reply('```clean\n=ArrayFormula(concatenate("[", join(", ", char(34)&B5:B54&char(34)), "]"))\n```'); + await interaction.reply('```=JOIN(",", B5:B54)```'); } } \ No newline at end of file diff --git a/src/commands/Members.ts b/src/commands/Members.ts index 426591f..f895a06 100644 --- a/src/commands/Members.ts +++ b/src/commands/Members.ts @@ -102,11 +102,11 @@ export class MembersCommand extends Subcommand { .addSubcommand((command) => command .setName('import') - .setDescription('Import MPs from JSON list (provided on whip sheet), /help import-formula') + .setDescription('Import MPs from list (provided on whip sheet), /help import-formula') .addStringOption((option) => option - .setName('json') - .setDescription('The JSON input') + .setName('input') + .setDescription('The comma delimited input') .setRequired(true) ) ), @@ -205,16 +205,25 @@ export class MembersCommand extends Subcommand { } public async chatInputImport(interaction: Subcommand.ChatInputCommandInteraction) { - const input = interaction.options.getString('json'); - if (!input || !isValidJson(input)) { - await interaction.reply({ content: 'Please provide valid JSON.' }); + const input = interaction.options.getString('input'); + if (!input) { + await interaction.reply({ content: 'Please provide valid input.' }); return; } await interaction.deferReply(); - - console.log(input); - await interaction.editReply({ content: 'Test' }); + const memberUsernames = (await this.memberRepository.find()).map(member => member.redditUsername); + var arr = input.split(/[ ,]+/); + arr = arr.filter(entry => entry.trim() != ''); + const usernames = arr.filter(entry => !memberUsernames.includes(entry)); + for (const i in usernames) { + const member = new Member(); + member.redditUsername = usernames[i]; + member.sendDiscordReminders = false; + await this.memberRepository.save(member); + } + + await interaction.editReply({ content: `Added ${arr.length} members excluding duplicates.` }); } private formatReminderChannelsString(member: Member) { diff --git a/src/commands/Whip.ts b/src/commands/Whip.ts index c51a1f6..9ee63a8 100644 --- a/src/commands/Whip.ts +++ b/src/commands/Whip.ts @@ -3,6 +3,11 @@ import { AppDataSource } from "../data-source"; import { Division } from "../entity/Division"; import { whipIssueChannelId, memberRoleId } from "../whipConfig.json"; import { ChannelType, EmbedBuilder, TextChannel } from "discord.js"; +import { fetchThread } from "../reddit/Subreddit"; +import { ValidVotes } from "../enums/ValidVotes"; +import { Thread } from "../reddit/Thread"; +import { formatDate } from "../utilities/Formatters"; +import { MoreThan } from "typeorm"; export class WhipCommand extends Subcommand { private divisionsRepository = AppDataSource.getRepository(Division); @@ -13,7 +18,9 @@ export class WhipCommand extends Subcommand { name: 'whip', description: 'Whip commands', subcommands: [ - { name: 'issue', chatInputRun: 'chatInputIssue' } + { name: 'issue', chatInputRun: 'chatInputIssue' }, + { name: 'check', chatInputRun: 'chatInputCheck' }, + { name: 'check-active', chatInputRun: 'chatInputCheckActive' }, ] }); } @@ -42,11 +49,61 @@ export class WhipCommand extends Subcommand { .addStringOption(option => option.setName('notes').setDescription('Notes displayed alongside').setRequired(false) ) + ) + .addSubcommand(command => + command + .setName('check') + .setDescription('Check the status of a division') + .addStringOption(option => + option.setName('division_id').setDescription('Division ID').setAutocomplete(true).setRequired(true) + ) + ) + .addSubcommand(command => + command + .setName('check-active') + .setDescription('Check the status of all active divisions') ), { idHints: ['1116387569484181524'] } ); } + private async createCheckEmbed(division: Division, thread: Thread) { + const embed = new EmbedBuilder() + .setTitle(`Status of ${division.shortName}`) + .setDescription(`${division.directive} - ends ${formatDate(division.closesAt)}`) + .setURL(division.url) + .setFooter({ text: division.longName }); + + + console.log('Ayes'+ thread.getAyes()); + let ayeField = ''; + let noField = ''; + let abstainField = ''; + let noVoteField = ''; + thread.getAyes().forEach(username => { + ayeField += `${username} \n`; + }); + thread.getNoes().forEach(username => { + noField += `${username} \n`; + }); + thread.getAbstentions().forEach(username => { + abstainField += `${username} \n`; + }); + const notVoted = await thread.getMembersNotVoted() + notVoted.forEach(username => { + noVoteField += `${username} \n`; + }); + + embed.addFields( + { name: 'Aye votes', value: ayeField != '' ? ayeField : 'None recorded.' }, + { name: 'No votes', value: noField != '' ? noField : 'None recorded.' }, + { name: 'Abstain votes', value: abstainField != '' ? abstainField : 'None recorded.' }, + { name: 'No vote recorded', value: noVoteField != '' ? noVoteField : 'None recorded.' }, + ); + + return embed; + } + public async chatInputIssue(interaction: Subcommand.ChatInputCommandInteraction) { await interaction.deferReply(); @@ -92,4 +149,59 @@ export class WhipCommand extends Subcommand { }); await interaction.editReply({ content: `Whips issued in <#${channel.id}>` }); } + + public async chatInputCheck(interaction: Subcommand.ChatInputCommandInteraction) { + await interaction.deferReply(); + + const divisionId = interaction.options.getString('division_id'); + if (!divisionId) { + await interaction.editReply({ content: 'Please provide a division ID.' }); + return; + } + const division = await this.divisionsRepository.findOneBy({ shortName: divisionId }); + if (!division) { + await interaction.editReply({ content: 'Please provide a valid division ID.' }); + return; + } + const thread = await fetchThread(division.url); + if (!thread) { + await interaction.reply({ content: 'Error. Re-add the division.' }); + return; + } + + const responseEmbed = await this.createCheckEmbed(division, thread); + + await interaction.editReply({ embeds: [responseEmbed] }); + } + + public async chatInputCheckActive(interaction: Subcommand.ChatInputCommandInteraction) { + await interaction.deferReply(); + + const divisions = await this.divisionsRepository.find({ + where: { + closesAt: MoreThan(new Date()), + }, + order: { + closesAt: 'ASC' + } + }) + if (divisions.length == 0) { + interaction.editReply('No divisions found.'); + return; + } + + const embeds = []; + for (const division of divisions) { + const thread = await fetchThread(division.url); + if (!thread) { + await interaction.reply({ content: `Error. Re-add the division ${division.shortName}.` }); + return; + } + const responseEmbed = await this.createCheckEmbed(division, thread); + embeds.push(responseEmbed); + } + + + await interaction.editReply({ embeds: embeds }); + } } \ No newline at end of file diff --git a/src/entity/Division.ts b/src/entity/Division.ts index c4294b1..817a85c 100644 --- a/src/entity/Division.ts +++ b/src/entity/Division.ts @@ -58,7 +58,7 @@ export class Division { public get directive(): string { if (this.freeVote) return "Free Vote"; - else return `${this.whipLine} line ${this.whipVote.toUpperCase()}` + else return `${this.whipLine} Line ${this.whipVote.toUpperCase()}` } public get whipEmbed(): EmbedBuilder { diff --git a/src/reddit/Subreddit.ts b/src/reddit/Subreddit.ts index b78ac7a..230e507 100644 --- a/src/reddit/Subreddit.ts +++ b/src/reddit/Subreddit.ts @@ -6,11 +6,15 @@ import { Thread } from "./Thread"; import { container } from "@sapphire/framework"; import { VoteComment } from "./VoteComment"; import { commentToVoteEnum } from "../utilities/Formatters"; +import { AppDataSource } from "../data-source"; +import { Member } from "../entity/Member"; const ignore: string[] = [ 'AutoModerator' ]; +const membersRepository = AppDataSource.getRepository(Member); + const api = new Snoowrap({ userAgent: 'Solidarity Whip Bot version 2.0.0', clientId: process.env.REDDIT_CLIENT_ID, @@ -32,6 +36,7 @@ export async function fetchThread(url: string) { const thread = new Thread(); thread.id = id; thread.url = url; + const memberUsernames = (await membersRepository.find()).map(member => member.redditUsername); // Fill title and comments try { @@ -48,7 +53,7 @@ export async function fetchThread(url: string) { // Comments thread.comments = []; response.comments.forEach(Comment => { - if (ignore.includes(Comment.author.name)) { + if ((ignore.includes(Comment.author.name)) || !(memberUsernames.includes(Comment.author.name))) { return; } const voteEnum = commentToVoteEnum(Comment.body); diff --git a/src/reddit/Thread.ts b/src/reddit/Thread.ts index cbdc1a4..e35d280 100644 --- a/src/reddit/Thread.ts +++ b/src/reddit/Thread.ts @@ -1,5 +1,10 @@ +import { AppDataSource } from "../data-source"; +import { Member } from "../entity/Member"; +import { ValidVotes } from "../enums/ValidVotes"; import { VoteComment } from "./VoteComment"; +const membersRepository = AppDataSource.getRepository(Member); + export class Thread { id: string; shortName: string; @@ -20,4 +25,27 @@ export class Thread { // url = url; // comments = comments; // } + + public getCommentsByVote(whip: ValidVotes) { + return this.comments.filter((vote) => vote.vote == whip).map(vote => vote.username); + } + + public getAyes(): string[] { + return this.getCommentsByVote(ValidVotes.Aye); + } + + public getNoes(): string[] { + return this.getCommentsByVote(ValidVotes.No); + } + + public getAbstentions(): string[] { + return this.getCommentsByVote(ValidVotes.Abstain); + } + + public async getMembersNotVoted() { + const memberUsernames = (await membersRepository.find()).map(member => member.redditUsername); + const haveVoted = this.comments.map(comment => comment.username); + const notVoted = memberUsernames.filter(username => !haveVoted.some(voted => username === voted)); + return notVoted; + } } \ No newline at end of file diff --git a/src/reddit/VoteComment.ts b/src/reddit/VoteComment.ts index c203909..db46ab2 100644 --- a/src/reddit/VoteComment.ts +++ b/src/reddit/VoteComment.ts @@ -1,7 +1,11 @@ import { ValidVotes } from "../enums/ValidVotes"; export class VoteComment { - constructor( - username: string, vote: ValidVotes - ) {} + public username: string; + public vote: ValidVotes; + + public constructor(username: string, vote: ValidVotes) { + this.username = username; + this.vote = vote; + } } \ No newline at end of file diff --git a/src/utilities/ImportUsername.ts b/src/utilities/ImportUsername.ts new file mode 100644 index 0000000..411789e --- /dev/null +++ b/src/utilities/ImportUsername.ts @@ -0,0 +1,3 @@ +interface ImportUsername { + username: string +} \ No newline at end of file