Replace configuration file INI by TOML.

Fix ping functionality when activated as component (thank you Guus and MattJ).
Add initial code for XEP-0060: Publish-Subscribe.
Fix case-sensitivity with setting keys sent in-chat-command (Thank you mirux)
This commit is contained in:
Schimon Jehudah 2024-03-12 17:13:01 +00:00
parent ba61250f84
commit b1a1955545
22 changed files with 802 additions and 1168 deletions

View file

@ -85,21 +85,24 @@ from slixfeed.version import __version__
# import socks # import socks
# import socket # import socket
xmpp_type = config.get_value('accounts', 'XMPP', 'type') account = config.get_values('accounts.toml', 'xmpp')
if not 'mode' in account['settings']:
account_mode = 'client'
print('Key "mode" was not found.\nSetting value to "client".')
# raise Exception('Key type is missing from accounts.toml.')
else:
account_mode = 'component'
if not xmpp_type: match account_mode:
raise Exception('Key type is missing from accounts.ini.')
match xmpp_type:
case 'client': case 'client':
from slixfeed.xmpp.client import Slixfeed from slixfeed.xmpp.client import Slixfeed
from slixfeed.config import ConfigClient as ConfigAccount # from slixfeed.config import ConfigClient as ConfigAccount
case 'component': case 'component':
from slixfeed.xmpp.component import SlixfeedComponent from slixfeed.xmpp.component import SlixfeedComponent
from slixfeed.config import ConfigComponent as ConfigAccount # from slixfeed.config import ConfigComponent as ConfigAccount
account = ConfigAccount() # TODO Delete as soon as posible after is no longer needed # account = ConfigAccount() # TODO Delete as soon as posible after is no longer needed
class JabberComponent: class JabberComponent:
def __init__(self, jid, secret, hostname, port, alias=None): def __init__(self, jid, secret, hostname, port, alias=None):
@ -175,9 +178,8 @@ class JabberClient:
# xmpp.proxy = {'socks5': ('localhost', 9050)} # xmpp.proxy = {'socks5': ('localhost', 9050)}
# Connect to the XMPP server and start processing XMPP stanzas. # Connect to the XMPP server and start processing XMPP stanzas.
if hostname and port:
if account.setting['hostname'] and account.setting['port']: xmpp.connect((hostname, port))
xmpp.connect((account.setting['hostname'], account.setting['port']))
else: else:
xmpp.connect() xmpp.connect()
xmpp.process() xmpp.process()
@ -200,7 +202,7 @@ def main():
# # socket.socket = socks.socksocket # # socket.socket = socks.socksocket
# Setup the command line arguments. # Setup the command line arguments.
match xmpp_type: match account_mode:
case 'client': case 'client':
parser = ArgumentParser(description=Slixfeed.__doc__) parser = ArgumentParser(description=Slixfeed.__doc__)
case 'component': case 'component':
@ -242,11 +244,11 @@ def main():
# logwrn = logger.warning # logwrn = logger.warning
# Try configuration file # Try configuration file
alias = account.setting['alias'] jid = account[account_mode]['jid']
jid = account.setting['jid'] password = account[account_mode]['password']
password = account.setting['password'] alias = account[account_mode]['alias'] if 'alias' in account[account_mode] else None
hostname = account.setting['hostname'] hostname = account[account_mode]['hostname'] if 'hostname' in account[account_mode] else None
port = account.setting['port'] port = account[account_mode]['port'] if 'port' in account[account_mode] else None
# Use arguments if were given # Use arguments if were given
if args.jid: if args.jid:
@ -268,7 +270,7 @@ def main():
if not alias: if not alias:
alias = (input('Alias: ')) or 'Slixfeed' alias = (input('Alias: ')) or 'Slixfeed'
match xmpp_type: match account_mode:
case 'client': case 'client':
JabberClient(jid, password, hostname=hostname, port=port, alias=alias) JabberClient(jid, password, hostname=hostname, port=port, alias=alias)
case 'component': case 'component':

View file

@ -113,6 +113,27 @@ async def export_feeds(self, jid, jid_file, ext):
# response = 'Not yet implemented.' # response = 'Not yet implemented.'
return filename return filename
"""
TODO
Consider to append text to remind to share presence
'✒️ Share online status to receive updates'
# TODO Request for subscription
if (await get_chat_type(self, jid_bare) == 'chat' and
not self.client_roster[jid_bare]['to']):
XmppPresence.subscription(self, jid_bare, 'subscribe')
await XmppRoster.add(self, jid_bare)
status_message = '✒️ Share online status to receive updates'
XmppPresence.send(self, jid_bare, status_message)
message_subject = 'RSS News Bot'
message_body = 'Share online status to receive updates.'
XmppMessage.send_headline(self, jid_bare, message_subject,
message_body, 'chat')
"""
async def xmpp_send_status(self, jid): async def xmpp_send_status(self, jid):
""" """
Send status message. Send status message.
@ -127,7 +148,7 @@ async def xmpp_send_status(self, jid):
status_text = '📜️ Slixfeed RSS News Bot' status_text = '📜️ Slixfeed RSS News Bot'
jid_file = jid.replace('/', '_') jid_file = jid.replace('/', '_')
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
enabled = self.settings[jid]['enabled'] or self.settings['default']['enabled'] enabled = Config.get_setting_value(self.settings, jid, 'enabled')
if not enabled: if not enabled:
status_mode = 'xa' status_mode = 'xa'
status_text = '📪️ Send "Start" to receive updates' status_text = '📪️ Send "Start" to receive updates'
@ -179,11 +200,11 @@ async def xmpp_send_update(self, jid, num=None):
logger.debug('{}: jid: {} num: {}'.format(function_name, jid, num)) logger.debug('{}: jid: {} num: {}'.format(function_name, jid, num))
jid_file = jid.replace('/', '_') jid_file = jid.replace('/', '_')
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
enabled = self.settings[jid]['enabled'] or self.settings['default']['enabled'] enabled = Config.get_setting_value(self.settings, jid, 'enabled')
if enabled: if enabled:
show_media = self.settings[jid]['media'] or self.settings['default']['media'] show_media = Config.get_setting_value(self.settings, jid, 'media')
if not num: if not num:
num = self.settings[jid]['quantum'] or self.settings['default']['quantum'] num = Config.get_setting_value(self.settings, jid, 'quantum')
else: else:
num = int(num) num = int(num)
results = sqlite.get_unread_entries(db_file, num) results = sqlite.get_unread_entries(db_file, num)
@ -465,7 +486,7 @@ def list_unread_entries(self, result, feed_title, jid):
summary = summary.replace('\n', ' ') summary = summary.replace('\n', ' ')
summary = summary.replace(' ', ' ') summary = summary.replace(' ', ' ')
summary = summary.replace(' ', ' ') summary = summary.replace(' ', ' ')
length = self.settings[jid]['length'] or self.settings['default']['length'] length = Config.get_setting_value(self.settings, jid, 'length')
length = int(length) length = int(length)
summary = summary[:length] + " […]" summary = summary[:length] + " […]"
# summary = summary.strip().split('\n') # summary = summary.strip().split('\n')
@ -476,13 +497,13 @@ def list_unread_entries(self, result, feed_title, jid):
link = (replace_hostname(link, "link")) or link link = (replace_hostname(link, "link")) or link
# news_item = ("\n{}\n{}\n{} [{}]\n").format(str(title), str(link), # news_item = ("\n{}\n{}\n{} [{}]\n").format(str(title), str(link),
# str(feed_title), str(ix)) # str(feed_title), str(ix))
formatting = self.settings[jid]['formatting'] or self.settings['default']['formatting'] formatting = Config.get_setting_value(self.settings, jid, 'formatting')
news_item = formatting.format(feed_title=feed_title, news_item = formatting.format(feed_title=feed_title,
title=title, title=title,
summary=summary, summary=summary,
link=link, link=link,
ix=ix) ix=ix)
news_item = news_item.replace('\\n', '\n') # news_item = news_item.replace('\\n', '\n')
return news_item return news_item
@ -502,11 +523,9 @@ def list_search_results(query, results):
return message return message
def list_feeds_by_query(db_file, query): def list_feeds_by_query(query, results):
function_name = sys._getframe().f_code.co_name function_name = sys._getframe().f_code.co_name
logger.debug('{}: db_file: {} query: {}' logger.debug('{}'.format(function_name))
.format(function_name, db_file, query))
results = sqlite.search_feeds(db_file, query)
message = ('Feeds containing "{}":\n\n```' message = ('Feeds containing "{}":\n\n```'
.format(query)) .format(query))
for result in results: for result in results:
@ -549,10 +568,10 @@ async def list_options(self, jid_bare):
# value = "Default" # value = "Default"
# values.extend([value]) # values.extend([value])
value_archive = self.settings[jid_bare]['archive'] or self.settings['default']['archive'] value_archive = Config.get_setting_value(self.settings, jid_bare, 'archive')
value_interval = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] value_interval = Config.get_setting_value(self.settings, jid_bare, 'interval')
value_quantum = self.settings[jid_bare]['quantum'] or self.settings['default']['quantum'] value_quantum = Config.get_setting_value(self.settings, jid_bare, 'quantum')
value_enabled = self.settings[jid_bare]['archive'] or self.settings['default']['enabled'] value_enabled = Config.get_setting_value(self.settings, jid_bare, 'enabled')
message = ("Options:" message = ("Options:"
"\n" "\n"
@ -647,13 +666,10 @@ def list_feeds(results):
logger.debug('{}'.format(function_name)) logger.debug('{}'.format(function_name))
message = "\nList of subscriptions:\n\n```\n" message = "\nList of subscriptions:\n\n```\n"
for result in results: for result in results:
message += ("Name : {}\n" message += ("{} [{}]\n"
"URL : {}\n" "{}\n"
# "Updated : {}\n" "\n\n"
# "Status : {}\n" .format(str(result[1]), str(result[0]), str(result[2])))
"ID : {}\n"
"\n"
.format(str(result[0]), str(result[1]), str(result[2])))
if len(results): if len(results):
message += ('```\nTotal of {} subscriptions.\n' message += ('```\nTotal of {} subscriptions.\n'
.format(len(results))) .format(len(results)))
@ -793,7 +809,7 @@ async def add_feed(self, jid_bare, db_file, url):
status_code=status_code, status_code=status_code,
updated=updated) updated=updated)
await scan(self, jid_bare, db_file, url) await scan(self, jid_bare, db_file, url)
old = self.settings[jid_bare]['old'] or self.settings['default']['old'] old = Config.get_setting_value(self.settings, jid_bare, 'old')
feed_id = sqlite.get_feed_id(db_file, url) feed_id = sqlite.get_feed_id(db_file, url)
feed_id = feed_id[0] feed_id = feed_id[0]
if not old: if not old:
@ -804,9 +820,6 @@ async def add_feed(self, jid_bare, db_file, url):
'code' : status_code, 'code' : status_code,
'error' : False, 'error' : False,
'exist' : False} 'exist' : False}
response = ('> {}\nNews source "{}" has been '
'added to subscription list.'
.format(url, title))
break break
# NOTE This elif statement be unnecessary # NOTE This elif statement be unnecessary
# when feedparser be supporting json feed. # when feedparser be supporting json feed.
@ -843,7 +856,7 @@ async def add_feed(self, jid_bare, db_file, url):
status_code=status_code, status_code=status_code,
updated=updated) updated=updated)
await scan_json(self, jid_bare, db_file, url) await scan_json(self, jid_bare, db_file, url)
old = self.settings[jid_bare]['old'] or self.settings['default']['old'] old = Config.get_setting_value(self.settings, jid_bare, 'old')
if not old: if not old:
feed_id = sqlite.get_feed_id(db_file, url) feed_id = sqlite.get_feed_id(db_file, url)
feed_id = feed_id[0] feed_id = feed_id[0]
@ -854,9 +867,6 @@ async def add_feed(self, jid_bare, db_file, url):
'code' : status_code, 'code' : status_code,
'error' : False, 'error' : False,
'exist' : False} 'exist' : False}
response = ('> {}\nNews source "{}" has been '
'added to subscription list.'
.format(url, title))
break break
else: else:
# NOTE Do not be tempted to return a compact dictionary. # NOTE Do not be tempted to return a compact dictionary.
@ -886,8 +896,6 @@ async def add_feed(self, jid_bare, db_file, url):
'code' : status_code, 'code' : status_code,
'error' : True, 'error' : True,
'exist' : False} 'exist' : False}
response = ('> {}\nFailed to load URL. Reason: {}'
.format(url, status_code))
break break
else: else:
ix = exist[0] ix = exist[0]
@ -898,9 +906,6 @@ async def add_feed(self, jid_bare, db_file, url):
'code' : None, 'code' : None,
'error' : False, 'error' : False,
'exist' : True} 'exist' : True}
response = ('> {}\nNews source "{}" is already '
'listed in the subscription list at '
'index {}'.format(url, name, ix))
break break
return result_final return result_final
@ -1254,7 +1259,7 @@ async def scan(self, jid_bare, db_file, url):
return return
# new_entry = 0 # new_entry = 0
for entry in entries: for entry in entries:
logger.debug('{}: entry: {}'.format(function_name, entry.title)) logger.debug('{}: entry: {}'.format(function_name, entry.link))
if entry.has_key("published"): if entry.has_key("published"):
date = entry.published date = entry.published
date = dt.rfc2822_to_iso8601(date) date = dt.rfc2822_to_iso8601(date)
@ -1481,6 +1486,7 @@ async def extract_image_from_html(url):
'contains(@src, "emoji") or ' 'contains(@src, "emoji") or '
'contains(@src, "icon") or ' 'contains(@src, "icon") or '
'contains(@src, "logo") or ' 'contains(@src, "logo") or '
'contains(@src, "letture") or '
'contains(@src, "search") or ' 'contains(@src, "search") or '
'contains(@src, "share") or ' 'contains(@src, "share") or '
'contains(@src, "smiley")' 'contains(@src, "smiley")'
@ -1614,7 +1620,7 @@ async def remove_nonexistent_entries(self, jid_bare, db_file, url, feed):
feed_id = feed_id[0] feed_id = feed_id[0]
items = sqlite.get_entries_of_feed(db_file, feed_id) items = sqlite.get_entries_of_feed(db_file, feed_id)
entries = feed.entries entries = feed.entries
limit = self.settings[jid_bare]['archive'] or self.settings['default']['archive'] limit = Config.get_setting_value(self.settings, jid_bare, 'archive')
for item in items: for item in items:
ix = item[0] ix = item[0]
entry_title = item[1] entry_title = item[1]
@ -1723,7 +1729,7 @@ async def remove_nonexistent_entries_json(self, jid_bare, db_file, url, feed):
feed_id = feed_id[0] feed_id = feed_id[0]
items = sqlite.get_entries_of_feed(db_file, feed_id) items = sqlite.get_entries_of_feed(db_file, feed_id)
entries = feed["items"] entries = feed["items"]
limit = self.settings[jid_bare]['archive'] or self.settings['default']['archive'] limit = Config.get_setting_value(self.settings, jid_bare, 'archive')
for item in items: for item in items:
ix = item[0] ix = item[0]
entry_title = item[1] entry_title = item[1]

View file

