From 5535a36cb8347ad99272a55c62d1934d5ad0d865 Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Wed, 5 May 2021 14:24:16 +0700 Subject: [PATCH] #4 working likes on posts --- src/service/db/index.ts | 22 ++-- src/service/db/postgres/entities/Event.ts | 4 +- src/service/db/postgres/entities/Post.ts | 21 ++++ src/service/db/postgres/index.ts | 55 +++++++--- src/service/vk/handlers/PostNewHandler.ts | 120 ++++++++++++++++------ src/service/vk/handlers/VkEventHandler.ts | 27 ++++- 6 files changed, 190 insertions(+), 59 deletions(-) create mode 100644 src/service/db/postgres/entities/Post.ts diff --git a/src/service/db/index.ts b/src/service/db/index.ts index 5a53ebf..4bc1a53 100644 --- a/src/service/db/index.ts +++ b/src/service/db/index.ts @@ -1,15 +1,21 @@ import { VkEvent } from "../vk/types"; import { Like } from "./postgres/entities/Like"; import { Event } from "./postgres/entities/Event"; +import { Post } from "./postgres/entities/Post"; export interface Storage { - getEvent( + getEventByMessageId( + type: VkEvent, + tgMessageId: number, + groupId: number, + channel: string + ): Promise; + getEventById( type: VkEvent, eventId: number, groupId: number, channel: string ): Promise; - createEvent( type: VkEvent, eventId: number, @@ -18,10 +24,14 @@ export interface Storage { tgMessageId: number, text: Record ): Promise; - - createOrUpdateLike(like: Partial): Promise; - + createOrUpdateLike( + messageId: number, + channel: string, + author: number, + text: string + ): Promise; getLikesFor(channel: string, messageId: number): Promise; - getLikeBy(channel: string, messageId: number, author: number): Promise; + createPost(eventId: number, text: string): Promise; + findPostByEvent(eventId: number): Promise; } diff --git a/src/service/db/postgres/entities/Event.ts b/src/service/db/postgres/entities/Event.ts index 310a8d8..9f84ba2 100644 --- a/src/service/db/postgres/entities/Event.ts +++ b/src/service/db/postgres/entities/Event.ts @@ -14,9 +14,9 @@ export class Event { @Column() type: VkEvent; @Column() - eventId: number; + vkEventId: number; @Column() - groupId: number; + vkGroupId: number; @Column() channel: string; @Column() diff --git a/src/service/db/postgres/entities/Post.ts b/src/service/db/postgres/entities/Post.ts new file mode 100644 index 0000000..8e4ad86 --- /dev/null +++ b/src/service/db/postgres/entities/Post.ts @@ -0,0 +1,21 @@ +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from "typeorm"; + +@Entity() +export class Post { + @PrimaryGeneratedColumn() + id: number; + @Column() + eventId: number; + @Column({ type: "text" }) + text: string; + @CreateDateColumn() + createdAt: Date; + @UpdateDateColumn() + updatedAt: Date; +} diff --git a/src/service/db/postgres/index.ts b/src/service/db/postgres/index.ts index 9fdbc4c..0c29372 100644 --- a/src/service/db/postgres/index.ts +++ b/src/service/db/postgres/index.ts @@ -6,6 +6,7 @@ import logger from "../../logger"; import path from "path"; import { Like } from "./entities/Like"; import { Event } from "./entities/Event"; +import { Post } from "./entities/Post"; const entities = [path.join(__dirname, "./entities/*")]; @@ -13,6 +14,7 @@ export class PostgresDB implements Storage { private connection: Connection; private events: Repository; private likes: Repository; + private posts: Repository; constructor(private config: PostgresConfig) {} @@ -29,17 +31,37 @@ export class PostgresDB implements Storage { this.events = this.connection.getRepository(Event); this.likes = this.connection.getRepository(Like); + this.posts = this.connection.getRepository(Post); logger.info(`db connected to ${this.config.uri}`); }; - getEvent = async ( + getEventByMessageId = async ( type: VkEvent, - eventId: number, - groupId: number, + tgMessageId: number, + vkGroupId: number, channel: string ) => { - return await this.events.findOne({ type, eventId, groupId, channel }); + return await this.events.findOne({ + type, + tgMessageId, + vkGroupId, + channel, + }); + }; + + getEventById = async ( + type: VkEvent, + id: number, + vkGroupId: number, + channel: string + ) => { + return await this.events.findOne({ + type, + id, + vkGroupId, + channel, + }); }; createEvent = async ( @@ -52,8 +74,8 @@ export class PostgresDB implements Storage { ) => { const event = this.events.create({ type, - eventId, - groupId, + vkEventId: eventId, + vkGroupId: groupId, channel, tgMessageId, text, @@ -76,25 +98,26 @@ export class PostgresDB implements Storage { }); }; - createOrUpdateLike = async ({ - channel, - author, - text, - messageId, - }: Partial) => { + createOrUpdateLike = async (messageId, channel, author, text) => { const like = await this.likes.findOne({ channel, author, messageId }); if (like) { - like.text = text; - return await this.likes.save(like); + return await this.likes.save({ ...like, text }); } else { - const created = await this.likes.create({ + return this.likes.save({ channel, author, text, messageId, }); - return created[0]; } }; + + findPostByEvent = async (eventId: number) => { + return this.posts.findOne({ eventId }); + }; + + createPost = async (eventId: number, text: string) => { + return this.posts.save({ eventId, text }); + }; } diff --git a/src/service/vk/handlers/PostNewHandler.ts b/src/service/vk/handlers/PostNewHandler.ts index 33a0a4e..e0f60e2 100644 --- a/src/service/vk/handlers/PostNewHandler.ts +++ b/src/service/vk/handlers/PostNewHandler.ts @@ -4,7 +4,7 @@ import { NextMiddleware } from "middleware-io"; import { UsersUserFull } from "vk-io/lib/api/schemas/objects"; import { ConfigGroup } from "../types"; import { ExtraReplyMessage } from "telegraf/typings/telegram-types"; -import { InlineKeyboardButton, Update } from "typegram"; +import { InlineKeyboardButton, InlineKeyboardMarkup, Update } from "typegram"; import { keys } from "ramda"; import { extractURLs } from "../../../utils/extract"; import logger from "../../logger"; @@ -15,8 +15,8 @@ type Button = "links" | "likes"; type UrlPrefix = string; type ExtraGenerator = ( text: string, - messageId?: number -) => InlineKeyboardButton[]; + eventId?: number +) => Promise; interface Fields { image?: boolean; @@ -55,7 +55,7 @@ export class PostNewHandler extends VkEventHandler { return; } - const exist = await this.getEvent(id); + const exist = await this.getEventById(id); if (exist) { logger.warn( `received duplicate entry for ${this.group.name}, ${this.type}, ${id}` @@ -78,17 +78,21 @@ export class PostNewHandler extends VkEventHandler { const extras: ExtraReplyMessage = { disable_web_page_preview: true, + reply_markup: await this.createKeyboard(text), }; - this.appendExtras(extras, text); - const msg = await this.telegram.sendMessageToChan( this.channel, parsed, extras ); - await this.createEvent(id, msg.message_id, context.wall.toJSON()); + const event = await this.createEvent( + id, + msg.message_id, + context.wall.toJSON() + ); + await this.db.createPost(event.id, context.wall.text); await next(); }; @@ -103,33 +107,32 @@ export class PostNewHandler extends VkEventHandler { /** * Creates extras */ - private appendExtras = ( - extras: ExtraReplyMessage, + private createKeyboard = async ( text: string, - messageId?: number - ) => { + eventId?: number + ): Promise => { const { buttons } = this.template.fields; + if (!buttons?.length) { return; } - const keyboard = buttons - .map((button) => this.extrasGenerators[button](text, messageId)) - .filter((el) => el && el.length); + const rows = await Promise.all( + buttons.map((button) => this.extrasGenerators[button](text, eventId)) + ); + const inline_keyboard = rows.filter((el) => el && el.length); - if (!keyboard.length) { + if (!inline_keyboard.length) { return; } - extras.reply_markup = { - inline_keyboard: keyboard, - }; + return { inline_keyboard }; }; /** * Generates link buttons for post */ - private generateLinks: ExtraGenerator = (text) => { + private generateLinks: ExtraGenerator = async (text) => { const links = this.template.fields.links; if (!links) { @@ -156,8 +159,25 @@ export class PostNewHandler extends VkEventHandler { /** * Generates like button */ - private generateLikes: ExtraGenerator = () => { - return this.likes.map((like, i) => ({ + private generateLikes: ExtraGenerator = async (text, eventId) => { + if (eventId) { + const event = await this.getEventById(eventId); + const likes = await this.db.getLikesFor(this.channel, event.tgMessageId); + const withCount = likes.reduce( + (acc, like) => ({ + ...acc, + [like.text]: acc[like.text] ? acc[like.text] + 1 : 1, + }), + {} as Record + ); + + return this.likes.map((like) => ({ + text: withCount[like] ? `${like} ${withCount[like]}` : like, + callback_data: `/like ${this.channel} ${like}`, + })); + } + + return this.likes.map((like) => ({ text: like, callback_data: `/like ${this.channel} ${like}`, })); @@ -191,14 +211,15 @@ export class PostNewHandler extends VkEventHandler { */ onLikeAction = async (ctx: LikeCtx, next) => { const id = ctx.update.callback_query.message.message_id; - const [_, channel, emo] = ctx.match; - const exist = await this.getEvent(id); + const author = ctx.update.callback_query.from.id; + const [, channel, emo] = ctx.match; + const event = await this.getEventByTgMessageId(id); if ( !channel || !emo || !id || - !exist || + !event || channel != this.channel || !this.likes.includes(emo) ) { @@ -206,17 +227,50 @@ export class PostNewHandler extends VkEventHandler { return; } - // const extras: ExtraReplyMessage = {}; - // this.appendExtras(extras, exist.text); - // await ctx.telegram.editMessageReplyMarkup( - // ctx.chat.id, - // id, - // ctx.inlineMessageId, - // extras.reply_markup.inline_keyboard - // ); + const post = await this.db.findPostByEvent(event.id); + if (!post) { + await next(); + return; + } - logger.warn( + const like = await this.getLike(author, id); + if (like?.text === emo) { + await next(); + return; + } + + await this.createOrUpdateLike(author, event.tgMessageId, emo); + + const markup = await this.createKeyboard(post.text, event.id); + + await ctx.telegram.editMessageReplyMarkup( + ctx.chat.id, + id, + ctx.inlineMessageId, + markup + ); + + logger.info( `someone reacted with ${emo} to message ${id} on channel ${channel}` ); + + next(); + }; + + createOrUpdateLike = async ( + author: number, + messageId: number, + emo: string + ) => { + return await this.db.createOrUpdateLike( + messageId, + this.channel, + author, + emo + ); + }; + + getLike = async (author: number, messageId: number) => { + return await this.db.getLikeBy(this.channel, messageId, author); }; } diff --git a/src/service/vk/handlers/VkEventHandler.ts b/src/service/vk/handlers/VkEventHandler.ts index b6a6305..3335052 100644 --- a/src/service/vk/handlers/VkEventHandler.ts +++ b/src/service/vk/handlers/VkEventHandler.ts @@ -51,12 +51,35 @@ export class VkEventHandler< /** * Checks for duplicates */ - getEvent = async (id?: number): Promise => { + getEventById = async (id?: number): Promise => { if (!id) { return undefined; } - return await this.db.getEvent(this.type, id, this.group.id, this.channel); + return await this.db.getEventById( + this.type, + id, + this.group.id, + this.channel + ); + }; + + /** + * Checks for duplicates + */ + getEventByTgMessageId = async ( + tgMessageId?: number + ): Promise => { + if (!tgMessageId) { + return undefined; + } + + return await this.db.getEventByMessageId( + this.type, + tgMessageId, + this.group.id, + this.channel + ); }; /**