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

added plaintext fallback for markdown

This commit is contained in:
Fedor Katurov 2021-05-27 12:27:13 +07:00
parent 1b0a1f20d5
commit ef4794a33f
8 changed files with 161 additions and 68 deletions

View file

@ -73,10 +73,14 @@ export class TelegramService {
public sendMessageToChan = async ( public sendMessageToChan = async (
channel: string, channel: string,
message: string, message: string,
markdown?: boolean,
extra?: ExtraReplyMessage extra?: ExtraReplyMessage
) => { ) => {
logger.debug(`sending message "${message}" to chan "${channel}"`); logger.debug(`sending message "${message}" to chan "${channel}"`);
return await this.bot.telegram.sendMessage(channel, message, extra); return await this.bot.telegram.sendMessage(channel, message, {
...extra,
...(markdown ? { parse_mode: "Markdown" } : {}),
});
}; };
/** /**
@ -86,12 +90,14 @@ export class TelegramService {
channel: string, channel: string,
caption: string, caption: string,
src: string, src: string,
markdown?: boolean,
extra?: ExtraReplyMessage extra?: ExtraReplyMessage
) => { ) => {
logger.debug(`sending photo message "${caption}" to chan "${channel}"`); logger.debug(`sending photo message "${caption}" to chan "${channel}"`);
return await this.bot.telegram.sendPhoto(channel, src, { return await this.bot.telegram.sendPhoto(channel, src, {
...extra, ...extra,
caption, caption,
...(markdown ? { parse_mode: "Markdown" } : {}),
}); });
}; };

View file

@ -8,6 +8,7 @@ import toVFile from "to-vfile";
import path from "path"; import path from "path";
import hb from "handlebars"; import hb from "handlebars";
import strip from "strip-markdown"; import strip from "strip-markdown";
import { VFileCompatible } from "vfile";
const removeFrontmatter = () => (tree) => { const removeFrontmatter = () => (tree) => {
tree.children = tree.children.filter((item) => item.type !== "yaml"); tree.children = tree.children.filter((item) => item.type !== "yaml");
@ -20,25 +21,26 @@ export class Template<
public fields: F = {} as F; public fields: F = {} as F;
public template: string = ""; public template: string = "";
private readonly file: VFileCompatible = "";
constructor(filename: string) { constructor(filename: string) {
try { try {
if (!filename) { if (!filename) {
return; return;
} }
// read file and fields from it to this.fields
const processor = unified() const processor = unified()
.use(stringify) .use(stringify)
.use(frontmatter) .use(frontmatter)
.use(extract, { yaml: parse }) .use(extract, { yaml: parse })
.use(removeFrontmatter) .use(removeFrontmatter)
.use(parser) .use(parser);
.use(strip);
const file = toVFile.readSync(path.join(__dirname, "../../", filename));
const result = processor.processSync(file);
this.file = toVFile.readSync(path.join(__dirname, "../../", filename));
const result = processor.processSync(this.file);
this.fields = result.data as F; this.fields = result.data as F;
this.template = result.contents.toString().trim();
} catch (e) { } catch (e) {
throw new Error(`Template: ${e.toString()}`); throw new Error(`Template: ${e.toString()}`);
} }
@ -47,8 +49,23 @@ export class Template<
/** /**
* Themes the template with values * Themes the template with values
*/ */
public theme = (values: V) => { public theme = (values: V, markdown?: boolean) => {
return hb.compile(this.template)(values).replace(/\n+/g, "\n\n"); const processor = unified()
.use(stringify)
.use(frontmatter)
.use(removeFrontmatter)
.use(parser);
if (!markdown) {
processor.use(strip);
}
const result = processor.processSync(this.file);
const template = result.contents.toString().trim();
return hb
.compile(template)(values)
.replace(/\n{2,}/g, "\n\n");
}; };
/** /**
@ -62,15 +79,12 @@ export class Template<
} }
public static cleanText(text: string) { public static cleanText(text: string) {
const processor = unified() return unified()
.use(stringify) .use(stringify)
.use(frontmatter)
.use(extract, { yaml: parse })
.use(removeFrontmatter)
.use(parser) .use(parser)
.use(strip); .use(strip)
.processSync(text)
return processor.processSync(text).contents.toString(); .contents.toString();
} }
} }

View file

@ -29,20 +29,27 @@ export class JoinLeaveHandler extends VkEventHandler<Fields, Values> {
`vk, group ${this.group.name}: ${user.first_name} ${user.last_name} ${dir} the group` `vk, group ${this.group.name}: ${user.first_name} ${user.last_name} ${dir} the group`
); );
const parsed = this.template.theme({ const parsed = this.template.theme(
user, {
group: this.group, user,
isJoined: context.isJoin, group: this.group,
isLeave: context.isLeave, isJoined: context.isJoin,
count, isLeave: context.isLeave,
}); count,
},
!!this.channel.markdown
);
const extras: ExtraReplyMessage = { const extras: ExtraReplyMessage = {
parse_mode: "Markdown",
disable_web_page_preview: true, disable_web_page_preview: true,
}; };
await this.telegram.sendMessageToChan(this.channel.id, parsed, extras); await this.telegram.sendMessageToChan(
this.channel.id,
parsed,
!!this.channel.markdown,
extras
);
await next(); await next();
}; };

View file

@ -34,20 +34,27 @@ export class MessageNewHandler extends VkEventHandler<Fields, Values> {
`vk, group ${this.group.name} received message from ${user.first_name} ${user.last_name}: "${context.text}"` `vk, group ${this.group.name} received message from ${user.first_name} ${user.last_name}: "${context.text}"`
); );
const parsed = this.template.theme({ const parsed = this.template.theme(
user, {
group: this.group, user,
text: context?.text || "", group: this.group,
}); text: context?.text || "",
},
!!this.channel.markdown
);
const extras: ExtraReplyMessage = { const extras: ExtraReplyMessage = {
parse_mode: "Markdown",
disable_web_page_preview: true, disable_web_page_preview: true,
}; };
this.appendButtons(extras, user.id); this.appendButtons(extras, user.id);
await this.telegram.sendMessageToChan(this.channel.id, parsed, extras); await this.telegram.sendMessageToChan(
this.channel.id,
parsed,
!!this.channel.markdown,
extras
);
await next(); await next();
}; };

View file

@ -100,33 +100,20 @@ export class PostNewHandler extends VkEventHandler<Fields, Values> {
reply_markup: await this.createKeyboard(text, undefined, context.wall.id), reply_markup: await this.createKeyboard(text, undefined, context.wall.id),
}; };
let msg: Message | PhotoMessage;
const images = context.wall.getAttachments("photo"); const images = context.wall.getAttachments("photo");
const thumbs = images const thumbs = images
.map(getAttachment) .map(getAttachment)
.filter((el) => el) .filter((el) => el)
.slice(0, this.template.fields.images_limit) as string[]; .slice(0, this.template.fields.images_limit) as string[];
const hasThumb = const msg = await this.sendMessage(
!!this.template.fields.image && text,
!!this.template.fields.images_limit && thumbs,
thumbs.length > 0; extras,
postType,
if (hasThumb) { user,
msg = await this.telegram.sendPhotoToChan( this.channel.markdown
this.channel.id, );
this.trimTextForPhoto(text, PHOTO_CAPTION_LIMIT, postType, user),
thumbs[0]!,
extras
);
} else {
msg = await this.telegram.sendMessageToChan(
this.channel.id,
this.trimTextForPhoto(text, POST_TEXT_LIMIT, postType, user),
extras
);
}
const event = await this.createEvent( const event = await this.createEvent(
id, id,
@ -380,14 +367,18 @@ export class PostNewHandler extends VkEventHandler<Fields, Values> {
private themeText = ( private themeText = (
text: string, text: string,
type?: string, type?: string,
user?: UsersUserFull user?: UsersUserFull,
markdown?: boolean
): string => { ): string => {
return this.template.theme({ return this.template.theme(
user, {
group: this.group, user,
type, group: this.group,
text: Template.cleanText(text), type,
}); text: Template.cleanText(text),
},
markdown
);
}; };
/** /**
@ -397,9 +388,10 @@ export class PostNewHandler extends VkEventHandler<Fields, Values> {
text: string, text: string,
maxChars: number, maxChars: number,
type?: string, type?: string,
user?: UsersUserFull user?: UsersUserFull,
markdown?: boolean
): string => { ): string => {
const withText = this.themeText(text, type, user); const withText = this.themeText(text, type, user, markdown);
const limit = this.template.fields.char_limit const limit = this.template.fields.char_limit
? Math.min(this.template.fields.char_limit, maxChars) ? Math.min(this.template.fields.char_limit, maxChars)
: maxChars; : maxChars;
@ -408,13 +400,11 @@ export class PostNewHandler extends VkEventHandler<Fields, Values> {
return withText; return withText;
} }
const withoutText = this.themeText("", type, user); const withoutText = this.themeText("", type, user, markdown);
const suffix = "..."; const suffix = "...";
const trimmed = text.slice(0, limit - withoutText.length - suffix.length); const trimmed = text.slice(0, limit - withoutText.length - suffix.length);
const txt = this.themeText(`${trimmed}${suffix}`, type, user); return this.themeText(`${trimmed}${suffix}`, type, user, markdown);
return txt;
}; };
/** /**
@ -431,4 +421,69 @@ export class PostNewHandler extends VkEventHandler<Fields, Values> {
.filter((el) => el) .filter((el) => el)
.join(" ") .join(" ")
.trim() || "someone"; .trim() || "someone";
/**
* Sends message
*/
sendMessage = async (
text: string,
thumbs: string[],
extras?: ExtraReplyMessage,
postType?: string,
user?: UsersUserFull,
markdown?: boolean
) => {
try {
const hasThumb =
!!this.template.fields.image &&
!!this.template.fields.images_limit &&
thumbs.length > 0;
if (hasThumb) {
const trimmed = this.trimTextForPhoto(
text,
PHOTO_CAPTION_LIMIT,
postType,
user,
markdown
);
return await this.telegram.sendPhotoToChan(
this.channel.id,
trimmed,
thumbs[0]!,
markdown,
extras
);
} else {
const trimmed = this.trimTextForPhoto(
text,
POST_TEXT_LIMIT,
postType,
user,
markdown
);
return await this.telegram.sendMessageToChan(
this.channel.id,
trimmed,
markdown,
extras
);
}
} catch (e) {
if (!markdown) {
throw e;
}
// Try to send as plain text
logger.warn(
`telegram: failed to send markdown, falling back to plaintext: ${e}`,
e
);
return this.sendMessage(text, thumbs, extras, postType, user, false);
}
};
} }

