Little convenience in student life

3r3658. A simple story about how I felt ashamed to constantly ask the groupmates for missing information and I decided to make our lives a little easier. 3r3661. 3r3662.  3r3777. 3r3658. Little convenience in student life 3r3661. 3r3662.  3r3777. 3r3658. I suppose that many of my peers are familiar with the situation when in the general chat, where important information flashes quite often, there are about 30 active interlocutors who constantly load Vkontakte databases with their messages. Under these conditions, it is unlikely that everyone will see this important information. It happens to me. A year ago, it was decided to correct this misunderstanding. 3r3661. 3r3662.  3r3777. 3r3658. Those who are ready not to resent the next article about the bot, please under the cat. 3r3661. vk_api for use Vk Api
 3r3777. 3r3666. peewee orm to work with the database
 3r3777. 3r3666. and built-in python
modules.  3r3777.
3r3662.  3r3777. 3r3658. Also, before reading, I suggest refreshing the Observer patterns (3r3358. Habr 3r?6668., 3r3-360. Wiki 3r?6688.) And Fasade (3r3626. Habr 3r3668.,
rr3r3r3r3r3r3r3r3r3r3r3r3r3r3r3r3r3r3r3r3r3r3r3r3r. 3r3662.  3r3777. 3r3658. 3r3659. Part 1. "Nice to meet you, comrade bot." 3r3660. 3r3661. 3r3662.  3r3777. 3r3658. First you need to teach our bot to understand itself as a community. Create a class called Group. Let the session object and the representative (Proxy) object of the database be taken as arguments. 3r3661. 3r3662.  3r3777. 3r3630. 3r3633. class Group (BaseCommunicateVK):
3r3777. def __init __ (self, vksession, storage):
super () .__ init __ (vksession)
self.storage = storage 3r3638. 3r3662.  3r3777. 3r? 3534. 3r33535. BaseCommunicateVK? What is there? 3r33536. 3r33537. 3r3658. The decision to bring this functionality into a separate class is explained by the fact that in the future, one of you may decide to supplement the bot with some other Vkontakte functionality. 3r3662.  3r3777. Well, to relieve the abstraction of the community, of course. 3r3661. 3r3662.  3r3777. 3r3630. 3r3633. class BaseCommunicateVK:
3r3777. longpoll = None
3r3777. def __init __ (self, vksession):
self.session = vksession
self.api = vksession.get_api ()
if BaseCommunicateVK.longpoll is None:
BaseCommunicateVK.longpoll = VkLongPoll (self.session)
3r3777. def get_api (self):
Return self.api
3r3777. def get_longpoll (self):
return self.longpoll
3r3777. def method (self, func, args):
return self.api.method (func, args)
3r3777. @staticmethod
def create_session (token = None, login = None, password = None, api_v = '???'):
try: 3r3777. if token:
session = vk_api.VkApi (token = token, api_version = api_v)
3r3777. elif login and password:
session = vk_api.VkApi (login, password, api_version = api_v)
3r3777. else:
raise vk_api.AuthError ("Define login and password or token.")
3r3777. return session
3r3777. except vk_api.ApiError as error:
logging.info (error)
3r3777. def get_last_message (self, user_id):
3r3777. return self.api.messages.getHistory (
peer_id = user_id, count = 1)["items"] [0]3r3777. 3r3777. @staticmethod
def get_attachments (last_message):
3r3777. if not last_message or "attachments" not in last_message:
Return "" 3r37777. 3r3777. attachments = last_message["attachments"]3r3777. attach_strings =[]3r3777. 3r3777. for attachments in attachments:
3r3777. attach_type = attach["type"]3r3777. attach_info = attach[attach_type]3r3777. 3r3777. attach_id = attach_info["id"]3r3777. attach_owner_id = attach_info["owner_id"]3r3777. 3r3777. if "access_key" in attach_info:
access_key = attach_info["access_key"]3r3777. attach_string = "{} {} _ {} _ {}". format (attach_type, attach_owner_id, attach_id, access_key)
3r3777. else:
attach_string = "{} {} _ {}". format (attach_type, attach_owner_id, attach_id)
3r3777. attach_strings.append (attach_string)
3r3777. return ",". join (attach_strings) 3r37777. 3r3777. @staticmethod
def get_forwards (attachments, last_message):
3r3777. if not attachments or "fwd_count" not in attachments:
Return "" 3r37777. 3r3777. if len (last_message["fwd_messages"]) == int (attachments["fwd_count"]):
return last_message["id"]3r3777. 3r3777. def send (self, user_id, message, attachments = None, ** kwargs):
send_to = int (user_id) 3r33677. 3r3777. if "last_message" in kwargs:
last_message = kwargs["last_message"]3r3777. else:
last_message = None
3r3777. p_attachments = self.get_attachments (last_message)
p_forward = self.get_forwards (attachments, last_message)
3r3777. if message or p_attachments or p_forward:
self.api.messages.send (
user_id = send_to, message = message,
attachment = p_attachments,
forward_messages = p_forward)
3r3777. if destroy:
accept_msg_id = self.api.messages
.getHistory (peer_id = user_id, count = 1)
.get ('items')[0].get ('id')
3r3777. self.delete (accept_msg_id, destroy_type = destroy_type)
3r3777. def delete (self, msg_id, destroy_type = 1):
self.api.messages.delete (message_id = msg_id, delete_for_all = destroy_type) 3r3638. 3r3678. 3r3678. 3r3662.  3r3777. 3r3658. Create a method for updating community members. Immediately divide them into administrators and members and save in the database. 3r3661. 3r3662.  3r3777. 3r3664.  3r3777. 3r3666. self.api is configured when creating the Group base class (BaseCommunicateVK)
 3r3777.
3r3662.  3r3777. 3r3630. 3r3633. def update_members (self):
fields = 'domain, sex'
3r3777. admins = self.api.groups.getMembers (group_id = self.group_id, fields = fields, filter = 'managers') 3r37777. self.save_members (self._configure_users (admins))
3r3777. members = self.api.groups.getMembers (group_id = self.group_id, fields = fields)
self.save_members (self._configure_users (members))
3r3777. return self
3r3777. def save_members (self, members):
self.storage.update (members)
3r3777. @staticmethod
def _configure_users (items, exclude = None):
3r3777. if exclude is None:
exclude =[]3r3777. 3r3777. users =[]3r3777. for user in items.get ('items'):
3r3777. if user.get ('id') not in exclude:
member = User ()
member.configure (** user)
users.append (member)
3r3777. return users 3r3638. 3r3662.  3r3777. 3r3658. Also, this class should be able to send messages to addressees, therefore the following method is in the studio. In the parameters: the list of recipients, text messages and applications. All this business is launched in a separate thread so that the bot can receive messages from other participants. 3r3662.  3r3777. Messages are received in synchronous mode, so with an increase in the number of active clients, the response speed will obviously diminish. 3r3661. 3r3662.  3r3777. 3r3630. 3r3633. def broadcast (self, uids, message, attachments = None, ** kwargs):
3r3777. report = BroadcastReport ()
3r3777. def send_all ():
users_ids = uids
if not isinstance (users_ids, list):
users_ids = list (users_ids)
3r3777. report.should_be_sent = len (users_ids)
3r3777. for user_id in users_ids:
try: 3r3777. self.send (user_id, message, attachments, ** kwargs)
if message or attachments:
report.sent + = 1 r3r3677. 3r3777. except vk_api.VkApiError as error:
report.errors.append ('vk.com/id {}: {}'. format (user_id, error))
3r3777. except ValueError:
continue
3r3777. for uid in self.get_member_ids (admins = True, moders = True):
self.send (uid, str (report)) 3r33677. 3r3777. broadcast_thread = Thread (target = send_all)
broadcast_thread.start ()
broadcast_thread.join () 3r3638. 3r3662.  3r3777. 3r? 3534. 3r33535. BroadcastReport - report class 3r33636. 3r33537. 3r3630. 3r3633. class BroadcastReport:
3r3777. def __init __ (self):
self.should_be_sent = 0
self.sent = 0
self.errors =[]3r3777. 3r3777. def __str __ (self):
res = "# Report #"
res + = "nplan: {} messages" .format (self.should_be_sent)
res + = "n distributed: {}" .format (self.sent)
if self.errors:
res + = "n Errors:"
for i in self.errors:
res + = "n- {}". format (i) 3r3-36777. return res 3r3638. 3r3678. 3r3678. 3r3662.  3r3777. 3r3658. On this, it seems, the abstraction of the group is complete. We met with all members of the community, now we must learn to understand them. 3r3661. 3r3662.  3r3777. 3r3658. 3r3659. Part 2. "Psh welcome " 3r363660. 3r3661. 3r3662.  3r3777. 3r3658. Make the bot listen to all messages from members of our community. 3r3662.  3r3777. To do this, create a class HandHandler, which will deal with this
 3r3777. In the parameters:
3r3662.  3r3777. 3r3664.  3r3777. 3r3666. group_manager is an instance of the
community class we just wrote.  3r3777. 3r3666. command_observer recognizes connected commands (but about this in the third part)
 3r3777.
3r3662.  3r3777. 3r3630. 3r3633. class ChatHandler (Handler):
3r3777. def __init __ (self, group_manager, command_observer):
super () .__ init __ () 3r-33677. 3r3777. self.longpoll = group_manager.get_longpoll ()
self.group = group_manager
self.api = group_manager.get_api ()
self.command_observer = command_observer 3r3638. 3r3662.  3r3777. 3r3658. Next, in fact, we listen to messages from users and recognize the commands. 3r3661. 3r3662.  3r3777. 3r3630. 3r3633. def listen (self):
try: 3r3777. for event in self.longpoll.listen ():
if event.user_id and event.type == VkEventType.MESSAGE_NEW and event.to_me:
self.group.api.messages.markAsRead (peer_id = event.user_id)
self.handle (event.user_id, event.text, event.attachments, message_id = event.message_id)
3r3777. except Connectionerror:
logging.error ("I HAVE BEEN DOWNED AT {}". format (datetime.datetime.today ())) 3r37777. self.longpoll.update_longpoll_server ()
3r3777. def handle (self, user_id, message, attachments, ** kwargs):
member = self.group.get_member (user_id)
self.group.update_members ()
self.command_observer.execute (member, message, attachments, self.group, ** kwargs)
3r3777. def run (self):
self.listen () 3r3638. 3r3662.  3r3777. 3r3658. 3r3659. Part 3. "What did you write about my ?" 3r3660. 3r3661. 3r3662.  3r3777. 3r3658. A separate subsystem implemented through the "Observer" pattern deals with the recognition of commands. 3r3662.  3r3777. Attention, CommandObserver:
3r3662.  3r3777. 3r3630. 3r3633. class CommandObserver (AbstractObserver):
3r3777. def execute (self, member, message, attachments, group, ** kwargs):
3r3777. for command in self.commands:
3r3777. for trigger in command.triggers:
3r3777. body = command.get_body (trigger, message) 3r37777. if body is not None:
group.api.messages.setActivity (user_id = member.id, type = "typing")
3r3777. if command.system:
kwargs.update ({"trigger": trigger, "commands": self.commands})
else:
kwargs.update ({"trigger": trigger})
3r3777. return command.proceed (member, body, attachments, group, ** kwargs) 3r3638. 3r3662.  3r3777. 3r? 3534. 3r33535. AbstractObserver [/b] 3r33537. 3r3658. Again, the imposition is made for future possible expansion. 3r3661. 3r3662.  3r3777. 3r3630. 3r3633. class AbstractObserver (metaclass = ABCMeta):
3r3777. def __init __ (self):
self.commands =[]3r3777. 3r3777. def add (self, * args):
for arg in args:
self.commands.append (arg)
3r3777. @abstractmethod
def execute (self, * args, ** kwargs):
pass 3r3638. 3r3678. 3r3678. 3r3662.  3r3777. 3r3658. But what will this observer recognize? 3r3662.  3r3777. So we got to the most interesting - the team. 3r3662.  3r3777. Each command is an independent class, a descendant of the base Command class. 3r3662.  3r3777. All that is required of the command is to run the proceed () method if its keyword is found at the beginning of the user's message. The command keywords are defined in the triggers variable of the command class (string or list of strings) 3r3661. 3r3662.  3r3777. 3r3630. 3r3633. class Command (metaclass = ABCMeta):
3r3777. def __init __ (self):
self.triggers =[]3r3777. self.description = "Empty description." 3r3777. 3r3777. self.system = False
self.privilege = False
3r3777. self.activate_times =[]3r3777. self.activate_days = set ()
self.autostart_func = self.proceed
3r3777. def proceed (self, member, message, attachments, group, ** kwargs):
raise NotImplementedError ()
3r3777. @staticmethod
def get_body (kw, message): 3r33677. if not isinstance (kw, list): kw =[kw, ]3r3777. 3r3777. for i in kw:
reg = '^ * ( {}) *'. format (i)
3r3777. if re.search (reg, message): 3r33677. return re.sub (reg, '', message) .strip ('') 3r3638. 3r3662.  3r3777. 3r3658. As can be seen from the signature of the proceed () method, each team receives as input a link to an instance of a group member, its message (without a keyword), applications, and a link to an instance of a group. That is, all interaction with a group member falls on the shoulders of the team. I think this is the most correct decision, since it is thus possible to create a shell (Shell) for greater interactivity. 3r3662.  3r3777. (In truth, for this you will either need to make an asynchronous, because the processing is synchronous, or each received message is processed in a new thread, which is not profitable) 3r3661. 3r3662.  3r3777. 3r3658. Examples of the implementation of commands: 3r36666. 3r3662.  3r3777. 3r? 3534. 3r33535. BroadcastCommand [/b] 3r33537. 3r3630. 3r3633. class BroadcastCommand (Command):
3r3777. def __init __ (self):
super () .__ init __ () 3r-33677. self.triggers =['.mb']3r3777. 3r3777. self.privilege = True
self.description = "Sending a message to all members of the community." 3r3777. 3r3777. def proceed (self, member, message, attachments, group, ** kwargs):
if member.id not in group.get_member_ids (admins = True, editors = True):
group.send (member.id, "You cant do this ^ _ ^")
return True 3r3r777. 3r3777. last_message = group.get_last_message (member.id)
group.broadcast (group.get_member_ids (), message, attachments, last_message = last_message, ** kwargs)
3r3777. return True 3r3637. 3r3638. 3r3678. 3r3678. 3r3662.  3r3777. 3r? 3534. 3r33535. HelpCommand [/b] 3r33537. 3r3630. 3r3633. class HelpCommand (Command):
3r3777. def __init __ (self):
super () .__ init __ () 3r-33677. self.commands =[]3r3777. self.triggers =['.h', '.help']3r3777. 3r3777. self.system = True
self.description = "Showing this message." 3r3777. 3r3777. def proceed (self, member, message, attachments, group, ** kwargs):
commands = kwargs["commands"]3r3777. help = "The following commands are implemented: nn"
admins = group.get_member_ids (admins = True, moders = True)
i = 0 3r37777. for command in commands:
if command.privilege and member.id not in admins:
continue
help + = "{}) {} nn" .format (i + ? command.name ()) 3r37777. i + = 1 3r37777. group.send (member.id, help)
return True 3r3637. 3r3638. 3r3678. 3r3678. 3r3662.  3r3777. 3r3658. 3r3659. Part 4. "We are one big team." 3r3660. 3r3661. 3r3662.  3r3777. 3r3658. Now all these modules and handlers need to be combined and configured. 3r3662.  3r3777. One more class please! 3r3662.  3r3777. Create a facade that will customize our bot. 3r3661. 3r3662.  3r3777. 3r3630. 3r3633. class VKManage:
3r3777. def __init __ (self, token = None, login = None, password = None):
self.session = BaseCommunicateVK.create_session (token, login, password, api_version)
3r3777. self.storage = DBProxy (Database)
self.group = Group (self.session, self.storage) .setup (). update_members ()
self.chat = ChatHandler (self.group, CommandObserver.get_observer ())
3r3777. def start (self):
self.chat.run ()
3r3777. def get_command (self, command_name):
return {3r3777. "sending to the participants": BroadcastCommand (), 3r33677. "sending to admins": AdminBroadcastCommand (),
Help: HelpCommand (), 3r37777. "accounting for absenteeism": SkippedLectionsCommand (),
schedule: TopicTimetableCommand (). setup_account (self.bot.api),
} .get (command_name)
3r3777. def connect_command (self, command_name):
command = self.get_command (str (command_name) .lower ())
if command:
self.chat.command_observer.add (command)
return self
3r3777. def connect_commands (self, command_names):
for i in command_names.split (','): self.connect_command (i.strip ())
return self 3r3638. 3r3662.  3r3777. 3r3658. The last stage is the launch. Always the nastiest, because some kind of surprise might come out. Not this time. 3r3661. 3r3662.  3r3777. 3r3664.  3r3777. 3r3666. ConfigParser is imported from core.settings.ConfigParser. In fact, just reads the config. 3r3669.  3r3777. 3r3666. 3r3658. project_path is imported from the settings module in the project root. 3r3661. 3r3662.  3r3777. 3r3630. 3r3633. if __name__ == '__main__':
config = ConfigParser (project_path)
3r3777. VKManage (token = config['token'], Login = config['login'], Password = config['password'])
.connect_commands ("help, sending out to participants, sending out to admins, recording absenteeism")
.start () 3r36337. 3r3638. 3r3662.  3r3777. 3r3669.  3r3777.
3r3662.  3r3777. 3r3658. This seems to be all. 3r3661. 3r3662.  3r3777. 3r3658. At the moment, this program has benefited at least three groups and, I hope, it will bring you too. 3r3661. 3r3662.  3r3777. 3r3658. You can deploy for free on Heroku, but that's another story. 3r3661. 3r3662.  3r3777. 3r3658. 3r3659. References: 3r36036. 3r3661. 3r3662.  3r3777. 3r3664.  3r3777. 3r3666. 3r3667. 3r3668 repository. on github
 3r3777.
3r3678. 3r3777. 3r3777. 3r3675. ! function (e) {function t (t, n) {if (! (n in e)) {for (var r, a = e.document, i = a.scripts, o = i.length; o-- ;) if (-1! == i[o].src.indexOf (t)) {r = i[o]; break} if (! r) {r = a.createElement ("script"), r.type = "text /jаvascript", r.async =! ? r.defer =! ? r.src = t, r.charset = "UTF-8"; var d = function () {var e = a.getElementsByTagName ("script")[0]; e. ): d ()}}} t ("//mediator.mail.ru/script/2820404/"""_mediator") () ();
3r3777. 3r3678.
+ 0 -

Add comment