Add export/import forms.

Improve Subscriptions form (non functional yet).
This commit is contained in:
Schimon Jehudah 2024-02-15 00:16:51 +00:00
parent c8cd5e1b09
commit e8d5f082d0
3 changed files with 305 additions and 136 deletions

View file

@ -87,6 +87,27 @@ except ImportError:
"Arc90 Lab algorithm is disabled.") "Arc90 Lab algorithm is disabled.")
async def export_feeds(self, jid, jid_file, ext):
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 + '/' + ext):
os.mkdir(cache_dir + '/' + ext)
filename = os.path.join(
cache_dir, ext, 'slixfeed_' + dt.timestamp() + '.' + ext)
db_file = config.get_pathname_to_database(jid_file)
results = await sqlite.get_feeds(db_file)
match ext:
# case 'html':
# response = 'Not yet implemented.'
case 'md':
export_to_markdown(jid, filename, results)
case 'opml':
export_to_opml(jid, filename, results)
# case 'xbel':
# response = 'Not yet implemented.'
return filename
async def xmpp_send_status(self, jid): async def xmpp_send_status(self, jid):
""" """
Send status message. Send status message.

View file

@ -46,6 +46,7 @@ from slixmpp.plugins.xep_0048.stanza import Bookmarks
# import xml.etree.ElementTree as ET # import xml.etree.ElementTree as ET
# from lxml import etree # from lxml import etree
import slixfeed.action as action
import slixfeed.config as config import slixfeed.config as config
from slixfeed.dt import timestamp from slixfeed.dt import timestamp
import slixfeed.sqlite as sqlite import slixfeed.sqlite as sqlite
@ -58,6 +59,7 @@ import slixfeed.xmpp.profile as profile
from slixfeed.xmpp.roster import XmppRoster from slixfeed.xmpp.roster import XmppRoster
# import slixfeed.xmpp.service as service # import slixfeed.xmpp.service as service
from slixfeed.xmpp.presence import XmppPresence from slixfeed.xmpp.presence import XmppPresence
from slixfeed.xmpp.upload import XmppUpload
from slixfeed.xmpp.utility import get_chat_type from slixfeed.xmpp.utility import get_chat_type
main_task = [] main_task = []
@ -470,33 +472,51 @@ class Slixfeed(slixmpp.ClientXMPP):
self['xep_0050'].add_command(node='subscriptions', self['xep_0050'].add_command(node='subscriptions',
name='📰️ Subscriptions', name='📰️ Subscriptions',
handler=self._handle_subscriptions) handler=self._handle_subscriptions)
self['xep_0050'].add_command(node='subscriptions_cat', # self['xep_0050'].add_command(node='subscriptions_cat',
name='🔖️ Categories', # name='🔖️ Categories',
handler=self._handle_subscription) # handler=self._handle_subscription)
self['xep_0050'].add_command(node='subscriptions_tag', # self['xep_0050'].add_command(node='subscriptions_tag',
name='🏷️ Tags', # name='🏷️ Tags',
handler=self._handle_subscription) # handler=self._handle_subscription)
self['xep_0050'].add_command(node='subscriptions_index', # self['xep_0050'].add_command(node='subscriptions_index',
name='📑️ Index (A - Z)', # name='📑️ Index (A - Z)',
handler=self._handle_subscription) # handler=self._handle_subscription)
self['xep_0050'].add_command(node='settings', self['xep_0050'].add_command(node='settings',
name='📮️ Settings', name='📮️ Settings',
handler=self._handle_settings) handler=self._handle_settings)
self['xep_0050'].add_command(node='filters', self['xep_0050'].add_command(node='filters',
name='🛡️ Filters', name='🛡️ Filters',
handler=self._handle_filters) handler=self._handle_filters)
self['xep_0050'].add_command(node='bookmarks', self['xep_0050'].add_command(node='bookmarks',
name='📕 Bookmarks', name='📕 Bookmarks',
handler=self._handle_bookmarks) handler=self._handle_bookmarks)
self['xep_0050'].add_command(node='roster', # self['xep_0050'].add_command(node='roster',
name='📓 Roster', # 📋 # name='📓 Roster', # 📋
handler=self._handle_roster) # handler=self._handle_roster)
self['xep_0050'].add_command(node='help', self['xep_0050'].add_command(node='help',
name='📔️ Manual', name='📔️ Manual',
handler=self._handle_help) handler=self._handle_help)
self['xep_0050'].add_command(node='motd', self['xep_0050'].add_command(node='totd',
name='🗓️ MOTD', name='💡️ TOTD',
handler=self._handle_motd) handler=self._handle_totd)
self['xep_0050'].add_command(node='fotd',
name='🗓️ FOTD',
handler=self._handle_fotd)
self['xep_0050'].add_command(node='activity',
name='📠️ Activity',
handler=self._handle_activity)
self['xep_0050'].add_command(node='statistics',
name='📊️ Statistics',
handler=self._handle_statistics)
self['xep_0050'].add_command(node='import',
name='📥️ Import',
handler=self._handle_import)
self['xep_0050'].add_command(node='export',
name='📤️ Export',
handler=self._handle_export)
self['xep_0050'].add_command(node='privacy',
name='Privacy',
handler=self._handle_privacy)
self['xep_0050'].add_command(node='credit', self['xep_0050'].add_command(node='credit',
name='Credits', # 💡️ name='Credits', # 💡️
handler=self._handle_credit) handler=self._handle_credit)
@ -595,7 +615,9 @@ class Slixfeed(slixmpp.ClientXMPP):
options = form.add_field(var='subscriptions', options = form.add_field(var='subscriptions',
ftype='list-multi', ftype='list-multi',
label='Subscriptions', label='Subscriptions',
desc='Select subscriptions to perform action.') desc=('Select subscriptions to perform '
'actions upon.'),
required=True)
jid_file = jid jid_file = jid
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
subscriptions = await sqlite.get_feeds(db_file) subscriptions = await sqlite.get_feeds(db_file)
@ -604,15 +626,15 @@ class Slixfeed(slixmpp.ClientXMPP):
title = subscription[0] title = subscription[0]
url = subscription[1] url = subscription[1]
options.addOption(title, url) options.addOption(title, url)
options = form.add_field(var='action', # options = form.add_field(var='action',
ftype='list-single', # ftype='list-single',
label='Action', # label='Action',
value='none') # value='none')
options.addOption('None', 'none') # options.addOption('None', 'none')
options.addOption('Reset', 'reset') # options.addOption('Reset', 'reset')
options.addOption('Enable', 'enable') # options.addOption('Enable', 'enable')
options.addOption('Disable', 'disable') # options.addOption('Disable', 'disable')
options.addOption('Delete', 'delete') # options.addOption('Delete', 'delete')
session['payload'] = form session['payload'] = form
session['next'] = self._handle_subscription_editor session['next'] = self._handle_subscription_editor
session['has_next'] = True session['has_next'] = True
@ -680,36 +702,83 @@ class Slixfeed(slixmpp.ClientXMPP):
async def _handle_subscription_editor(self, payload, session): async def _handle_subscription_editor(self, payload, session):
urls = payload['values']['subscriptions']
jid = session['from'].bare jid = session['from'].bare
jid_file = jid jid_file = jid
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
# url = payload['values']['subscriptions'] url_count = len(urls)
urls = payload['values'] if url_count > 1:
for i in urls: form = self['xep_0004'].make_form('form', 'Subscription editor')
if urls[i]: form['instructions'] = '📂️ Editing {} subscriptions'.format(url_count)
url = urls[i] form.add_field(var='options',
break ftype='fixed',
feed_id = await sqlite.get_feed_id(db_file, url) value='Options')
feed_id = feed_id[0] options = form.add_field(var='action',
title = sqlite.get_feed_title(db_file, feed_id) ftype='list-single',
title = title[0] label='Action',
form = self['xep_0004'].make_form('form', 'Subscription editor') value='none')
form['instructions'] = '📂️ Editing subscription #{}'.format(feed_id) options.addOption('None', 'none')
form.add_field(var='properties', counter = 0
ftype='fixed', for url in urls:
value='Properties') feed_id = await sqlite.get_feed_id(db_file, url)
form.add_field(var='name', feed_id = feed_id[0]
ftype='text-single', count = sqlite.get_number_of_unread_entries_by_feed(db_file, feed_id)
label='Name', counter += count[0]
value=title) if int(counter):
# NOTE This does not look good in Gajim options.addOption('Mark {} entries as read'.format(counter), 'reset')
# options = form.add_field(var='url', options.addOption('Delete {} subscriptions'.format(url_count), 'delete')
# ftype='fixed', options.addOption('Export {} subscriptions'.format(url_count), 'export')
# value=url) else:
form.add_field(var='url', url = urls[0]
ftype='text-single', feed_id = await sqlite.get_feed_id(db_file, url)
label='URL', feed_id = feed_id[0]
value=url) 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='options',
ftype='fixed',
value='Options')
options = form.add_field(var='action',
ftype='list-single',
label='Action',
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')
form.add_field(var='enable',
ftype='boolean',
label='Enable',
value=True)
options = form.add_field(var='priority',
ftype='list-single',
label='Priority',
value='0')
options['validate']['datatype'] = 'xs:integer'
options['validate']['range'] = { 'minimum': 1, 'maximum': 5 }
i = 0
while i <= 5:
num = str(i)
options.addOption(num, num)
i += 1
form.add_field(var='labels', form.add_field(var='labels',
ftype='fixed', ftype='fixed',
value='Labels') value='Labels')
@ -737,32 +806,6 @@ class Slixfeed(slixmpp.ClientXMPP):
# ftype='text-multi', # ftype='text-multi',
label='Tags', label='Tags',
value='') 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='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['payload'] = form
session['next'] = self._handle_subscription_complete session['next'] = self._handle_subscription_complete
session['has_next'] = True session['has_next'] = True
@ -777,7 +820,6 @@ class Slixfeed(slixmpp.ClientXMPP):
async def _handle_about(self, iq, session): async def _handle_about(self, iq, session):
import slixfeed.action as action
# form = self['xep_0004'].make_form('result', 'Thanks') # form = self['xep_0004'].make_form('result', 'Thanks')
# form['instructions'] = action.manual('information.toml', 'thanks') # form['instructions'] = action.manual('information.toml', 'thanks')
# session['payload'] = form # session['payload'] = form
@ -804,6 +846,125 @@ class Slixfeed(slixmpp.ClientXMPP):
return session return session
async def _handle_activity(self, iq, session):
# TODO dialog for JID and special dialog for operator
text = ('Here you can monitor activity')
session['notes'] = [['info', text]]
return session
async def _handle_statistics(self, iq, session):
text = ('Here you can monitor statistics')
session['notes'] = [['info', text]]
return session
async def _handle_import(self, iq, session):
jid = session['from'].bare
form = self['xep_0004'].make_form('form',
'Import data for {}'.format(jid))
form['instructions'] = '🗞️ Import feeds from OPML'
form.add_field(var='url',
ftype='text-single',
label='URL',
desc='Enter URL to OPML file.',
required=True)
session['payload'] = form
session['next'] = self._handle_import_complete
session['has_next'] = True
return session
async def _handle_import_complete(self, payload, session):
url = payload['values']['url']
if url.startswith('http') and url.endswith('.opml'):
jid = session['from'].bare
jid_file = jid.replace('/', '_')
db_file = config.get_pathname_to_database(jid_file)
count = await action.import_opml(db_file, url)
try:
int(count)
form = self['xep_0004'].make_form('result',
'Import data for {}'.format(jid))
form['instructions'] = ('✅️ Feeds have been imported')
message = '{} feeds have been imported to {}.'.format(count, jid)
form.add_field(var='message',
ftype='text-single',
value=message)
session['payload'] = form
session["has_next"] = False
except:
session['notes'] = [['error', 'Import failed. Filetype does not appear to be an OPML file.']]
session['has_next'] = False
# Mitigate Cheogram issue
# Gajim acts also strage, so this might be an issue with slixmmpp
# session['next'] = False
else:
session['notes'] = [['error', 'Import aborted. Send URL of OPML file.']]
session['has_next'] = False
# Mitigate Cheogram issue
# Gajim acts also strage, so this might be an issue with slixmmpp
# session['next'] = False
return session
async def _handle_export(self, iq, session):
jid = session['from'].bare
form = self['xep_0004'].make_form('form',
'Export data for {}'.format(jid))
form['instructions'] = '🗞️ Export feeds'
options = form.add_field(var='filetype',
ftype='list-multi',
label='Format',
desc='Choose export format.',
value='opml',
required=True)
options.addOption('Markdown', 'md')
options.addOption('OPML', 'opml')
# options.addOption('HTML', 'html')
# options.addOption('XBEL', 'xbel')
session['payload'] = form
session['next'] = self._handle_export_complete
session['has_next'] = True
return session
async def _handle_export_complete(self, payload, session):
jid = session['from'].bare
jid_file = jid.replace('/', '_')
form = self['xep_0004'].make_form('result',
'Export data for {}'.format(jid))
form['instructions'] = ('✅️ Feeds have been exported')
exts = payload['values']['filetype']
for ext in exts:
filename = await action.export_feeds(self, jid, jid_file, ext)
url = await XmppUpload.start(self, jid, filename)
form.add_field(var=ext.upper(),
ftype='text-single',
label=ext,
value=url)
session['payload'] = form
session["has_next"] = False
session['next'] = None
return session
async def _handle_privacy(self, iq, session):
text = ('Privacy Policy')
text += '\n\n'
text += ''.join(action.manual('information.toml', 'privacy'))
session['notes'] = [['info', text]]
return session
async def _handle_fotd(self, iq, session):
text = ('Here we publish featured news feeds!')
session['notes'] = [['info', text]]
return session
async def _handle_motd(self, iq, session): async def _handle_motd(self, iq, session):
# TODO add functionality to attach image. # TODO add functionality to attach image.
text = ('Here you can add groupchat rules,post schedule, tasks or ' text = ('Here you can add groupchat rules,post schedule, tasks or '
@ -812,12 +973,17 @@ class Slixfeed(slixmpp.ClientXMPP):
return session return session
async def _handle_totd(self, iq, session):
text = ('Tips and tricks you might have not known about Slixfeed and XMPP!')
session['notes'] = [['info', text]]
return session
async def _handle_credit(self, iq, session): async def _handle_credit(self, iq, session):
import slixfeed.action as action
# form = self['xep_0004'].make_form('result', 'Thanks') # form = self['xep_0004'].make_form('result', 'Thanks')
# form['instructions'] = action.manual('information.toml', 'thanks') # form['instructions'] = action.manual('information.toml', 'thanks')
# session['payload'] = form # session['payload'] = form
text = '💡️ We are XMPP\n\n' text = 'We are XMPP\n\n'
fren = action.manual('information.toml', 'thanks') fren = action.manual('information.toml', 'thanks')
fren = "".join(fren) fren = "".join(fren)
fren = fren.split(';') fren = fren.split(';')
@ -873,7 +1039,6 @@ class Slixfeed(slixmpp.ClientXMPP):
form = self['xep_0004'].make_form('form', 'Bookmarks') form = self['xep_0004'].make_form('form', 'Bookmarks')
form['instructions'] = '📖️ Organize bookmarks' form['instructions'] = '📖️ Organize bookmarks'
options = form.add_field(var='bookmarks', options = form.add_field(var='bookmarks',
# ftype='list-multi'
ftype='list-single', ftype='list-single',
label='Select a bookmark', label='Select a bookmark',
desc='Select a bookmark to edit.') desc='Select a bookmark to edit.')
@ -1038,7 +1203,7 @@ class Slixfeed(slixmpp.ClientXMPP):
value = config.get_setting_value(db_file, 'interval') value = config.get_setting_value(db_file, 'interval')
value = int(value) value = int(value)
value = value value = value/60
value = int(value) value = int(value)
value = str(value) value = str(value)
options = form.add_field(var='interval', options = form.add_field(var='interval',
@ -1046,15 +1211,16 @@ class Slixfeed(slixmpp.ClientXMPP):
label='Interval', label='Interval',
desc='Set interval update (in hours).', desc='Set interval update (in hours).',
value=value) value=value)
i = 60 options['validate']['datatype'] = 'xs:integer'
while i <= 2880: options['validate']['range'] = { 'minimum': 1, 'maximum': 48 }
num = str(i) i = 1
lab = str(int(i/60)) while i <= 48:
options.addOption(lab, num) x = str(i)
if i >= 720: options.addOption(x, x)
i += 360 if i >= 12:
i += 6
else: else:
i += 60 i += 1
value = config.get_setting_value(db_file, 'archive') value = config.get_setting_value(db_file, 'archive')
value = str(value) value = str(value)
@ -1124,8 +1290,8 @@ class Slixfeed(slixmpp.ClientXMPP):
if key == 'interval': if key == 'interval':
val = int(val) val = int(val)
if val < 60: if val < 1: val = 1
val = 90 val = val * 60
if sqlite.get_settings_value(db_file, key): if 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])

