Passed
Pull Request — master (#117)
by
unknown
05:28
created

bot   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 207
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 22
eloc 98
dl 0
loc 207
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A Bot.update_version() 0 14 4
A Bot.is_admin() 0 13 3
A Bot.auth() 0 22 5
1
"""
2
:project: ralph
3
:version: see VERSION.txt
4
:authors: dadyarri, 6a16ec
5
:contact: https://vk.me/dadyarri, https://vk.me/6a16ec
6
:license: MIT
7
8
:copyright: (c) 2019 - 2020 dadyarri, 6a16ec
9
10
"""
11
12
13
import os
14
import random
15
from typing import List
16
from typing import NoReturn
17
18
import requests
19
import vk_api
20
21
from database import Database
22
from keyboard import Keyboards
23
from logger import Logger
24
from vkbotlongpoll import RalphVkBotLongPoll
25
from singleton import SingletonMeta
26
27
28
class Bot(metaclass=SingletonMeta):
29
    """Класс, описывающий объект бота, включая авторизацию в API, и все методы бота.
30
    
31
    Attributes:
32
        token (str): Токен доступа к сообществу ВКонтакте
33
        user_token (str): Токен доступа пользователя-администратора ВКонтакте (используется для обновления статуса сообщества)
34
        gid (str): Идентификатор сообщества ВКонтакте
35
        cid (str): Идентификатор активной беседы (используется в рассылке расписания)
36
        kbs (Keyboards): Объект класса Keyboards, содержащий генераторы клавиатур
37
        db (Database): Объект класса Database, инициирующий подключение к базе данных
38
        admins (List[str]): Список идентификаторов ВКонтакте пользователей, имеющих доступ администратора бота 
39
    
40
    Example:
41
        .. code-block:: python
42
        
43
            from bot import Bot
44
            bot = Bot()
45
            bot.auth()
46
    """
47
48
    def __init__(self) -> None:
49
50
        self.log = Logger().init()
51
52
        self.log.info("Инициализация...")
53
54
        self.token = os.environ["VK_TOKEN"]
55
        self.user_token = os.environ["VK_USER_TOKEN"]
56
        self.gid = os.environ["GROUP_ID"]
57
58
        self.kbs = Keyboards()
59
60
        self.db = Database(os.environ["DATABASE_URL"])
61
62
        self.bot_session = None
63
        self.user_session = None
64
        self.bot_vk = None
65
        self.user_vk = None
66
        self.longpoll = None
67
68
        self.log.info("Инициализация завершена.")
69
70
    def auth(self):
71
72
        """Авторизация ВКонтакте, подключение к API
73
        """
74
75
        self.log.info("Авторизация ВКонтакте...")
76
        try:
77
            self.bot_session = vk_api.VkApi(token=self.token, api_version="5.103")
78
            self.user_session = vk_api.VkApi(token=self.user_token, api_version="5.103")
79
            self.bot_vk = self.bot_session.get_api()
80
            self.user_vk = self.user_session.get_api()
81
            self.longpoll = RalphVkBotLongPoll(vk=self.bot_session, group_id=self.gid)
82
        except vk_api.exceptions.AuthError:
83
            self.log.exception("Авторизация ВКонтакте неудачна. Ошибка авторизации.")
84
        except requests.exceptions.ConnectionError:
85
            self.log.exception(
86
                "Авторизация ВКонтакте неудачна. Превышен лимит попыток подключения."
87
            )
88
        except vk_api.exceptions.ApiError:
89
            self.log.exception("Авторизация ВКонтакте неудачна. Ошибка доступа.")
90
        else:
91
            self.log.info("Авторизация ВКонтакте успешна.")
92
93
    def send_message(
94
        self,
95
        msg: str = "",
96
        pid: int = None,
97
        keyboard: str = "",
98
        attachment: str = None,
99
        user_ids: str = None,
100
        forward: str = "",
101
    ) -> NoReturn:
102
103
        """Обёртка над API ВКонтакте, отправляющая сообщения
104
        
105
        Arguments:
106
            msg: Текст отправляемого сообщения
107
            pid: Идентификатор пользователя/беседы/сообщества получателя сообщения (*не нужен, если указан user_ids*)
108
            keyboard: JSON-подобная строка со встроенной клавиатурой
109
            attachment: Вложения к сообщению (**не работает**)
110
            user_ids: Перечень адресатов для отправки одного сообщения (*не нужен, если указан pid*)
111
            forward: Перечень идентификаторов сообщений для пересылки
112
        """
113
114
        try:
115
            self.bot_vk.messages.send(
116
                peer_id=pid,
117
                random_id=random.getrandbits(64),
118
                message=msg,
119
                keyboard=keyboard,
120
                attachment=attachment,
121
                user_ids=user_ids,
122
                forward_messages=forward,
123
            )
124
125
        except vk_api.exceptions.ApiError as e:
126
            self.log.exception(msg=e.__str__())
127
128
    def send_mailing(self, m_id: int, text: str, group: int, attach: str = ""):
129
        """Генерирует строку с упоминаниями из списка идентификаторов
130
131
        Arguments:
132
            group: Номер группы для поиска подписчиков
133
            m_id: Идентификатор рассылки
134
            text: Сообщение рассылки
135
            attach: Список вложений, прикрепляемых к рассылке
136
        """
137
        subscribers = self.db.fetch_subcribers(m_id, group).split(",")
138
        for sub in subscribers:
139
            self.send_message(
140
                msg=text,
141
                pid=int(sub),
142
                attachment=attach,
143
                keyboard=self.kbs.inline_unsubscribe(m_id, int(sub)),
144
            )
145
146
    def generate_mentions(self, ids: str, names: bool) -> str:
147
        """Генерирует строку с упоминаниями из списка идентификаторов
148
        
149
        Arguments:
150
            ids: Перечень идентификаторов пользователей
151
            names: Флаг, указывающий на необходимость использования имён
152
            
153
        Returns:
154
            str: Сообщение, упоминающее выбранных пользователей
155
        """
156
        ids = ids.replace(" ", "").split(",")
157
        if not ids[-1]:
158
            ids = ids[:-1]
159
        if names:
160
            users_names = self.db.get_users_names(ids)
161
        else:
162
            users_names = ["!"] * len(ids)
163
        result = (", " if names else "").join(
164
            [f"@id{_id}({users_names[i]})" for (i, _id) in enumerate(ids)]
165
        )
166
        return result
167
168
    def is_admin(self, _id: int) -> bool:
169
        """Проверяет, является ли пользователь администратором бота
170
171
        Arguments:
172
            _id: Идентификатор пользователя для проверки привелегий
173
        Returns:
174
            bool: Флаг, указывающий на принадлежность текущего пользователя к касте Администраторов
175
        """
176
        admins = self.db.get_list_of_administrators()
177
        for admin in admins:
178
            if _id == admin[0]:
179
                return True
180
        return False
181
182
    def send_gui(self, pid: int, text: str = "Привет!") -> NoReturn:
183
        """Отправляет клавиатуру главного меню
184
        
185
        Arguments:
186
            pid: Получатель клавиатуры
187
            text: Сообщение, вместе с которым будет отправлена клавиатура
188
        """
189
        self.send_message(
190
            msg=text, pid=pid, keyboard=self.kbs.generate_main_menu(self.is_admin(pid)),
191
        )
192
193
    def update_version(self):
194
        """
195
        Обновляет версию в статусе группы с ботом
196
        """
197
198
        self.log.info("Обновление версии в статусе группы...")
199
        try:
200
            with open("VERSION.txt", "r") as f:
201
                v = f"Версия: {f.read()}"
202
            self.user_vk.status.set(text=v, group_id=self.gid)
203
        except vk_api.exceptions.ApiError as e:
204
            self.log.error(f"Ошибка {e.__str__()}")
205
        else:
206
            self.log.info(f"Статус группы успешно обновлён.")
207