@ -1,98 +0,0 @@
# Settings to tell the bot to which accounts to connect
# and also from which accounts it receives instructions.
[XMPP]
operator =
reconnect_timeout = 30
type = client
#type = component
[XMPP Client]
alias = Slixfeed
jid =
password =
#hostname =
#port =
[XMPP Component]
alias = Slixfeed
jid =
password =
hostname =
port =
[XMPP Profile]
name = Slixfeed
nickname = Slixfeed
role = Syndication News Bot
organization = RSS Task Force
url = https://gitgud.io/sjehuda/slixfeed
description = XMPP news bot (supports Atom, JSON, RDF and RSS).
note = This is a syndication news bot powered by Slixfeed.
birthday = 21 June 2022
[XMPP Proxy]
# NOTE You might want to consider support for socks4 too (this
# note was written when keys were proxy_host and proxy_port)
# NOTE Consider not to use a version number as it might give an
# impression of an archaic feature in the future.
#socks5_host =
#socks5_port =
#socks5_username =
#socks5_password =
[ActivityPub]
username =
password =
operator =
[Email]
recipient_emails =
sender_emails =
[IMAP]
username =
password =
#port = 993
[SMTP]
host =
#port = 465
[IRC]
username =
password =
#port = 6667
operator =
[LXMF]
username =
password =
operator =
[Matrix]
username =
password =
operator =
[Nostr]
username =
password =
operator =
[Session]
username =
password =
operator =
[SIP]
username =
password =
operator =
[TOX]
username =
password =
operator =

View file

@ -0,0 +1,108 @@
# Settings to tell the bot to which accounts to connect
# and also from which accounts it receives instructions.
[xmpp.settings]
#mode = "component"
reconnect_timeout = 30
[[xmpp.operators]]
name = "Mr. Operator"
jid = ""
[[xmpp.operators]]
name = "Mrs. Operator"
jid = ""
[xmpp.proxy.socks5]
#host = "127.0.0.1"
#port = 9050
#username = ""
#password = ""
[xmpp.profile]
FN = "Slixfeed"
NICKNAME = "Slixfeed"
ROLE = "Syndication News Bot"
ORG = "RSS Task Force"
URL = "https://gitgud.io/sjehuda/slixfeed"
NOTE = """
This is a syndication news bot powered by Slixfeed.
This bot can read Atom, JSON, RDF and RSS feeds.
This bot can communicate to 1:1 chats and groupchats as one.
You are welcome to join our groupchat at:
xmpp:slixfeed@chat.woodpeckersnest.space?join
"""
BDAY = "21 June 2022"
TITLE = "XMPP News Bot"
DESC = "Syndication bot made for XMPP."
[xmpp.client]
alias = "Slixfeed"
jid = "slixfeed@your.server/slixfeed"
password = ""
#hostname =
#port =
[xmpp.component]
alias = "Slixfeed"
jid = "rss.your.server"
password = ""
hostname = "your.server"
#port =
[activitypub]
username = ""
password = ""
operator = ""
[activitypub.profile]
user_agent = "Slixfeed"
[email]
recipient_emails = ""
sender_emails = ""
[email.imap]
username = ""
password = ""
#port = 993
[email.smtp]
host = ""
#port = 465
[irc]
username = ""
password = ""
#port = 6667
operator = ""
[deltachat]
username = ""
password = ""
operator = ""
[lxmf]
username = ""
password = ""
operator = ""
[nostr]
username = ""
password = ""
operator = ""
[session]
username = ""
password = ""
operator = ""
[sip]
username = ""
password = ""
operator = ""
[tox]
username = ""
password = ""
operator = ""

View file

