From a8014e363c1cdadf220648e1ac77d6e08b554b65 Mon Sep 17 00:00:00 2001 From: Wojciech Kwolek Date: Sun, 9 Jan 2022 05:55:10 +0100 Subject: [PATCH] send unread links with buttons to mark as read or delete --- main.py | 27 ++++++++++++++++++--------- models.py | 50 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/main.py b/main.py index 4392516..0c8fdeb 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,4 @@ +import json from functools import wraps import logging import os @@ -8,7 +9,7 @@ from datetime import datetime from pprint import pprint from dotenv import load_dotenv -from telegram import Update, user +from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ParseMode import telegram from telegram.ext import (CallbackContext, CommandHandler, MessageHandler, Updater) @@ -25,12 +26,13 @@ link_regex = re.compile( def private_only(f): @wraps(f) - def cb(update: Update, *args, **kwargs): + def cb(self, update: Update, *args, **kwargs): if update.effective_chat.type != 'private': return - return f(update, *args, **kwargs) + return f(self, update, *args, **kwargs) return cb + @dataclass class BotSettings: token: str @@ -51,7 +53,7 @@ class Bot: for f in dir(self): if f.startswith("cmd_"): self.dispatcher.add_handler( - CommandHandler(f[4:], private_only(getattr(self, f)))) + CommandHandler(f[4:], getattr(self, f))) def _register_handlers(self): self.dispatcher.add_handler(MessageHandler( @@ -75,11 +77,13 @@ class Bot: ); """) + @private_only def cmd_start(self, update: Update, context: CallbackContext): context.bot.send_message(chat_id=update.effective_chat.id, text='Hi!') # TODO: timezones # TODO: ask to set time or something + @private_only def cmd_test(self, update: Update, context: CallbackContext): l = Link( link="2137", @@ -88,6 +92,7 @@ class Bot: with self.db as db: l.create(db) + @private_only def cmd_unread(self, update: Update, context: CallbackContext): # TODO: ignore messages from group user_id = update.effective_user.id @@ -98,10 +103,16 @@ class Bot: unread = Link.get_unread(db, user_id) bot.send_message( - user_id, f"**Your unread links as of {datetime.now().isoformat()}:", parse_mode="MARKDOWN") + user_id, f"*Your unread links as of {datetime.now().isoformat()}:*", parse_mode=ParseMode.MARKDOWN) for link in unread: - bot.send_message(user_id, link.link) - # TODO: button to read or postpone + keyboard = [ + [InlineKeyboardButton(text='Mark as read', + callback_data=f"mark_as_read:{link.id}"), + InlineKeyboardButton(text='Delete', callback_data=f"delete:{link.id}")] + ] + bot.send_message(user_id, link.link, + reply_markup=InlineKeyboardMarkup(keyboard)) + # TODO: button to postpone # TODO: button to mark all as read or postpone def _natural_count(self, n, singular, plural): @@ -111,8 +122,6 @@ class Bot: @private_only def message(self, update: Update, context: CallbackContext): - if update.effective_chat.type != 'private': - return user_id = update.effective_user.id links = re.findall(link_regex, update.message.text) with self.db as db: diff --git a/models.py b/models.py index af0737d..80fca93 100644 --- a/models.py +++ b/models.py @@ -1,7 +1,7 @@ import sqlite3 from dataclasses import dataclass from datetime import datetime -from typing import Tuple +from typing import Dict, List, Tuple @dataclass @@ -12,6 +12,39 @@ class Link: read_at: datetime = None added_at: datetime = None + def as_dict(self): + def timestamp(t): return t.timestamp() if t is not None else None + return { + "id": self.id, + "user_id": self.user_id, + "read_at": timestamp(self.read_at), + "added_at": timestamp(self.added_at), + "link": self.link, + } + + @classmethod + def from_dict(cls, dict: Dict) -> 'Link': + def convert_date(t): return datetime.fromtimestamp( + t) if t is not None else None + return Link( + id=dict.get('id', None), + user_id=dict["user_id"], + read_at=convert_date(dict.get("read_at", None)), + added_at=convert_date(dict.get("added_at", None)), + link=dict["link"] + ) + + @classmethod + def _from_tuple(cls, tuple) -> 'Link': + id, link, user_id, read_at, added_at = tuple + return cls( + id=id, + link=link, + user_id=user_id, + read_at=read_at, + added_at=added_at + ) + def create(self, db: sqlite3.Connection): self.added_at = datetime.now() cur = db.cursor() @@ -26,22 +59,11 @@ class Link: self.id = r[0][0] @classmethod - def _from_tuple(cls, tuple) -> 'Link': - id, link, user_id, read_at, added_at = tuple - return cls( - id=id, - link=link, - user_id=user_id, - read_at=read_at, - added_at=added_at - ) - - @classmethod - def _get(cls, db: sqlite3.Connection, where: str, values: Tuple = ()) -> 'Link': + def _get(cls, db: sqlite3.Connection, where: str, values: Tuple = ()) -> List['Link']: cur = db.cursor() rows = cur.execute(f"SELECT * FROM links WHERE {where}", values) return map(cls._from_tuple, rows) @classmethod - def get_unread(cls, db: sqlite3.Connection, user_id: str): + def get_unread(cls, db: sqlite3.Connection, user_id: str) -> List['Link']: return cls._get(db, "user_id = ? AND read_at IS NULL", (user_id,))