WIP: Closer to fix double message. See task.py

This commit is contained in:
Schimon Jehudah 2024-02-10 17:53:53 +00:00
parent c568145ecf
commit 9434833449
16 changed files with 369 additions and 242 deletions

View file

@ -8,9 +8,9 @@ FIXME
Consequently, it might result in database lock error upon Consequently, it might result in database lock error upon
feed removal attempt feed removal attempt
TODO 2) Communicate to messages of new contacts (not subscribed and not in roster)
1) SQL prepared statements; TODO
2) Machine Learning for scrapping Title, Link, Summary and Timstamp; 2) Machine Learning for scrapping Title, Link, Summary and Timstamp;
Scrape element </article> (example: Liferea) Scrape element </article> (example: Liferea)
@ -22,15 +22,9 @@ TODO
Perhaps not, as it would require to check every feed for this setting. Perhaps not, as it would require to check every feed for this setting.
Maybe a separate bot; Maybe a separate bot;
4) Support categories; 5) OMEMO;
5) XMPP commands; 6) Logging;
6) Bot as service;
7) OMEMO;
8) Logging;
https://docs.python.org/3/howto/logging.html https://docs.python.org/3/howto/logging.html
9) Readability 9) Readability
@ -43,12 +37,6 @@ TODO
Store 5 upcoming summaries. Store 5 upcoming summaries.
This would help making the database files smaller. This would help making the database files smaller.
11) Support protocol Gopher
See project /michael-lazar/pygopherd
See project /gopherball/gb
12) Support ActivityPub @person@domain (see Tip Of The Day).
13) Tip Of The Day. 13) Tip Of The Day.
Did you know that you can follow you favorite Mastodon feeds by just Did you know that you can follow you favorite Mastodon feeds by just
sending the URL address? sending the URL address?
@ -57,21 +45,15 @@ TODO
Mastodon, Misskey, Pixelfed, Pleroma, Socialhome, Soapbox. Mastodon, Misskey, Pixelfed, Pleroma, Socialhome, Soapbox.
14) Brand: News Broker, Newsman, Newsdealer, Laura Harbinger 14) Brand: News Broker, Newsman, Newsdealer, Laura Harbinger
15) See project /offpunk/offblocklist.py
16) Search messages of government regulated publishers, and promote other sources. 16) Search messages of government regulated publishers, and promote other sources.
Dear reader, we couldn't get news from XYZ as they don't provide RSS feeds. Dear reader, we couldn't get news from XYZ as they don't provide RSS feeds.
However, you might want to get news from (1) (2) and (3) instead! However, you might want to get news from (1) (2) and (3) instead!
17) Make the program portable (directly use the directory assets) -- Thorsten 17) The operator account will be given reports from the bot about its
18) The operator account will be given reports from the bot about its
activities every X minutes. activities every X minutes.
When a suspicious activity is detected, it will be reported immediately. When a suspicious activity is detected, it will be reported immediately.
19) Communicate to messages of new contacts (not subscribed and not in roster)
""" """
# vars and their meanings: # vars and their meanings:

View file

@ -86,6 +86,7 @@ except ImportError:
"Package readability was not found.\n" "Package readability was not found.\n"
"Arc90 Lab algorithm is disabled.") "Arc90 Lab algorithm is disabled.")
def manual(filename, section=None, command=None): def manual(filename, section=None, command=None):
config_dir = config.get_default_config_directory() config_dir = config.get_default_config_directory()
with open(config_dir + '/' + filename, mode="rb") as commands: with open(config_dir + '/' + filename, mode="rb") as commands:
@ -111,7 +112,8 @@ def manual(filename, section=None, command=None):
return cmd_list return cmd_list
async def xmpp_change_interval(self, key, val, jid, jid_file, message=None, session=None): async def xmpp_change_interval(self, key, val, jid, jid_file, message=None,
session=None):
if val: if val:
# response = ( # response = (
# 'Updates will be sent every {} minutes.' # 'Updates will be sent every {} minutes.'
@ -123,7 +125,7 @@ async def xmpp_change_interval(self, key, val, jid, jid_file, message=None, sess
await sqlite.set_settings_value(db_file, [key, val]) await sqlite.set_settings_value(db_file, [key, val])
# NOTE Perhaps this should be replaced # NOTE Perhaps this should be replaced
# by functions clean and start # by functions clean and start
await task.refresh_task(self, jid, task.send_update, key, val) await task.refresh_task(self, jid, task.task_send, key, val)
response = ('Updates will be sent every {} minutes.' response = ('Updates will be sent every {} minutes.'
.format(val)) .format(val))
else: else:
@ -344,17 +346,17 @@ def list_search_results(query, results):
return message return message
def list_feeds_by_query(query, results): def list_feeds_by_query(db_file, query):
results = sqlite.search_feeds(db_file, query)
message = ( message = (
"Feeds containing '{}':\n\n```" 'Feeds containing "{}":\n\n```'
).format(query) .format(query))
for result in results: for result in results:
message += ( message += (
"\nName : {} [{}]" '\nName : {} [{}]'
"\nURL : {}" '\nURL : {}'
"\n" '\n'
).format( .format(str(result[0]), str(result[1]), str(result[2])))
str(result[0]), str(result[1]), str(result[2]))
if len(results): if len(results):
message += "\n```\nTotal of {} feeds".format(len(results)) message += "\n```\nTotal of {} feeds".format(len(results))
else: else:

View file

@ -141,15 +141,19 @@ interval <minutes>
Set interval update to every given <minutes>. Set interval update to every given <minutes>.
""" """
length = """ length = """
length length <number>
Set maximum length of news item description. (0 for no limit) Set maximum length of news item description. (0 for no limit)
""" """
media = """
media [off|on]
Attach media (i.e. audio, image, video) to messages when available.
"""
quantum = """ quantum = """
quantum <number> quantum <number>
Set amount of updates per message by given <number>. Set amount of updates per message by given <number>.
""" """
random = """ random = """
random random [off|on]
Send messages by random order instead of date. Send messages by random order instead of date.
""" """
@ -190,7 +194,7 @@ Disable bot and stop updates.
[preview] [preview]
read = """ read = """
read <url> read <url>
Display most recent 5 titles of given <url>. Display recent 5 titles of given <url>.
""" """
read_num = """ read_num = """
read <url> <index> read <url> <index>

View file

@ -3,10 +3,10 @@ Slixfeed
A Syndication bot for the XMPP communication network. A Syndication bot for the XMPP communication network.
Slixfeed aims to be an easy to use and fully-featured news \ Slixfeed is a news broker which aims to be an easy to use and fully-\
aggregator bot for XMPP. It provides a convenient access to Blogs, \ featured news aggregator bot. It provides a convenient access to \
News websites and even Fediverse instances, along with filtering \ Blogs, News websites and even Fediverse instances, along with \
functionality. filtering functionality.
Slixfeed is primarily designed for XMPP (aka Jabber). \ Slixfeed is primarily designed for XMPP (aka Jabber). \
Visit https://xmpp.org/software/ for more information. Visit https://xmpp.org/software/ for more information.
@ -61,7 +61,7 @@ No operator was specified for this instance.
platforms = """ platforms = """
Supported platforms: XMPP Supported platforms: XMPP
Platforms to be added in future: ActivityPub, Briar, Email, IRC, LXMF, Matrix, MQTT, Nostr, Tox. Platforms to be added in future: ActivityPub, Briar, Email, IRC, LXMF, Matrix, MQTT, Nostr, Session, Tox.
For ideal experience, we recommend using XMPP. For ideal experience, we recommend using XMPP.
""" """
@ -78,10 +78,15 @@ Protocols to be added in future: Dat, FTP, Gemini, Gopher, IPFS.
resources = """ resources = """
Slixfeed Slixfeed
https://gitgud.io/sjehuda/slixfeed https://gitgud.io/sjehuda/slixfeed
Slixmpp Slixmpp
https://slixmpp.readthedocs.io/ https://slixmpp.readthedocs.io/
feedparser feedparser
https://pythonhosted.org/feedparser https://pythonhosted.org/feedparser
XMPP
https://xmpp.org/about/
""" """
terms = """ terms = """
@ -113,13 +118,14 @@ imattau (atomtopubsub), \
Jaussoin Timothée <mov.im> (Movim, France), \ Jaussoin Timothée <mov.im> (Movim, France), \
Justin Karneges <jblog.andbit.net> (Psi, California), \ Justin Karneges <jblog.andbit.net> (Psi, California), \
Kevin Smith <isode.com> (Swift IM, Wales), \ Kevin Smith <isode.com> (Swift IM, Wales), \
Lars Windolf (Liferea, Germany), \
Luis Henrique Mello (SalixOS, Brazil), \ Luis Henrique Mello (SalixOS, Brazil), \
magicfelix, \ magicfelix, \
Markus Muttilainen (SalixOS), \ Markus Muttilainen (SalixOS), \
Martin <debacle@debian.org> (Debian, Germany), \ Martin <debacle@debian.org> (Debian, Germany), \
Mathieu Pasquet (slixmpp, France), \ Mathieu Pasquet (slixmpp, France), \
Maxime Buquet (slixmpp, France), \ Maxime Buquet (slixmpp, France), \
Phillip Watkins (United Kingdom, SalixOS), \ Phillip Watkins (SalixOS, United Kingdom), \
Pierrick Le Brun (SalixOS, France), \ Pierrick Le Brun (SalixOS, France), \
Raphael Groner (Fedora, Germany), \ Raphael Groner (Fedora, Germany), \
Remko Tronçon <mko.re> (Psi , Belgium), \ Remko Tronçon <mko.re> (Psi , Belgium), \

View file

@ -19,6 +19,10 @@ TODO
6) Use TOML https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell 6) Use TOML https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell
7) Make the program portable (directly use the directory assets) -- Thorsten
7.1) Read missing files from base directories or either set error message.
""" """
import configparser import configparser

View file

@ -3,6 +3,14 @@
""" """
FIXME
1) https://wiki.pine64.org
File "/slixfeed/crawl.py", line 178, in feed_mode_guess
address = join_url(url, parted_url.path.split('/')[1] + path)
~~~~~~~~~~~~~~~~~~~~~~~~~~^^^
IndexError: list index out of range
TODO TODO
1.1) Attempt to scan more paths: /blog/, /news/ etc., including root / 1.1) Attempt to scan more paths: /blog/, /news/ etc., including root /

View file

@ -21,6 +21,14 @@ TODO
4) Replace sqlite.remove_nonexistent_entries by sqlite.check_entry_exist 4) Replace sqlite.remove_nonexistent_entries by sqlite.check_entry_exist
Same check, just reverse. Same check, just reverse.
5) Support protocol Gopher
See project /michael-lazar/pygopherd
See project /gopherball/gb
6) Support ActivityPub @person@domain (see Tip Of The Day).
7) See project /offpunk/offblocklist.py
""" """
from aiohttp import ClientError, ClientSession, ClientTimeout from aiohttp import ClientError, ClientSession, ClientTimeout
@ -41,18 +49,25 @@ except:
"BitTorrent is disabled.") "BitTorrent is disabled.")
# class FetchDat:
# async def dat(): # async def dat():
# class FetchFtp:
# async def ftp(): # async def ftp():
# class FetchGemini:
# async def gemini(): # async def gemini():
# class FetchGopher:
# async def gopher(): # async def gopher():
# class FetchHttp:
# async def http(): # async def http():
# class FetchIpfs:
# async def ipfs(): # async def ipfs():
def http_response(url): def http_response(url):
""" """
Download response headers. Download response headers.

View file

