Skip to content
This repository has been archived by the owner on Mar 29, 2019. It is now read-only.

Commit

Permalink
New bot structure. Upgradable
Browse files Browse the repository at this point in the history
  • Loading branch information
ya7on committed Jan 29, 2019
1 parent dbd329d commit 405c881
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 129 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# VKBOTAPI

# Contents
* [api](#api)
* * [call()](#call)
* [msg](#msg)
* * [poll()](#poll)

# Docs

## api
* ### call()

## msg
* ### poll()
15 changes: 15 additions & 0 deletions examples/simple.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { Bot } = require('../index')

const bot = new Bot({
token: process.argv[2]
})

bot.msg.hear(/hello/i, msg => {
msg.send('hello1')
msg.send('hello2')
msg.send('hello3')
msg.send('hello4')
msg.send('hello5')
})

bot.msg.poll()
31 changes: 31 additions & 0 deletions src/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const utils = require('./utils')

class API {
constructor (parent) {
this.parent = parent
}

/**
* Запрос к VK API
* @param {String} method Вызываемый метод API
* @param {{}} data Объект параметров { ключ: значение }
* @returns {{}} Ответ сервера в виде JSON
*/
async call (method, data = {}) {
data.v = data.v || this.parent.v

const parameters = []

for (const key in data) {
parameters.push(`${key}=${data[key]}`)
}

const url = `https://api.vk.com/method/${method}?${parameters.join('&')}&access_token=${this.parent.token}`

return JSON.parse(await utils.httpGet(url))
}
}

module.exports = {
API
}
99 changes: 13 additions & 86 deletions src/bot.js
Original file line number Diff line number Diff line change
@@ -1,102 +1,29 @@
const utils = require('./utils')
const { API } = require('./api')
const { Msg } = require('./msg')

class Bot {
/**
* @param {BotOptions} options
*/
constructor (options) {
if (!options.token) {
throw Error('Vk API must be specified')
}
this.token = options.token
this.alias = options.alias || 'Бот'
this.version = options.version || 5.92

/** @type {BotCommand[]} */
this.commands = []
}

/**
* @returns {void}
*/
async poll () {
const pollData = await this._call('messages.getLongPollServer', {
v: this.version
})

if (pollData.error) {
throw Error(pollData.error.error_msg)
if (!this.token) {
throw Error('"token" is a required parameter.')
}

let server = pollData.response.server
let key = pollData.response.key
let ts = pollData.response.ts

while (true) {
const pollServer = `https://${server}?act=a_check&key=${key}&ts=${ts}&wait=25&mode=2&version=1`
const data = JSON.parse(await utils.httpGet(pollServer))

if (data.failed) {
this.poll()
console.log('Разорвано соединение с Long Polling')
return
}

const newMessages = data.updates.filter(item => item[0] === 4)
this.v = options.v || '5.92'

for (const msg of newMessages) {
const roomName = msg[5]
const text = roomName === ' ... ' ? `${this.alias} ${msg[6]}` : msg[6]
this.account = {}

for (const item of this.commands) {
if (text.match(item.regexp)) {
const messageInstance = new Msg(this, ...msg)
item.callback(messageInstance)
}
}
}

ts = data.ts || ts
}
}

/**
* @param {RegExp} regexp
* @param {(msg:VKMsg) => void} callback
* @returns {void}
*/
respond (regexp, callback) {
const command = new RegExp(`${this.alias} ${regexp.source}`, regexp.flags)
this.hear(command, callback)
this.api = new API(this)
this.msg = new Msg(this)
}

/**
* @param {RegExp} regexp
* @param {(msg:VKMsg) => void} callback
* @returns {void}
* Получение информации об авторизованном аккаунте
*/
hear (regexp, callback) {
const command = {
regexp,
callback
async whoAmI () {
const identity = await this.api.call('users.get')
if (identity.error) {
throw Error(identity.error.error_msg)
}
this.commands.push(command)
}

/**
* @param {String} method
* @param {{}} data
* @returns {void}
*/
async _call (method, data = {}) {
const parameters = []
for (const key in data) {
parameters.push(`${key}=${data[key]}`)
}
const url = `https://api.vk.com/method/${method}?${parameters.join('&')}&access_token=${this.token}`

return JSON.parse(await utils.httpGet(url))
return identity.response.shift()
}
}

Expand Down
38 changes: 38 additions & 0 deletions src/message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class Message {
constructor (options) {
this.bot = options.bot

this.regexp = options.regexp

this.code = options.code
this.messageId = options.messageId
this.flags = options.flags
this.peerId = options.peerId
this.timestamp = options.timestamp
this.roomName = options.roomName
this.text = options.text
this.attachments = options.attachments

this.match = this.text.match(this.regexp)
}

async send (text) {
const msg = await this.bot.api.call('messages.send', {
peer_id: this.peerId,
message: text,
random_id: Math.random()
})

if (msg.error) {
switch (msg.error.error_code) {
case 14:
setTimeout(() => this.send(...arguments), 1000)
break
}
}
}
}

module.exports = {
Message
}
103 changes: 81 additions & 22 deletions src/msg.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,90 @@
const utils = require('./utils')
const { Message } = require('./message')

class Msg {
/**
* @param {VKBot} parent
* @param {LongPollMessage} options
*/
constructor (parent, ...options) {
constructor (parent) {
this.parent = parent
this.messageId = options[1]
this.flags = options[2]
this.author = options[3]
this.roomName = options[5]
this.text = options[6]
this.attach = options[7]

this.events = {
hear: []
}
}

/**
* @param {String} text
* @returns {{}}
*/
async send (text) {
const msg = await this.parent._call('messages.send', {
peer_id: this.author,
message: text,
v: this.parent.version,
random_id: this.messageId
/** Ответ бота на сообщение пользователя */
hear (regexp, callback) {
this.events.hear.push({
regexp,
callback
})
}

// /** Создание листенера */
// listen (listener, callback) {
// // TODO
// }

/** Подключение по Long Polling */
async poll () {
this.parent.account = await this.parent.whoAmI()

// Get Long Polling server
const longPollingServerData = await this.parent.api.call('messages.getLongPollServer')

if (longPollingServerData.error) {
throw Error(longPollingServerData.error.error_msg)
}

let server = longPollingServerData.response.server
let key = longPollingServerData.response.key
let ts = longPollingServerData.response.ts

// StartPoll
while (true) {
// Getting updates from long polling server
const longPollingServer = `https://${server}?act=a_check&key=${key}&ts=${ts}&wait=25&mode=2&version=1`

const data = JSON.parse(await utils.httpGet(longPollingServer))

if (data.failed) {
console.log('Long Polling connection is broken')
this.poll()
return
}

const updates = utils.updatesAllocation(data.updates)

// New messages
this.newMessagesHandler(updates[4] || [])

ts = data.ts || ts
}
}

// HANDLERS

/** Обработчик сообщений (код 4) */
newMessagesHandler (updates) {
for (const msg of updates) {
// События .hear
for (const event of this.events.hear) {
const message = new Message({
bot: this.parent,
regexp: event.regexp,
code: msg[0], // Код уведомления
messageId: msg[1], // id сообщения
flags: utils.flagDecoding(msg[2]), // флаги сообщения
peerId: msg[3], // от кого пришло
timestamp: msg[4], // когда
roomName: msg[5], // где
text: msg[6], // что
attachments: msg[7] // всякая хуйня
})

return msg
if (message.match && !message.flags.includes('OUTBOX')) {
event.callback(message)
}
}
}
}
}

Expand Down
45 changes: 44 additions & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,49 @@ async function httpGet (url) {
})
}

function updatesAllocation (updates) {
const result = {}

for (const msg of updates) {
if (!result[msg[0]]) {
result[msg[0]] = []
}

result[msg[0]].push(msg)
}

return result
}

function flagDecoding (code) {
const flags = []
const legend = {
532464: 'I DONT KNOW WHAT IT IS', // ASK SUPPORT
131072: '',
65536: 'HIDDEN',
8192: 'I DONT KNOW WHAT IT IS 2', // ASK SUPPORT
512: 'MEDIA',
256: 'FIXED',
128: 'DELETED',
64: 'SPAM',
32: 'FRIENDS',
16: 'CHAT',
8: 'IMPORTANT',
4: 'REPLIED',
2: 'OUTBOX',
1: 'UNREAD'
}
let sum = code
for (const n of Object.keys(legend).sort((a, b) => b - a)) {
if (sum - n < 0) continue
sum -= n
flags.push(legend[n])
}
return flags
}

module.exports = {
httpGet
httpGet,
updatesAllocation,
flagDecoding
}
Loading

0 comments on commit 405c881

Please sign in to comment.