diff --git a/slixfeed/__main__.py b/slixfeed/__main__.py index 04a2808..51579fe 100644 --- a/slixfeed/__main__.py +++ b/slixfeed/__main__.py @@ -229,9 +229,19 @@ def main(): logging.basicConfig(level=args.loglevel, format='%(levelname)-8s %(message)s') + # # Setup logging. + # logging.basicConfig(level=args.loglevel, + # format='%(levelname)-8s %(message)s') + # # logging.basicConfig(format='[%(levelname)s] %(message)s') + # logger = logging.getLogger() + # logdbg = logger.debug + # logerr = logger.error + # lognfo = logger.info + # logwrn = logger.warning + # Try configuration file - values = config.get_value('accounts', 'XMPP Client', - ['alias', 'jid', 'password', 'hostname', 'port']) + key_list = ['alias', 'jid', 'password', 'hostname', 'port'] + values = config.get_value('accounts', 'XMPP Client', key_list) alias = values[0] jid = values[1] password = values[2] diff --git a/slixfeed/action.py b/slixfeed/action.py index e585bfa..0f2aca9 100644 --- a/slixfeed/action.py +++ b/slixfeed/action.py @@ -30,12 +30,11 @@ from bs4 import BeautifulSoup from feedparser import parse from http.client import IncompleteRead import json -import logging +from slixfeed.log import Logger from lxml import html import os import slixfeed.config as config import slixfeed.crawl as crawl - import slixfeed.dt as dt import slixfeed.fetch as fetch import slixfeed.sqlite as sqlite @@ -53,41 +52,43 @@ from slixfeed.xmpp.message import XmppMessage from slixfeed.xmpp.presence import XmppPresence from slixfeed.xmpp.upload import XmppUpload from slixfeed.xmpp.utility import get_chat_type +import sys import tomllib from urllib import error from urllib.parse import parse_qs, urlsplit import xml.etree.ElementTree as ET +logger = Logger(__name__) + try: import xml2epub except ImportError: - logging.info( - "Package xml2epub was not found.\n" - "ePUB support is disabled.") + logger.error('Package xml2epub was not found.\n' + 'ePUB support is disabled.') try: import html2text except ImportError: - logging.info( - "Package html2text was not found.\n" - "Markdown support is disabled.") + logger.error('Package html2text was not found.\n' + 'Markdown support is disabled.') try: import pdfkit except ImportError: - logging.info( - "Package pdfkit was not found.\n" - "PDF support is disabled.") + logger.error('Package pdfkit was not found.\n' + 'PDF support is disabled.') try: from readability import Document except ImportError: - logging.info( - "Package readability was not found.\n" - "Arc90 Lab algorithm is disabled.") + logger.error('Package readability was not found.\n' + 'Arc90 Lab algorithm is disabled.') async def export_feeds(self, jid, jid_file, ext): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid)) cache_dir = config.get_default_cache_directory() if not os.path.isdir(cache_dir): os.mkdir(cache_dir) @@ -117,7 +118,9 @@ async def xmpp_send_status(self, jid): jid : str Jabber ID. """ - logging.info('Sending a status message to JID {}'.format(jid)) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid)) status_text = '📜️ Slixfeed RSS News Bot' jid_file = jid.replace('/', '_') db_file = config.get_pathname_to_database(jid_file) @@ -169,6 +172,9 @@ async def xmpp_send_update(self, jid, num=None): num : str, optional Number. The default is None. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid)) jid_file = jid.replace('/', '_') db_file = config.get_pathname_to_database(jid_file) enabled = config.get_setting_value(db_file, 'enabled') @@ -267,6 +273,9 @@ async def xmpp_send_update(self, jid, num=None): def manual(filename, section=None, command=None): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, filename)) config_dir = config.get_default_config_directory() with open(config_dir + '/' + filename, mode="rb") as commands: cmds = tomllib.load(commands) @@ -279,7 +288,7 @@ def manual(filename, section=None, command=None): try: cmd_list = cmds[section][command] except KeyError as e: - logging.error(str(e)) + logger.error(str(e)) cmd_list = None elif section: try: @@ -287,7 +296,7 @@ def manual(filename, section=None, command=None): for cmd in cmds[section]: cmd_list.extend([cmd]) except KeyError as e: - logging.error('KeyError:' + str(e)) + logger.error('KeyError:' + str(e)) cmd_list = None else: cmd_list = [] @@ -316,6 +325,9 @@ def log_to_markdown(timestamp, filename, jid, message): None. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, filename)) with open(filename + '.md', 'a') as file: # entry = "{} {}:\n{}\n\n".format(timestamp, jid, message) entry = ( @@ -342,6 +354,8 @@ def is_feed_json(document): val : boolean True or False. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated'.format(function_name)) value = False try: feed = json.loads(document) @@ -376,6 +390,8 @@ def is_feed(feed): val : boolean True or False. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated'.format(function_name)) value = False # message = None if not feed.entries: @@ -410,6 +426,8 @@ def is_feed(feed): def list_unread_entries(result, feed_title, jid_file): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated'.format(function_name)) # TODO Add filtering # TODO Do this when entry is added to list and mark it as read # DONE! @@ -469,6 +487,9 @@ def list_unread_entries(result, feed_title, jid_file): def list_search_results(query, results): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for query {}.' + .format(function_name, query)) message = ("Search results for '{}':\n\n```" .format(query)) for result in results: @@ -482,6 +503,9 @@ def list_search_results(query, results): def list_feeds_by_query(db_file, query): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for query {}.' + .format(function_name, query)) results = sqlite.search_feeds(db_file, query) message = ('Feeds containing "{}":\n\n```' .format(query)) @@ -511,6 +535,9 @@ async def list_statistics(db_file): msg : str Statistics as message. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) entries_unread = await sqlite.get_number_of_entries_unread(db_file) entries = await sqlite.get_number_of_items(db_file, 'entries') archive = await sqlite.get_number_of_items(db_file, 'archive') @@ -554,6 +581,9 @@ async def list_statistics(db_file): # FIXME Replace counter by len def list_last_entries(results, num): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated.' + .format(function_name)) message = "Recent {} titles:\n\n```".format(num) for result in results: message += ("\n{}\n{}\n" @@ -566,6 +596,9 @@ def list_last_entries(results, num): def pick_a_feed(lang=None): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated.' + .format(function_name)) config_dir = config.get_default_config_directory() with open(config_dir + '/' + 'feeds.toml', mode="rb") as feeds: urls = tomllib.load(feeds) @@ -575,6 +608,9 @@ def pick_a_feed(lang=None): def list_feeds(results): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated.' + .format(function_name)) message = "\nList of subscriptions:\n\n```\n" for result in results: message += ("Name : {}\n" @@ -597,6 +633,9 @@ def list_feeds(results): async def list_bookmarks(self): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated.' + .format(function_name)) conferences = await XmppBookmark.get(self) message = '\nList of groupchats:\n\n```\n' for conference in conferences: @@ -610,6 +649,9 @@ async def list_bookmarks(self): def export_to_markdown(jid, filename, results): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid)) with open(filename, 'w') as file: file.write('# Subscriptions for {}\n'.format(jid)) file.write('## Set of feeds exported with Slixfeed\n') @@ -622,6 +664,9 @@ def export_to_markdown(jid, filename, results): # TODO Consider adding element jid as a pointer of import def export_to_opml(jid, filename, results): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid)) root = ET.Element("opml") root.set("version", "1.0") head = ET.SubElement(root, "head") @@ -645,6 +690,9 @@ def export_to_opml(jid, filename, results): async def import_opml(db_file, url): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) result = await fetch.http(url) if not result['error']: document = result['content'] @@ -667,6 +715,9 @@ async def import_opml(db_file, url): async def add_feed(db_file, url): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) while True: exist = await sqlite.get_feed_id_and_name(db_file, url) if not exist: @@ -832,6 +883,9 @@ async def scan_json(db_file, url): url : str, optional URL. The default is None. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) if isinstance(url, tuple): url = url[0] result = await fetch.http(url) if not result['error']: @@ -866,7 +920,7 @@ async def scan_json(db_file, url): IncompleteRead, error.URLError ) as e: - logging.error(e) + logger.error(e) return # new_entry = 0 for entry in entries: @@ -890,9 +944,10 @@ async def scan_json(db_file, url): entry_id = entry["id"] if "id" in entry.keys() else link feed_id = await sqlite.get_feed_id(db_file, url) feed_id = feed_id[0] - exist = await sqlite.check_entry_exist( - db_file, feed_id, entry_id=entry_id, - title=title, link=link, date=date) + exist = sqlite.check_entry_exist(db_file, feed_id, + entry_id=entry_id, + title=title, link=link, + date=date) if not exist: summary = entry["summary"] if "summary" in entry.keys() else '' if not summary: @@ -916,12 +971,12 @@ async def scan_json(db_file, url): string) if reject_list: read_status = 1 - logging.debug('Rejected : {}' - '\n' - 'Keyword : {}' - .format(link, reject_list)) + logger.debug('Rejected : {}' + '\n' + 'Keyword : {}' + .format(link, reject_list)) if isinstance(date, int): - logging.error('Variable "date" is int: {}'.format(date)) + logger.error('Variable "date" is int: {}'.format(date)) media_link = '' if "attachments" in entry.keys(): for e_link in entry["attachments"]: @@ -938,10 +993,10 @@ async def scan_json(db_file, url): media_link = trim_url(media_link) break except: - logging.info('KeyError: "url"\n' + logger.info('KeyError: "url"\n' 'Missing "url" attribute for {}' .format(url)) - logging.info('Continue scanning for next ' + logger.info('Continue scanning for next ' 'potential enclosure of {}' .format(link)) entry = { @@ -965,6 +1020,9 @@ async def scan_json(db_file, url): async def view_feed(url): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for URL {}.' + .format(function_name, url)) while True: result = await fetch.http(url) if not result['error']: @@ -1027,6 +1085,9 @@ async def view_feed(url): async def view_entry(url, num): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for URL {}.' + .format(function_name, url)) while True: result = await fetch.http(url) if not result['error']: @@ -1104,6 +1165,9 @@ async def scan(db_file, url): url : str, optional URL. The default is None. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and URL {}.' + .format(function_name, db_file, url)) if isinstance(url, tuple): url = url[0] result = await fetch.http(url) if not result['error']: @@ -1144,7 +1208,7 @@ async def scan(db_file, url): len(feed["entries"]), updated) # await update_feed_status except (IncompleteReadError, IncompleteRead, error.URLError) as e: - logging.error(e) + logger.error(e) return # new_entry = 0 for entry in entries: @@ -1168,10 +1232,10 @@ async def scan(db_file, url): entry_id = entry.id if entry.has_key("id") else link feed_id = await sqlite.get_feed_id(db_file, url) feed_id = feed_id[0] - exist = await sqlite.check_entry_exist(db_file, feed_id, - entry_id=entry_id, - title=title, link=link, - date=date) + exist = sqlite.check_entry_exist(db_file, feed_id, + entry_id=entry_id, + title=title, link=link, + date=date) if not exist: summary = entry.summary if entry.has_key("summary") else '' read_status = 0 @@ -1187,12 +1251,12 @@ async def scan(db_file, url): string) if reject_list: read_status = 1 - logging.debug('Rejected : {}' + logger.debug('Rejected : {}' '\n' 'Keyword : {}'.format(link, reject_list)) if isinstance(date, int): - logging.error('Variable "date" is int: {}' + logger.error('Variable "date" is int: {}' .format(date)) media_link = '' if entry.has_key("links"): @@ -1212,10 +1276,10 @@ async def scan(db_file, url): media_link = trim_url(media_link) break except: - logging.info('KeyError: "href"\n' + logger.info('KeyError: "href"\n' 'Missing "href" attribute for {}' .format(url)) - logging.info('Continue scanning for next ' + logger.info('Continue scanning for next ' 'potential enclosure of {}' .format(link)) entry = { @@ -1240,6 +1304,9 @@ async def scan(db_file, url): def get_document_title(data): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated.' + .format(function_name)) try: document = Document(data) title = document.short_title() @@ -1250,6 +1317,9 @@ def get_document_title(data): def get_document_content(data): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated.' + .format(function_name)) try: document = Document(data) content = document.summary() @@ -1260,6 +1330,9 @@ def get_document_content(data): def get_document_content_as_text(data): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated.' + .format(function_name)) try: document = Document(data) content = document.summary() @@ -1271,6 +1344,9 @@ def get_document_content_as_text(data): def generate_document(data, url, ext, filename, readability=False): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and URL {}.' + .format(function_name, filename, url)) error = None if readability: try: @@ -1278,15 +1354,15 @@ def generate_document(data, url, ext, filename, readability=False): content = document.summary() except: content = data - logging.warning('Check that package readability is installed.') + logger.warning('Check that package readability is installed.') else: content = data match ext: case "epub": error = generate_epub(content, filename) if error: - logging.error(error) - # logging.error( + logger.error(error) + # logger.error( # "Check that packages xml2epub is installed, " # "or try again.") case "html": @@ -1295,14 +1371,14 @@ def generate_document(data, url, ext, filename, readability=False): try: generate_markdown(content, filename) except: - logging.warning('Check that package html2text ' + logger.warning('Check that package html2text ' 'is installed, or try again.') error = 'Package html2text was not found.' case "pdf": error = generate_pdf(content, filename) if error: - logging.error(error) - # logging.warning( + logger.error(error) + # logger.warning( # "Check that packages pdfkit and wkhtmltopdf " # "are installed, or try again.") # error = ( @@ -1321,6 +1397,9 @@ def generate_document(data, url, ext, filename, readability=False): async def extract_image_from_feed(db_file, feed_id, url): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and URL {}.' + .format(function_name, db_file, url)) feed_url = sqlite.get_feed_url(db_file, feed_id) feed_url = feed_url[0] result = await fetch.http(feed_url) @@ -1336,11 +1415,14 @@ async def extract_image_from_feed(db_file, feed_id, url): image_url = link.href return image_url except: - logging.error(url) - logging.error('AttributeError: object has no attribute "link"') + logger.error(url) + logger.error('AttributeError: object has no attribute "link"') async def extract_image_from_html(url): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for URL {}.' + .format(function_name, url)) result = await fetch.http(url) if not result['error']: data = result['content'] @@ -1349,7 +1431,7 @@ async def extract_image_from_html(url): content = document.summary() except: content = data - logging.warning('Check that package readability is installed.') + logger.warning('Check that package readability is installed.') tree = html.fromstring(content) # TODO Exclude banners, class="share" links etc. images = tree.xpath( @@ -1370,6 +1452,9 @@ async def extract_image_from_html(url): def generate_epub(text, pathname): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, pathname)) ## create an empty eBook pathname_list = pathname.split("/") filename = pathname_list.pop() @@ -1397,11 +1482,17 @@ def generate_epub(text, pathname): def generate_html(text, filename): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, filename)) with open(filename, 'w') as file: file.write(text) def generate_markdown(text, filename): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, filename)) h2m = html2text.HTML2Text() # Convert HTML to Markdown markdown = h2m.handle(text) @@ -1410,6 +1501,9 @@ def generate_markdown(text, filename): def generate_pdf(text, filename): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, filename)) try: pdfkit.from_string(text, filename) except IOError as error: @@ -1419,17 +1513,26 @@ def generate_pdf(text, filename): def generate_txt(text, filename): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, filename)) text = remove_html_tags(text) with open(filename, 'w') as file: file.write(text) def remove_html_tags(data): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated.' + .format(function_name)) data = BeautifulSoup(data, "lxml").text data = data.replace("\n\n", "\n") return data # TODO Add support for eDonkey, Gnutella, Soulseek async def get_magnet(link): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for URL {}.' + .format(function_name, link)) parted_link = urlsplit(link) queries = parse_qs(parted_link.query) query_xt = queries["xt"][0] @@ -1437,12 +1540,11 @@ async def get_magnet(link): filename = queries["dn"][0] checksum = query_xt[len("urn:btih:"):] torrent = await fetch.magnet(link) - logging.debug('Attempting to retrieve {} ({})' - .format(filename, checksum)) + logger.debug('Attempting to retrieve {} ({})' + .format(filename, checksum)) if not torrent: - logging.debug( - "Attempting to retrieve {} from HTTP caching service".format( - filename)) + logger.debug('Attempting to retrieve {} from HTTP caching service' + .format(filename)) urls = [ 'https://watercache.libertycorp.org/get/{}/{}', 'https://itorrents.org/torrent/{}.torrent?title={}', @@ -1471,6 +1573,9 @@ async def remove_nonexistent_entries(db_file, url, feed): feed : list Parsed feed document. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and URL {}.' + .format(function_name, db_file, url)) feed_id = await sqlite.get_feed_id(db_file, url) feed_id = feed_id[0] items = await sqlite.get_entries_of_feed(db_file, feed_id) @@ -1577,6 +1682,9 @@ async def remove_nonexistent_entries_json(db_file, url, feed): feed : list Parsed feed document. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and URL {}.' + .format(function_name, db_file, url)) feed_id = await sqlite.get_feed_id(db_file, url) feed_id = feed_id[0] items = await sqlite.get_entries_of_feed(db_file, feed_id) diff --git a/slixfeed/log.py b/slixfeed/log.py new file mode 100644 index 0000000..6b3ffe2 --- /dev/null +++ b/slixfeed/log.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" + +To use this class, first, instantiate Logger with the name of your module +or class, then call the appropriate logging methods on that instance. + +logger = Logger(__name__) +logger.debug('This is a debug message') + +""" + +import logging + +class Logger: + + def __init__(self, name): + self.logger = logging.getLogger(name) + self.logger.setLevel(logging.DEBUG) + + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + ch.setFormatter(formatter) + + self.logger.addHandler(ch) + + def critical(self, message): + self.logger.critical(message) + + def debug(self, message): + self.logger.debug(message) + + def error(self, message): + self.logger.error(message) + + def info(self, message): + self.logger.info(message) + + def warning(self, message): + self.logger.warning(message) diff --git a/slixfeed/sqlite.py b/slixfeed/sqlite.py index 5c64b52..1cb3ecc 100644 --- a/slixfeed/sqlite.py +++ b/slixfeed/sqlite.py @@ -18,9 +18,10 @@ TODO """ from asyncio import Lock -import logging # from slixfeed.data import join_url +from slixfeed.log import Logger from sqlite3 import connect, Error, IntegrityError +import sys import time # from eliot import start_action, to_file @@ -31,10 +32,12 @@ import time # # with start_action(action_type="search_entries()", query=query): # # with start_action(action_type="check_entry()", link=link): +CURSORS = {} + # aiosqlite DBLOCK = Lock() -CURSORS = {} +logger = Logger(__name__) def create_connection(db_file): """ @@ -51,6 +54,9 @@ def create_connection(db_file): conn : object Connection object or None. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) conn = None try: conn = connect(db_file) @@ -70,6 +76,9 @@ def create_tables(db_file): db_file : str Path to database file. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) with create_connection(db_file) as conn: archive_table_sql = ( """ @@ -296,6 +305,9 @@ def get_cursor(db_file): CURSORS[db_file] : object Cursor. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) if db_file in CURSORS: return CURSORS[db_file] else: @@ -316,6 +328,9 @@ async def import_feeds(db_file, feeds): feeds : list Set of feeds (Title and URL). """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -337,8 +352,8 @@ async def import_feeds(db_file, feeds): try: cur.execute(sql, par) except IntegrityError as e: - logging.warning("Skipping: " + str(url)) - logging.error(e) + logger.warning("Skipping: " + str(url)) + logger.error(e) async def add_metadata(db_file): @@ -350,6 +365,9 @@ async def add_metadata(db_file): db_file : str Path to database file. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -376,6 +394,9 @@ def insert_feed_status(cur, feed_id): cur : object Cursor object. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for feed_id {}.' + .format(function_name, feed_id)) sql = ( """ INSERT @@ -389,9 +410,9 @@ def insert_feed_status(cur, feed_id): try: cur.execute(sql, par) except IntegrityError as e: - logging.warning( + logger.warning( "Skipping feed_id {} for table feeds_state".format(feed_id)) - logging.error(e) + logger.error(e) def insert_feed_properties(cur, feed_id): @@ -403,6 +424,9 @@ def insert_feed_properties(cur, feed_id): cur : object Cursor object. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for feed_id {}.' + .format(function_name, feed_id)) sql = ( """ INSERT @@ -416,14 +440,14 @@ def insert_feed_properties(cur, feed_id): try: cur.execute(sql, par) except IntegrityError as e: - logging.warning( + logger.warning( "Skipping feed_id {} for table feeds_properties".format(feed_id)) - logging.error(e) + logger.error(e) -async def insert_feed( - db_file, url, title=None, entries=None, version=None, - encoding=None, language=None, status_code=None, updated=None): +async def insert_feed(db_file, url, title=None, entries=None, version=None, + encoding=None, language=None, status_code=None, + updated=None): """ Insert a new feed into the feeds table. @@ -448,6 +472,9 @@ async def insert_feed( updated : ???, optional Date feed was last updated. The default is None. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and URL {}.' + .format(function_name, db_file, url)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -501,9 +528,9 @@ async def insert_feed( cur.execute(sql, par) -async def insert_feed_( - db_file, url, title=None, entries=None, version=None, - encoding=None, language=None, status_code=None, updated=None): +async def insert_feed_(db_file, url, title=None, entries=None, version=None, + encoding=None, language=None, status_code=None, + updated=None): """ Insert a new feed into the feeds table. @@ -532,6 +559,9 @@ async def insert_feed_( updated : ???, optional Date feed was last updated. The default is None. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and URL {}.' + .format(function_name, db_file, url)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -575,6 +605,9 @@ async def remove_feed_by_url(db_file, url): url : str URL of feed. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and URL {}.' + .format(function_name, db_file, url)) with create_connection(db_file) as conn: async with DBLOCK: cur = conn.cursor() @@ -600,6 +633,9 @@ async def remove_feed_by_index(db_file, ix): ix : str Index of feed. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Index {}.' + .format(function_name, db_file, ix)) with create_connection(db_file) as conn: async with DBLOCK: cur = conn.cursor() @@ -645,6 +681,9 @@ def get_feeds_by_tag_id(db_file, tag_id): result : tuple List of tags. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Tag ID {}.' + .format(function_name, db_file, tag_id)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -677,6 +716,9 @@ def get_tags_by_feed_id(db_file, feed_id): result : tuple List of tags. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Feed ID {}.' + .format(function_name, db_file, feed_id)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -706,6 +748,9 @@ async def set_feed_id_and_tag_id(db_file, feed_id, tag_id): tag_id : str Tag ID """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}, Feed ID {} and Tag ID {}.' + .format(function_name, db_file, feed_id, tag_id)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -741,6 +786,9 @@ def get_tag_id(db_file, tag_name): ix : str Tag ID. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Tag {}.' + .format(function_name, db_file, tag_name)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -771,6 +819,9 @@ def get_tag_name(db_file, ix): tag_name : str Tag name. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Index {}.' + .format(function_name, db_file, ix)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -801,6 +852,9 @@ def is_tag_id_associated(db_file, tag_id): tag_id : str Tag ID. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Tag ID {}.' + .format(function_name, db_file, tag_id)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -818,6 +872,9 @@ def is_tag_id_associated(db_file, tag_id): async def delete_tag_by_index(db_file, ix): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Index {}.' + .format(function_name, db_file, ix)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -852,6 +909,9 @@ def is_tag_id_of_feed_id(db_file, tag_id, feed_id): tag_id : str Tag ID. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}, Feed ID {} and Tag ID {}.' + .format(function_name, db_file, feed_id, tag_id)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -870,6 +930,9 @@ def is_tag_id_of_feed_id(db_file, tag_id, feed_id): async def delete_feed_id_tag_id(db_file, feed_id, tag_id): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}, Feed ID {} and Tag ID {}.' + .format(function_name, db_file, feed_id, tag_id)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -898,6 +961,9 @@ async def set_new_tag(db_file, tag): tag : str Tag """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Tag {}.' + .format(function_name, db_file, tag)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -934,6 +1000,9 @@ async def get_feed_id_and_name(db_file, url): result : tuple List of ID and Name of feed. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and URL {}.' + .format(function_name, db_file, url)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -964,6 +1033,9 @@ async def get_number_of_items(db_file, table): count : ? Number of rows. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Table {}.' + .format(function_name, db_file, table)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -990,6 +1062,9 @@ async def get_number_of_feeds_active(db_file): count : str Number of rows. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -1017,6 +1092,9 @@ async def get_number_of_entries_unread(db_file): count : ? Number of rows. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -1053,6 +1131,9 @@ async def get_unread_entries(db_file, num): result : tuple News items. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Num {}.' + .format(function_name, db_file, num)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -1088,6 +1169,9 @@ def get_feed_id_by_entry_index(db_file, ix): feed_id : str Feed index. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Index {}.' + .format(function_name, db_file, ix)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -1118,6 +1202,9 @@ async def get_feed_id(db_file, url): feed_id : str Feed index. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and URL {}.' + .format(function_name, db_file, url)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -1143,6 +1230,9 @@ async def mark_entry_as_read(cur, ix): ix : str Index of entry. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Index {}.' + .format(function_name, db_file, ix)) sql = ( """ UPDATE entries @@ -1165,6 +1255,9 @@ def get_number_of_unread_entries_by_feed(db_file, feed_id): feed_id : str Feed Id. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Feed ID {}.' + .format(function_name, db_file, feed_id)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -1190,6 +1283,9 @@ async def mark_feed_as_read(db_file, feed_id): feed_id : str Feed Id. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Feed ID {}.' + .format(function_name, db_file, feed_id)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -1215,6 +1311,9 @@ async def delete_entry_by_id(db_file, ix): ix : str Index. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Index {}.' + .format(function_name, db_file, ix)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -1240,6 +1339,9 @@ async def archive_entry(db_file, ix): ix : str Index. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Index {}.' + .format(function_name, db_file, ix)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -1255,11 +1357,9 @@ async def archive_entry(db_file, ix): par = (ix,) try: cur.execute(sql, par) - except: - print( - "ERROR DB insert from entries " - "into archive at index", ix - ) + except Exception as e: + print('ERROR DB insert from entries into archive at index {} ' + 'for {}. Reason: {}'.format(ix, db_file, e)) sql = ( """ DELETE @@ -1278,6 +1378,9 @@ async def archive_entry(db_file, ix): def get_feed_title(db_file, ix): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Index {}.' + .format(function_name, db_file, ix)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -1305,6 +1408,9 @@ async def set_feed_title(db_file, feed_id, name): name : str New name. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}, Feed ID {} and Name {}.' + .format(function_name, db_file, feed_id, name)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -1323,6 +1429,9 @@ async def set_feed_title(db_file, feed_id, name): def get_entry_title(db_file, ix): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Index {}.' + .format(function_name, db_file, ix)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( # TODO Handletable archive too @@ -1338,6 +1447,9 @@ def get_entry_title(db_file, ix): def get_entry_url(db_file, ix): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Index {}.' + .format(function_name, db_file, ix)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( # TODO Handletable archive too @@ -1353,6 +1465,9 @@ def get_entry_url(db_file, ix): def get_feed_url(db_file, ix): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Index {}.' + .format(function_name, db_file, ix)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -1368,6 +1483,9 @@ def get_feed_url(db_file, ix): async def mark_as_read(db_file, ix): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Index {}.' + .format(function_name, db_file, ix)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -1390,6 +1508,9 @@ async def mark_all_as_read(db_file): db_file : str Path to database file. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -1420,6 +1541,9 @@ async def delete_archived_entry(cur, ix): ix : str Index of entry. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for Index {}.' + .format(function_name, ix)) sql = ( """ DELETE @@ -1440,6 +1564,9 @@ async def update_statistics(cur): cur : object Cursor object. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated.' + .format(function_name)) stat_dict = {} stat_dict["feeds"] = await get_number_of_items(cur, 'feeds') stat_dict["entries"] = await get_number_of_items(cur, 'entries') @@ -1491,6 +1618,9 @@ async def set_enabled_status(db_file, feed_id, status): status : int 0 or 1. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}, Feed ID {} and Status {}.' + .format(function_name, db_file, feed_id, status)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -1519,8 +1649,8 @@ When time functions of slixfeed.timedate were async, there were errors of coroutines """ -async def add_entry( - db_file, title, link, entry_id, feed_id, date, read_status): +async def add_entry(db_file, title, link, entry_id, feed_id, date, + read_status): """ Add a new entry row into the entries table. @@ -1541,6 +1671,9 @@ async def add_entry( read_status : str 0 or 1. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Feed ID {}.' + .format(function_name, db_file, feed_id)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -1590,6 +1723,9 @@ async def add_entries_and_update_timestamp(db_file, feed_id, new_entries): new_entries : tuple Set of entries as dict. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Feed ID {}.' + .format(function_name, db_file, feed_id)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -1639,6 +1775,9 @@ async def set_date(db_file, feed_id): feed_id : str Feed Id. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Feed ID {}.' + .format(function_name, db_file, feed_id)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -1670,6 +1809,9 @@ async def update_feed_status(db_file, feed_id, status_code): status : str Status ID or message. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}, Feed ID {} and Status Code {}.' + .format(function_name, db_file, feed_id, status_code)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -1701,6 +1843,9 @@ async def update_feed_validity(db_file, feed_id, valid): valid : boolean 0 or 1. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}, Feed ID {} and Validity {}.' + .format(function_name, db_file, feed_id, valid)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -1733,6 +1878,9 @@ async def update_feed_properties(db_file, feed_id, entries, updated): updated : ??? Date feed was last updated. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Feed ID {}.' + .format(function_name, db_file, feed_id)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -1761,6 +1909,9 @@ async def maintain_archive(db_file, limit): limit : str Number of maximum entries to store. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Limit {}.' + .format(function_name, db_file, limit)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -1815,6 +1966,9 @@ async def get_entries_of_feed(db_file, feed_id): feed_id : str Feed Id. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Feed ID {}.' + .format(function_name, db_file, feed_id)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -1869,6 +2023,9 @@ async def get_feeds_url(db_file): result : tuple URLs of active feeds. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -1897,6 +2054,9 @@ def get_feeds_by_enabled_state(db_file, enabled_state): result : tuple List of URLs. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and State {}.' + .format(function_name, db_file, enabled_state)) if enabled_state: enabled_state = 1 else: @@ -1930,6 +2090,9 @@ async def get_active_feeds_url(db_file): result : tuple URLs of active feeds. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -1958,6 +2121,9 @@ def get_tags(db_file): result : tuple List of tags. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -1984,6 +2150,9 @@ async def get_feeds(db_file): result : tuple URLs of feeds. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) # TODO # 1) Select id from table feeds # Select name, url (feeds) updated, enabled, feed_id (status) @@ -2017,6 +2186,9 @@ async def last_entries(db_file, num): titles_list : tuple List of recent N entries as message. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Num {}.' + .format(function_name, db_file, num)) with create_connection(db_file) as conn: cur = conn.cursor() # sql = ( @@ -2059,6 +2231,9 @@ def search_feeds(db_file, query): result : tuple Feeds of specified keywords as message. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Query {}.' + .format(function_name, db_file, query)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -2091,6 +2266,9 @@ async def search_entries(db_file, query): titles_list : tuple Entries of specified keywords as message. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Query {}.' + .format(function_name, db_file, query)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -2132,8 +2310,8 @@ ERROR DATE: result = https://blog.heckel.io/feed/ 19:32:06 ERROR DATE: result = https://mwl.io/feed """ -async def check_entry_exist( - db_file, feed_id, entry_id=None, title=None, link=None, date=None): +def check_entry_exist(db_file, feed_id, entry_id=None, title=None, link=None, + date=None): """ Check whether an entry exists. If entry has an ID, check by ID. @@ -2160,6 +2338,9 @@ async def check_entry_exist( bool True or None. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Feed ID {}.' + .format(function_name, db_file, feed_id)) with create_connection(db_file) as conn: cur = conn.cursor() exist = False @@ -2194,8 +2375,8 @@ async def check_entry_exist( result = cur.execute(sql, par).fetchone() if result: exist = True except: - logging.error("source =", feed_id) - logging.error("date =", date) + logger.error("source =", feed_id) + logger.error("date =", date) else: sql = ( """ @@ -2237,6 +2418,10 @@ async def set_setting_value(db_file, key_value): key = key_value[0] value = key_value[1] + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}, Key {} and Value {}.' + .format(function_name, db_file, key, value)) + if not value: match key: case 'interval': @@ -2288,6 +2473,11 @@ async def update_setting_value(db_file, key_value): # val = 0 key = key_value[0] value = key_value[1] + + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}, Key {} and Value {}.' + .format(function_name, db_file, key, value)) + async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -2310,6 +2500,9 @@ async def update_setting_value(db_file, key_value): async def delete_filter(db_file, key): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Key {}.' + .format(function_name, db_file, key)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -2325,6 +2518,9 @@ async def delete_filter(db_file, key): async def delete_setting(db_file, key): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Key {}.' + .format(function_name, db_file, key)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -2340,6 +2536,9 @@ async def delete_setting(db_file, key): async def delete_settings(db_file): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -2369,6 +2568,9 @@ def get_setting_value(db_file, key): val : str Numeric value. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Key {}.' + .format(function_name, db_file, key)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -2399,6 +2601,9 @@ def is_setting_key(db_file, key): key : str Key. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Key {}.' + .format(function_name, db_file, key)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -2429,6 +2634,11 @@ async def set_filter_value(db_file, key_value): """ key = key_value[0] val = key_value[1] + + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}, Key {} and Value {}.' + .format(function_name, db_file, key, val)) + async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -2473,6 +2683,11 @@ async def update_filter_value(db_file, key_value): # val = 0 key = key_value[0] val = key_value[1] + + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}, Key {} and Value {}.' + .format(function_name, db_file, key, val)) + async with DBLOCK: with create_connection(db_file) as conn: cur = conn.cursor() @@ -2506,6 +2721,9 @@ def is_filter_key(db_file, key): key : str Key. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}, Key {}.' + .format(function_name, db_file, key)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -2536,6 +2754,9 @@ def get_filter_value(db_file, key): value : str List of strings. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}, Key {}.' + .format(function_name, db_file, key)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -2563,6 +2784,9 @@ async def set_last_update_time(db_file): ------- None. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -2595,6 +2819,9 @@ async def get_last_update_time(db_file): val : str Time. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) with create_connection(db_file) as conn: cur = conn.cursor() try: @@ -2609,7 +2836,7 @@ async def get_last_update_time(db_file): value = str(value) except: value = None - logging.debug( + logger.debug( "No specific value set for key last_update.") return value @@ -2627,6 +2854,9 @@ async def update_last_update_time(db_file): ------- None. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -2661,6 +2891,9 @@ def get_categories(db_file): categories : str List of categories. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -2688,6 +2921,9 @@ def get_locales(db_file): locales : tuple List of locales. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -2715,6 +2951,9 @@ def get_nations(db_file): nations : tuple List of nations. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -2769,6 +3008,9 @@ def get_titles_tags_urls(db_file): titles_urls : tuple List of titles and urls. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {}.' + .format(function_name, db_file)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( @@ -2797,6 +3039,9 @@ def get_titles_tags_urls_by_category(db_file, category): titles_urls : tuple List of titles and urls. """ + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for filename {} and Category {}.' + .format(function_name, db_file, category)) with create_connection(db_file) as conn: cur = conn.cursor() sql = ( diff --git a/slixfeed/version.py b/slixfeed/version.py index b1e5a0e..fadb6f2 100644 --- a/slixfeed/version.py +++ b/slixfeed/version.py @@ -1,2 +1,2 @@ -__version__ = '0.1.25' -__version_info__ = (0, 1, 25) +__version__ = '0.1.26' +__version_info__ = (0, 1, 26) diff --git a/slixfeed/xmpp/client.py b/slixfeed/xmpp/client.py index 1666b4b..af7d4fc 100644 --- a/slixfeed/xmpp/client.py +++ b/slixfeed/xmpp/client.py @@ -31,7 +31,6 @@ NOTE """ import asyncio -import logging import slixmpp import slixfeed.task as task @@ -44,6 +43,7 @@ import slixfeed.task as task # from lxml import etree import slixfeed.config as config +from slixfeed.log import Logger from slixfeed.version import __version__ from slixfeed.xmpp.bookmark import XmppBookmark from slixfeed.xmpp.connect import XmppConnect @@ -55,6 +55,7 @@ from slixfeed.xmpp.roster import XmppRoster # import slixfeed.xmpp.service as service from slixfeed.xmpp.presence import XmppPresence from slixfeed.xmpp.utility import get_chat_type +import sys import asyncio from datetime import datetime @@ -91,6 +92,7 @@ loop = asyncio.get_event_loop() # current_time = now.strftime("%H:%M:%S") # return current_time +logger = Logger(__name__) class Slixfeed(slixmpp.ClientXMPP): """ @@ -179,7 +181,10 @@ class Slixfeed(slixmpp.ClientXMPP): # TODO Test async def on_groupchat_invite(self, message): - logging.warning("on_groupchat_invite") + jid_full = str(message['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) inviter = message['from'].bare muc_jid = message['groupchat_invite']['jid'] await XmppBookmark.add(self, muc_jid) @@ -194,6 +199,10 @@ class Slixfeed(slixmpp.ClientXMPP): # NOTE Tested with Gajim and Psi async def on_groupchat_direct_invite(self, message): + jid_full = str(message['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) inviter = message['from'].bare muc_jid = message['groupchat_invite']['jid'] await XmppBookmark.add(self, muc_jid) @@ -212,11 +221,14 @@ class Slixfeed(slixmpp.ClientXMPP): async def on_connection_failed(self, event): + logger.info('Event connection_failed has been initiated.') message = 'Connection has failed. Reason: {}'.format(event) XmppConnect.recover(self, message) async def on_session_start(self, event): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated.'.format(function_name)) # self.send_presence() profile.set_identity(self, 'client') # XmppCommand.adhoc_commands(self) @@ -235,6 +247,8 @@ class Slixfeed(slixmpp.ClientXMPP): def on_session_resumed(self, event): + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated.'.format(function_name)) # self.send_presence() profile.set_identity(self, 'client') self['xep_0115'].update_caps() @@ -242,34 +256,41 @@ class Slixfeed(slixmpp.ClientXMPP): async def on_disco_info(self, DiscoInfo): - jid = DiscoInfo['from'] + jid_full = str(DiscoInfo['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) # self.service_reactions() # self.send_presence(pto=jid) - await self['xep_0115'].update_caps(jid=jid) + await self['xep_0115'].update_caps(jid=jid_full) async def on_message(self, message): - jid = message['from'].bare - if jid == self.boundjid.bare: + jid_full = str(message['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = message['from'].bare + if jid_bare == self.boundjid.bare: status_type = 'dnd' status_message = ('Slixfeed is not designed to receive messages ' 'from itself') - XmppPresence.send(self, jid, status_message, + XmppPresence.send(self, jid_bare, status_message, status_type=status_type) await asyncio.sleep(5) status_message = ('Slixfeed news bot from RSS Task Force') - XmppPresence.send(self, jid, status_message) + XmppPresence.send(self, jid_bare, status_message) else: # TODO Request for subscription - if (await get_chat_type(self, jid) == 'chat' and - not self.client_roster[jid]['to']): - XmppPresence.subscription(self, jid, 'subscribe') - await XmppRoster.add(self, jid) + if (await get_chat_type(self, jid_bare) == 'chat' and + not self.client_roster[jid_bare]['to']): + XmppPresence.subscription(self, jid_bare, 'subscribe') + await XmppRoster.add(self, jid_bare) status_message = '✒️ Share online status to receive updates' - XmppPresence.send(self, jid, status_message) + XmppPresence.send(self, jid_bare, status_message) message_subject = 'RSS News Bot' message_body = 'Share online status to receive updates.' - XmppMessage.send_headline(self, jid, message_subject, + XmppMessage.send_headline(self, jid_bare, message_subject, message_body, 'chat') await process.message(self, message) # chat_type = message["type"] @@ -278,80 +299,106 @@ class Slixfeed(slixmpp.ClientXMPP): async def on_changed_status(self, presence): + jid_full = str(presence['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) # await task.check_readiness(self, presence) - jid = presence['from'].bare - if jid in self.boundjid.bare: + jid_bare = presence['from'].bare + if jid_bare in self.boundjid.bare: return if presence['show'] in ('away', 'dnd', 'xa'): - task.clean_tasks_xmpp(self, jid, ['interval']) - await task.start_tasks_xmpp(self, jid, ['status', 'check']) + key_list = ['interval'] + task.clean_tasks_xmpp(self, jid_bare, key_list) + key_list = ['status', 'check'] + await task.start_tasks_xmpp(self, jid_bare, key_list) async def on_presence_subscribe(self, presence): - jid = presence['from'].bare - if not self.client_roster[jid]['to']: + jid_full = str(presence['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = presence['from'].bare + if not self.client_roster[jid_bare]['to']: # XmppPresence.subscription(self, jid, 'subscribe') - XmppPresence.subscription(self, jid, 'subscribed') - await XmppRoster.add(self, jid) + XmppPresence.subscription(self, jid_bare, 'subscribed') + await XmppRoster.add(self, jid_bare) status_message = '✒️ Share online status to receive updates' - XmppPresence.send(self, jid, status_message) + XmppPresence.send(self, jid_bare, status_message) message_subject = 'RSS News Bot' message_body = 'Share online status to receive updates.' - XmppMessage.send_headline(self, jid, message_subject, message_body, - 'chat') + XmppMessage.send_headline(self, jid_bare, message_subject, + message_body, 'chat') def on_presence_subscribed(self, presence): - jid = presence['from'].bare - # XmppPresence.subscription(self, jid, 'subscribed') + jid_full = str(presence['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) message_subject = 'RSS News Bot' message_body = ('Greetings! I am {}, the news anchor.\n' 'My job is to bring you the latest ' 'news from sources you provide me with.\n' 'You may always reach me via xmpp:{}?message' .format(self.alias, self.boundjid.bare)) - XmppMessage.send_headline(self, jid, message_subject, message_body, - 'chat') + jid_bare = presence['from'].bare + # XmppPresence.subscription(self, jid, 'subscribed') + XmppMessage.send_headline(self, jid_bare, message_subject, + message_body, 'chat') async def on_presence_available(self, presence): + jid_full = str(presence['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) # TODO Add function to check whether task is already running or not # await task.start_tasks(self, presence) # NOTE Already done inside the start-task function - jid = presence['from'].bare - if jid in self.boundjid.bare: + jid_bare = presence['from'].bare + if jid_bare in self.boundjid.bare: return - logging.info('JID {} is available'.format(jid)) # FIXME TODO Find out what is the source responsible for a couple presences with empty message # NOTE This is a temporary solution await asyncio.sleep(10) - await task.start_tasks_xmpp(self, jid) + await task.start_tasks_xmpp(self, jid_bare) self.add_event_handler("presence_unavailable", self.on_presence_unavailable) def on_presence_unsubscribed(self, presence): - jid = presence['from'].bare + jid_full = str(presence['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = presence['from'].bare message_body = 'You have been unsubscribed.' # status_message = '🖋️ Subscribe to receive updates' # status_message = None - XmppMessage.send(self, jid, message_body, 'chat') - XmppPresence.subscription(self, jid, 'unsubscribed') + XmppMessage.send(self, jid_bare, message_body, 'chat') + XmppPresence.subscription(self, jid_bare, 'unsubscribed') # XmppPresence.send(self, jid, status_message, # presence_type='unsubscribed') - XmppRoster.remove(self, jid) + XmppRoster.remove(self, jid_bare) def on_presence_unavailable(self, presence): - jid = presence['from'].bare - logging.info('JID {} is unavailable'.format(jid)) + jid_full = str(presence['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = presence['from'].bare + logger.info('Event presence_unavailable has been initiated for JID {}.' + .format(jid_bare)) # await task.stop_tasks(self, jid) - task.clean_tasks_xmpp(self, jid) + task.clean_tasks_xmpp(self, jid_bare) # NOTE Albeit nice to ~have~ see, this would constantly # send presence messages to server to no end. status_message = 'Farewell' - XmppPresence.send(self, jid, status_message, + XmppPresence.send(self, jid_bare, status_message, presence_type='unavailable') self.del_event_handler("presence_unavailable", self.on_presence_unavailable) @@ -363,9 +410,12 @@ class Slixfeed(slixmpp.ClientXMPP): # If roster, remove contact JID into file # If bookmarks, remove groupchat JID into file def on_presence_error(self, presence): - jid = presence["from"].bare - logging.info('JID {} (error)'.format(jid)) - task.clean_tasks_xmpp(self, jid) + jid_full = str(presence['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = presence["from"].bare + task.clean_tasks_xmpp(self, jid_bare) def on_reactions(self, message): @@ -374,8 +424,12 @@ class Slixfeed(slixmpp.ClientXMPP): async def on_chatstate_active(self, message): - jid = message['from'].bare - if jid in self.boundjid.bare: + jid_full = str(message['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = message['from'].bare + if jid_bare in self.boundjid.bare: return if message['type'] in ('chat', 'normal'): # NOTE: Required for Cheogram @@ -383,12 +437,17 @@ class Slixfeed(slixmpp.ClientXMPP): # self.send_presence(pto=jid) # task.clean_tasks_xmpp(self, jid, ['status']) await asyncio.sleep(5) - await task.start_tasks_xmpp(self, jid, ['status']) + key_list = ['status'] + await task.start_tasks_xmpp(self, jid_bare, key_list) async def on_chatstate_composing(self, message): + jid_full = str(message['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) if message['type'] in ('chat', 'normal'): - jid = message['from'].bare + jid_bare = message['from'].bare # NOTE: Required for Cheogram # await self['xep_0115'].update_caps(jid=jid) # self.send_presence(pto=jid) @@ -396,34 +455,49 @@ class Slixfeed(slixmpp.ClientXMPP): await asyncio.sleep(5) status_message = ('💡 Send "help" for manual, or "info" for ' 'information.') - XmppPresence.send(self, jid, status_message) + XmppPresence.send(self, jid_bare, status_message) async def on_chatstate_gone(self, message): - jid = message['from'].bare - if jid in self.boundjid.bare: + jid_full = str(message['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = message['from'].bare + if jid_bare in self.boundjid.bare: return if message['type'] in ('chat', 'normal'): # task.clean_tasks_xmpp(self, jid, ['status']) - await task.start_tasks_xmpp(self, jid, ['status']) + key_list = ['status'] + await task.start_tasks_xmpp(self, jid_bare, key_list) async def on_chatstate_inactive(self, message): - jid = message['from'].bare - if jid in self.boundjid.bare: + jid_full = str(message['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = message['from'].bare + if jid_bare in self.boundjid.bare: return if message['type'] in ('chat', 'normal'): # task.clean_tasks_xmpp(self, jid, ['status']) - await task.start_tasks_xmpp(self, jid, ['status']) + key_list = ['status'] + await task.start_tasks_xmpp(self, jid_bare, key_list) async def on_chatstate_paused(self, message): - jid = message['from'].bare - if jid in self.boundjid.bare: + jid_full = str(message['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = message['from'].bare + if jid_bare in self.boundjid.bare: return if message['type'] in ('chat', 'normal'): # task.clean_tasks_xmpp(self, jid, ['status']) - await task.start_tasks_xmpp(self, jid, ['status']) + key_list = ['status'] + await task.start_tasks_xmpp(self, jid_bare, key_list) @@ -468,12 +542,11 @@ class Slixfeed(slixmpp.ClientXMPP): None. """ - form = self['xep_0004'].make_form( - 'form', 'Reactions Information' - ) + form = self['xep_0004'].make_form('form', 'Reactions Information') def adhoc_commands(self): + logger.info('Function adhoc_commands has been initiated.') # self["xep_0050"].add_command( # node="updates_enable", # name="Enable/Disable News Updates", @@ -523,12 +596,16 @@ class Slixfeed(slixmpp.ClientXMPP): # http://jabber.org/protocol/commands#actions async def _handle_profile(self, iq, session): - jid = session['from'].bare - jid_file = jid + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) form = self['xep_0004'].make_form('form', 'Profile') form['instructions'] = ('Displaying information\nJabber ID {}' - .format(jid)) + .format(jid_bare)) form.add_field(ftype='fixed', value='News') feeds_all = str(await sqlite.get_number_of_items(db_file, 'feeds')) @@ -614,11 +691,14 @@ class Slixfeed(slixmpp.ClientXMPP): return session async def _handle_filters(self, iq, session): - jid = session['from'].bare jid_full = str(session['from']) - chat_type = await get_chat_type(self, jid) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare + chat_type = await get_chat_type(self, jid_bare) if chat_type == 'groupchat': - moderator = is_moderator(self, jid, jid_full) + moderator = is_moderator(self, jid_bare, jid_full) if chat_type == 'chat' or moderator: jid = session['from'].bare jid_file = jid @@ -664,13 +744,17 @@ class Slixfeed(slixmpp.ClientXMPP): session. Additional, custom data may be saved here to persist across handler callbacks. """ + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) # Text is not displayed; only labels form = payload - jid = session['from'].bare + jid_bare = session['from'].bare # form = self['xep_0004'].make_form('result', 'Done') # form['instructions'] = ('✅️ Filters have been updated') - jid_file = jid + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) # In this case (as is typical), the payload is a form values = payload['values'] @@ -698,11 +782,14 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_subscription_add(self, iq, session): - jid = session['from'].bare jid_full = str(session['from']) - chat_type = await get_chat_type(self, jid) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare + chat_type = await get_chat_type(self, jid_bare) if chat_type == 'groupchat': - moderator = is_moderator(self, jid, jid_full) + moderator = is_moderator(self, jid_bare, jid_full) if chat_type == 'chat' or moderator: form = self['xep_0004'].make_form('form', 'Subscription') form['instructions'] = 'Adding subscription' @@ -730,12 +817,16 @@ class Slixfeed(slixmpp.ClientXMPP): session['payload'] = form else: text_warn = ('This resource is restricted to moderators of {}.' - .format(jid)) + .format(jid_bare)) session['notes'] = [['warn', text_warn]] return session async def _handle_recent(self, iq, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) form = self['xep_0004'].make_form('form', 'Updates') form['instructions'] = 'Browse and read news' options = form.add_field(var='action', @@ -757,8 +848,12 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_recent_result(self, payload, session): - jid = session['from'].bare - jid_file = jid + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) num = 100 match payload['values']['action']: @@ -792,21 +887,25 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_recent_select(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) values = payload['values'] ix = values['update'] - jid = session['from'].bare - jid_file = jid + jid_bare = session['from'].bare + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) title = sqlite.get_entry_title(db_file, ix) title = title[0] if title else 'Untitled' form = self['xep_0004'].make_form('form', 'Article') url = sqlite.get_entry_url(db_file, ix) url = url[0] - logging.info('Original URL: {}'.format(url)) + logger.debug('Original URL: {}'.format(url)) url = uri.remove_tracking_parameters(url) - logging.info('Processed URL (tracker removal): {}'.format(url)) + logger.debug('Processed URL (tracker removal): {}'.format(url)) url = (uri.replace_hostname(url, 'link')) or url - logging.info('Processed URL (replace hostname): {}'.format(url)) + logger.debug('Processed URL (replace hostname): {}'.format(url)) result = await fetch.http(url) if 'content' in result: data = result['content'] @@ -855,10 +954,14 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_recent_action(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) ext = payload['values']['filetype'] url = payload['values']['url'][0] - jid = session['from'].bare - jid_file = jid + jid_bare = session['from'].bare + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) cache_dir = config.get_default_cache_directory() if not os.path.isdir(cache_dir): @@ -888,7 +991,7 @@ class Slixfeed(slixmpp.ClientXMPP): 'Reason: {}'.format(ext.upper(), url, error)) session['notes'] = [['error', text_error]] else: - url = await XmppUpload.start(self, jid, filename) + url = await XmppUpload.start(self, jid_bare, filename) form = self['xep_0004'].make_form('result', 'Download') form['instructions'] = ('Download {} document.' .format(ext.upper())) @@ -905,8 +1008,12 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_subscription_new(self, payload, session): - jid = session['from'].bare - jid_file = jid + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) # scan = payload['values']['scan'] url = payload['values']['subscription'] @@ -1028,9 +1135,13 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_subscription_enable(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) form = payload - jid = session['from'].bare - jid_file = jid + jid_bare = session['from'].bare + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) ixs = payload['values']['subscriptions'] form.add_field(ftype='fixed', @@ -1058,9 +1169,13 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_subscription_disable(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) form = payload - jid = session['from'].bare - jid_file = jid + jid_bare = session['from'].bare + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) ixs = payload['values']['subscriptions'] form.add_field(ftype='fixed', @@ -1088,9 +1203,13 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_subscription_del_complete(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) form = payload - jid = session['from'].bare - jid_file = jid + jid_bare = session['from'].bare + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) ixs = payload['values']['subscriptions'] form.add_field(ftype='fixed', @@ -1118,6 +1237,10 @@ class Slixfeed(slixmpp.ClientXMPP): def _handle_cancel(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) text_note = ('Operation has been cancelled.' '\n' '\n' @@ -1127,11 +1250,14 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_discover(self, iq, session): - jid = session['from'].bare jid_full = str(session['from']) - chat_type = await get_chat_type(self, jid) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare + chat_type = await get_chat_type(self, jid_bare) if chat_type == 'groupchat': - moderator = is_moderator(self, jid, jid_full) + moderator = is_moderator(self, jid_bare, jid_full) if chat_type == 'chat' or moderator: form = self['xep_0004'].make_form('form', 'Discover & Search') form['instructions'] = 'Discover news subscriptions of all kinds' @@ -1150,12 +1276,16 @@ class Slixfeed(slixmpp.ClientXMPP): session['prev'] = None else: text_warn = ('This resource is restricted to moderators of {}.' - .format(jid)) + .format(jid_bare)) session['notes'] = [['warn', text_warn]] return session def _handle_discover_type(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) values = payload['values'] search_type = values['search_type'] config_dir = config.get_default_config_directory() @@ -1208,6 +1338,10 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_discover_category(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) values = payload['values'] category = values['category'] config_dir = config.get_default_config_directory() @@ -1234,11 +1368,14 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_subscriptions(self, iq, session): - jid = session['from'].bare jid_full = str(session['from']) - chat_type = await get_chat_type(self, jid) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare + chat_type = await get_chat_type(self, jid_bare) if chat_type == 'groupchat': - moderator = is_moderator(self, jid, jid_full) + moderator = is_moderator(self, jid_bare, jid_full) if chat_type == 'chat' or moderator: form = self['xep_0004'].make_form('form', 'Subscriptions') form['instructions'] = 'Managing subscriptions' @@ -1258,14 +1395,18 @@ class Slixfeed(slixmpp.ClientXMPP): session['has_next'] = True else: text_warn = ('This resource is restricted to moderators of {}.' - .format(jid)) + .format(jid_bare)) session['notes'] = [['warn', text_warn]] return session async def _handle_subscriptions_result(self, payload, session): - jid = session['from'].bare - jid_file = jid + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) form = self['xep_0004'].make_form('form', 'Subscriptions') match payload['values']['action']: @@ -1364,8 +1505,12 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_subscription_tag(self, payload, session): - jid = session['from'].bare - jid_file = jid + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) tag_id = payload['values']['tag'] tag_name = sqlite.get_tag_name(db_file, tag_id)[0] @@ -1394,13 +1539,17 @@ class Slixfeed(slixmpp.ClientXMPP): # FIXME There are feeds that are missing (possibly because of sortings) async def _handle_subscription(self, iq, session): - jid = session['from'].bare + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare form = self['xep_0004'].make_form('form', 'Subscription editor') form['instructions'] = '📰️ Edit subscription preferences and properties' # form.addField(var='interval', # ftype='text-single', # label='Interval period') - jid_file = jid + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) subscriptions = await sqlite.get_feeds(db_file) # subscriptions = set(subscriptions) @@ -1417,7 +1566,7 @@ class Slixfeed(slixmpp.ClientXMPP): categorized_subscriptions[letter].append(subscription) # title[0].capitalize()].append(subscription) except Exception as e: - logging.warning('Title might be empty:', str(e)) + logger.warning('Title might be empty:', str(e)) for category in sorted(categorized_subscriptions): options = form.add_field(var=category, ftype='list-single', @@ -1437,8 +1586,12 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_subscription_editor(self, payload, session): - jid = session['from'].bare - jid_file = jid + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) if 'edit' in payload['values'] and not payload['values']['edit']: session['payload'] = None @@ -1529,9 +1682,13 @@ class Slixfeed(slixmpp.ClientXMPP): # TODO Create a new form. Do not "recycle" the last form. async def _handle_subscription_complete(self, payload, session): - jid = session['from'].bare + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare values = payload['values'] - jid_file = jid + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) # url = values['url'] # feed_id = await sqlite.get_feed_id(db_file, url) @@ -1590,7 +1747,11 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_subscription_selector(self, payload, session): - jid = session['from'].bare + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare form = self['xep_0004'].make_form('form', 'Add Subscription') form['instructions'] = ('📰️ Select a subscription to add\n' 'Subsciptions discovered for {}' @@ -1604,7 +1765,7 @@ class Slixfeed(slixmpp.ClientXMPP): desc=('Select subscriptions to perform ' 'actions upon.'), required=True) - jid_file = jid + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) subscriptions = await sqlite.get_feeds(db_file) subscriptions = sorted(subscriptions, key=lambda x: x[0]) @@ -1628,11 +1789,14 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_advanced(self, iq, session): - jid = session['from'].bare jid_full = str(session['from']) - chat_type = await get_chat_type(self, jid) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare + chat_type = await get_chat_type(self, jid_bare) if chat_type == 'groupchat': - moderator = is_moderator(self, jid, jid_full) + moderator = is_moderator(self, jid_bare, jid_full) if chat_type == 'chat' or moderator: form = self['xep_0004'].make_form('form', 'Advanced') form['instructions'] = 'Extended options' @@ -1660,6 +1824,10 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_advanced_result(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) match payload['values']['option']: case 'activity': # TODO dialog for JID and special dialog for operator @@ -1669,8 +1837,8 @@ class Slixfeed(slixmpp.ClientXMPP): case 'admin': # NOTE Even though this check is already conducted on previous # form, this check is being done just in case. - jid = session['from'].bare - if jid == config.get_value('accounts', 'XMPP', 'operator'): + jid_bare = session['from'].bare + if jid_bare == config.get_value('accounts', 'XMPP', 'operator'): if self.is_component: # NOTE This will be changed with XEP-0222 XEP-0223 text_info = ('Subscriber management options are ' @@ -1698,12 +1866,9 @@ class Slixfeed(slixmpp.ClientXMPP): session['next'] = self._handle_admin_action session['has_next'] = True else: - logging.warning('An unauthorized attempt to access bookmarks has ' - 'been detected!\n' - 'Details:\n' - ' Jabber ID: {}\n' - ' Timestamp: {}\n' - .format(jid, dt.timestamp())) + logger.warning('An unauthorized attempt to access ' + 'bookmarks has been detected for JID {} at ' + '{}'.format(jid_bare, dt.timestamp())) text_warn = 'This resource is restricted.' session['notes'] = [['warn', text_warn]] session['has_next'] = False @@ -1764,6 +1929,10 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_about(self, iq, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) form = self['xep_0004'].make_form('form', 'About') form['instructions'] = 'Information about Slixfeed and related projects' options = form.add_field(var='option', @@ -1789,6 +1958,10 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_about_result(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) match payload['values']['option']: case 'about': title = 'About' @@ -1868,6 +2041,10 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_motd(self, iq, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) # TODO add functionality to attach image. # Here you can add groupchat rules,post schedule, tasks or # anything elaborated you might deem fit. Good luck! @@ -1877,6 +2054,11 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_help(self, iq, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + import tomllib config_dir = config.get_default_config_directory() with open(config_dir + '/' + 'commands.toml', mode="rb") as commands: @@ -1914,11 +2096,15 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_import_complete(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) form = payload url = payload['values']['url'] if url.startswith('http') and url.endswith('.opml'): - jid = session['from'].bare - jid_file = jid.replace('/', '_') + jid_bare = session['from'].bare + jid_file = jid_bare.replace('/', '_') db_file = config.get_pathname_to_database(jid_file) count = await action.import_opml(db_file, url) try: @@ -1947,22 +2133,26 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_export_complete(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) form = payload - jid = session['from'].bare - jid_file = jid.replace('/', '_') + jid_bare = session['from'].bare + jid_file = jid_bare.replace('/', '_') # form = self['xep_0004'].make_form('result', 'Done') # form['instructions'] = ('✅️ Feeds have been exported') exts = payload['values']['filetype'] for ext in exts: - filename = await action.export_feeds(self, jid, jid_file, ext) - url = await XmppUpload.start(self, jid, filename) + filename = await action.export_feeds(self, jid_bare, jid_file, ext) + url = await XmppUpload.start(self, jid_bare, filename) url_field = form.add_field(var=ext.upper(), ftype='text-single', label=ext, value=url) url_field['validate']['datatype'] = 'xs:anyURI' - chat_type = await get_chat_type(self, jid) - XmppMessage.send_oob(self, jid, url, chat_type) + chat_type = await get_chat_type(self, jid_bare) + XmppMessage.send_oob(self, jid_bare, url, chat_type) form['type'] = 'result' form['title'] = 'Done' form['instructions'] = ('Completed successfully!') @@ -1976,11 +2166,15 @@ class Slixfeed(slixmpp.ClientXMPP): # TODO Attempt to look up for feeds of hostname of JID (i.e. scan # jabber.de for feeds for juliet@jabber.de) async def _handle_promoted(self, iq, session): - jid = session['from'].bare jid_full = str(session['from']) - chat_type = await get_chat_type(self, jid) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare + jid_full = str(session['from']) + chat_type = await get_chat_type(self, jid_bare) if chat_type == 'groupchat': - moderator = is_moderator(self, jid, jid_full) + moderator = is_moderator(self, jid_bare, jid_full) if chat_type == 'chat' or moderator: form = self['xep_0004'].make_form('form', 'Subscribe') # NOTE Refresh button would be of use @@ -2039,8 +2233,12 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_admin_action(self, payload, session): - jid = session['from'].bare - jid_file = jid + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) subscriptions = await sqlite.get_feeds(db_file) subscriptions = sorted(subscriptions, key=lambda x: x[0]) @@ -2119,40 +2317,44 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_subscribers_complete(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) values = payload['values'] - jid = values['jid'] + jid_bare = values['jid'] value_subject = values['subject'] message_subject = value_subject if value_subject else None value_message = values['message'] message_body = value_message if value_message else None match values['action']: case 'from': - XmppPresence.subscription(self, jid, 'subscribe') + XmppPresence.subscription(self, jid_bare, 'subscribe') if not message_subject: message_subject = 'System Message' if not message_body: - message_body = ('This user wants to subscribe to your presence' - '. Click the button labelled "Add/Auth" to ' - 'authorize the subscription. This will also ' - 'add the person to your contact list if it is ' - 'not already there.') + message_body = ('This user wants to subscribe to your ' + 'presence. Click the button labelled ' + '"Add/Auth" toauthorize the subscription. ' + 'This will also add the person to your ' + 'contact list if it is not already there.') case 'remove': - XmppRoster.remove(self, jid) + XmppRoster.remove(self, jid_bare) if not message_subject: message_subject = 'System Message' if not message_body: message_body = 'Your authorization has been removed!' case 'to': - XmppPresence.subscription(self, jid, 'subscribed') + XmppPresence.subscription(self, jid_bare, 'subscribed') if not message_subject: message_subject = 'System Message' if not message_body: message_body = 'Your authorization has been approved!' if message_subject: - XmppMessage.send_headline(self, jid, message_subject, message_body, - 'chat') + XmppMessage.send_headline(self, jid_bare, message_subject, + message_body, 'chat') elif message_body: - XmppMessage.send(self, jid, message_body, 'chat') + XmppMessage.send(self, jid_bare, message_body, 'chat') form = payload form['title'] = 'Done' form['instructions'] = ('has been completed!') @@ -2163,19 +2365,23 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_contact_action(self, payload, session): - jid = payload['values']['jid'] + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = payload['values']['jid'] form = self['xep_0004'].make_form('form', 'Contacts') session['allow_complete'] = True roster = await XmppRoster.get_contacts(self) - properties = roster[jid] + properties = roster[jid_bare] match payload['values']['action']: case 'edit': form['instructions'] = 'Editing contact' options = form.add_field(var='jid', ftype='list-single', label='Jabber ID', - value=jid) - options.addOption(jid, jid) + value=jid_bare) + options.addOption(jid_bare, jid_bare) form.add_field(var='name', ftype='text-single', label='Name', @@ -2185,7 +2391,7 @@ class Slixfeed(slixmpp.ClientXMPP): case 'view': form['instructions'] = 'Viewing contact' contact_name = properties['name'] - contact_name = contact_name if contact_name else jid + contact_name = contact_name if contact_name else jid_bare form.add_field(var='name', ftype='text-single', label='Name', @@ -2226,17 +2432,21 @@ class Slixfeed(slixmpp.ClientXMPP): def _handle_contacts_complete(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) values = payload['values'] - jid = values['jid'] + jid_bare = values['jid'] name = values['name'] - name_old = XmppRoster.get_contact_name(self, jid) + name_old = XmppRoster.get_contact_name(self, jid_bare) if name == name_old: message = ('No action has been taken. Reason: New ' 'name is identical to the current one.') session['payload'] = None session['notes'] = [['info', message]] else: - XmppRoster.set_contact_name(self, jid, name) + XmppRoster.set_contact_name(self, jid_bare, name) form = payload form['title'] = 'Done' form['instructions'] = ('has been completed!') @@ -2246,8 +2456,12 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_bookmarks_editor(self, payload, session): - jid = payload['values']['jid'] - properties = await XmppBookmark.properties(self, jid) + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = payload['values']['jid'] + properties = await XmppBookmark.properties(self, jid_bare) form = self['xep_0004'].make_form('form', 'Bookmarks') form['instructions'] = 'Editing bookmark' jid_split = properties['jid'].split('@') @@ -2256,8 +2470,8 @@ class Slixfeed(slixmpp.ClientXMPP): options = form.addField(var='jid', ftype='list-single', label='Jabber ID', - value=jid) - options.addOption(jid, jid) + value=jid_bare) + options.addOption(jid_bare, jid_bare) form.addField(var='name', ftype='text-single', label='Name', @@ -2307,6 +2521,10 @@ class Slixfeed(slixmpp.ClientXMPP): async def _handle_bookmarks_complete(self, payload, session): + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) # form = self['xep_0004'].make_form('result', 'Done') # form['instructions'] = ('✅️ Bookmark has been saved') # # In this case (as is typical), the payload is a form @@ -2338,13 +2556,16 @@ class Slixfeed(slixmpp.ClientXMPP): session. Additional, custom data may be saved here to persist across handler callbacks. """ - jid = session['from'].bare jid_full = str(session['from']) - chat_type = await get_chat_type(self, jid) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare + chat_type = await get_chat_type(self, jid_bare) if chat_type == 'groupchat': - moderator = is_moderator(self, jid, jid_full) + moderator = is_moderator(self, jid_bare, jid_full) if chat_type == 'chat' or moderator: - jid_file = jid + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) form = self['xep_0004'].make_form('form', 'Settings') form['instructions'] = 'Editing settings' @@ -2442,15 +2663,19 @@ class Slixfeed(slixmpp.ClientXMPP): session['payload'] = form else: text_warn = ('This resource is restricted to moderators of {}.' - .format(jid)) + .format(jid_bare)) session['notes'] = [['warn', text_warn]] return session async def _handle_settings_complete(self, payload, session): - jid = session['from'].bare + jid_full = str(session['from']) + function_name = sys._getframe().f_code.co_name + logger.info('Function {} has been initiated for JID {}.' + .format(function_name, jid_full)) + jid_bare = session['from'].bare form = payload - jid_file = jid + jid_file = jid_bare db_file = config.get_pathname_to_database(jid_file) # In this case (as is typical), the payload is a form values = payload['values'] @@ -2465,22 +2690,23 @@ class Slixfeed(slixmpp.ClientXMPP): if (key == 'enabled' and val == 1 and config.get_setting_value(db_file, 'enabled') == 0): - logging.info('Slixfeed has been enabled for {}'.format(jid)) + logger.info('Slixfeed has been enabled for {}'.format(jid_bare)) status_type = 'available' status_message = '📫️ Welcome back!' - XmppPresence.send(self, jid, status_message, + XmppPresence.send(self, jid_bare, status_message, status_type=status_type) await asyncio.sleep(5) - await task.start_tasks_xmpp(self, jid, ['check', 'status', - 'interval']) + key_list = ['check', 'status', 'interval'] + await task.start_tasks_xmpp(self, jid_bare, key_list) if (key == 'enabled' and val == 0 and config.get_setting_value(db_file, 'enabled') == 1): - logging.info('Slixfeed has been disabled for {}'.format(jid)) - task.clean_tasks_xmpp(self, jid, ['interval', 'status']) + logger.info('Slixfeed has been disabled for {}'.format(jid_bare)) + key_list = ['interval', 'status'] + task.clean_tasks_xmpp(self, jid_bare, key_list) status_type = 'xa' status_message = '📪️ Send "Start" to receive updates' - XmppPresence.send(self, jid, status_message, + XmppPresence.send(self, jid_bare, status_message, status_type=status_type) await config.set_setting_value(db_file, key, val) diff --git a/slixfeed/xmpp/presence.py b/slixfeed/xmpp/presence.py index a0ccaa1..4d5d942 100644 --- a/slixfeed/xmpp/presence.py +++ b/slixfeed/xmpp/presence.py @@ -9,7 +9,7 @@ Accept symbols 🉑️ 👍️ ✍ TODO -Remove subscription from JID that do not (stopped) share presence. +Remove subscription from JID that do not (i.e. has stopped) share presence. """