diff --git a/src/bots/Reputation.test.ts b/src/bots/Reputation.test.ts index e73948f..7646fa0 100644 --- a/src/bots/Reputation.test.ts +++ b/src/bots/Reputation.test.ts @@ -589,6 +589,78 @@ describe('ReputationBot', () => { assertBotSaid(messages, /.*?/); await assert(triggerMessage); }); + + // Testing when user is mention + it('when user mentions another user with "boo"', async () => { + await createUserInDb(); + const triggerMessage = createTgTextMessage('@omar_alfaruq boo', { + chat: thisChat, + from: senderUser, + entities: [ + { + type: 'mention', + offset: 0, + length: 13, + }, + ], + }); + + const messages = await runBot([ScriberBot, ReputationBot], ({ sendMessage }) => { + sendMessage(mainMessage); + sendMessage(triggerMessage); + }); + + assertBotSaid(messages, /decreased reputation/); + await assert({ + ...triggerMessage, + reply_to_message: { + from: { + id: 2, + username: 'omar_alfaruq', + }, + }, + }); + }); + + // Testing when user is mention (text_mention -> when user has no username) + // https://core.telegram.org/bots/api#messageentity + it('when user mentions another user with "boo"', async () => { + await createUserInDb(); + const triggerMessage = createTgTextMessage('@omar_alfaruq boo', { + chat: thisChat, + from: senderUser, + entities: [ + { + type: 'text_mention', + offset: 0, + length: 13, + user: { + id: 2, + is_bot: false, + first_name: 'Omar', + last_name: 'Al-Faruq', + username: 'omar_alfaruq', + }, + }, + ], + }); + + const messages = await runBot([ScriberBot, ReputationBot], ({ sendMessage }) => { + sendMessage(mainMessage); + sendMessage(triggerMessage); + }); + + assertBotSaid(messages, /decreased reputation/); + await assert({ + ...triggerMessage, + reply_to_message: { + from: { + id: 2, + username: 'omar_alfaruq', + }, + }, + }); + }); }); describe('does not change reputation', () => { @@ -809,6 +881,38 @@ describe('ReputationBot', () => { assertBotSaid(messages, 'Tag only one user at a time to increase rep!'); await assert(0); }); + + it('when user mentions multiple users with "boo"', async () => { + const mainMessage = createTgTextMessage('Is it your new PC?', { + chat: thisChat, + from: recipientUser, + reply_to_message: undefined, + }); + const triggerMessage = createTgTextMessage('@omar_alfaruq @abu_bakr boo', { + chat: thisChat, + from: senderUser, + entities: [ + { + type: 'mention', + offset: 0, + length: 13, + }, + { + type: 'mention', + offset: 0, + length: 9, + }, + ], + }); + + const messages = await runBot([ScriberBot, ReputationBot], ({ sendMessage }) => { + sendMessage(mainMessage); + sendMessage(triggerMessage); + }); + + assertBotSaid(messages, 'Tag only one user at a time to increase rep!'); + await assert(0); + }); }); }); diff --git a/src/bots/Reputation.ts b/src/bots/Reputation.ts index 7bd1a0b..034210f 100644 --- a/src/bots/Reputation.ts +++ b/src/bots/Reputation.ts @@ -11,7 +11,7 @@ import { MsocietyBotContext } from '../context'; const bot = new Composer(); -async function vote(ctx: MsocietyBotContext, sender: TelegramUser, recipient: TelegramUser) { +async function upvote(ctx: MsocietyBotContext, sender: TelegramUser, recipient: TelegramUser) { const canVote = await Reputation.isAllowedToVote(ctx.entityManager, sender); // Can't vote for yourself :) @@ -49,11 +49,45 @@ async function vote(ctx: MsocietyBotContext, sender: TelegramUser, recipient: Te } } +async function downvote(ctx: MsocietyBotContext, sender: TelegramUser, recipient: TelegramUser) { + const canVote = await Reputation.isAllowedToVote(ctx.entityManager, sender); + + // Can't vote for yourself :) + if (recipient.id === sender.id) { + await ctx.replyWithMarkdown('Are you _ok_?', { reply_to_message_id: ctx.message.message_id }); + } + // Can't vote for the bot. + else if (recipient.id === ctx.botInfo.id) { + // eslint-disable-next-line quotes + await ctx.reply('🙂 Excuse me?', { + reply_to_message_id: ctx.message.message_id, + }); + } + // Does user have enough votes in their quota to give out? + else if (!canVote) { + const { nextVote } = await Reputation.getVoteQuota(ctx.entityManager, ctx.message.from); + + await ctx.replyWithMarkdown( + `You have already used up your vote quota of ${voteQuota} for the past ${voteQuotaDuration} hours. Please try again later!\nYou will receive a new vote in *${nextVote.hours} hours* and *${nextVote.minutes} minutes*`, + { reply_to_message_id: ctx.message.message_id }, + ); + } + // Can't vote for bots + else if (!recipient.is_bot && canVote) { + await insertReputation(ctx.entityManager, sender, recipient, ctx.chat, ctx.message, defaultVoteValue * -1); + const senderRep = await Reputation.getLocalReputation(ctx.entityManager, sender, ctx.chat); + const recipientRep = await Reputation.getLocalReputation(ctx.entityManager, recipient, ctx.chat); + await ctx.replyWithMarkdown( + `*${sender.first_name}* (${senderRep}) has decreased reputation of *${recipient.first_name}* (${recipientRep})`, + { reply_to_message_id: ctx.message.message_id }, + ); + } +} bot.hears(/thank you|thanks|👍|💯|👆|🆙|🔥/i, async ctx => { const mentionEntities = ctx.message?.entities?.filter(entity => ['mention', 'text_mention'].includes(entity.type)) || []; if (ctx.message.reply_to_message !== undefined) { - await vote(ctx, ctx.from, ctx.message.reply_to_message.from); + await upvote(ctx, ctx.from, ctx.message.reply_to_message.from); } else if ( // check if there is a mention mentionEntities.length @@ -80,48 +114,47 @@ bot.hears(/thank you|thanks|👍|💯|👆|🆙|🔥/i, async ctx => { return; } const recipient = await ctx.getChatMember(+recipientId); - await vote(ctx, ctx.message.from, recipient.user); + await upvote(ctx, ctx.message.from, recipient.user); } else if (entity.type === 'text_mention') { - await vote(ctx, ctx.message.from, entity.user); + await upvote(ctx, ctx.message.from, entity.user); } } }); bot.hears(/👎|👇|🔽|\bboo(o*)\b|\beww(w*)\b/i, async ctx => { + const mentionEntities = + ctx.message?.entities?.filter(entity => ['mention', 'text_mention'].includes(entity.type)) || []; if (ctx.message.reply_to_message !== undefined) { - const sender = ctx.message.from; - const recipient = ctx.message.reply_to_message.from; - const canVote = await Reputation.isAllowedToVote(ctx.entityManager, sender); - - // Can't vote for yourself :) - if (recipient.id === sender.id) { - await ctx.replyWithMarkdown('Are you _ok_?', { reply_to_message_id: ctx.message.message_id }); - } - // Can't vote for the bot. - else if (recipient.id === ctx.botInfo.id) { - // eslint-disable-next-line quotes - await ctx.reply('🙂 Excuse me?', { + await downvote(ctx, ctx.from, ctx.message.reply_to_message.from); + } else if ( + // check if there is a mention + mentionEntities.length + ) { + if (mentionEntities.length > 1) { + await ctx.reply('Tag only one user at a time to increase rep!', { reply_to_message_id: ctx.message.message_id, }); + return; } - // Does user have enough votes in their quota to give out? - else if (!canVote) { - const { nextVote } = await Reputation.getVoteQuota(ctx.entityManager, ctx.message.from); + // get the mention metadata + const entity = mentionEntities[0]; - await ctx.replyWithMarkdown( - `You have already used up your vote quota of ${voteQuota} for the past ${voteQuotaDuration} hours. Please try again later!\nYou will receive a new vote in *${nextVote.hours} hours* and *${nextVote.minutes} minutes*`, - { reply_to_message_id: ctx.message.message_id }, - ); - } - // Can't vote for bots - else if (!recipient.is_bot && canVote) { - await insertReputation(ctx.entityManager, sender, recipient, ctx.chat, ctx.message, defaultVoteValue * -1); - const senderRep = await Reputation.getLocalReputation(ctx.entityManager, sender, ctx.chat); - const recipientRep = await Reputation.getLocalReputation(ctx.entityManager, recipient, ctx.chat); - await ctx.replyWithMarkdown( - `*${sender.first_name}* (${senderRep}) has decreased reputation of *${recipient.first_name}* (${recipientRep})`, - { reply_to_message_id: ctx.message.message_id }, - ); + if (entity.type === 'mention') { + //get username from text using metadata + const username = ctx.message.text.substr(entity.offset + 1, entity.length - 1); + // telegram api doesn't provide resolving user by username so we depend on our db to get userID + const recipientId = (await User.getUserByUsername(ctx.entityManager, username))?.id; + if (!recipientId) { + // eslint-disable-next-line quotes + await ctx.reply("Can't find user in DB. Did they change their username?", { + reply_to_message_id: ctx.message.message_id, + }); + return; + } + const recipient = await ctx.getChatMember(+recipientId); + await downvote(ctx, ctx.message.from, recipient.user); + } else if (entity.type === 'text_mention') { + await downvote(ctx, ctx.message.from, entity.user); } } });