Skip to content

Commit 6d4ae93

Browse files
lorenzocoralloCopilotcoderabbitai[bot]toto04
authored
refactor: new moderation stack (#42)
* chore: just for fun * refactor: move delete perform outside of tgLogger * refactor: move moderation functions inside a Moderation obj * refactor: moderation class * feat: getUser function, use Moderation in tgLogger menus * fix: some revision * fix: typo and wrong field in multiChatSpam Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: handle API call error in getUser * feat: support retrieving user from memoryStorage In the rare case when we need to get an user by userId, the context cannot retrieve it and the user is yet to be synced to the backend, we can retrieve it from the temporary map. * fix: new getUser return type * feat: handle preDel abort, better Moderation errors * fix: missing moderator in moderationAction log * fix: command /del not using the new Moderation method * feat: delete preLogs when forwarded but not deleted message(s) * fix: username arg with custom type, pass ctx to getUser * fix: getUser ctx undefined propagation in optional chaining * fix: typo Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: typos * refactor: moved UI actions middleware in new moderation stack * docs: todoooooooo * fix: use moderation stack in `group-specific-actions` * refactor: types --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Tommaso Morganti <tommaso.morganti01@gmail.com>
1 parent 0c78ee3 commit 6d4ae93

24 files changed

Lines changed: 683 additions & 586 deletions

TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
- [x] /report to allow user to report (@admin is not implemented)
2121
- [x] track ban, mute and kick done via telegram UI (not by command)
2222
- [ ] controlled moderation flow (see #42)
23-
- [ ] audit log (implemented, need to audit every mod action)
23+
- [x] audit log (implemented, need to audit every mod action)
2424
- [ ] send in-chat action log (deprived of chat ids and stuff)
2525
- [x] automatic moderation
2626
- [x] delete non-latin alphabet characters

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"@grammyjs/menu": "^1.3.1",
3838
"@grammyjs/parse-mode": "^1.11.1",
3939
"@grammyjs/runner": "^2.0.3",
40-
"@polinetwork/backend": "^0.15.2",
40+
"@polinetwork/backend": "^0.15.3",
4141
"@t3-oss/env-core": "^0.13.4",
4242
"@trpc/client": "^11.5.1",
4343
"@types/ssdeep.js": "^0.0.2",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/bot.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import { checkUsername } from "./middlewares/check-username"
1515
import { GroupSpecificActions } from "./middlewares/group-specific-actions"
1616
import { messageLink } from "./middlewares/message-link"
1717
import { MessageUserStorage } from "./middlewares/message-user-storage"
18-
import { UIActionsLogger } from "./middlewares/ui-actions-logger"
1918
import { modules, sharedDataInit } from "./modules"
19+
import { Moderation } from "./modules/moderation"
2020
import { redis } from "./redis"
2121
import { once } from "./utils/once"
2222
import { setTelegramId } from "./utils/telegram-id"
@@ -78,7 +78,7 @@ bot.use(commands)
7878
bot.use(new BotMembershipHandler())
7979
bot.use(new AutoModerationStack())
8080
bot.use(new GroupSpecificActions())
81-
bot.use(new UIActionsLogger())
81+
bot.use(Moderation)
8282

8383
bot.on("message", async (ctx, next) => {
8484
const { username, id } = ctx.message.from

src/commands/ban.ts

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { logger } from "@/logger"
2-
import { ban, unban } from "@/modules/moderation"
2+
import { Moderation } from "@/modules/moderation"
33
import { duration } from "@/utils/duration"
44
import { fmt } from "@/utils/format"
55
import { getTelegramId } from "@/utils/telegram-id"
6+
import { numberOrString } from "@/utils/types"
7+
import { getUser } from "@/utils/users"
68
import { wait } from "@/utils/wait"
7-
89
import { _commandsBase } from "./_base"
910

1011
_commandsBase
@@ -25,22 +26,10 @@ _commandsBase
2526
return
2627
}
2728

28-
const res = await ban({
29-
ctx: context,
30-
target: repliedTo.from,
31-
from: context.from,
32-
message: repliedTo,
33-
reason: args.reason,
34-
})
35-
36-
if (res.isErr()) {
37-
const msg = await context.reply(res.error)
38-
await wait(5000)
39-
await msg.delete()
40-
return
41-
}
42-
43-
await context.reply(res.value)
29+
const res = await Moderation.ban(repliedTo.from, context.chat, context.from, null, [repliedTo], args.reason)
30+
const msg = await context.reply(res.isErr() ? res.error.fmtError : "OK")
31+
await wait(5000)
32+
await msg.delete()
4433
},
4534
})
4635
.createCommand({
@@ -68,28 +57,22 @@ _commandsBase
6857
return
6958
}
7059

71-
const res = await ban({
72-
ctx: context,
73-
target: repliedTo.from,
74-
from: context.from,
75-
message: repliedTo,
76-
duration: args.duration,
77-
reason: args.reason,
78-
})
79-
80-
if (res.isErr()) {
81-
const msg = await context.reply(res.error)
82-
await wait(5000)
83-
await msg.delete()
84-
return
85-
}
86-
87-
await context.reply(res.value)
60+
const res = await Moderation.ban(
61+
repliedTo.from,
62+
context.chat,
63+
context.from,
64+
args.duration,
65+
[repliedTo],
66+
args.reason
67+
)
68+
const msg = await context.reply(res.isErr() ? res.error.fmtError : "OK")
69+
await wait(5000)
70+
await msg.delete()
8871
},
8972
})
9073
.createCommand({
9174
trigger: "unban",
92-
args: [{ key: "username", optional: false, description: "Username (or user id) to unban" }],
75+
args: [{ key: "username", type: numberOrString, description: "Username (or user id) to unban" }],
9376
description: "Unban a user from a group",
9477
scope: "group",
9578
permissions: {
@@ -98,7 +81,9 @@ _commandsBase
9881
},
9982
handler: async ({ args, context }) => {
10083
await context.deleteMessage()
101-
const userId = args.username.startsWith("@") ? await getTelegramId(args.username) : parseInt(args.username, 10)
84+
const userId: number | null =
85+
typeof args.username === "string" ? await getTelegramId(args.username.replaceAll("@", "")) : args.username
86+
10287
if (!userId) {
10388
logger.debug(`unban: no userId for username ${args.username}`)
10489
const msg = await context.reply(fmt(({ b }) => b`@${context.from.username} user not found`))
@@ -107,12 +92,18 @@ _commandsBase
10792
return
10893
}
10994

110-
const res = await unban({ ctx: context, from: context.from, targetId: userId })
111-
if (res.isErr()) {
112-
const msg = await context.reply(res.error)
95+
const user = await getUser(userId, context)
96+
if (!user) {
97+
const msg = await context.reply("Error: cannot find this user")
98+
logger.error({ userId }, "UNBAN: cannot retrieve the user")
11399
await wait(5000)
114100
await msg.delete()
115101
return
116102
}
103+
104+
const res = await Moderation.unban(user, context.chat, context.from)
105+
const msg = await context.reply(res.isErr() ? res.error.fmtError : "OK")
106+
await wait(5000)
107+
await msg.delete()
117108
},
118109
})

src/commands/del.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { logger } from "@/logger"
2-
import { modules } from "@/modules"
2+
import { Moderation } from "@/modules/moderation"
33
import { getText } from "@/utils/messages"
4+
import { wait } from "@/utils/wait"
45
import { _commandsBase } from "./_base"
56

67
_commandsBase.createCommand({
@@ -13,6 +14,7 @@ _commandsBase.createCommand({
1314
description: "Deletes the replied to message",
1415
reply: "required",
1516
handler: async ({ repliedTo, context }) => {
17+
await context.deleteMessage()
1618
const { text, type } = getText(repliedTo)
1719
logger.info({
1820
action: "delete_message",
@@ -21,7 +23,10 @@ _commandsBase.createCommand({
2123
sender: repliedTo.from?.username,
2224
})
2325

24-
await modules.get("tgLogger").delete([repliedTo], "Command /del", context.from) // actual message to delete
25-
await context.deleteMessage() // /del message
26+
const res = await Moderation.deleteMessages([repliedTo], context.from, "Command /del")
27+
// TODO: better error and ok response
28+
const msg = await context.reply(res.isErr() ? "Cannot delete the message" : "OK")
29+
await wait(5000)
30+
await msg.delete()
2631
},
2732
})

src/commands/kick.ts

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { logger } from "@/logger"
2-
import { kick } from "@/modules/moderation"
2+
import { Moderation } from "@/modules/moderation"
33
import { wait } from "@/utils/wait"
44

55
import { _commandsBase } from "./_base"
@@ -21,20 +21,9 @@ _commandsBase.createCommand({
2121
return
2222
}
2323

24-
const res = await kick({
25-
ctx: context,
26-
target: repliedTo.from,
27-
from: context.from,
28-
message: repliedTo,
29-
reason: args.reason,
30-
})
31-
if (res.isErr()) {
32-
const msg = await context.reply(res.error)
33-
await wait(5000)
34-
await msg.delete()
35-
return
36-
}
37-
38-
await context.reply(res.value)
24+
const res = await Moderation.kick(repliedTo.from, context.chat, context.from, [repliedTo], args.reason)
25+
const msg = await context.reply(res.isErr() ? res.error.fmtError : "OK")
26+
await wait(5000)
27+
await msg.delete()
3928
},
4029
})

src/commands/mute.ts

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { logger } from "@/logger"
2-
import { mute, unmute } from "@/modules/moderation"
2+
import { Moderation } from "@/modules/moderation"
33
import { duration } from "@/utils/duration"
44
import { fmt } from "@/utils/format"
55
import { getTelegramId } from "@/utils/telegram-id"
6+
import { numberOrString } from "@/utils/types"
7+
import { getUser } from "@/utils/users"
68
import { wait } from "@/utils/wait"
7-
89
import { _commandsBase } from "./_base"
910

1011
_commandsBase
@@ -33,21 +34,17 @@ _commandsBase
3334
return
3435
}
3536

36-
const res = await mute({
37-
ctx: context,
38-
target: repliedTo.from,
39-
message: repliedTo,
40-
from: context.from,
41-
duration: args.duration,
42-
reason: args.reason,
43-
})
44-
45-
if (res.isErr()) {
46-
const msg = await context.reply(res.error)
47-
await wait(5000)
48-
await msg.delete()
49-
return
50-
}
37+
const res = await Moderation.mute(
38+
repliedTo.from,
39+
context.chat,
40+
context.from,
41+
args.duration,
42+
[repliedTo],
43+
args.reason
44+
)
45+
const msg = await context.reply(res.isErr() ? res.error.fmtError : "OK")
46+
await wait(5000)
47+
await msg.delete()
5148
},
5249
})
5350
.createCommand({
@@ -67,25 +64,15 @@ _commandsBase
6764
return
6865
}
6966

70-
const res = await mute({
71-
ctx: context,
72-
target: repliedTo.from,
73-
message: repliedTo,
74-
from: context.from,
75-
reason: args.reason,
76-
})
77-
78-
if (res.isErr()) {
79-
const msg = await context.reply(res.error)
80-
await wait(5000)
81-
await msg.delete()
82-
return
83-
}
67+
const res = await Moderation.mute(repliedTo.from, context.chat, context.from, null, [repliedTo], args.reason)
68+
const msg = await context.reply(res.isErr() ? res.error.fmtError : "OK")
69+
await wait(5000)
70+
await msg.delete()
8471
},
8572
})
8673
.createCommand({
8774
trigger: "unmute",
88-
args: [{ key: "username", optional: false, description: "Username (or user id) to unmute" }],
75+
args: [{ key: "username", type: numberOrString, description: "Username (or user id) to unmute" }],
8976
description: "Unmute a user from a group",
9077
scope: "group",
9178
permissions: {
@@ -94,7 +81,8 @@ _commandsBase
9481
},
9582
handler: async ({ args, context }) => {
9683
await context.deleteMessage()
97-
const userId = args.username.startsWith("@") ? await getTelegramId(args.username) : parseInt(args.username, 10)
84+
const userId: number | null =
85+
typeof args.username === "string" ? await getTelegramId(args.username.replaceAll("@", "")) : args.username
9886
if (!userId) {
9987
logger.debug(`unmute: no userId for username ${args.username}`)
10088
const msg = await context.reply(fmt(({ b }) => b`@${context.from.username} user not found`))
@@ -103,12 +91,18 @@ _commandsBase
10391
return
10492
}
10593

106-
const res = await unmute({ ctx: context, from: context.from, targetId: userId })
107-
if (res.isErr()) {
108-
const msg = await context.reply(res.error)
94+
const user = await getUser(userId, context)
95+
if (!user) {
96+
const msg = await context.reply("Error: cannot find this user")
97+
logger.error({ userId }, "UNMUTE: cannot retrieve the user")
10998
await wait(5000)
11099
await msg.delete()
111100
return
112101
}
102+
103+
const res = await Moderation.unmute(user, context.chat, context.from)
104+
const msg = await context.reply(res.isErr() ? res.error.fmtError : "OK")
105+
await wait(5000)
106+
await msg.delete()
113107
},
114108
})

0 commit comments

Comments
 (0)