@ -10,6 +10,11 @@ TODO
All other functions to receive cursor. All other functions to receive cursor.
2) Merge function add_metadata into function import_feeds. 2) Merge function add_metadata into function import_feeds.
3) SQL prepared statements.
4) Support categories;
""" """
from asyncio import Lock from asyncio import Lock
@ -66,6 +71,51 @@ def create_tables(db_file):
Path to database file. Path to database file.
""" """
with create_connection(db_file) as conn: with create_connection(db_file) as conn:
archive_table_sql = (
"""
CREATE TABLE IF NOT EXISTS archive (
id INTEGER NOT NULL,
title TEXT NOT NULL,
link TEXT NOT NULL,
enclosure TEXT,
entry_id TEXT NOT NULL,
feed_id INTEGER NOT NULL,
timestamp TEXT,
read INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY ("feed_id") REFERENCES "feeds" ("id")
ON UPDATE CASCADE
ON DELETE CASCADE,
PRIMARY KEY ("id")
);
"""
)
categories_table_sql = (
"""
CREATE TABLE IF NOT EXISTS categories (
id INTEGER NOT NULL,
name TEXT,
PRIMARY KEY ("id")
);
"""
)
entries_table_sql = (
"""
CREATE TABLE IF NOT EXISTS entries (
id INTEGER NOT NULL,
title TEXT NOT NULL,
link TEXT NOT NULL,
enclosure TEXT,
entry_id TEXT NOT NULL,
feed_id INTEGER NOT NULL,
timestamp TEXT,
read INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY ("feed_id") REFERENCES "feeds" ("id")
ON UPDATE CASCADE
ON DELETE CASCADE,
PRIMARY KEY ("id")
);
"""
)
feeds_table_sql = ( feeds_table_sql = (
""" """
CREATE TABLE IF NOT EXISTS feeds ( CREATE TABLE IF NOT EXISTS feeds (
@ -76,18 +126,19 @@ def create_tables(db_file):
); );
""" """
) )
feeds_statistics_table_sql = ( # TODO Rethink!
# Albeit, probably, more expensive, we might want to have feed_id
# as foreign key, as it is with feeds_properties and feeds_state
feeds_categories_table_sql = (
""" """
CREATE TABLE IF NOT EXISTS statistics ( CREATE TABLE IF NOT EXISTS feeds_categories (
id INTEGER NOT NULL, id INTEGER NOT NULL,
feed_id INTEGER NOT NULL UNIQUE, category_id INTEGER NOT NULL UNIQUE,
offline INTEGER, feed_id INTEGER,
entries INTEGER, FOREIGN KEY ("category_id") REFERENCES "categories" ("id")
entries INTEGER,
FOREIGN KEY ("feed_id") REFERENCES "feeds" ("id")
ON UPDATE CASCADE ON UPDATE CASCADE
ON DELETE CASCADE, ON DELETE CASCADE,
PRIMARY KEY ("id") PRIMARY KEY (id)
); );
""" """
) )
@ -127,53 +178,32 @@ def create_tables(db_file):
); );
""" """
) )
feeds_statistics_table_sql = (
"""
CREATE TABLE IF NOT EXISTS statistics (
id INTEGER NOT NULL,
feed_id INTEGER NOT NULL UNIQUE,
offline INTEGER,
entries INTEGER,
entries INTEGER,
FOREIGN KEY ("feed_id") REFERENCES "feeds" ("id")
ON UPDATE CASCADE
ON DELETE CASCADE,
PRIMARY KEY ("id")
);
"""
)
# TODO # TODO
# Consider parameter unique: # Consider parameter unique:
# entry_id TEXT NOT NULL UNIQUE, # entry_id TEXT NOT NULL UNIQUE,
# Will eliminate function: # Will eliminate function:
# check_entry_exist # check_entry_exist
entries_table_sql = ( filters_table_sql = (
""" """
CREATE TABLE IF NOT EXISTS entries ( CREATE TABLE IF NOT EXISTS filters (
id INTEGER NOT NULL,
title TEXT NOT NULL,
link TEXT NOT NULL,
enclosure TEXT,
entry_id TEXT NOT NULL,
feed_id INTEGER NOT NULL,
timestamp TEXT,
read INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY ("feed_id") REFERENCES "feeds" ("id")
ON UPDATE CASCADE
ON DELETE CASCADE,
PRIMARY KEY ("id")
);
"""
)
archive_table_sql = (
"""
CREATE TABLE IF NOT EXISTS archive (
id INTEGER NOT NULL,
title TEXT NOT NULL,
link TEXT NOT NULL,
enclosure TEXT,
entry_id TEXT NOT NULL,
feed_id INTEGER NOT NULL,
timestamp TEXT,
read INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY ("feed_id") REFERENCES "feeds" ("id")
ON UPDATE CASCADE
ON DELETE CASCADE,
PRIMARY KEY ("id")
);
"""
)
status_table_sql = (
"""
CREATE TABLE IF NOT EXISTS status (
id INTEGER NOT NULL, id INTEGER NOT NULL,
key TEXT NOT NULL, key TEXT NOT NULL,
value INTEGER, value TEXT,
PRIMARY KEY ("id") PRIMARY KEY ("id")
); );
""" """
@ -188,12 +218,12 @@ def create_tables(db_file):
); );
""" """
) )
filters_table_sql = ( status_table_sql = (
""" """
CREATE TABLE IF NOT EXISTS filters ( CREATE TABLE IF NOT EXISTS status (
id INTEGER NOT NULL, id INTEGER NOT NULL,
key TEXT NOT NULL, key TEXT NOT NULL,
value TEXT, value INTEGER,
PRIMARY KEY ("id") PRIMARY KEY ("id")
); );
""" """
@ -863,17 +893,17 @@ async def archive_entry(db_file, ix):
) )
def get_feed_title(db_file, ix): def get_feed_title(db_file, feed_id):
with create_connection(db_file) as conn: with create_connection(db_file) as conn:
cur = conn.cursor() cur = conn.cursor()
sql = ( sql = (
""" """
SELECT name SELECT name
FROM feeds FROM feeds
WHERE id = :ix WHERE id = :feed_id
""" """
) )
par = (ix,) par = (feed_id,)
title = cur.execute(sql, par).fetchone() title = cur.execute(sql, par).fetchone()
return title return title
@ -1520,7 +1550,7 @@ async def last_entries(db_file, num):
return results return results
async def search_feeds(db_file, query): def search_feeds(db_file, query):
""" """
Query feeds. Query feeds.

View file

