Passed
Push — master ( 302a05...75982b )
by Daniil
01:44
created

bot.Bot.auth()   A

Complexity

Conditions 5

Size

Total Lines 20
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 17
dl 0
loc 20
rs 9.0833
c 0
b 0
f 0
cc 5
nop 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
        log = logger.init_logger()
51
52
        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
        log.info(f"Беседа... {'Тестовая' if self.cid.endswith('1') else 'Основная'}")
71
72
        log.info("Инициализация завершена.")
73
74
    def auth(self):
75
76
        log = logger.init_logger()
77
        log.info("Авторизация ВКонтакте...")
78
        try:
79
            self.bot_session = vk_api.VkApi(token=self.token, api_version="5.103")
80
            self.user_session = vk_api.VkApi(token=self.user_token, api_version="5.103")
81
            self.bot_vk = self.bot_session.get_api()
82
            self.user_vk = self.user_session.get_api()
83
            self.longpoll = RalphVkBotLongPoll(vk=self.bot_session, group_id=self.gid)
84
        except vk_api.exceptions.AuthError:
85
            log.exception("Авторизация ВКонтакте неудачна. Ошибка авторизации.")
86
        except requests.exceptions.ConnectionError:
87
            log.exception(
88
                "Авторизация ВКонтакте неудачна. Превышен лимит попыток подключения."
89
            )
90
        except vk_api.exceptions.ApiError:
91
            log.exception("Авторизация ВКонтакте неудачна. Ошибка доступа.")
92
        else:
93
            log.info("Авторизация ВКонтакте успешна.")
94
95
    def send_message(
96
        self,
97
        msg: str,
98
        pid: int = None,
99
        keyboard: str = "",
100
        attachments: str = None,
101
        user_ids: str = None,
102
        forward: str = "",
103
    ) -> NoReturn:
104
105
        """Обёртка над API ВКонтакте, отправляющая сообщения
106
        
107
        Arguments:
108
            msg: Текст отправляемого сообщения
109
            pid: Идентификатор пользователя/беседы/сообщества получателя сообщения (*не нужен, если указан user_ids*)
110
            keyboard: JSON-подобная строка со встроенной клавиатурой
111
            attachments: Вложения к сообщению (**не работает**)
112
            user_ids: Перечень адресатов для отправки одного сообщения (*не нужен, если указан pid*)
113
            forward: Перечень идентификаторов сообщений для пересылки
114
        """
115
116
        log = logger.init_logger()
117
118
        try:
119
            self.bot_vk.messages.send(
120
                peer_id=pid,
121
                random_id=random.getrandbits(64),
122
                message=msg,
123
                keyboard=keyboard,
124
                attachments=attachments,
125
                user_ids=user_ids,
126
                forward_messages=forward,
127
            )
128
129
        except vk_api.exceptions.ApiError as e:
130
            log.exception(msg=e.__str__())
131
132
    def get_users_names(self, ids: list) -> List[str]:
133
        """Получает имена пользователей  из базы данных по идентификаторам из списка
134
        
135
        Arguments:
136
            ids: Список идентификаторов для формирования списка имён
137
        
138
        Returns:
139
            List[str]: Список имён пользователей
140
        """
141
        user_ids = [
142
            self.db.query(f"SELECT id FROM users WHERE vk_id={i}")[0][0] for i in ids
143
        ]
144
        user_names = [
145
            self.db.query(f"SELECT first_name FROM users_info WHERE user_id={i}")[0][0]
146
            for i in user_ids
147
        ]
148
        return user_names
149
150
    def generate_mentions(self, ids: str, names: bool) -> str:
151
        """Генерирует строку с упоминаниями из списка идентификаторов
152
        
153
        Arguments:
154
            ids: Перечень идентификаторов пользователей
155
            names: Флаг, указывающий на необходимость использования имён
156
            
157
        Returns:
158
            str: Сообщение, упоминающее выбранных пользователей
159
        """
160
        ids = ids.replace(" ", "").split(",")[:-1]
161
        if names:
162
            users_names = self.get_users_names(ids)
163
        else:
164
            users_names = ["!"] * len(ids)
165
        result = (", " if names else "").join(
166
            [f"@id{_id}({users_names[i]})" for (i, _id) in enumerate(ids)]
167
        )
168
        return result
169
170
    def is_admin(self, _id: int) -> bool:
171
        """Проверяет, является ли пользователь администратором бота
172
173
        Arguments:
174
            _id: Идентификатор пользователя для проверки привелегий
175
        Returns:
176
            bool: Флаг, указывающий на принадлежность текущего пользователя к касте Администраторов
177
        """
178
        return str(_id) in self.admins
179
180
    def send_gui(self, pid: int, text: str = "Привет!") -> NoReturn:
181
        """Отправляет клавиатуру главного меню
182
        
183
        Arguments:
184
            pid: Получатель клавиатуры
185
            text: Сообщение, вместе с которым будет отправлена клавиатура
186
        """
187
        self.send_message(
188
            msg=text, pid=pid, keyboard=self.kbs.generate_main_menu(self.is_admin(pid)),
189
        )
190
191
    def update_version(self):
192
        """
193
        Обновляет версию в статусе группы с ботом
194
        """
195
        log = logger.init_logger()
196
197
        log.info("Обновление версии в статусе группы...")
198
        try:
199
            with open("VERSION.txt", "r") as f:
200
                v = f"Версия: {f.read()}"
201
            self.user_vk.status.set(text=v, group_id=self.gid)
202
        except vk_api.exceptions.ApiError as e:
203
            log.error(f"Ошибка {e.__str__()}")
204
        else:
205
            log.info(f"Статус группы успешно обновлён.")
206