Added more comments

This commit is contained in:
REDNBLACK 2016-12-11 20:41:40 +03:00
parent 784214ef90
commit de3a9aa3da
14 changed files with 106 additions and 15 deletions

View File

@ -7,6 +7,9 @@ from src.handler import *
class Bot:
"""
Main initializer and dispatcher of messages
"""
def __init__(self):
self.updater = Updater(token=config['bot']['token'])
self.dispatcher = self.updater.dispatcher

View File

@ -2,13 +2,17 @@ from abc import ABC
class AbstractEntity(ABC):
"""
Base class for all message entities
"""
def __init__(self, message):
self.chat_id = message.chat.id
self.chat_type = message.chat.type
self.message = message
def is_private(self):
"""Returns True if the message is private.
"""Returns True if chat type is private.
"""
return self.message.chat.type == 'private'

View File

@ -2,6 +2,9 @@ from .abstract_entity import AbstractEntity
class Command(AbstractEntity):
"""
Special class for message which contains command
"""
def __init__(self, message):
super(Command, self).__init__(message)
self.name = Command.parse_name(message)
@ -9,8 +12,18 @@ class Command(AbstractEntity):
@staticmethod
def parse_name(message):
"""
Parses command name from given message
:param message: Telegram message object
:return: Name of command
"""
return message.text[1:].split(' ')[0].split('@')[0]
@staticmethod
def parse_args(message):
"""
Parses command args from given message
:param message: Telegram message object
:return: List of command args
"""
return message.text.split()[1:]

View File

@ -6,10 +6,14 @@ from src.config import config
class Message(AbstractEntity):
"""
Basic message entity
"""
def __init__(self, chance, message):
super(Message, self).__init__(message)
self.chance = chance
self.anchors = config.getlist('bot', 'anchors')
if self.has_text():
self.text = message.text
@ -19,39 +23,48 @@ class Message(AbstractEntity):
self.words = []
def has_text(self):
"""Returns True if the message has text.
"""
Returns True if the message has text.
"""
return self.message.text.strip() != ''
def is_sticker(self):
"""Returns True if the message is a sticker.
"""
Returns True if the message is a sticker.
"""
return self.message.sticker is not None
def has_entities(self):
"""Returns True if the message has entities (attachments).
"""
Returns True if the message has entities (attachments).
"""
return self.message.entities is not None
def has_anchors(self):
"""Returns True if the message contains at least one anchor from anchors config.
"""
anchors = config.getlist('bot', 'anchors')
return self.has_text() and any(a in self.message.text.split(' ') for a in anchors)
Returns True if the message contains at least one anchor from anchors config.
"""
return self.has_text() and any(a in self.message.text.split(' ') for a in self.anchors)
def is_reply_to_bot(self):
"""Returns True if the message is a reply to bot.
"""
Returns True if the message is a reply to bot.
"""
user_name = deep_get_attr(self.message, 'reply_to_message.from_user.username')
return user_name == config['bot']['name']
def is_random_answer(self):
"""Returns True if reply chance for this chat is high enough
"""
Returns True if reply chance for this chat is high enough
"""
return random.randint(0, 100) < self.chance
def should_answer(self):
"""
Returns True if bot should answer to this message
:return: Should answer or not
"""
return self.has_anchors() \
or self.is_private() \
or self.is_reply_to_bot() \

View File

@ -4,20 +4,24 @@ from src.config import config
class Status(AbstractEntity):
bot_name = config['bot']['name']
"""
Special class for message which contains info about status change
"""
def __init__(self, message):
super(Status, self).__init__(message)
self.bot_name = config['bot']['name']
def is_bot_kicked(self):
"""Returns True if the bot was kicked from group.
"""
Returns True if the bot was kicked from group.
"""
user_name = deep_get_attr(self.message, 'left_chat_member.username')
return user_name == self.bot_name
def is_bot_added(self):
"""Returns True if the bot was added to group.
"""
Returns True if the bot was added to group.
"""
user_name = deep_get_attr(self.message, 'new_chat_member.username')

View File

@ -8,6 +8,9 @@ import src.entity.word
class Pair(Model):
"""
Pair entity, represents pairs table.
"""
__fillable__ = ['chat_id', 'first_id', 'second_id']
__timestamps__ = ['created_at']

View File

@ -7,6 +7,9 @@ import src.entity.word
class Reply(Model):
"""
Reply entity, represents replies table.
"""
__fillable__ = ['pair_id', 'word_id', 'count']
__timestamps__ = False

View File

@ -1,5 +1,9 @@
from orator.orm import Model
class Word(Model):
"""
Word entity, represents words table.
"""
__fillable__ = ['word']
__timestamps__ = False

View File

@ -2,6 +2,9 @@ import redis
class Redis:
"""
Small redis wrapper, to simplify work with connection pool
"""
def __init__(self, config):
self.pool = redis.ConnectionPool(host=config['redis']['host'],
port=config.getint('redis', 'port'),

View File

@ -2,17 +2,31 @@ from src.config import config, redis
class ChanceManager:
"""
Handles bot reply chance
"""
def __init__(self):
self.redis = redis
self.key = 'chance:{}'
self.default_chance = config.getint('bot', 'default_chance')
def get_chance(self, chat_id):
"""
Returns current chance of bot reply for chat_id
:param chat_id: ID of chat
:return: Current chance
"""
result = self.redis.instance().get(self.key.format(chat_id))
return int(result.decode("utf-8")) if result is not None else self.default_chance
def set_chance(self, chat_id, new_chance):
"""
Sets new reply chance for chat_id and returns old
:param chat_id: ID of chat
:param new_chance: Chance to set
:return: Old chance
"""
old_chance = self.redis.instance().getset(self.key.format(chat_id), new_chance)
return int(old_chance.decode("utf-8")) if old_chance is not None else self.default_chance

View File

@ -9,6 +9,9 @@ from src.config import config, redis
class ChatPurgeQueue:
"""
Scheduling and execution of chat purge
"""
def __init__(self):
self.redis = redis
self.default_interval = config.getfloat('bot', 'purge_interval')
@ -24,6 +27,11 @@ class ChatPurgeQueue:
return self
def add(self, chat_id, interval=None):
"""
Schedules purge of chat data
:param chat_id: ID of chat
:param interval: Interval in seconds
"""
interval = interval if interval is not None else self.default_interval
scheduled_at = datetime.now() + timedelta(seconds=interval)
@ -41,6 +49,10 @@ class ChatPurgeQueue:
)
def remove(self, chat_id):
"""
Removes scheduled purge job from queue
:param chat_id: ID of chat
"""
if chat_id not in self.jobs:
return

View File

@ -6,6 +6,10 @@ from src.entity.pair import Pair
class DataLearner:
def learn(self, message):
"""
Split message to trigrams and write to DB
:param message: Message
"""
self.__write_new_unique_words(message.words)
words = self.__normalize_words(message.words)

View File

@ -4,6 +4,9 @@ from urllib.parse import urlparse
class MediaUniquenessChecker:
"""
Checks message links and photos for uniqueness
"""
def __init__(self):
self.redis = redis
self.key = "media_checker:{}"

View File

@ -6,15 +6,23 @@ from src.entity.pair import Pair
class ReplyGenerator:
"""
Handles generation of responses for user message
"""
def generate(self, message):
result = self.generate_story(message, message.words, random.randint(0, 2) + 1)
"""
Generates response based on message words
:param message: Message
:return: Response or empty string, if generated response equals to user message
"""
result = self.__generate_story(message, message.words, random.randint(0, 2) + 1)
if strings_has_equal_letters(result, ''.join(message.words)):
return ''
return result
def generate_story(self, message, words, sentences_count):
def __generate_story(self, message, words, sentences_count):
word_ids = Word.where_in('word', words).lists('id').all()
return ' '.join([self.__generate_sentence(message, word_ids) for _ in range(sentences_count)])