@ -5,12 +5,18 @@
FIXME FIXME
0) URGENT!!! Place "await asyncio.sleep(next_update_time)" ***inside*** the
task, and not outside as it is now!
1) Function check_readiness or event "changed_status" is causing for 1) Function check_readiness or event "changed_status" is causing for
triple status messages and also false ones that indicate of lack triple status messages and also false ones that indicate of lack
of feeds. of feeds.
TODO TODO
0) Move functions send_status and send_update to module action
1) Deprecate "add" (see above) and make it interactive. 1) Deprecate "add" (see above) and make it interactive.
Slixfeed: Do you still want to add this URL to subscription list? Slixfeed: Do you still want to add this URL to subscription list?
See: case _ if message_lowercase.startswith("add"): See: case _ if message_lowercase.startswith("add"):
@ -70,13 +76,13 @@ loop = asyncio.get_event_loop()
# task_ping = asyncio.create_task(ping(self, jid=None)) # task_ping = asyncio.create_task(ping(self, jid=None))
def ping_task(self): def task_ping(self):
# global ping_task_instance # global task_ping_instance
try: try:
self.ping_task_instance.cancel() self.task_ping_instance.cancel()
except: except:
logging.info('No ping task to cancel.') logging.info('No ping task to cancel.')
self.ping_task_instance = asyncio.create_task(XmppConnect.ping(self)) self.task_ping_instance = asyncio.create_task(XmppConnect.ping(self))
""" """
@ -118,7 +124,7 @@ async def start_tasks_xmpp(self, jid, tasks=None):
self.task_manager[jid][task].cancel() self.task_manager[jid][task].cancel()
except: except:
logging.info('No task {} for JID {} (start_tasks_xmpp)' logging.info('No task {} for JID {} (start_tasks_xmpp)'
.format(task, jid)) .format(task, jid))
logging.info('Starting tasks {} for JID {}'.format(tasks, jid)) logging.info('Starting tasks {} for JID {}'.format(tasks, jid))
for task in tasks: for task in tasks:
# print("task:", task) # print("task:", task)
@ -133,34 +139,8 @@ async def start_tasks_xmpp(self, jid, tasks=None):
self.task_manager[jid]['status'] = asyncio.create_task( self.task_manager[jid]['status'] = asyncio.create_task(
send_status(self, jid)) send_status(self, jid))
case 'interval': case 'interval':
jid_file = jid.replace('/', '_')
db_file = config.get_pathname_to_database(jid_file)
update_interval = await config.get_setting_value(db_file,
'interval')
update_interval = 60 * int(update_interval)
last_update_time = await sqlite.get_last_update_time(db_file)
if last_update_time:
last_update_time = float(last_update_time)
diff = time.time() - last_update_time
if diff < update_interval:
next_update_time = update_interval - diff
await asyncio.sleep(next_update_time)
# print("jid :", jid, "\n"
# "time :", time.time(), "\n"
# "last_update_time :", last_update_time, "\n"
# "difference :", diff, "\n"
# "update interval :", update_interval, "\n"
# "next_update_time :", next_update_time, "\n"
# )
# elif diff > val:
# next_update_time = val
await sqlite.update_last_update_time(db_file)
else:
await sqlite.set_last_update_time(db_file)
self.task_manager[jid]['interval'] = asyncio.create_task( self.task_manager[jid]['interval'] = asyncio.create_task(
send_update(self, jid)) task_send(self, jid))
# for task in self.task_manager[jid].values(): # for task in self.task_manager[jid].values():
# print("task_manager[jid].values()") # print("task_manager[jid].values()")
# print(self.task_manager[jid].values()) # print(self.task_manager[jid].values())
@ -172,20 +152,57 @@ async def start_tasks_xmpp(self, jid, tasks=None):
# await task # await task
def clean_tasks_xmpp(self, jid, tasks=None): async def task_send(self, jid):
if not tasks: print("task_send for", jid)
tasks = ['interval', 'status', 'check'] try:
logging.info('Stopping tasks {} for JID {}'.format(tasks, jid)) self.task_manager[jid]['interval'].cancel()
for task in tasks: except:
# if self.task_manager[jid][task]: logging.info('No task interval for JID {} (start_tasks_xmpp)'
try: .format(jid))
self.task_manager[jid][task].cancel() jid_file = jid.replace('/', '_')
except: print(jid_file)
logging.debug('No task {} for JID {} (clean_tasks_xmpp)' db_file = config.get_pathname_to_database(jid_file)
.format(task, jid)) print(db_file)
update_interval = await config.get_setting_value(db_file, 'interval')
print(update_interval)
update_interval = 60 * int(update_interval)
print(update_interval)
last_update_time = await sqlite.get_last_update_time(db_file)
print(last_update_time)
if last_update_time:
print('if')
last_update_time = float(last_update_time)
diff = time.time() - last_update_time
if diff < update_interval:
next_update_time = update_interval - diff
print('next_update_time')
print(next_update_time)
print(next_update_time/60/60)
await asyncio.sleep(next_update_time) # FIXME!
print('after await sleep')
# print("jid :", jid, "\n"
# "time :", time.time(), "\n"
# "last_update_time :", last_update_time, "\n"
# "difference :", diff, "\n"
# "update interval :", update_interval, "\n"
# "next_update_time :", next_update_time, "\n"
# )
# elif diff > val:
# next_update_time = val
print('await (if)')
await sqlite.update_last_update_time(db_file)
else:
print('await (else)')
await sqlite.set_last_update_time(db_file)
print("await is done for", jid)
await xmpp_send_update(self, jid)
await start_tasks_xmpp(self, jid, ['status'])
await refresh_task(self, jid, task_send, 'interval')
async def send_update(self, jid, num=None): async def xmpp_send_update(self, jid, num=None):
""" """
Send news items as messages. Send news items as messages.
@ -196,21 +213,28 @@ async def send_update(self, jid, num=None):
num : str, optional num : str, optional
Number. The default is None. Number. The default is None.
""" """
logging.info('Sending a news update to JID {}'.format(jid)) print('Sending a news update to JID {}'.format(jid))
jid_file = jid.replace('/', '_') jid_file = jid.replace('/', '_')
print(jid_file)
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
print(db_file)
enabled = await config.get_setting_value(db_file, 'enabled') enabled = await config.get_setting_value(db_file, 'enabled')
print(enabled)
if enabled: if enabled:
print('enabled')
if not num: if not num:
num = await config.get_setting_value(db_file, 'quantum') num = await config.get_setting_value(db_file, 'quantum')
else: else:
num = int(num) num = int(num)
news_digest = [] print(num)
results = await sqlite.get_unread_entries(db_file, num) results = await sqlite.get_unread_entries(db_file, num)
print(results)
news_digest = '' news_digest = ''
media = None media = None
chat_type = await get_chat_type(self, jid) chat_type = await get_chat_type(self, jid)
print(jid, num, chat_type)
for result in results: for result in results:
print(result)
ix = result[0] ix = result[0]
title_e = result[1] title_e = result[1]
url = result[2] url = result[2]
@ -239,6 +263,7 @@ async def send_update(self, jid, num=None):
if media and news_digest: if media and news_digest:
print('SENDING MESSAGE (if media and news_digest)') print('SENDING MESSAGE (if media and news_digest)')
print(news_digest) print(news_digest)
print(media)
# Send textual message # Send textual message
XmppMessage.send(self, jid, news_digest, chat_type) XmppMessage.send(self, jid, news_digest, chat_type)
news_digest = '' news_digest = ''
@ -249,12 +274,13 @@ async def send_update(self, jid, num=None):
if news_digest: if news_digest:
print('SENDING MESSAGE (if news_digest)') print('SENDING MESSAGE (if news_digest)')
print(news_digest) print(news_digest)
XmppMessage.send(self, jid, news_digest, chat_type)
# TODO Add while loop to assure delivery. # TODO Add while loop to assure delivery.
# print(await current_time(), ">>> ACT send_message",jid) # print(await current_time(), ">>> ACT send_message",jid)
# NOTE Do we need "if statement"? See NOTE at is_muc. # NOTE Do we need "if statement"? See NOTE at is_muc.
if chat_type in ('chat', 'groupchat'): # if chat_type in ('chat', 'groupchat'):
# TODO Provide a choice (with or without images) # # TODO Provide a choice (with or without images)
XmppMessage.send(self, jid, news_digest, chat_type) # XmppMessage.send(self, jid, news_digest, chat_type)
# See XEP-0367 # See XEP-0367
# if media: # if media:
# # message = xmpp.Slixfeed.make_message( # # message = xmpp.Slixfeed.make_message(
@ -266,7 +292,10 @@ async def send_update(self, jid, num=None):
# TODO Do not refresh task before # TODO Do not refresh task before
# verifying that it was completed. # verifying that it was completed.
await refresh_task(self, jid, send_update, 'interval')
# await start_tasks_xmpp(self, jid, ['status'])
# await refresh_task(self, jid, send_update, 'interval')
# interval = await initdb( # interval = await initdb(
# jid, # jid,
# sqlite.get_settings_value, # sqlite.get_settings_value,
@ -292,6 +321,19 @@ async def send_update(self, jid, num=None):
# await handle_event() # await handle_event()
def clean_tasks_xmpp(self, jid, tasks=None):
if not tasks:
tasks = ['interval', 'status', 'check']
logging.info('Stopping tasks {} for JID {}'.format(tasks, jid))
for task in tasks:
# if self.task_manager[jid][task]:
try:
self.task_manager[jid][task].cancel()
except:
logging.debug('No task {} for JID {} (clean_tasks_xmpp)'
.format(task, jid))
async def send_status(self, jid): async def send_status(self, jid):
""" """
Send status message. Send status message.

