From 18cc05c1c3014af348310a7ba068576d101c16b0 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Fri, 17 May 2019 17:03:06 +0100 Subject: [PATCH 1/6] Add class GitService (prov names) --- Pipfile | 1 + vcs.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 vcs.py diff --git a/Pipfile b/Pipfile index d03f378..16ec328 100644 --- a/Pipfile +++ b/Pipfile @@ -6,6 +6,7 @@ name = "pypi" [packages] python-telegram-bot = "==11.1.0" prettyconf = "==2.0.1" +pygithub = "*" [dev-packages] "flake8" = "*" diff --git a/vcs.py b/vcs.py new file mode 100644 index 0000000..0581a2a --- /dev/null +++ b/vcs.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + + +from collections import namedtuple + + +Issue = namedtuple('Issue', ['issue_id', 'title']) + + +class GitService: + + user = None + + def __init__(self): + from github import Github + if GitService.user is None: + conn = Github() + GitService.user = conn.get_user('pythoncanarias') + + def list_repos(self) -> list: + """List the names of all the public repositories. + """ + return [r.name for r in self.user.get_repos()] + + def list_issues(self, repo_name: str) -> list: + repo = GitService.user.get_repo(repo_name) + result = [ + Issue(issue_id=i.number, title=i.title) + for i in repo.get_issues() + ] + return result From f0bf75cdef4a9e69535da08c35f5b5b8f76836f4 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Wed, 22 May 2019 14:37:53 +0100 Subject: [PATCH 2/6] =?UTF-8?q?A=C3=B1ade=20nuevos=20comandos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /settings muestra algunos de los valores de configuracion /debug para ser usado en desarrollo ademas, las respuestas y los comandos responden usando formateo con markdown --- .gitignore | 2 ++ bot.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++------- config.py | 5 +++++ 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 4d538a7..504c397 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ *.pyc +*.swp pycache +tags myconfig.py virtualenv .idea diff --git a/bot.py b/bot.py index a7d1353..51ebb89 100644 --- a/bot.py +++ b/bot.py @@ -14,17 +14,26 @@ def command_start(bot, update): logger.info('Received command /start') - bot.send_message(chat_id=update.message.chat_id, text=config.BOT_GREETING) + bot.send_message( + chat_id=update.message.chat_id, + text=config.BOT_GREETING, + parse_mode='Markdown', + ) def command_help(bot, update): logger.info('Received command /help') bot.send_message( chat_id=update.message.chat_id, - text="Available commands:\n" - " - /start - start intereaction with the bot\n" - " - /help - Show commands\n" - " - /status - Show status and alive time\n" + text='\n'.join([ + "Available commands:", + " - `/start` - start intereaction with the bot", + " - `/help` - Show commands", + " - `/status` - Show status and alive time", + " - `/settings` - Show current settings", + " - `/debug` - Show debug information for developers", + ]), + parse_mode='Markdown', ) @@ -32,8 +41,44 @@ def command_status(bot, update): logger.info('bot asked to execute /status commamd') bot.send_message( chat_id=update.message.chat_id, - text=f'Status is OK, running since {utils.since()}', - ) + text=f'Status is **OK**, running since {utils.since()}', + parse_mode='Markdown', + ) + + +def command_settings(bot, update): + logger.info('Received command /settings') + chat = update.message.chat + if config.VERBOSITY < 1.0: + verbosity_level = f'{round(config.VERBOSITY*100.0, 2)}%' + else: + verbosity_level = 'Palicoso' + bot.send_message( + chat_id=update.message.chat_id, + text="\n".join([ + f"Settings for {chat.title}:", + f" - Verbosity level: **{verbosity_level}**", + f" - Log level: **{config.LOG_LEVEL}**", + f" - Poll interval time (in seconds): **{config.POLL_INTERVAL}**", + f" - Repos (**0**)", + ]), + parse_mode='Markdown', + ) + + +def command_debug(bot, update): + logger.info('Received command /debug') + chat = update.message.chat + bot.send_message( + chat_id=update.message.chat_id, + text='\n'.join([ + "Chat:", + " - id: {}".format(chat.id), + " - type: {}".format(chat.type), + " - title: {}".format(chat.title) + ]), + parse_mode='Markdown', + ) def welcome(bot: Bot, update: Update): @@ -73,7 +118,8 @@ def reply(bot, update): logger.info(f'bot sends reply {reply_spec.reply}') bot.send_message( chat_id=update.message.chat_id, - text=reply_spec.reply + text=reply_spec.reply, + parse_mode='Markdown', ) @@ -90,6 +136,8 @@ def main(): dp.add_handler(CommandHandler('start', command_start)) dp.add_handler(CommandHandler('help', command_help)) dp.add_handler(CommandHandler('status', command_status)) + dp.add_handler(CommandHandler('settings', command_settings)) + dp.add_handler(CommandHandler('debug', command_debug)) dp.add_handler(MessageHandler(Filters.status_update.new_chat_members, welcome)) dp.add_handler(MessageHandler(Filters.group, reply)) diff --git a/config.py b/config.py index 89b7825..e36f719 100644 --- a/config.py +++ b/config.py @@ -104,6 +104,11 @@ def bot_replies_enabled() -> bool: "la puerta de Tannhäuser. Todos esos momentos se perderán en el " "tiempo... como lágrimas en la lluvia. Es hora de morir. 🔫", ("python", "pitón", "piton"): THE_ZEN_OF_PYTHON, + ("phyton",): [ + "Se dice **Python**, simplón", + "s/phython/python/", + "_Phyton_ también decía mi abuela...", + ], } MAXLEN_FOR_USERNAME_TO_TREAT_AS_HUMAN = 100 From a5378977d670ec3c3be986ceee37a4ffb296b83d Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Wed, 22 May 2019 18:39:28 +0100 Subject: [PATCH 3/6] failed --- bot.py | 38 ++++++++++++-------- dba.py | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 14 deletions(-) create mode 100644 dba.py diff --git a/bot.py b/bot.py index 51ebb89..3ba88a3 100644 --- a/bot.py +++ b/bot.py @@ -7,18 +7,21 @@ import config import utils - +import dba logger = logging.getLogger('bot') def command_start(bot, update): logger.info('Received command /start') - bot.send_message( - chat_id=update.message.chat_id, - text=config.BOT_GREETING, - parse_mode='Markdown', - ) + chat = update.message.chat + is_new = dba.save_chat(chat.id, chat.type, chat.title) + if is_new: + bot.send_message( + chat_id=update.message.chat_id, + text=config.BOT_GREETING, + parse_mode='Markdown', + ) def command_help(bot, update): @@ -57,8 +60,9 @@ def command_settings(bot, update): chat_id=update.message.chat_id, text="\n".join([ f"Settings for {chat.title}:", - f" - Verbosity level: **{verbosity_level}**", - f" - Log level: **{config.LOG_LEVEL}**", + f" - Database: `{config.DB_NAME}`", + f" - Verbosity level: `{verbosity_level}`", + f" - Log level: `{config.LOG_LEVEL}`", f" - Poll interval time (in seconds): **{config.POLL_INTERVAL}**", f" - Repos (**0**)", ]), @@ -69,14 +73,20 @@ def command_settings(bot, update): def command_debug(bot, update): logger.info('Received command /debug') chat = update.message.chat + conn = dba.get_connection() + all_chats = dba.get_rows(conn, 'SELECT * FROM chat') + buff = [ + "Chat:", + f" - id: {chat.id}", + f" - type: {chat.type}", + f" - title: {chat.title}", + f" - Chats: ({len(all_chats)})", + ] + for c in all_chats: + buff.append(f' - {c.title}') bot.send_message( chat_id=update.message.chat_id, - text='\n'.join([ - "Chat:", - " - id: {}".format(chat.id), - " - type: {}".format(chat.type), - " - title: {}".format(chat.title) - ]), + text='\n'.join(buff), parse_mode='Markdown', ) diff --git a/dba.py b/dba.py new file mode 100644 index 0000000..6312563 --- /dev/null +++ b/dba.py @@ -0,0 +1,110 @@ +import os + +import sqlite3 +from collections import OrderedDict +from collections.abc import MutableMapping + +import config + + +class ObjectRow(sqlite3.Row): + + def __getattr__(self, name): + if name in self.keys(): + return self[name] + else: + raise AttributeError(name) + + +def get_value(conn, sql, *args, cast=None): + cur = conn.cursor() + try: + cur.execute(str(sql), tuple(args)) + row = cur.fetchone() + if row: + result = row[0] + if cast is not None: + result = cast(result) + return result + else: + return default + finally: + cur.close() + + +def get_row(conn, sql, *args, cast=None): + cur = conn.cursor() + try: + cur.execute(str(sql), tuple(args)) + row = cur.fetchone() + if not row: + return {} + if cast is not None: + row = cast(row) + return row + finally: + cur.close() + + +def get_rows(conn, sql, *args, cast=None, limit=0): + cur = conn.cursor() + rows = [] + try: + cur.execute(str(sql), tuple(args)) + if limit > 0: + rows = cur.fetchmany(limit) + else: + rows = cur.fetchall() + if cast is not None: + rows = [cast(row) for row in rows] + return list(rows) + finally: + cur.close() + + +CREATE_TABLE_CHAT = ''' + CREATE TABLE IF NOT EXISTS chat ( + id_chat long, + title text, + chat_type text + ) +''' + +def initialize_db(conn): + execute(conn, CREATE_TABLE_CHAT) + +_conn = None + + +def get_connection(): + global _conn + if _conn is None: + db_name = config.DB_NAME + first_time = any([ + db_name == ':memory:', + not os.path.exists(db_name), + ]) + _conn = sqlite3.connect(db_name) + _conn.row_factory = ObjectRow + if first_time: + initialize_db(_conn) + return _conn + + +def execute(db, statement, *args): + cur = db.cursor() + try: + cur.execute(statement, args) + finally: + cur.close() + + + +def save_chat(id_chat, chat_type, title): + conn = get_connection() + exists = get_row(conn, 'SELECT * FROM chat where id_chat=?', id_chat) + if not exists: + sql = 'INSERT INTO chat (id_chat, chat_type, title) VALUES (?, ?, ?)' + execute(conn, sql, id_chat, chat_type, title) + return 1 + From a22faa45004f040e64f08eba743ea842cbd6769a Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Wed, 22 May 2019 18:40:08 +0100 Subject: [PATCH 4/6] failed --- config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/config.py b/config.py index e36f719..073b5f1 100644 --- a/config.py +++ b/config.py @@ -55,6 +55,7 @@ def log(logger_method): int, default=100) +DB_NAME = config('DB_NAME', default='pydeckard_db') # We have found, through empiric evidence, that a large ration of Chinese # characters usually indicates the user is a spammer or bot. From 2c22b782596992a3e25583f5a475361e8190b9eb Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Thu, 23 May 2019 18:17:56 +0100 Subject: [PATCH 5/6] Fixed error with dba --- bot.py | 2 +- dba.py | 39 +++++++++++++++++++++++++++++---------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/bot.py b/bot.py index 3ba88a3..f5b0fc4 100644 --- a/bot.py +++ b/bot.py @@ -74,7 +74,7 @@ def command_debug(bot, update): logger.info('Received command /debug') chat = update.message.chat conn = dba.get_connection() - all_chats = dba.get_rows(conn, 'SELECT * FROM chat') + all_chats = dba.load_all_chats() buff = [ "Chat:", f" - id: {chat.id}", diff --git a/dba.py b/dba.py index 6312563..2f854a6 100644 --- a/dba.py +++ b/dba.py @@ -1,4 +1,5 @@ import os +import logging import sqlite3 from collections import OrderedDict @@ -7,6 +8,9 @@ import config +logger = logging.getLogger(__name__) + + class ObjectRow(sqlite3.Row): def __getattr__(self, name): @@ -62,32 +66,41 @@ def get_rows(conn, sql, *args, cast=None, limit=0): cur.close() +def _exists_db(db_name): + return any([ + db_name == ':memory:', + not os.path.exists(db_name), + ]) + + CREATE_TABLE_CHAT = ''' CREATE TABLE IF NOT EXISTS chat ( id_chat long, - title text, - chat_type text - ) + chat_type text, + title text + ); ''' -def initialize_db(conn): + +def _initialize_db(conn): + logger.info('No database (first time running)') execute(conn, CREATE_TABLE_CHAT) + logger.info('Database initialized') + _conn = None -def get_connection(): +def get_connection( + ): global _conn if _conn is None: db_name = config.DB_NAME - first_time = any([ - db_name == ':memory:', - not os.path.exists(db_name), - ]) + first_time = _exists_db(db_name) _conn = sqlite3.connect(db_name) _conn.row_factory = ObjectRow if first_time: - initialize_db(_conn) + _initialize_db(_conn) return _conn @@ -99,6 +112,7 @@ def execute(db, statement, *args): cur.close() +# Highlevel functions (other module?) def save_chat(id_chat, chat_type, title): conn = get_connection() @@ -108,3 +122,8 @@ def save_chat(id_chat, chat_type, title): execute(conn, sql, id_chat, chat_type, title) return 1 + +def load_all_chats(): + conn = get_connection() + return get_rows(conn, 'SELECT * FROM chat') + From 906a1c10bdc5f4b60576a0a3ef39ba42b7d32f8f Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rodriguez de Leon Date: Mon, 10 Jun 2019 18:47:22 +0100 Subject: [PATCH 6/6] Modulo dba como paquete --- bot.py | 14 ++++-- config.py | 7 ++- dba.py | 129 ------------------------------------------------ dba/__init__.py | 15 ++++++ dba/chat.py | 20 ++++++++ dba/core.py | 100 +++++++++++++++++++++++++++++++++++++ 6 files changed, 151 insertions(+), 134 deletions(-) delete mode 100644 dba.py create mode 100644 dba/__init__.py create mode 100644 dba/chat.py create mode 100644 dba/core.py diff --git a/bot.py b/bot.py index f5b0fc4..b17ccf8 100644 --- a/bot.py +++ b/bot.py @@ -7,15 +7,21 @@ import config import utils -import dba + +from dba import connect +from dba import save_chat +from dba import load_all_chats + logger = logging.getLogger('bot') + def command_start(bot, update): logger.info('Received command /start') chat = update.message.chat - is_new = dba.save_chat(chat.id, chat.type, chat.title) + db = connect(config.DB_NAME) + is_new = save_chat(db, chat.id, chat.type, chat.title) if is_new: bot.send_message( chat_id=update.message.chat_id, @@ -73,8 +79,8 @@ def command_settings(bot, update): def command_debug(bot, update): logger.info('Received command /debug') chat = update.message.chat - conn = dba.get_connection() - all_chats = dba.load_all_chats() + db = connect(config.DB_NAME) + all_chats = load_all_chats(db) buff = [ "Chat:", f" - id: {chat.id}", diff --git a/config.py b/config.py index 073b5f1..f9e8ded 100644 --- a/config.py +++ b/config.py @@ -47,7 +47,12 @@ def log(logger_method): POLL_INTERVAL = config('POLL_INTERVAL', int, default=3) # Bot message for start command -BOT_GREETING = config('BOT_GREETING', default="Hi! I'm a friendly, slightly psychopath robot") +BOT_GREETING = config('BOT_GREETING', default=( + "### Hi! I'm a friendly, slightly psychopath robot.\n" + "Now I can remember this chat. This means you " + "can configure some settings and can use some " + "new features like GitHub integration." + )) # A username longer than this will be considered non-human # - Allowed values: An integer larger than 1 diff --git a/dba.py b/dba.py deleted file mode 100644 index 2f854a6..0000000 --- a/dba.py +++ /dev/null @@ -1,129 +0,0 @@ -import os -import logging - -import sqlite3 -from collections import OrderedDict -from collections.abc import MutableMapping - -import config - - -logger = logging.getLogger(__name__) - - -class ObjectRow(sqlite3.Row): - - def __getattr__(self, name): - if name in self.keys(): - return self[name] - else: - raise AttributeError(name) - - -def get_value(conn, sql, *args, cast=None): - cur = conn.cursor() - try: - cur.execute(str(sql), tuple(args)) - row = cur.fetchone() - if row: - result = row[0] - if cast is not None: - result = cast(result) - return result - else: - return default - finally: - cur.close() - - -def get_row(conn, sql, *args, cast=None): - cur = conn.cursor() - try: - cur.execute(str(sql), tuple(args)) - row = cur.fetchone() - if not row: - return {} - if cast is not None: - row = cast(row) - return row - finally: - cur.close() - - -def get_rows(conn, sql, *args, cast=None, limit=0): - cur = conn.cursor() - rows = [] - try: - cur.execute(str(sql), tuple(args)) - if limit > 0: - rows = cur.fetchmany(limit) - else: - rows = cur.fetchall() - if cast is not None: - rows = [cast(row) for row in rows] - return list(rows) - finally: - cur.close() - - -def _exists_db(db_name): - return any([ - db_name == ':memory:', - not os.path.exists(db_name), - ]) - - -CREATE_TABLE_CHAT = ''' - CREATE TABLE IF NOT EXISTS chat ( - id_chat long, - chat_type text, - title text - ); -''' - - -def _initialize_db(conn): - logger.info('No database (first time running)') - execute(conn, CREATE_TABLE_CHAT) - logger.info('Database initialized') - - -_conn = None - - -def get_connection( - ): - global _conn - if _conn is None: - db_name = config.DB_NAME - first_time = _exists_db(db_name) - _conn = sqlite3.connect(db_name) - _conn.row_factory = ObjectRow - if first_time: - _initialize_db(_conn) - return _conn - - -def execute(db, statement, *args): - cur = db.cursor() - try: - cur.execute(statement, args) - finally: - cur.close() - - -# Highlevel functions (other module?) - -def save_chat(id_chat, chat_type, title): - conn = get_connection() - exists = get_row(conn, 'SELECT * FROM chat where id_chat=?', id_chat) - if not exists: - sql = 'INSERT INTO chat (id_chat, chat_type, title) VALUES (?, ?, ?)' - execute(conn, sql, id_chat, chat_type, title) - return 1 - - -def load_all_chats(): - conn = get_connection() - return get_rows(conn, 'SELECT * FROM chat') - diff --git a/dba/__init__.py b/dba/__init__.py new file mode 100644 index 0000000..6dca60e --- /dev/null +++ b/dba/__init__.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from dba.chat import save_chat +from dba.chat import load_all_chats +from dba.core import DBA + +def connect(name): + if connect.singleton is None: + connect.singleton = DBA(name) + return connect.singleton + + +connect.singleton = None + diff --git a/dba/chat.py b/dba/chat.py new file mode 100644 index 0000000..719d16c --- /dev/null +++ b/dba/chat.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + + +from .core import DBA + + +# Highlevel functions + + + +def save_chat(db, id_chat, chat_type, title): + exists = db.get_row('SELECT * FROM chat where id_chat=?', id_chat) + if not exists: + sql = 'INSERT INTO chat (id_chat, chat_type, title) VALUES (?, ?, ?)' + db.execute(sql, (id_chat, chat_type, title)) + return 1 + + +def load_all_chats(db): + return db.get_rows('SELECT * FROM chat') diff --git a/dba/core.py b/dba/core.py new file mode 100644 index 0000000..2b9b16d --- /dev/null +++ b/dba/core.py @@ -0,0 +1,100 @@ +import os +import logging + +import sqlite3 + + +logger = logging.getLogger(__name__) + + +def _exists_db(db_name): + return any([ + db_name == ':memory:', + not os.path.exists(db_name), + ]) + + +CREATE_TABLE_CHAT = ''' + CREATE TABLE IF NOT EXISTS chat ( + id_chat long, + chat_type text, + title text + ); +''' + + +class ObjectRow(sqlite3.Row): + + def __getattr__(self, name): + if name in self.keys(): + return self[name] + else: + raise AttributeError(name) + + +class DBA: + + def __init__(self, db_name): + is_first_time = _exists_db(db_name) + self.conn = sqlite3.connect(db_name) + self.conn.row_factory = ObjectRow + if is_first_time: + self.initialize_db() + + def initialize_db(self): + logger.info('No database (first time running)') + self.conn.execute(CREATE_TABLE_CHAT) + logger.info('Database initialized') + + def execute(self, db, statement, *args): + cur = self.conn.cursor() + result = None + try: + result = cur.execute(statement, args) + finally: + cur.close() + return result + + def get_value(self, sql, *args, cast=None, default=None): + cur = self.conn.cursor() + result = default + try: + cur.execute(str(sql), tuple(args)) + row = cur.fetchone() + if row: + result = row[0] + if cast is not None: + result = cast(result) + finally: + cur.close() + return default + + def get_row(self, sql, *args, cast=None): + cur = self.conn.cursor() + row = {} + try: + cur.execute(str(sql), tuple(args)) + row = cur.fetchone() + if row and cast is not None: + row = cast(row) + finally: + cur.close() + return row + + def get_rows(self, sql, *args, cast=None, limit=0): + cur = self.conn.cursor() + rows = [] + try: + cur.execute(str(sql), tuple(args)) + if limit > 0: + rows = list(cur.fetchmany(limit)) + else: + rows = list(cur.fetchall()) + if cast is not None: + rows = [cast(row) for row in rows] + finally: + cur.close() + return rows + + +