Fix commands info and help to ignore case.

Fix command read to handle lack of URL.
Add bookmark properties.
Add new experimental forms.
Thank you mirux.
This commit is contained in:
Schimon Jehudah 2024-02-13 19:34:37 +00:00
parent 93a65f14d8
commit d3af15d623
4 changed files with 301 additions and 102 deletions

View file

@ -795,6 +795,31 @@ async def mark_entry_as_read(cur, ix):
cur.execute(sql, par)
def get_number_of_unread_entries_by_feed(db_file, feed_id):
"""
Count entries of goven feed.
Parameters
----------
db_file : str
Path to database file.
feed_id : str
Feed Id.
"""
with create_connection(db_file) as conn:
cur = conn.cursor()
sql = (
"""
SELECT count(id)
FROM entries
WHERE read = 0 AND feed_id = ?
"""
)
par = (feed_id,)
count = cur.execute(sql, par).fetchone()
return count
async def mark_feed_as_read(db_file, feed_id):
"""
Set read status of entries of given feed as read.
@ -1689,7 +1714,7 @@ async def check_entry_exist(
"""
SELECT id
FROM entries
WHERE title = :title and link = :link and timestamp = :date
WHERE title = :title AND link = :link AND timestamp = :date
"""
)
par = {
@ -1708,7 +1733,7 @@ async def check_entry_exist(
"""
SELECT id
FROM entries
WHERE title = :title and link = :link
WHERE title = :title AND link = :link
"""
)
par = {

View file

@ -18,10 +18,26 @@ class XmppBookmark:
async def get(self):
result = await self.plugin['xep_0048'].get_bookmarks()
bookmarks = result['private']['bookmarks']
conferences = bookmarks['conferences']
conferences = bookmarks['conferences'] # We might not want this here
# conferences = bookmarks
return conferences
async def properties(self, jid):
result = await self.plugin['xep_0048'].get_bookmarks()
groupchats = result['private']['bookmarks']['conferences']
for groupchat in groupchats:
if jid == groupchat['jid']:
properties = {'password': groupchat['password'],
'jid': groupchat['jid'],
'name': groupchat['name'],
'nick': groupchat['nick'],
'autojoin': groupchat['autojoin'],
'lang': groupchat['lang']}
break
return properties
async def add(self, muc_jid):
result = await self.plugin['xep_0048'].get_bookmarks()
bookmarks = result['private']['bookmarks']

View file

@ -47,6 +47,7 @@ from slixmpp.plugins.xep_0048.stanza import Bookmarks
# from lxml import etree
import slixfeed.config as config
from slixfeed.dt import timestamp
import slixfeed.sqlite as sqlite
from slixfeed.xmpp.bookmark import XmppBookmark
from slixfeed.xmpp.connect import XmppConnect
@ -108,6 +109,8 @@ class Slixfeed(slixmpp.ClientXMPP):
# self.add_event_handler("got_online", self.check_readiness)
self.add_event_handler("changed_status",
self.on_changed_status)
self.add_event_handler("disco_info",
self.on_disco_info)
self.add_event_handler("presence_available",
self.on_presence_available)
# self.add_event_handler("presence_unavailable",
@ -199,7 +202,7 @@ class Slixfeed(slixmpp.ClientXMPP):
async def on_session_start(self, event):
self.send_presence()
await self['xep_0115'].update_caps()
await self['xep_0115'].update_caps(preserve=True)
await self.get_roster()
await XmppGroupchat.autojoin(self)
profile.set_identity(self, 'client')
@ -215,7 +218,7 @@ class Slixfeed(slixmpp.ClientXMPP):
async def on_session_resumed(self, event):
self.send_presence()
self['xep_0115'].update_caps()
self['xep_0115'].update_caps(preserve=True)
await XmppGroupchat.autojoin(self)
profile.set_identity(self, 'client')
@ -226,6 +229,15 @@ class Slixfeed(slixmpp.ClientXMPP):
self.service_reactions()
async def on_disco_info(self, DiscoInfo):
jid = DiscoInfo['from']
self.send_presence(pto=jid)
await self['xep_0115'].update_caps(jid=jid,
preserve=True)
self.service_commands()
self.service_reactions()
# TODO Request for subscription
async def on_message(self, message):
jid = message["from"].bare
@ -348,15 +360,17 @@ class Slixfeed(slixmpp.ClientXMPP):
if message['type'] in ('chat', 'normal'):
# task.clean_tasks_xmpp(self, jid, ['status'])
await task.start_tasks_xmpp(self, jid, ['status'])
# NOTE: Required for Cheogram
def on_chatstate_composing(self, message):
async def on_chatstate_composing(self, message):
if message['type'] in ('chat', 'normal'):
jid = message['from'].bare
# task.clean_tasks_xmpp(self, jid, ['status'])
status_message = ('💡 Send "help" for manual, or "info" for '
'information.')
XmppPresence.send(self, jid, status_message)
# NOTE: Required for Cheogram
async def on_chatstate_gone(self, message):
@ -447,28 +461,36 @@ class Slixfeed(slixmpp.ClientXMPP):
# )
# 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='Edit Settings',
name='📮️ Edit settings',
handler=self._handle_settings)
self['xep_0050'].add_command(node='filters',
name='Manage Filters',
name='🕸️ Manage filters',
handler=self._handle_filters)
self['xep_0050'].add_command(node='roster',
name='🧾️ Manage roster',
handler=self._handle_roster)
self['xep_0050'].add_command(node='bookmarks',
name='📔️ Organize bookmarks',
handler=self._handle_bookmarks)
self['xep_0050'].add_command(node='subscriptions',
name='Manage subscriptions',
name='📰️ Subscriptions - All',
handler=self._handle_subscriptions)
self['xep_0050'].add_command(node='subscription',
name='View subscription',
self['xep_0050'].add_command(node='subscriptions_cat',
name='🔖️ Subscriptions - Categories',
handler=self._handle_subscription)
self['xep_0050'].add_command(node='subscriptions_tag',
name='🏷️ Subscriptions - Tags',
handler=self._handle_subscription)
self['xep_0050'].add_command(node='subscriptions_index',
name='📑️ Subscriptions - Indexed',
handler=self._handle_subscription)
# self['xep_0050'].add_command(node='search',
# name='Search',
# handler=self._handle_search)
# Special interface
# http://jabber.org/protocol/commands#actions
async def _handle_filters(self, iq, session):
jid = session['from'].bare
@ -534,7 +556,7 @@ class Slixfeed(slixmpp.ClientXMPP):
# result = '{}: {}'.format(key, val)
form.add_field(var=key + '_title',
ftype='fixed',
value=key.capitalize() + ' Filter')
value=key.capitalize() + ' filter')
form.add_field(var=key.capitalize() + ' list',
ftype='text-single',
value=val)
@ -554,17 +576,27 @@ class Slixfeed(slixmpp.ClientXMPP):
# label='Interval period')
options = form.add_field(var='subscriptions',
ftype='list-multi',
label='Select subscriptions',
desc='Select subscriptions to edit.')
label='Subscriptions',
desc='Select subscriptions to perform action.')
jid_file = jid
db_file = config.get_pathname_to_database(jid_file)
subscriptions = await sqlite.get_feeds(db_file)
subscriptions = sorted(subscriptions, key=lambda x: x[0])
for subscription in subscriptions:
title = subscription[0]
url = subscription[1]
options.addOption(title, url)
options = form.add_field(var='action',
ftype='list-single',
label='Action',
value='none')
options.addOption('None', 'none')
options.addOption('Reset', 'reset')
options.addOption('Enable', 'enable')
options.addOption('Disable', 'disable')
options.addOption('Delete', 'delete')
session['payload'] = form
session['next'] = self._edit_subscription
session['next'] = self._handle_subscription_editor
session['has_next'] = True
# Other useful session values:
# session['to'] -- The JID that received the
@ -583,109 +615,232 @@ class Slixfeed(slixmpp.ClientXMPP):
return session
# TODO Make form for a single subscription and several subscriptions
# single: Delete, Disable, Reset and Rename
# several: Delete, Disable, Reset
# FIXME There are feeds that are missing (possibly because of sortings)
async def _handle_subscription(self, iq, session):
jid = session['from'].bare
form = self['xep_0004'].make_form('form',
'Subscriptions for {}'.format(jid))
form['instructions'] = '📰️ View subscription properties'
form['instructions'] = '📰️ Edit subscription'
# form.addField(var='interval',
# ftype='text-single',
# label='Interval period')
options = form.add_field(var='subscriptions',
ftype='list-single',
label='Select subscriptions',
desc='Select a subscription to view.')
jid_file = jid
db_file = config.get_pathname_to_database(jid_file)
subscriptions = await sqlite.get_feeds(db_file)
# subscriptions = set(subscriptions)
categorized_subscriptions = {}
for subscription in subscriptions:
title = subscription[0]
url = subscription[1]
options.addOption(title, url)
try:
letter = title[0].capitalize()
if letter not in categorized_subscriptions:
categorized_subscriptions[letter] = [subscription]
# title[0].capitalize()] = [subscription]
else:
categorized_subscriptions[letter].append(subscription)
# title[0].capitalize()].append(subscription)
except Exception as e:
logging.warning('Title might be empty:', str(e))
for category in sorted(categorized_subscriptions):
options = form.add_field(var=category,
ftype='list-single',
label=category.capitalize(),
desc='Select a subscription to view.')
subscriptions_ = categorized_subscriptions[category]
subscriptions_ = sorted(subscriptions_, key=lambda x: x[0])
for subscription_ in subscriptions_:
# for subscription in categorized_subscriptions[category]:
# breakpoint()
title = subscription_[0]
url = subscription_[1]
options.addOption(title, url)
session['payload'] = form
session['next'] = self._edit_subscription
session['next'] = self._handle_subscription_editor
session['has_next'] = True
return session
async def _edit_subscription(self, iq, session):
async def _handle_subscription_editor(self, payload, session):
jid = session['from'].bare
form = self['xep_0004'].make_form('form',
'Subscription editor'.format(jid))
form['instructions'] = '🗞️ Edit subscription: {}'.format(title)
options = form.add_field(var='enable',
ftype='boolean',
label='Enable',
value=True)
jid_file = jid
db_file = config.get_pathname_to_database(jid_file)
# url = payload['values']['subscriptions']
urls = payload['values']
for i in urls:
if urls[i]:
url = urls[i]
break
feed_id = await sqlite.get_feed_id(db_file, url)
feed_id = feed_id[0]
title = sqlite.get_feed_title(db_file, feed_id)
title = title[0]
form = self['xep_0004'].make_form('form', 'Subscription editor')
form['instructions'] = '📂️ Editing subscription #{}'.format(feed_id)
form.add_field(var='properties',
ftype='fixed',
value='Properties')
form.add_field(var='name',
ftype='text-single',
label='Name',
value=title)
# NOTE This does not look good in Gajim
# options = form.add_field(var='url',
# ftype='fixed',
# value=url)
form.add_field(var='url',
ftype='text-single',
label='URL',
value=url)
form.add_field(var='labels',
ftype='fixed',
value='Labels')
options = form.add_field(var='category',
ftype='list-single',
label='Category',
value='none')
options.addOption('None', 'none')
options.addOption('Activity', 'activity')
options.addOption('Catalogues', 'catalogues')
options.addOption('Clubs', 'clubs')
options.addOption('Events', 'events')
options.addOption('Forums', 'forums')
options.addOption('Music', 'music')
options.addOption('News', 'news')
options.addOption('Organizations', 'organizations')
options.addOption('Podcasts', 'podcasts')
options.addOption('Projects', 'projects')
options.addOption('Schools', 'schools')
options.addOption('Stores', 'stores')
options.addOption('Tutorials', 'tutorials')
options.addOption('Videos', 'videos')
options = form.add_field(var='tags',
ftype='text-single',
# ftype='text-multi',
label='Tags',
value='')
form.add_field(var='options',
ftype='fixed',
value='Options')
form.add_field(var='enable',
ftype='boolean',
label='Enable',
value=True)
options = form.add_field(var='priority',
ftype='list-single',
label='Priority',
value='0')
i = 0
while i <= 5:
num = str(i)
options.addOption(num, num)
i += 1
options = form.add_field(var='action',
ftype='list-single',
label='Action',
value='reset')
options.addOption('Delete', 'delete')
options.addOption('Reset', 'reset')
value='none')
options.addOption('None', 'none')
count = sqlite.get_number_of_unread_entries_by_feed(db_file, feed_id)
count = count[0]
if int(count):
options.addOption('Mark {} entries as read'.format(count), 'reset')
options.addOption('Delete subscription', 'delete')
session['payload'] = form
session['next'] = None
session['has_next'] = False
session['next'] = self._handle_subscription_complete
session['has_next'] = True
return session
async def _handle_subscription_complete(self, payload, session):
form = self['xep_0004'].make_form('form', 'Subscription editor')
form['instructions'] = ('📁️ Subscription #{} has been {}'
.format(feed_id, action))
pass
async def _handle_bookmarks(self, iq, session):
jid = session['from'].bare
form = self['xep_0004'].make_form('form',
'Bookmarks for {}'.format(jid))
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'])
if jid == config.get_value('accounts', 'XMPP', 'operator'):
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['name'], conference['jid'])
session['next'] = self._handle_bookmarks_editor
session['has_next'] = True
else:
logging.warning('An unauthorized attempt to access bookmarks has '
'been detected!\n'
'Details:\n'
' Jabber ID: {}\n'
' Timestamp: {}\n'
.format(jid, timestamp()))
form = self['xep_0004'].make_form('form', 'Denied')
# form = self['xep_0004'].make_form('error', 'Denied') # Cheogram crashes
form['instructions'] = '⚠️ Access denied'
form.add_field(var='warning',
ftype='fixed',
label='Warning',
value='You are not allowed to access this resource.')
session['next'] = False
session['has_next'] = False
session['payload'] = form
session['next'] = self._handle_command_complete
session['has_next'] = False
return session
async def _handle_bookmarks_editor(self, iq, session):
async def _handle_bookmarks_editor(self, payload, session):
jid = payload['values']['bookmarks']
properties = await XmppBookmark.properties(self, jid)
jid = session['from'].bare
form = self['xep_0004'].make_form('form',
'Bookmarks for {}'.format(jid))
form['instructions'] = '📝️ Edit bookmarks'
form = self['xep_0004'].make_form('form', 'Edit bookmark')
form['instructions'] = 'Edit bookmark {}'.format(properties['name'])
jid = properties['jid'].split('@')
room = jid[0]
host = jid[1]
form.addField(var='name',
ftype='text-single',
label='Name')
form.addField(var='host',
ftype='text-single',
label='Host',
required=True)
label='Name',
value=properties['name'])
form.addField(var='room',
ftype='text-single',
label='Room',
value=room,
required=True)
form.addField(var='host',
ftype='text-single',
label='Host',
value=host,
required=True)
form.addField(var='alias',
ftype='text-single',
label='Alias')
label='Alias',
value=properties['nick'])
form.addField(var='password',
ftype='text-private',
label='Password')
label='Password',
value=properties['password'])
form.addField(var='language',
ftype='text-single',
label='Language',
value=properties['lang'])
form.add_field(var='autojoin',
ftype='boolean',
label='Auto-join',
value=True)
value=properties['autojoin'])
options = form.add_field(var='action',
ftype='list-single',
label='Action',
value='join')
options.addOption('Add', 'add')
# options.addOption('Add', 'add')
options.addOption('Join', 'join')
options.addOption('Remove', 'remove')
session['payload'] = form
session['next'] = None
session['next'] = False
session['has_next'] = False
return session
@ -756,9 +911,9 @@ class Slixfeed(slixmpp.ClientXMPP):
value=value)
i = 60
while i <= 2880:
var = str(i)
num = str(i)
lab = str(int(i/60))
options.addOption(lab, var)
options.addOption(lab, num)
if i >= 720:
i += 360
else:

View file

@ -202,7 +202,7 @@ async def message(self, message):
print(response)
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('help'):
command = message_text[5:]
command = message_text[5:].lower()
command = command.split(' ')
if len(command) == 2:
command_root = command[0]
@ -239,7 +239,7 @@ async def message(self, message):
.format(command_list))
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('info'):
command = message_text[5:]
command = message_text[5:].lower()
command_list = action.manual('information.toml', command)
if command_list:
# command_list = '\n'.join(command_list)
@ -319,7 +319,7 @@ async def message(self, message):
response = 'Missing URL.'
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('allow +'):
key = 'filter-' + message_text[:5]
key = message_text[:5]
val = message_text[7:]
if val:
db_file = config.get_pathname_to_database(jid_file)
@ -337,7 +337,7 @@ async def message(self, message):
response = 'Missing keywords.'
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('allow -'):
key = 'filter-' + message_text[:5]
key = message_text[:5]
val = message_text[7:]
if val:
db_file = config.get_pathname_to_database(jid_file)
@ -712,31 +712,34 @@ async def message(self, message):
data = message_text[5:]
data = data.split()
url = data[0]
task.clean_tasks_xmpp(self, jid, ['status'])
status_type = 'dnd'
status_message = ('📫️ Processing request to fetch data from {}'
.format(url))
XmppPresence.send(self, jid, status_message,
status_type=status_type)
if url.startswith('feed:'):
url = uri.feed_to_http(url)
url = (uri.replace_hostname(url, 'feed')) or url
match len(data):
case 1:
if url.startswith('http'):
response = await action.view_feed(url)
else:
response = 'Missing URL.'
case 2:
num = data[1]
if url.startswith('http'):
response = await action.view_entry(url, num)
else:
response = 'Missing URL.'
case _:
response = ('Enter command as follows:\n'
'`read <url>` or `read <url> <number>`\n'
'URL must not contain white space.')
if url:
task.clean_tasks_xmpp(self, jid, ['status'])
status_type = 'dnd'
status_message = ('📫️ Processing request to fetch data from {}'
.format(url))
XmppPresence.send(self, jid, status_message,
status_type=status_type)
if url.startswith('feed:'):
url = uri.feed_to_http(url)
url = (uri.replace_hostname(url, 'feed')) or url
match len(data):
case 1:
if url.startswith('http'):
response = await action.view_feed(url)
else:
response = 'Missing URL.'
case 2:
num = data[1]
if url.startswith('http'):
response = await action.view_entry(url, num)
else:
response = 'Missing URL.'
case _:
response = ('Enter command as follows:\n'
'`read <url>` or `read <url> <number>`\n'
'URL must not contain white space.')
else:
response = 'Missing URL.'
XmppMessage.send_reply(self, message, response)
await task.start_tasks_xmpp(self, jid, ['status'])
case _ if message_lowercase.startswith('recent'):