View file

@ -3,6 +3,13 @@
""" """
FIXME
1) Do not handle base64
https://www.lilithsaintcrow.com/2024/02/love-anonymous/
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABaAAAAeAAQAAAAAQ6M16AAAAAnRSTlMAAHaTzTgAAAFmSURBVBgZ7cEBAQAAAIKg/q92SMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgWE3LAAGyZmPPAAAAAElFTkSuQmCC
https://www.lilithsaintcrow.com/2024/02/love-anonymous//image/png;base64,iVBORw0KGgoAAAANSUhEUgAABaAAAAeAAQAAAAAQ6M16AAAAAnRSTlMAAHaTzTgAAAFmSURBVBgZ7cEBAQAAAIKg/q92SMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgWE3LAAGyZmPPAAAAAElFTkSuQmCC
TODO TODO
1) ActivityPub URL revealer activitypub_to_http. 1) ActivityPub URL revealer activitypub_to_http.
@ -40,6 +47,7 @@ def get_hostname(url):
parted_url = urlsplit(url) parted_url = urlsplit(url)
return parted_url.netloc return parted_url.netloc
def replace_hostname(url, url_type): def replace_hostname(url, url_type):
""" """
Replace hostname. Replace hostname.
@ -120,6 +128,8 @@ def remove_tracking_parameters(url):
url : str url : str
URL. URL.
""" """
if url.startswith('data:') and ';base64,' in url:
return url
parted_url = urlsplit(url) parted_url = urlsplit(url)
protocol = parted_url.scheme protocol = parted_url.scheme
hostname = parted_url.netloc hostname = parted_url.netloc
@ -192,6 +202,8 @@ def complete_url(source, link):
str str
URL. URL.
""" """
if link.startswith('data:') and ';base64,' in link:
return link
if link.startswith('www.'): if link.startswith('www.'):
return 'http://' + link return 'http://' + link
parted_link = urlsplit(link) parted_link = urlsplit(link)
@ -270,6 +282,8 @@ def join_url(source, link):
str str
URL. URL.
""" """
if link.startswith('data:') and ';base64,' in link:
return link
if link.startswith('www.'): if link.startswith('www.'):
new_link = 'http://' + link new_link = 'http://' + link
elif link.startswith('%20') and link.endswith('%20'): elif link.startswith('%20') and link.endswith('%20'):
@ -296,6 +310,8 @@ def trim_url(url):
url : str url : str
URL. URL.
""" """
if url.startswith('data:') and ';base64,' in url:
return url
parted_url = urlsplit(url) parted_url = urlsplit(url)
protocol = parted_url.scheme protocol = parted_url.scheme
hostname = parted_url.netloc hostname = parted_url.netloc

View file

@ -1,2 +1,2 @@
__version__ = '0.1.2' __version__ = '0.1.3'
__version_info__ = (0, 1, 2) __version_info__ = (0, 1, 3)

View file

