Passed
Push — master ( aaab20...67ce97 )
by Daniil
01:41
created

bot   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 189
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 17
eloc 88
dl 0
loc 189
rs 10
c 0
b 0
f 0

3 Methods

Rating   Name   Duplication   Size   Complexity  
A Bot.auth() 0 22 5
A Bot.update_version() 0 14 4
A Bot.is_admin() 0 9 1
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
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_logger()
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["GID_ID"]
57
        self.cid = os.environ["CID_ID"]
58
59
        self.kbs = Keyboards()
60
61
        self.db = Database(os.environ["DATABASE_URL"])
62
        self.admins = os.environ["ADMINS_IDS"].split(",")
63
64
        self.bot_session = None
65
        self.user_session = None
66
        self.bot_vk = None
67
        self.user_vk = None
68
        self.longpoll = None
69
70
        self.log.info(
71
            f"Беседа..." f" {'Тестовая' if self.cid.endswith('1') else 'Основная'}"
72
        )
73
74
        self.log.info("Инициализация завершена.")
75
76
    def auth(self):
77
78
        """Авторизация ВКонтакте, подключение к API
79
        """
80
81
        self.log.info("Авторизация ВКонтакте...")
82
        try:
83
            self.bot_session = vk_api.VkApi(token=self.token, api_version="5.103")
84
            self.user_session = vk_api.VkApi(token=self.user_token, api_version="5.103")
85
            self.bot_vk = self.bot_session.get_api()
86
            self.user_vk = self.user_session.get_api()
87
            self.longpoll = RalphVkBotLongPoll(vk=self.bot_session, group_id=self.gid)
88
        except vk_api.exceptions.AuthError:
89
            self.log.exception("Авторизация ВКонтакте неудачна. Ошибка авторизации.")
90
        except requests.exceptions.ConnectionError:
91
            self.log.exception(
92
                "Авторизация ВКонтакте неудачна. Превышен лимит попыток подключения."
93
            )
94
        except vk_api.exceptions.ApiError:
95
            self.log.exception("Авторизация ВКонтакте неудачна. Ошибка доступа.")
96
        else:
97
            self.log.info("Авторизация ВКонтакте успешна.")
98
99
    def send_message(
100
        self,
101
        msg: str,
102
        pid: int = None,
103
        keyboard: str = "",
104
        attachments: str = None,
105
        user_ids: str = None,
106
        forward: str = "",
107
    ) -> NoReturn:
108
109
        """Обёртка над API ВКонтакте, отправляющая сообщения
110
        
111
        Arguments:
112
            msg: Текст отправляемого сообщения
113
            pid: Идентификатор пользователя/беседы/сообщества получателя сообщения (*не нужен, если указан user_ids*)
114
            keyboard: JSON-подобная строка со встроенной клавиатурой
115
            attachments: Вложения к сообщению (**не работает**)
116
            user_ids: Перечень адресатов для отправки одного сообщения (*не нужен, если указан pid*)
117
            forward: Перечень идентификаторов сообщений для пересылки
118
        """
119
120
        try:
121
            self.bot_vk.messages.send(
122
                peer_id=pid,
123
                random_id=random.getrandbits(64),
124
                message=msg,
125
                keyboard=keyboard,
126
                attachments=attachments,
127
                user_ids=user_ids,
128
                forward_messages=forward,
129
            )
130
131
        except vk_api.exceptions.ApiError as e:
132
            self.log.exception(msg=e.__str__())
133
134
    def generate_mentions(self, ids: str, names: bool) -> str:
135
        """Генерирует строку с упоминаниями из списка идентификаторов
136
        
137
        Arguments:
138
            ids: Перечень идентификаторов пользователей
139
            names: Флаг, указывающий на необходимость использования имён
140
            
141
        Returns:
142
            str: Сообщение, упоминающее выбранных пользователей
143
        """
144
        ids = ids.replace(" ", "").split(",")[:-1]
145
        if names:
146
            users_names = self.db.get_users_names(ids)
147
        else:
148
            users_names = ["!"] * len(ids)
149
        result = (", " if names else "").join(
150
            [f"@id{_id}({users_names[i]})" for (i, _id) in enumerate(ids)]
151
        )
152
        return result
153
154
    def is_admin(self, _id: int) -> bool:
155
        """Проверяет, является ли пользователь администратором бота
156
157
        Arguments:
158
            _id: Идентификатор пользователя для проверки привелегий
159
        Returns:
160
            bool: Флаг, указывающий на принадлежность текущего пользователя к касте Администраторов
161
        """
162
        return str(_id) in self.admins
163
164
    def send_gui(self, pid: int, text: str = "Привет!") -> NoReturn:
165
        """Отправляет клавиатуру главного меню
166
        
167
        Arguments:
168
            pid: Получатель клавиатуры
169
            text: Сообщение, вместе с которым будет отправлена клавиатура
170
        """
171
        self.send_message(
172
            msg=text, pid=pid, keyboard=self.kbs.generate_main_menu(self.is_admin(pid)),
173
        )
174
175
    def update_version(self):
176
        """
177
        Обновляет версию в статусе группы с ботом
178
        """
179
180
        self.log.info("Обновление версии в статусе группы...")
181
        try:
182
            with open("VERSION.txt", "r") as f:
183
                v = f"Версия: {f.read()}"
184
            self.user_vk.status.set(text=v, group_id=self.gid)
185
        except vk_api.exceptions.ApiError as e:
186
            self.log.error(f"Ошибка {e.__str__()}")
187
        else:
188
            self.log.info(f"Статус группы успешно обновлён.")
189