View file

@ -102,7 +102,6 @@ async def message(self, message):
response = 'Successfully imported {} feeds.'.format(count) response = 'Successfully imported {} feeds.'.format(count)
else: else:
response = 'OPML file was not imported.' response = 'OPML file was not imported.'
# task.clean_tasks_xmpp(self, jid, ['status'])
await task.start_tasks_xmpp(self, jid, ['status']) await task.start_tasks_xmpp(self, jid, ['status'])
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
return return
@ -454,32 +453,16 @@ async def message(self, message):
response = 'Missing keywords.' response = 'Missing keywords.'
XmppMessage.send_reply(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:] ext = message_text[7:]
if ex in ('opml', 'html', 'md', 'xbel'): if ext in ('md', 'opml'): # html xbel
status_type = 'dnd' status_type = 'dnd'
status_message = ('📤️ Procesing request to ' status_message = ('📤️ Procesing request to '
'export feeds into {}...' 'export feeds into {}...'
.format(ex)) .format(ext.upper()))
XmppPresence.send(self, jid, status_message, XmppPresence.send(self, jid, status_message,
status_type=status_type) status_type=status_type)
cache_dir = config.get_default_cache_directory() filename = await action.export_feeds(self, jid, jid_file,
if not os.path.isdir(cache_dir): ext)
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 = config.get_pathname_to_database(jid_file)
results = await sqlite.get_feeds(db_file)
match ex:
case 'html':
response = 'Not yet implemented.'
case 'md':
action.export_to_markdown(jid, filename, results)
case 'opml':
action.export_to_opml(jid, filename, results)
case 'xbel':
response = 'Not yet implemented.'
url = await XmppUpload.start(self, jid, filename) url = await XmppUpload.start(self, jid, filename)
# response = ( # response = (
# 'Feeds exported successfully to {}.\n{}' # 'Feeds exported successfully to {}.\n{}'
@ -490,7 +473,7 @@ async def message(self, message):
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: md or opml')
XmppMessage.send_reply(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:')):
@ -507,24 +490,23 @@ async def message(self, message):
ix_url = message_text.split(' ')[0] ix_url = message_text.split(' ')[0]
await action.download_document(self, message, jid, jid_file, await action.download_document(self, message, jid, jid_file,
message_text, ix_url, True) message_text, ix_url, True)
# 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
# task.clean_tasks_xmpp(self, jid, ['status']) task.clean_tasks_xmpp(self, jid, ['status'])
# status_type = 'dnd' status_type = 'dnd'
# status_message = '📥️ Procesing request to import feeds...' status_message = '📥️ Procesing request to import feeds...'
# XmppPresence.send(self, jid, status_message, XmppPresence.send(self, jid, status_message,
# status_type=status_type) 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:
# response = ('Successfully imported {} feeds.' response = ('Successfully imported {} feeds.'
# .format(count)) .format(count))
# else: else:
# response = 'OPML file was not imported.' response = 'OPML file was not imported.'
# task.clean_tasks_xmpp(self, jid, ['status']) await task.start_tasks_xmpp(self, jid, ['status'])
# await task.start_tasks_xmpp(self, jid, ['status']) XmppMessage.send_reply(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