You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

288 lines
7.1 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# -*- coding: utf-8 -*-
"""
:authors: deker104, python273
:license: Apache License, Version 2.0, see LICENSE file
:copyright: (c) 2019 python273
"""
from enum import Enum
import requests
CHAT_START_ID = int(2E9)
class DotDict(dict):
__getattr__ = dict.get
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
class VkBotEventType(Enum):
MESSAGE_NEW = 'message_new'
MESSAGE_REPLY = 'message_reply'
MESSAGE_EDIT = 'message_edit'
MESSAGE_EVENT = 'message_event'
MESSAGE_TYPING_STATE = 'message_typing_state'
MESSAGE_ALLOW = 'message_allow'
MESSAGE_DENY = 'message_deny'
PHOTO_NEW = 'photo_new'
PHOTO_COMMENT_NEW = 'photo_comment_new'
PHOTO_COMMENT_EDIT = 'photo_comment_edit'
PHOTO_COMMENT_RESTORE = 'photo_comment_restore'
PHOTO_COMMENT_DELETE = 'photo_comment_delete'
AUDIO_NEW = 'audio_new'
VIDEO_NEW = 'video_new'
VIDEO_COMMENT_NEW = 'video_comment_new'
VIDEO_COMMENT_EDIT = 'video_comment_edit'
VIDEO_COMMENT_RESTORE = 'video_comment_restore'
VIDEO_COMMENT_DELETE = 'video_comment_delete'
WALL_POST_NEW = 'wall_post_new'
WALL_REPOST = 'wall_repost'
WALL_REPLY_NEW = 'wall_reply_new'
WALL_REPLY_EDIT = 'wall_reply_edit'
WALL_REPLY_RESTORE = 'wall_reply_restore'
WALL_REPLY_DELETE = 'wall_reply_delete'
BOARD_POST_NEW = 'board_post_new'
BOARD_POST_EDIT = 'board_post_edit'
BOARD_POST_RESTORE = 'board_post_restore'
BOARD_POST_DELETE = 'board_post_delete'
MARKET_COMMENT_NEW = 'market_comment_new'
MARKET_COMMENT_EDIT = 'market_comment_edit'
MARKET_COMMENT_RESTORE = 'market_comment_restore'
MARKET_COMMENT_DELETE = 'market_comment_delete'
GROUP_LEAVE = 'group_leave'
GROUP_JOIN = 'group_join'
USER_BLOCK = 'user_block'
USER_UNBLOCK = 'user_unblock'
POLL_VOTE_NEW = 'poll_vote_new'
GROUP_OFFICERS_EDIT = 'group_officers_edit'
GROUP_CHANGE_SETTINGS = 'group_change_settings'
GROUP_CHANGE_PHOTO = 'group_change_photo'
VKPAY_TRANSACTION = 'vkpay_transaction'
class VkBotEvent(object):
""" Событие Bots Long Poll
:ivar raw: событие, в каком виде было получено от сервера
:ivar type: тип события
:vartype type: VkBotEventType or str
:ivar t: сокращение для type
:vartype t: VkBotEventType or str
:ivar object: объект события, в каком виде был получен от сервера
:ivar obj: сокращение для object
:ivar group_id: ID группы бота
:vartype group_id: int
"""
__slots__ = (
'raw',
't', 'type',
'obj', 'object',
'client_info', 'message',
'group_id'
)
def __init__(self, raw):
self.raw = raw
try:
self.type = VkBotEventType(raw['type'])
except ValueError:
self.type = raw['type']
self.t = self.type # shortcut
self.object = DotDict(raw['object'])
try:
self.message = DotDict(raw['object']['message'])
except KeyError:
self.message = None
self.obj = self.object
try:
self.client_info = DotDict(raw['object']['client_info'])
except KeyError:
self.client_info = None
self.group_id = raw['group_id']
def __repr__(self):
return '<{}({})>'.format(type(self), self.raw)
class VkBotMessageEvent(VkBotEvent):
""" Событие с сообщением Bots Long Poll
:ivar from_user: сообщение от пользователя
:vartype from_user: bool
:ivar from_chat: сообщение из беседы
:vartype from_chat: bool
:ivar from_group: сообщение от группы
:vartype from_group: bool
:ivar chat_id: ID чата
:vartype chat_id: int
"""
__slots__ = ('from_user', 'from_chat', 'from_group', 'chat_id')
def __init__(self, raw):
super(VkBotMessageEvent, self).__init__(raw)
self.from_user = False
self.from_chat = False
self.from_group = False
self.chat_id = None
peer_id = self.obj.peer_id or self.message.peer_id
if peer_id < 0:
self.from_group = True
elif peer_id < CHAT_START_ID:
self.from_user = True
else:
self.from_chat = True
self.chat_id = peer_id - CHAT_START_ID
class VkBotLongPoll(object):
""" Класс для работы с Bots Long Poll сервером
`Подробнее в документации VK API <https://vk.com/dev/bots_longpoll>`__.
:param vk: объект :class:`VkApi`
:param group_id: id группы
:param wait: время ожидания
"""
__slots__ = (
'vk', 'wait', 'group_id',
'url', 'session',
'key', 'server', 'ts'
)
#: Классы для событий по типам
CLASS_BY_EVENT_TYPE = {
VkBotEventType.MESSAGE_NEW.value: VkBotMessageEvent,
VkBotEventType.MESSAGE_REPLY.value: VkBotMessageEvent,
VkBotEventType.MESSAGE_EDIT.value: VkBotMessageEvent,
}
#: Класс для событий
DEFAULT_EVENT_CLASS = VkBotEvent
def __init__(self, vk, group_id, wait=25):
self.vk = vk
self.group_id = group_id
self.wait = wait
self.url = None
self.key = None
self.server = None
self.ts = None
self.session = requests.Session()
self.update_longpoll_server()
def _parse_event(self, raw_event):
event_class = self.CLASS_BY_EVENT_TYPE.get(
raw_event['type'],
self.DEFAULT_EVENT_CLASS
)
return event_class(raw_event)
def update_longpoll_server(self, update_ts=True):
values = {
'group_id': self.group_id
}
response = self.vk.method('groups.getLongPollServer', values)
self.key = response['key']
self.server = response['server']
self.url = self.server
if update_ts:
self.ts = response['ts']
def check(self):
""" Получить события от сервера один раз
:returns: `list` of :class:`Event`
"""
values = {
'act': 'a_check',
'key': self.key,
'ts': self.ts,
'wait': self.wait,
}
response = self.session.get(
self.url,
params=values,
timeout=self.wait + 10
).json()
if 'failed' not in response:
self.ts = response['ts']
return [
self._parse_event(raw_event)
for raw_event in response['updates']
]
elif response['failed'] == 1:
self.ts = response['ts']
elif response['failed'] == 2:
self.update_longpoll_server(update_ts=False)
elif response['failed'] == 3:
self.update_longpoll_server()
return []
def listen(self):
""" Слушать сервер
:yields: :class:`Event`
"""
while True:
for event in self.check():
yield event