├── .npmignore ├── renovate.json ├── .gitignore ├── src ├── decorators │ ├── metadata │ │ ├── VkControllerMetadata.ts │ │ ├── AnyMetadata.ts │ │ ├── GetPayloadMetadata.ts │ │ └── GetMetadata.ts │ ├── VkController.ts │ ├── index.ts │ ├── Update.ts │ ├── Voice.ts │ ├── Payload.ts │ ├── Sticker.ts │ ├── PollError.ts │ ├── CommandNotFound.ts │ ├── GetPayload.ts │ ├── Get.ts │ ├── StoreMetadata.ts │ └── builder.ts ├── interfaces │ ├── Message.ts │ ├── UserEvent.ts │ ├── UploadedPhoto.ts │ ├── APIResponses.ts │ └── MessageSendParams.ts ├── Keyboard.ts ├── functions │ └── poll.ts └── index.ts ├── examples ├── decorators │ ├── decorators.ts │ └── controllers │ │ └── TestController.ts ├── longpoll.ts ├── upload_pic.js ├── keyboards.js └── callback_api.js ├── .travis.yml ├── tsconfig.json ├── LICENSE.txt ├── package.json ├── Gruntfile.js ├── tslint.json ├── test └── test.ts ├── README_ru.md └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | .baseDir.* 2 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .grunt 2 | node_modules 3 | *.log 4 | .vscode 5 | .tscache 6 | .baseDir.* 7 | build -------------------------------------------------------------------------------- /src/decorators/metadata/VkControllerMetadata.ts: -------------------------------------------------------------------------------- 1 | export interface VkControllerMetadata { 2 | target: Function 3 | } -------------------------------------------------------------------------------- /src/decorators/metadata/AnyMetadata.ts: -------------------------------------------------------------------------------- 1 | export interface AnyMetadata { 2 | target: any 3 | propertyKey: string 4 | name: string 5 | } -------------------------------------------------------------------------------- /src/decorators/metadata/GetPayloadMetadata.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface GetPayloadMetadata { 3 | target: any 4 | propertyKey: string 5 | jsonString: string 6 | } -------------------------------------------------------------------------------- /examples/decorators/decorators.ts: -------------------------------------------------------------------------------- 1 | import {Bot} from '../../src' 2 | 3 | const bot = new Bot({ 4 | token: 'TOKEN', 5 | group_id: 123456, 6 | controllers: [__dirname + '/controllers/*.ts'] 7 | }).start() -------------------------------------------------------------------------------- /src/decorators/metadata/GetMetadata.ts: -------------------------------------------------------------------------------- 1 | import UserEvent from '../../interfaces/UserEvent' 2 | 3 | export interface GetMetadata { 4 | target: any 5 | propertyKey: string 6 | pattern: UserEvent['pattern'] 7 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "7" 5 | before_install: 6 | - npm install -g grunt 7 | install: 8 | - npm install 9 | before_script: 10 | - grunt 11 | script: 12 | - npm test 13 | -------------------------------------------------------------------------------- /src/decorators/VkController.ts: -------------------------------------------------------------------------------- 1 | import StoreMetadata from './StoreMetadata' 2 | 3 | export function VkController(): Function { 4 | return function (target, propertyKey: string, descriptor: PropertyDescriptor) { 5 | StoreMetadata.vkControllerMetadata.push({target}) 6 | } 7 | } -------------------------------------------------------------------------------- /src/interfaces/Message.ts: -------------------------------------------------------------------------------- 1 | export default interface Message { 2 | id: number, 3 | peer_id: number, 4 | date: number, 5 | text: string, 6 | from_id: number, 7 | attachments: any, 8 | important: boolean, 9 | conversation_message_id: number, 10 | fwd_messages: any, 11 | payload?: string 12 | } 13 | -------------------------------------------------------------------------------- /src/interfaces/UserEvent.ts: -------------------------------------------------------------------------------- 1 | import Message from './Message' 2 | import MessageSendParams from './MessageSendParams' 3 | import { VKResponse } from './APIResponses' 4 | import { replyFunc } from '..' 5 | 6 | export default interface UserEvent { 7 | pattern: RegExp, 8 | listener(msg?: Message, exec?: RegExpExecArray, reply?: replyFunc): void 9 | } -------------------------------------------------------------------------------- /src/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Get' 2 | export * from './VkController' 3 | export * from './CommandNotFound' 4 | export * from './Sticker' 5 | export * from './Update' 6 | export * from './Payload' 7 | export * from './PollError' 8 | export * from './Voice' 9 | export * from './StoreMetadata' 10 | export * from './metadata/AnyMetadata' -------------------------------------------------------------------------------- /src/decorators/Update.ts: -------------------------------------------------------------------------------- 1 | import StoreMetadata from './StoreMetadata' 2 | 3 | export function Update() { 4 | return function (target, propertyKey: string, descriptor: PropertyDescriptor) { 5 | StoreMetadata.anyMetadata.push({ 6 | name: 'update', 7 | propertyKey, 8 | target 9 | }) 10 | } 11 | } -------------------------------------------------------------------------------- /src/decorators/Voice.ts: -------------------------------------------------------------------------------- 1 | import StoreMetadata from './StoreMetadata' 2 | 3 | export function Voice() { 4 | return function (target, propertyKey: string, descriptor: PropertyDescriptor) { 5 | StoreMetadata.anyMetadata.push({ 6 | name: 'voice', 7 | propertyKey, 8 | target 9 | }) 10 | } 11 | } -------------------------------------------------------------------------------- /src/decorators/Payload.ts: -------------------------------------------------------------------------------- 1 | import StoreMetadata from './StoreMetadata' 2 | 3 | export function Payload() { 4 | return function (target, propertyKey: string, descriptor: PropertyDescriptor) { 5 | StoreMetadata.anyMetadata.push({ 6 | name: 'payload', 7 | propertyKey, 8 | target 9 | }) 10 | } 11 | } -------------------------------------------------------------------------------- /src/decorators/Sticker.ts: -------------------------------------------------------------------------------- 1 | import StoreMetadata from './StoreMetadata' 2 | 3 | export function Sticker() { 4 | return function (target, propertyKey: string, descriptor: PropertyDescriptor) { 5 | StoreMetadata.anyMetadata.push({ 6 | name: 'sticker', 7 | propertyKey, 8 | target 9 | }) 10 | } 11 | } -------------------------------------------------------------------------------- /src/decorators/PollError.ts: -------------------------------------------------------------------------------- 1 | import StoreMetadata from './StoreMetadata' 2 | 3 | export function PollError() { 4 | return function (target, propertyKey: string, descriptor: PropertyDescriptor) { 5 | StoreMetadata.anyMetadata.push({ 6 | name: 'poll-error', 7 | propertyKey, 8 | target 9 | }) 10 | } 11 | } -------------------------------------------------------------------------------- /src/decorators/CommandNotFound.ts: -------------------------------------------------------------------------------- 1 | import StoreMetadata from './StoreMetadata' 2 | 3 | export function CommandNotFound() { 4 | return function (target, propertyKey: string, descriptor: PropertyDescriptor) { 5 | StoreMetadata.anyMetadata.push({ 6 | name: 'command-notfound', 7 | propertyKey, 8 | target 9 | }) 10 | } 11 | } -------------------------------------------------------------------------------- /src/decorators/GetPayload.ts: -------------------------------------------------------------------------------- 1 | import StoreMetadata from './StoreMetadata' 2 | 3 | export function GetPayload(jsonString: string) { 4 | return function (target, propertyKey: string, descriptor: PropertyDescriptor) { 5 | StoreMetadata.getPayloadMetadata.push({ 6 | jsonString, 7 | propertyKey, 8 | target 9 | }) 10 | } 11 | } -------------------------------------------------------------------------------- /src/interfaces/UploadedPhoto.ts: -------------------------------------------------------------------------------- 1 | export default interface UploadedPhoto { 2 | id: number, 3 | album_id: number, 4 | owner_id: number, 5 | photo_75?: string, 6 | photo_130?: string, 7 | photo_604?: string, 8 | photo_807?: string, 9 | photo_1280?: string, 10 | photo_2560?: string, 11 | width?: number, 12 | height?: number, 13 | text?: string, 14 | date: number 15 | } -------------------------------------------------------------------------------- /src/interfaces/APIResponses.ts: -------------------------------------------------------------------------------- 1 | export type VKResponse = any 2 | 3 | export interface VKError { 4 | method?: string 5 | error_code: number, 6 | error_msg: string, 7 | request_params?: { 8 | [prop: number]: { key: string, value: string } 9 | } 10 | } 11 | 12 | export interface VKExecuteResponse { 13 | response?: VKResponse, 14 | execute_errors?: VKError[], 15 | error?: VKError 16 | } 17 | -------------------------------------------------------------------------------- /src/decorators/Get.ts: -------------------------------------------------------------------------------- 1 | import UserEvent from '../interfaces/UserEvent' 2 | import StoreMetadata from './StoreMetadata' 3 | 4 | export function Get(pattern: UserEvent['pattern']) { 5 | return function (target, propertyKey: string, descriptor: PropertyDescriptor) { 6 | StoreMetadata.getMetadata.push({ 7 | pattern, 8 | propertyKey, 9 | target 10 | }) 11 | } 12 | } -------------------------------------------------------------------------------- /examples/longpoll.ts: -------------------------------------------------------------------------------- 1 | import { Bot, Message } from '..' 2 | import * as path from 'path' 3 | 4 | const bot = new Bot({ 5 | token: 'token', 6 | group_id: 123456 7 | }) 8 | .start() 9 | .get(/cat|kitten/, async (msg, exec, reply) => { 10 | const photoPath = path.join(__dirname, 'kitten.jpg') 11 | const photo = await bot.uploadPhoto(photoPath) 12 | reply('Take this', { 13 | attachment: `photo${photo.owner_id}_${photo.id}` 14 | }) 15 | }) -------------------------------------------------------------------------------- /examples/upload_pic.js: -------------------------------------------------------------------------------- 1 | // Отправляет картинку на любое входящее сообщение 2 | 3 | const { Bot } = require('node-vk-bot') 4 | const path = require('path') 5 | 6 | const bot = new Bot({ 7 | token: 'TOKEN', 8 | group_id: 123456 9 | }).start() 10 | 11 | bot.on('command-notfound', msg => { 12 | bot.uploadPhoto(path.join(__dirname, './kitten.jpg')) 13 | .then(photo => bot.send('', msg.peer_id, { 14 | attachment: `photo${photo.owner_id}_${photo.id}` 15 | })) 16 | }) -------------------------------------------------------------------------------- /src/interfaces/MessageSendParams.ts: -------------------------------------------------------------------------------- 1 | import Keyboard from '../Keyboard' 2 | 3 | export default interface MessageSendParams { 4 | message?: string, 5 | peer_id?: number, 6 | random_id?: number, 7 | domain?: string, 8 | chat_id?: string, 9 | user_ids?: string, // list of comma-separated numbers, max 100 10 | lat?: number, 11 | long?: number, 12 | attachment?: string, 13 | forward_messages?: string | number, 14 | sticker_id?: number, 15 | keyboard?: string | Keyboard, 16 | payload?: any 17 | } -------------------------------------------------------------------------------- /examples/keyboards.js: -------------------------------------------------------------------------------- 1 | const { Bot, Keyboard, KeyboardColor } = require('..'); 2 | const bot = new Bot({ 3 | token: 'Community API token', 4 | group_id: 123456 5 | }).start(); 6 | 7 | bot.get(/Hi|Hello|Hey/i, message => { 8 | const keyboard = new Keyboard(true) 9 | .addButton('Red', KeyboardColor.NEGATIVE) 10 | .addButton('Green', KeyboardColor.POSITIVE) 11 | 12 | bot.send('Hello!', message.peer_id, keyboard) 13 | }); 14 | 15 | bot.get(/Red|Green/, msg => bot.send('You clicked a button - ' + msg.body, msg.peer_id)) -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "lib": [ 6 | "es6", 7 | "es2016" 8 | ], 9 | "target": "es5", 10 | "noImplicitAny": false, 11 | "sourceMap": false, 12 | "declaration": true, 13 | "rootDir": "./src", 14 | "outDir": "./build", 15 | "experimentalDecorators": true, 16 | "emitDecoratorMetadata": true 17 | }, 18 | "exclude": [ 19 | "./examples", 20 | "./node_modules", 21 | "./test", 22 | "./build" 23 | ] 24 | } -------------------------------------------------------------------------------- /examples/decorators/controllers/TestController.ts: -------------------------------------------------------------------------------- 1 | import {Bot, Get, Sticker, VkController, Voice} from '../../../src/' 2 | 3 | @VkController() 4 | export class TestController { 5 | constructor(private bot: Bot) {} 6 | 7 | @Get(/hi/) 8 | hi(message, exec, reply) { 9 | reply('Hello!') 10 | } 11 | 12 | @Voice() 13 | voice(msg) { 14 | this.bot.send('Wooow is voice message!', msg.peer_id) 15 | } 16 | 17 | @Sticker() 18 | sticker(msg) { 19 | this.bot.send('Wooow is sticker!', msg.peer_id) 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/decorators/StoreMetadata.ts: -------------------------------------------------------------------------------- 1 | import {VkControllerMetadata} from './metadata/VkControllerMetadata' 2 | import {GetMetadata} from './metadata/GetMetadata' 3 | import {AnyMetadata} from './metadata/AnyMetadata' 4 | import {GetPayloadMetadata} from './metadata/GetPayloadMetadata' 5 | 6 | class StoreMetadata { 7 | public getPayloadMetadata: GetPayloadMetadata[] = [] 8 | public getMetadata: GetMetadata[] = [] 9 | public vkControllerMetadata: VkControllerMetadata[] = [] 10 | public anyMetadata: AnyMetadata[] = [] 11 | 12 | } 13 | export default new StoreMetadata() 14 | -------------------------------------------------------------------------------- /examples/callback_api.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | const { Bot } = require('..'); 4 | const bot = new Bot({ 5 | token: 'Community API token', 6 | group_id: 123456 7 | }); 8 | 9 | const port = 8000; 10 | const app = express(); 11 | 12 | app.use(bodyParser.json()); 13 | 14 | app.post('/bot', (req, res) => { 15 | if (req.body.type == 'confirmation') return res.send('CONFIRMATION CODE'); 16 | bot.processUpdate(req.body); 17 | res.sendStatus(200); 18 | }); 19 | 20 | app.listen(port, () => { 21 | console.log(`Express server is listening on ${port}`); 22 | }); 23 | 24 | bot.get(/Hi|Hello|Hey/i, message => { 25 | bot.send('Hello!', message.peer_id); 26 | }); 27 | -------------------------------------------------------------------------------- /src/Keyboard.ts: -------------------------------------------------------------------------------- 1 | export enum KeyboardColor { 2 | PRIMARY = 'primary', 3 | DEFAULT = 'default', 4 | NEGATIVE = 'negative', 5 | POSITIVE = 'positive' 6 | } 7 | 8 | /** 9 | * Класс для создания клавиатуры для бота 10 | */ 11 | export default class Keyboard { 12 | private obj: any 13 | 14 | constructor(oneTime = false) { this.obj = { one_time: oneTime, buttons: [] } } 15 | 16 | addButton(label: string, color: string = KeyboardColor.DEFAULT, payload = null) { 17 | if (!this.obj.buttons.length) this.obj.buttons.push([]) 18 | 19 | let lastRow = this.obj.buttons[this.obj.buttons.length - 1] 20 | if (lastRow.length === 4) throw new Error('Maximum amount of buttons in one row = 4') 21 | 22 | lastRow.push({ action: { type: 'text', label, payload }, color }) 23 | 24 | return this 25 | } 26 | 27 | addRow() { 28 | if (this.obj.buttons.length === 10) throw new Error('Maximum amount of rows = 10') 29 | this.obj.buttons.push([]) 30 | 31 | return this 32 | } 33 | 34 | toString() { return JSON.stringify(this.obj) } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Vitaly Volynsky 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-vk-bot", 3 | "version": "1.2.3", 4 | "description": "Create and control VK bots easily.", 5 | "main": "./build/index.js", 6 | "typings": "./build/index.d.ts", 7 | "scripts": { 8 | "test": "grunt test" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/vitalyavolyn/node-vk-bot" 13 | }, 14 | "keywords": [ 15 | "VK", 16 | "bot", 17 | "api", 18 | "ВК", 19 | "вконтакте" 20 | ], 21 | "author": "Vitaly Volynsky", 22 | "license": "MIT", 23 | "devDependencies": { 24 | "@types/mocha": "5.2.5", 25 | "@types/node": "7.10.2", 26 | "@types/request": "2.48.1", 27 | "@types/request-promise-native": "1.0.15", 28 | "grunt": "1.0.3", 29 | "grunt-contrib-clean": "2.0.0", 30 | "grunt-mocha-test": "0.13.3", 31 | "grunt-ts": "6.0.0-beta.21", 32 | "grunt-tslint": "5.0.2", 33 | "mocha": "5.2.0", 34 | "ts-node": "7.0.1", 35 | "tslint": "5.11.0", 36 | "typescript": "3.2.1", 37 | "@types/glob": "7.1.1" 38 | }, 39 | "dependencies": { 40 | "glob": "^7.1.3", 41 | "request": "^2.87.0", 42 | "request-promise-native": "^1.0.5" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.initConfig({ 3 | clean: ['build'], 4 | tslint: { 5 | options: { 6 | // can be a configuration object or a filepath to tslint.json 7 | configuration: 'tslint.json', 8 | // If set to true, tslint errors will be reported, but not fail the task 9 | // If set to false, tslint errors will be reported, and the task will fail 10 | force: false, 11 | fix: false 12 | }, 13 | files: { 14 | src: [ 15 | 'src/**/*.ts' 16 | ] 17 | } 18 | }, 19 | ts: { 20 | default: { 21 | tsconfig: true 22 | } 23 | }, 24 | mochaTest: { 25 | test: { 26 | options: { 27 | require: 'ts-node/register', 28 | timeout: 5000 29 | }, 30 | src: ['test/**/*.ts'] 31 | } 32 | } 33 | }) 34 | 35 | grunt.loadNpmTasks('grunt-contrib-clean') 36 | grunt.loadNpmTasks('grunt-tslint') 37 | grunt.loadNpmTasks('grunt-ts') 38 | grunt.loadNpmTasks('grunt-mocha-test') 39 | 40 | grunt.registerTask('default', ['clean', 'ts']) 41 | grunt.registerTask('test', ['tslint', 'mochaTest']) 42 | } 43 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "comment-format": [ 5 | true, 6 | "check-space" 7 | ], 8 | "indent": [ 9 | true, 10 | "spaces" 11 | ], 12 | "no-eval": true, 13 | "no-internal-module": true, 14 | "no-trailing-whitespace": true, 15 | "no-unsafe-finally": true, 16 | "no-var-keyword": true, 17 | "one-line": [ 18 | true, 19 | "check-open-brace", 20 | "check-whitespace" 21 | ], 22 | "quotemark": [ 23 | true, 24 | "single" 25 | ], 26 | "semicolon": [ 27 | true, 28 | "never" 29 | ], 30 | "triple-equals": [ 31 | true, 32 | "allow-null-check" 33 | ], 34 | "typedef-whitespace": [ 35 | true, 36 | { 37 | "call-signature": "nospace", 38 | "index-signature": "nospace", 39 | "parameter": "nospace", 40 | "property-declaration": "nospace", 41 | "variable-declaration": "nospace" 42 | } 43 | ], 44 | "variable-name": [ 45 | true, 46 | "ban-keywords" 47 | ], 48 | "whitespace": [ 49 | true, 50 | "check-branch", 51 | "check-decl", 52 | "check-operator", 53 | "check-separator", 54 | "check-type" 55 | ], 56 | "array-type": [ 57 | true, 58 | "array" 59 | ], 60 | "jsdoc-format": true 61 | } 62 | } -------------------------------------------------------------------------------- /src/functions/poll.ts: -------------------------------------------------------------------------------- 1 | import * as rq from 'request-promise-native' 2 | 3 | const DEFAULT_DELAY = 1000 / 3 // 1/3 of a second 4 | const LOST_HISTORY_ERROR = 1 5 | 6 | const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) 7 | 8 | export default function poll(bot, delay: number = DEFAULT_DELAY) { 9 | return bot.api('groups.getLongPollServer', { group_id: bot.options.group_id }) 10 | .then(res => { 11 | return request(`${res.server}?act=a_check&key=${res.key}` + 12 | `&wait=25&version=1&ts=${res.ts}`, delay) 13 | }) 14 | .catch(error => { 15 | bot.emit('poll-error', error) 16 | 17 | // restart on error 18 | return poll(bot, delay) 19 | }) 20 | 21 | function request(url, delay: number) { 22 | return rq(url, { json: true }) 23 | .then(res => { 24 | if (!res || !res.ts || (res.failed && res.failed !== LOST_HISTORY_ERROR)) 25 | throw new Error('response of the Long Poll server isn\'t valid ' + 26 | `(${JSON.stringify(res)})`) 27 | 28 | if (res.failed && res.failed === LOST_HISTORY_ERROR) 29 | bot.emit('poll-error', new Error('event history went out of date or was partially lost')) 30 | 31 | url = url.replace(/ts=.*/, `ts=${res.ts}`) // set new timestamp 32 | 33 | if (!res.failed && res.updates && res.updates.length > 0) { 34 | for (let update of res.updates) { 35 | if (update.type === 'message_new') bot.emit('update', update) 36 | } 37 | } 38 | 39 | if (bot._stop) return null 40 | return delay !== 0 41 | ? sleep(delay).then(() => request(url, delay)) 42 | : request(url, delay) 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/decorators/builder.ts: -------------------------------------------------------------------------------- 1 | import {Bot} from '../index' 2 | import * as path from 'path' 3 | import * as glob from 'glob' 4 | import StoreMetadata from './StoreMetadata' 5 | export function build(bot: Bot, controllers: string[]) { 6 | if (!(controllers as any[]).every(value => value instanceof Function)) 7 | (controllers as string[]).forEach(value => glob.sync(path.normalize(value)).filter(file => 8 | file.substring(file.length - 5, file.length) !== '.d.ts' 9 | ).forEach(dir => require(dir))) 10 | 11 | StoreMetadata.vkControllerMetadata.forEach(controller => { 12 | let controllerInstance = new (controller.target as any)(bot) 13 | 14 | StoreMetadata.getMetadata 15 | .filter(get => get.target === controller.target.prototype) 16 | .forEach(getMetadata => { 17 | bot.get(getMetadata.pattern, function(...args) { 18 | controllerInstance[getMetadata.propertyKey](...args) 19 | }) 20 | }) 21 | StoreMetadata.getPayloadMetadata 22 | .filter(get => get.target === controller.target.prototype) 23 | .forEach(getMetadata => { 24 | bot.getPayload(getMetadata.jsonString, function(...args) { 25 | controllerInstance[getMetadata.propertyKey](...args) 26 | }) 27 | }) 28 | 29 | StoreMetadata.anyMetadata 30 | .filter(get => get.target === controller.target.prototype) 31 | .forEach(anyMetadata => { 32 | bot.on(anyMetadata.name, function(...args) { 33 | controllerInstance[anyMetadata.propertyKey](...args) 34 | }) 35 | }) 36 | }) 37 | } -------------------------------------------------------------------------------- /test/test.ts: -------------------------------------------------------------------------------- 1 | import * as rq from 'request-promise-native' 2 | import * as assert from 'assert' 3 | import { Bot } from '../src' 4 | 5 | import 'mocha' 6 | 7 | const botToken = process.env.COMMUNITY_TOKEN 8 | const userToken = process.env.USER_TOKEN 9 | const id = Number(process.env.GROUP_ID) 10 | 11 | describe('Bot', () => { 12 | const bot = new Bot({ token: botToken, group_id: id, api: {} }) 13 | let peer 14 | 15 | bot.start() 16 | 17 | it('Gets LongPoll updates', done => { 18 | bot.on('update', update => { 19 | peer = update.object.from_id 20 | done() 21 | }) 22 | 23 | rq('https://api.vk.com/method/messages.send', { 24 | form: { 25 | message: 'test', 26 | access_token: userToken, 27 | peer_id: -id, 28 | v: '5.80' 29 | }, 30 | method: 'POST', 31 | json: true 32 | }) 33 | }) 34 | 35 | describe('#send', () => { 36 | it('Sends message', () => { 37 | return bot.send('test', peer) 38 | }) 39 | }) 40 | 41 | describe('#api', () => { 42 | describe('Languages', () => { 43 | it('Gets response in Russian', () => { 44 | bot.options.api.lang = 'ru' 45 | return bot.api('users.get', { user_ids: 1 }) 46 | .then(res => res[0]) 47 | .then(durov => { 48 | assert.equal(durov.first_name, 'Павел') 49 | }) 50 | }) 51 | 52 | it('Gets response in English', () => { 53 | bot.options.api.lang = 'en' 54 | return bot.api('users.get', { user_ids: 1 }) 55 | .then(res => res[0]) 56 | .then(durov => { 57 | assert.equal(durov.first_name, 'Pavel') 58 | }) 59 | }) 60 | }) 61 | 62 | it('Throws error when using old API version', () => { 63 | assert.throws(() => new Bot({ token: botToken, group_id: id, api: { v: '5.79' } }), Error) 64 | }) 65 | }) 66 | }) -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events' 2 | import * as rq from 'request-promise-native' 3 | import * as fs from 'fs' 4 | import * as stream from 'stream' 5 | 6 | import poll from './functions/poll' 7 | 8 | import { VKError, VKExecuteResponse, VKResponse } from './interfaces/APIResponses' 9 | import UploadedPhoto from './interfaces/UploadedPhoto' 10 | import Message from './interfaces/Message' 11 | import UserEvent from './interfaces/UserEvent' 12 | import MessageSendParams from './interfaces/MessageSendParams' 13 | import { build } from './decorators/builder' 14 | import Keyboard, { KeyboardColor } from './Keyboard' 15 | 16 | export interface Options { 17 | token: string, 18 | api?: { lang?: string, v?: string }, 19 | group_id: number, 20 | controllers?: string[] 21 | } 22 | 23 | export type replyFunc = (text?: string, params?: MessageSendParams) => Promise 24 | 25 | export class Bot extends EventEmitter { 26 | _events: Object = {} 27 | _userEvents: UserEvent[] = [] 28 | _stop: boolean = false 29 | 30 | constructor(public options: Options) { 31 | super() 32 | 33 | if (!options.token) throw new Error('token can\'t be empty') 34 | if (!options.group_id) throw new Error('group_id can\'t be empty') 35 | if (options.api && Number(options.api.v) < 5.80) throw new Error('API version must be > 5.80') 36 | if (options.controllers && options.controllers.length) build(this, options.controllers) 37 | } 38 | 39 | /** 40 | * Access VK API 41 | */ 42 | api(method: string, params: any = {}): Promise { 43 | let o = this.options 44 | if (o.api) { 45 | params.v = params.v || o.api.v || '5.80' 46 | params.lang = params.lang || o.api.lang 47 | if (params.lang == null) delete params.lang 48 | } else params.v = params.v || '5.80' 49 | 50 | params.access_token = this.options.token 51 | 52 | return rq({ 53 | baseUrl: 'https://api.vk.com', 54 | uri: '/method/' + method, 55 | form: params, 56 | method: 'POST', 57 | json: true 58 | }).then(res => { 59 | if (/execute/.test(method)) { // there may be errors and responses in same object 60 | return res as VKExecuteResponse 61 | } else if (res.error) { 62 | throw res.error as VKError 63 | } 64 | return res.response as VKResponse 65 | }) 66 | } 67 | 68 | /** 69 | * Send messages 70 | */ 71 | send(text: string, peer: number, params: MessageSendParams = {}): Promise { 72 | params.message = params.message || text 73 | params.peer_id = params.peer_id || peer 74 | 75 | if (params.keyboard && params.keyboard instanceof Keyboard) { 76 | params.keyboard = params.keyboard.toString() 77 | } 78 | 79 | return this.api('messages.send', params) 80 | } 81 | 82 | /** 83 | * Process Callback API response when using webhook 84 | */ 85 | processUpdate(res: any) { 86 | if (res.type === 'message_new') return this._update(res) 87 | } 88 | 89 | /** 90 | * Start polling 91 | */ 92 | start(poll_delay?: number) { 93 | this.on('update', this._update) 94 | poll(this, poll_delay) 95 | return this 96 | } 97 | 98 | stop() { 99 | this._stop = true 100 | this.removeListener('update', this._update) 101 | this._events = {} 102 | return this 103 | } 104 | 105 | /** 106 | * Listens on specific message matching the RegExp pattern 107 | */ 108 | get(pattern: UserEvent['pattern'], listener: UserEvent['listener']) { 109 | this._userEvents.push({ 110 | pattern, listener 111 | }) 112 | return this 113 | } 114 | 115 | getPayload(jsonString: string, listener: (msg?: Message, reply?: replyFunc) => void) { 116 | this.on('payload', (msg, reply) => { 117 | if (JSON.stringify(JSON.parse(msg.payload)) === (jsonString)) { 118 | listener(msg, reply) 119 | } 120 | }) 121 | } 122 | 123 | /** 124 | * Upload photo 125 | */ 126 | uploadPhoto(photo: string | stream.Stream): Promise { 127 | let photoStream: stream.Stream 128 | if (typeof photo === 'string') { 129 | photoStream = fs.createReadStream(photo) 130 | } else if (photo instanceof stream.Stream) { 131 | photoStream = photo 132 | } 133 | 134 | return this.api('photos.getMessagesUploadServer') 135 | .then(server => rq({ 136 | method: 'POST', 137 | uri: server.upload_url, 138 | formData: { 139 | photo: photoStream 140 | }, 141 | json: true 142 | })) 143 | .then(upload => this.api('photos.saveMessagesPhoto', { 144 | server: upload.server, 145 | photo: upload.photo, 146 | hash: upload.hash 147 | })) 148 | .then(photos => photos[0] as UploadedPhoto) 149 | } 150 | 151 | /** 152 | * The internal update event listener, 153 | * used to parse messages and fire 154 | * get events - YOU SHOULD NOT USE THIS 155 | */ 156 | private _update(update: { object?, group_id: number }) { 157 | if (update.group_id !== this.options.group_id) return 158 | 159 | let msg = update.object || false 160 | if (!msg) return false 161 | 162 | const hasAttachments: boolean = msg.attachments && msg.attachments.length || false 163 | 164 | const message: Message = { 165 | id: msg.id, 166 | peer_id: msg.peer_id, 167 | from_id: msg.from_id, 168 | date: msg.date, 169 | text: msg.text, 170 | attachments: msg.attachments, 171 | important: msg.important, 172 | conversation_message_id: msg.conversation_message_id, 173 | fwd_messages: msg.fwd_messages 174 | } 175 | 176 | const reply = (text: string, params: MessageSendParams = {}) => this.send(text, message.peer_id, params) 177 | 178 | if (msg.payload) { 179 | message.payload = msg.payload 180 | this.emit('payload', msg, reply) 181 | } 182 | 183 | if (hasAttachments && msg.attachments[0].type === 'sticker') return this.emit('sticker', message) 184 | 185 | const isAudioMsg = hasAttachments && 186 | msg.attachments[0].type === 'doc' && 187 | msg.attachments[0].doc.preview && 188 | msg.attachments[0].doc.preview.audio_msg 189 | 190 | if (isAudioMsg) return this.emit('voice', message) 191 | 192 | if (!message.text && !hasAttachments) return 193 | 194 | const ev = this._userEvents.find(({ pattern }) => pattern.test(message.text)) 195 | 196 | if (!ev) { 197 | this.emit('command-notfound', message) 198 | return 199 | } 200 | 201 | ev.listener( 202 | message, 203 | ev.pattern.exec(message.text), 204 | reply 205 | ) 206 | } 207 | } 208 | 209 | export { 210 | Message, 211 | UploadedPhoto, 212 | VKError, 213 | VKExecuteResponse, 214 | VKResponse, 215 | UserEvent, 216 | MessageSendParams, 217 | Keyboard, 218 | KeyboardColor 219 | } 220 | 221 | export * from './decorators' 222 | -------------------------------------------------------------------------------- /README_ru.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/vitalyavolyn/node-vk-bot.svg?branch=master)](https://travis-ci.org/vitalyavolyn/node-vk-bot) 2 | 3 | # Боты ВКонтакте 4 | Библиотека для создания чат-ботов ВК. 5 | Для получения новых сообщений используется LongPoll или Callback API. 6 | 7 | ```sh 8 | npm install --save node-vk-bot 9 | ``` 10 | 11 | Не забудьте выполнить `npm i` при клонировании репозитория 12 | 13 | # Пример 14 | ```javascript 15 | const { Bot, Keyboard } = require('node-vk-bot') 16 | 17 | const bot = new Bot({ 18 | token: 'YOUR TOKEN', 19 | group_id: 123456 20 | }).start() 21 | 22 | bot.get(/Hi|Hello|Hey/i, (message, exec, reply) => { 23 | const keyboard = new Keyboard().addButton('Hi!') 24 | const options = { forward_messages: message.id, keyboard } 25 | 26 | reply('Hello!', options) 27 | }) 28 | ``` 29 | 30 | [Больше примеров](https://github.com/vitalyavolyn/node-vk-bot/tree/master/examples) (как использовать вебхуки, загружать фото, ...) 31 | 32 | # Боты, созданные с помощью этой библиотеки 33 | если вы хотите добавить своего в этот список, просто сделайте pull request 34 | 35 | - [**GitHub Events**](https://vk.com/githubbot) - Оповещения о новых коммитах, issues и т.д. в сообщения ВК _by [@vitalyavolyn](https://github.com/vitalyavolyn)_ 36 | 37 | # Содержание 38 | - [Начало работы](#getting-started) 39 | - [`Bot`](#bot) 40 | - [Methods](#methods) 41 | - [`start`](#start) 42 | - [`get`](#get) 43 | - [`getPayload`](#getPayload) 44 | - [`send`](#send) 45 | - [`uploadPhoto`](#uploadPhoto) 46 | - [`api`](#api) 47 | - [`processUpdate`](#processUpdate) 48 | - [`stop`](#stop) 49 | - [Events](#events) 50 | - [update](#update) 51 | - [voice](#voice) 52 | - [sticker](#sticker) 53 | - [payload](#payload) 54 | - [poll-error](#poll-error) 55 | - [command-notfound](#command-notfound) 56 | - [Keyboard](#keyboard) 57 | - [addButton](#addButton) 58 | - [addRow](#addRow) 59 | - [toString](#toString) 60 | - [KeyboardColor](#KeyboardColor) 61 | - [The `Message` Object](#the-message-object) 62 | 63 | ## Начало работы 64 | В примере выше показан бот, который лишь отвечает на приветствия 65 | 66 | Разберем код примера c LongPoll 67 | 1. Создаем бота, используя класс `Bot`. 68 | 2. Вызов `bot.start()` начинает получение новых сообщений. 69 | 3. Затем мы тестируем каждое входящее сообщение с помощью `/Hi|Hello|Hey/i`, и если сообщеине подходит, отвечаем на него. 70 | 71 | ------- 72 | 73 | ## Bot 74 | Класс, используемый для создания ботов, принимает один аргумент: `options`. 75 | 76 | ```javascript 77 | new Bot({ 78 | token: 'TOKEN', 79 | group_id: 123456, 80 | api: { 81 | v: 5.80, // >= 5.80 82 | lang: 'ru' 83 | } 84 | }) 85 | ``` 86 | 87 | | Параметр | Тип | Обязателен? | 88 | |-----------|:----:|---------:| 89 | | token | String | Да | 90 | | group_id | Number | Да | 91 | | api | Object| Нет | 92 | | controllers | String[] | Нет | 93 | 94 | `api` - объект с настройками API: **v**ersion и **lang**uage. (и то, и то - строки) ([узнать больше](https://vk.com/dev/api_requests)) 95 | 96 | ------- 97 | 98 | ### Методы 99 | #### start 100 | Начинает получение новых сообщений используя LongPoll. 101 | Бот вызывает событие `update` после получения сообщений с сервера. 102 | [Примеры объектов событий](https://vk.com/dev/using_longpoll). 103 | 104 | ------- 105 | 106 | #### get 107 | Проверяет полученные сообщения RegExp'ом 108 | ```javascript 109 | bot.get(/Hello/i, (msg, exec, reply) => { 110 | console.log(msg) 111 | reply('Hi!') 112 | }) 113 | 114 | Аргументы callback: 115 | msg - [объект сообщения](#the-message-object) 116 | exec - результат `pattern.exec(text)` 117 | reply - Функция для ответа на сообщение, принимает текст сообщения и необязательные параметры message.send 118 | ``` 119 | 120 | Аргументы, с которыми вызывается callback - объект [`Message`](#the-message-object) и результат `pattern.exec(text)` (где `text` - текст сообщения). 121 | 122 | ------- 123 | 124 | #### getPayload 125 | То же самое, что и get, но для payload клавиатур 126 | 127 | Это синтаксический сахар для [события `payload`](#payload) 128 | 129 | ``` 130 | bot.getPayload('{"command": "start"}', (msg, reply) => console.log(msg)) 131 | ``` 132 | 133 | Аргументы: строка json и callback 134 | 135 | ------- 136 | 137 | #### send 138 | Отправляет сообщение. 139 | 140 | ```javascript 141 | bot.send('text', peer_id, params) 142 | ``` 143 | 144 | ------- 145 | 146 | #### uploadPhoto 147 | Загружает фото. 148 | 149 | Принимает абсолютный путь к изображению или `Stream` 150 | Возвращает Promise с [объектом фотографии](https://vk.com/dev/photos.saveMessagesPhoto) 151 | ```javascript 152 | bot.uploadPhoto('~/kittens.png').then(photo => { 153 | console.log(photo) 154 | }) 155 | 156 | let stream = fs.createReadStream('./kittens.png') 157 | bot.uploadPhoto(stream).then(photo => { 158 | console.log(photo) 159 | }) 160 | ``` 161 | 162 | ------- 163 | 164 | #### api 165 | Доступ к VK API. 166 | 167 | ```javascript 168 | bot.api('users.get', { user_ids: 1 }) 169 | .then(res => console.log(res[0].first_name)) // Павел 170 | ``` 171 | 172 | При использовании метода API `execute`, функция возвращает результат в том же виде, в котором о н пришел с сервера. 173 | 174 | ------- 175 | 176 | #### processUpdate 177 | Функция для использования Callback API. 178 | Пример в папке `examples` 179 | 180 | ------- 181 | 182 | #### stop 183 | Остановить проверку новых сообщений 184 | 185 | ```javascript 186 | bot.stop() 187 | ``` 188 | 189 | ------- 190 | 191 | ### События 192 | #### update 193 | Вызывается при получении новых соообщений с LongPoll сервера 194 | 195 | ```javascript 196 | bot.on('update', update => { 197 | if (update.from_id === 1) { 198 | console.log('Got a message from Pavel Durov!'); 199 | } 200 | }) 201 | ``` 202 | ------- 203 | 204 | #### voice 205 | Вызывется при получении голосового сообщения (возвращает объект `Message`). 206 | 207 | ------- 208 | 209 | #### sticker 210 | Вызывется при получении стикера (возвращает объект `Message`). 211 | 212 | ------- 213 | 214 | #### payload 215 | Вызывается при получении сообщения с json payload (используется в клавиатурах) 216 | Возвращает объект `Message` и функцию [reply](#get) 217 | 218 | ------- 219 | 220 | #### poll-error 221 | Вызывается при ошибке при использовании LongPoll 222 | 223 | ```javascript 224 | bot.on('poll-error', error => { 225 | console.error('error occurred on a working with the Long Poll server ' + 226 | `(${util.inspect(error)})`) 227 | }) 228 | ``` 229 | ------- 230 | 231 | #### command-notfound 232 | Вызывается при получении сообщения, которое не подходит ни к одному RegExp'у `.get()` 233 | 234 | ```javascript 235 | bot.on('command-notfound', msg => { 236 | bot.send('What?', msg.peer_id) 237 | }) 238 | ``` 239 | 240 | ------- 241 | 242 | ## Keyboard 243 | Класс для создания клавиатуры для бота 244 | ```javascript 245 | bot.get(/Hi|Hello|Hey/i, message => { 246 | const keyboard = new Keyboard(true) 247 | .addButton('Red', KeyboardColor.NEGATIVE) 248 | .addButton('Green', KeyboardColor.POSITIVE) 249 | .addRow() 250 | .addButton('Blue', KeyboardColor.PRIMARY) 251 | .addButton('White') 252 | 253 | bot.send('Hello!', message.peer_id, keyboard) 254 | }); 255 | ``` 256 | [Пример целиком](https://github.com/vitalyavolyn/node-vk-bot/blob/master/examples/keyboards.js) 257 | 258 | Единственный параметр - one_time 259 | Если True, клавиатура исчезнет после нажатия на кнопку 260 | ```javascript 261 | new Keyboard(true) 262 | ``` 263 | ------- 264 | 265 | ### addButton 266 | Добавить кнопку в последнюю строчку кнопок. 267 | 268 | Parameters: 269 | - label (string) - Текст на кнопке (required) 270 | - color (string или [KeyboardColor](#KeyboardColor)) 271 | - payload (any) - Параметр для callback api 272 | 273 | Максимальное количество кнопок на строке - 4 274 | 275 | ------- 276 | 277 | ### addRow 278 | Добавляет новую строчку для кнопок 279 | 280 | Максимальное количество строк - 10 281 | 282 | ------- 283 | 284 | ### toString 285 | Получить клавиатуру в виде JSON строки 286 | 287 | ------- 288 | 289 | ### KeyboardColor 290 | ```javascript 291 | addButton('label', KeyboardColor.NEGATIVE) 292 | ``` 293 | 294 | Возможные цвета кнопок: 295 | - PRIMARY - синяя 296 | - DEFAULT - белая 297 | - NEGATIVE - красная 298 | - POSITIVE - зеленая 299 | 300 | ------- 301 | 302 | ## Объект `Message` 303 | ```typescript 304 | interface Message { 305 | id: number, 306 | peer_id: number, 307 | date: number, 308 | text: string, 309 | from_id: number, 310 | attachments: any, 311 | important: boolean, 312 | conversation_message_id: number, 313 | fwd_messages: any 314 | } 315 | 316 | // https://vk.com/dev/objects/message 317 | ``` 318 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/vitalyavolyn/node-vk-bot.svg?branch=master)](https://travis-ci.org/vitalyavolyn/node-vk-bot) 2 | 3 | [README на русском](https://github.com/vitalyavolyn/node-vk-bot/blob/master/README_ru.md) 4 | 5 | # VK BOTS 6 | - Create and control VK bots easily. 7 | - Uses LongPoll or Callback API to get new messages 8 | 9 | ```sh 10 | npm install --save node-vk-bot 11 | ``` 12 | 13 | If you are cloning this repository, remember to run `npm install` to install dependencies. 14 | 15 | # Example 16 | ```javascript 17 | const { Bot, Keyboard } = require('node-vk-bot') 18 | 19 | const bot = new Bot({ 20 | token: 'YOUR TOKEN', 21 | group_id: 123456 22 | }).start() 23 | 24 | bot.get(/Hi|Hello|Hey/i, (message, exec, reply) => { 25 | const keyboard = new Keyboard().addButton('Hi!') 26 | const options = { forward_messages: message.id, keyboard } 27 | 28 | reply('Hello!', options) 29 | }) 30 | ``` 31 | 32 | [More examples](https://github.com/vitalyavolyn/node-vk-bot/tree/master/examples) (how to use webhooks, upload pictures, ...) 33 | 34 | # Bots created with this library 35 | if you want your bot to be in this list, just make a pull request 36 | 37 | - [**GitHub Events**](https://vk.com/githubbot) - Notifies you about new issues, commits, etc. in private messages _by [@vitalyavolyn](https://github.com/vitalyavolyn)_ 38 | 39 | # Table of contents 40 | - [Getting Started](#getting-started) 41 | - [`Bot`](#bot) 42 | - [Methods](#methods) 43 | - [`start`](#start) 44 | - [`get`](#get) 45 | - [`getPayload`](#getPayload) 46 | - [`send`](#send) 47 | - [`uploadPhoto`](#uploadPhoto) 48 | - [`api`](#api) 49 | - [`processUpdate`](#processUpdate) 50 | - [`stop`](#stop) 51 | - [Events](#events) 52 | - [update](#update) 53 | - [voice](#voice) 54 | - [sticker](#sticker) 55 | - [payload](#payload) 56 | - [poll-error](#poll-error) 57 | - [command-notfound](#command-notfound) 58 | - [Keyboard](#keyboard) 59 | - [addButton](#addButton) 60 | - [addRow](#addRow) 61 | - [toString](#toString) 62 | - [KeyboardColor](#KeyboardColor) 63 | - [The `Message` Object](#the-message-object) 64 | 65 | ## Getting Started 66 | In the example above you can see a super simple VK Bot. This bot will answer our greetings, that's all. 67 | 68 | Let's explain the code, it's pretty simple. 69 | 70 | 1. Then I create a bot instance with my group's token. 71 | 2. By calling `bot.start()` the bot starts polling updates from the server. 72 | 3. Then I simply listen on messages which pass the RegExp test, when I get such message, Then I send a new message with text 'Hello' to that chat with a forwarded message. 73 | 74 | ------- 75 | 76 | ## Bot 77 | The class used to create new bots, it takes a single argument, an `options` object. 78 | 79 | ```javascript 80 | new Bot({ 81 | token: 'TOKEN', 82 | group_id: 123456 83 | api: { 84 | v: 5.80, // >= 5.80 85 | lang: 'ru' 86 | } 87 | }) 88 | ``` 89 | 90 | | Parameter | Type | Required | 91 | |-----------|:----:|---------:| 92 | | token | String | Yes | 93 | | group_id | Number | Yes 94 | | api | Object| No | 95 | | controllers | String[] | No | 96 | 97 | `api` is object with API settings: **v**ersion and **lang**uage. (both strings) ([Read more](https://vk.com/dev/api_requests)) 98 | 99 | ------- 100 | 101 | ### Methods 102 | #### start 103 | Starts polling updates from API. 104 | Emits an `update` event after getting updates with the response from server. 105 | [Update examples](https://vk.com/dev/using_longpoll). 106 | 107 | ------- 108 | 109 | #### get 110 | Listens on specific message matching the RegExp pattern. 111 | ```javascript 112 | bot.get(/Hello/i, (msg, exec, reply) => { 113 | console.log(msg) 114 | reply('Hi!') 115 | }) 116 | ``` 117 | 118 | The argument passed to callback is a [`Message`](#the-message-object) object, result of `pattern.exec(text)` and a `reply` function. 119 | 120 | `reply` takes text as first argument and optional message.send parameters as second. 121 | 122 | ------- 123 | 124 | #### getPayload 125 | Listens for specific `payload` (used for keyboards) 126 | This is a syntactic sugar for the [`payload` event](#payload) 127 | 128 | ``` 129 | bot.getPayload('{"command": "start"}', (msg, reply) => console.log(msg)) 130 | ``` 131 | 132 | Arguments: json string and listener 133 | 134 | ------- 135 | 136 | #### send 137 | Sends message. 138 | 139 | ```javascript 140 | bot.send('text', peer_id, params) 141 | ``` 142 | 143 | ------- 144 | 145 | #### uploadPhoto 146 | Upload a photo. 147 | 148 | The only parameter is an absolute path to picture or a stream object. 149 | Returns a Promise that resolves with a [photo object](https://vk.com/dev/photos.saveMessagesPhoto) 150 | ```javascript 151 | bot.uploadPhoto('~/kittens.png').then(photo => { 152 | console.log(photo) 153 | }) 154 | 155 | let stream = fs.createReadStream('./kittens.png') 156 | bot.uploadPhoto(stream).then(photo => { 157 | console.log(photo) 158 | }) 159 | ``` 160 | 161 | ------- 162 | 163 | #### api 164 | Access VK API. 165 | 166 | ```javascript 167 | bot.api('users.get', { user_ids: 1 }) 168 | .then(res => console.log(res[0].first_name)) // Pavel 169 | ``` 170 | 171 | When using `execute` method, this function returns full response object. (Because there may be errors and responses in same object). 172 | 173 | ------- 174 | 175 | #### processUpdate 176 | Process an update from Callback API. 177 | Example of usage may be found in `examples` folder 178 | 179 | ------- 180 | 181 | #### stop 182 | Stops the bot from listening on updates. 183 | 184 | ```javascript 185 | bot.stop() 186 | ``` 187 | 188 | ------- 189 | 190 | ### Events 191 | #### update 192 | The update event is emitted whenever there is a response from LongPoll. 193 | 194 | ```javascript 195 | bot.on('update', update => { 196 | if (update.from_id === 1) { 197 | console.log('Got a message from Pavel Durov!'); 198 | } 199 | }) 200 | ``` 201 | ------- 202 | 203 | #### voice 204 | The voice event is emitted whenever there is a new voice message. (emits `Message` object) 205 | 206 | ------- 207 | 208 | #### sticker 209 | The sticker event is emitted whenever there is a new incoming sticker. (emits `Message` object) 210 | 211 | ------- 212 | 213 | #### payload 214 | Emitted when bot recieves a message with json payload (used in keyboards) 215 | Emits `Message` object and [reply function](#get) 216 | 217 | ------- 218 | 219 | 220 | #### poll-error 221 | The poll-error event is emitted whenever there is an error occurred in LongPoll. 222 | 223 | ```javascript 224 | bot.on('poll-error', error => { 225 | console.error('error occurred on a working with the Long Poll server ' + 226 | `(${util.inspect(error)})`) 227 | }) 228 | ``` 229 | ------- 230 | 231 | #### command-notfound 232 | This event is emitted whenever there's no `.get()` listeners matching 233 | 234 | ```javascript 235 | bot.on('command-notfound', msg => { 236 | bot.send('What?', msg.peer_id) 237 | }) 238 | ``` 239 | 240 | ------- 241 | 242 | ## Keyboard 243 | The class used to create keyboards in messages 244 | ```javascript 245 | bot.get(/Hi|Hello|Hey/i, message => { 246 | const keyboard = new Keyboard(true) 247 | .addButton('Red', KeyboardColor.NEGATIVE) 248 | .addButton('Green', KeyboardColor.POSITIVE) 249 | .addRow() 250 | .addButton('Blue', KeyboardColor.PRIMARY) 251 | .addButton('White') 252 | 253 | bot.send('Hello!', message.peer_id, keyboard) 254 | }); 255 | ``` 256 | [Full example](https://github.com/vitalyavolyn/node-vk-bot/blob/master/examples/keyboards.js) 257 | 258 | The only argument - one_time 259 | If `true`, the keyboard hides after user replies 260 | ```javascript 261 | new Keyboard(true) 262 | ``` 263 | ------- 264 | 265 | ### addButton 266 | Add a button to the last row. 267 | 268 | Parameters: 269 | - label (string) - Text on button (required) 270 | - color (string or [KeyboardColor](#KeyboardColor)) 271 | - payload (any) - A parameter for Callback API 272 | 273 | Maximum amount of buttons in one row is 4 274 | 275 | ------- 276 | 277 | ### addRow 278 | Add a new row to the keyboard. 279 | 280 | Maximum amount of rows is 10 281 | 282 | ------- 283 | 284 | ### toString 285 | Get the keyboard as a JSON string 286 | 287 | ------- 288 | 289 | ### KeyboardColor 290 | ```javascript 291 | addButton('label', KeyboardColor.NEGATIVE) 292 | ``` 293 | 294 | Available colors: 295 | - PRIMARY - blue 296 | - DEFAULT - white 297 | - NEGATIVE - red 298 | - POSITIVE - green 299 | 300 | ------- 301 | 302 | ## The `Message` Object 303 | ```typescript 304 | interface Message { 305 | id: number, 306 | peer_id: number, 307 | date: number, 308 | text: string, 309 | from_id: number, 310 | attachments: any, 311 | important: boolean, 312 | conversation_message_id: number, 313 | fwd_messages: any 314 | } 315 | 316 | // Reference: https://vk.com/dev/objects/message 317 | ``` 318 | --------------------------------------------------------------------------------