@ -1,9 +1,5 @@
[[about]] [about]
title = "About" info = """
subtitle = "Slixfeed news bot"
[[about]]
info = ["""
Slixfeed is a news broker bot for syndicated news which aims to be \ Slixfeed is a news broker bot for syndicated news which aims to be \
an easy to use and fully-featured news aggregating bot. an easy to use and fully-featured news aggregating bot.
@ -12,10 +8,13 @@ even Fediverse instances, along with filtering and other privacy \
driven functionalities. driven functionalities.
Slixfeed is designed primarily for the XMPP communication network \ Slixfeed is designed primarily for the XMPP communication network \
(aka Jabber). Visit https://xmpp.org/software/ for more information. (aka Jabber).
"""]
note = [""" https://gitgud.io/sjehuda/slixfeed
"""
[note]
note = """
You can run your own Slixfeed instance as a client, from your own \ You can run your own Slixfeed instance as a client, from your own \
computer, server, and even from a Linux phone (i.e. Droidian, Kupfer, \ computer, server, and even from a Linux phone (i.e. Droidian, Kupfer, \
Mobian, NixOS, postmarketOS), as well as from Termux. Mobian, NixOS, postmarketOS), as well as from Termux.
@ -24,107 +23,59 @@ All you need is one of the above and an XMPP account to connect \
Slixfeed with. Slixfeed with.
Good luck! Good luck!
"""]
filetypes = "Atom, JSON, RDF, RSS, XML."
platforms = "XMPP"
# platforms = "ActivityPub, Briar, Email, IRC, LXMF, MQTT, Nostr, Session, Tox."
comment = "For ideal experience, we recommend using XMPP."
url = "https://gitgud.io/sjehuda/slixfeed"
[[authors]]
title = "Authors"
subtitle = "The people who have made Slixfeed"
[[authors]]
name = "Laura Lapina"
role = "Co-Author, Instructor and Mentor"
type = "AsyncIO, SQLite"
[[authors]]
name = "Schimon Zackary"
role = "Creator and Author"
[[contributors]]
title = "Contributors"
subtitle = "The people who have contributed to Slixfeed"
[[contributors]]
name = "Stephen Paul Weber"
role = "Contributor and forms coordinator"
type = "XEP-0004, XEP-0050, XEP-0122"
project = "Cheogram"
[[friends]]
title = "Similar Projects"
subtitle = """
From Argentina to Germany. Syndication bots made by our counterparts.
""" """
[[friends]] [authors]
name = "err-rssreader" info = """
info = ["A port of old Brutal's RSS Reader for Errbot"] Schimon Zackary
url = "https://github.com/errbotters/err-rssreader" Laura Lapina
"""
[[friends]] [contributors]
name = "feed-to-muc" info = """
info = [""" Guus der Kinderen
An XMPP bot which posts to a MUC (groupchat) if there is an update in newsfeeds. grym (from #python IRC channel)
"""] Stephen Paul Weber
url = "https://salsa.debian.org/mdosch/feed-to-muc" """
[[friends]] [bots]
name = "JabRSS (fork)" info = """
info = [""" Syndication bots made by our counterparts.
Never miss a headline again! JabRSS is a simple RSS (RDF Site Summary) \
headline notification service for Jabber.
It is based on jabrss@cmeerw.net from Christof. Morbot
https://codeberg.org/TheCoffeMaker/Morbot
It was restructured and offers additional features (see help, help filter and \ feed-to-muc
show plugins). https://salsa.debian.org/mdosch/feed-to-muc
"""]
url = "http://www.jotwewe.de/de/xmpp/jabrss/jabrss_en.htm"
[[friends]] JabRSS
name = "JabRSS" http://www.jotwewe.de/de/xmpp/jabrss/jabrss_en.htm
info = ["""
A simple RSS (RDF Site Summary) headline notification service for Jabber/XMPP.
A public instance of the bot is available via xmpp:jabrss@cmeerw.net JabRSS
"""] https://dev.cmeerw.org/Projects/jabrss
url = "https://dev.cmeerw.org/Projects/jabrss"
[[friends]] err-rssreader"
name = "Morbot" https://github.com/errbotters/err-rssreader
info = ["""
Morbo is a simple Slixmpp bot that will take new articles from listed RSS \
feeds and send them to assigned XMPP MUCs (groupchats).
"""]
url = "https://codeberg.org/TheCoffeMaker/Morbot"
[[legal]] XMPP Bot
title = "Legal" https://github.com/nioc/xmpp-bot
subtitle = "Legal Notice" """
[[legal]] [legal]
info = [""" info = """
Slixfeed is free software; you can redistribute it and/or modify it under the \ Slixfeed is free software; you can redistribute it and/or modify it under the \
terms of the MIT License. terms of the MIT License.
Slixfeed is distributed in the hope that it will be useful, but WITHOUT ANY \ Slixfeed is distributed in the hope that it will be useful, but WITHOUT ANY \
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR \ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR \
A PARTICULAR PURPOSE. See the MIT License for more details. A PARTICULAR PURPOSE. See the MIT License for more details.
"""]
link = "https://gitgud.io/sjehuda/slixfeed"
[[license]] https://gitgud.io/sjehuda/slixfeed
title = "License" """
subtitle = "MIT License"
[[license]] [license]
license = [""" info = """
Copyright 2022 - 2024 Schimon Zackary Jehudah Copyright 2022 - 2024 Schimon Jehudah Zackary
Permission is hereby granted, free of charge, to any person obtaining a copy \ Permission is hereby granted, free of charge, to any person obtaining a copy \
of this software and associated documentation files (the Software), to deal \ of this software and associated documentation files (the Software), to deal \
@ -143,558 +94,136 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING \ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING \
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS \ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS \
IN THE SOFTWARE. IN THE SOFTWARE.
"""] """
owner = "Schimon Zackary"
[[support]] [support]
title = "Support" info = """
subtitle = "Slixfeed Support Groupchat" Slixfeed Support Groupchat
xmpp:slixfeed@chat.woodpeckersnest.space?join
"""
[[support]] [thanks]
jid = "xmpp:slixfeed@chat.woodpeckersnest.space?join" info = """
[[thanks]]
title = "Thanks"
subtitle = """
From SalixOS to Gajim. A journey of 15 years. \ From SalixOS to Gajim. A journey of 15 years. \
The people who have made all this possible. The people who have made all this possible.
Alixander Court (Utah), Arne-Brün Vogelsang (Germany), Chris Farrell (Oregon) \
Christian Dersch, Cyrille Pontvieux (France), Denis Fomin (Russia), Dimitris \
Tzemos (Greece), Emmanuel Gil Peyrot (France), Florent Le Coz (France), George \
Vlahavas (Greece), Guus der Kinderen (Netherlands), habnabit_ (#python), Imar \
van Erven Dorens (Netherlands), imattau (atomtopubsub), Jaussoin Timothée \
(France), Justin Karneges (California), Kevin Smith (Wales), Lars Windolf \
(Germany), Luis Henrique Mello (Brazil), magicfelix, Markus Muttilainen \
(SalixOS), Martin (Germany), Mathieu Pasquet (France), Maxime Buquet (France), \
mirux (Germany), Phillip Watkins (England), Pierrick Le Brun (France), Raphael \
Groner (Germany), Remko Tronçon (Belgium), Richard Lapointe (Connecticut), \
Simone "roughnecks" Canaletti (Italy), Strix from Loqi, Thibaud Guerin \
(SalixOS), Thorsten Fröhlich (France), Thorsten Mühlfelder (Germany), Tim \
Beech (Brazil), Tomoki Tsuchiya (SalixOS), Yann Leboulanger (France)
Thanks also to the friends of #python at irc.libera.chat
""" """
[[thanks]] [operators]
name = "Alixander Court" info = """
country = "Utah" No operator was specified for this instance.
url = "https://alixandercourt.com" """
[[thanks]] [terms]
name = "Arne-Brün Vogelsang" info = """
country = "Germany"
project = "monocles"
url = "https://monocles.eu/more"
[[thanks]]
name = "Chris Farrell"
alias = "timcowchip"
country = "Oregon"
project = "SalixOS"
[[thanks]]
name = "Christian Dersch"
alias = "christian"
project = "SalixOS"
[[thanks]]
name = "Cyrille Pontvieux"
alias = "JRD"
country = "France"
project = "SalixOS"
url = "http://animeka.com http://enialis.net"
jabber = "xmpp:jrd@jabber.cz?message"
[[thanks]]
name = "Denis Fomin"
alias = "Dicson"
country = "Russia"
project = "Gajim"
url = "https://juick.com/dicson"
[[thanks]]
name = "Dimitris Tzemos"
alias = "djemos"
country = "Greece"
projects = "SalixOS, Slackel"
url = "http://slackel.gr"
jabber = "xmpp:djemos@jabber.org?message"
[[thanks]]
name = "Emmanuel Gil Peyrot"
alias = "Link mauve"
country = "France"
projects = "Poezio, slixmpp"
jabber = "xmpp:linkmauve@linkmauve.fr?message"
url = "https://linkmauve.fr"
[[thanks]]
name = "Florent Le Coz"
alias = "louiz"
country = "France"
projects = "Poezio, slixmpp"
jabber = "xmpp:louiz@louiz.org?message"
url = "https://louiz.org"
[[thanks]]
name = "George Vlahavas"
alias = "gapan"
country = "Greece"
project = "SalixOS"
url = "https://salixos.org https://vlahavas.com"
[[thanks]]
name = "Guus der Kinderen"
country = "Netherlands"
project = "Openfire"
url = "https://igniterealtime.org"
[[thanks]]
name = "habnabit_"
alias = "habnabit_"
irc = "irc://irc.libera.chat/#python"
[[thanks]]
name = "Imar van Erven Dorens"
country = "Netherlands"
project = "SalixOS"
url = "https://simplicit.nl"
[[thanks]]
name = "imattau"
alias = "imattau"
project = "atomtopubsub"
[[thanks]]
name = "Jaussoin Timothée"
alias = "edhelas"
country = "France"
projects = "atomtopubsub, Movim"
url = "https://mov.im"
[[thanks]]
name = "Justin Karneges"
country = "California"
project = "Psi"
url = "https://jblog.andbit.net https://psi-im.org"
[[thanks]]
name = "Kevin Smith"
alias = "Kev"
country = "Wales"
projects = "Psi, SleekXMPP, Swift IM"
url = "http://kismith.co.uk https://isode.com https://swift.im"
[[thanks]]
name = "Lars Windolf"
alias = "lwindolf"
country = "Germany"
project = "Liferea"
url = "https://lzone.de"
[[thanks]]
name = "Luis Henrique Mello"
alias = "lmello"
country = "Brazil"
project = "SalixOS"
[[thanks]]
name = "magicfelix"
alias = "magicfelix"
[[thanks]]
name = "Markus Muttilainen"
alias = "stillborn"
project = "SalixOS"
[[thanks]]
name = "Martin"
alias = "debacle"
country = "Germany"
projects = "Debian, sms4you"
email = "mailto:debacle@debian.org"
[[thanks]]
name = "Mathieu Pasquet"
alias = "mathieui"
country = "France"
project = "slixmpp"
jabber = "xmpp:mathieui@mathieui.net?message"
url = "https://blog.mathieui.net"
[[thanks]]
name = "Maxime Buquet"
alias = "pep"
country = "France"
project = "slixmpp"
jabber = "xmpp:pep@bouah.net?message"
url = "https://bouah.net"
[[thanks]]
name = "mirux"
alias = "mirux"
country = "Germany"
[[thanks]]
name = "Phillip Watkins"
alias = "pwatk"
country = "England"
project = "SalixOS"
[[thanks]]
name = "Pierrick Le Brun"
alias = "akuna"
country = "France"
project = "SalixOS"
url = "https://mossieur-ballon.com"
[[thanks]]
name = "Raphael Groner"
alias = "rapgro"
country = "Germany"
project = "Fedora"
[[thanks]]
name = "Remko Tronçon"
country = "Belgium"
projects = "Psi, SleekXMPP, Swift IM"
url = "http://el-tramo.be https://mko.re https://psi-im.org"
[[thanks]]
name = "Richard Lapointe"
alias = "laprjns"
country = "Connecticut"
projects = "SalixOS, Zenwalk"
[[thanks]]
name = "Simone Canaletti"
alias = "roughnecks"
country = "Italy"
url = "https://woodpeckersnest.space"
[[thanks]]
name = "Stephen Paul Weber"
alias = "singpolyma"
projects = "Cheogram, JMP, Sopranica"
url = "https://singpolyma.net"
[[thanks]]
name = "Strix from Loqi"
alias = "Strix"
[[thanks]]
name = "Thibaud Guerin"
alias = "guth"
project = "SalixOS"
[[thanks]]
name = "Thorsten Fröhlich"
country = "France"
[[thanks]]
name = "Thorsten Mühlfelder"
alias = "thenktor"
country = "Germany"
project = "SalixOS"
[[thanks]]
name = "Tim Beech"
alias = "mimosa"
country = "Brazil"
project = "SalixOS"
url = "https://apipucos.wordpress.com"
[[thanks]]
name = "Tomoki Tsuchiya"
alias = "tsuren"
project = "SalixOS"
[[thanks]]
name = "Yann Leboulanger"
alias = "asterix"
country = "France"
project = "Gajim"
jabber = "xmpp:asterix@jabber.lagaule.org?message"
url = "https://gajim.org"
[[thanks]]
name = "#python (IRC Channel)"
irc = "irc://irc.libera.chat/#python"
[[thanks]]
name = "The Salix Team"
about = ["""
Previously part of the Zenwalk team.
The stubbornness of the Salix OS team members, and their determination to the \
cause, no matter whether popular or else, you are the people who have lead \
the creator of this software to the XMPP network.
It may well be said, that without you, gentlemen, and without your kind \
honesty, sincerity and even the arguments however difficult these arguments \
were, Slixfeed would have never been existed today.
All this from an XMPP groupchat that started out from 5 to 8 participants, \
fifteen years ago (2009).
Thank you.
"""]
irc = "irc://irc.libera.chat/#salix"
jabber = "xmpp:salix@chat.meticul.eu?join"
url = "https://docs.salixos.org/wiki/Salix_OS:Team"
[[thanks]]
name = "The XMPP Community"
about = ["""
For over a couple of decades, the people of XMPP form a strong community \
which strives to provide you and your loved ones, private, secure and \
stable communication experience.
While we are for private property and high standard of living, in the XMPP \
realm we cooperate and we compete together to provide you with the best \
communication platform in the world.
With governments and intelligence agencies around the world making an \
extensive - and sometimes exclusive - use of XMPP, you can be rest assured \
that you can never be wrong by making XMPP your prime and premier choice \
for communications.
We are XMPP.
Join us!
"""]
[[operators]]
title = "Operators"
subtitle = "Slixfeed Operators"
[[operators]]
name = "Mr. Operator"
jid = "No operator was specified for this instance."
[[policies]]
title = "Policies"
subtitle = "Terms of service"
[[policies]]
name = "Terms and Conditions"
info = ["""
You are bound to these terms. You are bound to these terms.
"""]
[[policies]]
name = "Privacy Policy"
info = ["""
All your data belongs to us.
"""]
[[clients]]
title = "Recommended Clients"
subtitle = """
As a chat bot, Slixfeed works with any XMPP messenger, yet we have deemed it \
appropriate to list the software that work best with Slixfeed, namely those \
that provide support for XEP-0050: Ad-Hoc Commands.
""" """
[[clients]] [privacy]
name = "Cheogram" info = """
info = "XMPP client for mobile" All your data belongs to us.
url = "https://cheogram.com" """
# [[clients]] [clients]
# name = "Conversations" info = """
# info = "XMPP client for mobile" Recommended Clients:
# url = "https://conversations.im"
[[clients]] Cheogram
name = "Converse" https://cheogram.com
info = "XMPP client for desktop and mobile"
url = "https://conversejs.org"
# [[clients]] Converse
# name = "Gajim" https://conversejs.org
# info = "XMPP client for desktop"
# url = "https://gajim.org"
# [[clients]] Gajim
# name = "Monal IM" https://gajim.org
# info = "XMPP client for desktop and mobile"
# url = "https://monal-im.org"
[[clients]] monocles chat
name = "monocles chat" https://monocles.chat
info = "XMPP client for mobile"
url = "https://monocles.chat"
[[clients]] Movim
name = "Movim" https://mov.im
info = "XMPP client for desktop and mobile"
url = "https://mov.im"
# [[clients]] Poezio
# name = "Moxxy" https://poez.io
# info = "XMPP client for mobile" """
# url = "https://moxxy.org"
[[clients]] [services]
name = "Psi" info = """
info = "XMPP client for desktop" Recommended Syndication Services
url = "https://psi-im.org"
[[clients]] Feed Creator
name = "Psi+" https://www.fivefilters.org/feed-creator/"
info = "XMPP client for desktop"
url = "https://psi-plus.com"
# [[clients]] Kill the Newsletter
# name = "Swift" https://kill-the-newsletter.com
# info = "XMPP client for desktop"
# url = "https://swift.im"
# [[clients]] Open RSS
# name = "yaxim" https://openrss.org
# info = "XMPP client for mobile"
# url = "https://yaxim.org"
[[services]] RSS-Bridge
title = "Recommended News Services" https://rss-bridge.org/bridge01/
subtitle = ["""
Below are online services that extend the syndication experience by means \
of bookmarking and multimedia, and also enhance it by restoring access to \
news web feeds.
"""]
[[services]] RSSHub
name = "Feed Creator" https://docs.rsshub.app
info = [""" """
Feed Creator is a service that creates feeds from HTML pages. \
It generates RSS and JSON feeds from a set of links or other HTML elements.
"""]
link = "https://www.fivefilters.org/feed-creator/"
[[services]] [software]
name = "Kill the Newsletter" info = """
info = "Kill the Newsletter converts email newsletters into Web feeds." Recommended News Software
link = "https://kill-the-newsletter.com"
[[services]] CommaFeed
name = "Open RSS" https://commafeed.com
info = ["""
Open RSS is a nonprofit organization that provides free RSS feeds for \
websites and applications that don't already provide them, so RSS feeds can \
continue to be a reliable way for people to stay up-to-date with content \
anywhere on the internet.
"""]
link = "https://openrss.org"
[[services]] FreshRSS
name = "RSS-Bridge" https://freshrss.org
info = ["""
RSS-Bridge is free and open source software for generating Atom or RSS \
feeds from websites which dont have one. It is written in PHP and intended \
to run on a Web server.
"""]
link = "https://rss-bridge.org/bridge01/"
[[services]] Liferea
name = "RSSHub" https://lzone.de/liferea/
info = ["""
RSSHub is an open source, easy to use, and extensible RSS feed generator. \
It's capable of generating RSS feeds from pretty much everything.
"""]
link = "https://docs.rsshub.app"
[[software]] NetNewsWire
title = "Recommended News Software" https://netnewswire.com
subtitle = ["""
Take back control of your news. With free, quality, software for your \
desktop, home and mobile devices.
"""]
[[software]] Newsboat
name = "CommaFeed" https://newsboat.org
info = ["""
A self-hosted RSS reader, based on Dropwizard and React/TypeScript.
"""]
link = "https://commafeed.com"
os = "Any (HTML)"
[[software]] Spot-On
name = "FreshRSS" https://textbrowser.github.io/spot-on/
info = ["""
FreshRSS is a self-hosted RSS and Atom feed aggregator.
It is lightweight, easy to work with, powerful, and customizable.
"""]
link = "https://freshrss.org"
os = "Any (HTML)"
[[software]] Vienna RSS
name = "Liferea" https://vienna-rss.com
info = [""" """
Liferea is a feed reader/news aggregator that brings together all of the \
content from your favorite subscriptions into a simple interface that makes \
it easy to organize and browse feeds. Its GUI is similar to a desktop \
mail/news client, with an embedded web browser.
"""]
link = "https://lzone.de/liferea/"
os = "FreeBSD and Linux"
[[software]] [resources]
name = "NetNewsWire" info = """
info = [""" Useful Resources:
NetNewsWire shows you articles from your favorite blogs and news sites and \
keeps track of what youve read.
This means you can stop going from page to page in your browser looking for \ feedparser
new articles to read. Do it the easy way instead: let NetNewsWire bring you \ https://pythonhosted.org/feedparser
the news.
And, if youve been getting your news via the commercial Social Networks \ Slixmpp
with their ads, algorithms, user tracking, outrage, and misinformation you \ https://slixmpp.readthedocs.io
can switch to NetNewsWire to get news directly and more reliably from the \
sites you trust.
"""]
link = "https://netnewswire.com"
os = "MacOS"
[[software]] XMPP
name = "Newsboat" https://xmpp.org/about
info = [""" """
Newsboat is an RSS/Atom feed reader for the text console. Its an actively \
maintained fork of Newsbeuter
"""]
link = "https://newsboat.org"
os = "Any"
[[software]] [rss-task-force]
name = "Spot-On" info = """
info = ["""
Spot-On is a software carnival which brings chat, email, news, newsgroups, \
search and other forms of communications into a single communications \
orchestra.
"""]
link = "https://textbrowser.github.io/spot-on/"
os = "Any"
[[software]]
name = "Vienna RSS"
info = ["""
Vienna is an RSS/Atom reader for macOS, packed with powerful features that \
help you make sense of the flood of information that is distributed via \
these formats today.
"""]
link = "https://vienna-rss.com"
os = "MacOS"
[[resources]]
title = "Useful Resources"
subtitle = "Technologies which Slixfeed is based upon"
[[resources]]
name = "feedparser"
info = "Syndication Library"
url = "https://pythonhosted.org/feedparser"
[[resources]]
name = "Slixmpp"
info = "XMPP Library"
url = "https://slixmpp.readthedocs.io"
[[resources]]
name = "XMPP"
info = "Messaging Protocol"
url = "https://xmpp.org/about"
[[rss_task_force]]
title = "About RSS Task Force"
subtitle = "Swiss Organization"
[[rss_task_force]]
info = ["""
The RSS Task Force (previously known as The Syndication Society) is an \ The RSS Task Force (previously known as The Syndication Society) is an \
international organization headquartered in Switzerland. international organization headquartered in Switzerland.
@ -706,44 +235,35 @@ Thanks to a joint effort of transport and travel companies, in 2021 we have \
expanded our cause towards all entities of all types and sorts. expanded our cause towards all entities of all types and sorts.
The RSS Task Force was founded by two taxicab drivers in 2018. The RSS Task Force was founded by two taxicab drivers in 2018.
"""] """
[[sleekxmpp]] [sleekxmpp]
title = "About Project SleekXMPP" info = """
subtitle = "SleekXMPP XMPP Library"
[[sleekxmpp]]
info = ["""
SleekXMPP is an MIT licensed XMPP library for Python 2.6/3.1+, and is \ SleekXMPP is an MIT licensed XMPP library for Python 2.6/3.1+, and is \
featured in examples in the book XMPP: The Definitive Guide by Kevin Smith, \ featured in examples in the book XMPP: The Definitive Guide by Kevin Smith, \
Remko Tronçon, and Peter Saint-Andre. Remko Tronçon, and Peter Saint-Andre.
"""]
url = "https://codeberg.org/fritzy/SleekXMPP"
[[slixmpp]] https://codeberg.org/fritzy/SleekXMPP
title = "About Project Slixmpp" """
subtitle = "Slixmpp XMPP Library"
[[slixmpp]] [slixmpp]
info = [""" info = """
Slixmpp is an MIT licensed XMPP library for Python 3.7+. It is a fork of \ Slixmpp is an MIT licensed XMPP library for Python 3.7+. It is a fork of \
SleekXMPP. SleekXMPP.
Slixmpp's goals is to only rewrite the core of the SleekXMPP library \ Slixmpp's goals is to only rewrite the core of the SleekXMPP library \
(the low level socket handling, the timers, the events dispatching) \ (the low level socket handling, the timers, the events dispatching) \
in order to remove all threads. in order to remove all threads.
"""]
url = "https://codeberg.org/poezio/slixmpp"
[[xmpp]] https://codeberg.org/poezio/slixmpp
title = "About XMPP" """
subtitle = "Previously known as Jabber"
[[xmpp]] [xmpp]
info = [""" info = """
XMPP is the Extensible Messaging and Presence Protocol, a set of open \ XMPP is the Extensible Messaging and Presence Protocol, a set of open \
technologies for instant messaging, presence, multi-party chat, voice and \ technologies for instant messaging, presence, multi-party chat, voice and \
video calls, collaboration, lightweight middleware, content syndication, and \ video calls, collaboration, lightweight middleware, content syndication, and \
generalized routing of XML data. generalized routing of XML data.
"""]
link = "https://xmpp.org/about" https://xmpp.org/about
"""

View file

@ -1,77 +0,0 @@
# This file lists default settings per database.
# See file /usr/share/slixfeed/settings.ini
#[Bot Settings]
# Check interval
#interval = 90
[Settings]
#[Contact Settings]
# Maximum items to archive (0 - 500)
archive = 50
# Source check interval (recommended 90; minimum 10)
check = 120
# Work status (Value 0 to disable)
enabled = 1
# Enable filters (Value 1 to enable)
filter = 0
# Update interval in minutes (Minimum value 10)
interval = 300
# Maximum length of summary (Value 0 to disable)
length = 300
# Display media (audio, image, video) when available
media = 0
# Mark entries of newly added entries as unread
old = 0
# Amount of entries per update
quantum = 3
# Pick random item from database
random = 0
# Set message formatting
formatting = {title}\n> {summary}\n{link}\n{feed_title} [{ix}]\n\n
# Utilized in case of missing protocol support.
[Bridge]
gopher =
i2p =
nostr =
tor =
yggdrasil =
[Network]
# Example http://localhost:8118 (privoxy)
http_proxy =
# User Agent
user_agent = Slixfeed/0.1
# Enable policed DNS system (not recommended)
# clearnet = 1
# Enable I2P mixnet system (safer)
i2p = 1
# Enable Loki mixnet system (safer)
loki = 1
# Enable Tor semi-mixnet system (semi-safer)
tor = 1
# Enable Yggdrasil mixnet system (safer)
yggdrasil = 1

View file

@ -0,0 +1,47 @@
# This file lists default settings per database.
# See file /usr/share/slixfeed/settings.toml
[default]
archive = 50 # Maximum items to archive (0 - 500)
check = 120 # Source check interval (recommended 90; minimum 10)
enabled = 1 # Work status (Value 0 to disable)
interval = 300 # Update interval (Minimum value 10)
length = 300 # Maximum length of summary (Value 0 to disable)
media = 0 # Display media (audio, image, video) when available
old = 0 # Mark entries of newly added entries as unread
quantum = 3 # Amount of entries per update
random = 0 # Pick random item from database
# Message styling is not to be modified from bot
# * title = Title of item
# * summary = Summary of item
# * link = Link of item
# * feed_title = Title of news source
# * ix = Index of item
formatting = """
{title}
> {summary}
{link}
{feed_title} [{ix}]
"""
# Utilized in case of missing protocol support.
[bridge]
gopher = ""
i2p = ""
ipfs = ""
nostr = ""
tor = ""
yggdrasil = ""
[network]
http_proxy = "http://localhost:8118"
user_agent = "Slixfeed/0.1"
clearnet = 0 # Enable policed DNS system (not recommended)
i2p = 1 # Enable I2P mixnet system (safer)
ipfs = 1 # Enable IPFS DHT system (safer)
loki = 1 # Enable Loki mixnet system (safer)
tor = 1 # Enable Tor semi-mixnet system (semi-safer)
yggdrasil = 1 # Enable Yggdrasil mixnet system (safer)

View file

@ -50,26 +50,21 @@ except:
class Config: class Config:
def add_settings_default(settings): def add_settings_default(settings):
settings['default'] = {} settings_default = get_values('settings.toml', 'settings')
for key in ('archive', 'check', 'enabled', 'filter', 'formatting', settings['default'] = settings_default
'interval', 'length', 'media', 'old', 'quantum'):
value = get_value('settings', 'Settings', key)
settings['default'][key] = value
# TODO Open SQLite file once
def add_settings_jid(settings, jid_bare, db_file): def add_settings_jid(settings, jid_bare, db_file):
settings[jid_bare] = {} settings[jid_bare] = {}
for key in ('archive', 'enabled', 'filter', 'formatting', 'interval', for key in ('archive', 'enabled', 'filter', 'formatting', 'interval',
'length', 'media', 'old', 'quantum'): 'length', 'media', 'old', 'quantum'):
value = sqlite.get_setting_value(db_file, key) value = sqlite.get_setting_value(db_file, key)
if value: value = value[0] if value: settings[jid_bare][key] = value[0]
settings[jid_bare][key] = value
def add_settings_xmpp(settings):
settings['xmpp'] = {}
for key in ('operator', 'reconnect_timeout', 'type'):
value = get_value('accounts', 'XMPP', key)
settings['xmpp'][key] = value
def get_settings_xmpp(key=None):
result = get_values('accounts.toml', 'xmpp')
result = result[key] if key else result
return result
async def set_setting_value(settings, jid_bare, db_file, key, val): async def set_setting_value(settings, jid_bare, db_file, key, val):
key = key.lower() key = key.lower()
@ -81,73 +76,12 @@ class Config:
await sqlite.set_setting_value(db_file, key_val) await sqlite.set_setting_value(db_file, key_val)
def get_setting_value(settings, jid_bare, key): def get_setting_value(settings, jid_bare, key):
if key in settings[jid_bare]: if jid_bare in settings and key in settings[jid_bare]:
value = settings[jid_bare][key] value = settings[jid_bare][key]
else: else:
value = settings['default'][key] value = settings['default'][key]
return value return value
# self.settings = {}
# initiate an empty dict and the rest would be:
# settings['account'] = {}
# settings['default'] = {}
# settings['jabber@id'] = {}
# def __init__(self, db_file):
# self.archive = get_setting_value(db_file, 'archive')
# self.enabled = get_setting_value(db_file, 'enabled')
# self.formatting = get_setting_value(db_file, 'formatting')
# self.interval = get_setting_value(db_file, 'interval')
# self.length = get_setting_value(db_file, 'length')
# self.media = get_setting_value(db_file, 'media')
# self.old = get_setting_value(db_file, 'old')
# self.quantum = get_setting_value(db_file, 'quantum')
# def default():
# archive = get_value('settings', 'Settings', 'archive')
# enabled = get_value('settings', 'Settings', 'enabled')
# formatting = get_value('settings', 'Settings', 'formatting')
# interval = get_value('settings', 'Settings', 'interval')
# length = get_value('settings', 'Settings', 'length')
# media = get_value('settings', 'Settings', 'media')
# old = get_value('settings', 'Settings', 'old')
# quantum = get_value('settings', 'Settings', 'quantum')
# def jid(db_file):
# archive = sqlite.get_setting_value(db_file, 'archive')
# enabled = sqlite.get_setting_value(db_file, 'enabled')
# formatting = sqlite.get_setting_value(db_file, 'formatting')
# interval = sqlite.get_setting_value(db_file, 'interval')
# length = sqlite.get_setting_value(db_file, 'length')
# media = sqlite.get_setting_value(db_file, 'media')
# old = sqlite.get_setting_value(db_file, 'old')
# quantum = sqlite.get_setting_value(db_file, 'quantum')
class ConfigXMPP:
def __init__(self):
self.setting = {}
for key in ('operator', 'reconnect_timeout', 'type'):
value = get_value('accounts', 'XMPP', key)
self.setting[key] = value
class ConfigClient:
def __init__(self):
self.setting = {}
for key in ('alias', 'jid', 'operator', 'password', 'hostname', 'port'):
value = get_value('accounts', 'XMPP Client', key)
self.setting[key] = value
class ConfigDefault:
def __init__(self, settings):
settings['default'] = {}
for key in ('archive', 'check', 'enabled', 'filter', 'formatting',
'interval', 'length', 'media', 'old', 'quantum'):
value = get_value('settings', 'Settings', key)
settings['default'][key] = value
class ConfigNetwork: class ConfigNetwork:
def __init__(self, settings): def __init__(self, settings):
settings['network'] = {} settings['network'] = {}
@ -167,6 +101,19 @@ class ConfigJabberID:
settings[jid_bare][key] = value settings[jid_bare][key] = value
def get_values(filename, key=None):
config_dir = get_default_config_directory()
if not os.path.isdir(config_dir):
config_dir = '/usr/share/slixfeed/'
if not os.path.isdir(config_dir):
config_dir = os.path.dirname(__file__) + "/assets"
config_file = os.path.join(config_dir, filename)
with open(config_file, mode="rb") as defaults:
result = tomllib.load(defaults)
values = result[key] if key else result
return values
def get_setting_value(db_file, key): def get_setting_value(db_file, key):
value = sqlite.get_setting_value(db_file, key) value = sqlite.get_setting_value(db_file, key)
if value: if value:
@ -299,9 +246,7 @@ def get_value(filename, section, keys):
for key in keys: for key in keys:
if key in section_res: if key in section_res:
value = section_res[key] value = section_res[key]
logging.debug( logging.debug("Found value {} for key {}".format(value, key))
"Found value {} for key {}".format(value, key)
)
else: else:
value = '' value = ''
logging.debug("Missing key:", key) logging.debug("Missing key:", key)
@ -310,9 +255,7 @@ def get_value(filename, section, keys):
key = keys key = keys
if key in section_res: if key in section_res:
result = section_res[key] result = section_res[key]
logging.debug( logging.debug("Found value {} for key {}".format(result, key))
"Found value {} for key {}".format(result, key)
)
else: else:
result = '' result = ''
# logging.error("Missing key:", key) # logging.error("Missing key:", key)

View file

@ -119,10 +119,11 @@ async def http(url):
msg: list or str msg: list or str
Document or error message. Document or error message.
""" """
user_agent = (config.get_value("settings", "Network", "user_agent") user_agent = (config.get_values('settings.toml', 'network')['user_agent']
or 'Slixfeed/0.1') or 'Slixfeed/0.1')
headers = {'User-Agent': user_agent} headers = {'User-Agent': user_agent}
proxy = (config.get_value("settings", "Network", "http_proxy") or '') proxy = (config.get_values('settings.toml', 'network')['http_proxy']
or '')
timeout = ClientTimeout(total=10) timeout = ClientTimeout(total=10)
async with ClientSession(headers=headers) as session: async with ClientSession(headers=headers) as session:
# async with ClientSession(trust_env=True) as session: # async with ClientSession(trust_env=True) as session:

View file

@ -2243,7 +2243,7 @@ def get_feeds(db_file):
cur = conn.cursor() cur = conn.cursor()
sql = ( sql = (
""" """
SELECT name, url, id SELECT id, name, url
FROM feeds FROM feeds
""" """
) )

View file

@ -172,7 +172,7 @@ async def task_send(self, jid_bare):
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
if jid_bare not in self.settings: if jid_bare not in self.settings:
Config.add_settings_jid(self.settings, jid_bare, db_file) Config.add_settings_jid(self.settings, jid_bare, db_file)
update_interval = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] update_interval = Config.get_setting_value(self.settings, jid_bare, 'interval')
update_interval = 60 * int(update_interval) update_interval = 60 * int(update_interval)
last_update_time = sqlite.get_last_update_time(db_file) last_update_time = sqlite.get_last_update_time(db_file)
if last_update_time: if last_update_time:
@ -232,7 +232,7 @@ def refresh_task(self, jid_bare, callback, key, val=None):
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
if jid_bare not in self.settings: if jid_bare not in self.settings:
Config.add_settings_jid(self.settings, jid_bare, db_file) Config.add_settings_jid(self.settings, jid_bare, db_file)
val = self.settings[jid_bare][key] or self.settings['default'][key] val = Config.get_setting_value(self.settings, jid_bare, key)
# if self.task_manager[jid][key]: # if self.task_manager[jid][key]:
if jid_bare in self.task_manager: if jid_bare in self.task_manager:
try: try:
@ -268,7 +268,7 @@ async def wait_and_run(self, callback, jid_bare, val):
# TODO Take this function out of # TODO Take this function out of
# <class 'slixmpp.clientxmpp.ClientXMPP'> # <class 'slixmpp.clientxmpp.ClientXMPP'>
async def check_updates(self, jid): async def check_updates(self, jid_bare):
""" """
Start calling for update check up. Start calling for update check up.
@ -277,15 +277,15 @@ async def check_updates(self, jid):
jid : str jid : str
Jabber ID. Jabber ID.
""" """
logging.info('Scanning for updates for JID {}'.format(jid)) logging.info('Scanning for updates for JID {}'.format(jid_bare))
while True: while True:
jid_file = jid.replace('/', '_') jid_file = jid_bare.replace('/', '_')
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
urls = sqlite.get_active_feeds_url(db_file) urls = sqlite.get_active_feeds_url(db_file)
for url in urls: for url in urls:
await action.scan(self, jid, db_file, url) await action.scan(self, jid_bare, db_file, url)
await asyncio.sleep(50) await asyncio.sleep(50)
val = self.settings['default']['check'] val = Config.get_setting_value(self.settings, jid_bare, 'check')
await asyncio.sleep(60 * float(val)) await asyncio.sleep(60 * float(val))
# Schedule to call this function again in 90 minutes # Schedule to call this function again in 90 minutes
# loop.call_at( # loop.call_at(

View file

@ -1,2 +1,2 @@
__version__ = '0.1.36' __version__ = '0.1.37'
__version_info__ = (0, 1, 36) __version_info__ = (0, 1, 37)

View file

@ -55,7 +55,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.utility import get_chat_type from slixfeed.xmpp.utility import get_chat_type, is_operator
import sys import sys
import time import time
@ -70,6 +70,7 @@ import logging
import os import os
import slixfeed.action as action import slixfeed.action as action
import slixfeed.config as config import slixfeed.config as config
from slixfeed.config import Config
import slixfeed.crawl as crawl import slixfeed.crawl as crawl
import slixfeed.dt as dt import slixfeed.dt as dt
import slixfeed.fetch as fetch import slixfeed.fetch as fetch
@ -123,14 +124,18 @@ class Slixfeed(slixmpp.ClientXMPP):
self.task_ping_instance = {} self.task_ping_instance = {}
# Handler for configuration # Handler for configuration
self.settings = {} self.settings = config.get_values('settings.toml')
# Populate handler # Handler for operators
Config.add_settings_default(self.settings) self.operators = config.get_values('accounts.toml', 'xmpp')['operators']
Config.add_settings_xmpp(self.settings)
# self.settings = {}
# # Populate dict handler
# Config.add_settings_default(self.settings)
# Handlers for connection events # Handlers for connection events
self.connection_attempts = 0 self.connection_attempts = 0
self.max_connection_attempts = 10 self.max_connection_attempts = 10
self.reconnect_timeout = config.get_values('accounts.toml', 'xmpp')['settings']['reconnect_timeout']
self.add_event_handler("session_start", self.add_event_handler("session_start",
self.on_session_start) self.on_session_start)
@ -261,16 +266,14 @@ class Slixfeed(slixmpp.ClientXMPP):
function_name = sys._getframe().f_code.co_name function_name = sys._getframe().f_code.co_name
message_log = '{}' message_log = '{}'
logger.debug(message_log.format(function_name)) logger.debug(message_log.format(function_name))
jid_operator = config.get_value('accounts', 'XMPP', 'operator') status_message = 'Slixfeed version {}'.format(__version__)
if jid_operator: for operator in self.operators:
status_message = ('Wait while Slixfeed {} is being loaded...' XmppPresence.send(self, operator['jid'], status_message)
.format(__version__))
XmppPresence.send(self, jid_operator, status_message)
await profile.update(self) await profile.update(self)
profile.set_identity(self, 'client') profile.set_identity(self, 'client')
await self['xep_0115'].update_caps() await self['xep_0115'].update_caps()
# self.send_presence() # self.send_presence()
# await self.get_roster() await self.get_roster()
bookmarks = await self.plugin['xep_0048'].get_bookmarks() bookmarks = await self.plugin['xep_0048'].get_bookmarks()
XmppGroupchat.autojoin(self, bookmarks) XmppGroupchat.autojoin(self, bookmarks)
# XmppCommand.adhoc_commands(self) # XmppCommand.adhoc_commands(self)
@ -335,16 +338,16 @@ class Slixfeed(slixmpp.ClientXMPP):
XmppPresence.send(self, jid_bare, status_message) XmppPresence.send(self, jid_bare, status_message)
else: else:
# TODO Request for subscription # TODO Request for subscription
if (await get_chat_type(self, jid_bare) == 'chat' and # if (await get_chat_type(self, jid_bare) == 'chat' and
not self.client_roster[jid_bare]['to']): # not self.client_roster[jid_bare]['to']):
XmppPresence.subscription(self, jid_bare, 'subscribe') # XmppPresence.subscription(self, jid_bare, 'subscribe')
await XmppRoster.add(self, jid_bare) # await XmppRoster.add(self, jid_bare)
status_message = '✒️ Share online status to receive updates' # status_message = '✒️ Share online status to receive updates'
XmppPresence.send(self, jid_bare, status_message) # XmppPresence.send(self, jid_bare, status_message)
message_subject = 'RSS News Bot' # message_subject = 'RSS News Bot'
message_body = 'Share online status to receive updates.' # message_body = 'Share online status to receive updates.'
XmppMessage.send_headline(self, jid_bare, message_subject, # XmppMessage.send_headline(self, jid_bare, message_subject,
message_body, 'chat') # message_body, 'chat')
await process.message(self, message) await process.message(self, message)
# chat_type = message["type"] # chat_type = message["type"]
# message_body = message["body"] # message_body = message["body"]
@ -671,13 +674,16 @@ class Slixfeed(slixmpp.ClientXMPP):
# ) # )
# NOTE https://codeberg.org/poezio/slixmpp/issues/3515 # NOTE https://codeberg.org/poezio/slixmpp/issues/3515
# if jid == self.settings['xmpp']['operator']: # if is_operator(self, jid_bare):
self['xep_0050'].add_command(node='recent', self['xep_0050'].add_command(node='recent',
name='📰️ Browse', name='📰️ Browse',
handler=self._handle_recent) handler=self._handle_recent)
self['xep_0050'].add_command(node='subscription', self['xep_0050'].add_command(node='subscription',
name='🪶️ Subscribe', name='🪶️ Subscribe',
handler=self._handle_subscription_add) handler=self._handle_subscription_add)
self['xep_0050'].add_command(node='publish',
name='📣️ Publish',
handler=self._handle_publish)
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)
@ -712,6 +718,42 @@ class Slixfeed(slixmpp.ClientXMPP):
# Special interface # Special interface
# http://jabber.org/protocol/commands#actions # http://jabber.org/protocol/commands#actions
async def _handle_publish(self, iq, session):
form = self['xep_0004'].make_form('form', 'Publish')
form['instructions'] = ('In order to publish via Pubsub Social Feed '
'(XEP-0472), you will have to choose a '
'Publish-Subscribe (XEP-0060) hostname and '
'be permitted to publish into it.')
# TODO Select from list-multi
form.add_field(var='subscription',
ftype='text-single',
label='URL',
desc='Enter subscription URL.',
value='http://',
required=True)
form.add_field(var='subscription',
ftype='text-single',
label='PubSub',
desc='Enter a PubSub URL.',
value='pubsub.' + self.boundjid.host,
required=True)
session['allow_prev'] = False
session['has_next'] = True
session['next'] = self._handle_preview
session['prev'] = None
session['payload'] = form
return session
def _handle_preview(self, payload, session):
jid_full = str(session['from'])
function_name = sys._getframe().f_code.co_name
logger.debug('{}: jid_full: {}'
.format(function_name, jid_full))
text_note = ('XEP-0472: Pubsub Social Feed will be available soon.')
session['notes'] = [['info', text_note]]
session['payload'] = None
return session
async def _handle_profile(self, iq, session): async def _handle_profile(self, iq, session):
jid_full = str(session['from']) jid_full = str(session['from'])
function_name = sys._getframe().f_code.co_name function_name = sys._getframe().f_code.co_name
@ -747,42 +789,42 @@ class Slixfeed(slixmpp.ClientXMPP):
value=unread) value=unread)
form.add_field(ftype='fixed', form.add_field(ftype='fixed',
value='Options') value='Options')
key_archive = self.settings[jid_bare]['archive'] or self.settings['default']['archive'] key_archive = Config.get_setting_value(self.settings, jid_bare, 'archive')
key_archive = str(key_archive) key_archive = str(key_archive)
form.add_field(label='Archive', form.add_field(label='Archive',
ftype='text-single', ftype='text-single',
value=key_archive) value=key_archive)
key_enabled = self.settings[jid_bare]['enabled'] or self.settings['default']['enabled'] key_enabled = Config.get_setting_value(self.settings, jid_bare, 'enabled')
key_enabled = str(key_enabled) key_enabled = str(key_enabled)
form.add_field(label='Enabled', form.add_field(label='Enabled',
ftype='text-single', ftype='text-single',
value=key_enabled) value=key_enabled)
key_interval = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] key_interval = Config.get_setting_value(self.settings, jid_bare, 'interval')
key_interval = str(key_interval) key_interval = str(key_interval)
form.add_field(label='Interval', form.add_field(label='Interval',
ftype='text-single', ftype='text-single',
value=key_interval) value=key_interval)
key_length = self.settings[jid_bare]['length'] or self.settings['default']['length'] key_length = Config.get_setting_value(self.settings, jid_bare, 'length')
key_length = str(key_length) key_length = str(key_length)
form.add_field(label='Length', form.add_field(label='Length',
ftype='text-single', ftype='text-single',
value=key_length) value=key_length)
key_media = self.settings[jid_bare]['media'] or self.settings['default']['media'] key_media = Config.get_setting_value(self.settings, jid_bare, 'media')
key_media = str(key_media) key_media = str(key_media)
form.add_field(label='Media', form.add_field(label='Media',
ftype='text-single', ftype='text-single',
value=key_media) value=key_media)
key_old = self.settings[jid_bare]['old'] or self.settings['default']['old'] key_old = Config.get_setting_value(self.settings, jid_bare, 'old')
key_old = str(key_old) key_old = str(key_old)
form.add_field(label='Old', form.add_field(label='Old',
ftype='text-single', ftype='text-single',
value=key_old) value=key_old)
key_quantum = self.settings[jid_bare]['quantum'] or self.settings['default']['quantum'] key_quantum = Config.get_setting_value(self.settings, jid_bare, 'quantum')
key_quantum = str(key_quantum) key_quantum = str(key_quantum)
form.add_field(label='Quantum', form.add_field(label='Quantum',
ftype='text-single', ftype='text-single',
value=key_quantum) value=key_quantum)
update_interval = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] update_interval = Config.get_setting_value(self.settings, jid_bare, 'interval')
update_interval = str(update_interval) update_interval = str(update_interval)
update_interval = 60 * int(update_interval) update_interval = 60 * int(update_interval)
last_update_time = sqlite.get_last_update_time(db_file) last_update_time = sqlite.get_last_update_time(db_file)
@ -797,7 +839,7 @@ class Slixfeed(slixmpp.ClientXMPP):
else: else:
next_update = 'n/a' next_update = 'n/a'
else: else:
last_update_time = 'n/a' last_update = 'n/a'
next_update = 'n/a' next_update = 'n/a'
form.add_field(ftype='fixed', form.add_field(ftype='fixed',
value='Schedule') value='Schedule')
@ -1394,8 +1436,10 @@ class Slixfeed(slixmpp.ClientXMPP):
.format(function_name, jid_full)) .format(function_name, jid_full))
jid_bare = session['from'].bare jid_bare = session['from'].bare
chat_type = await get_chat_type(self, jid_bare) chat_type = await get_chat_type(self, jid_bare)
moderator = None
if chat_type == 'groupchat': if chat_type == 'groupchat':
moderator = is_moderator(self, jid_bare, jid_full) moderator = is_moderator(self, jid_bare, jid_full)
# moderator = moderator if moderator else None
if chat_type == 'chat' or moderator: if chat_type == 'chat' or moderator:
form = self['xep_0004'].make_form('form', 'Discover & Search') form = self['xep_0004'].make_form('form', 'Discover & Search')
form['instructions'] = 'Discover news subscriptions of all kinds' form['instructions'] = 'Discover news subscriptions of all kinds'
@ -1557,10 +1601,10 @@ class Slixfeed(slixmpp.ClientXMPP):
desc=('Select a subscription to edit.'), desc=('Select a subscription to edit.'),
required=True) required=True)
subscriptions = sqlite.get_feeds(db_file) subscriptions = sqlite.get_feeds(db_file)
subscriptions = sorted(subscriptions, key=lambda x: x[0]) subscriptions = sorted(subscriptions, key=lambda x: x[1])
for subscription in subscriptions: for subscription in subscriptions:
title = subscription[0] title = subscription[1]
url = subscription[1] url = subscription[2]
options.addOption(title, url) options.addOption(title, url)
session['has_next'] = True session['has_next'] = True
session['next'] = self._handle_subscription_editor session['next'] = self._handle_subscription_editor
@ -1576,10 +1620,10 @@ class Slixfeed(slixmpp.ClientXMPP):
desc=('Select subscriptions to remove.'), desc=('Select subscriptions to remove.'),
required=True) required=True)
subscriptions = sqlite.get_feeds(db_file) subscriptions = sqlite.get_feeds(db_file)
subscriptions = sorted(subscriptions, key=lambda x: x[0]) subscriptions = sorted(subscriptions, key=lambda x: x[1])
for subscription in subscriptions: for subscription in subscriptions:
title = subscription[0] title = subscription[1]
ix = str(subscription[2]) ix = str(subscription[0])
options.addOption(title, ix) options.addOption(title, ix)
session['cancel'] = self._handle_cancel session['cancel'] = self._handle_cancel
session['has_next'] = False session['has_next'] = False
@ -1693,8 +1737,8 @@ class Slixfeed(slixmpp.ClientXMPP):
# subscriptions = set(subscriptions) # subscriptions = set(subscriptions)
categorized_subscriptions = {} categorized_subscriptions = {}
for subscription in subscriptions: for subscription in subscriptions:
title = subscription[0] title = subscription[1]
url = subscription[1] url = subscription[2]
try: try:
letter = title[0].capitalize() letter = title[0].capitalize()
if letter not in categorized_subscriptions: if letter not in categorized_subscriptions:
@ -1906,10 +1950,10 @@ class Slixfeed(slixmpp.ClientXMPP):
jid_file = jid_bare jid_file = jid_bare
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
subscriptions = sqlite.get_feeds(db_file) subscriptions = sqlite.get_feeds(db_file)
subscriptions = sorted(subscriptions, key=lambda x: x[0]) subscriptions = sorted(subscriptions, key=lambda x: x[1])
for subscription in subscriptions: for subscription in subscriptions:
title = subscription[0] title = subscription[1]
url = subscription[1] url = subscription[2]
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',
@ -1949,7 +1993,7 @@ class Slixfeed(slixmpp.ClientXMPP):
options.addOption('Import', 'import') options.addOption('Import', 'import')
options.addOption('Export', 'export') options.addOption('Export', 'export')
jid = session['from'].bare jid = session['from'].bare
if jid == self.settings['xmpp']['operator']: if is_operator(self, jid):
options.addOption('Administration', 'admin') options.addOption('Administration', 'admin')
session['payload'] = form session['payload'] = form
session['next'] = self._handle_advanced_result session['next'] = self._handle_advanced_result
@ -1976,7 +2020,7 @@ class Slixfeed(slixmpp.ClientXMPP):
# NOTE Even though this check is already conducted on previous # NOTE Even though this check is already conducted on previous
# form, this check is being done just in case. # form, this check is being done just in case.
jid_bare = session['from'].bare jid_bare = session['from'].bare
if jid_bare == self.settings['xmpp']['operator']: if is_operator(self, jid_bare):
if self.is_component: if self.is_component:
# NOTE This will be changed with XEP-0222 XEP-0223 # NOTE This will be changed with XEP-0222 XEP-0223
text_info = ('Subscriber management options are ' text_info = ('Subscriber management options are '
@ -2076,9 +2120,10 @@ class Slixfeed(slixmpp.ClientXMPP):
options = form.add_field(var='option', options = form.add_field(var='option',
ftype='list-single', ftype='list-single',
label='About', label='About',
required=True) required=True,
value='about')
config_dir = config.get_default_config_directory() config_dir = config.get_default_config_directory()
with open(config_dir + '/' + 'information.toml', mode="rb") as information: with open(config_dir + '/' + 'about.toml', mode="rb") as information:
entries = tomllib.load(information) entries = tomllib.load(information)
for entry in entries: for entry in entries:
label = entries[entry][0]['title'] label = entries[entry][0]['title']
@ -2096,7 +2141,7 @@ class Slixfeed(slixmpp.ClientXMPP):
logger.debug('{}: jid_full: {}' logger.debug('{}: jid_full: {}'
.format(function_name, jid_full)) .format(function_name, jid_full))
config_dir = config.get_default_config_directory() config_dir = config.get_default_config_directory()
with open(config_dir + '/' + 'information.toml', mode="rb") as information: with open(config_dir + '/' + 'about.toml', mode="rb") as information:
entries = tomllib.load(information) entries = tomllib.load(information)
entry_key = payload['values']['option'] entry_key = payload['values']['option']
# case 'terms': # case 'terms':
@ -2174,7 +2219,7 @@ class Slixfeed(slixmpp.ClientXMPP):
cmds = tomllib.load(commands) cmds = tomllib.load(commands)
form = self['xep_0004'].make_form('result', 'Manual') form = self['xep_0004'].make_form('result', 'Manual')
form['instructions'] = '🛟️ Help manual for interactive chat' form['instructions'] = 'Help manual for interactive chat'
# text = '🛟️ Help and Information about Slixfeed\n\n' # text = '🛟️ Help and Information about Slixfeed\n\n'
# for cmd in cmds: # for cmd in cmds:
@ -2350,7 +2395,7 @@ class Slixfeed(slixmpp.ClientXMPP):
jid_file = jid_bare jid_file = jid_bare
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
subscriptions = sqlite.get_feeds(db_file) subscriptions = sqlite.get_feeds(db_file)
subscriptions = sorted(subscriptions, key=lambda x: x[0]) subscriptions = sorted(subscriptions, key=lambda x: x[1])
form = self['xep_0004'].make_form('form', 'Subscriptions') form = self['xep_0004'].make_form('form', 'Subscriptions')
match payload['values']['action']: match payload['values']['action']:
case 'bookmarks': case 'bookmarks':
@ -2680,7 +2725,7 @@ class Slixfeed(slixmpp.ClientXMPP):
Config.add_settings_jid(self.settings, jid_bare, db_file) Config.add_settings_jid(self.settings, jid_bare, db_file)
form = self['xep_0004'].make_form('form', 'Settings') form = self['xep_0004'].make_form('form', 'Settings')
form['instructions'] = 'Editing settings' form['instructions'] = 'Editing settings'
value = self.settings[jid_bare]['enabled'] or self.settings['default']['enabled'] value = Config.get_setting_value(self.settings, jid_bare, 'enabled')
value = str(value) value = str(value)
value = int(value) value = int(value)
if value: if value:
@ -2692,7 +2737,7 @@ class Slixfeed(slixmpp.ClientXMPP):
label='Enabled', label='Enabled',
desc='Enable news updates.', desc='Enable news updates.',
value=value) value=value)
value = self.settings[jid_bare]['media'] or self.settings['default']['media'] value = Config.get_setting_value(self.settings, jid_bare, 'media')
value = str(value) value = str(value)
value = int(value) value = int(value)
if value: if value:
@ -2704,7 +2749,7 @@ class Slixfeed(slixmpp.ClientXMPP):
desc='Send audio, images or videos if found.', desc='Send audio, images or videos if found.',
label='Display media', label='Display media',
value=value) value=value)
value = self.settings[jid_bare]['old'] or self.settings['default']['old'] value = Config.get_setting_value(self.settings, jid_bare, 'old')
value = str(value) value = str(value)
value = int(value) value = int(value)
if value: if value:
@ -2717,7 +2762,7 @@ class Slixfeed(slixmpp.ClientXMPP):
# label='Send only new items', # label='Send only new items',
label='Include old news', label='Include old news',
value=value) value=value)
value = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] value = Config.get_setting_value(self.settings, jid_bare, 'interval')
value = str(value) value = str(value)
value = int(value) value = int(value)
value = value/60 value = value/60
@ -2738,7 +2783,7 @@ class Slixfeed(slixmpp.ClientXMPP):
i += 6 i += 6
else: else:
i += 1 i += 1
value = self.settings[jid_bare]['quantum'] or self.settings['default']['quantum'] value = Config.get_setting_value(self.settings, jid_bare, 'quantum')
value = str(value) value = str(value)
options = form.add_field(var='quantum', options = form.add_field(var='quantum',
ftype='list-single', ftype='list-single',
@ -2752,7 +2797,7 @@ class Slixfeed(slixmpp.ClientXMPP):
x = str(i) x = str(i)
options.addOption(x, x) options.addOption(x, x)
i += 1 i += 1
value = self.settings[jid_bare]['archive'] or self.settings['default']['archive'] value = Config.get_setting_value(self.settings, jid_bare, 'archive')
value = str(value) value = str(value)
options = form.add_field(var='archive', options = form.add_field(var='archive',
ftype='list-single', ftype='list-single',
@ -2807,7 +2852,7 @@ class Slixfeed(slixmpp.ClientXMPP):
if val < 1: val = 1 if val < 1: val = 1
val = val * 60 val = val * 60
is_enabled = self.settings[jid_bare]['enabled'] or self.settings['default']['enabled'] is_enabled = Config.get_setting_value(self.settings, jid_bare, 'enabled')
if (key == 'enabled' and if (key == 'enabled' and
val == 1 and val == 1 and

View file

@ -36,7 +36,7 @@ from slixfeed.log import Logger
from slixfeed.version import __version__ from slixfeed.version import __version__
from slixfeed.xmpp.connect import XmppConnect from slixfeed.xmpp.connect import XmppConnect
# NOTE MUC is possible for component # NOTE MUC is possible for component
# from slixfeed.xmpp.muc import XmppGroupchat from slixfeed.xmpp.muc import XmppGroupchat
from slixfeed.xmpp.message import XmppMessage from slixfeed.xmpp.message import XmppMessage
import slixfeed.xmpp.process as process import slixfeed.xmpp.process as process
import slixfeed.xmpp.profile as profile import slixfeed.xmpp.profile as profile
@ -46,7 +46,7 @@ from slixfeed.xmpp.presence import XmppPresence
# from slixmpp.xmlstream import ET # from slixmpp.xmlstream import ET
# from slixmpp.xmlstream.handler import Callback # from slixmpp.xmlstream.handler import Callback
# from slixmpp.xmlstream.matcher import MatchXPath # from slixmpp.xmlstream.matcher import MatchXPath
from slixfeed.xmpp.utility import get_chat_type from slixfeed.xmpp.utility import get_chat_type, is_operator
import sys import sys
import time import time
@ -115,14 +115,18 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
self.task_ping_instance = {} self.task_ping_instance = {}
# Handler for configuration # Handler for configuration
self.settings = {} self.settings = config.get_values('settings.toml')
# Populate handler # Handler for operators
Config.add_settings_default(self.settings) self.operators = config.get_values('accounts.toml', 'xmpp')['operators']
Config.add_settings_xmpp(self.settings)
# self.settings = {}
# # Populate dict handler
# Config.add_settings_default(self.settings)
# Handlers for connection events # Handlers for connection events
self.connection_attempts = 0 self.connection_attempts = 0
self.max_connection_attempts = 10 self.max_connection_attempts = 10
self.reconnect_timeout = config.get_values('accounts.toml', 'xmpp')['settings']['reconnect_timeout']
self.add_event_handler("session_start", self.add_event_handler("session_start",
self.on_session_start) self.on_session_start)
@ -155,10 +159,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
self.add_event_handler("message", self.add_event_handler("message",
self.on_message) self.on_message)
# self.add_event_handler("groupchat_invite", self.add_event_handler("groupchat_invite",
# self.on_groupchat_invite) # XEP_0045 self.on_groupchat_invite) # XEP_0045
# self.add_event_handler("groupchat_direct_invite", self.add_event_handler("groupchat_direct_invite",
# self.on_groupchat_direct_invite) # XEP_0249 self.on_groupchat_direct_invite) # XEP_0249
# self.add_event_handler("groupchat_message", self.message) # self.add_event_handler("groupchat_message", self.message)
# self.add_event_handler("disconnected", self.reconnect) # self.add_event_handler("disconnected", self.reconnect)
@ -184,19 +188,19 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
self.on_session_end) self.on_session_end)
# async def on_groupchat_invite(self, message): async def on_groupchat_invite(self, message):
# logging.warning("on_groupchat_invite") # logging.warning("on_groupchat_invite")
# inviter = message['from'].bare inviter = message['from'].bare
# muc_jid = message['groupchat_invite']['jid'] muc_jid = message['groupchat_invite']['jid']
# await muc.join(self, inviter, muc_jid) XmppGroupchat.join(self, inviter, muc_jid)
# await bookmark.add(self, muc_jid) # await bookmark.add(self, muc_jid)
# NOTE Tested with Gajim and Psi # NOTE Tested with Gajim and Psi
# async def on_groupchat_direct_invite(self, message): async def on_groupchat_direct_invite(self, message):
# inviter = message['from'].bare inviter = message['from'].bare
# muc_jid = message['groupchat_invite']['jid'] muc_jid = message['groupchat_invite']['jid']
# await muc.join(self, inviter, muc_jid) XmppGroupchat.join(self, inviter, muc_jid)
# await bookmark.add(self, muc_jid) # await bookmark.add(self, muc_jid)
@ -234,10 +238,9 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
task.task_ping(self) task.task_ping(self)
# bookmarks = await self.plugin['xep_0048'].get_bookmarks() # bookmarks = await self.plugin['xep_0048'].get_bookmarks()
# XmppGroupchat.autojoin(self, bookmarks) # XmppGroupchat.autojoin(self, bookmarks)
jid_operator = config.get_value('accounts', 'XMPP', 'operator') status_message = 'Slixfeed version {}'.format(__version__)
if jid_operator: for operator in self.operators:
status_message = 'Slixfeed version {}'.format(__version__) XmppPresence.send(self, operator['jid'], status_message)
XmppPresence.send(self, jid_operator, status_message)
time_end = time.time() time_end = time.time()
difference = time_end - time_begin difference = time_end - time_begin
if difference > 1: logger.warning('{} (time: {})'.format(function_name, if difference > 1: logger.warning('{} (time: {})'.format(function_name,
@ -346,7 +349,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
if not self.client_roster[jid_bare]['to']: if not self.client_roster[jid_bare]['to']:
# XmppPresence.subscription(self, jid, 'subscribe') # XmppPresence.subscription(self, jid, 'subscribe')
XmppPresence.subscription(self, jid_bare, 'subscribed') XmppPresence.subscription(self, jid_bare, 'subscribed')
await XmppRoster.add(self, jid_bare) # await XmppRoster.add(self, jid_bare)
status_message = '✒️ Share online status to receive updates' status_message = '✒️ Share online status to receive updates'
XmppPresence.send(self, jid_bare, status_message) XmppPresence.send(self, jid_bare, status_message)
message_subject = 'RSS News Bot' message_subject = 'RSS News Bot'
@ -418,9 +421,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# status_message = None # status_message = None
XmppMessage.send(self, jid_bare, message_body, 'chat') XmppMessage.send(self, jid_bare, message_body, 'chat')
XmppPresence.subscription(self, jid_bare, 'unsubscribed') XmppPresence.subscription(self, jid_bare, 'unsubscribed')
# XmppPresence.send(self, jid, status_message, # status_message = '🖋️ You have been uubscribed'
# XmppPresence.send(self, jid_bare, status_message,
# presence_type='unsubscribed') # presence_type='unsubscribed')
XmppRoster.remove(self, jid_bare) # XmppRoster.remove(self, jid_bare)
time_end = time.time() time_end = time.time()
difference = time_end - time_begin difference = time_end - time_begin
if difference > 1: logger.warning('{} (time: {})'.format(function_name, if difference > 1: logger.warning('{} (time: {})'.format(function_name,
@ -631,13 +635,16 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# ) # )
# NOTE https://codeberg.org/poezio/slixmpp/issues/3515 # NOTE https://codeberg.org/poezio/slixmpp/issues/3515
# if jid == self.settings['xmpp']['operator']: # if is_operator(self, jid_bare):
self['xep_0050'].add_command(node='recent', self['xep_0050'].add_command(node='recent',
name='📰️ Browse', name='📰️ Browse',
handler=self._handle_recent) handler=self._handle_recent)
self['xep_0050'].add_command(node='subscription', self['xep_0050'].add_command(node='subscription',
name='🪶️ Subscribe', name='🪶️ Subscribe',
handler=self._handle_subscription_add) handler=self._handle_subscription_add)
self['xep_0050'].add_command(node='publish',
name='📣️ Publish',
handler=self._handle_publish)
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)
@ -672,6 +679,42 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# Special interface # Special interface
# http://jabber.org/protocol/commands#actions # http://jabber.org/protocol/commands#actions
async def _handle_publish(self, iq, session):
form = self['xep_0004'].make_form('form', 'Publish')
form['instructions'] = ('In order to publish via Pubsub Social Feed '
'(XEP-0472), you will have to choose a '
'Publish-Subscribe (XEP-0060) hostname and '
'be permitted to publish into it.')
# TODO Select from list-multi
form.add_field(var='subscription',
ftype='text-single',
label='URL',
desc='Enter subscription URL.',
value='http://',
required=True)
form.add_field(var='subscription',
ftype='text-single',
label='PubSub',
desc='Enter a PubSub URL.',
value='pubsub.' + self.boundjid.host,
required=True)
session['allow_prev'] = False
session['has_next'] = True
session['next'] = self._handle_preview
session['prev'] = None
session['payload'] = form
return session
def _handle_preview(self, payload, session):
jid_full = str(session['from'])
function_name = sys._getframe().f_code.co_name
logger.debug('{}: jid_full: {}'
.format(function_name, jid_full))
text_note = ('XEP-0472: Pubsub Social Feed will be available soon.')
session['notes'] = [['info', text_note]]
session['payload'] = None
return session
async def _handle_profile(self, iq, session): async def _handle_profile(self, iq, session):
jid_full = str(session['from']) jid_full = str(session['from'])
function_name = sys._getframe().f_code.co_name function_name = sys._getframe().f_code.co_name
@ -707,42 +750,42 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
value=unread) value=unread)
form.add_field(ftype='fixed', form.add_field(ftype='fixed',
value='Options') value='Options')
key_archive = self.settings[jid_bare]['archive'] or self.settings['default']['archive'] key_archive = Config.get_setting_value(self.settings, jid_bare, 'archive')
key_archive = str(key_archive) key_archive = str(key_archive)
form.add_field(label='Archive', form.add_field(label='Archive',
ftype='text-single', ftype='text-single',
value=key_archive) value=key_archive)
key_enabled = self.settings[jid_bare]['enabled'] or self.settings['default']['enabled'] key_enabled = Config.get_setting_value(self.settings, jid_bare, 'enabled')
key_enabled = str(key_enabled) key_enabled = str(key_enabled)
form.add_field(label='Enabled', form.add_field(label='Enabled',
ftype='text-single', ftype='text-single',
value=key_enabled) value=key_enabled)
key_interval = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] key_interval = Config.get_setting_value(self.settings, jid_bare, 'interval')
key_interval = str(key_interval) key_interval = str(key_interval)
form.add_field(label='Interval', form.add_field(label='Interval',
ftype='text-single', ftype='text-single',
value=key_interval) value=key_interval)
key_length = self.settings[jid_bare]['length'] or self.settings['default']['length'] key_length = Config.get_setting_value(self.settings, jid_bare, 'length')
key_length = str(key_length) key_length = str(key_length)
form.add_field(label='Length', form.add_field(label='Length',
ftype='text-single', ftype='text-single',
value=key_length) value=key_length)
key_media = self.settings[jid_bare]['media'] or self.settings['default']['media'] key_media = Config.get_setting_value(self.settings, jid_bare, 'media')
key_media = str(key_media) key_media = str(key_media)
form.add_field(label='Media', form.add_field(label='Media',
ftype='text-single', ftype='text-single',
value=key_media) value=key_media)
key_old = self.settings[jid_bare]['old'] or self.settings['default']['old'] key_old = Config.get_setting_value(self.settings, jid_bare, 'old')
key_old = str(key_old) key_old = str(key_old)
form.add_field(label='Old', form.add_field(label='Old',
ftype='text-single', ftype='text-single',
value=key_old) value=key_old)
key_quantum = self.settings[jid_bare]['quantum'] or self.settings['default']['quantum'] key_quantum = Config.get_setting_value(self.settings, jid_bare, 'quantum')
key_quantum = str(key_quantum) key_quantum = str(key_quantum)
form.add_field(label='Quantum', form.add_field(label='Quantum',
ftype='text-single', ftype='text-single',
value=key_quantum) value=key_quantum)
update_interval = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] update_interval = Config.get_setting_value(self.settings, jid_bare, 'interval')
update_interval = str(update_interval) update_interval = str(update_interval)
update_interval = 60 * int(update_interval) update_interval = 60 * int(update_interval)
last_update_time = sqlite.get_last_update_time(db_file) last_update_time = sqlite.get_last_update_time(db_file)
@ -757,7 +800,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
else: else:
next_update = 'n/a' next_update = 'n/a'
else: else:
last_update_time = 'n/a' last_update = 'n/a'
next_update = 'n/a' next_update = 'n/a'
form.add_field(ftype='fixed', form.add_field(ftype='fixed',
value='Schedule') value='Schedule')
@ -1354,8 +1397,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
.format(function_name, jid_full)) .format(function_name, jid_full))
jid_bare = session['from'].bare jid_bare = session['from'].bare
chat_type = await get_chat_type(self, jid_bare) chat_type = await get_chat_type(self, jid_bare)
moderator = None
if chat_type == 'groupchat': if chat_type == 'groupchat':
moderator = is_moderator(self, jid_bare, jid_full) moderator = is_moderator(self, jid_bare, jid_full)
# moderator = moderator if moderator else None
if chat_type == 'chat' or moderator: if chat_type == 'chat' or moderator:
form = self['xep_0004'].make_form('form', 'Discover & Search') form = self['xep_0004'].make_form('form', 'Discover & Search')
form['instructions'] = 'Discover news subscriptions of all kinds' form['instructions'] = 'Discover news subscriptions of all kinds'
@ -1517,10 +1562,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
desc=('Select a subscription to edit.'), desc=('Select a subscription to edit.'),
required=True) required=True)
subscriptions = sqlite.get_feeds(db_file) subscriptions = sqlite.get_feeds(db_file)
subscriptions = sorted(subscriptions, key=lambda x: x[0]) subscriptions = sorted(subscriptions, key=lambda x: x[1])
for subscription in subscriptions: for subscription in subscriptions:
title = subscription[0] title = subscription[1]
url = subscription[1] url = subscription[2]
options.addOption(title, url) options.addOption(title, url)
session['has_next'] = True session['has_next'] = True
session['next'] = self._handle_subscription_editor session['next'] = self._handle_subscription_editor
@ -1536,10 +1581,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
desc=('Select subscriptions to remove.'), desc=('Select subscriptions to remove.'),
required=True) required=True)
subscriptions = sqlite.get_feeds(db_file) subscriptions = sqlite.get_feeds(db_file)
subscriptions = sorted(subscriptions, key=lambda x: x[0]) subscriptions = sorted(subscriptions, key=lambda x: x[1])
for subscription in subscriptions: for subscription in subscriptions:
title = subscription[0] title = subscription[1]
ix = str(subscription[2]) ix = str(subscription[0])
options.addOption(title, ix) options.addOption(title, ix)
session['cancel'] = self._handle_cancel session['cancel'] = self._handle_cancel
session['has_next'] = False session['has_next'] = False
@ -1653,8 +1698,8 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# subscriptions = set(subscriptions) # subscriptions = set(subscriptions)
categorized_subscriptions = {} categorized_subscriptions = {}
for subscription in subscriptions: for subscription in subscriptions:
title = subscription[0] title = subscription[1]
url = subscription[1] url = subscription[2]
try: try:
letter = title[0].capitalize() letter = title[0].capitalize()
if letter not in categorized_subscriptions: if letter not in categorized_subscriptions:
@ -1866,10 +1911,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
jid_file = jid_bare jid_file = jid_bare
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
subscriptions = sqlite.get_feeds(db_file) subscriptions = sqlite.get_feeds(db_file)
subscriptions = sorted(subscriptions, key=lambda x: x[0]) subscriptions = sorted(subscriptions, key=lambda x: x[1])
for subscription in subscriptions: for subscription in subscriptions:
title = subscription[0] title = subscription[1]
url = subscription[1] url = subscription[2]
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',
@ -1909,7 +1954,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
options.addOption('Import', 'import') options.addOption('Import', 'import')
options.addOption('Export', 'export') options.addOption('Export', 'export')
jid = session['from'].bare jid = session['from'].bare
if jid == self.settings['xmpp']['operator']: if is_operator(self, jid):
options.addOption('Administration', 'admin') options.addOption('Administration', 'admin')
session['payload'] = form session['payload'] = form
session['next'] = self._handle_advanced_result session['next'] = self._handle_advanced_result
@ -1936,7 +1981,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# NOTE Even though this check is already conducted on previous # NOTE Even though this check is already conducted on previous
# form, this check is being done just in case. # form, this check is being done just in case.
jid_bare = session['from'].bare jid_bare = session['from'].bare
if jid_bare == self.settings['xmpp']['operator']: if is_operator(self, jid_bare):
if self.is_component: if self.is_component:
# NOTE This will be changed with XEP-0222 XEP-0223 # NOTE This will be changed with XEP-0222 XEP-0223
text_info = ('Subscriber management options are ' text_info = ('Subscriber management options are '
@ -2036,9 +2081,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
options = form.add_field(var='option', options = form.add_field(var='option',
ftype='list-single', ftype='list-single',
label='About', label='About',
required=True) required=True,
value='about')
config_dir = config.get_default_config_directory() config_dir = config.get_default_config_directory()
with open(config_dir + '/' + 'information.toml', mode="rb") as information: with open(config_dir + '/' + 'about.toml', mode="rb") as information:
entries = tomllib.load(information) entries = tomllib.load(information)
for entry in entries: for entry in entries:
label = entries[entry][0]['title'] label = entries[entry][0]['title']
@ -2056,7 +2102,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
logger.debug('{}: jid_full: {}' logger.debug('{}: jid_full: {}'
.format(function_name, jid_full)) .format(function_name, jid_full))
config_dir = config.get_default_config_directory() config_dir = config.get_default_config_directory()
with open(config_dir + '/' + 'information.toml', mode="rb") as information: with open(config_dir + '/' + 'about.toml', mode="rb") as information:
entries = tomllib.load(information) entries = tomllib.load(information)
entry_key = payload['values']['option'] entry_key = payload['values']['option']
# case 'terms': # case 'terms':
@ -2086,7 +2132,6 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
e_key = e_key.capitalize() e_key = e_key.capitalize()
# form.add_field(ftype='fixed', # form.add_field(ftype='fixed',
# value=e_val) # value=e_val)
print(type(e_val))
if e_key == 'Name': if e_key == 'Name':
form.add_field(ftype='fixed', form.add_field(ftype='fixed',
value=e_val) value=e_val)
@ -2135,7 +2180,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
cmds = tomllib.load(commands) cmds = tomllib.load(commands)
form = self['xep_0004'].make_form('result', 'Manual') form = self['xep_0004'].make_form('result', 'Manual')
form['instructions'] = '🛟️ Help manual for interactive chat' form['instructions'] = 'Help manual for interactive chat'
# text = '🛟️ Help and Information about Slixfeed\n\n' # text = '🛟️ Help and Information about Slixfeed\n\n'
# for cmd in cmds: # for cmd in cmds:
@ -2311,7 +2356,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
jid_file = jid_bare jid_file = jid_bare
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
subscriptions = sqlite.get_feeds(db_file) subscriptions = sqlite.get_feeds(db_file)
subscriptions = sorted(subscriptions, key=lambda x: x[0]) subscriptions = sorted(subscriptions, key=lambda x: x[1])
form = self['xep_0004'].make_form('form', 'Subscriptions') form = self['xep_0004'].make_form('form', 'Subscriptions')
match payload['values']['action']: match payload['values']['action']:
case 'bookmarks': case 'bookmarks':
@ -2641,7 +2686,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
Config.add_settings_jid(self.settings, jid_bare, db_file) Config.add_settings_jid(self.settings, jid_bare, db_file)
form = self['xep_0004'].make_form('form', 'Settings') form = self['xep_0004'].make_form('form', 'Settings')
form['instructions'] = 'Editing settings' form['instructions'] = 'Editing settings'
value = self.settings[jid_bare]['enabled'] or self.settings['default']['enabled'] value = Config.get_setting_value(self.settings, jid_bare, 'enabled')
value = str(value) value = str(value)
value = int(value) value = int(value)
if value: if value:
@ -2653,7 +2698,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
label='Enabled', label='Enabled',
desc='Enable news updates.', desc='Enable news updates.',
value=value) value=value)
value = self.settings[jid_bare]['media'] or self.settings['default']['media'] value = Config.get_setting_value(self.settings, jid_bare, 'media')
value = str(value) value = str(value)
value = int(value) value = int(value)
if value: if value:
@ -2665,7 +2710,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
desc='Send audio, images or videos if found.', desc='Send audio, images or videos if found.',
label='Display media', label='Display media',
value=value) value=value)
value = self.settings[jid_bare]['old'] or self.settings['default']['old'] value = Config.get_setting_value(self.settings, jid_bare, 'old')
value = str(value) value = str(value)
value = int(value) value = int(value)
if value: if value:
@ -2678,7 +2723,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# label='Send only new items', # label='Send only new items',
label='Include old news', label='Include old news',
value=value) value=value)
value = self.settings[jid_bare]['interval'] or self.settings['default']['interval'] value = Config.get_setting_value(self.settings, jid_bare, 'interval')
value = str(value) value = str(value)
value = int(value) value = int(value)
value = value/60 value = value/60
@ -2699,7 +2744,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
i += 6 i += 6
else: else:
i += 1 i += 1
value = self.settings[jid_bare]['quantum'] or self.settings['default']['quantum'] value = Config.get_setting_value(self.settings, jid_bare, 'quantum')
value = str(value) value = str(value)
options = form.add_field(var='quantum', options = form.add_field(var='quantum',
ftype='list-single', ftype='list-single',
@ -2713,7 +2758,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
x = str(i) x = str(i)
options.addOption(x, x) options.addOption(x, x)
i += 1 i += 1
value = self.settings[jid_bare]['archive'] or self.settings['default']['archive'] value = Config.get_setting_value(self.settings, jid_bare, 'archive')
value = str(value) value = str(value)
options = form.add_field(var='archive', options = form.add_field(var='archive',
ftype='list-single', ftype='list-single',
@ -2768,7 +2813,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
if val < 1: val = 1 if val < 1: val = 1
val = val * 60 val = val * 60
is_enabled = self.settings[jid_bare]['enabled'] or self.settings['default']['enabled'] is_enabled = Config.get_setting_value(self.settings, jid_bare, 'enabled')
if (key == 'enabled' and if (key == 'enabled' and
val == 1 and val == 1 and

View file

@ -3,6 +3,13 @@
""" """
FIXME
Message from OpenFire server log.
2024.03.12 14:21:22.518 ERROR [nioEventLoopGroup-3-2]: org.jivesoftware.openfire.IQRouter - Unable to process a stanza that has no 'from' attribute, addressed to a remote entity. Stanza is being dropped: <iq id="8e4e60ae0d894b40a2fc465268d46d0b" type="get" to="rss.simon.goodbytes.im"><ping xmlns="urn:xmpp:ping"></ping></iq>
TODO TODO
1) Check interval, and if no connection is establish after 30 seconds 1) Check interval, and if no connection is establish after 30 seconds
@ -38,21 +45,23 @@ class XmppConnect:
None. None.
""" """
jid_from = str(self.boundjid) if self.is_component else None
if not jid: if not jid:
jid = self.boundjid.bare jid = self.boundjid.bare
while True: while True:
rtt = None rtt = None
try: try:
rtt = await self['xep_0199'].ping(jid, timeout=10) rtt = await self['xep_0199'].ping(jid,
ifrom=jid_from,
timeout=10)
logging.info('Success! RTT: %s', rtt) logging.info('Success! RTT: %s', rtt)
except IqError as e: except IqError as e:
logging.info('Error pinging %s: %s', logging.error('Error pinging %s: %s', jid,
jid, e.iq['error']['condition'])
e.iq['error']['condition'])
except IqTimeout: except IqTimeout:
logging.info('No response from %s', jid) logging.warning('No response from %s', jid)
if not rtt: if not rtt:
logging.info('Disconnecting...') logging.warning('Disconnecting...')
self.disconnect() self.disconnect()
break break
await asyncio.sleep(60 * 1) await asyncio.sleep(60 * 1)
@ -68,7 +77,7 @@ class XmppConnect:
# print(current_time(),"Maximum connection attempts exceeded.") # print(current_time(),"Maximum connection attempts exceeded.")
# logging.error("Maximum connection attempts exceeded.") # logging.error("Maximum connection attempts exceeded.")
print(current_time(), 'Attempt number', self.connection_attempts) print(current_time(), 'Attempt number', self.connection_attempts)
seconds = (get_value('accounts', 'XMPP', 'reconnect_timeout')) or 30 seconds = self.reconnect_timeout or 30
seconds = int(seconds) seconds = int(seconds)
print(current_time(), 'Next attempt within', seconds, 'seconds') print(current_time(), 'Next attempt within', seconds, 'seconds')
# NOTE asyncio.sleep doesn't interval as expected # NOTE asyncio.sleep doesn't interval as expected

14
slixfeed/xmpp/iq.py Normal file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from slixmpp.exceptions import IqError
class XmppIQ:
async def send(self, iq):
try:
await iq.send(timeout=5)
except IqError as e:
if e.etype == 'cancel' and e.condition == 'conflict':
return
raise

View file

@ -34,15 +34,17 @@ class XmppMessage:
def send(self, jid, message_body, chat_type): def send(self, jid, message_body, chat_type):
jid_from = str(self.boundjid) if self.is_component else None
self.send_message(mto=jid, self.send_message(mto=jid,
mfrom=self.boundjid.bare, mfrom=jid_from,
mbody=message_body, mbody=message_body,
mtype=chat_type) mtype=chat_type)
def send_headline(self, jid, message_subject, message_body, chat_type): def send_headline(self, jid, message_subject, message_body, chat_type):
jid_from = str(self.boundjid) if self.is_component else None
self.send_message(mto=jid, self.send_message(mto=jid,
mfrom=self.boundjid.bare, mfrom=jid_from,
# mtype='headline', # mtype='headline',
msubject=message_subject, msubject=message_subject,
mbody=message_body, mbody=message_body,
@ -58,13 +60,14 @@ class XmppMessage:
# } # }
# return saxutils.escape(raw_string, escape_map) # return saxutils.escape(raw_string, escape_map)
def send_oob(self, jid, url, chat_type): def send_oob(self, jid, url, chat_type):
jid_from = str(self.boundjid) if self.is_component else None
url = saxutils.escape(url) url = saxutils.escape(url)
# try: # try:
html = ( html = (
f'<body xmlns="http://www.w3.org/1999/xhtml">' f'<body xmlns="http://www.w3.org/1999/xhtml">'
f'<a href="{url}">{url}</a></body>') f'<a href="{url}">{url}</a></body>')
message = self.make_message(mto=jid, message = self.make_message(mto=jid,
mfrom=self.boundjid.bare, mfrom=jid_from,
mbody=url, mbody=url,
mhtml=html, mhtml=html,
mtype=chat_type) mtype=chat_type)

View file

@ -42,8 +42,10 @@ class XmppGroupchat:
conference["nick"] = self.alias conference["nick"] = self.alias
logging.error('Alias (i.e. Nicknname) is missing for ' logging.error('Alias (i.e. Nicknname) is missing for '
'bookmark {}'.format(conference['name'])) 'bookmark {}'.format(conference['name']))
# jid_from = str(self.boundjid) if self.is_component else None
self.plugin['xep_0045'].join_muc(conference["jid"], self.plugin['xep_0045'].join_muc(conference["jid"],
conference["nick"], conference["nick"],
# pfrom=jid_from,
# If a room password is needed, use: # If a room password is needed, use:
# password=the_room_password, # password=the_room_password,
) )
@ -83,8 +85,10 @@ class XmppGroupchat:
'JID : {}\n' 'JID : {}\n'
'Inviter : {}\n' 'Inviter : {}\n'
.format(jid, inviter)) .format(jid, inviter))
jid_from = str(self.boundjid) if self.is_component else None
self.plugin['xep_0045'].join_muc(jid, self.plugin['xep_0045'].join_muc(jid,
self.alias, self.alias,
pfrom=jid_from
# If a room password is needed, use: # If a room password is needed, use:
# password=the_room_password, # password=the_room_password,
) )

View file

@ -23,6 +23,9 @@ TODO
""" """
from slixfeed.xmpp.publish import XmppPubsub
from slixfeed.xmpp.iq import XmppIQ
import asyncio import asyncio
import logging import logging
import os import os
@ -40,9 +43,14 @@ from slixfeed.xmpp.muc import XmppGroupchat
from slixfeed.xmpp.message import XmppMessage from slixfeed.xmpp.message import XmppMessage
from slixfeed.xmpp.presence import XmppPresence from slixfeed.xmpp.presence import XmppPresence
from slixfeed.xmpp.upload import XmppUpload from slixfeed.xmpp.upload import XmppUpload
from slixfeed.xmpp.utility import get_chat_type, is_moderator from slixfeed.xmpp.utility import get_chat_type, is_moderator, is_operator
import time import time
try:
import tomllib
except:
import tomli as tomllib
# for task in main_task: # for task in main_task:
# task.cancel() # task.cancel()
@ -181,7 +189,7 @@ async def message(self, message):
response = None response = None
match message_lowercase: match message_lowercase:
# case 'breakpoint': # case 'breakpoint':
# if jid == get_value('accounts', 'XMPP', 'operator'): # if is_operator(self, jid_bare):
# breakpoint() # breakpoint()
# print('task_manager[jid]') # print('task_manager[jid]')
# print(task_manager[jid]) # print(task_manager[jid])
@ -244,21 +252,25 @@ async def message(self, message):
'or command key & name') 'or command key & name')
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case 'info': case 'info':
command_list = action.manual('information.toml') config_dir = config.get_default_config_directory()
with open(config_dir + '/' + 'information.toml', mode="rb") as information:
entries = tomllib.load(information)
response = ('Available command options:\n' response = ('Available command options:\n'
'```\n{}\n```\n' '```\n{}\n```\n'
'Usage: `info <option>`' 'Usage: `info <option>`'
.format(command_list)) .format(', '.join(entries)))
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('info'): case _ if message_lowercase.startswith('info'):
command = message_text[5:].lower() entry = message_text[5:].lower()
command_list = action.manual('information.toml', command) config_dir = config.get_default_config_directory()
if command_list: with open(config_dir + '/' + 'information.toml', mode="rb") as information:
entries = tomllib.load(information)
if entry in entries:
# command_list = '\n'.join(command_list) # command_list = '\n'.join(command_list)
response = (command_list) response = (entries[entry]['info'])
else: else:
response = ('KeyError for {}' response = ('KeyError for {}'
.format(command)) .format(entry))
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase in ['greetings', 'hallo', 'hello', case _ if message_lowercase in ['greetings', 'hallo', 'hello',
'hey', 'hi', 'hola', 'holla', 'hey', 'hi', 'hola', 'holla',
@ -293,7 +305,7 @@ async def message(self, message):
# response = 'Activation code is not valid.' # response = 'Activation code is not valid.'
# else: # else:
# response = 'This command is valid for groupchat only.' # response = 'This command is valid for groupchat only.'
case _ if message_lowercase.startswith('add'): case _ if message_lowercase.startswith('add '):
# Add given feed without validity check. # Add given feed without validity check.
message_text = message_text[4:] message_text = message_text[4:]
url = message_text.split(' ')[0] url = message_text.split(' ')[0]
@ -338,7 +350,7 @@ async def message(self, message):
'Missing URL.') 'Missing URL.')
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('allow +'): case _ if message_lowercase.startswith('allow +'):
key = message_text[:5] key = message_text[:5].lower()
val = message_text[7:] val = message_text[7:]
if val: if val:
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
@ -359,7 +371,7 @@ async def message(self, message):
'Missing keywords.') 'Missing keywords.')
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('allow -'): case _ if message_lowercase.startswith('allow -'):
key = message_text[:5] key = message_text[:5].lower()
val = message_text[7:] val = message_text[7:]
if val: if val:
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
@ -379,8 +391,8 @@ async def message(self, message):
'\n' '\n'
'Missing keywords.') 'Missing keywords.')
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('archive'): case _ if message_lowercase.startswith('archive '):
key = message_text[:7] key = message_text[:7].lower()
val = message_text[8:] val = message_text[8:]
if val: if val:
try: try:
@ -406,7 +418,7 @@ async def message(self, message):
'Missing value.') 'Missing value.')
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('bookmark +'): case _ if message_lowercase.startswith('bookmark +'):
if jid_bare == config.get_value('accounts', 'XMPP', 'operator'): if is_operator(self, jid_bare):
muc_jid = message_text[11:] muc_jid = message_text[11:]
await XmppBookmark.add(self, jid=muc_jid) await XmppBookmark.add(self, jid=muc_jid)
response = ('Groupchat {} has been added to bookmarks.' response = ('Groupchat {} has been added to bookmarks.'
@ -416,7 +428,7 @@ async def message(self, message):
'Type: adding bookmarks.') 'Type: adding bookmarks.')
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('bookmark -'): case _ if message_lowercase.startswith('bookmark -'):
if jid_bare == config.get_value('accounts', 'XMPP', 'operator'): if is_operator(self, jid_bare):
muc_jid = message_text[11:] muc_jid = message_text[11:]
await XmppBookmark.remove(self, muc_jid) await XmppBookmark.remove(self, muc_jid)
response = ('Groupchat {} has been removed from bookmarks.' response = ('Groupchat {} has been removed from bookmarks.'
@ -446,14 +458,14 @@ async def message(self, message):
response = 'Filter {} has been purged.'.format(key) response = 'Filter {} has been purged.'.format(key)
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case 'bookmarks': case 'bookmarks':
if jid_bare == config.get_value('accounts', 'XMPP', 'operator'): if is_operator(self, jid_bare):
response = await action.list_bookmarks(self) response = await action.list_bookmarks(self)
else: else:
response = ('This action is restricted. ' response = ('This action is restricted. '
'Type: viewing bookmarks.') 'Type: viewing bookmarks.')
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('deny +'): case _ if message_lowercase.startswith('deny +'):
key = message_text[:4] key = message_text[:4].lower()
val = message_text[6:] val = message_text[6:]
if val: if val:
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
@ -474,7 +486,7 @@ async def message(self, message):
'Missing keywords.') 'Missing keywords.')
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('deny -'): case _ if message_lowercase.startswith('deny -'):
key = message_text[:4] key = message_text[:4].lower()
val = message_text[6:] val = message_text[6:]
if val: if val:
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
@ -494,7 +506,7 @@ async def message(self, message):
'\n' '\n'
'Missing keywords.') '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 '):
ext = message_text[7:] ext = message_text[7:]
if ext in ('md', 'opml'): # html xbel if ext in ('md', 'opml'): # html xbel
status_type = 'dnd' status_type = 'dnd'
@ -687,7 +699,8 @@ async def message(self, message):
if query: if query:
if len(query) > 3: if len(query) > 3:
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
response = action.list_feeds_by_query(db_file, query) result = sqlite.search_feeds(db_file, query)
response = action.list_feeds_by_query(query, result)
else: else:
response = 'Enter at least 4 characters to search' response = 'Enter at least 4 characters to search'
else: else:
@ -702,8 +715,8 @@ async def message(self, message):
else: else:
response = 'This command is valid in groupchat only.' response = 'This command is valid in groupchat only.'
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('interval'): case _ if message_lowercase.startswith('interval '):
key = message_text[:8] key = message_text[:8].lower()
val = message_text[9:] val = message_text[9:]
if val: if val:
try: try:
@ -738,8 +751,8 @@ async def message(self, message):
'XMPP URI is not valid.' 'XMPP URI is not valid.'
.format(message_text)) .format(message_text))
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('length'): case _ if message_lowercase.startswith('length '):
key = message_text[:6] key = message_text[:6].lower()
val = message_text[7:] val = message_text[7:]
if val: if val:
try: try:
@ -832,7 +845,7 @@ async def message(self, message):
case 'options': case 'options':
response = 'Options:\n```' response = 'Options:\n```'
for key in self.settings[jid_bare]: for key in self.settings[jid_bare]:
val = self.settings[jid_bare][key] or self.settings['default'][key] val = Config.get_setting_value(self.settings, jid_bare, key)
# val = Config.get_setting_value(self.settings, jid_bare, key) # val = Config.get_setting_value(self.settings, jid_bare, key)
steps = 11 - len(key) steps = 11 - len(key)
pulse = '' pulse = ''
@ -842,8 +855,8 @@ async def message(self, message):
print(response) print(response)
response += '\n```' response += '\n```'
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('quantum'): case _ if message_lowercase.startswith('quantum '):
key = message_text[:7] key = message_text[:7].lower()
val = message_text[8:] val = message_text[8:]
if val: if val:
try: try:
@ -872,7 +885,7 @@ async def message(self, message):
# NOTE sqlitehandler.get_entry_unread # NOTE sqlitehandler.get_entry_unread
response = 'Updates will be sent by random order.' response = 'Updates will be sent by random order.'
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('read'): case _ if message_lowercase.startswith('read '):
data = message_text[5:] data = message_text[5:]
data = data.split() data = data.split()
url = data[0] url = data[0]
@ -914,7 +927,7 @@ async def message(self, message):
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
key_list = ['status'] key_list = ['status']
await task.start_tasks_xmpp(self, jid_bare, key_list) await task.start_tasks_xmpp(self, jid_bare, key_list)
case _ if message_lowercase.startswith('recent'): case _ if message_lowercase.startswith('recent '):
num = message_text[7:] num = message_text[7:]
if num: if num:
try: try:
@ -934,50 +947,53 @@ async def message(self, message):
'\n' '\n'
'Missing value.') 'Missing value.')
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('remove'): case _ if message_lowercase.startswith('remove '):
ix_url = message_text[7:] ix_url = message_text[7:]
ix_url = ix_url.split(' ')
if ix_url: if ix_url:
db_file = config.get_pathname_to_database(jid_file) for i in ix_url:
try: if i:
ix = int(ix_url) db_file = config.get_pathname_to_database(jid_file)
url = sqlite.get_feed_url(db_file, ix) try:
if url: ix = int(i)
url = url[0] url = sqlite.get_feed_url(db_file, ix)
name = sqlite.get_feed_title(db_file, ix) if url:
name = name[0] url = url[0]
await sqlite.remove_feed_by_index(db_file, ix) name = sqlite.get_feed_title(db_file, ix)
response = ('> {}\n' name = name[0]
'News source "{}" has been ' await sqlite.remove_feed_by_index(db_file, ix)
'removed from subscription list.' response = ('> {}\n'
.format(url, name)) 'News source "{}" has been '
else: 'removed from subscription list.'
response = ('No news source with index {}.' .format(url, name))
.format(ix)) else:
except: response = ('No news source with index {}.'
url = ix_url .format(ix))
feed_id = sqlite.get_feed_id(db_file, url) except:
if feed_id: url = i
feed_id = feed_id[0] feed_id = sqlite.get_feed_id(db_file, url)
await sqlite.remove_feed_by_url(db_file, url) if feed_id:
response = ('> {}\n' feed_id = feed_id[0]
'News source has been removed ' await sqlite.remove_feed_by_url(db_file, url)
'from subscription list.' response = ('> {}\n'
.format(url)) 'News source has been removed '
else: 'from subscription list.'
response = ('> {}\n' .format(url))
# 'No action has been made.' else:
'News source does not exist. ' response = ('> {}\n'
.format(url)) # 'No action has been made.'
# refresh_task(self, jid_bare, send_status, 'status', 20) 'News source does not exist. '
# task.clean_tasks_xmpp(self, jid_bare, ['status']) .format(url))
key_list = ['status'] # refresh_task(self, jid_bare, send_status, 'status', 20)
await task.start_tasks_xmpp(self, jid_bare, key_list) # task.clean_tasks_xmpp(self, jid_bare, ['status'])
key_list = ['status']
await task.start_tasks_xmpp(self, jid_bare, key_list)
XmppMessage.send_reply(self, message, response)
else: else:
response = ('No action has been taken.' response = ('No action has been taken.'
'\n' '\n'
'Missing argument. ' 'Missing argument. '
'Enter feed URL or index number.') 'Enter feed URL or index number.')
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('reset'): case _ if message_lowercase.startswith('reset'):
# TODO Reset also by ID # TODO Reset also by ID
ix_url = message_text[6:] ix_url = message_text[6:]
@ -1028,7 +1044,7 @@ async def message(self, message):
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
key_list = ['status'] key_list = ['status']
await task.start_tasks_xmpp(self, jid_bare, key_list) await task.start_tasks_xmpp(self, jid_bare, key_list)
case _ if message_lowercase.startswith('search'): case _ if message_lowercase.startswith('search '):
query = message_text[7:] query = message_text[7:]
if query: if query:
if len(query) > 1: if len(query) > 1:
@ -1080,7 +1096,7 @@ async def message(self, message):
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
key_list = ['status'] key_list = ['status']
await task.start_tasks_xmpp(self, jid_bare, key_list) await task.start_tasks_xmpp(self, jid_bare, key_list)
case _ if message_lowercase.startswith('rename'): case _ if message_lowercase.startswith('rename '):
message_text = message_text[7:] message_text = message_text[7:]
feed_id = message_text.split(' ')[0] feed_id = message_text.split(' ')[0]
name = ' '.join(message_text.split(' ')[1:]) name = ' '.join(message_text.split(' ')[1:])
@ -1117,7 +1133,7 @@ async def message(self, message):
'Missing argument. ' 'Missing argument. '
'Enter subscription Id and name.') 'Enter subscription Id and name.')
XmppMessage.send_reply(self, message, response) XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('enable'): case _ if message_lowercase.startswith('enable '):
feed_id = message_text[7:] feed_id = message_text[7:]
db_file = config.get_pathname_to_database(jid_file) db_file = config.get_pathname_to_database(jid_file)
try: try:

View file

@ -26,7 +26,8 @@ TODO
""" """
import glob import glob
from slixfeed.config import get_value, get_default_config_directory from slixfeed.config import Config
import slixfeed.config as config
# from slixmpp.exceptions import IqTimeout, IqError # from slixmpp.exceptions import IqTimeout, IqError
# import logging # import logging
import os import os
@ -42,7 +43,7 @@ async def update(self):
async def set_avatar(self): async def set_avatar(self):
config_dir = get_default_config_directory() config_dir = config.get_default_config_directory()
if not os.path.isdir(config_dir): if not os.path.isdir(config_dir):
config_dir = '/usr/share/slixfeed/' config_dir = '/usr/share/slixfeed/'
filename = glob.glob(config_dir + '/image.*') filename = glob.glob(config_dir + '/image.*')
@ -89,18 +90,8 @@ def set_identity(self, category):
async def set_vcard(self): async def set_vcard(self):
vcard = self.plugin['xep_0054'].make_vcard() vcard = self.plugin['xep_0054'].make_vcard()
fields = { profile = config.get_values('accounts.toml', 'xmpp')['profile']
'BDAY': 'birthday', for key in profile:
'DESC': 'description', vcard[key] = profile[key]
'FN': 'name',
'NICKNAME': 'nickname',
'NOTE': 'note',
'ORG': 'organization',
'ROLE': 'role',
'TITLE': 'title',
'URL': 'url',
}
for key in fields:
vcard[key] = get_value('accounts', 'XMPP Profile', fields[key])
await self.plugin['xep_0054'].publish_vcard(vcard) await self.plugin['xep_0054'].publish_vcard(vcard)

