diff --git a/config.example.yml b/config.example.yml index e386c83..dd09baa 100644 --- a/config.example.yml +++ b/config.example.yml @@ -1,13 +1,17 @@ http: port: 3002 telegram: + # Get it from bot father key: '' -webhook: - url: https://something.org:65534/webhook - enabled: false + webhook: + url: https://something.org:3002/webhook + enabled: false logger: level: info -#vk: +vk: + # Default path for POST requests from VK api + endpoint: / + groups: [] # groups: # - id: 0 # name: 'Group name' diff --git a/src/api/http/index.ts b/src/api/http/index.ts index ce10b0e..92cf1af 100644 --- a/src/api/http/index.ts +++ b/src/api/http/index.ts @@ -6,19 +6,21 @@ import loggerHttpMiddleware from "../../service/logger/http"; import logger from "../../service/logger"; import { TelegramService } from "../../service/telegram"; import http from "http"; -import { WebhookConfig } from "../../config/types"; import { URL } from "url"; import { corsMiddleware, errorMiddleware } from "./middleware"; +import { WebhookConfig } from "../../service/telegram/types"; export class HttpApi { app: Express; + webhook: WebhookConfig; constructor( private props: HttpConfig, private telegram: TelegramService, - private vk: VkService, - private webhook?: WebhookConfig + private vk: VkService ) { + this.webhook = this.telegram.webhook; + this.app = express(); this.app.use(corsMiddleware); this.app.use(express.json()); @@ -28,11 +30,7 @@ export class HttpApi { this.app.use(bodyParser.json()); this.app.use(express.json()); - if (this?.webhook?.enabled && this?.webhook?.url) { - const url = new URL(this.webhook.url); - logger.info(`using webhook at ${url.pathname}`); - this.app.post(url.pathname, this.handleWebhook); - } + this.setupHandlers(); this.app.use(errorMiddleware); } @@ -46,6 +44,21 @@ export class HttpApi { logger.info(`http api listening at ${this.props.port}`); } + /** + * Adds webhandlers + */ + private setupHandlers() { + // Webhooks (if available) + if (this?.webhook?.enabled && this?.webhook?.url) { + const url = new URL(this.webhook.url); + logger.info(`using webhook at ${url.pathname}`); + this.app.post(url.pathname, this.handleWebhook); + this.app.get(url.pathname, this.testWebhook); + } + + this.app.post(this.vk.endpoint, this.handleVkEvent); + } + /** * Handles telegram webhooks */ @@ -53,4 +66,19 @@ export class HttpApi { logger.debug("got message via webhook", req.body); await this.telegram.handleUpdate(req.body, res); }; + + /** + * Just returns 200 + */ + private testWebhook = async (req: Request, res: Response) => { + res.sendStatus(200); + }; + + /** + * Handles VK events + */ + private handleVkEvent = async (req: Request, res: Response) => { + await this.vk.handle(req.body); + res.sendStatus(200); + }; } diff --git a/src/config/types.ts b/src/config/types.ts index f6079aa..1c089aa 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -3,14 +3,9 @@ import { VkConfig } from "../service/vk/types"; import { HttpConfig } from "../api/http/types"; import { LoggerConfig } from "../service/logger/types"; -export interface WebhookConfig { - url?: string; - enabled?: boolean; -} export interface Config extends Record { http: HttpConfig; telegram: TelegramConfig; vk: VkConfig; logger?: LoggerConfig; - webhook?: WebhookConfig; } diff --git a/src/config/validate.ts b/src/config/validate.ts index b5e0608..c56f573 100644 --- a/src/config/validate.ts +++ b/src/config/validate.ts @@ -5,17 +5,11 @@ import { vkConfigSchema } from "../service/vk/validation"; import { telegramConfigSchema } from "../service/telegram/validation"; import { loggerConfigSchema } from "../service/logger/config"; -const webhookValidationSchema = object().optional().shape({ - url: string(), - enabled: boolean(), -}); - const configSchema = object().required().shape({ http: httpConfigSchema, vk: vkConfigSchema, telegram: telegramConfigSchema, logger: loggerConfigSchema, - webhook: webhookValidationSchema, }); export const validateConfig = (config: Config) => diff --git a/src/index.ts b/src/index.ts index 58de77e..4baefb8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,18 +8,13 @@ import { HttpApi } from "./api/http"; async function main() { try { const config = prepareConfig(); - const telegram = new TelegramService(config.telegram, config.webhook); + const telegram = new TelegramService(config.telegram); const vkService = new VkService(config.vk); const telegramApi = new TelegramApi(telegram).listen(); await telegram.start(); - const httpApi = new HttpApi( - config.http, - telegram, - vkService, - config.webhook - ).listen(); + const httpApi = new HttpApi(config.http, telegram, vkService).listen(); } catch (e) { logger.error(e.message); } diff --git a/src/service/telegram/index.ts b/src/service/telegram/index.ts index b3f48fe..4cec80f 100644 --- a/src/service/telegram/index.ts +++ b/src/service/telegram/index.ts @@ -1,17 +1,17 @@ -import { TelegramConfig } from "./types"; +import { TelegramConfig, WebhookConfig } from "./types"; import { Telegraf } from "telegraf"; import logger from "../logger"; import { Response } from "express"; import { Update } from "typegram"; import loggerTgMiddleware from "../logger/tg"; -import { WebhookConfig } from "../../config/types"; // import SocksProxyAgent from 'socks-proxy-agent'; export class TelegramService { public readonly bot: Telegraf; + public readonly webhook: WebhookConfig = {}; - constructor(private props: TelegramConfig, private webhook: WebhookConfig) { + constructor(private props: TelegramConfig) { // const agent = (CONFIG.PROXY && new SocksProxyAgent(CONFIG.PROXY)) || null; const options: Partial> = { telegram: { @@ -21,6 +21,8 @@ export class TelegramService { }, }; + this.webhook = props.webhook; + this.bot = new Telegraf(props.key, options); this.bot.use(loggerTgMiddleware); diff --git a/src/service/telegram/types.ts b/src/service/telegram/types.ts index 3b7a6e6..adb7bf9 100644 --- a/src/service/telegram/types.ts +++ b/src/service/telegram/types.ts @@ -1,3 +1,9 @@ +export interface WebhookConfig { + url?: string; + enabled?: boolean; +} + export interface TelegramConfig { key: string; + webhook: WebhookConfig; } diff --git a/src/service/telegram/validation.ts b/src/service/telegram/validation.ts index 0b7a6c8..2f1f9d6 100644 --- a/src/service/telegram/validation.ts +++ b/src/service/telegram/validation.ts @@ -1,5 +1,12 @@ import * as yup from "yup"; +import { boolean, object, string } from "yup"; + +const webhookValidationSchema = object().optional().shape({ + url: string(), + enabled: boolean(), +}); export const telegramConfigSchema = yup.object().required().shape({ key: yup.string().required(), + webhook: webhookValidationSchema, }); diff --git a/src/service/vk/index.ts b/src/service/vk/index.ts index 7e8f61d..f1f59e5 100644 --- a/src/service/vk/index.ts +++ b/src/service/vk/index.ts @@ -1,9 +1,20 @@ -import { VkConfig } from './types'; +import { VkConfig } from "./types"; export class VkService { + public endpoint: string = "/"; + constructor(private config: VkConfig) { if (!config.groups.length) { - throw new Error('No vk groups to handle. Specify them in config') + throw new Error("No vk groups to handle. Specify them in config"); } + + this.endpoint = config.endpoint; } + + /** + * Handles incoming VK events + */ + public handle = async (event: any) => { + // TODO: handle events + }; } diff --git a/src/service/vk/types.ts b/src/service/vk/types.ts index 192684d..030f33f 100644 --- a/src/service/vk/types.ts +++ b/src/service/vk/types.ts @@ -1,26 +1,27 @@ export interface VkConfig extends Record { - groups: ConfigGroup[] + groups: ConfigGroup[]; + endpoint?: string; } interface ConfigGroup { - id: number - name: string - testResponse: string - secretKey: string - apiKey: string - channels: GroupChannel[] + id: number; + name: string; + testResponse: string; + secretKey: string; + apiKey: string; + channels: GroupChannel[]; } interface GroupChannel { - id: string, - events: VkEvent[] + id: string; + events: VkEvent[]; } export enum VkEvent { - Confirmation = 'confirmation', - WallPostNew = 'wall_post_new', - PostSuggestion = 'post_suggestion', - GroupJoin = 'group_join', - GroupLeave = 'group_leave', - MessageNew = 'message_new', + Confirmation = "confirmation", + WallPostNew = "wall_post_new", + PostSuggestion = "post_suggestion", + GroupJoin = "group_join", + GroupLeave = "group_leave", + MessageNew = "message_new", } diff --git a/src/service/vk/validation.ts b/src/service/vk/validation.ts index c5950fa..f379115 100644 --- a/src/service/vk/validation.ts +++ b/src/service/vk/validation.ts @@ -1,20 +1,35 @@ -import * as yup from 'yup' -import { VkConfig, VkEvent } from './types'; +import * as yup from "yup"; +import { VkConfig, VkEvent } from "./types"; -const vkChannelEventSchema = yup.string().oneOf(Object.values(VkEvent)) +const vkChannelEventSchema = yup.string().oneOf(Object.values(VkEvent)); -const vkChannelSchema = yup.object().required().shape({ - id: yup.string().required().matches(/^@/, ({ path }) => `${path} should start with "@"`), - events: yup.array().of(vkChannelEventSchema) -}) +const vkChannelSchema = yup + .object() + .required() + .shape({ + id: yup + .string() + .required() + .matches(/^@/, ({ path }) => `${path} should start with "@"`), + events: yup.array().of(vkChannelEventSchema), + }); -export const vkConfigSchema = yup.object().required().shape({ - groups: yup.array().required().of(yup.object().shape({ - id: yup.number().positive(), - name: yup.string().required(), - testResponse: yup.string().required(), - secretKey: yup.string().required(), - apiKey: yup.string().required(), - channels: yup.array().of(vkChannelSchema), - })) -}) +export const vkConfigSchema = yup + .object() + .required() + .shape({ + endpoint: yup.string().optional(), + groups: yup + .array() + .required() + .of( + yup.object().shape({ + id: yup.number().positive(), + name: yup.string().required(), + testResponse: yup.string().required(), + secretKey: yup.string().required(), + apiKey: yup.string().required(), + channels: yup.array().of(vkChannelSchema), + }) + ), + });