@ -108,7 +108,7 @@ class Slixfeed(slixmpp.ClientXMPP):
self.task_manager = {} self.task_manager = {}
# Handlers for ping # Handlers for ping
self.ping_task_instance = {} self.task_ping_instance = {}
# Handlers for connection events # Handlers for connection events
self.connection_attempts = 0 self.connection_attempts = 0
@ -124,8 +124,8 @@ class Slixfeed(slixmpp.ClientXMPP):
self.on_changed_status) self.on_changed_status)
self.add_event_handler("presence_available", self.add_event_handler("presence_available",
self.on_presence_available) self.on_presence_available)
self.add_event_handler("presence_unavailable", # self.add_event_handler("presence_unavailable",
self.on_presence_unavailable) # self.on_presence_unavailable)
self.add_event_handler("chatstate_active", self.add_event_handler("chatstate_active",
self.on_chatstate_active) self.on_chatstate_active)
self.add_event_handler("chatstate_composing", self.add_event_handler("chatstate_composing",
@ -218,7 +218,7 @@ class Slixfeed(slixmpp.ClientXMPP):
await XmppGroupchat.autojoin(self) await XmppGroupchat.autojoin(self)
profile.set_identity(self, 'client') profile.set_identity(self, 'client')
await profile.update(self) await profile.update(self)
task.ping_task(self) task.task_ping(self)
# Service.commands(self) # Service.commands(self)
# Service.reactions(self) # Service.reactions(self)
@ -262,6 +262,8 @@ class Slixfeed(slixmpp.ClientXMPP):
async def on_changed_status(self, presence): async def on_changed_status(self, presence):
# await task.check_readiness(self, presence) # await task.check_readiness(self, presence)
jid = presence['from'].bare jid = presence['from'].bare
if jid in self.boundjid.bare:
return
if presence['show'] in ('away', 'dnd', 'xa'): if presence['show'] in ('away', 'dnd', 'xa'):
task.clean_tasks_xmpp(self, jid, ['interval']) task.clean_tasks_xmpp(self, jid, ['interval'])
await task.start_tasks_xmpp(self, jid, ['status', 'check']) await task.start_tasks_xmpp(self, jid, ['status', 'check'])
@ -270,6 +272,7 @@ class Slixfeed(slixmpp.ClientXMPP):
async def on_presence_subscribe(self, presence): async def on_presence_subscribe(self, presence):
jid = presence['from'].bare jid = presence['from'].bare
if not self.client_roster[jid]['to']: if not self.client_roster[jid]['to']:
# XmppPresence.subscription(self, jid, 'subscribe')
XmppPresence.subscription(self, jid, 'subscribed') XmppPresence.subscription(self, jid, 'subscribed')
await XmppRoster.add(self, jid) await XmppRoster.add(self, jid)
status_message = '✒️ Share online status to receive updates' status_message = '✒️ Share online status to receive updates'
@ -280,8 +283,9 @@ class Slixfeed(slixmpp.ClientXMPP):
'chat') 'chat')
async def on_presence_subscribed(self, presence): def on_presence_subscribed(self, presence):
jid = presence['from'].bare jid = presence['from'].bare
# XmppPresence.subscription(self, jid, 'subscribed')
message_subject = 'RSS News Bot' message_subject = 'RSS News Bot'
message_body = ('Greetings! I am {}, the news anchor.\n' message_body = ('Greetings! I am {}, the news anchor.\n'
'My job is to bring you the latest ' 'My job is to bring you the latest '
@ -297,13 +301,18 @@ class Slixfeed(slixmpp.ClientXMPP):
# await task.start_tasks(self, presence) # await task.start_tasks(self, presence)
# NOTE Already done inside the start-task function # NOTE Already done inside the start-task function
jid = presence['from'].bare jid = presence['from'].bare
if jid in self.boundjid.bare:
return
print('JID available:', jid)
# FIXME TODO Find out what is the source responsible for a couple presences with empty message # FIXME TODO Find out what is the source responsible for a couple presences with empty message
# NOTE This is a temporary solution # NOTE This is a temporary solution
await asyncio.sleep(10) await asyncio.sleep(10)
await task.start_tasks_xmpp(self, jid) await task.start_tasks_xmpp(self, jid)
self.add_event_handler("presence_unavailable",
self.on_presence_unavailable)
async def on_presence_unsubscribed(self, presence): def on_presence_unsubscribed(self, presence):
jid = presence['from'].bare jid = presence['from'].bare
message_body = 'You have been unsubscribed.' message_body = 'You have been unsubscribed.'
# status_message = '🖋️ Subscribe to receive updates' # status_message = '🖋️ Subscribe to receive updates'
@ -312,64 +321,81 @@ class Slixfeed(slixmpp.ClientXMPP):
XmppPresence.subscription(self, jid, 'unsubscribed') XmppPresence.subscription(self, jid, 'unsubscribed')
# XmppPresence.send(self, jid, status_message, # XmppPresence.send(self, jid, status_message,
# presence_type='unsubscribed') # presence_type='unsubscribed')
await XmppRoster.remove(self, jid) XmppRoster.remove(self, jid)
async def on_presence_unavailable(self, presence): def on_presence_unavailable(self, presence):
jid = presence['from'].bare jid = presence['from'].bare
print('JID unavailable:', jid)
# await task.stop_tasks(self, jid) # await task.stop_tasks(self, jid)
task.clean_tasks_xmpp(self, jid) task.clean_tasks_xmpp(self, jid)
# 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,
presence_type='unavailable')
self.del_event_handler("presence_unavailable",
self.on_presence_unavailable)
# TODO # TODO
# Send message that database will be deleted within 30 days # Send message that database will be deleted within 30 days
# Check whether JID is in bookmarks or roster # Check whether JID is in bookmarks or roster
# If roster, remove contact JID into file # If roster, remove contact JID into file
# If bookmarks, remove groupchat JID into file # If bookmarks, remove groupchat JID into file
async def on_presence_error(self, presence): def on_presence_error(self, presence):
print("on_presence_error")
print(presence)
jid = presence["from"].bare jid = presence["from"].bare
print('JID error:', jid)
task.clean_tasks_xmpp(self, jid) task.clean_tasks_xmpp(self, jid)
async def on_reactions(self, message): def on_reactions(self, message):
print(message['from']) print(message['from'])
print(message['reactions']['values']) print(message['reactions']['values'])
async def on_chatstate_active(self, message): async def on_chatstate_active(self, message):
jid = message['from'].bare
if jid in self.boundjid.bare:
return
if message['type'] in ('chat', 'normal'): if message['type'] in ('chat', 'normal'):
jid = message['from'].bare
# task.clean_tasks_xmpp(self, jid, ['status']) # task.clean_tasks_xmpp(self, jid, ['status'])
await task.start_tasks_xmpp(self, jid, ['status']) await task.start_tasks_xmpp(self, jid, ['status'])
async def on_chatstate_composing(self, message): def on_chatstate_composing(self, message):
if message['type'] in ('chat', 'normal'): if message['type'] in ('chat', 'normal'):
jid = message['from'].bare jid = message['from'].bare
# task.clean_tasks_xmpp(self, jid, ['status']) # task.clean_tasks_xmpp(self, jid, ['status'])
status_message='💡 Press "help" for manual, or "info" for information.' status_message = ('💡 Send "help" for manual, or "info" for '
'information.')
XmppPresence.send(self, jid, status_message) XmppPresence.send(self, jid, status_message)
async def on_chatstate_gone(self, message): async def on_chatstate_gone(self, message):
jid = message['from'].bare
if jid in self.boundjid.bare:
return
if message['type'] in ('chat', 'normal'): if message['type'] in ('chat', 'normal'):
jid = message['from'].bare
# task.clean_tasks_xmpp(self, jid, ['status']) # task.clean_tasks_xmpp(self, jid, ['status'])
await task.start_tasks_xmpp(self, jid, ['status']) await task.start_tasks_xmpp(self, jid, ['status'])
async def on_chatstate_inactive(self, message): async def on_chatstate_inactive(self, message):
jid = message['from'].bare
if jid in self.boundjid.bare:
return
if message['type'] in ('chat', 'normal'): if message['type'] in ('chat', 'normal'):
jid = message['from'].bare
# task.clean_tasks_xmpp(self, jid, ['status']) # task.clean_tasks_xmpp(self, jid, ['status'])
await task.start_tasks_xmpp(self, jid, ['status']) await task.start_tasks_xmpp(self, jid, ['status'])
async def on_chatstate_paused(self, message): async def on_chatstate_paused(self, message):
jid = message['from'].bare
if jid in self.boundjid.bare:
return
if message['type'] in ('chat', 'normal'): if message['type'] in ('chat', 'normal'):
jid = message['from'].bare
# task.clean_tasks_xmpp(self, jid, ['status']) # task.clean_tasks_xmpp(self, jid, ['status'])
await task.start_tasks_xmpp(self, jid, ['status']) await task.start_tasks_xmpp(self, jid, ['status'])

View file

@ -13,7 +13,7 @@ TODO
import asyncio import asyncio
import logging import logging
# import os # import os
from random import randrange # from random import randrange
import slixmpp import slixmpp
import slixfeed.task as task import slixfeed.task as task
from time import sleep from time import sleep
@ -31,11 +31,11 @@ import slixfeed.sqlite as sqlite
from slixfeed.xmpp.bookmark import XmppBookmark from slixfeed.xmpp.bookmark import XmppBookmark
from slixfeed.xmpp.connect import XmppConnect from slixfeed.xmpp.connect import XmppConnect
# NOTE MUC is possible for component # NOTE MUC is possible for component
from slixfeed.xmpp.muc import XmppGroupchat # from slixfeed.xmpp.muc import XmppGroupchat
from slixfeed.xmpp.message import XmppMessage from slixfeed.xmpp.message import XmppMessage
import slixfeed.xmpp.process as process import slixfeed.xmpp.process as process
import slixfeed.xmpp.profile as profile import slixfeed.xmpp.profile as profile
from slixfeed.xmpp.roster import XmppRoster # from slixfeed.xmpp.roster import XmppRoster
# import slixfeed.xmpp.service as service # import slixfeed.xmpp.service as service
from slixfeed.xmpp.presence import XmppPresence from slixfeed.xmpp.presence import XmppPresence
from slixfeed.xmpp.utility import get_chat_type from slixfeed.xmpp.utility import get_chat_type
@ -74,7 +74,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
self.task_manager = {} self.task_manager = {}
# Handlers for ping # Handlers for ping
self.ping_task_instance = {} self.task_ping_instance = {}
# Handlers for connection events # Handlers for connection events
self.connection_attempts = 0 self.connection_attempts = 0
@ -170,7 +170,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# await XmppGroupchat.autojoin(self) # await XmppGroupchat.autojoin(self)
profile.set_identity(self, 'service') profile.set_identity(self, 'service')
await profile.update(self) await profile.update(self)
task.ping_task(self) task.task_ping(self)
# Service.commands(self) # Service.commands(self)
# Service.reactions(self) # Service.reactions(self)
@ -179,7 +179,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
self.service_reactions() self.service_reactions()
async def on_session_resumed(self, event): def on_session_resumed(self, event):
self.send_presence() self.send_presence()
self['xep_0115'].update_caps() self['xep_0115'].update_caps()
# await XmppGroupchat.autojoin(self) # await XmppGroupchat.autojoin(self)
@ -220,13 +220,13 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
await task.start_tasks_xmpp(self, jid, ['status', 'check']) await task.start_tasks_xmpp(self, jid, ['status', 'check'])
async def on_presence_subscribe(self, presence): def on_presence_subscribe(self, presence):
jid = presence['from'].bare jid = presence['from'].bare
# XmppPresence.request(self, jid) # XmppPresence.request(self, jid)
XmppPresence.subscription(self, jid, 'subscribe') XmppPresence.subscription(self, jid, 'subscribe')
async def on_presence_subscribed(self, presence): def on_presence_subscribed(self, presence):
jid = presence['from'].bare jid = presence['from'].bare
message_subject = 'RSS News Bot' message_subject = 'RSS News Bot'
message_body = ('Greetings! I am {}, the news anchor.\n' message_body = ('Greetings! I am {}, the news anchor.\n'
@ -249,7 +249,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
await task.start_tasks_xmpp(self, jid) await task.start_tasks_xmpp(self, jid)
async def on_presence_unsubscribed(self, presence): def on_presence_unsubscribed(self, presence):
jid = presence['from'].bare jid = presence['from'].bare
message_body = 'You have been unsubscribed.' message_body = 'You have been unsubscribed.'
# status_message = '🖋️ Subscribe to receive updates' # status_message = '🖋️ Subscribe to receive updates'
@ -260,7 +260,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# presence_type='unsubscribed') # presence_type='unsubscribed')
async def on_presence_unavailable(self, presence): def on_presence_unavailable(self, presence):
jid = presence['from'].bare jid = presence['from'].bare
# await task.stop_tasks(self, jid) # await task.stop_tasks(self, jid)
task.clean_tasks_xmpp(self, jid) task.clean_tasks_xmpp(self, jid)
@ -271,14 +271,14 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# Check whether JID is in bookmarks or roster # Check whether JID is in bookmarks or roster
# If roster, remove contact JID into file # If roster, remove contact JID into file
# If bookmarks, remove groupchat JID into file # If bookmarks, remove groupchat JID into file
async def on_presence_error(self, presence): def on_presence_error(self, presence):
print("on_presence_error") print("on_presence_error")
print(presence) print(presence)
jid = presence["from"].bare jid = presence["from"].bare
task.clean_tasks_xmpp(self, jid) task.clean_tasks_xmpp(self, jid)
async def on_reactions(self, message): def on_reactions(self, message):
print(message['from']) print(message['from'])
print(message['reactions']['values']) print(message['reactions']['values'])
@ -290,11 +290,12 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
await task.start_tasks_xmpp(self, jid, ['status']) await task.start_tasks_xmpp(self, jid, ['status'])
async def on_chatstate_composing(self, message): def on_chatstate_composing(self, message):
if message['type'] in ('chat', 'normal'): if message['type'] in ('chat', 'normal'):
jid = message['from'].bare jid = message['from'].bare
# task.clean_tasks_xmpp(self, jid, ['status']) # task.clean_tasks_xmpp(self, jid, ['status'])
status_message='💡 Press "help" for manual, or "info" for information.' status_message = ('💡 Send "help" for manual, or "info" for '
'information.')
XmppPresence.send(self, jid, status_message) XmppPresence.send(self, jid, status_message)

View file

@ -17,6 +17,7 @@ from slixfeed.xmpp.presence import XmppPresence
from slixfeed.xmpp.upload import XmppUpload from slixfeed.xmpp.upload import XmppUpload
from slixfeed.xmpp.utility import get_chat_type from slixfeed.xmpp.utility import get_chat_type
import time import time
import xml.sax.saxutils as saxutils
""" """
@ -24,28 +25,6 @@ NOTE
See XEP-0367: Message Attaching See XEP-0367: Message Attaching
FIXME
ERROR:asyncio:Task exception was never retrieved
future: <Task finished name='Task-3410' coro=<send_update() done, defined at /home/admin/.venv/lib/python3.11/site-packages/slixfeed/task.py:181> exception=ParseError('not well-formed (invalid token): line 1, column 198')>
Traceback (most recent call last):
File "/home/jojo/.venv/lib/python3.11/site-packages/slixfeed/task.py", line 237, in send_update
XmppMessage.send_oob(self, jid, media, chat_type)
File "/home/jojo/.venv/lib/python3.11/site-packages/slixfeed/xmpp/message.py", line 56, in send_oob
message = self.make_message(mto=jid,
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/jojo/.venv/lib/python3.11/site-packages/slixmpp/basexmpp.py", line 517, in make_message
message['html']['body'] = mhtml
~~~~~~~~~~~~~~~^^^^^^^^
File "/home/jojo/.venv/lib/python3.11/site-packages/slixmpp/xmlstream/stanzabase.py", line 792, in __setitem__
getattr(self, set_method)(value, **kwargs)
File "/home/jojo/.venv/lib/python3.11/site-packages/slixmpp/plugins/xep_0071/stanza.py", line 38, in set_body
xhtml = ET.fromstring(content)
^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/xml/etree/ElementTree.py", line 1338, in XML
parser.feed(text)
xml.etree.ElementTree.ParseError: not well-formed (invalid token): line 1, column 198
""" """
class XmppMessage: class XmppMessage:
@ -71,21 +50,29 @@ class XmppMessage:
mnick=self.alias) mnick=self.alias)
# NOTE We might want to add more characters
# def escape_to_xml(raw_string):
# escape_map = {
# '"' : '&quot;',
# "'" : '&apos;'
# }
# return saxutils.escape(raw_string, escape_map)
def send_oob(self, jid, url, chat_type): def send_oob(self, jid, url, chat_type):
try: url = saxutils.escape(url)
html = ( # try:
f'<body xmlns="http://www.w3.org/1999/xhtml">' html = (
f'<a href="{url}">{url}</a></body>') f'<body xmlns="http://www.w3.org/1999/xhtml">'
message = self.make_message(mto=jid, f'<a href="{url}">{url}</a></body>')
mfrom=self.boundjid.bare, message = self.make_message(mto=jid,
mbody=url, mfrom=self.boundjid.bare,
mhtml=html, mbody=url,
mtype=chat_type) mhtml=html,
message['oob']['url'] = url mtype=chat_type)
message.send() message['oob']['url'] = url
except: message.send()
logging.error('ERROR!') # except:
logging.error(jid, url, chat_type, html) # logging.error('ERROR!')
# logging.error(jid, url, chat_type, html)
# FIXME Solve this function # FIXME Solve this function

View file

@ -534,8 +534,7 @@ async def message(self, message):
if query: if query:
if len(query) > 3: if len(query) > 3:
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
result = await sqlite.search_feeds(db_file, query) response = action.list_feeds_by_query(db_file, query)
response = action.list_feeds_by_query(query, result)
else: else:
response = 'Enter at least 4 characters to search' response = 'Enter at least 4 characters to search'
else: else:
@ -546,7 +545,7 @@ async def message(self, message):
case 'goodbye': case 'goodbye':
if message['type'] == 'groupchat': if message['type'] == 'groupchat':
await XmppGroupchat.leave(self, jid) await XmppGroupchat.leave(self, jid)
await XmppBookmark.remove(self, muc_jid) await XmppBookmark.remove(self, jid)
else: else:
response = 'This command is valid in groupchat only.' response = 'This command is valid in groupchat only.'
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
@ -631,7 +630,7 @@ async def message(self, message):
# num = message_text[5:] # num = message_text[5:]
# await task.send_update(self, jid, num) # await task.send_update(self, jid, num)
await task.send_update(self, jid) await task.xmpp_send_update(self, jid)
# task.clean_tasks_xmpp(self, jid, ['interval', 'status']) # task.clean_tasks_xmpp(self, jid, ['interval', 'status'])
# await task.start_tasks_xmpp(self, jid, ['status', 'interval']) # await task.start_tasks_xmpp(self, jid, ['status', 'interval'])
@ -842,8 +841,11 @@ async def message(self, message):
try: try:
await sqlite.set_enabled_status(db_file, feed_id, 0) await sqlite.set_enabled_status(db_file, feed_id, 0)
await sqlite.mark_feed_as_read(db_file, feed_id) await sqlite.mark_feed_as_read(db_file, feed_id)
response = ('Updates are now disabled for news source {}.' name = sqlite.get_feed_title(db_file, feed_id)[0]
.format(feed_id)) addr = sqlite.get_feed_url(db_file, feed_id)[0]
response = ('> {}\n'
'Updates are now disabled for news source "{}"'
.format(addr, name))
except: except:
response = 'No news source with index {}.'.format(feed_id) response = 'No news source with index {}.'.format(feed_id)
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
@ -853,8 +855,11 @@ async def message(self, message):
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
try: try:
await sqlite.set_enabled_status(db_file, feed_id, 1) await sqlite.set_enabled_status(db_file, feed_id, 1)
response = ('Updates are now enabled for news source {}.' name = sqlite.get_feed_title(db_file, feed_id)[0]
.format(feed_id)) addr = sqlite.get_feed_url(db_file, feed_id)[0]
response = ('> {}\n'
'Updates are now enabled for news source "{}"'
.format(addr, name))
except: except:
response = 'No news source with index {}.'.format(ix) response = 'No news source with index {}.'.format(ix)
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)

View file

@ -12,22 +12,6 @@ TODO
class XmppRoster: class XmppRoster:
async def remove(self, jid):
"""
Remove JID to roster.
Parameters
----------
jid : str
Jabber ID.
Returns
-------
None.
"""
self.update_roster(jid, subscription="remove")
async def add(self, jid): async def add(self, jid):
""" """
Add JID to roster. Add JID to roster.
@ -45,5 +29,20 @@ class XmppRoster:
""" """
await self.get_roster() await self.get_roster()
if jid not in self.client_roster.keys(): if jid not in self.client_roster.keys():
self.update_roster(jid, subscription="both") self.update_roster(jid, subscription='both')
def remove(self, jid):
"""
Remove JID from roster.
Parameters
----------
jid : str
Jabber ID.
Returns
-------
None.
"""
self.update_roster(jid, subscription='remove')