Add ad-hoc commands
This commit is contained in:
parent
56aaccce68
commit
422e0669f1
12 changed files with 964 additions and 247 deletions
|
@ -129,7 +129,7 @@ class JabberComponent:
|
|||
xmpp.register_plugin('xep_0066') # Out of Band Data
|
||||
xmpp.register_plugin('xep_0071') # XHTML-IM
|
||||
xmpp.register_plugin('xep_0084') # User Avatar
|
||||
xmpp.register_plugin('xep_0085') # Chat State Notifications
|
||||
# xmpp.register_plugin('xep_0085') # Chat State Notifications
|
||||
xmpp.register_plugin('xep_0115') # Entity Capabilities
|
||||
xmpp.register_plugin('xep_0153') # vCard-Based Avatars
|
||||
xmpp.register_plugin('xep_0199', {'keepalive': True}) # XMPP Ping
|
||||
|
@ -155,7 +155,7 @@ class JabberClient:
|
|||
xmpp.register_plugin('xep_0066') # Out of Band Data
|
||||
xmpp.register_plugin('xep_0071') # XHTML-IM
|
||||
xmpp.register_plugin('xep_0084') # User Avatar
|
||||
xmpp.register_plugin('xep_0085') # Chat State Notifications
|
||||
# xmpp.register_plugin('xep_0085') # Chat State Notifications
|
||||
xmpp.register_plugin('xep_0115') # Entity Capabilities
|
||||
xmpp.register_plugin('xep_0153') # vCard-Based Avatars
|
||||
xmpp.register_plugin('xep_0199', {'keepalive': True}) # XMPP Ping
|
||||
|
|
|
@ -48,7 +48,10 @@ from slixfeed.url import (
|
|||
replace_hostname,
|
||||
trim_url
|
||||
)
|
||||
import slixfeed.xmpp.bookmark as bookmark
|
||||
import slixfeed.task as task
|
||||
from slixfeed.xmpp.bookmark import XmppBookmark
|
||||
from slixfeed.xmpp.message import XmppMessage
|
||||
from slixfeed.xmpp.status import XmppStatus
|
||||
import tomllib
|
||||
from urllib import error
|
||||
from urllib.parse import parse_qs, urlsplit
|
||||
|
@ -56,28 +59,28 @@ import xml.etree.ElementTree as ET
|
|||
|
||||
try:
|
||||
import xml2epub
|
||||
except:
|
||||
except ImportError:
|
||||
logging.info(
|
||||
"Package xml2epub was not found.\n"
|
||||
"ePUB support is disabled.")
|
||||
|
||||
try:
|
||||
import html2text
|
||||
except:
|
||||
except ImportError:
|
||||
logging.info(
|
||||
"Package html2text was not found.\n"
|
||||
"Markdown support is disabled.")
|
||||
|
||||
try:
|
||||
import pdfkit
|
||||
except:
|
||||
except ImportError:
|
||||
logging.info(
|
||||
"Package pdfkit was not found.\n"
|
||||
"PDF support is disabled.")
|
||||
|
||||
try:
|
||||
from readability import Document
|
||||
except:
|
||||
except ImportError:
|
||||
logging.info(
|
||||
"Package readability was not found.\n"
|
||||
"Arc90 Lab algorithm is disabled.")
|
||||
|
@ -107,6 +110,45 @@ def manual(filename, section=None, command=None):
|
|||
return cmd_list
|
||||
|
||||
|
||||
async def xmpp_change_interval(self, key, val, jid, jid_file, message=None, session=None):
|
||||
if val:
|
||||
# response = (
|
||||
# 'Updates will be sent every {} minutes.'
|
||||
# ).format(response)
|
||||
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])
|
||||
# NOTE Perhaps this should be replaced
|
||||
# by functions clean and start
|
||||
await task.refresh_task(self, jid, task.send_update,
|
||||
key, val)
|
||||
response = ('Updates will be sent every {} minutes.'
|
||||
.format(val))
|
||||
else:
|
||||
response = 'Missing value.'
|
||||
if message:
|
||||
XmppMessage.send_reply(self, message, response)
|
||||
if session:
|
||||
await XmppMessage.send(self, jid, response, chat_type='chat')
|
||||
|
||||
|
||||
async def xmpp_stop_updates(self, message, jid, jid_file):
|
||||
key = 'enabled'
|
||||
val = 0
|
||||
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.clean_tasks_xmpp(jid, ['interval', 'status'])
|
||||
response = 'Updates are disabled.'
|
||||
XmppMessage.send_reply(self, message, response)
|
||||
status_type = 'xa'
|
||||
status_message = '💡️ Send "Start" to receive Jabber updates'
|
||||
await XmppStatus.send(self, jid, status_message, status_type)
|
||||
|
||||
def log_to_markdown(timestamp, filename, jid, message):
|
||||
"""
|
||||
Log message to file.
|
||||
|
@ -404,7 +446,7 @@ def list_feeds(results):
|
|||
|
||||
|
||||
async def list_bookmarks(self):
|
||||
conferences = await bookmark.get(self)
|
||||
conferences = await XmppBookmark.get(self)
|
||||
message = "\nList of groupchats:\n\n```\n"
|
||||
for conference in conferences:
|
||||
message += (
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[action]
|
||||
url = """
|
||||
<url>
|
||||
Add given <url> to subscription list.
|
||||
Add given <url> to subscription list (prefix http. Default).
|
||||
"""
|
||||
add = """
|
||||
add <url> <title>
|
||||
|
@ -30,6 +30,17 @@ backup news text
|
|||
Send a Plain Text file of your news items.
|
||||
"""
|
||||
|
||||
[bookmarks]
|
||||
bookmark = """
|
||||
bookmark [+|-] <muc>
|
||||
Groupchat to add or remove.
|
||||
'+' appends to, '-' removes from.
|
||||
"""
|
||||
bookmarks = """
|
||||
bookmarks
|
||||
List bookmarked groupchats.
|
||||
"""
|
||||
|
||||
[custom]
|
||||
new = """
|
||||
new
|
||||
|
@ -70,10 +81,18 @@ Reset deny list.
|
|||
"""
|
||||
|
||||
[groupchat]
|
||||
uri = """
|
||||
<muc>
|
||||
Join groupchat by given <muc> (prefix xmpp).
|
||||
"""
|
||||
join = """
|
||||
join <muc>
|
||||
Join groupchat by given <muc>.
|
||||
"""
|
||||
leave = """
|
||||
goodbye
|
||||
Leave groupchat and delete it from bookmarks.
|
||||
"""
|
||||
|
||||
[manual]
|
||||
help = """
|
||||
|
@ -122,10 +141,18 @@ Set maximum length of news item description. (0 for no limit)
|
|||
"""
|
||||
quantum = """
|
||||
quantum <number>
|
||||
Set amount of updates per interval by given <number>.
|
||||
Set amount of updates per message by given <number>.
|
||||
"""
|
||||
random = """
|
||||
random
|
||||
Send messages by random order instead of date.
|
||||
"""
|
||||
|
||||
[modification]
|
||||
archive = """
|
||||
archive <number>
|
||||
Number of news items to archive (maximum value 500).
|
||||
"""
|
||||
remove = """
|
||||
remove <id>
|
||||
Remove feed of from subscription list by given <id>.
|
||||
|
@ -158,7 +185,7 @@ Disable bot and stop updates.
|
|||
[preview]
|
||||
read = """
|
||||
read <url>
|
||||
Display most recent 20 titles of given <url>.
|
||||
Display most recent 5 titles of given <url>.
|
||||
"""
|
||||
read_num = """
|
||||
read <url> <index>
|
||||
|
@ -180,10 +207,14 @@ Search news items by given <text>.
|
|||
"""
|
||||
recent = """
|
||||
recent <number>
|
||||
List recent <number> news items (up to 50 items).
|
||||
List recent <number> news items (max. 50).
|
||||
"""
|
||||
|
||||
[statistics]
|
||||
stats = """
|
||||
stats
|
||||
Show general statistics.
|
||||
"""
|
||||
analyses = """
|
||||
analyses
|
||||
Show report and statistics of feeds.
|
||||
|
|
|
@ -61,7 +61,7 @@ No operator was specified for this instance.
|
|||
|
||||
platforms = """
|
||||
Supported platforms: XMPP
|
||||
Platforms to be added in future: Briar, Email, IRC, Matrix, MQTT, Tox.
|
||||
Platforms to be added in future: Briar, Email, IRC, Matrix, MQTT, Nostr, Tox.
|
||||
For ideal experience, we recommend using XMPP.
|
||||
"""
|
||||
|
||||
|
@ -97,27 +97,36 @@ https://gitgud.io/sjehuda/slixfeed
|
|||
"""
|
||||
|
||||
thanks = """
|
||||
Alixander Court (alixandercourt.com, Utah), \
|
||||
Alixander Court <alixandercourt.com> (Utah), \
|
||||
Chriss Farrell (SalixOS, Oregon), \
|
||||
Christian Dersch (SalixOS), \
|
||||
Cyrille Pontvieux (SalixOS, France), \
|
||||
Cyrille Pontvieux <enialis.net> (SalixOS, France), \
|
||||
Denis Fomin (Gajim, Russia), \
|
||||
Dimitris Tzemos (SalixOS, Greece), \
|
||||
Emmanuel Gil Peyrot (Poezio, France), \
|
||||
Florent Le Coz (Poezio, France), \
|
||||
George Vlahavas (SalixOS, Greece), \
|
||||
Guus der Kinderen from IgniteRealtime.org (Openfire, Netherlands), \
|
||||
George Vlahavas <vlahavas.com> (SalixOS, Greece), \
|
||||
Guus der Kinderen <igniterealtime.org> (Openfire, Netherlands), \
|
||||
habnabit_ from #python on irc.libera.chat, \
|
||||
Imar van Erven Dorens <simplicit.nl> (SalixOS, Netherlands), \
|
||||
imattau (atomtopubsub), \
|
||||
Jaussoin Timothée (Movim, France), \
|
||||
Kevin Smith from Isode (Swift, Wales), \
|
||||
Jaussoin Timothée <mov.im> (Movim, France), \
|
||||
Justin Karneges <jblog.andbit.net> (Psi, California), \
|
||||
Kevin Smith <isode.com> (Swift IM, Wales), \
|
||||
Luis Henrique Mello (SalixOS, Brazil), \
|
||||
magicfelix, \
|
||||
Markus Muttilainen (SalixOS), \
|
||||
Mathieu Pasquet (slixmpp, France), \
|
||||
Maxime Buquet (slixmpp, France), \
|
||||
Phillip Watkins (United Kingdom, SalixOS), \
|
||||
Pierrick Le Brun (SalixOS, France), \
|
||||
Raphael Groner (Fedora, Germany), \
|
||||
Remko Tronçon (Swift, Germany), \
|
||||
Simone "roughnecks" Canaletti (woodpeckersnest.space, Italy), \
|
||||
Remko Tronçon <mko.re> (Psi , Belgium), \
|
||||
Simone "roughnecks" Canaletti <woodpeckersnest.space> (Italy), \
|
||||
Richard Lapointe (SalixOS, Connecticut), \
|
||||
Strix from Loqi, \
|
||||
Thibaud Guerin (SalixOS), \
|
||||
Tim Beech (SalixOS, Brazil), \
|
||||
Thorsten Mühlfelder (SalixOS, Germany), \
|
||||
Yann Leboulanger (Gajim, France).
|
||||
"""
|
||||
|
|
|
@ -12,14 +12,17 @@ TODO
|
|||
from slixmpp.plugins.xep_0048.stanza import Bookmarks
|
||||
|
||||
|
||||
async def get(self):
|
||||
class XmppBookmark:
|
||||
|
||||
|
||||
async def get(self):
|
||||
result = await self.plugin['xep_0048'].get_bookmarks()
|
||||
bookmarks = result['private']['bookmarks']
|
||||
conferences = bookmarks['conferences']
|
||||
return conferences
|
||||
|
||||
|
||||
async def add(self, muc_jid):
|
||||
async def add(self, muc_jid):
|
||||
result = await self.plugin['xep_0048'].get_bookmarks()
|
||||
bookmarks = result['private']['bookmarks']
|
||||
conferences = bookmarks['conferences']
|
||||
|
@ -46,7 +49,7 @@ async def add(self, muc_jid):
|
|||
# await self['xep_0402'].publish(bm)
|
||||
|
||||
|
||||
async def remove(self, muc_jid):
|
||||
async def remove(self, muc_jid):
|
||||
result = await self.plugin['xep_0048'].get_bookmarks()
|
||||
bookmarks = result['private']['bookmarks']
|
||||
conferences = bookmarks['conferences']
|
||||
|
|
|
@ -60,7 +60,9 @@ from slixmpp.plugins.xep_0048.stanza import Bookmarks
|
|||
# import xml.etree.ElementTree as ET
|
||||
# from lxml import etree
|
||||
|
||||
import slixfeed.xmpp.bookmark as bookmark
|
||||
import slixfeed.config as config
|
||||
import slixfeed.sqlite as sqlite
|
||||
from slixfeed.xmpp.bookmark import XmppBookmark
|
||||
import slixfeed.xmpp.connect as connect
|
||||
import slixfeed.xmpp.muc as muc
|
||||
import slixfeed.xmpp.process as process
|
||||
|
@ -169,7 +171,7 @@ class Slixfeed(slixmpp.ClientXMPP):
|
|||
inviter = message["from"].bare
|
||||
muc_jid = message['groupchat_invite']['jid']
|
||||
await muc.join(self, inviter, muc_jid)
|
||||
await bookmark.add(self, muc_jid)
|
||||
await XmppBookmark.add(self, muc_jid)
|
||||
|
||||
|
||||
# NOTE Tested with Gajim and Psi
|
||||
|
@ -177,7 +179,7 @@ class Slixfeed(slixmpp.ClientXMPP):
|
|||
inviter = message["from"].bare
|
||||
muc_jid = message['groupchat_invite']['jid']
|
||||
await muc.join(self, inviter, muc_jid)
|
||||
await bookmark.add(self, muc_jid)
|
||||
await XmppBookmark.add(self, muc_jid)
|
||||
|
||||
|
||||
async def on_session_end(self, event):
|
||||
|
@ -191,18 +193,33 @@ class Slixfeed(slixmpp.ClientXMPP):
|
|||
|
||||
|
||||
async def on_session_start(self, event):
|
||||
await process.event(self)
|
||||
self.send_presence()
|
||||
await self["xep_0115"].update_caps()
|
||||
await self.get_roster()
|
||||
await muc.autojoin(self)
|
||||
profile.set_identity(self, "client")
|
||||
await profile.update(self)
|
||||
task.ping_task(self)
|
||||
|
||||
# Service.commands(self)
|
||||
# Service.reactions(self)
|
||||
|
||||
self.service_commands()
|
||||
self.service_reactions()
|
||||
|
||||
|
||||
async def on_session_resumed(self, event):
|
||||
await process.event(self)
|
||||
self.send_presence()
|
||||
self["xep_0115"].update_caps()
|
||||
await muc.autojoin(self)
|
||||
profile.set_identity(self, "client")
|
||||
|
||||
# Service.commands(self)
|
||||
# Service.reactions(self)
|
||||
|
||||
self.service_commands()
|
||||
self.service_reactions()
|
||||
|
||||
|
||||
# TODO Request for subscription
|
||||
async def on_message(self, message):
|
||||
|
@ -305,3 +322,301 @@ class Slixfeed(slixmpp.ClientXMPP):
|
|||
jid = message['from'].bare
|
||||
# await task.clean_tasks_xmpp(jid, ['status'])
|
||||
await task.start_tasks_xmpp(self, jid, ['status'])
|
||||
|
||||
|
||||
# TODO Move class Service to a separate file
|
||||
# class Service(Slixfeed):
|
||||
# def __init__(self):
|
||||
# super().__init__()
|
||||
|
||||
# TODO https://xmpp.org/extensions/xep-0115.html
|
||||
# https://xmpp.org/extensions/xep-0444.html#disco
|
||||
|
||||
|
||||
# TODO https://xmpp.org/extensions/xep-0444.html#disco-restricted
|
||||
def service_reactions(self):
|
||||
"""
|
||||
Publish allow list of reactions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None.
|
||||
|
||||
"""
|
||||
form = self['xep_0004'].make_form(
|
||||
'form', 'Reactions Information'
|
||||
)
|
||||
|
||||
|
||||
# TODO Move class Command to a separate file
|
||||
# class Command(Slixfeed):
|
||||
# def __init__(self):
|
||||
# super().__init__()
|
||||
|
||||
|
||||
def service_commands(self):
|
||||
# self["xep_0050"].add_command(
|
||||
# node="updates_enable",
|
||||
# name="Enable/Disable News Updates",
|
||||
# handler=option_enable_updates,
|
||||
# )
|
||||
|
||||
# if jid == config.get_value('accounts', 'XMPP', 'operator'):
|
||||
# self['xep_0050'].add_command(node='bookmarks',
|
||||
# name='Bookmarks',
|
||||
# handler=self._handle_bookmarks)
|
||||
# self['xep_0050'].add_command(node='roster',
|
||||
# name='Roster',
|
||||
# handler=self._handle_roster)
|
||||
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)
|
||||
# self['xep_0050'].add_command(node='search',
|
||||
# name='Search',
|
||||
# handler=self._handle_search)
|
||||
# self['xep_0050'].add_command(node='filters',
|
||||
# name='Filters',
|
||||
# handler=self._handle_filters)
|
||||
|
||||
|
||||
async def _handle_subscriptions(self, iq, session):
|
||||
form = self['xep_0004'].make_form('form', 'Subscriptions')
|
||||
form['instructions'] = '📰️ Manage subscriptions.'
|
||||
# form.addField(var='interval',
|
||||
# ftype='text-single',
|
||||
# label='Interval period')
|
||||
options = form.add_field(var='subscriptions',
|
||||
ftype='list-multi',
|
||||
label='Select subscriptions',
|
||||
desc='Select subscription(s) to edit.')
|
||||
jid = session['from'].bare
|
||||
jid_file = jid
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
subscriptions = await sqlite.get_feeds(db_file)
|
||||
for subscription in subscriptions:
|
||||
title = subscription[0]
|
||||
url = subscription[1]
|
||||
options.addOption(title, url)
|
||||
session['payload'] = form
|
||||
session['next'] = self._handle_subscription_editor
|
||||
session['has_next'] = True
|
||||
# Other useful session values:
|
||||
# session['to'] -- The JID that received the
|
||||
# command request.
|
||||
# session['from'] -- The JID that sent the
|
||||
# command request.
|
||||
# session['has_next'] = True -- There are more steps to complete
|
||||
# session['allow_complete'] = True -- Allow user to finish immediately
|
||||
# and possibly skip steps
|
||||
# session['cancel'] = handler -- Assign a handler for if the user
|
||||
# cancels the command.
|
||||
# session['notes'] = [ -- Add informative notes about the
|
||||
# ('info', 'Info message'), command's results.
|
||||
# ('warning', 'Warning message'),
|
||||
# ('error', 'Error message')]
|
||||
return session
|
||||
|
||||
|
||||
# TODO Make form for a single subscription and several subscriptions
|
||||
# single: Delete, Disable, Reset and Rename
|
||||
# several: Delete, Disable, Reset
|
||||
async def _handle_subscription_editor(self, iq, session):
|
||||
form = self['xep_0004'].make_form('form', 'Subscriptions')
|
||||
form['instructions'] = '🗞️ Edit subscriptions.'
|
||||
options = form.add_field(var='enable',
|
||||
ftype='boolean',
|
||||
label='Enable',
|
||||
value=True)
|
||||
options = form.add_field(var='action',
|
||||
ftype='list-single',
|
||||
label='Action',
|
||||
value='reset')
|
||||
options.addOption('Delete', 'delete')
|
||||
options.addOption('Reset', 'reset')
|
||||
session['payload'] = form
|
||||
session['next'] = None
|
||||
session['has_next'] = False
|
||||
return session
|
||||
|
||||
|
||||
async def _handle_bookmarks(self, iq, session):
|
||||
form = self['xep_0004'].make_form('form', 'Bookmarks')
|
||||
form['instructions'] = '📑️ Organize bookmarks.'
|
||||
options = form.add_field(var='bookmarks',
|
||||
# ftype='list-multi'
|
||||
ftype='list-single',
|
||||
label='Select a bookmark',
|
||||
desc='Select a bookmark to edit.')
|
||||
conferences = await XmppBookmark.get(self)
|
||||
for conference in conferences:
|
||||
options.addOption(conference['jid'], conference['jid'])
|
||||
session['payload'] = form
|
||||
session['next'] = self._handle_command_complete
|
||||
session['has_next'] = False
|
||||
return session
|
||||
|
||||
|
||||
async def _handle_bookmarks_editor(self, iq, session):
|
||||
form = self['xep_0004'].make_form('form', 'Bookmarks')
|
||||
form['instructions'] = '📝️ Edit bookmarks.'
|
||||
form.addField(var='name',
|
||||
ftype='text-single',
|
||||
label='Name')
|
||||
form.addField(var='host',
|
||||
ftype='text-single',
|
||||
label='Host',
|
||||
required=True)
|
||||
form.addField(var='room',
|
||||
ftype='text-single',
|
||||
label='Room',
|
||||
required=True)
|
||||
form.addField(var='alias',
|
||||
ftype='text-single',
|
||||
label='Alias')
|
||||
form.addField(var='password',
|
||||
ftype='text-private',
|
||||
label='Password')
|
||||
form.add_field(var='autojoin',
|
||||
ftype='boolean',
|
||||
label='Auto-join',
|
||||
value=True)
|
||||
options = form.add_field(var='action',
|
||||
ftype='list-single',
|
||||
label='Action',
|
||||
value='join')
|
||||
options.addOption('Add', 'add')
|
||||
options.addOption('Join', 'join')
|
||||
options.addOption('Remove', 'remove')
|
||||
session['payload'] = form
|
||||
session['next'] = None
|
||||
session['has_next'] = False
|
||||
return session
|
||||
|
||||
|
||||
async def _handle_settings(self, iq, session):
|
||||
"""
|
||||
Respond to the initial request for a command.
|
||||
|
||||
Arguments:
|
||||
iq -- The iq stanza containing the command request.
|
||||
session -- A dictionary of data relevant to the command
|
||||
session. Additional, custom data may be saved
|
||||
here to persist across handler callbacks.
|
||||
"""
|
||||
form = self['xep_0004'].make_form('form', 'Settings')
|
||||
form['instructions'] = ('📮️ Customize news updates.')
|
||||
jid = session['from'].bare
|
||||
jid_file = jid
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
value = await config.get_setting_value(db_file, 'enabled')
|
||||
value = int(value)
|
||||
if value:
|
||||
value = True
|
||||
else:
|
||||
value = False
|
||||
form.add_field(var='enabled',
|
||||
ftype='boolean',
|
||||
label='Enable',
|
||||
desc='Enable news updates.',
|
||||
value=value)
|
||||
value = await config.get_setting_value(db_file, 'old')
|
||||
value = int(value)
|
||||
if value:
|
||||
value = False
|
||||
else:
|
||||
value = True
|
||||
form.add_field(var='old',
|
||||
ftype='boolean',
|
||||
desc='Mark items of newly added subscriptions as read.',
|
||||
# label='Send only new items',
|
||||
label='Include old news',
|
||||
value=value)
|
||||
value = await config.get_setting_value(db_file, 'interval')
|
||||
value = str(int(value/60))
|
||||
options = form.add_field(var='interval',
|
||||
ftype='list-single',
|
||||
label='Interval',
|
||||
desc='Set interval update (in hours).',
|
||||
value=value)
|
||||
i = 60
|
||||
while i <= 2880:
|
||||
var = str(i)
|
||||
lab = str(int(i/60))
|
||||
options.addOption(lab, var)
|
||||
i += 60
|
||||
value = await config.get_setting_value(db_file, 'archive')
|
||||
value = str(value)
|
||||
options = form.add_field(var='archive',
|
||||
ftype='list-single',
|
||||
label='Archive',
|
||||
desc='Number of news items to archive.',
|
||||
value=value)
|
||||
i = 0
|
||||
while i <= 500:
|
||||
x = str(i)
|
||||
options.addOption(x, x)
|
||||
i += 1
|
||||
value = await config.get_setting_value(db_file, 'quantum')
|
||||
value = str(value)
|
||||
options = form.add_field(var='quantum',
|
||||
ftype='list-single',
|
||||
label='Amount',
|
||||
desc='Set amount of updates per update.',
|
||||
value='3')
|
||||
i = 1
|
||||
while i <= 10:
|
||||
x = str(i)
|
||||
options.addOption(x, x)
|
||||
i += 1
|
||||
session['payload'] = form
|
||||
session['next'] = self._handle_settings_complete
|
||||
session['has_next'] = False
|
||||
return session
|
||||
|
||||
|
||||
async def _handle_settings_complete(self, payload, session):
|
||||
"""
|
||||
Process a command result from the user.
|
||||
|
||||
Arguments:
|
||||
payload -- Either a single item, such as a form, or a list
|
||||
of items or forms if more than one form was
|
||||
provided to the user. The payload may be any
|
||||
stanza, such as jabber:x:oob for out of band
|
||||
data, or jabber:x:data for typical data forms.
|
||||
session -- A dictionary of data relevant to the command
|
||||
session. Additional, custom data may be saved
|
||||
here to persist across handler callbacks.
|
||||
"""
|
||||
|
||||
jid = session['from'].bare
|
||||
jid_file = jid
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
# In this case (as is typical), the payload is a form
|
||||
form = payload
|
||||
values = form['values']
|
||||
for value in values:
|
||||
key = value
|
||||
val = values[value]
|
||||
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])
|
||||
match value:
|
||||
case 'enabled':
|
||||
pass
|
||||
case 'interval':
|
||||
pass
|
||||
# Having no return statement is the same as unsetting the 'payload'
|
||||
# and 'next' session values and returning the session.
|
||||
# Unless it is the final step, always return the session dictionary.
|
||||
session['payload'] = None
|
||||
session['next'] = None
|
||||
return session
|
||||
|
|
|
@ -60,7 +60,9 @@ from slixmpp.plugins.xep_0048.stanza import Bookmarks
|
|||
# import xml.etree.ElementTree as ET
|
||||
# from lxml import etree
|
||||
|
||||
# import slixfeed.xmpp.bookmark as bookmark
|
||||
import slixfeed.config as config
|
||||
import slixfeed.sqlite as sqlite
|
||||
from slixfeed.xmpp.bookmark import XmppBookmark
|
||||
import slixfeed.xmpp.connect as connect
|
||||
# NOTE MUC is possible for component
|
||||
# import slixfeed.xmpp.muc as muc
|
||||
|
@ -97,35 +99,54 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
|
|||
# and the XML streams are ready for use. We want to
|
||||
# listen for this event so that we we can initialize
|
||||
# our roster.
|
||||
self.add_event_handler("session_start", self.on_session_start)
|
||||
self.add_event_handler("session_resumed", self.on_session_resumed)
|
||||
self.add_event_handler("session_start",
|
||||
self.on_session_start)
|
||||
self.add_event_handler("session_resumed",
|
||||
self.on_session_resumed)
|
||||
self.add_event_handler("got_offline", print("got_offline"))
|
||||
# self.add_event_handler("got_online", self.check_readiness)
|
||||
self.add_event_handler("changed_status", self.on_changed_status)
|
||||
self.add_event_handler("presence_available", self.on_presence_available)
|
||||
self.add_event_handler("presence_unavailable", self.on_presence_unavailable)
|
||||
self.add_event_handler("chatstate_active", self.on_chatstate_active)
|
||||
self.add_event_handler("chatstate_gone", self.on_chatstate_gone)
|
||||
self.add_event_handler("chatstate_composing", self.check_chatstate_composing)
|
||||
self.add_event_handler("chatstate_paused", self.check_chatstate_paused)
|
||||
self.add_event_handler("changed_status",
|
||||
self.on_changed_status)
|
||||
self.add_event_handler("presence_available",
|
||||
self.on_presence_available)
|
||||
self.add_event_handler("presence_unavailable",
|
||||
self.on_presence_unavailable)
|
||||
self.add_event_handler("chatstate_active",
|
||||
self.on_chatstate_active)
|
||||
self.add_event_handler("chatstate_composing",
|
||||
self.on_chatstate_composing)
|
||||
self.add_event_handler("chatstate_gone",
|
||||
self.on_chatstate_gone)
|
||||
self.add_event_handler("chatstate_inactive",
|
||||
self.on_chatstate_inactive)
|
||||
self.add_event_handler("chatstate_paused",
|
||||
self.on_chatstate_paused)
|
||||
|
||||
# The message event is triggered whenever a message
|
||||
# stanza is received. Be aware that that includes
|
||||
# MUC messages and error messages.
|
||||
self.add_event_handler("message", self.on_message)
|
||||
self.add_event_handler("message",
|
||||
self.on_message)
|
||||
|
||||
self.add_event_handler("groupchat_invite", self.on_groupchat_invite) # XEP_0045
|
||||
self.add_event_handler("groupchat_direct_invite", self.on_groupchat_direct_invite) # XEP_0249
|
||||
self.add_event_handler("groupchat_invite",
|
||||
self.on_groupchat_invite) # XEP_0045
|
||||
self.add_event_handler("groupchat_direct_invite",
|
||||
self.on_groupchat_direct_invite) # XEP_0249
|
||||
# self.add_event_handler("groupchat_message", self.message)
|
||||
|
||||
# self.add_event_handler("disconnected", self.reconnect)
|
||||
# self.add_event_handler("disconnected", self.inspect_connection)
|
||||
|
||||
self.add_event_handler("reactions", self.on_reactions)
|
||||
self.add_event_handler("presence_error", self.on_presence_error)
|
||||
self.add_event_handler("presence_subscribe", self.on_presence_subscribe)
|
||||
self.add_event_handler("presence_subscribed", self.on_presence_subscribed)
|
||||
self.add_event_handler("presence_unsubscribed", self.on_presence_unsubscribed)
|
||||
self.add_event_handler("reactions",
|
||||
self.on_reactions)
|
||||
self.add_event_handler("presence_error",
|
||||
self.on_presence_error)
|
||||
self.add_event_handler("presence_subscribe",
|
||||
self.on_presence_subscribe)
|
||||
self.add_event_handler("presence_subscribed",
|
||||
self.on_presence_subscribed)
|
||||
self.add_event_handler("presence_unsubscribed",
|
||||
self.on_presence_unsubscribed)
|
||||
|
||||
# Initialize event loop
|
||||
# self.loop = asyncio.get_event_loop()
|
||||
|
@ -165,31 +186,28 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
|
|||
|
||||
async def on_session_start(self, event):
|
||||
self.send_presence()
|
||||
await process.event_component(self)
|
||||
await self["xep_0115"].update_caps()
|
||||
# await muc.autojoin(self)
|
||||
profile.set_identity(self, "service")
|
||||
await profile.update(self)
|
||||
connect.ping_task(self)
|
||||
task.ping_task(self)
|
||||
|
||||
# await Service.capabilities(self)
|
||||
# Service.commands(self)
|
||||
# Service.reactions(self)
|
||||
|
||||
await self.service_capabilities()
|
||||
self.service_commands()
|
||||
self.service_reactions()
|
||||
|
||||
|
||||
async def on_session_resumed(self, event):
|
||||
await process.event_component(self)
|
||||
self.send_presence()
|
||||
self["xep_0115"].update_caps()
|
||||
# await muc.autojoin(self)
|
||||
profile.set_identity(self, "service")
|
||||
|
||||
# await Service.capabilities(self)
|
||||
# Service.commands(self)
|
||||
# Service.reactions(self)
|
||||
|
||||
await self.service_capabilities()
|
||||
self.service_commands()
|
||||
self.service_reactions()
|
||||
|
||||
|
@ -207,7 +225,11 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
|
|||
|
||||
|
||||
async def on_changed_status(self, presence):
|
||||
await task.check_readiness(self, presence)
|
||||
# await task.check_readiness(self, presence)
|
||||
jid = presence['from'].bare
|
||||
if presence['show'] in ('away', 'dnd', 'xa'):
|
||||
await task.clean_tasks_xmpp(jid, ['interval'])
|
||||
await task.start_tasks_xmpp(self, jid, ['status', 'check'])
|
||||
|
||||
|
||||
async def on_presence_subscribe(self, presence):
|
||||
|
@ -228,7 +250,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
|
|||
|
||||
async def on_presence_available(self, presence):
|
||||
# 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
|
||||
jid = presence["from"].bare
|
||||
await task.start_tasks_xmpp(self, jid)
|
||||
|
||||
|
||||
async def on_presence_unsubscribed(self, presence):
|
||||
|
@ -237,12 +262,20 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
|
|||
|
||||
async def on_presence_unavailable(self, presence):
|
||||
jid = presence["from"].bare
|
||||
await task.stop_tasks(self, jid)
|
||||
# await task.stop_tasks(self, jid)
|
||||
await task.clean_tasks_xmpp(jid)
|
||||
|
||||
|
||||
# TODO
|
||||
# Send message that database will be deleted within 30 days
|
||||
# Check whether JID is in bookmarks or roster
|
||||
# If roster, remove contact JID into file
|
||||
# If bookmarks, remove groupchat JID into file
|
||||
async def on_presence_error(self, presence):
|
||||
print("on_presence_error")
|
||||
print(presence)
|
||||
jid = presence["from"].bare
|
||||
await task.clean_tasks_xmpp(jid)
|
||||
|
||||
|
||||
async def on_reactions(self, message):
|
||||
|
@ -253,28 +286,332 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
|
|||
async def on_chatstate_active(self, message):
|
||||
if message['type'] in ('chat', 'normal'):
|
||||
jid = message['from'].bare
|
||||
await task.clean_tasks_xmpp(jid, ['status'])
|
||||
# await task.clean_tasks_xmpp(jid, ['status'])
|
||||
await task.start_tasks_xmpp(self, jid, ['status'])
|
||||
|
||||
|
||||
async def on_chatstate_composing(self, message):
|
||||
if message['type'] in ('chat', 'normal'):
|
||||
jid = message['from'].bare
|
||||
# await task.clean_tasks_xmpp(jid, ['status'])
|
||||
status_text='Press "help" for manual, or "info" for information.'
|
||||
status.send(self, jid, status_text)
|
||||
|
||||
|
||||
async def on_chatstate_gone(self, message):
|
||||
if message['type'] in ('chat', 'normal'):
|
||||
jid = message['from'].bare
|
||||
await task.clean_tasks_xmpp(jid, ['status'])
|
||||
# await task.clean_tasks_xmpp(jid, ['status'])
|
||||
await task.start_tasks_xmpp(self, jid, ['status'])
|
||||
|
||||
|
||||
async def check_chatstate_composing(self, message):
|
||||
async def on_chatstate_inactive(self, message):
|
||||
if message['type'] in ('chat', 'normal'):
|
||||
jid = message['from'].bare
|
||||
await task.clean_tasks_xmpp(jid, ['status'])
|
||||
status_text='Press "help" for manual, or "info" for information.'
|
||||
status.send(self, jid, status_text)
|
||||
|
||||
|
||||
async def check_chatstate_paused(self, message):
|
||||
if message['type'] in ('chat', 'normal'):
|
||||
jid = message['from'].bare
|
||||
await task.clean_tasks_xmpp(jid, ['status'])
|
||||
# await task.clean_tasks_xmpp(jid, ['status'])
|
||||
await task.start_tasks_xmpp(self, jid, ['status'])
|
||||
|
||||
|
||||
async def on_chatstate_paused(self, message):
|
||||
if message['type'] in ('chat', 'normal'):
|
||||
jid = message['from'].bare
|
||||
# await task.clean_tasks_xmpp(jid, ['status'])
|
||||
await task.start_tasks_xmpp(self, jid, ['status'])
|
||||
|
||||
|
||||
# TODO Move class Service to a separate file
|
||||
# class Service(Slixfeed):
|
||||
# def __init__(self):
|
||||
# super().__init__()
|
||||
|
||||
# TODO https://xmpp.org/extensions/xep-0115.html
|
||||
# https://xmpp.org/extensions/xep-0444.html#disco
|
||||
|
||||
|
||||
# TODO https://xmpp.org/extensions/xep-0444.html#disco-restricted
|
||||
def service_reactions(self):
|
||||
"""
|
||||
Publish allow list of reactions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None.
|
||||
|
||||
"""
|
||||
form = self['xep_0004'].make_form(
|
||||
'form', 'Reactions Information'
|
||||
)
|
||||
|
||||
|
||||
# TODO Move class Command to a separate file
|
||||
# class Command(Slixfeed):
|
||||
# def __init__(self):
|
||||
# super().__init__()
|
||||
|
||||
|
||||
def service_commands(self):
|
||||
# self["xep_0050"].add_command(
|
||||
# node="updates_enable",
|
||||
# name="Enable/Disable News Updates",
|
||||
# handler=option_enable_updates,
|
||||
# )
|
||||
|
||||
# if jid == config.get_value('accounts', 'XMPP', 'operator'):
|
||||
# self['xep_0050'].add_command(node='bookmarks',
|
||||
# name='Bookmarks',
|
||||
# handler=self._handle_bookmarks)
|
||||
# self['xep_0050'].add_command(node='roster',
|
||||
# name='Roster',
|
||||
# handler=self._handle_roster)
|
||||
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)
|
||||
# self['xep_0050'].add_command(node='search',
|
||||
# name='Search',
|
||||
# handler=self._handle_search)
|
||||
# self['xep_0050'].add_command(node='filters',
|
||||
# name='Filters',
|
||||
# handler=self._handle_filters)
|
||||
|
||||
|
||||
async def _handle_subscriptions(self, iq, session):
|
||||
form = self['xep_0004'].make_form('form', 'Subscriptions')
|
||||
form['instructions'] = '📰️ Manage subscriptions.'
|
||||
# form.addField(var='interval',
|
||||
# ftype='text-single',
|
||||
# label='Interval period')
|
||||
options = form.add_field(var='subscriptions',
|
||||
ftype='list-multi',
|
||||
label='Select subscriptions',
|
||||
desc='Select subscription(s) to edit.')
|
||||
jid = session['from'].bare
|
||||
jid_file = jid
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
subscriptions = await sqlite.get_feeds(db_file)
|
||||
for subscription in subscriptions:
|
||||
title = subscription[0]
|
||||
url = subscription[1]
|
||||
options.addOption(title, url)
|
||||
session['payload'] = form
|
||||
session['next'] = self._handle_subscription_editor
|
||||
session['has_next'] = True
|
||||
# Other useful session values:
|
||||
# session['to'] -- The JID that received the
|
||||
# command request.
|
||||
# session['from'] -- The JID that sent the
|
||||
# command request.
|
||||
# session['has_next'] = True -- There are more steps to complete
|
||||
# session['allow_complete'] = True -- Allow user to finish immediately
|
||||
# and possibly skip steps
|
||||
# session['cancel'] = handler -- Assign a handler for if the user
|
||||
# cancels the command.
|
||||
# session['notes'] = [ -- Add informative notes about the
|
||||
# ('info', 'Info message'), command's results.
|
||||
# ('warning', 'Warning message'),
|
||||
# ('error', 'Error message')]
|
||||
return session
|
||||
|
||||
|
||||
# TODO Make form for a single subscription and several subscriptions
|
||||
# single: Delete, Disable, Reset and Rename
|
||||
# several: Delete, Disable, Reset
|
||||
async def _handle_subscription_editor(self, iq, session):
|
||||
form = self['xep_0004'].make_form('form', 'Subscriptions')
|
||||
form['instructions'] = '🗞️ Edit subscriptions.'
|
||||
options = form.add_field(var='enable',
|
||||
ftype='boolean',
|
||||
label='Enable',
|
||||
value=True)
|
||||
options = form.add_field(var='action',
|
||||
ftype='list-single',
|
||||
label='Action',
|
||||
value='reset')
|
||||
options.addOption('Delete', 'delete')
|
||||
options.addOption('Reset', 'reset')
|
||||
session['payload'] = form
|
||||
session['next'] = None
|
||||
session['has_next'] = False
|
||||
return session
|
||||
|
||||
|
||||
async def _handle_bookmarks(self, iq, session):
|
||||
form = self['xep_0004'].make_form('form', 'Bookmarks')
|
||||
form['instructions'] = '📑️ Organize bookmarks.'
|
||||
options = form.add_field(var='bookmarks',
|
||||
# ftype='list-multi'
|
||||
ftype='list-single',
|
||||
label='Select a bookmark',
|
||||
desc='Select a bookmark to edit.')
|
||||
conferences = await XmppBookmark.get(self)
|
||||
for conference in conferences:
|
||||
options.addOption(conference['jid'], conference['jid'])
|
||||
session['payload'] = form
|
||||
session['next'] = self._handle_command_complete
|
||||
session['has_next'] = False
|
||||
return session
|
||||
|
||||
|
||||
async def _handle_bookmarks_editor(self, iq, session):
|
||||
form = self['xep_0004'].make_form('form', 'Bookmarks')
|
||||
form['instructions'] = '📝️ Edit bookmarks.'
|
||||
form.addField(var='name',
|
||||
ftype='text-single',
|
||||
label='Name')
|
||||
form.addField(var='host',
|
||||
ftype='text-single',
|
||||
label='Host',
|
||||
required=True)
|
||||
form.addField(var='room',
|
||||
ftype='text-single',
|
||||
label='Room',
|
||||
required=True)
|
||||
form.addField(var='alias',
|
||||
ftype='text-single',
|
||||
label='Alias')
|
||||
form.addField(var='password',
|
||||
ftype='text-private',
|
||||
label='Password')
|
||||
form.add_field(var='autojoin',
|
||||
ftype='boolean',
|
||||
label='Auto-join',
|
||||
value=True)
|
||||
options = form.add_field(var='action',
|
||||
ftype='list-single',
|
||||
label='Action',
|
||||
value='join')
|
||||
options.addOption('Add', 'add')
|
||||
options.addOption('Join', 'join')
|
||||
options.addOption('Remove', 'remove')
|
||||
session['payload'] = form
|
||||
session['next'] = None
|
||||
session['has_next'] = False
|
||||
return session
|
||||
|
||||
|
||||
async def _handle_settings(self, iq, session):
|
||||
"""
|
||||
Respond to the initial request for a command.
|
||||
|
||||
Arguments:
|
||||
iq -- The iq stanza containing the command request.
|
||||
session -- A dictionary of data relevant to the command
|
||||
session. Additional, custom data may be saved
|
||||
here to persist across handler callbacks.
|
||||
"""
|
||||
form = self['xep_0004'].make_form('form', 'Settings')
|
||||
form['instructions'] = ('📮️ Customize news updates.')
|
||||
jid = session['from'].bare
|
||||
jid_file = jid
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
value = await config.get_setting_value(db_file, 'enabled')
|
||||
value = int(value)
|
||||
if value:
|
||||
value = True
|
||||
else:
|
||||
value = False
|
||||
form.add_field(var='enabled',
|
||||
ftype='boolean',
|
||||
label='Enable',
|
||||
desc='Enable news updates.',
|
||||
value=value)
|
||||
value = await config.get_setting_value(db_file, 'old')
|
||||
value = int(value)
|
||||
if value:
|
||||
value = False
|
||||
else:
|
||||
value = True
|
||||
form.add_field(var='old',
|
||||
ftype='boolean',
|
||||
desc='Mark items of newly added subscriptions as read.',
|
||||
# label='Send only new items',
|
||||
label='Include old news',
|
||||
value=value)
|
||||
value = await config.get_setting_value(db_file, 'interval')
|
||||
value = str(int(value/60))
|
||||
options = form.add_field(var='interval',
|
||||
ftype='list-single',
|
||||
label='Interval',
|
||||
desc='Set interval update (in hours).',
|
||||
value=value)
|
||||
i = 60
|
||||
while i <= 2880:
|
||||
var = str(i)
|
||||
lab = str(int(i/60))
|
||||
options.addOption(lab, var)
|
||||
i += 60
|
||||
value = await config.get_setting_value(db_file, 'archive')
|
||||
value = str(value)
|
||||
options = form.add_field(var='archive',
|
||||
ftype='list-single',
|
||||
label='Archive',
|
||||
desc='Number of news items to archive.',
|
||||
value=value)
|
||||
i = 0
|
||||
while i <= 500:
|
||||
x = str(i)
|
||||
options.addOption(x, x)
|
||||
i += 1
|
||||
value = await config.get_setting_value(db_file, 'quantum')
|
||||
value = str(value)
|
||||
options = form.add_field(var='quantum',
|
||||
ftype='list-single',
|
||||
label='Amount',
|
||||
desc='Set amount of updates per update.',
|
||||
value='3')
|
||||
i = 1
|
||||
while i <= 10:
|
||||
x = str(i)
|
||||
options.addOption(x, x)
|
||||
i += 1
|
||||
session['payload'] = form
|
||||
session['next'] = self._handle_settings_complete
|
||||
session['has_next'] = False
|
||||
return session
|
||||
|
||||
|
||||
async def _handle_settings_complete(self, payload, session):
|
||||
"""
|
||||
Process a command result from the user.
|
||||
|
||||
Arguments:
|
||||
payload -- Either a single item, such as a form, or a list
|
||||
of items or forms if more than one form was
|
||||
provided to the user. The payload may be any
|
||||
stanza, such as jabber:x:oob for out of band
|
||||
data, or jabber:x:data for typical data forms.
|
||||
session -- A dictionary of data relevant to the command
|
||||
session. Additional, custom data may be saved
|
||||
here to persist across handler callbacks.
|
||||
"""
|
||||
|
||||
jid = session['from'].bare
|
||||
jid_file = jid
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
# In this case (as is typical), the payload is a form
|
||||
form = payload
|
||||
values = form['values']
|
||||
for value in values:
|
||||
key = value
|
||||
val = values[value]
|
||||
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])
|
||||
match value:
|
||||
case 'enabled':
|
||||
pass
|
||||
case 'interval':
|
||||
pass
|
||||
# Having no return statement is the same as unsetting the 'payload'
|
||||
# and 'next' session values and returning the session.
|
||||
# Unless it is the final step, always return the session dictionary.
|
||||
session['payload'] = None
|
||||
session['next'] = None
|
||||
return session
|
||||
|
|
37
slixfeed/xmpp/message.py
Normal file
37
slixfeed/xmpp/message.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from slixfeed.xmpp.utility import get_chat_type
|
||||
|
||||
|
||||
class XmppMessage:
|
||||
|
||||
async def send(self, jid, message, chat_type=None):
|
||||
if not chat_type:
|
||||
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):
|
||||
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 send_reply(self, message, response):
|
||||
message.reply(response).send()
|
|
@ -17,7 +17,6 @@ FIXME
|
|||
|
||||
"""
|
||||
import logging
|
||||
import slixfeed.xmpp.bookmark as bookmark
|
||||
import slixfeed.xmpp.process as process
|
||||
from slixfeed.dt import current_time
|
||||
|
||||
|
|
|
@ -21,32 +21,19 @@ TODO
|
|||
import logging
|
||||
import os
|
||||
import slixfeed.action as action
|
||||
from slixfeed.config import (
|
||||
add_to_list,
|
||||
get_default_cache_directory,
|
||||
get_default_data_directory,
|
||||
get_value,
|
||||
get_pathname_to_database,
|
||||
remove_from_list)
|
||||
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
|
||||
import slixfeed.xmpp.bookmark as bookmark
|
||||
from slixfeed.xmpp.bookmark import XmppBookmark
|
||||
import slixfeed.xmpp.muc as groupchat
|
||||
import slixfeed.xmpp.status as status
|
||||
from slixfeed.xmpp.status import XmppStatus
|
||||
import slixfeed.xmpp.upload as upload
|
||||
from slixfeed.xmpp.utility import get_chat_type
|
||||
import time
|
||||
|
||||
async def event_component(self):
|
||||
self.send_presence()
|
||||
|
||||
|
||||
async def event(self):
|
||||
self.send_presence()
|
||||
await self.get_roster()
|
||||
|
||||
# for task in main_task:
|
||||
# task.cancel()
|
||||
|
@ -101,9 +88,8 @@ async def message(self, message):
|
|||
await task.clean_tasks_xmpp(jid, ['status'])
|
||||
status_type = 'dnd'
|
||||
status_message = '📥️ Procesing request to import feeds...'
|
||||
status.send(
|
||||
self, jid, status_message, status_type)
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
await XmppStatus.send(self, jid, status_message, status_type)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
count = await action.import_opml(db_file, url)
|
||||
if count:
|
||||
response = 'Successfully imported {} feeds.'.format(count)
|
||||
|
@ -163,7 +149,7 @@ async def message(self, message):
|
|||
|
||||
# # Begin processing new JID
|
||||
# # Deprecated in favour of event 'presence_available'
|
||||
# db_dir = get_default_data_directory()
|
||||
# db_dir = config.get_default_data_directory()
|
||||
# os.chdir(db_dir)
|
||||
# if jid + '.db' not in os.listdir():
|
||||
# await task_jid(jid)
|
||||
|
@ -301,7 +287,7 @@ async def message(self, message):
|
|||
if not title:
|
||||
title = uri.get_hostname(url)
|
||||
if url.startswith('http'):
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
exist = await sqlite.get_feed_id_and_name(db_file, url)
|
||||
if not exist:
|
||||
await sqlite.insert_feed(db_file, url, title)
|
||||
|
@ -334,9 +320,9 @@ async def message(self, message):
|
|||
key = 'filter-' + message_text[:5]
|
||||
val = message_text[7:]
|
||||
if val:
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
keywords = await sqlite.get_filters_value(db_file, key)
|
||||
val = await add_to_list(val, keywords)
|
||||
val = await config.add_to_list(val, keywords)
|
||||
if await sqlite.get_filters_value(db_file, key):
|
||||
await sqlite.update_filters_value(db_file,
|
||||
[key, val])
|
||||
|
@ -352,9 +338,9 @@ async def message(self, message):
|
|||
key = 'filter-' + message_text[:5]
|
||||
val = message_text[7:]
|
||||
if val:
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
keywords = await sqlite.get_filters_value(db_file, key)
|
||||
val = await remove_from_list(val, keywords)
|
||||
val = await config.remove_from_list(val, keywords)
|
||||
if await sqlite.get_filters_value(db_file, key):
|
||||
await sqlite.update_filters_value(db_file,
|
||||
[key, val])
|
||||
|
@ -374,7 +360,7 @@ async def message(self, message):
|
|||
if int(val) > 500:
|
||||
response = 'Value may not be greater than 500.'
|
||||
else:
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
if await sqlite.get_settings_value(db_file,
|
||||
[key, val]):
|
||||
await sqlite.update_settings_value(db_file,
|
||||
|
@ -391,9 +377,9 @@ async def message(self, message):
|
|||
response = 'Missing value.'
|
||||
send_reply_message(self, message, response)
|
||||
case _ if message_lowercase.startswith('bookmark -'):
|
||||
if jid == get_value('accounts', 'XMPP', 'operator'):
|
||||
if jid == config.get_value('accounts', 'XMPP', 'operator'):
|
||||
muc_jid = message_text[11:]
|
||||
await bookmark.remove(self, muc_jid)
|
||||
await XmppBookmark.remove(self, muc_jid)
|
||||
response = ('Groupchat {} has been removed '
|
||||
'from bookmarks.'
|
||||
.format(muc_jid))
|
||||
|
@ -402,7 +388,7 @@ async def message(self, message):
|
|||
'Type: removing bookmarks.')
|
||||
send_reply_message(self, message, response)
|
||||
case 'bookmarks':
|
||||
if jid == get_value('accounts', 'XMPP', 'operator'):
|
||||
if jid == config.get_value('accounts', 'XMPP', 'operator'):
|
||||
response = await action.list_bookmarks(self)
|
||||
else:
|
||||
response = ('This action is restricted. '
|
||||
|
@ -412,9 +398,9 @@ async def message(self, message):
|
|||
key = 'filter-' + message_text[:4]
|
||||
val = message_text[6:]
|
||||
if val:
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
keywords = await sqlite.get_filters_value(db_file, key)
|
||||
val = await add_to_list(val, keywords)
|
||||
val = await config.add_to_list(val, keywords)
|
||||
if await sqlite.get_filters_value(db_file, key):
|
||||
await sqlite.update_filters_value(db_file,
|
||||
[key, val])
|
||||
|
@ -430,9 +416,9 @@ async def message(self, message):
|
|||
key = 'filter-' + message_text[:4]
|
||||
val = message_text[6:]
|
||||
if val:
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
keywords = await sqlite.get_filters_value(db_file, key)
|
||||
val = await remove_from_list(val, keywords)
|
||||
val = await config.remove_from_list(val, keywords)
|
||||
if await sqlite.get_filters_value(db_file, key):
|
||||
await sqlite.update_filters_value(db_file,
|
||||
[key, val])
|
||||
|
@ -451,16 +437,16 @@ async def message(self, message):
|
|||
status_message = ('📤️ Procesing request to '
|
||||
'export feeds into {}...'
|
||||
.format(ex))
|
||||
status.send(
|
||||
self, jid, status_message, status_type)
|
||||
cache_dir = get_default_cache_directory()
|
||||
await XmppStatus.send(self, jid, status_message,
|
||||
status_type)
|
||||
cache_dir = config.get_default_cache_directory()
|
||||
if not os.path.isdir(cache_dir):
|
||||
os.mkdir(cache_dir)
|
||||
if not os.path.isdir(cache_dir + '/' + ex):
|
||||
os.mkdir(cache_dir + '/' + ex)
|
||||
filename = os.path.join(
|
||||
cache_dir, ex, 'slixfeed_' + timestamp() + '.' + ex)
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
results = await sqlite.get_feeds(db_file)
|
||||
match ex:
|
||||
case 'html':
|
||||
|
@ -505,9 +491,10 @@ async def message(self, message):
|
|||
status_message = ('📃️ Procesing request to '
|
||||
'produce {} document...'
|
||||
.format(ext.upper()))
|
||||
status.send(self, jid, status_message, status_type)
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
cache_dir = get_default_cache_directory()
|
||||
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)
|
||||
|
@ -578,9 +565,9 @@ async def message(self, message):
|
|||
# status_message = (
|
||||
# '📥️ Procesing request to import feeds...'
|
||||
# )
|
||||
# status.send(
|
||||
# await XmppStatus.send(
|
||||
# self, jid, status_message, status_type)
|
||||
# db_file = get_pathname_to_database(jid_file)
|
||||
# db_file = config.get_pathname_to_database(jid_file)
|
||||
# count = await action.import_opml(db_file, url)
|
||||
# if count:
|
||||
# response = (
|
||||
|
@ -603,11 +590,11 @@ async def message(self, message):
|
|||
status_message = ('📫️ Processing request '
|
||||
'to fetch data from {}'
|
||||
.format(url))
|
||||
status.send(self, jid, status_message, status_type)
|
||||
await XmppStatus.send(self, jid, status_message, status_type)
|
||||
if url.startswith('feed:'):
|
||||
url = uri.feed_to_http(url)
|
||||
url = (uri.replace_hostname(url, 'feed')) or url
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
# try:
|
||||
response = await action.add_feed(db_file, url)
|
||||
# await task.clean_tasks_xmpp(jid, ['status'])
|
||||
|
@ -623,49 +610,28 @@ async def message(self, message):
|
|||
query = message_text[6:]
|
||||
if query:
|
||||
if len(query) > 3:
|
||||
db_file = 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(query, result)
|
||||
else:
|
||||
response = 'Enter at least 4 characters to search'
|
||||
else:
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
result = await sqlite.get_feeds(db_file)
|
||||
response = action.list_feeds(result)
|
||||
send_reply_message(self, message, response)
|
||||
case 'goodbye':
|
||||
if message['type'] == 'groupchat':
|
||||
await groupchat.leave(self, jid)
|
||||
await bookmark.remove(self, muc_jid)
|
||||
await XmppBookmark.remove(self, muc_jid)
|
||||
else:
|
||||
response = 'This command is valid in groupchat only.'
|
||||
send_reply_message(self, message, response)
|
||||
case _ if message_lowercase.startswith('interval'):
|
||||
# FIXME
|
||||
# The following error occurs only upon first attempt to set interval.
|
||||
# /usr/lib/python3.11/asyncio/events.py:73: RuntimeWarning: coroutine 'Slixfeed.send_update' was never awaited
|
||||
# self._args = None
|
||||
# RuntimeWarning: Enable tracemalloc to get the object allocation traceback
|
||||
key = message_text[:8]
|
||||
val = message_text[9:]
|
||||
if val:
|
||||
# response = (
|
||||
# 'Updates will be sent every {} minutes.'
|
||||
# ).format(response)
|
||||
db_file = 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])
|
||||
# NOTE Perhaps this should be replaced
|
||||
# by functions clean and start
|
||||
await task.refresh_task(self, jid, task.send_update,
|
||||
key, val)
|
||||
response = ('Updates will be sent every {} minutes.'
|
||||
.format(val))
|
||||
else:
|
||||
response = 'Missing value.'
|
||||
send_reply_message(self, message, response)
|
||||
await action.xmpp_change_interval(
|
||||
self, key, val, jid, jid_file, message=message)
|
||||
case _ if message_lowercase.startswith('join'):
|
||||
muc_jid = uri.check_xmpp_uri(message_text[5:])
|
||||
if muc_jid:
|
||||
|
@ -684,7 +650,7 @@ async def message(self, message):
|
|||
if val:
|
||||
try:
|
||||
val = int(val)
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
if await sqlite.get_settings_value(db_file,
|
||||
[key, val]):
|
||||
await sqlite.update_settings_value(db_file,
|
||||
|
@ -711,7 +677,7 @@ async def message(self, message):
|
|||
# get_settings_value,
|
||||
# key
|
||||
# )
|
||||
# val = await add_to_list(
|
||||
# val = await config.add_to_list(
|
||||
# val,
|
||||
# names
|
||||
# )
|
||||
|
@ -728,7 +694,7 @@ async def message(self, message):
|
|||
# response = 'Missing value.'
|
||||
send_reply_message(self, message, response)
|
||||
case 'new':
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
key = 'old'
|
||||
val = 0
|
||||
if await sqlite.get_settings_value(db_file, key):
|
||||
|
@ -765,7 +731,7 @@ async def message(self, message):
|
|||
# )
|
||||
# await refresh_task(jid, key, val)
|
||||
case 'old':
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
key = 'old'
|
||||
val = 1
|
||||
if await sqlite.get_settings_value(db_file, key):
|
||||
|
@ -783,11 +749,13 @@ async def message(self, message):
|
|||
# response = (
|
||||
# 'Every update will contain {} news items.'
|
||||
# ).format(response)
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
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])
|
||||
await sqlite.update_settings_value(db_file,
|
||||
[key, val])
|
||||
else:
|
||||
await sqlite.set_settings_value(db_file, [key, val])
|
||||
await sqlite.set_settings_value(db_file,
|
||||
[key, val])
|
||||
response = ('Next update will contain {} news items.'
|
||||
.format(val))
|
||||
except:
|
||||
|
@ -808,8 +776,7 @@ async def message(self, message):
|
|||
status_type = 'dnd'
|
||||
status_message = ('📫️ Processing request to fetch data from {}'
|
||||
.format(url))
|
||||
status.send(
|
||||
self, jid, status_message, status_type)
|
||||
await XmppStatus.send(self, jid, status_message, status_type)
|
||||
if url.startswith('feed:'):
|
||||
url = uri.feed_to_http(url)
|
||||
url = (uri.replace_hostname(url, 'feed')) or url
|
||||
|
@ -839,7 +806,7 @@ async def message(self, message):
|
|||
if num < 1 or num > 50:
|
||||
response = 'Value must be ranged from 1 to 50.'
|
||||
else:
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
result = await sqlite.last_entries(db_file, num)
|
||||
response = action.list_last_entries(result, num)
|
||||
except:
|
||||
|
@ -850,7 +817,7 @@ async def message(self, message):
|
|||
case _ if message_lowercase.startswith('remove'):
|
||||
ix_url = message_text[7:]
|
||||
if ix_url:
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
try:
|
||||
ix = int(ix_url)
|
||||
url = sqlite.get_feed_url(db_file, ix)
|
||||
|
@ -899,10 +866,10 @@ async def message(self, message):
|
|||
await task.clean_tasks_xmpp(jid, ['status'])
|
||||
status_type = 'dnd'
|
||||
status_message = '📫️ Marking entries as read...'
|
||||
status.send(self, jid, status_message, status_type)
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
await XmppStatus.send(self, jid, status_message, status_type)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
if ix_url:
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
try:
|
||||
ix = int(ix_url)
|
||||
url = sqlite.get_feed_url(db_file, ix)
|
||||
|
@ -944,7 +911,7 @@ async def message(self, message):
|
|||
query = message_text[7:]
|
||||
if query:
|
||||
if len(query) > 1:
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
results = await sqlite.search_entries(db_file, query)
|
||||
response = action.list_search_results(query, results)
|
||||
else:
|
||||
|
@ -956,7 +923,7 @@ async def message(self, message):
|
|||
# response = 'Updates are enabled.'
|
||||
key = 'enabled'
|
||||
val = 1
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
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:
|
||||
|
@ -967,12 +934,12 @@ async def message(self, message):
|
|||
# print(task_manager[jid])
|
||||
send_reply_message(self, message, response)
|
||||
case 'stats':
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
response = await action.list_statistics(db_file)
|
||||
send_reply_message(self, message, response)
|
||||
case _ if message_lowercase.startswith('disable '):
|
||||
ix = message_text[8:]
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
try:
|
||||
await sqlite.set_enabled_status(db_file, ix, 0)
|
||||
response = ('Updates are now disabled for news source {}.'
|
||||
|
@ -982,7 +949,7 @@ async def message(self, message):
|
|||
send_reply_message(self, message, response)
|
||||
case _ if message_lowercase.startswith('enable'):
|
||||
ix = message_text[7:]
|
||||
db_file = get_pathname_to_database(jid_file)
|
||||
db_file = config.get_pathname_to_database(jid_file)
|
||||
try:
|
||||
await sqlite.set_enabled_status(db_file, ix, 1)
|
||||
response = ('Updates are now enabled for news source {}.'
|
||||
|
@ -991,40 +958,7 @@ async def message(self, message):
|
|||
response = 'No news source with index {}.'.format(ix)
|
||||
send_reply_message(self, message, response)
|
||||
case 'stop':
|
||||
# FIXME
|
||||
# The following error occurs only upon first attempt to stop.
|
||||
# /usr/lib/python3.11/asyncio/events.py:73: RuntimeWarning: coroutine 'Slixfeed.send_update' was never awaited
|
||||
# self._args = None
|
||||
# RuntimeWarning: Enable tracemalloc to get the object allocation traceback
|
||||
# response = 'Updates are disabled.'
|
||||
# try:
|
||||
# # task_manager[jid]['check'].cancel()
|
||||
# # task_manager[jid]['status'].cancel()
|
||||
# task_manager[jid]['interval'].cancel()
|
||||
# key = 'enabled'
|
||||
# val = 0
|
||||
# response = await initdb(
|
||||
# jid,
|
||||
# update_settings_value,
|
||||
# [key, val]
|
||||
# )
|
||||
# except:
|
||||
# response = 'Updates are already disabled.'
|
||||
# # print('Updates are already disabled. Nothing to do.')
|
||||
# # await send_status(jid)
|
||||
key = 'enabled'
|
||||
val = 0
|
||||
db_file = 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.clean_tasks_xmpp(jid, ['interval', 'status'])
|
||||
response = 'Updates are disabled.'
|
||||
send_reply_message(self, message, response)
|
||||
status_type = 'xa'
|
||||
status_message = '💡️ Send "Start" to receive Jabber updates'
|
||||
status.send(self, jid, status_message, status_type)
|
||||
await action.xmpp_stop_updates(self, message, jid, jid_file)
|
||||
case 'support':
|
||||
# TODO Send an invitation.
|
||||
response = 'Join xmpp:slixfeed@chat.woodpeckersnest.space?join'
|
||||
|
@ -1057,7 +991,7 @@ async def message(self, message):
|
|||
send_reply_message(self, message, response)
|
||||
|
||||
if not response: response = 'EMPTY MESSAGE - ACTION ONLY'
|
||||
data_dir = get_default_data_directory()
|
||||
data_dir = config.get_default_data_directory()
|
||||
if not os.path.isdir(data_dir):
|
||||
os.mkdir(data_dir)
|
||||
if not os.path.isdir(data_dir + '/logs/'):
|
||||
|
|
|
@ -31,6 +31,7 @@ from slixfeed.config import get_value, get_default_config_directory
|
|||
# import logging
|
||||
import os
|
||||
|
||||
# class XmppProfile:
|
||||
|
||||
async def update(self):
|
||||
"""
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
def send(self, jid, status_message, status_type=None):
|
||||
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,
|
||||
pfrom=self.boundjid.bare,
|
||||
pto=jid
|
||||
ptype=chat_type
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue