Refactoring of structure

This commit is contained in:
REDNBLACK 2016-11-12 15:37:14 +03:00
parent 75c6effe9a
commit 35b65126f9
17 changed files with 212 additions and 203 deletions

View File

@ -4,10 +4,11 @@ name=
anchors=
messages=
purge_interval=43200.0
default_chance=5
[grammar]
end_sentence=.!?
all=.!?,;:
end_sentence=.....!!?
all=.!?,;:()\"
[logging]
level=INFO

View File

@ -1,3 +1,3 @@
from src.chat_purge_queue_handler import ChatPurgeQueueHandler
from src.chat_purge_queue import ChatPurgeQueue
chat_purge_queue_handler = ChatPurgeQueueHandler()
chat_purge_queue = ChatPurgeQueue()

View File

@ -1,9 +1,10 @@
import logging
from telegram.ext import Updater
from src.command_handler import CommandHandler
from src.message_handler import MessageHandler
from . import chat_purge_queue_handler
from src.handlers.message_handler import MessageHandler
from src.handlers.command_handler import CommandHandler
from src.handlers.status_handler import StatusHandler
from . import chat_purge_queue
class Bot:
@ -14,15 +15,15 @@ class Bot:
def run(self):
logging.info("Bot started")
chat_purge_queue_handler.init(
chat_purge_queue.init(
queue=self.updater.job_queue,
default_interval=self.config['bot']['purge_interval']
)
message_handler = MessageHandler(self.config)
command_handler = CommandHandler()
self.dispatcher.add_handler(MessageHandler(self.config))
self.dispatcher.add_handler(CommandHandler(self.config))
self.dispatcher.add_handler(StatusHandler(self.config))
self.dispatcher.add_handler(message_handler)
self.dispatcher.add_handler(command_handler)
self.updater.start_polling()
self.updater.idle()

View File

@ -1,9 +1,11 @@
import logging
from telegram.ext import Job
from src.domain.chat import Chat
from src.entity.chat import Chat
class ChatPurgeQueueHandler:
class ChatPurgeQueue:
queue = None
default_interval = 99999.0
jobs = {}
@ -49,7 +51,7 @@ class ChatPurgeQueueHandler:
chat_id = job.context
logging.info("Removing chat #%d data..." % chat_id)
chat = Chat.find(job.context)
chat = Chat.find(chat_id)
if chat is not None:
chat.pairs().delete()
chat.delete()

View File

@ -1,104 +0,0 @@
from telegram.ext import Handler
from telegram import Update
from src.domain.chat import Chat
class CommandHandler(Handler):
def __init__(self, allow_edited = False, pass_update_queue = False, pass_job_queue=False,
pass_user_data = False, pass_chat_data = False):
super(CommandHandler, self).__init__(self.handle,
pass_update_queue = pass_update_queue,
pass_job_queue = pass_job_queue,
pass_user_data = pass_user_data,
pass_chat_data = pass_chat_data)
self.allow_edited = allow_edited
self.commands = {
'start': self.__start_command,
'help': self.__help_command,
'ping': self.__ping_command,
'set_chance': self.__set_chance_command,
'get_chance': self.__get_chance_command,
'get_stats': self.__get_stats_command
}
def check_update(self, update):
if isinstance(update, Update) and (update.message or update.edited_message and self.allow_edited):
message = update.message or update.edited_message
return (message.text and message.text.startswith('/') and self.__parse_command_name(update) in self.commands)
else:
return False
def handle_update(self, update, dispatcher):
optional_args = self.collect_optional_args(dispatcher, update)
message = update.message or update.edited_message
optional_args['args'] = message.text.split()[1:]
return self.callback(dispatcher.bot, update, **optional_args)
def handle(self, bot, update, args):
try:
command = self.__parse_command_name(update)
method = self.commands[command]
method(update, args)
except (IndexError, ValueError):
update.message.reply_text('Invalid command! Type /help')
def __parse_command_name(self, update):
message = update.message or update.edited_message
return message.text[1:].split(' ')[0].split('@')[0]
def __start_command(self, update, args):
update.message.reply_text('Hi! :3')
def __help_command(self, update, args):
update.message.reply_text(
"""Add me to your group and let me listen to your chat for a while.
When I learn enough word pairs, I'll start bringing fun and absurdity to your conversations.
Available commands:
/ping,
/get_stats: get the number of word pairs I've learned in this chat,
/set_chance: set the chance that I'll reply to a random message (must be in range 1-50, default: 5),
/get_chance: get the current chance of my random reply.
If you get tired of me, you can kick me from the group.
In 12 hours, I'll forget everything that have been learned in your chat, so you can add me again and teach me new things!
"""
)
def __ping_command(self, update, args):
update.message.reply_text('pong')
def __set_chance_command(self, update, args):
try:
random_chance = int(args[0])
if random_chance < 1 or random_chance > 50:
raise ValueError
chat = Chat.get_chat(update.message)
chat.random_chance = random_chance
chat.save()
update.message.reply_text('Set chance to: {}'.format(random_chance))
except (IndexError, ValueError):
update.message.reply_text('Usage: /set_chance 1-50.')
def __get_chance_command(self, update, args):
update.message.reply_text('Current chance: {}'.format(Chat.get_chat(update.message).random_chance))
def __get_stats_command(self, update, args):
update.message.reply_text('Pairs: {}'.format(Chat.get_chat(update.message).pairs().count()))

15
src/domain/command.py Normal file
View File

@ -0,0 +1,15 @@
class Command:
def __init__(self, chat, message, config):
self.chat = chat
self.message = message
self.config = config
self.name = Command.parse_name(message)
self.args = Command.parse_args(message)
@staticmethod
def parse_name(message):
return message.text[1:].split(' ')[0].split('@')[0]
@staticmethod
def parse_args(message):
return message.text.split()[1:]

View File

@ -1,14 +1,12 @@
import random
import src.domain.chat as chat
from src.utils import deep_get_attr
class Message:
def __init__(self, message, config):
def __init__(self, chat, message, config):
self.chat = chat
self.message = message
self.config = config
self.chat = chat.Chat.get_chat(message)
if self.has_text():
self.text = message.text
@ -48,20 +46,6 @@ class Message:
"""
return self.message.chat.type == 'private'
def is_bot_kicked(self):
"""Returns True if the bot was kicked from group.
"""
user_name = deep_get_attr(self.message, 'left_chat_member.username')
return user_name == self.config['bot']['name']
def is_bot_added(self):
"""Returns True if the bot was added to group.
"""
user_name = deep_get_attr(self.message, 'new_chat_member.username')
return user_name == self.config['bot']['name']
def is_reply_to_bot(self):
"""Returns True if the message is a reply to bot.
"""
@ -72,12 +56,7 @@ class Message:
def is_random_answer(self):
"""Returns True if reply chance for this chat is high enough
"""
return random.randint(0, 100) < getattr(self.chat, 'random_chance', 5)
def is_command(self):
"""Returns True if the message is a command (`/start`, `/do_stuff`).
"""
return self.has_text() and self.text[0] == '/'
return random.randint(0, 100) < getattr(self.chat, 'random_chance', self.config['bot']['default_chance'])
def __get_words(self):
text = list(self.text)

22
src/domain/status.py Normal file
View File

@ -0,0 +1,22 @@
from src.utils import deep_get_attr
class Status:
def __init__(self, chat, message, config):
self.chat = chat
self.message = message
self.config = config
def is_bot_kicked(self):
"""Returns True if the bot was kicked from group.
"""
user_name = deep_get_attr(self.message, 'left_chat_member.username')
return user_name == self.config['bot']['name']
def is_bot_added(self):
"""Returns True if the bot was added to group.
"""
user_name = deep_get_attr(self.message, 'new_chat_member.username')
return user_name == self.config['bot']['name']

0
src/entity/__init__.py Normal file
View File

View File

@ -1,8 +1,9 @@
from orator.orm import Model
from orator.orm import has_many
import logging
import src.domain.pair
from orator.orm import Model
from orator.orm import has_many
import src.entity.pair
class Chat(Model):
@ -10,7 +11,7 @@ class Chat(Model):
@has_many
def pairs(self):
return src.domain.pair.Pair
return src.entity.pair.Pair
# def migrate_to_chat_id(self, new_id):
# logging.info("[Chat %s %s] Migrating ID to %s" % (self.chat_type, self.telegram_id, new_id))
@ -19,10 +20,7 @@ class Chat(Model):
@staticmethod
def get_chat(message):
telegram_id = message.chat.id
type = message.chat.type
return Chat.first_or_create(telegram_id=telegram_id, chat_type=type)
return Chat.first_or_create(telegram_id=message.chat.id, chat_type=message.chat.type)
# Events
Chat.created(lambda chat: logging.info("[Chat %s %s] Created with internal ID #%s" %

View File

@ -1,15 +1,13 @@
from datetime import datetime, timedelta
from orator.orm import Model
from orator.orm import belongs_to
from orator.orm import has_many
import src.entity.reply
import src.entity.chat
import src.entity.word
from src.utils import *
import src.domain.chat
import src.domain.reply
import src.domain.word
class Pair(Model):
__guarded__ = ['id']
@ -20,19 +18,19 @@ class Pair(Model):
@has_many
def replies(self):
return src.domain.reply.Reply
return src.entity.reply.Reply
@belongs_to
def chat(self):
return src.domain.chat.Chat
return src.entity.chat.Chat
@belongs_to
def first(self):
return src.domain.word.Word
return src.entity.word.Word
@belongs_to
def second(self):
return src.domain.word.Word
return src.entity.word.Word
@staticmethod
def generate(message):
@ -40,7 +38,7 @@ class Pair(Model):
@staticmethod
def generate_story(message, words, sentences):
words_ids = src.domain.word.Word.where_in('word', words).get().pluck('id').all()
words_ids = src.entity.word.Word.where_in('word', words).get().pluck('id').all()
result = []
for _ in range(0, sentences):
@ -50,7 +48,7 @@ class Pair(Model):
@staticmethod
def learn(message):
src.domain.word.Word.learn(message.words)
src.entity.word.Word.learn(message.words)
words = [None]
for word in message.words:
@ -63,7 +61,7 @@ class Pair(Model):
while any(word for word in words):
trigram = words[:3]
first_word_id, second_word_id, *third_word_id = list(map(
lambda x: None if x is None else src.domain.word.Word.where('word', x).first().id,
lambda x: None if x is None else src.entity.word.Word.where('word', x).first().id,
trigram
))
third_word_id = None if len(third_word_id) == 0 else third_word_id[0]

View File

@ -2,8 +2,8 @@ from orator.orm import Model
from orator.orm import belongs_to
from orator.orm import belongs_to_many
import src.domain.pair
import src.domain.word
import src.entity.pair
import src.entity.word
class Reply(Model):
@ -12,8 +12,8 @@ class Reply(Model):
@belongs_to_many
def pairs(self):
return src.domain.pair.Pair
return src.entity.pair.Pair
@belongs_to
def word(self):
return src.domain.word.Word
return src.entity.word.Word

View File

@ -1,8 +1,9 @@
from collections import OrderedDict
from orator.orm import Model
from orator.orm import has_many
import src.domain.chat
from collections import OrderedDict
import src.entity.chat
class Word(Model):
@ -11,7 +12,7 @@ class Word(Model):
@has_many
def chats(self):
return src.domain.chat.Chat
return src.entity.chat.Chat
@staticmethod
def learn(words):

0
src/handlers/__init__.py Normal file
View File

View File

@ -0,0 +1,83 @@
from telegram import Update
from telegram.ext import Handler
from src.entity.chat import Chat
from src.domain.command import Command
class CommandHandler(Handler):
def __init__(self, config):
super(CommandHandler, self).__init__(self.handle)
self.config = config
self.commands = {
'start': self.__start_command,
'help': self.__help_command,
'ping': self.__ping_command,
'set_chance': self.__set_chance_command,
'get_chance': self.__get_chance_command,
'get_stats': self.__get_stats_command
}
def check_update(self, update):
if isinstance(update, Update) and update.message:
message = update.message
return message.text and message.text.startswith('/') and Command.parse_name(message) in self.commands
else:
return False
def handle_update(self, update, dispatcher):
optional_args = self.collect_optional_args(dispatcher, update)
return self.callback(dispatcher.bot, update, **optional_args)
def handle(self, bot, update):
try:
chat = Chat.get_chat(update.message)
command = Command(chat=chat, message=update.message, config=self.config)
callback = self.commands[command.name]
callback(update, command)
except (IndexError, ValueError):
update.message.reply_text('Invalid command! Type /help')
def __start_command(self, update, command):
update.message.reply_text('Hi! :3')
def __help_command(self, update, command):
update.message.reply_text(
"""Add me to your group and let me listen to your chat for a while.
When I learn enough word pairs, I'll start bringing fun and absurdity to your conversations.
Available commands:
/ping,
/get_stats: get the number of word pairs I've learned in this chat,
/set_chance: set the chance that I'll reply to a random message (must be in range 1-50, default: 5),
/get_chance: get the current chance of my random reply.
If you get tired of me, you can kick me from the group.
In 12 hours, I'll forget everything that have been learned in your chat, so you can add me again and teach me new things!
"""
)
def __ping_command(self, update, command):
update.message.reply_text('pong')
def __set_chance_command(self, update, command):
try:
random_chance = int(command.args[0])
if random_chance < 1 or random_chance > 50:
raise ValueError
command.chat.update(random_chance=random_chance)
update.message.reply_text('Set chance to: {}'.format(random_chance))
except (IndexError, ValueError):
update.message.reply_text('Usage: /set_chance 1-50.')
def __get_chance_command(self, update, command):
update.message.reply_text('Current chance: {}'.format(command.chat.random_chance))
def __get_stats_command(self, update, command):
update.message.reply_text('Pairs: {}'.format(command.chat.pairs().count()))

View File

@ -3,30 +3,21 @@ import logging
from telegram.ext import MessageHandler as ParentHandler, Filters
from src.domain.message import Message
from src.domain.pair import Pair
from . import chat_purge_queue_handler
from src.entity.pair import Pair
from src.entity.chat import Chat
class MessageHandler(ParentHandler):
def __init__(self,
config,
allow_edited=False,
pass_update_queue=False,
pass_user_data=False,
pass_chat_data=False):
def __init__(self, config):
super(MessageHandler, self).__init__(
Filters.text | Filters.sticker | Filters.status_update,
self.handle,
pass_update_queue=pass_update_queue,
pass_job_queue=False,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data,
allow_edited=allow_edited)
Filters.text | Filters.sticker,
self.handle)
self.config = config
def handle(self, bot, update):
message = Message(message=update.message, config=self.config)
chat = Chat.get_chat(update.message)
message = Message(chat=chat, message=update.message, config=self.config)
if message.has_text():
logging.debug("[Chat %s %s bare_text] %s" %
@ -34,14 +25,10 @@ class MessageHandler(ParentHandler):
message.chat.telegram_id,
message.text))
if message.has_text() and not (message.is_editing() or message.is_command()):
if message.has_text() and not message.is_editing():
return self.__process_message(bot, message)
elif message.is_sticker():
return self.__process_sticker(bot, message)
elif message.is_bot_added():
return self.__process_bot_add(message)
elif message.is_bot_kicked():
return self.__process_bot_kick(message)
def __process_message(self, bot, message):
Pair.learn(message)
@ -88,15 +75,3 @@ class MessageHandler(ParentHandler):
bot.sendSticker(chat_id=message.chat.telegram_id,
reply_to_message_id=message.message.message_id,
sticker=sticker_id)
def __process_bot_kick(self, message):
logging.debug("[Chat %s %s bot_kicked]" %
(message.chat.chat_type, message.chat.telegram_id))
chat_purge_queue_handler.add(message.chat.id)
def __process_bot_add(self, message):
logging.debug("[Chat %s %s bot_added]" %
(message.chat.chat_type, message.chat.telegram_id))
chat_purge_queue_handler.remove(message.chat.id)

View File

@ -0,0 +1,38 @@
import logging
from telegram.ext import MessageHandler, Filters
from src.domain.status import Status
from src.entity.chat import Chat
from src import chat_purge_queue
class StatusHandler(MessageHandler):
def __init__(self, config):
super(StatusHandler, self).__init__(
Filters.status_update,
self.handle,
pass_job_queue=True)
self.config = config
def handle(self, bot, update, job_queue):
chat = Chat.get_chat(update.message)
status = Status(chat=chat, message=update.message, config=self.config)
if status.is_bot_added():
return self.__process_bot_add(status, job_queue)
elif status.is_bot_kicked():
return self.__process_bot_kick(status, job_queue)
def __process_bot_kick(self, status, job_queue):
logging.debug("[Chat %s %s bot_kicked]" %
(status.chat.chat_type, status.chat.telegram_id))
chat_purge_queue.add(status.chat.id)
def __process_bot_add(self, status, job_queue):
logging.debug("[Chat %s %s bot_added]" %
(status.chat.chat_type, status.chat.telegram_id))
chat_purge_queue.remove(status.chat.id)