Skip to content

Commit 8fcfea6

Browse files
committed
Add /permit, closes #72
1 parent e54719a commit 8fcfea6

File tree

7 files changed

+92
-7
lines changed

7 files changed

+92
-7
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Command | Role | Available at | Description
5656
`/warn <reason>` | _Admin_ | _Groups_ | Warns the user.
5757
`/unwarn` | _Admin_ | _Everywhere_ | Removes the last warn from the user.
5858
`/nowarns` | _Admin_ | _Everywhere_ | Clears warns for the user.
59+
`/permit` | _Admin_ | _Everywhere_ | Permits the user to advertise once, within 24 hours.
5960
`/ban <reason>` | _Admin_ | _Groups_ | Bans the user from groups.
6061
`/unban` | _Admin_ | _Everywhere_ | Removes the user from ban list.
6162
`/user` | _Admin_ | _Everywhere_ | Shows the status of the user.

handlers/commands/commands.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const adminCommands = `\
2525
<code>/warn &lt;reason&gt;</code> - Warns the user.
2626
<code>/unwarn</code> - Removes the last warn from the user.
2727
<code>/nowarns</code> - Clears warns for the user.
28+
<code>/permit</code> - Permits the user to advertise once, within 24 hours.
2829
<code>/ban &lt;reason&gt;</code> - Bans the user from groups.
2930
<code>/unban</code> - Removes the user from ban list.
3031
<code>/user</code> - Shows user's status and warns.

handlers/commands/permit.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { displayUser, scheduleDeletion } from "../../utils/tg";
2+
import { html, lrm } from "../../utils/html";
3+
import { parse, strip } from "../../utils/cmd";
4+
import type { ExtendedContext } from "../../typings/context";
5+
import { permit } from "../../stores/user";
6+
7+
export = async (ctx: ExtendedContext) => {
8+
if (ctx.from?.status !== "admin") return null;
9+
10+
const { targets } = parse(ctx.message);
11+
if (targets.length !== 1) {
12+
return ctx
13+
.replyWithHTML("ℹ️ <b>Specify one user to permit.</b>")
14+
.then(scheduleDeletion());
15+
}
16+
17+
const permitted = await permit(strip(targets[0]), {
18+
by_id: ctx.from.id,
19+
date: new Date(),
20+
});
21+
22+
return ctx.replyWithHTML(html`
23+
🎟 ${lrm}${ctx.from.first_name} <b>permitted</b> ${displayUser(permitted)} to
24+
promote once within the next 24 hours!
25+
`);
26+
};

handlers/commands/user.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const { isMaster, isWarnNotExpired } = require('../../utils/config');
99
const { parse, strip } = require('../../utils/cmd');
1010

1111
// DB
12-
const { getUser } = require('../../stores/user');
12+
const { getUser, permit } = require('../../stores/user');
1313

1414
/** @param {Date} date */
1515
const formatDate = date =>
@@ -100,9 +100,19 @@ const getWarnsHandler = async ({ from, message, replyWithHTML }) => {
100100
formatDate(theUser.createdAt),
101101
);
102102

103-
return replyWithHTML(TgHtml.join('\n\n', [
103+
const permitS = permit.isValid(theUser.permit)
104+
// eslint-disable-next-line max-len
105+
? `🎟 ${(await getUser({ id: theUser.permit.by_id })).first_name}, ${formatDate(theUser.permit.date)}`
106+
: '';
107+
108+
const oneliners = TgHtml.join('\n', [
104109
header,
105110
firstSeen,
111+
permitS,
112+
].filter(isNotEmpty));
113+
114+
return replyWithHTML(TgHtml.join('\n\n', [
115+
oneliners,
106116
userWarns,
107117
banReason,
108118
].filter(isNotEmpty))).then(scheduleDeletion());

handlers/middlewares/checkLinks.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
/* eslint new-cap: ["error", {"capIsNewExceptionPattern": "^(?:Action|jspack)\."}] */
22

33
import * as R from "ramda";
4+
import { html, lrm } from "../../utils/html";
5+
import { isAdmin, permit } from "../../stores/user";
46
import { config } from "../../utils/config";
57
import type { ExtendedContext } from "../../typings/context";
68
import fetch from "node-fetch";
7-
import { isAdmin } from "../../stores/user";
89
import { jspack } from "jspack";
910
import { managesGroup } from "../../stores/group";
1011
import type { MessageEntity } from "telegraf/typings/telegram-types";
@@ -223,6 +224,13 @@ export = async (ctx: ExtendedContext, next) => {
223224
if (userToWarn.id === 777000) return next();
224225
if (await isAdmin(userToWarn)) return next();
225226

227+
if (await permit.revoke(userToWarn)) {
228+
await ctx.replyWithHTML(html`
229+
${lrm}${userToWarn.first_name} used 🎟 permit!
230+
`);
231+
return next();
232+
}
233+
226234
ctx.deleteMessage().catch(() => null);
227235
return ctx.warn({
228236
admin: ctx.botInfo!,

handlers/middlewares/removeChannelForwards.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
const R = require('ramda');
44
const { optional, passThru } = require('telegraf');
55

6+
const { permit } = require('../../stores/user');
7+
8+
const { html, lrm } = require('../../utils/html');
69
const { excludeLinks = [] } = require('../../utils/config').config;
710

811
if (excludeLinks === false || excludeLinks === '*') {
9-
10-
/** @type { import('../../typings/context').GuardMiddlewareFn } */
1112
module.exports = passThru();
1213
return;
1314
}
@@ -46,7 +47,12 @@ const pred = R.allPass([
4647
]);
4748

4849
/** @param { import('../../typings/context').ExtendedContext } ctx */
49-
const handler = ctx => {
50+
const handler = async (ctx, next) => {
51+
if (await permit.revoke(ctx.from)) {
52+
await ctx.replyWithHTML(html`${lrm}${ctx.from.first_name} used 🎟 permit!`);
53+
return next();
54+
}
55+
5056
ctx.deleteMessage().catch(() => null);
5157
return ctx.warn({
5258
admin: ctx.botInfo,

stores/user.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
'use strict';
22

3+
/**
4+
* @typedef { { id: number } | { username: string } } UserQuery
5+
* @exports UserQuery
6+
*/
7+
38
// Utils
49
const { strip } = require('../utils/cmd');
510

611
const Datastore = require('nedb-promise');
12+
const ms = require('millisecond');
713
const R = require('ramda');
814

915
const User = new Datastore({
@@ -117,10 +123,36 @@ const unban = ({ id }) =>
117123
},
118124
);
119125

126+
/**
127+
* @param {UserQuery} user
128+
*/
129+
const permit = (user, { by_id, date }) =>
130+
User.update(
131+
user,
132+
{ $set: { permit: { by_id, date } } },
133+
{ returnUpdatedDocs: true },
134+
).then(getUpdatedDocument);
135+
136+
/**
137+
* @param {UserQuery} user
138+
*/
139+
permit.revoke = (user) =>
140+
User.update(
141+
{ permit: { $exists: true }, ...strip(user) },
142+
{ $unset: { permit: true } },
143+
{ returnUpdatedDocs: true },
144+
).then(getUpdatedDocument);
145+
146+
permit.isValid = (p) => Date.now() - ms('24h') < p?.date;
147+
120148
const warn = ({ id }, reason, { amend }) =>
121149
User.update(
122150
{ id, $not: { status: 'admin' } },
123-
{ $pop: { warns: +!!amend }, $push: { warns: reason } },
151+
{
152+
$pop: { warns: +!!amend },
153+
$push: { warns: reason },
154+
$unset: { permit: true },
155+
},
124156
{ returnUpdatedDocs: true },
125157
).then(getUpdatedDocument);
126158

@@ -145,6 +177,7 @@ module.exports = {
145177
getUser,
146178
isAdmin,
147179
nowarns,
180+
permit,
148181
unadmin,
149182
unban,
150183
unwarn,

0 commit comments

Comments
 (0)