View file

@ -22,6 +22,7 @@ export interface GroupChannel {
events: VkEvent[]; events: VkEvent[];
post_types: WallPostType[]; post_types: WallPostType[];
templates: Partial<TemplateConfig>; templates: Partial<TemplateConfig>;
markdown?: boolean;
} }
export enum VkEvent { export enum VkEvent {

View file

@ -14,6 +14,7 @@ const vkChannelSchema = yup
.matches(/^@/, ({ path }) => `${path} should start with "@"`), .matches(/^@/, ({ path }) => `${path} should start with "@"`),
events: yup.array().of(vkChannelEventSchema), events: yup.array().of(vkChannelEventSchema),
templates: templateOptionalSchema, templates: templateOptionalSchema,
markdown: yup.boolean().optional(),
}); });
export const vkConfigSchema = yup export const vkConfigSchema = yup

View file

@ -19,5 +19,7 @@
--}} --}}
{{#ifEq type 'suggest'}}Предложка:{{/ifEq}} {{#ifEq type 'suggest'}}Предложка:{{/ifEq}}
{{text}}
{{#if user}}[{{user.first_name}} {{user.last_name}}](https://vk.com/id{{user.id}}){{/if}} {{{text}}}
{{#if user}}
— [{{user.first_name}} {{user.last_name}}](https://vk.com/id{{user.id}}){{/if}}