From 6e7c57d745645b127c7f0fb68d2061eddd52beab Mon Sep 17 00:00:00 2001 From: Schimon Jehudah Date: Wed, 29 Nov 2023 15:32:35 +0000 Subject: [PATCH] Add option to ignore old news items --- slixfeed/confighandler.py | 2 + slixfeed/datahandler.py | 22 +++---- slixfeed/sqlitehandler.py | 61 +++++++++++++++++-- slixfeed/xmpphandler.py | 121 ++++++++++++++++++++++++++++++-------- 4 files changed, 167 insertions(+), 39 deletions(-) diff --git a/slixfeed/confighandler.py b/slixfeed/confighandler.py index 6727277..5820be7 100644 --- a/slixfeed/confighandler.py +++ b/slixfeed/confighandler.py @@ -47,6 +47,8 @@ async def get_value_default(key): result = randrange(100000, 999999) case "token": result = "none" + case "old": + result = 0 return result diff --git a/slixfeed/datahandler.py b/slixfeed/datahandler.py index b6b98b2..dbc77e5 100644 --- a/slixfeed/datahandler.py +++ b/slixfeed/datahandler.py @@ -132,7 +132,12 @@ async def download_updates(db_file, url=None): date = entry.updated date = await datetimehandler.rfc2822_to_iso8601(date) else: - date = None + # TODO Just set date = "*** No date ***" + # date = await datetime.now().isoformat() + date = await datetimehandler.now() + # NOTE Would seconds result in better database performance + # date = datetime.datetime(date) + # date = (date-datetime.datetime(1970,1,1)).total_seconds() exist = await sqlitehandler.check_entry_exist( db_file, source, @@ -143,22 +148,19 @@ async def download_updates(db_file, url=None): ) if not exist: # new_entry = new_entry + 1 - if not date: - # TODO Just set date = "*** No date ***" - # date = await datetime.now().isoformat() - date = await datetimehandler.now() - # NOTE Would seconds result in better database performance - # date = datetime.datetime(date) - # date = (date-datetime.datetime(1970,1,1)).total_seconds() # TODO Enhance summary if entry.has_key("summary"): summary = entry.summary # Remove HTML tags summary = BeautifulSoup(summary, "lxml").text # TODO Limit text length - summary = summary.replace("\n\n", "\n")[:300] + " ‍⃨" + summary = summary.replace("\n\n\n", "\n\n") + summary = summary[:300] + " ‍⃨" + summary = summary.strip().split('\n') + summary = ["> " + line for line in summary] + summary = "\n".join(summary) else: - summary = "*** No summary ***" + summary = "> *** No summary ***" read_status = 0 pathname = urlsplit(link).path string = ( diff --git a/slixfeed/sqlitehandler.py b/slixfeed/sqlitehandler.py index 2775f06..0c0455a 100644 --- a/slixfeed/sqlitehandler.py +++ b/slixfeed/sqlitehandler.py @@ -478,23 +478,26 @@ async def get_entry_unread(db_file, num=None): str(summary), str(link) ) + # TODO While `async with DBLOCK` does work well from + # outside of functions, it would be better practice + # to place it within the functions. async with DBLOCK: # NOTE: We can use DBLOCK once for both # functions, because, due to exclusive # ID, only one can ever occur. - await mark_as_read(cur, ix) + await mark_entry_as_read(cur, ix) await delete_entry(cur, ix) return news_list -async def mark_as_read(cur, ix): +async def mark_entry_as_read(cur, ix): """ - Set read status of entry. + Set read status of entry as read. Parameters ---------- - db_file : str - Path to database file. + cur : object + Cursor object. ix : str Index of entry. """ @@ -506,6 +509,51 @@ async def mark_as_read(cur, ix): cur.execute(sql, (ix,)) +async def mark_source_as_read(db_file, source): + """ + Set read status of entries of given source as read. + + Parameters + ---------- + db_file : str + Path to database file. + source : str + URL. + """ + async with DBLOCK: + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + "UPDATE entries " + "SET summary = '', read = 1 " + "WHERE source = ?" + ) + cur.execute(sql, (source,)) + + +async def mark_all_as_read(db_file): + """ + Set read status of all entries as read. + + Parameters + ---------- + db_file : str + Path to database file. + """ + async with DBLOCK: + with create_connection(db_file) as conn: + cur = conn.cursor() + sql = ( + "UPDATE entries " + "SET summary = '', read = 1 " + ) + cur.execute(sql) + sql = ( + "DELETE FROM archive" + ) + cur.execute(sql) + + async def delete_entry(cur, ix): """ Delete entry from table archive. @@ -715,6 +763,9 @@ async def add_entry_and_set_date(db_file, source, entry): entry : list Entry properties. """ + # TODO While `async with DBLOCK` does work well from + # outside of functions, it would be better practice + # to place it within the functions. async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() diff --git a/slixfeed/xmpphandler.py b/slixfeed/xmpphandler.py index 3ad52fe..c9722ef 100644 --- a/slixfeed/xmpphandler.py +++ b/slixfeed/xmpphandler.py @@ -274,6 +274,13 @@ class Slixfeed(slixmpp.ClientXMPP): async def remove_and_leave_muc(self, muc_jid): + self.send_message( + mto=muc_jid, + mbody=( + "If you need me again, contact me directly at {}\n" + "Goodbye!" + ).format(self.boundjid.bare) + ) result = await self.plugin['xep_0048'].get_bookmarks() bookmarks = result["private"]["bookmarks"] conferences = bookmarks["conferences"] @@ -661,13 +668,28 @@ class Slixfeed(slixmpp.ClientXMPP): datahandler.add_feed_no_check, [url, title] ) - await taskhandler.refresh_task( - self, + old = await filehandler.initdb( jid, - taskhandler.send_status, - "status", - 20 + sqlitehandler.get_settings_value, + "old" ) + if old: + await taskhandler.clean_tasks_xmpp( + jid, + ["status"] + ) + # await taskhandler.send_status(jid) + await taskhandler.start_tasks_xmpp( + self, + jid, + ["status"] + ) + else: + await filehandler.initdb( + jid, + sqlitehandler.mark_source_as_read, + url + ) else: action = "Missing URL." case _ if message_lowercase.startswith("allow +"): @@ -789,16 +811,28 @@ class Slixfeed(slixmpp.ClientXMPP): # 20 # ) # NOTE This would show the number of new unread entries - await taskhandler.clean_tasks_xmpp( + old = await filehandler.initdb( jid, - ["status"] - ) - # await taskhandler.send_status(jid) - await taskhandler.start_tasks_xmpp( - self, - jid, - ["status"] + sqlitehandler.get_settings_value, + "old" ) + if old: + await taskhandler.clean_tasks_xmpp( + jid, + ["status"] + ) + # await taskhandler.send_status(jid) + await taskhandler.start_tasks_xmpp( + self, + jid, + ["status"] + ) + else: + await filehandler.initdb( + jid, + sqlitehandler.mark_source_as_read, + url + ) case _ if message_lowercase.startswith("feeds"): query = message[6:] if query: @@ -839,6 +873,8 @@ class Slixfeed(slixmpp.ClientXMPP): sqlitehandler.set_settings_value, [key, val] ) + # NOTE Perhaps this should be replaced + # by functions clean and start await taskhandler.refresh_task( self, jid, @@ -878,6 +914,15 @@ class Slixfeed(slixmpp.ClientXMPP): ).format(val) else: action = "Missing value." + case "new": + await filehandler.initdb( + jid, + sqlitehandler.set_settings_value, + ["old", 0] + ) + action = ( + "Only new items of added feeds will be sent." + ) case _ if message_lowercase.startswith("next"): num = message[5:] await taskhandler.clean_tasks_xmpp( @@ -904,6 +949,15 @@ class Slixfeed(slixmpp.ClientXMPP): # 20 # ) # await taskhandler.refresh_task(jid, key, val) + case "old": + await filehandler.initdb( + jid, + sqlitehandler.set_settings_value, + ["old", 1] + ) + action = ( + "All items of added feeds will be sent." + ) case _ if message_lowercase.startswith("quantum"): key = message[:7] val = message[8:] @@ -1089,20 +1143,31 @@ def print_info(): """ msg = ( "```\n" - "NAME\n" - "Slixfeed - News syndication bot for Jabber/XMPP\n" + "ABOUT\n" + " Slixfeed aims to be an easy to use and fully-featured news\n" + " aggregator bot for XMPP. It provides a convenient access to Blogs,\n" + " Fediverse and News websites along with filtering functionality." "\n" - "DESCRIPTION\n" - " Slixfeed is a news aggregator bot for online news feeds.\n" - " This program is primarily designed for XMPP.\n" - " For more information, visit https://xmpp.org/software/\n" + " Slixfeed is primarily designed for XMPP (aka Jabber).\n" + " Visit https://xmpp.org/software/ for more information.\n" "\n" - # "PROTOCOLS\n" - # " Supported prootcols are IRC, Matrix and XMPP.\n" + " XMPP is the Extensible Messaging and Presence Protocol, a set\n" + " of open technologies for instant messaging, presence, multi-party\n" + " chat, voice and video calls, collaboration, lightweight\n" + " middleware, content syndication, and generalized routing of XML\n" + " data." + " Visit https://xmpp.org/about/ for more information on the XMPP\n" + " protocol." + " " + # "PLATFORMS\n" + # " Supported prootcols are IRC, Matrix, Tox and XMPP.\n" # " For the best experience, we recommend you to use XMPP.\n" # "\n" "FILETYPES\n" - " Supported filetypes are Atom, RDF and RSS.\n" + " Supported filetypes: Atom, RDF, RSS and XML.\n" + "\n" + "PROTOCOLS\n" + " Supported protocols: Dat, FTP, Gemini, Gopher, HTTP and IPFS.\n" "\n" "AUTHORS\n" " Laura Harbinger, Schimon Zackary.\n" @@ -1118,11 +1183,15 @@ def print_info(): " Florent Le Coz (poezio, France)," "\n" " George Vlahavas (SalixOS, Greece)," + " Maxime Buquet (slixmpp, France)," + "\n" + " Mathieu Pasquet (slixmpp, France)," " Pierrick Le Brun (SalixOS, France)," "\n" + " Remko Tronçon (Swift, Germany)," " Thorsten Mühlfelder (SalixOS, Germany)," - " Yann Leboulanger (Gajim, France).\n" "\n" + " Yann Leboulanger (Gajim, France).\n" "COPYRIGHT\n" " Slixfeed is free software; you can redistribute it and/or\n" " modify it under the terms of the GNU General Public License\n" @@ -1135,7 +1204,7 @@ def print_info(): "\n" "NOTE\n" " You can run Slixfeed on your own computer, server, and\n" - " even on a Linux phone (i.e. Droidian, Mobian NixOS,\n" + " even on a Linux phone (i.e. Droidian, Kupfer, Mobian, NixOS,\n" " postmarketOS). You can also use Termux.\n" "\n" " All you need is one of the above and an XMPP account to\n" @@ -1183,6 +1252,10 @@ def print_help(): " Display most recent 20 titles of given URL.\n" " read URL N\n" " Display specified entry number from given URL.\n" + " new\n" + " Send only new items of added feeds.\n" + " old\n" + " Send all items of added feeds.\n" "\n" "MESSAGE OPTIONS\n" " start\n"