Fix presence and subscription handling.

Segregate and atomize into classes.
This commit is contained in:
Schimon Jehudah 2024-02-07 00:26:42 +00:00
parent 422e0669f1
commit 00a8ed180a
18 changed files with 734 additions and 782 deletions

View file

@ -24,6 +24,7 @@ TODO
""" """
import asyncio
from asyncio.exceptions import IncompleteReadError from asyncio.exceptions import IncompleteReadError
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from feedparser import parse from feedparser import parse
@ -34,13 +35,11 @@ from lxml import html
import os import os
import slixfeed.config as config import slixfeed.config as config
import slixfeed.crawl as crawl import slixfeed.crawl as crawl
from slixfeed.dt import (
current_date, current_time, now, import slixfeed.dt as dt
convert_struct_time_to_iso8601,
rfc2822_to_iso8601
)
import slixfeed.fetch as fetch import slixfeed.fetch as fetch
import slixfeed.sqlite as sqlite import slixfeed.sqlite as sqlite
import slixfeed.url as uri
from slixfeed.url import ( from slixfeed.url import (
complete_url, complete_url,
join_url, join_url,
@ -51,7 +50,9 @@ from slixfeed.url import (
import slixfeed.task as task import slixfeed.task as task
from slixfeed.xmpp.bookmark import XmppBookmark from slixfeed.xmpp.bookmark import XmppBookmark
from slixfeed.xmpp.message import XmppMessage from slixfeed.xmpp.message import XmppMessage
from slixfeed.xmpp.status import XmppStatus from slixfeed.xmpp.presence import XmppPresence
from slixfeed.xmpp.upload import XmppUpload
from slixfeed.xmpp.utility import get_chat_type
import tomllib import tomllib
from urllib import error from urllib import error
from urllib.parse import parse_qs, urlsplit from urllib.parse import parse_qs, urlsplit
@ -122,8 +123,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, await task.refresh_task(self, jid, task.send_update, key, val)
key, val)
response = ('Updates will be sent every {} minutes.' response = ('Updates will be sent every {} minutes.'
.format(val)) .format(val))
else: else:
@ -131,7 +131,24 @@ async def xmpp_change_interval(self, key, val, jid, jid_file, message=None, sess
if message: if message:
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
if session: if session:
await XmppMessage.send(self, jid, response, chat_type='chat') XmppMessage.send(self, jid, response, chat_type='chat')
async def xmpp_start_updates(self, message, jid, jid_file):
key = 'enabled'
val = 1
db_file = config.get_pathname_to_database(jid_file)
if await sqlite.get_settings_value(db_file, key):
await sqlite.update_settings_value(db_file, [key, val])
else:
await sqlite.set_settings_value(db_file, [key, val])
status_type = 'available'
status_message = '💡️ Welcome back!'
XmppPresence.send(self, jid, status_message, status_type=status_type)
message_body = 'Updates are enabled.'
XmppMessage.send_reply(self, message, message_body)
await asyncio.sleep(5)
await task.start_tasks_xmpp(self, jid, ['check', 'status', 'interval'])
async def xmpp_stop_updates(self, message, jid, jid_file): async def xmpp_stop_updates(self, message, jid, jid_file):
@ -143,11 +160,12 @@ async def xmpp_stop_updates(self, message, jid, jid_file):
else: else:
await sqlite.set_settings_value(db_file, [key, val]) await sqlite.set_settings_value(db_file, [key, val])
await task.clean_tasks_xmpp(jid, ['interval', 'status']) await task.clean_tasks_xmpp(jid, ['interval', 'status'])
response = 'Updates are disabled.' message_body = 'Updates are disabled.'
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, message_body)
status_type = 'xa' status_type = 'xa'
status_message = '💡️ Send "Start" to receive Jabber updates' status_message = '💡️ Send "Start" to receive Jabber updates'
await XmppStatus.send(self, jid, status_message, status_type) XmppPresence.send(self, jid, status_message, status_type=status_type)
def log_to_markdown(timestamp, filename, jid, message): def log_to_markdown(timestamp, filename, jid, message):
""" """
@ -473,7 +491,7 @@ def export_to_markdown(jid, filename, results):
file.write( file.write(
'\n\n* * *\n\nThis list was saved on {} from xmpp:{} using ' '\n\n* * *\n\nThis list was saved on {} from xmpp:{} using '
'[Slixfeed](https://gitgud.io/sjehuda/slixfeed)\n'.format( '[Slixfeed](https://gitgud.io/sjehuda/slixfeed)\n'.format(
current_date(), jid)) dt.current_date(), jid))
# TODO Consider adding element jid as a pointer of import # TODO Consider adding element jid as a pointer of import
@ -487,7 +505,7 @@ def export_to_opml(jid, filename, results):
ET.SubElement(head, "generator").text = "Slixfeed" ET.SubElement(head, "generator").text = "Slixfeed"
ET.SubElement(head, "urlPublic").text = ( ET.SubElement(head, "urlPublic").text = (
"https://gitgud.io/sjehuda/slixfeed") "https://gitgud.io/sjehuda/slixfeed")
time_stamp = current_time() time_stamp = dt.current_time()
ET.SubElement(head, "dateCreated").text = time_stamp ET.SubElement(head, "dateCreated").text = time_stamp
ET.SubElement(head, "dateModified").text = time_stamp ET.SubElement(head, "dateModified").text = time_stamp
body = ET.SubElement(root, "body") body = ET.SubElement(root, "body")
@ -548,7 +566,7 @@ async def add_feed(db_file, url):
if "updated_parsed" in feed["feed"].keys(): if "updated_parsed" in feed["feed"].keys():
updated = feed["feed"]["updated_parsed"] updated = feed["feed"]["updated_parsed"]
try: try:
updated = convert_struct_time_to_iso8601(updated) updated = dt.convert_struct_time_to_iso8601(updated)
except: except:
updated = '' updated = ''
else: else:
@ -594,7 +612,7 @@ async def add_feed(db_file, url):
if "date_published" in feed.keys(): if "date_published" in feed.keys():
updated = feed["date_published"] updated = feed["date_published"]
try: try:
updated = convert_struct_time_to_iso8601(updated) updated = dt.convert_struct_time_to_iso8601(updated)
except: except:
updated = '' updated = ''
else: else:
@ -676,7 +694,7 @@ async def scan_json(db_file, url):
if "date_published" in feed.keys(): if "date_published" in feed.keys():
updated = feed["date_published"] updated = feed["date_published"]
try: try:
updated = convert_struct_time_to_iso8601(updated) updated = dt.convert_struct_time_to_iso8601(updated)
except: except:
updated = '' updated = ''
else: else:
@ -697,12 +715,12 @@ async def scan_json(db_file, url):
for entry in entries: for entry in entries:
if "date_published" in entry.keys(): if "date_published" in entry.keys():
date = entry["date_published"] date = entry["date_published"]
date = rfc2822_to_iso8601(date) date = dt.rfc2822_to_iso8601(date)
elif "date_modified" in entry.keys(): elif "date_modified" in entry.keys():
date = entry["date_modified"] date = entry["date_modified"]
date = rfc2822_to_iso8601(date) date = dt.rfc2822_to_iso8601(date)
else: else:
date = now() date = dt.now()
if "url" in entry.keys(): if "url" in entry.keys():
# link = complete_url(source, entry.link) # link = complete_url(source, entry.link)
link = join_url(url, entry["url"]) link = join_url(url, entry["url"])
@ -820,10 +838,10 @@ async def view_feed(url):
link = "*** No link ***" link = "*** No link ***"
if entry.has_key("published"): if entry.has_key("published"):
date = entry.published date = entry.published
date = rfc2822_to_iso8601(date) date = dt.rfc2822_to_iso8601(date)
elif entry.has_key("updated"): elif entry.has_key("updated"):
date = entry.updated date = entry.updated
date = rfc2822_to_iso8601(date) date = dt.rfc2822_to_iso8601(date)
else: else:
date = "*** No date ***" date = "*** No date ***"
response += ( response += (
@ -877,10 +895,10 @@ async def view_entry(url, num):
title = "*** No title ***" title = "*** No title ***"
if entry.has_key("published"): if entry.has_key("published"):
date = entry.published date = entry.published
date = rfc2822_to_iso8601(date) date = dt.rfc2822_to_iso8601(date)
elif entry.has_key("updated"): elif entry.has_key("updated"):
date = entry.updated date = entry.updated
date = rfc2822_to_iso8601(date) date = dt.rfc2822_to_iso8601(date)
else: else:
date = "*** No date ***" date = "*** No date ***"
if entry.has_key("summary"): if entry.has_key("summary"):
@ -965,7 +983,7 @@ async def scan(db_file, url):
if "updated_parsed" in feed["feed"].keys(): if "updated_parsed" in feed["feed"].keys():
updated = feed["feed"]["updated_parsed"] updated = feed["feed"]["updated_parsed"]
try: try:
updated = convert_struct_time_to_iso8601(updated) updated = dt.convert_struct_time_to_iso8601(updated)
except: except:
updated = '' updated = ''
else: else:
@ -986,12 +1004,12 @@ async def scan(db_file, url):
for entry in entries: for entry in entries:
if entry.has_key("published"): if entry.has_key("published"):
date = entry.published date = entry.published
date = rfc2822_to_iso8601(date) date = dt.rfc2822_to_iso8601(date)
elif entry.has_key("updated"): elif entry.has_key("updated"):
date = entry.updated date = entry.updated
date = rfc2822_to_iso8601(date) date = dt.rfc2822_to_iso8601(date)
else: else:
date = now() date = dt.now()
if entry.has_key("link"): if entry.has_key("link"):
# link = complete_url(source, entry.link) # link = complete_url(source, entry.link)
link = join_url(url, entry.link) link = join_url(url, entry.link)
@ -1073,6 +1091,89 @@ async def scan(db_file, url):
db_file, feed_id, new_entries) db_file, feed_id, new_entries)
async def download_document(self, message, jid, jid_file, message_text, ix_url,
readability):
ext = ' '.join(message_text.split(' ')[1:])
ext = ext if ext else 'pdf'
url = None
error = None
response = None
if ext in ('epub', 'html', 'markdown',
'md', 'pdf', 'text', 'txt'):
match ext:
case 'markdown':
ext = 'md'
case 'text':
ext = 'txt'
status_type = 'dnd'
status_message = ('📃️ Procesing request to produce {} document...'
.format(ext.upper()))
XmppPresence.send(self, jid, status_message,
status_type=status_type)
db_file = config.get_pathname_to_database(jid_file)
cache_dir = config.get_default_cache_directory()
if ix_url:
if not os.path.isdir(cache_dir):
os.mkdir(cache_dir)
if not os.path.isdir(cache_dir + '/readability'):
os.mkdir(cache_dir + '/readability')
try:
ix = int(ix_url)
try:
url = sqlite.get_entry_url(db_file, ix)
except:
response = 'No entry with index {}'.format(ix)
except:
url = ix_url
if url:
logging.info('Original URL: {}'
.format(url))
url = uri.remove_tracking_parameters(url)
logging.info('Processed URL (tracker removal): {}'
.format(url))
url = (uri.replace_hostname(url, 'link')) or url
logging.info('Processed URL (replace hostname): {}'
.format(url))
result = await fetch.http(url)
data = result[0]
code = result[1]
if data:
title = get_document_title(data)
title = title.strip().lower()
for i in (' ', '-'):
title = title.replace(i, '_')
for i in ('?', '"', '\'', '!'):
title = title.replace(i, '')
filename = os.path.join(
cache_dir, 'readability',
title + '_' + dt.timestamp() + '.' + ext)
error = generate_document(data, url, ext, filename,
readability)
if error:
response = ('> {}\n'
'Failed to export {}. Reason: {}'
.format(url, ext.upper(), error))
else:
url = await XmppUpload.start(self, jid,
filename)
chat_type = await get_chat_type(self, jid)
XmppMessage.send_oob(self, jid, url, chat_type)
else:
response = ('> {}\n'
'Failed to fetch URL. Reason: {}'
.format(url, code))
await task.start_tasks_xmpp(self, jid, ['status'])
else:
response = 'Missing entry index number.'
else:
response = ('Unsupported filetype.\n'
'Try: epub, html, md (markdown), '
'pdf, or txt (text)')
if response:
logging.warning('Error for URL {}: {}'.format(url, error))
XmppMessage.send_reply(self, message, response)
def get_document_title(data): def get_document_title(data):
try: try:
document = Document(data) document = Document(data)
@ -1083,14 +1184,17 @@ def get_document_title(data):
return title return title
def generate_document(data, url, ext, filename): def generate_document(data, url, ext, filename, readability=False):
error = None error = None
try: if readability:
document = Document(data) try:
content = document.summary() document = Document(data)
except: content = document.summary()
except:
content = data
logging.warning('Check that package readability is installed.')
else:
content = data content = data
logging.warning('Check that package readability is installed.')
match ext: match ext:
case "epub": case "epub":
error = generate_epub(content, filename) error = generate_epub(content, filename)
@ -1319,7 +1423,7 @@ async def remove_nonexistent_entries(db_file, url, feed):
# print("compare11:", title, link, time) # print("compare11:", title, link, time)
# print("compare22:", entry_title, entry_link, timestamp) # print("compare22:", entry_title, entry_link, timestamp)
# print("============") # print("============")
time = rfc2822_to_iso8601(entry.published) time = dt.rfc2822_to_iso8601(entry.published)
if (entry_title == title and if (entry_title == title and
entry_link == link and entry_link == link and
timestamp == time): timestamp == time):
@ -1423,7 +1527,7 @@ async def remove_nonexistent_entries_json(db_file, url, feed):
link = url link = url
# "date_published" "date_modified" # "date_published" "date_modified"
if entry.has_key("date_published") and timestamp: if entry.has_key("date_published") and timestamp:
time = rfc2822_to_iso8601(entry["date_published"]) time = dt.rfc2822_to_iso8601(entry["date_published"])
if (entry_title == title and if (entry_title == title and
entry_link == link and entry_link == link and
timestamp == time): timestamp == time):

View file

@ -52,9 +52,14 @@ Send all items of newly added feeds.
""" """
[document] [document]
get = """ content = """
get <id> <type> content <id>/<url> <type>
Send an article as file. Specify <id> and <type>. Send a readability (arc90) version of an article as file. Specify <id> or <url> and <type>.
Supported types are ePUB, HTML, MD and PDF (default).
"""
page = """
page <id>/<url> <type>
Send an article as file. Specify <id> or <url> and <type>.
Supported types are ePUB, HTML, MD and PDF (default). Supported types are ePUB, HTML, MD and PDF (default).
""" """

View file

@ -116,6 +116,7 @@ Kevin Smith <isode.com> (Swift IM, Wales), \
Luis Henrique Mello (SalixOS, Brazil), \ Luis Henrique Mello (SalixOS, Brazil), \
magicfelix, \ magicfelix, \
Markus Muttilainen (SalixOS), \ Markus Muttilainen (SalixOS), \
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 (United Kingdom, SalixOS), \

View file

@ -47,9 +47,10 @@ import slixfeed.config as config
# from slixfeed.dt import current_time # from slixfeed.dt import current_time
import slixfeed.sqlite as sqlite import slixfeed.sqlite as sqlite
# from xmpp import Slixfeed # from xmpp import Slixfeed
import slixfeed.xmpp.client as xmpp from slixfeed.xmpp.presence import XmppPresence
import slixfeed.xmpp.connect as connect from slixfeed.xmpp.message import XmppMessage
import slixfeed.xmpp.utility as utility from slixfeed.xmpp.connect import XmppConnect
from slixfeed.xmpp.utility import get_chat_type
import time import time
main_task = [] main_task = []
@ -70,12 +71,12 @@ loop = asyncio.get_event_loop()
def ping_task(self): def ping_task(self):
global ping_task global ping_task_instance
try: try:
ping_task.cancel() ping_task_instance.cancel()
except: except:
logging.info('No ping task to cancel.') logging.info('No ping task to cancel.')
ping_task = asyncio.create_task(connect.ping(self)) ping_task_instance = asyncio.create_task(XmppConnect.ping(self))
""" """
@ -102,7 +103,7 @@ async def start_tasks_xmpp(self, jid, tasks=None):
logging.debug('KeyError:', str(e)) logging.debug('KeyError:', str(e))
logging.info('Creating new task manager for JID {}'.format(jid)) logging.info('Creating new task manager for JID {}'.format(jid))
if not tasks: if not tasks:
tasks = ['interval', 'status', 'check'] tasks = ['status', 'check', 'interval']
logging.info('Stopping tasks {} for JID {}'.format(tasks, jid)) logging.info('Stopping tasks {} for JID {}'.format(tasks, jid))
for task in tasks: for task in tasks:
# if task_manager[jid][task]: # if task_manager[jid][task]:
@ -201,7 +202,7 @@ async def send_update(self, jid, num=None):
results = await sqlite.get_unread_entries(db_file, num) results = await sqlite.get_unread_entries(db_file, num)
news_digest = '' news_digest = ''
media = None media = None
chat_type = await utility.get_chat_type(self, jid) chat_type = await get_chat_type(self, jid)
for result in results: for result in results:
ix = result[0] ix = result[0]
title_e = result[1] title_e = result[1]
@ -230,24 +231,10 @@ async def send_update(self, jid, num=None):
if media and news_digest: if media and news_digest:
# Send textual message # Send textual message
xmpp.Slixfeed.send_message( XmppMessage.send(self, jid, news_digest, chat_type)
self,
mto=jid,
mfrom=self.boundjid.bare,
mbody=news_digest,
mtype=chat_type
)
news_digest = '' news_digest = ''
# Send media # Send media
message = xmpp.Slixfeed.make_message( XmppMessage.send_oob(self, jid, media, chat_type)
self,
mto=jid,
mfrom=self.boundjid.bare,
mbody=media,
mtype=chat_type
)
message['oob']['url'] = media
message.send()
media = None media = None
if news_digest: if news_digest:
@ -256,13 +243,8 @@ async def send_update(self, jid, num=None):
# 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)
xmpp.Slixfeed.send_message( XmppMessage.send(self, jid, news_digest, chat_type)
self, # See XEP-0367
mto=jid,
mfrom=self.boundjid.bare,
mbody=news_digest,
mtype=chat_type
)
# if media: # if media:
# # message = xmpp.Slixfeed.make_message( # # message = xmpp.Slixfeed.make_message(
# # self, mto=jid, mbody=new, mtype=chat_type) # # self, mto=jid, mbody=new, mtype=chat_type)
@ -340,13 +322,7 @@ async def send_status(self, jid):
# breakpoint() # breakpoint()
# print(await current_time(), status_text, "for", jid) # print(await current_time(), status_text, "for", jid)
xmpp.Slixfeed.send_presence( XmppPresence.send(self, jid, status_text, status_type=status_mode)
self,
pto=jid,
pfrom=self.boundjid.bare,
pshow=status_mode,
pstatus=status_text
)
# await asyncio.sleep(60 * 20) # await asyncio.sleep(60 * 20)
await refresh_task(self, jid, send_status, 'status', '90') await refresh_task(self, jid, send_status, 'status', '90')
# loop.call_at( # loop.call_at(

View file

@ -77,13 +77,8 @@ def replace_hostname(url, url_type):
parted_proxy_url = urlsplit(proxy_url) parted_proxy_url = urlsplit(proxy_url)
protocol_new = parted_proxy_url.scheme protocol_new = parted_proxy_url.scheme
hostname_new = parted_proxy_url.netloc hostname_new = parted_proxy_url.netloc
url_new = urlunsplit([ url_new = urlunsplit([protocol_new, hostname_new,
protocol_new, pathname, queries, fragment])
hostname_new,
pathname,
queries,
fragment
])
response = fetch.http_response(url_new) response = fetch.http_response(url_new)
if (response and if (response and
response.status_code == 200 and response.status_code == 200 and
@ -96,8 +91,11 @@ def replace_hostname(url, url_type):
proxies_file = config_dir + '/proxies.toml' proxies_file = config_dir + '/proxies.toml'
if not os.path.isfile(proxies_obsolete_file): if not os.path.isfile(proxies_obsolete_file):
config.create_skeleton(proxies_file) config.create_skeleton(proxies_file)
config.backup_obsolete(proxies_obsolete_file, proxy_name, proxy_type, proxy_url) config.backup_obsolete(proxies_obsolete_file,
config.update_proxies(proxies_file, proxy_name, proxy_type, proxy_url) proxy_name, proxy_type,
proxy_url)
config.update_proxies(proxies_file, proxy_name,
proxy_type, proxy_url)
url_new = None url_new = None
else: else:
logging.warning( logging.warning(
@ -132,13 +130,7 @@ def remove_tracking_parameters(url):
for tracker in trackers: for tracker in trackers:
if tracker in queries: del queries[tracker] if tracker in queries: del queries[tracker]
queries_new = urlencode(queries, doseq=True) queries_new = urlencode(queries, doseq=True)
url = urlunsplit([ url = urlunsplit([protocol, hostname, pathname, queries_new, fragment])
protocol,
hostname,
pathname,
queries_new,
fragment
])
return url return url
@ -157,13 +149,8 @@ def feed_to_http(url):
URL. URL.
""" """
par_url = urlsplit(url) par_url = urlsplit(url)
new_url = urlunsplit([ new_url = urlunsplit(['http', par_url.netloc, par_url.path, par_url.query,
'http', par_url.fragment])
par_url.netloc,
par_url.path,
par_url.query,
par_url.fragment
])
return new_url return new_url
@ -215,21 +202,13 @@ def complete_url(source, link):
return link return link
if link.startswith('//'): if link.startswith('//'):
if parted_link.netloc and parted_link.path: if parted_link.netloc and parted_link.path:
new_link = urlunsplit([ new_link = urlunsplit([parted_feed.scheme, parted_link.netloc,
parted_feed.scheme, parted_link.path, parted_link.query,
parted_link.netloc, parted_link.fragment])
parted_link.path,
parted_link.query,
parted_link.fragment
])
elif link.startswith('/'): elif link.startswith('/'):
new_link = urlunsplit([ new_link = urlunsplit([parted_feed.scheme, parted_feed.netloc,
parted_feed.scheme, parted_link.path, parted_link.query,
parted_feed.netloc, parted_link.fragment])
parted_link.path,
parted_link.query,
parted_link.fragment
])
elif link.startswith('../'): elif link.startswith('../'):
pathlink = parted_link.path.split('/') pathlink = parted_link.path.split('/')
pathfeed = parted_feed.path.split('/') pathfeed = parted_feed.path.split('/')
@ -246,13 +225,9 @@ def complete_url(source, link):
break break
pathlink = '/'.join(pathlink) pathlink = '/'.join(pathlink)
pathfeed.extend([pathlink]) pathfeed.extend([pathlink])
new_link = urlunsplit([ new_link = urlunsplit([parted_feed.scheme, parted_feed.netloc,
parted_feed.scheme, '/'.join(pathfeed), parted_link.query,
parted_feed.netloc, parted_link.fragment])
'/'.join(pathfeed),
parted_link.query,
parted_link.fragment
])
else: else:
pathlink = parted_link.path.split('/') pathlink = parted_link.path.split('/')
pathfeed = parted_feed.path.split('/') pathfeed = parted_feed.path.split('/')
@ -262,13 +237,9 @@ def complete_url(source, link):
pathfeed.pop() pathfeed.pop()
pathlink = '/'.join(pathlink) pathlink = '/'.join(pathlink)
pathfeed.extend([pathlink]) pathfeed.extend([pathlink])
new_link = urlunsplit([ new_link = urlunsplit([parted_feed.scheme, parted_feed.netloc,
parted_feed.scheme, '/'.join(pathfeed), parted_link.query,
parted_feed.netloc, parted_link.fragment])
'/'.join(pathfeed),
parted_link.query,
parted_link.fragment
])
return new_link return new_link
@ -333,13 +304,7 @@ def trim_url(url):
fragment = parted_url.fragment fragment = parted_url.fragment
while '//' in pathname: while '//' in pathname:
pathname = pathname.replace('//', '/') pathname = pathname.replace('//', '/')
url = urlunsplit([ url = urlunsplit([protocol, hostname, pathname, queries, fragment])
protocol,
hostname,
pathname,
queries,
fragment
])
return url return url

View file

@ -34,11 +34,9 @@ class XmppBookmark:
bookmarks = Bookmarks() bookmarks = Bookmarks()
mucs.extend([muc_jid]) mucs.extend([muc_jid])
for muc in mucs: for muc in mucs:
bookmarks.add_conference( bookmarks.add_conference(muc,
muc, self.alias,
self.alias, autojoin=True)
autojoin=True
)
await self.plugin['xep_0048'].set_bookmarks(bookmarks) await self.plugin['xep_0048'].set_bookmarks(bookmarks)
# bookmarks = Bookmarks() # bookmarks = Bookmarks()
# await self.plugin['xep_0048'].set_bookmarks(bookmarks) # await self.plugin['xep_0048'].set_bookmarks(bookmarks)
@ -61,9 +59,7 @@ class XmppBookmark:
bookmarks = Bookmarks() bookmarks = Bookmarks()
mucs.remove(muc_jid) mucs.remove(muc_jid)
for muc in mucs: for muc in mucs:
bookmarks.add_conference( bookmarks.add_conference(muc,
muc, self.alias,
self.alias, autojoin=True)
autojoin=True
)
await self.plugin['xep_0048'].set_bookmarks(bookmarks) await self.plugin['xep_0048'].set_bookmarks(bookmarks)

View file

@ -63,15 +63,15 @@ from slixmpp.plugins.xep_0048.stanza import Bookmarks
import slixfeed.config as config import slixfeed.config as config
import slixfeed.sqlite as sqlite import slixfeed.sqlite as sqlite
from slixfeed.xmpp.bookmark import XmppBookmark from slixfeed.xmpp.bookmark import XmppBookmark
import slixfeed.xmpp.connect as connect from slixfeed.xmpp.connect import XmppConnect
import slixfeed.xmpp.muc as muc from slixfeed.xmpp.message import XmppMessage
from slixfeed.xmpp.muc import XmppGroupchat
import slixfeed.xmpp.process as process import slixfeed.xmpp.process as process
import slixfeed.xmpp.profile as profile import slixfeed.xmpp.profile as profile
import slixfeed.xmpp.roster as roster from slixfeed.xmpp.roster import XmppRoster
# import slixfeed.xmpp.service as service # import slixfeed.xmpp.service as service
import slixfeed.xmpp.state as state from slixfeed.xmpp.presence import XmppPresence
import slixfeed.xmpp.status as status from slixfeed.xmpp.utility import get_chat_type
import slixfeed.xmpp.utility as utility
main_task = [] main_task = []
jid_tasker = {} jid_tasker = {}
@ -146,8 +146,8 @@ class Slixfeed(slixmpp.ClientXMPP):
self.add_event_handler("reactions", self.add_event_handler("reactions",
self.on_reactions) self.on_reactions)
self.add_event_handler("presence_error", # self.add_event_handler("presence_error",
self.on_presence_error) # self.on_presence_error)
self.add_event_handler("presence_subscribe", self.add_event_handler("presence_subscribe",
self.on_presence_subscribe) self.on_presence_subscribe)
self.add_event_handler("presence_subscribed", self.add_event_handler("presence_subscribed",
@ -161,43 +161,59 @@ class Slixfeed(slixmpp.ClientXMPP):
# handlers for connection events # handlers for connection events
self.connection_attempts = 0 self.connection_attempts = 0
self.max_connection_attempts = 10 self.max_connection_attempts = 10
self.add_event_handler("connection_failed", self.on_connection_failed) self.add_event_handler('connection_failed',
self.add_event_handler("session_end", self.on_session_end) self.on_connection_failed)
self.add_event_handler('session_end',
self.on_session_end)
# TODO Test # TODO Test
async def on_groupchat_invite(self, message): async def on_groupchat_invite(self, message):
logging.warning("on_groupchat_invite") logging.warning("on_groupchat_invite")
inviter = message["from"].bare inviter = message['from'].bare
muc_jid = message['groupchat_invite']['jid'] muc_jid = message['groupchat_invite']['jid']
await muc.join(self, inviter, muc_jid)
await XmppBookmark.add(self, muc_jid) await XmppBookmark.add(self, muc_jid)
await XmppGroupchat.join(self, inviter, muc_jid)
message_body = ('Greetings!\n'
'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(self, muc_jid, message_body, 'groupchat')
# NOTE Tested with Gajim and Psi # NOTE Tested with Gajim and Psi
async def on_groupchat_direct_invite(self, message): async def on_groupchat_direct_invite(self, message):
inviter = message["from"].bare inviter = message['from'].bare
muc_jid = message['groupchat_invite']['jid'] muc_jid = message['groupchat_invite']['jid']
await muc.join(self, inviter, muc_jid)
await XmppBookmark.add(self, muc_jid) await XmppBookmark.add(self, muc_jid)
await XmppGroupchat.join(self, inviter, muc_jid)
message_body = ('Greetings!\n'
'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(self, muc_jid, message_body, 'groupchat')
async def on_session_end(self, event): async def on_session_end(self, event):
message = "Session has ended." message = 'Session has ended.'
await connect.recover_connection(self, message) await XmppConnect.recover(self, message)
async def on_connection_failed(self, event): async def on_connection_failed(self, event):
message = "Connection has failed. Reason: {}".format(event) message = 'Connection has failed. Reason: {}'.format(event)
await connect.recover_connection(self, message) await XmppConnect.recover(self, message)
async def on_session_start(self, event): async def on_session_start(self, event):
self.send_presence() self.send_presence()
await self["xep_0115"].update_caps() await self['xep_0115'].update_caps()
await self.get_roster() await self.get_roster()
await muc.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.ping_task(self)
@ -210,9 +226,9 @@ class Slixfeed(slixmpp.ClientXMPP):
async def on_session_resumed(self, event): async def on_session_resumed(self, event):
self.send_presence() self.send_presence()
self["xep_0115"].update_caps() self['xep_0115'].update_caps()
await muc.autojoin(self) await XmppGroupchat.autojoin(self)
profile.set_identity(self, "client") profile.set_identity(self, 'client')
# Service.commands(self) # Service.commands(self)
# Service.reactions(self) # Service.reactions(self)
@ -224,9 +240,16 @@ class Slixfeed(slixmpp.ClientXMPP):
# TODO Request for subscription # TODO Request for subscription
async def on_message(self, message): async def on_message(self, message):
jid = message["from"].bare jid = message["from"].bare
if "chat" == await utility.get_chat_type(self, jid): if (await get_chat_type(self, jid) == 'chat' and
await roster.add(self, jid) not self.client_roster[jid]['to']):
await state.request(self, jid) XmppPresence.subscribe(self, jid)
await XmppRoster.add(self, jid)
status_message = '✒️ Share online status to receive updates'
XmppPresence.send(self, jid, 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')
await process.message(self, message) await process.message(self, message)
# chat_type = message["type"] # chat_type = message["type"]
# message_body = message["body"] # message_body = message["body"]
@ -242,31 +265,54 @@ 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
await state.request(self, jid) if not self.client_roster[jid]['to']:
XmppPresence.subscribe(self, jid)
await XmppRoster.add(self, jid)
status_message = '✒️ Share online status to receive updates'
XmppPresence.send(self, jid, 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')
async def on_presence_subscribed(self, presence): async def on_presence_subscribed(self, presence):
jid = presence["from"].bare jid = presence['from'].bare
process.greet(self, jid) message_subject = 'RSS News Bot'
message_body = ('Greetings!\n'
'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')
async def on_presence_available(self, presence): async def on_presence_available(self, presence):
# TODO Add function to check whether task is already running or not # TODO Add function to check whether task is already running or not
# 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
# 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)
async def on_presence_unsubscribed(self, presence): async def on_presence_unsubscribed(self, presence):
await state.unsubscribed(self, presence) jid = presence['from'].bare
jid = presence["from"].bare message_body = 'You have been unsubscribed.'
await roster.remove(self, jid) status_message = '🖋️ Subscribe to receive updates'
XmppMessage.send(self, jid, message_body, 'chat')
XmppPresence.send(self, jid, status_message,
presence_type='unsubscribed')
await XmppRoster.remove(self, jid)
async def on_presence_unavailable(self, presence): async 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)
await task.clean_tasks_xmpp(jid) await task.clean_tasks_xmpp(jid)
@ -299,8 +345,8 @@ class Slixfeed(slixmpp.ClientXMPP):
if message['type'] in ('chat', 'normal'): if message['type'] in ('chat', 'normal'):
jid = message['from'].bare jid = message['from'].bare
# await task.clean_tasks_xmpp(jid, ['status']) # await task.clean_tasks_xmpp(jid, ['status'])
status_text='Press "help" for manual, or "info" for information.' status_message='Press "help" for manual, or "info" for information.'
status.send(self, jid, status_text) XmppPresence.send(self, jid, status_message)
async def on_chatstate_gone(self, message): async def on_chatstate_gone(self, message):
@ -324,6 +370,25 @@ class Slixfeed(slixmpp.ClientXMPP):
await task.start_tasks_xmpp(self, jid, ['status']) await task.start_tasks_xmpp(self, jid, ['status'])
# NOTE Failed attempt
# Need to use Super or Inheritance or both
# self['xep_0050'].add_command(node='settings',
# name='Settings',
# handler=self._handle_settings)
# self['xep_0050'].add_command(node='subscriptions',
# name='Subscriptions',
# handler=self._handle_subscriptions)
# async def _handle_settings(self, iq, session):
# await XmppCommand._handle_settings(self, iq, session)
# async def _handle_subscriptions(self, iq, session):
# await XmppCommand._handle_subscriptions(self, iq, session)
# TODO Move class Service to a separate file # TODO Move class Service to a separate file
# class Service(Slixfeed): # class Service(Slixfeed):
# def __init__(self): # def __init__(self):
@ -534,7 +599,8 @@ class Slixfeed(slixmpp.ClientXMPP):
value = True value = True
form.add_field(var='old', form.add_field(var='old',
ftype='boolean', ftype='boolean',
desc='Mark items of newly added subscriptions as read.', desc='Do not mark items of newly added subscriptions '
'as read.',
# label='Send only new items', # label='Send only new items',
label='Include old news', label='Include old news',
value=value) value=value)

View file

@ -66,13 +66,16 @@ from slixfeed.xmpp.bookmark import XmppBookmark
import slixfeed.xmpp.connect as connect import slixfeed.xmpp.connect as connect
# NOTE MUC is possible for component # NOTE MUC is possible for component
# import slixfeed.xmpp.muc as muc # import slixfeed.xmpp.muc as muc
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
# import slixfeed.xmpp.roster as roster # import slixfeed.xmpp.roster as roster
# import slixfeed.xmpp.service as service # import slixfeed.xmpp.service as service
import slixfeed.xmpp.state as state from slixfeed.xmpp.presence import XmppPresence
import slixfeed.xmpp.status as status from slixfeed.xmpp.utility import XmppUtility
import slixfeed.xmpp.utility as utility from slixmpp.xmlstream import ET
# from slixmpp.xmlstream.handler import Callback
# from slixmpp.xmlstream.matcher import MatchXPath
main_task = [] main_task = []
jid_tasker = {} jid_tasker = {}
@ -154,13 +157,15 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# handlers for connection events # handlers for connection events
self.connection_attempts = 0 self.connection_attempts = 0
self.max_connection_attempts = 10 self.max_connection_attempts = 10
self.add_event_handler("connection_failed", self.on_connection_failed) self.add_event_handler('connection_failed',
self.add_event_handler("session_end", self.on_session_end) self.on_connection_failed)
self.add_event_handler('session_end',
self.on_session_end)
# async def on_groupchat_invite(self, message): # async def on_groupchat_invite(self, message):
# logging.warning("on_groupchat_invite") # logging.warning("on_groupchat_invite")
# inviter = message["from"].bare # inviter = message['from'].bare
# muc_jid = message['groupchat_invite']['jid'] # muc_jid = message['groupchat_invite']['jid']
# await muc.join(self, inviter, muc_jid) # await muc.join(self, inviter, muc_jid)
# await bookmark.add(self, muc_jid) # await bookmark.add(self, muc_jid)
@ -168,27 +173,27 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# NOTE Tested with Gajim and Psi # NOTE Tested with Gajim and Psi
# async def on_groupchat_direct_invite(self, message): # async def on_groupchat_direct_invite(self, message):
# inviter = message["from"].bare # inviter = message['from'].bare
# muc_jid = message['groupchat_invite']['jid'] # muc_jid = message['groupchat_invite']['jid']
# await muc.join(self, inviter, muc_jid) # await muc.join(self, inviter, muc_jid)
# await bookmark.add(self, muc_jid) # await bookmark.add(self, muc_jid)
async def on_session_end(self, event): async def on_session_end(self, event):
message = "Session has ended." message = 'Session has ended.'
await connect.recover_connection(self, message) await connect.recover_connection(self, message)
async def on_connection_failed(self, event): async def on_connection_failed(self, event):
message = "Connection has failed. Reason: {}".format(event) message = 'Connection has failed. Reason: {}'.format(event)
await connect.recover_connection(self, message) await connect.recover_connection(self, message)
async def on_session_start(self, event): async def on_session_start(self, event):
self.send_presence() self.send_presence()
await self["xep_0115"].update_caps() await self['xep_0115'].update_caps()
# await muc.autojoin(self) # await muc.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.ping_task(self)
@ -201,9 +206,9 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
async def on_session_resumed(self, event): async def on_session_resumed(self, event):
self.send_presence() self.send_presence()
self["xep_0115"].update_caps() self['xep_0115'].update_caps()
# await muc.autojoin(self) # await muc.autojoin(self)
profile.set_identity(self, "service") profile.set_identity(self, 'service')
# Service.commands(self) # Service.commands(self)
# Service.reactions(self) # Service.reactions(self)
@ -214,13 +219,21 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# TODO Request for subscription # TODO Request for subscription
async def on_message(self, message): async def on_message(self, message):
# jid = message["from"].bare # jid = message['from'].bare
# if "chat" == await utility.get_chat_type(self, jid):
# await roster.add(self, jid) # if "chat" == await XmppUtility.get_chat_type(self, jid):
# await state.request(self, jid) # presence_probe = ET.Element('presence')
# presence_probe.attrib['type'] = 'probe'
# presence_probe.attrib['to'] = jid
# print('presence_probe')
# print(presence_probe)
# self.send_raw(str(presence_probe))
# presence_probe.send()
await process.message(self, message) await process.message(self, message)
# chat_type = message["type"]
# message_body = message["body"] # chat_type = message['type']
# message_body = message['body']
# message_reply = message.reply # message_reply = message.reply
@ -233,31 +246,40 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
async def on_presence_subscribe(self, presence): async def on_presence_subscribe(self, presence):
jid = presence["from"].bare jid = presence['from'].bare
# await state.request(self, jid) # XmppPresence.request(self, jid)
self.send_presence_subscription( XmppPresence.subscribe(self, jid)
pto=jid,
pfrom=self.boundjid.bare,
ptype="subscribe",
pnick=self.alias
)
async def on_presence_subscribed(self, presence): async def on_presence_subscribed(self, presence):
jid = presence["from"].bare jid = presence['from'].bare
process.greet(self, jid) if await XmppUtility.get_chat_type(self, jid) == 'chat':
message_subject = 'RSS News Bot'
message_body = ('Greetings!\n'
'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')
async def on_presence_available(self, presence): async def on_presence_available(self, presence):
# TODO Add function to check whether task is already running or not # TODO Add function to check whether task is already running or not
# 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
await task.start_tasks_xmpp(self, jid) await task.start_tasks_xmpp(self, jid)
async def on_presence_unsubscribed(self, presence): async def on_presence_unsubscribed(self, presence):
await state.unsubscribed(self, presence) jid = presence['from'].bare
message = 'You have been unsubscribed.'
status_message = '🖋️ Subscribe to receive updates'
chat_type = await XmppUtility.get_chat_type(self, jid)
XmppMessage.send(self, jid, message, chat_type)
XmppPresence.send(self, jid, status_message, chat_type)
async def on_presence_unavailable(self, presence): async def on_presence_unavailable(self, presence):
@ -294,8 +316,9 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
if message['type'] in ('chat', 'normal'): if message['type'] in ('chat', 'normal'):
jid = message['from'].bare jid = message['from'].bare
# await task.clean_tasks_xmpp(jid, ['status']) # await task.clean_tasks_xmpp(jid, ['status'])
status_text='Press "help" for manual, or "info" for information.' status_message='Press "help" for manual, or "info" for information.'
status.send(self, jid, status_text) chat_type = await XmppUtility.get_chat_type(self, jid)
XmppPresence.send(self, jid, status_message, chat_type)
async def on_chatstate_gone(self, message): async def on_chatstate_gone(self, message):

View file

@ -21,63 +21,65 @@ from time import sleep
import logging import logging
async def ping(self, jid=None): class XmppConnect:
"""
Check for ping and disconnect if no ping has been received.
Parameters
----------
jid : str, optional
Jabber ID. The default is None.
Returns async def ping(self, jid=None):
------- """
None. Check for ping and disconnect if no ping has been received.
""" Parameters
if not jid: ----------
jid = self.boundjid.bare jid : str, optional
while True: Jabber ID. The default is None.
rtt = None
Returns
-------
None.
"""
if not jid:
jid = self.boundjid.bare
while True:
rtt = None
try:
rtt = await self['xep_0199'].ping(jid, timeout=10)
logging.info("Success! RTT: %s", rtt)
except IqError as e:
logging.info("Error pinging %s: %s",
jid,
e.iq['error']['condition'])
except IqTimeout:
logging.info("No response from %s", jid)
if not rtt:
self.disconnect()
await asyncio.sleep(60 * 1)
async def recover(self, message):
logging.warning(message)
print(current_time(), message, "Attempting to reconnect.")
self.connection_attempts += 1
# if self.connection_attempts <= self.max_connection_attempts:
# self.reconnect(wait=5.0) # wait a bit before attempting to reconnect
# else:
# print(current_time(),"Maximum connection attempts exceeded.")
# logging.error("Maximum connection attempts exceeded.")
print(current_time(), "Attempt number", self.connection_attempts)
seconds = (get_value("accounts", "XMPP", "reconnect_timeout")) or 30
seconds = int(seconds)
print(current_time(), "Next attempt within", seconds, "seconds")
# NOTE asyncio.sleep doesn't interval as expected
# await asyncio.sleep(seconds)
sleep(seconds)
self.reconnect(wait=5.0)
async def inspect(self):
print('Disconnected\n'
'Reconnecting...')
try: try:
rtt = await self['xep_0199'].ping(jid, timeout=10) self.reconnect
logging.info("Success! RTT: %s", rtt) except:
except IqError as e:
logging.info("Error pinging %s: %s",
jid,
e.iq['error']['condition'])
except IqTimeout:
logging.info("No response from %s", jid)
if not rtt:
self.disconnect() self.disconnect()
await asyncio.sleep(60 * 1) print('Problem reconnecting')
async def recover_connection(self, message):
logging.warning(message)
print(current_time(), message, "Attempting to reconnect.")
self.connection_attempts += 1
# if self.connection_attempts <= self.max_connection_attempts:
# self.reconnect(wait=5.0) # wait a bit before attempting to reconnect
# else:
# print(current_time(),"Maximum connection attempts exceeded.")
# logging.error("Maximum connection attempts exceeded.")
print(current_time(), "Attempt number", self.connection_attempts)
seconds = (get_value(
"accounts", "XMPP", "reconnect_timeout")) or 30
seconds = int(seconds)
print(current_time(), "Next attempt within", seconds, "seconds")
# NOTE asyncio.sleep doesn't interval as expected
# await asyncio.sleep(seconds)
sleep(seconds)
self.reconnect(wait=5.0)
async def inspect_connection(self, event):
print("Disconnected\nReconnecting...")
print(event)
try:
self.reconnect
except:
self.disconnect()
print("Problem reconnecting")

View file

@ -1,37 +1,77 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging
import os
# import slixfeed.action as action
import slixfeed.config as config
from slixfeed.dt import current_time, timestamp
import slixfeed.fetch as fetch
import slixfeed.sqlite as sqlite
import slixfeed.task as task
import slixfeed.url as uri
from slixfeed.xmpp.bookmark import XmppBookmark
# from slixfeed.xmpp.muc import XmppGroupchat
# 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 from slixfeed.xmpp.utility import get_chat_type
import time
"""
NOTE
See XEP-0367: Message Attaching
"""
class XmppMessage: class XmppMessage:
async def send(self, jid, message, chat_type=None):
if not chat_type: # def process():
chat_type = await get_chat_type(self, jid)
self.send_message(
mto=jid,
mfrom=self.boundjid.bare,
mbody=message,
mtype=chat_type
)
async def send_oob(self, jid, url): def send(self, jid, message_body, chat_type):
chat_type = await get_chat_type(self, jid) self.send_message(mto=jid,
mfrom=self.boundjid.bare,
mbody=message_body,
mtype=chat_type)
def send_headline(self, jid, message_subject, message_body, chat_type):
self.send_message(mto=jid,
mfrom=self.boundjid.bare,
# mtype='headline',
msubject=message_subject,
mbody=message_body,
mtype=chat_type,
mnick=self.alias)
def send_oob(self, jid, url, chat_type):
html = ( html = (
f'<body xmlns="http://www.w3.org/1999/xhtml">' f'<body xmlns="http://www.w3.org/1999/xhtml">'
f'<a href="{url}">{url}</a></body>') f'<a href="{url}">{url}</a></body>')
message = self.make_message( message = self.make_message(mto=jid,
mto=jid, mfrom=self.boundjid.bare,
mfrom=self.boundjid.bare, mbody=url,
mbody=url, mhtml=html,
mhtml=html, mtype=chat_type)
mtype=chat_type
)
message['oob']['url'] = url message['oob']['url'] = url
message.send() message.send()
# FIXME Solve this function
def send_oob_reply_message(message, url, response):
reply = message.reply(response)
reply['oob']['url'] = url
reply.send()
# def send_reply(self, message, message_body):
# message.reply(message_body).send()
def send_reply(self, message, response): def send_reply(self, message, response):
message.reply(response).send() message.reply(response).send()

View file

@ -17,102 +17,90 @@ FIXME
""" """
import logging import logging
import slixfeed.xmpp.process as process
from slixfeed.dt import current_time from slixfeed.dt import current_time
from slixfeed.xmpp.message import XmppMessage
# async def accept_muc_invite(self, message, ctr=None): class XmppGroupchat:
# # if isinstance(message, str):
# if not ctr: # async def accept_muc_invite(self, message, ctr=None):
# ctr = message["from"].bare # # if isinstance(message, str):
# jid = message['groupchat_invite']['jid'] # if not ctr:
# else: # ctr = message["from"].bare
# jid = message # jid = message['groupchat_invite']['jid']
async def accept_invitation(self, message): # else:
# operator muc_chat # jid = message
inviter = message["from"].bare async def accept_invitation(self, message):
muc_jid = message['groupchat_invite']['jid'] # operator muc_chat
await join(self, inviter, muc_jid) inviter = message["from"].bare
muc_jid = message['groupchat_invite']['jid']
await self.join(self, inviter, muc_jid)
async def autojoin(self): async def autojoin(self):
result = await self.plugin['xep_0048'].get_bookmarks() result = await self.plugin['xep_0048'].get_bookmarks()
bookmarks = result["private"]["bookmarks"] bookmarks = result["private"]["bookmarks"]
conferences = bookmarks["conferences"] conferences = bookmarks["conferences"]
for conference in conferences: for conference in conferences:
if conference["autojoin"]: if conference["autojoin"]:
muc_jid = conference["jid"] muc_jid = conference["jid"]
logging.info( self.plugin['xep_0045'].join_muc(muc_jid,
'Autojoin groupchat\n' conference["nick"],
'Name : {}\n' # If a room password is needed, use:
'JID : {}\n' # password=the_room_password,
'Alias : {}\n' )
.format( logging.info('Autojoin groupchat\n'
conference["name"], 'Name : {}\n'
muc_jid, 'JID : {}\n'
conference["nick"] 'Alias : {}\n'
)) .format(conference["name"],
self.plugin['xep_0045'].join_muc( muc_jid,
muc_jid, conference["nick"]))
conference["nick"],
# If a room password is needed, use:
# password=the_room_password,
)
async def join(self, inviter, muc_jid):
# token = await initdb(
# muc_jid,
# get_settings_value,
# "token"
# )
# if token != "accepted":
# token = randrange(10000, 99999)
# await initdb(
# muc_jid,
# update_settings_value,
# ["token", token]
# )
# self.send_message(
# mto=inviter,
# mfrom=self.boundjid.bare,
# mbody=(
# "Send activation token {} to groupchat xmpp:{}?join."
# ).format(token, muc_jid)
# )
logging.info(
'Joining groupchat\n'
'JID : {}\n'
'Inviter : {}\n'
.format(
muc_jid,
inviter
))
self.plugin['xep_0045'].join_muc(
muc_jid,
self.alias,
# If a room password is needed, use:
# password=the_room_password,
)
process.greet(self, muc_jid, chat_type="groupchat")
async def leave(self, muc_jid): async def join(self, inviter, muc_jid):
messages = [ # token = await initdb(
"Whenever you need an RSS service again, " # muc_jid,
"please dont hesitate to contact me.", # get_settings_value,
"My personal contact is xmpp:{}?message".format(self.boundjid.bare), # "token"
"Farewell, and take care." # )
] # if token != "accepted":
for message in messages: # token = randrange(10000, 99999)
self.send_message( # await initdb(
mto=muc_jid, # muc_jid,
mfrom=self.boundjid.bare, # update_settings_value,
mbody=message, # ["token", token]
mtype="groupchat" # )
) # self.send_message(
self.plugin['xep_0045'].leave_muc( # mto=inviter,
muc_jid, # mfrom=self.boundjid.bare,
self.alias, # mbody=(
"Goodbye!", # "Send activation token {} to groupchat xmpp:{}?join."
self.boundjid.bare # ).format(token, muc_jid)
) # )
logging.info('Joining groupchat\n'
'JID : {}\n'
'Inviter : {}\n'
.format(muc_jid, inviter))
self.plugin['xep_0045'].join_muc(muc_jid,
self.alias,
# If a room password is needed, use:
# password=the_room_password,
)
async def leave(self, muc_jid):
messages = [
"Whenever you need an RSS service again, "
"please dont hesitate to contact me.",
"My JID is xmpp:{}?message".format(self.boundjid.bare),
"Farewell."
]
for message in messages:
self.send_message(mto=muc_jid,
mfrom=self.boundjid.bare,
mbody=message,
mtype='groupchat')
self.plugin['xep_0045'].leave_muc(muc_jid,
self.alias,
'Goodbye!',
self.boundjid.bare)

37
slixfeed/xmpp/presence.py Normal file
View file

@ -0,0 +1,37 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
NOTE
Accept symbols 🉑 👍
"""
class XmppPresence:
def send(self, jid, status_message, presence_type=None, status_type=None):
self.send_presence(pto=jid,
pfrom=self.boundjid.bare,
pshow=status_type,
pstatus=status_message,
ptype=presence_type)
def subscribe(self, jid):
self.send_presence_subscription(pto=jid,
pfrom=self.boundjid.bare,
ptype='subscribe',
pnick=self.alias)
def remove(self):
"""
Remove subscription from JID that do not (stopped) share presence.
Returns
-------
None.
"""

View file

@ -9,7 +9,7 @@ TODO
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"):
2) If subscription is inadequate (see state.request), send a message that says so. 2) If subscription is inadequate (see XmppPresence.request), send a message that says so.
elif not self.client_roster[jid]["to"]: elif not self.client_roster[jid]["to"]:
breakpoint() breakpoint()
@ -28,9 +28,10 @@ import slixfeed.sqlite as sqlite
import slixfeed.task as task import slixfeed.task as task
import slixfeed.url as uri import slixfeed.url as uri
from slixfeed.xmpp.bookmark import XmppBookmark from slixfeed.xmpp.bookmark import XmppBookmark
import slixfeed.xmpp.muc as groupchat from slixfeed.xmpp.muc import XmppGroupchat
from slixfeed.xmpp.status import XmppStatus from slixfeed.xmpp.message import XmppMessage
import slixfeed.xmpp.upload as upload from slixfeed.xmpp.presence import XmppPresence
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
@ -88,7 +89,8 @@ async def message(self, message):
await task.clean_tasks_xmpp(jid, ['status']) await task.clean_tasks_xmpp(jid, ['status'])
status_type = 'dnd' status_type = 'dnd'
status_message = '📥️ Procesing request to import feeds...' status_message = '📥️ Procesing request to import feeds...'
await XmppStatus.send(self, jid, status_message, status_type) XmppPresence.send(self, jid, status_message,
status_type=status_type)
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
count = await action.import_opml(db_file, url) count = await action.import_opml(db_file, url)
if count: if count:
@ -97,7 +99,7 @@ async def message(self, message):
response = 'OPML file was not imported.' response = 'OPML file was not imported.'
# await task.clean_tasks_xmpp(jid, ['status']) # await task.clean_tasks_xmpp(jid, ['status'])
await task.start_tasks_xmpp(self, jid, ['status']) await task.start_tasks_xmpp(self, jid, ['status'])
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
return return
@ -190,7 +192,7 @@ async def message(self, message):
# 'This action is restricted. ' # 'This action is restricted. '
# 'Type: breakpoint.' # 'Type: breakpoint.'
# ) # )
# send_reply_message(self, message, response) # XmppMessage.send_reply(self, message, response)
case 'help': case 'help':
command_list = ' '.join(action.manual('commands.toml')) command_list = ' '.join(action.manual('commands.toml'))
response = ('Available command keys:\n' response = ('Available command keys:\n'
@ -198,7 +200,7 @@ async def message(self, message):
'Usage: `help <key>`' 'Usage: `help <key>`'
.format(command_list)) .format(command_list))
print(response) print(response)
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('help'): case _ if message_lowercase.startswith('help'):
command = message_text[5:] command = message_text[5:]
command = command.split(' ') command = command.split(' ')
@ -228,14 +230,14 @@ async def message(self, message):
else: else:
response = ('Invalid. Enter command key ' response = ('Invalid. Enter command key '
'or command key & name') 'or command key & name')
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case 'info': case 'info':
command_list = ' '.join(action.manual('information.toml')) command_list = ' '.join(action.manual('information.toml'))
response = ('Available command options:\n' response = ('Available command options:\n'
'```\n{}\n```\n' '```\n{}\n```\n'
'Usage: `info <option>`' 'Usage: `info <option>`'
.format(command_list)) .format(command_list))
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('info'): case _ if message_lowercase.startswith('info'):
command = message_text[5:] command = message_text[5:]
command_list = action.manual('information.toml', command) command_list = action.manual('information.toml', command)
@ -245,7 +247,7 @@ async def message(self, message):
else: else:
response = ('KeyError for {}' response = ('KeyError for {}'
.format(command)) .format(command))
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase in ['greetings', 'hallo', 'hello', case _ if message_lowercase in ['greetings', 'hallo', 'hello',
'hey', 'hi', 'hola', 'holla', 'hey', 'hi', 'hola', 'holla',
'hollo']: 'hollo']:
@ -253,7 +255,7 @@ async def message(self, message):
'I am an RSS News Bot.\n' 'I am an RSS News Bot.\n'
'Send "help" for further instructions.\n' 'Send "help" for further instructions.\n'
.format(self.alias)) .format(self.alias))
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
# case _ if message_lowercase.startswith('activate'): # case _ if message_lowercase.startswith('activate'):
# if message['type'] == 'groupchat': # if message['type'] == 'groupchat':
@ -315,7 +317,7 @@ async def message(self, message):
.format(url, name, ix)) .format(url, name, ix))
else: else:
response = 'Missing URL.' response = 'Missing URL.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('allow +'): case _ if message_lowercase.startswith('allow +'):
key = 'filter-' + message_text[:5] key = 'filter-' + message_text[:5]
val = message_text[7:] val = message_text[7:]
@ -333,7 +335,7 @@ async def message(self, message):
.format(val)) .format(val))
else: else:
response = 'Missing keywords.' response = 'Missing keywords.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('allow -'): case _ if message_lowercase.startswith('allow -'):
key = 'filter-' + message_text[:5] key = 'filter-' + message_text[:5]
val = message_text[7:] val = message_text[7:]
@ -351,7 +353,7 @@ async def message(self, message):
.format(val)) .format(val))
else: else:
response = 'Missing keywords.' response = 'Missing keywords.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('archive'): case _ if message_lowercase.startswith('archive'):
key = message_text[:7] key = message_text[:7]
val = message_text[8:] val = message_text[8:]
@ -375,7 +377,7 @@ async def message(self, message):
response = 'Enter a numeric value only.' response = 'Enter a numeric value only.'
else: else:
response = 'Missing value.' response = 'Missing value.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('bookmark -'): case _ if message_lowercase.startswith('bookmark -'):
if jid == config.get_value('accounts', 'XMPP', 'operator'): if jid == config.get_value('accounts', 'XMPP', 'operator'):
muc_jid = message_text[11:] muc_jid = message_text[11:]
@ -386,14 +388,14 @@ async def message(self, message):
else: else:
response = ('This action is restricted. ' response = ('This action is restricted. '
'Type: removing bookmarks.') 'Type: removing bookmarks.')
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case 'bookmarks': case 'bookmarks':
if jid == config.get_value('accounts', 'XMPP', 'operator'): if jid == config.get_value('accounts', 'XMPP', 'operator'):
response = await action.list_bookmarks(self) response = await action.list_bookmarks(self)
else: else:
response = ('This action is restricted. ' response = ('This action is restricted. '
'Type: viewing bookmarks.') 'Type: viewing bookmarks.')
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('deny +'): case _ if message_lowercase.startswith('deny +'):
key = 'filter-' + message_text[:4] key = 'filter-' + message_text[:4]
val = message_text[6:] val = message_text[6:]
@ -411,7 +413,7 @@ async def message(self, message):
.format(val)) .format(val))
else: else:
response = 'Missing keywords.' response = 'Missing keywords.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('deny -'): case _ if message_lowercase.startswith('deny -'):
key = 'filter-' + message_text[:4] key = 'filter-' + message_text[:4]
val = message_text[6:] val = message_text[6:]
@ -429,7 +431,7 @@ async def message(self, message):
.format(val)) .format(val))
else: else:
response = 'Missing keywords.' response = 'Missing keywords.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('export'): case _ if message_lowercase.startswith('export'):
ex = message_text[7:] ex = message_text[7:]
if ex in ('opml', 'html', 'md', 'xbel'): if ex in ('opml', 'html', 'md', 'xbel'):
@ -437,8 +439,8 @@ async def message(self, message):
status_message = ('📤️ Procesing request to ' status_message = ('📤️ Procesing request to '
'export feeds into {}...' 'export feeds into {}...'
.format(ex)) .format(ex))
await XmppStatus.send(self, jid, status_message, XmppPresence.send(self, jid, status_message,
status_type) status_type=status_type)
cache_dir = config.get_default_cache_directory() cache_dir = config.get_default_cache_directory()
if not os.path.isdir(cache_dir): if not os.path.isdir(cache_dir):
os.mkdir(cache_dir) os.mkdir(cache_dir)
@ -457,105 +459,33 @@ async def message(self, message):
action.export_to_opml(jid, filename, results) action.export_to_opml(jid, filename, results)
case 'xbel': case 'xbel':
response = 'Not yet implemented.' response = 'Not yet implemented.'
url = await upload.start(self, jid, filename) url = await XmppUpload.start(self, jid, filename)
# response = ( # response = (
# 'Feeds exported successfully to {}.\n{}' # 'Feeds exported successfully to {}.\n{}'
# ).format(ex, url) # ).format(ex, url)
# send_oob_reply_message(message, url, response) # XmppMessage.send_oob_reply_message(message, url, response)
await send_oob_message(self, jid, url) chat_type = await get_chat_type(self, jid)
XmppMessage.send_oob(self, jid, url, chat_type)
await task.start_tasks_xmpp(self, jid, ['status']) await task.start_tasks_xmpp(self, jid, ['status'])
else: else:
response = ('Unsupported filetype.\n' response = ('Unsupported filetype.\n'
'Try: html, md, opml, or xbel') 'Try: html, md, opml, or xbel')
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if (message_lowercase.startswith('gemini:') or case _ if (message_lowercase.startswith('gemini:') or
message_lowercase.startswith('gopher:')): message_lowercase.startswith('gopher:')):
response = 'Gemini and Gopher are not supported yet.' response = 'Gemini and Gopher are not supported yet.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
# TODO xHTML, HTMLZ, Markdown, MHTML, PDF, TXT # TODO xHTML, HTMLZ, MHTML
case _ if (message_lowercase.startswith('get')): case _ if (message_lowercase.startswith('page')):
message_text = message_text[4:] message_text = message_text[5:]
ix_url = message_text.split(' ')[0] ix_url = message_text.split(' ')[0]
ext = ' '.join(message_text.split(' ')[1:]) await action.download_document(self, message, jid, jid_file,
ext = ext if ext else 'pdf' message_text, ix_url, False)
url = None case _ if (message_lowercase.startswith('content')):
error = None message_text = message_text[8:]
if ext in ('epub', 'html', 'markdown', ix_url = message_text.split(' ')[0]
'md', 'pdf', 'text', 'txt'): await action.download_document(self, message, jid, jid_file,
match ext: message_text, ix_url, True)
case 'markdown':
ext = 'md'
case 'text':
ext = 'txt'
status_type = 'dnd'
status_message = ('📃️ Procesing request to '
'produce {} document...'
.format(ext.upper()))
await XmppStatus.send(self, jid, status_message,
status_type)
db_file = config.get_pathname_to_database(jid_file)
cache_dir = config.get_default_cache_directory()
if ix_url:
if not os.path.isdir(cache_dir):
os.mkdir(cache_dir)
if not os.path.isdir(cache_dir + '/readability'):
os.mkdir(cache_dir + '/readability')
try:
ix = int(ix_url)
try:
url = sqlite.get_entry_url(db_file, ix)
except:
response = 'No entry with index {}'.format(ix)
except:
url = ix_url
if url:
logging.info('Original URL: {}'
.format(url))
url = uri.remove_tracking_parameters(url)
logging.info('Processed URL (tracker removal): {}'
.format(url))
url = (uri.replace_hostname(url, 'link')) or url
logging.info('Processed URL (replace hostname): {}'
.format(url))
result = await fetch.http(url)
data = result[0]
code = result[1]
if data:
title = action.get_document_title(data)
title = title.strip().lower()
for i in (' ', '-'):
title = title.replace(i, '_')
for i in ('?', '"', '\'', '!'):
title = title.replace(i, '')
filename = os.path.join(
cache_dir, 'readability',
title + '_' + timestamp() + '.' + ext)
error = action.generate_document(data, url,
ext, filename)
if error:
response = ('> {}\n'
'Failed to export {}. '
'Reason: {}'
.format(url, ext.upper(),
error))
else:
url = await upload.start(self, jid,
filename)
await send_oob_message(self, jid, url)
else:
response = ('> {}\n'
'Failed to fetch URL. Reason: {}'
.format(url, code))
await task.start_tasks_xmpp(self, jid, ['status'])
else:
response = 'Missing entry index number.'
else:
response = ('Unsupported filetype.\n'
'Try: epub, html, md (markdown), '
'pdf, or txt (text)')
if response:
logging.warning('Error for URL {}: {}'.format(url, error))
send_reply_message(self, message, response)
# case _ if (message_lowercase.startswith('http')) and( # case _ if (message_lowercase.startswith('http')) and(
# message_lowercase.endswith('.opml')): # message_lowercase.endswith('.opml')):
# url = message_text # url = message_text
@ -565,8 +495,8 @@ async def message(self, message):
# status_message = ( # status_message = (
# '📥️ Procesing request to import feeds...' # '📥️ Procesing request to import feeds...'
# ) # )
# await XmppStatus.send( # XmppPresence.send(
# self, jid, status_message, status_type) # self, jid, status_message, status_type=status_type)
# db_file = config.get_pathname_to_database(jid_file) # db_file = config.get_pathname_to_database(jid_file)
# count = await action.import_opml(db_file, url) # count = await action.import_opml(db_file, url)
# if count: # if count:
@ -581,7 +511,7 @@ async def message(self, message):
# jid, ['status']) # jid, ['status'])
# await task.start_tasks_xmpp( # await task.start_tasks_xmpp(
# self, jid, ['status']) # self, jid, ['status'])
# send_reply_message(self, message, response) # XmppMessage.send_reply(self, message, response)
case _ if (message_lowercase.startswith('http') or case _ if (message_lowercase.startswith('http') or
message_lowercase.startswith('feed:')): message_lowercase.startswith('feed:')):
url = message_text url = message_text
@ -590,7 +520,8 @@ async def message(self, message):
status_message = ('📫️ Processing request ' status_message = ('📫️ Processing request '
'to fetch data from {}' 'to fetch data from {}'
.format(url)) .format(url))
await XmppStatus.send(self, jid, status_message, status_type) XmppPresence.send(self, jid, status_message,
status_type=status_type)
if url.startswith('feed:'): if url.startswith('feed:'):
url = uri.feed_to_http(url) url = uri.feed_to_http(url)
url = (uri.replace_hostname(url, 'feed')) or url url = (uri.replace_hostname(url, 'feed')) or url
@ -605,7 +536,7 @@ async def message(self, message):
# 'of being added to the subscription ' # 'of being added to the subscription '
# 'list.'.format(url) # 'list.'.format(url)
# ) # )
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('feeds'): case _ if message_lowercase.startswith('feeds'):
query = message_text[6:] query = message_text[6:]
if query: if query:
@ -619,14 +550,14 @@ async def message(self, message):
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
result = await sqlite.get_feeds(db_file) result = await sqlite.get_feeds(db_file)
response = action.list_feeds(result) response = action.list_feeds(result)
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case 'goodbye': case 'goodbye':
if message['type'] == 'groupchat': if message['type'] == 'groupchat':
await groupchat.leave(self, jid) await XmppGroupchat.leave(self, jid)
await XmppBookmark.remove(self, muc_jid) await XmppBookmark.remove(self, muc_jid)
else: else:
response = 'This command is valid in groupchat only.' response = 'This command is valid in groupchat only.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('interval'): case _ if message_lowercase.startswith('interval'):
key = message_text[:8] key = message_text[:8]
val = message_text[9:] val = message_text[9:]
@ -636,14 +567,14 @@ async def message(self, message):
muc_jid = uri.check_xmpp_uri(message_text[5:]) muc_jid = uri.check_xmpp_uri(message_text[5:])
if muc_jid: if muc_jid:
# TODO probe JID and confirm it's a groupchat # TODO probe JID and confirm it's a groupchat
await groupchat.join(self, jid, muc_jid) await XmppGroupchat.join(self, jid, muc_jid)
response = ('Joined groupchat {}' response = ('Joined groupchat {}'
.format(message_text)) .format(message_text))
else: else:
response = ('> {}\n' response = ('> {}\n'
'XMPP URI is not valid.' 'XMPP URI is not valid.'
.format(message_text)) .format(message_text))
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('length'): case _ if message_lowercase.startswith('length'):
key = message_text[:6] key = message_text[:6]
val = message_text[7:] val = message_text[7:]
@ -692,7 +623,7 @@ async def message(self, message):
# ).format(val) # ).format(val)
# else: # else:
# response = 'Missing value.' # response = 'Missing value.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case 'new': case 'new':
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
key = 'old' key = 'old'
@ -702,7 +633,7 @@ async def message(self, message):
else: else:
await sqlite.set_settings_value(db_file, [key, val]) await sqlite.set_settings_value(db_file, [key, val])
response = 'Only new items of newly added feeds will be sent.' response = 'Only new items of newly added feeds will be sent.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
# TODO Will you add support for number of messages? # TODO Will you add support for number of messages?
case 'next': case 'next':
# num = message_text[5:] # num = message_text[5:]
@ -739,7 +670,7 @@ async def message(self, message):
else: else:
await sqlite.set_settings_value(db_file, [key, val]) await sqlite.set_settings_value(db_file, [key, val])
response = 'All items of newly added feeds will be sent.' response = 'All items of newly added feeds will be sent.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('quantum'): case _ if message_lowercase.startswith('quantum'):
key = message_text[:7] key = message_text[:7]
val = message_text[8:] val = message_text[8:]
@ -762,12 +693,12 @@ async def message(self, message):
response = 'Enter a numeric value only.' response = 'Enter a numeric value only.'
else: else:
response = 'Missing value.' response = 'Missing value.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case 'random': case 'random':
# TODO /questions/2279706/select-random-row-from-a-sqlite-table # TODO /questions/2279706/select-random-row-from-a-sqlite-table
# NOTE sqlitehandler.get_entry_unread # NOTE sqlitehandler.get_entry_unread
response = 'Updates will be sent by random order.' response = 'Updates will be sent by random order.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('read'): case _ if message_lowercase.startswith('read'):
data = message_text[5:] data = message_text[5:]
data = data.split() data = data.split()
@ -776,7 +707,8 @@ async def message(self, message):
status_type = 'dnd' status_type = 'dnd'
status_message = ('📫️ Processing request to fetch data from {}' status_message = ('📫️ Processing request to fetch data from {}'
.format(url)) .format(url))
await XmppStatus.send(self, jid, status_message, status_type) XmppPresence.send(self, jid, status_message,
status_type=status_type)
if url.startswith('feed:'): if url.startswith('feed:'):
url = uri.feed_to_http(url) url = uri.feed_to_http(url)
url = (uri.replace_hostname(url, 'feed')) or url url = (uri.replace_hostname(url, 'feed')) or url
@ -796,7 +728,7 @@ async def message(self, message):
response = ('Enter command as follows:\n' response = ('Enter command as follows:\n'
'`read <url>` or `read <url> <number>`\n' '`read <url>` or `read <url> <number>`\n'
'URL must not contain white space.') 'URL must not contain white space.')
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
await task.start_tasks_xmpp(self, jid, ['status']) await task.start_tasks_xmpp(self, jid, ['status'])
case _ if message_lowercase.startswith('recent'): case _ if message_lowercase.startswith('recent'):
num = message_text[7:] num = message_text[7:]
@ -813,7 +745,7 @@ async def message(self, message):
response = 'Enter a numeric value only.' response = 'Enter a numeric value only.'
else: else:
response = 'Missing value.' response = 'Missing value.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('remove'): case _ if message_lowercase.startswith('remove'):
ix_url = message_text[7:] ix_url = message_text[7:]
if ix_url: if ix_url:
@ -859,14 +791,15 @@ async def message(self, message):
await task.start_tasks_xmpp(self, jid, ['status']) await task.start_tasks_xmpp(self, jid, ['status'])
else: else:
response = 'Missing feed URL or index number.' response = 'Missing feed URL or index number.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('reset'): case _ if message_lowercase.startswith('reset'):
# TODO Reset also by ID # TODO Reset also by ID
ix_url = message_text[6:] ix_url = message_text[6:]
await task.clean_tasks_xmpp(jid, ['status']) await task.clean_tasks_xmpp(jid, ['status'])
status_type = 'dnd' status_type = 'dnd'
status_message = '📫️ Marking entries as read...' status_message = '📫️ Marking entries as read...'
await XmppStatus.send(self, jid, status_message, status_type) XmppPresence.send(self, jid, status_message,
status_type=status_type)
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
if ix_url: if ix_url:
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
@ -905,7 +838,7 @@ async def message(self, message):
else: else:
await sqlite.mark_all_as_read(db_file) await sqlite.mark_all_as_read(db_file)
response = 'All entries have been marked as read.' response = 'All entries have been marked as read.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
await task.start_tasks_xmpp(self, jid, ['status']) await task.start_tasks_xmpp(self, jid, ['status'])
case _ if message_lowercase.startswith('search'): case _ if message_lowercase.startswith('search'):
query = message_text[7:] query = message_text[7:]
@ -918,25 +851,13 @@ async def message(self, message):
response = 'Enter at least 2 characters to search' response = 'Enter at least 2 characters to search'
else: else:
response = 'Missing search query.' response = 'Missing search query.'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case 'start': case 'start':
# response = 'Updates are enabled.' await action.xmpp_start_updates(self, message, jid, jid_file)
key = 'enabled'
val = 1
db_file = config.get_pathname_to_database(jid_file)
if await sqlite.get_settings_value(db_file, key):
await sqlite.update_settings_value(db_file, [key, val])
else:
await sqlite.set_settings_value(db_file, [key, val])
await task.start_tasks_xmpp(self, jid)
response = 'Updates are enabled.'
# print(current_time(), 'task_manager[jid]')
# print(task_manager[jid])
send_reply_message(self, message, response)
case 'stats': case 'stats':
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
response = await action.list_statistics(db_file) response = await action.list_statistics(db_file)
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('disable '): case _ if message_lowercase.startswith('disable '):
ix = message_text[8:] ix = message_text[8:]
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
@ -946,7 +867,7 @@ async def message(self, message):
.format(ix)) .format(ix))
except: except:
response = 'No news source with index {}.'.format(ix) response = 'No news source with index {}.'.format(ix)
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('enable'): case _ if message_lowercase.startswith('enable'):
ix = message_text[7:] ix = message_text[7:]
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
@ -956,29 +877,29 @@ async def message(self, message):
.format(ix)) .format(ix))
except: except:
response = 'No news source with index {}.'.format(ix) response = 'No news source with index {}.'.format(ix)
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case 'stop': case 'stop':
await action.xmpp_stop_updates(self, message, jid, jid_file) await action.xmpp_stop_updates(self, message, jid, jid_file)
case 'support': case 'support':
# TODO Send an invitation. # TODO Send an invitation.
response = 'Join xmpp:slixfeed@chat.woodpeckersnest.space?join' response = 'Join xmpp:slixfeed@chat.woodpeckersnest.space?join'
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('xmpp:'): case _ if message_lowercase.startswith('xmpp:'):
muc_jid = uri.check_xmpp_uri(message_text) muc_jid = uri.check_xmpp_uri(message_text)
if muc_jid: if muc_jid:
# TODO probe JID and confirm it's a groupchat # TODO probe JID and confirm it's a groupchat
await groupchat.join(self, jid, muc_jid) await XmppGroupchat.join(self, jid, muc_jid)
response = ('Joined groupchat {}' response = ('Joined groupchat {}'
.format(message_text)) .format(message_text))
else: else:
response = ('> {}\n' response = ('> {}\n'
'XMPP URI is not valid.' 'XMPP URI is not valid.'
.format(message_text)) .format(message_text))
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
case _: case _:
response = ('Unknown command. ' response = ('Unknown command. '
'Press "help" for list of commands') 'Press "help" for list of commands')
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
# TODO Use message correction here # TODO Use message correction here
# NOTE This might not be a good idea if # NOTE This might not be a good idea if
# commands are sent one close to the next # commands are sent one close to the next
@ -988,7 +909,7 @@ async def message(self, message):
command_time_total = command_time_finish - command_time_start command_time_total = command_time_finish - command_time_start
command_time_total = round(command_time_total, 3) command_time_total = round(command_time_total, 3)
response = 'Finished. Total time: {}s'.format(command_time_total) response = 'Finished. Total time: {}s'.format(command_time_total)
send_reply_message(self, message, response) XmppMessage.send_reply(self, message, response)
if not response: response = 'EMPTY MESSAGE - ACTION ONLY' if not response: response = 'EMPTY MESSAGE - ACTION ONLY'
data_dir = config.get_default_data_directory() data_dir = config.get_default_data_directory()
@ -1010,67 +931,3 @@ async def message(self, message):
'{}\n' '{}\n'
.format(message_text, jid, jid_file, response) .format(message_text, jid, jid_file, response)
) )
def send_reply_message(self, message, response):
message.reply(response).send()
# TODO Solve this function
def send_oob_reply_message(message, url, response):
reply = message.reply(response)
reply['oob']['url'] = url
reply.send()
async def send_oob_message(self, jid, url):
chat_type = await get_chat_type(self, jid)
html = (
f'<body xmlns="http://www.w3.org/1999/xhtml">'
f'<a href="{url}">{url}</a></body>')
message = self.make_message(
mto=jid,
mfrom=self.boundjid.bare,
mbody=url,
mhtml=html,
mtype=chat_type
)
message['oob']['url'] = url
message.send()
# def greet(self, jid, chat_type='chat'):
# messages = [
# 'Greetings!',
# 'I'm {}, the news anchor.'.format(self.nick),
# 'My job is to bring you the latest news '
# 'from sources you provide me with.',
# 'You may always reach me via '
# 'xmpp:{}?message'.format(self.boundjid.bare)
# ]
# for message in messages:
# self.send_message(
# mto=jid,
# mfrom=self.boundjid.bare,
# mbody=message,
# mtype=chat_type
# )
def greet(self, jid, chat_type='chat'):
message = (
'Greetings!\n'
'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
)
self.send_message(
mto=jid,
mfrom=self.boundjid.bare,
mbody=message,
mtype=chat_type
)

View file

@ -10,65 +10,40 @@ TODO
""" """
import slixfeed.xmpp.utility as utility class XmppRoster:
async def remove(self, jid): async def remove(self, jid):
""" """
Remove JID to roster. Remove JID to roster.
Parameters Parameters
---------- ----------
jid : str jid : str
Jabber ID. Jabber ID.
Returns Returns
------- -------
None. None.
""" """
self.update_roster( self.update_roster(jid, subscription="remove")
jid,
subscription="remove"
)
async def add(self, jid): async def add(self, jid):
""" """
Add JID to roster. Add JID to roster.
Parameters Add JID to roster if it is not already in roster.
----------
jid : str
Jabber ID.
Returns Parameters
------- ----------
None. jid : str
""" Jabber ID.
if await utility.get_chat_type(self, jid) == "groupchat":
# Check whether JID is in bookmarks; otherwise, add it. Returns
print(jid, "is muc") -------
return None.
else: """
await self.get_roster() await self.get_roster()
# Check whether JID is in roster; otherwise, add it.
if jid not in self.client_roster.keys(): if jid not in self.client_roster.keys():
self.send_presence_subscription( self.update_roster(jid, subscription="both")
pto=jid,
pfrom=self.boundjid.bare,
ptype="subscribe",
pnick=self.alias
)
self.update_roster(
jid,
subscription="both"
)
def remove_subscription(self):
"""
Remove subscription from contacts that don't share their presence.
Returns
-------
None.
"""

View file

@ -1,71 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from slixmpp.xmlstream.matcher import MatchXPath
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream import ET
async def request(self, jid):
"""
Ask contant to settle subscription.
Parameters
----------
jid : str
Jabber ID.
Returns
-------
None.
"""
# Check whether JID is subscribed; otherwise, ask for presence.
if self.is_component:
presence_probe = ET.Element('presence')
presence_probe.attrib['type'] = 'probe'
presence_probe.attrib['to'] = jid
print(presence_probe)
breakpoint()
self.send_raw(str(presence_probe))
presence_probe.send()
elif not self.client_roster[jid]["to"]:
self.send_presence_subscription(
pto=jid,
pfrom=self.boundjid.bare,
ptype="subscribe",
pnick=self.alias
)
self.send_message(
mto=jid,
mfrom=self.boundjid.bare,
# mtype="headline",
msubject="RSS News Bot",
mbody=(
"Share online status to receive updates."
),
mnick=self.alias
)
self.send_presence(
pto=jid,
pfrom=self.boundjid.bare,
# Accept symbol 🉑️ 👍️ ✍
pstatus=(
"✒️ Share online status to receive updates."
),
# ptype="subscribe",
pnick=self.alias
)
async def unsubscribed(self, presence):
jid = presence["from"].bare
self.send_message(
mto=jid,
mfrom=self.boundjid.bare,
mbody="You have been unsubscribed."
)
self.send_presence(
pto=jid,
pfrom=self.boundjid.bare,
pstatus="🖋️ Subscribe to receive updates",
pnick=self.alias
)

View file

@ -1,19 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from slixfeed.xmpp.utility import get_chat_type
class XmppStatus:
async def send(self, jid, status_message, status_type=None, chat_type=None):
if not chat_type:
chat_type = await get_chat_type(self, jid)
self.send_presence(
pto=jid,
pfrom=self.boundjid.bare,
pshow=status_type,
pstatus=status_message,
ptype=chat_type
)

View file

@ -11,38 +11,39 @@ from slixmpp.exceptions import IqTimeout, IqError
from slixmpp.plugins.xep_0363.http_upload import HTTPError from slixmpp.plugins.xep_0363.http_upload import HTTPError
# import sys # import sys
class XmppUpload:
async def start(self, jid, filename, domain=None): async def start(self, jid, filename, domain=None):
logging.info('Uploading file %s...', filename) logging.info('Uploading file %s...', filename)
try:
upload_file = self['xep_0363'].upload_file
# if self.encrypted and not self['xep_0454']:
# print(
# 'The xep_0454 module isn\'t available. '
# 'Ensure you have \'cryptography\' '
# 'from extras_require installed.',
# file=sys.stderr,
# )
# return
# elif self.encrypted:
# upload_file = self['xep_0454'].upload_file
try: try:
url = await upload_file( upload_file = self['xep_0363'].upload_file
filename, domain, timeout=10, # if self.encrypted and not self['xep_0454']:
) # print(
logging.info('Upload successful!') # 'The xep_0454 module isn\'t available. '
logging.info('Sending file to %s', jid) # 'Ensure you have \'cryptography\' '
except HTTPError: # 'from extras_require installed.',
url = ( # file=sys.stderr,
"Error: It appears that this server doesn't support "
"HTTP File Upload."
)
logging.error(
"It appears that this server doesn't support HTTP File Upload."
)
# raise HTTPError(
# "This server doesn't appear to support HTTP File Upload"
# ) # )
except IqTimeout: # return
raise TimeoutError('Could not send message in time') # elif self.encrypted:
return url # upload_file = self['xep_0454'].upload_file
try:
url = await upload_file(
filename, domain, timeout=10,
)
logging.info('Upload successful!')
logging.info('Sending file to %s', jid)
except HTTPError:
url = (
"Error: It appears that this server doesn't support "
"HTTP File Upload."
)
logging.error(
"It appears that this server doesn't support HTTP File Upload."
)
# raise HTTPError(
# "This server doesn't appear to support HTTP File Upload"
# )
except IqTimeout:
raise TimeoutError('Could not send message in time')
return url

View file

@ -1,9 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from slixmpp.exceptions import IqTimeout from slixmpp.exceptions import IqError, IqTimeout
import logging import logging
# class XmppChat
# class XmppUtility:
async def get_chat_type(self, jid): async def get_chat_type(self, jid):
""" """
@ -43,11 +45,15 @@ async def get_chat_type(self, jid):
'Chat Type: {}'.format(jid, chat_type)) 'Chat Type: {}'.format(jid, chat_type))
return chat_type return chat_type
# TODO Test whether this exception is realized # TODO Test whether this exception is realized
except IqError as e:
message = ('IQ Error\n'
'IQ Stanza: {}'
'Jabber ID: {}'
.format(e, jid))
logging.error(message)
except IqTimeout as e: except IqTimeout as e:
messages = [ message = ('IQ Timeout\n'
("Timeout IQ"), 'IQ Stanza: {}'
("IQ Stanza:", e), 'Jabber ID: {}'
("Jabber ID:", jid) .format(e, jid))
] logging.error(message)
for message in messages:
logging.error(message)