feat: shoutbox replies
This commit is contained in:
parent
cafaf94e83
commit
421203ca3a
8 changed files with 144 additions and 3 deletions
1
drizzle/0002_neat_firedrake.sql
Normal file
1
drizzle/0002_neat_firedrake.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE `shouts` ADD `reply` text;
|
101
drizzle/meta/0002_snapshot.json
Normal file
101
drizzle/meta/0002_snapshot.json
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "7b42bd29-38af-477e-a68a-7e24f641a26a",
|
||||||
|
"prevId": "98471e1c-ba87-4757-bd0d-89bc45907686",
|
||||||
|
"tables": {
|
||||||
|
"shouts": {
|
||||||
|
"name": "shouts",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"serial": {
|
||||||
|
"name": "serial",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"from_ip": {
|
||||||
|
"name": "from_ip",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"pending": {
|
||||||
|
"name": "pending",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"name": "text",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(CURRENT_TIMESTAMP)"
|
||||||
|
},
|
||||||
|
"reply": {
|
||||||
|
"name": "reply",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
},
|
||||||
|
"shouts_bans": {
|
||||||
|
"name": "shouts_bans",
|
||||||
|
"columns": {
|
||||||
|
"ip": {
|
||||||
|
"name": "ip",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires": {
|
||||||
|
"name": "expires",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,13 @@
|
||||||
"when": 1722973085867,
|
"when": 1722973085867,
|
||||||
"tag": "0001_sharp_luckman",
|
"tag": "0001_sharp_luckman",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 2,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1722975470474,
|
||||||
|
"tag": "0002_neat_firedrake",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -3,7 +3,7 @@ import { html } from '@mtcute/node'
|
||||||
import parseDuration from 'parse-duration'
|
import parseDuration from 'parse-duration'
|
||||||
|
|
||||||
import { env } from '../env'
|
import { env } from '../env'
|
||||||
import { approveShout, banShouts, declineShout, deleteBySerial, unbanShouts } from '../service/shoutbox'
|
import { answerBySerial, approveShout, banShouts, declineShout, deleteBySerial, unbanShouts } from '../service/shoutbox'
|
||||||
|
|
||||||
export const ShoutboxAction = new CallbackDataBuilder('shoutbox', 'id', 'action')
|
export const ShoutboxAction = new CallbackDataBuilder('shoutbox', 'id', 'action')
|
||||||
|
|
||||||
|
@ -12,9 +12,9 @@ const dp = Dispatcher.child()
|
||||||
dp.onCallbackQuery(ShoutboxAction.filter({ action: 'approve' }), async (ctx) => {
|
dp.onCallbackQuery(ShoutboxAction.filter({ action: 'approve' }), async (ctx) => {
|
||||||
if (ctx.chat.id !== env.TG_CHAT_ID) return
|
if (ctx.chat.id !== env.TG_CHAT_ID) return
|
||||||
|
|
||||||
approveShout(ctx.match.id)
|
const serial = approveShout(ctx.match.id)
|
||||||
await ctx.editMessageWith(msg => ({
|
await ctx.editMessageWith(msg => ({
|
||||||
text: html`${msg.textWithEntities}<br><br>✅ Approved!`,
|
text: html`${msg.textWithEntities}<br><br>✅ Approved! ID: <code>${serial}</code>`,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -61,4 +61,15 @@ dp.onNewMessage(filters.and(filters.chatId(env.TG_CHAT_ID), filters.command('sho
|
||||||
await ctx.answerText('done')
|
await ctx.answerText('done')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
dp.onNewMessage(filters.and(filters.chatId(env.TG_CHAT_ID), filters.command('shoutbox_reply')), async (ctx) => {
|
||||||
|
const serial = Number(ctx.command[1])
|
||||||
|
if (Number.isNaN(serial)) {
|
||||||
|
await ctx.answerText('invalid serial')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
answerBySerial(serial, ctx.command[2])
|
||||||
|
await ctx.answerText('done')
|
||||||
|
})
|
||||||
|
|
||||||
export { dp as shoutboxDp }
|
export { dp as shoutboxDp }
|
||||||
|
|
|
@ -10,6 +10,7 @@ export const shouts = sqliteTable('shouts', {
|
||||||
pending: integer('pending', { mode: 'boolean' }).notNull().default(true),
|
pending: integer('pending', { mode: 'boolean' }).notNull().default(true),
|
||||||
text: text('text'),
|
text: text('text'),
|
||||||
createdAt: text('created_at').notNull().default(sql`(CURRENT_TIMESTAMP)`),
|
createdAt: text('created_at').notNull().default(sql`(CURRENT_TIMESTAMP)`),
|
||||||
|
reply: text('reply'),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const shoutsBans = sqliteTable('shouts_bans', {
|
export const shoutsBans = sqliteTable('shouts_bans', {
|
||||||
|
|
|
@ -26,6 +26,7 @@ const fetchList = db.select({
|
||||||
text: shouts.text,
|
text: shouts.text,
|
||||||
pending: shouts.pending,
|
pending: shouts.pending,
|
||||||
serial: shouts.serial,
|
serial: shouts.serial,
|
||||||
|
reply: shouts.reply,
|
||||||
}).from(shouts)
|
}).from(shouts)
|
||||||
.where(filter)
|
.where(filter)
|
||||||
.limit(SHOUTS_PER_PAGE)
|
.limit(SHOUTS_PER_PAGE)
|
||||||
|
@ -58,6 +59,8 @@ export function approveShout(id: string) {
|
||||||
.set({ pending: false, serial: nextSerial })
|
.set({ pending: false, serial: nextSerial })
|
||||||
.where(eq(shouts.id, id))
|
.where(eq(shouts.id, id))
|
||||||
.run()
|
.run()
|
||||||
|
|
||||||
|
return nextSerial
|
||||||
}
|
}
|
||||||
|
|
||||||
export function declineShout(id: string) {
|
export function declineShout(id: string) {
|
||||||
|
@ -80,6 +83,13 @@ export function deleteBySerial(serial: number) {
|
||||||
.run({ serial })
|
.run({ serial })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function answerBySerial(serial: number, reply: string) {
|
||||||
|
db.update(shouts)
|
||||||
|
.set({ reply })
|
||||||
|
.where(eq(shouts.serial, serial))
|
||||||
|
.execute()
|
||||||
|
}
|
||||||
|
|
||||||
export function banShouts(ip: string, expires: number) {
|
export function banShouts(ip: string, expires: number) {
|
||||||
db.insert(shoutsBans)
|
db.insert(shoutsBans)
|
||||||
.values({
|
.values({
|
||||||
|
|
|
@ -69,3 +69,7 @@
|
||||||
.paginationLink {
|
.paginationLink {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reply {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
|
@ -73,6 +73,12 @@ function ShoutboxInner(props: {
|
||||||
</div>
|
</div>
|
||||||
<div class={css.text}>
|
<div class={css.text}>
|
||||||
{props.text}
|
{props.text}
|
||||||
|
{props.reply && (
|
||||||
|
<div class={css.reply}>
|
||||||
|
<b>reply: </b>
|
||||||
|
{props.reply}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue