1
0
Fork 0
mirror of https://github.com/muerwre/vk-tg-bot.git synced 2025-04-24 22:46:41 +07:00

#4 working likes on posts

This commit is contained in:
Fedor Katurov 2021-05-05 14:24:16 +07:00
parent a6e6209770
commit 5535a36cb8
6 changed files with 190 additions and 59 deletions

View file

@ -1,15 +1,21 @@
import { VkEvent } from "../vk/types"; import { VkEvent } from "../vk/types";
import { Like } from "./postgres/entities/Like"; import { Like } from "./postgres/entities/Like";
import { Event } from "./postgres/entities/Event"; import { Event } from "./postgres/entities/Event";
import { Post } from "./postgres/entities/Post";
export interface Storage { export interface Storage {
getEvent( getEventByMessageId(
type: VkEvent,
tgMessageId: number,
groupId: number,
channel: string
): Promise<Event>;
getEventById(
type: VkEvent, type: VkEvent,
eventId: number, eventId: number,
groupId: number, groupId: number,
channel: string channel: string
): Promise<Event>; ): Promise<Event>;
createEvent( createEvent(
type: VkEvent, type: VkEvent,
eventId: number, eventId: number,
@ -18,10 +24,14 @@ export interface Storage {
tgMessageId: number, tgMessageId: number,
text: Record<any, any> text: Record<any, any>
): Promise<Event>; ): Promise<Event>;
createOrUpdateLike(
createOrUpdateLike(like: Partial<Like>): Promise<Like>; messageId: number,
channel: string,
author: number,
text: string
): Promise<Like>;
getLikesFor(channel: string, messageId: number): Promise<Like[]>; getLikesFor(channel: string, messageId: number): Promise<Like[]>;
getLikeBy(channel: string, messageId: number, author: number): Promise<Like>; getLikeBy(channel: string, messageId: number, author: number): Promise<Like>;
createPost(eventId: number, text: string): Promise<Post>;
findPostByEvent(eventId: number): Promise<Post>;
} }

View file

@ -14,9 +14,9 @@ export class Event {
@Column() @Column()
type: VkEvent; type: VkEvent;
@Column() @Column()
eventId: number; vkEventId: number;
@Column() @Column()
groupId: number; vkGroupId: number;
@Column() @Column()
channel: string; channel: string;
@Column() @Column()

View file

@ -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;
}

View file

@ -6,6 +6,7 @@ import logger from "../../logger";
import path from "path"; import path from "path";
import { Like } from "./entities/Like"; import { Like } from "./entities/Like";
import { Event } from "./entities/Event"; import { Event } from "./entities/Event";
import { Post } from "./entities/Post";
const entities = [path.join(__dirname, "./entities/*")]; const entities = [path.join(__dirname, "./entities/*")];
@ -13,6 +14,7 @@ export class PostgresDB implements Storage {
private connection: Connection; private connection: Connection;
private events: Repository<Event>; private events: Repository<Event>;
private likes: Repository<Like>; private likes: Repository<Like>;
private posts: Repository<Post>;
constructor(private config: PostgresConfig) {} constructor(private config: PostgresConfig) {}
@ -29,17 +31,37 @@ export class PostgresDB implements Storage {
this.events = this.connection.getRepository(Event); this.events = this.connection.getRepository(Event);
this.likes = this.connection.getRepository(Like); this.likes = this.connection.getRepository(Like);
this.posts = this.connection.getRepository(Post);
logger.info(`db connected to ${this.config.uri}`); logger.info(`db connected to ${this.config.uri}`);
}; };
getEvent = async ( getEventByMessageId = async (
type: VkEvent, type: VkEvent,
eventId: number, tgMessageId: number,
groupId: number, vkGroupId: number,
channel: string 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 ( createEvent = async (
@ -52,8 +74,8 @@ export class PostgresDB implements Storage {
) => { ) => {
const event = this.events.create({ const event = this.events.create({
type, type,
eventId, vkEventId: eventId,
groupId, vkGroupId: groupId,
channel, channel,
tgMessageId, tgMessageId,
text, text,
@ -76,25 +98,26 @@ export class PostgresDB implements Storage {
}); });
}; };
createOrUpdateLike = async ({ createOrUpdateLike = async (messageId, channel, author, text) => {
channel,
author,
text,
messageId,
}: Partial<Like>) => {
const like = await this.likes.findOne({ channel, author, messageId }); const like = await this.likes.findOne({ channel, author, messageId });
if (like) { if (like) {
like.text = text; return await this.likes.save({ ...like, text });
return await this.likes.save(like);
} else { } else {
const created = await this.likes.create({ return this.likes.save({
channel, channel,
author, author,
text, text,
messageId, 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 });
};
} }

View file

@ -4,7 +4,7 @@ import { NextMiddleware } from "middleware-io";
import { UsersUserFull } from "vk-io/lib/api/schemas/objects"; import { UsersUserFull } from "vk-io/lib/api/schemas/objects";
import { ConfigGroup } from "../types"; import { ConfigGroup } from "../types";
import { ExtraReplyMessage } from "telegraf/typings/telegram-types"; import { ExtraReplyMessage } from "telegraf/typings/telegram-types";
import { InlineKeyboardButton, Update } from "typegram"; import { InlineKeyboardButton, InlineKeyboardMarkup, Update } from "typegram";
import { keys } from "ramda"; import { keys } from "ramda";
import { extractURLs } from "../../../utils/extract"; import { extractURLs } from "../../../utils/extract";
import logger from "../../logger"; import logger from "../../logger";
@ -15,8 +15,8 @@ type Button = "links" | "likes";
type UrlPrefix = string; type UrlPrefix = string;
type ExtraGenerator = ( type ExtraGenerator = (
text: string, text: string,
messageId?: number eventId?: number
) => InlineKeyboardButton[]; ) => Promise<InlineKeyboardButton[]>;
interface Fields { interface Fields {
image?: boolean; image?: boolean;
@ -55,7 +55,7 @@ export class PostNewHandler extends VkEventHandler<Fields, Values> {
return; return;
} }
const exist = await this.getEvent(id); const exist = await this.getEventById(id);
if (exist) { if (exist) {
logger.warn( logger.warn(
`received duplicate entry for ${this.group.name}, ${this.type}, ${id}` `received duplicate entry for ${this.group.name}, ${this.type}, ${id}`
@ -78,17 +78,21 @@ export class PostNewHandler extends VkEventHandler<Fields, Values> {
const extras: ExtraReplyMessage = { const extras: ExtraReplyMessage = {
disable_web_page_preview: true, disable_web_page_preview: true,
reply_markup: await this.createKeyboard(text),
}; };
this.appendExtras(extras, text);
const msg = await this.telegram.sendMessageToChan( const msg = await this.telegram.sendMessageToChan(
this.channel, this.channel,
parsed, parsed,
extras 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(); await next();
}; };
@ -103,33 +107,32 @@ export class PostNewHandler extends VkEventHandler<Fields, Values> {
/** /**
* Creates extras * Creates extras
*/ */
private appendExtras = ( private createKeyboard = async (
extras: ExtraReplyMessage,
text: string, text: string,
messageId?: number eventId?: number
) => { ): Promise<InlineKeyboardMarkup> => {
const { buttons } = this.template.fields; const { buttons } = this.template.fields;
if (!buttons?.length) { if (!buttons?.length) {
return; return;
} }
const keyboard = buttons const rows = await Promise.all(
.map((button) => this.extrasGenerators[button](text, messageId)) buttons.map((button) => this.extrasGenerators[button](text, eventId))
.filter((el) => el && el.length); );
const inline_keyboard = rows.filter((el) => el && el.length);
if (!keyboard.length) { if (!inline_keyboard.length) {
return; return;
} }
extras.reply_markup = { return { inline_keyboard };
inline_keyboard: keyboard,
};
}; };
/** /**
* Generates link buttons for post * Generates link buttons for post
*/ */
private generateLinks: ExtraGenerator = (text) => { private generateLinks: ExtraGenerator = async (text) => {
const links = this.template.fields.links; const links = this.template.fields.links;
if (!links) { if (!links) {
@ -156,8 +159,25 @@ export class PostNewHandler extends VkEventHandler<Fields, Values> {
/** /**
* Generates like button * Generates like button
*/ */
private generateLikes: ExtraGenerator = () => { private generateLikes: ExtraGenerator = async (text, eventId) => {
return this.likes.map((like, i) => ({ 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<string, number>
);
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, text: like,
callback_data: `/like ${this.channel} ${like}`, callback_data: `/like ${this.channel} ${like}`,
})); }));
@ -191,14 +211,15 @@ export class PostNewHandler extends VkEventHandler<Fields, Values> {
*/ */
onLikeAction = async (ctx: LikeCtx, next) => { onLikeAction = async (ctx: LikeCtx, next) => {
const id = ctx.update.callback_query.message.message_id; const id = ctx.update.callback_query.message.message_id;
const [_, channel, emo] = ctx.match; const author = ctx.update.callback_query.from.id;
const exist = await this.getEvent(id); const [, channel, emo] = ctx.match;
const event = await this.getEventByTgMessageId(id);
if ( if (
!channel || !channel ||
!emo || !emo ||
!id || !id ||
!exist || !event ||
channel != this.channel || channel != this.channel ||
!this.likes.includes(emo) !this.likes.includes(emo)
) { ) {
@ -206,17 +227,50 @@ export class PostNewHandler extends VkEventHandler<Fields, Values> {
return; return;
} }
// const extras: ExtraReplyMessage = {}; const post = await this.db.findPostByEvent(event.id);
// this.appendExtras(extras, exist.text); if (!post) {
// await ctx.telegram.editMessageReplyMarkup( await next();
// ctx.chat.id, return;
// id, }
// ctx.inlineMessageId,
// extras.reply_markup.inline_keyboard
// );
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}` `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);
}; };
} }

View file

@ -51,12 +51,35 @@ export class VkEventHandler<
/** /**
* Checks for duplicates * Checks for duplicates
*/ */
getEvent = async (id?: number): Promise<Event | undefined> => { getEventById = async (id?: number): Promise<Event | undefined> => {
if (!id) { if (!id) {
return undefined; 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<Event | undefined> => {
if (!tgMessageId) {
return undefined;
}
return await this.db.getEventByMessageId(
this.type,
tgMessageId,
this.group.id,
this.channel
);
}; };
/** /**