44
slixfeed/xmpp/publish.py Normal file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# TODO Implement XEP-0472: Pubsub Social Feed
class XmppPubsub:
def create(self, title, summary, node, server):
jid_from = str(self.boundjid) if self.is_component else None
iq = self.Iq(stype="set",
sto=server,
sfrom=jid_from)
iq['pubsub']['create']['node'] = node
form = iq['pubsub']['configure']['form']
form['type'] = 'submit'
form.addField('pubsub#title',
ftype='text-single',
value=title)
form.addField('pubsub#description',
ftype='text-single',
value=summary)
form.addField('pubsub#notify_retract',
ftype='boolean',
value=1)
form.addField('pubsub#max_items',
ftype='text-single',
value='20')
form.addField('pubsub#persist_items',
ftype='boolean',
value=1)
form.addField('pubsub#send_last_published_item',
ftype='text-single',
value='never')
form.addField('pubsub#deliver_payloads',
ftype='boolean',
value=0)
# TODO
form.addField('pubsub#type',
ftype='text-single',
value='http://www.w3.org/2005/Atom')
return iq

View file

@ -7,13 +7,24 @@ import logging
# class XmppChat # class XmppChat
# class XmppUtility: # class XmppUtility:
def is_operator(self, jid_bare):
result = False
for operator in self.operators:
if jid_bare == operator['jid']:
result = True
# operator_name = operator['name']
break
return result
def is_moderator(self, jid_bare, jid_full): def is_moderator(self, jid_bare, jid_full):
alias = jid_full[jid_full.index('/')+1:] alias = jid_full[jid_full.index('/')+1:]
role = self.plugin['xep_0045'].get_jid_property(jid_bare, alias, 'role') role = self.plugin['xep_0045'].get_jid_property(jid_bare, alias, 'role')
if role == 'moderator': if role == 'moderator':
return True result = True
else: else:
return False result = False
return result
# TODO Rename to get_jid_type # TODO Rename to get_jid_type