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 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:
raise Exception('Key type is missing from accounts.ini.')
match xmpp_type:
match account_mode:
case 'client':
from slixfeed.xmpp.client import Slixfeed
from slixfeed.config import ConfigClient as ConfigAccount
# from slixfeed.config import ConfigClient as ConfigAccount
case 'component':
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:
def __init__(self, jid, secret, hostname, port, alias=None):
@ -175,9 +178,8 @@ class JabberClient:
# xmpp.proxy = {'socks5': ('localhost', 9050)}
# Connect to the XMPP server and start processing XMPP stanzas.
if account.setting['hostname'] and account.setting['port']:
xmpp.connect((account.setting['hostname'], account.setting['port']))
if hostname and port:
xmpp.connect((hostname, port))
else:
xmpp.connect()
xmpp.process()
@ -200,7 +202,7 @@ def main():
# # socket.socket = socks.socksocket
# Setup the command line arguments.
match xmpp_type:
match account_mode:
case 'client':
parser = ArgumentParser(description=Slixfeed.__doc__)
case 'component':
@ -242,11 +244,11 @@ def main():
# logwrn = logger.warning
# Try configuration file
alias = account.setting['alias']
jid = account.setting['jid']
password = account.setting['password']
hostname = account.setting['hostname']
port = account.setting['port']
jid = account[account_mode]['jid']
password = account[account_mode]['password']
alias = account[account_mode]['alias'] if 'alias' in account[account_mode] else None
hostname = account[account_mode]['hostname'] if 'hostname' in account[account_mode] else None
port = account[account_mode]['port'] if 'port' in account[account_mode] else None
# Use arguments if were given
if args.jid:
@ -268,7 +270,7 @@ def main():
if not alias:
alias = (input('Alias: ')) or 'Slixfeed'
match xmpp_type:
match account_mode:
case 'client':
JabberClient(jid, password, hostname=hostname, port=port, alias=alias)
case 'component':

View file

@ -113,6 +113,27 @@ async def export_feeds(self, jid, jid_file, ext):
# response = 'Not yet implemented.'
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):
"""
Send status message.
@ -127,7 +148,7 @@ async def xmpp_send_status(self, jid):
status_text = '📜️ Slixfeed RSS News Bot'
jid_file = jid.replace('/', '_')
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:
status_mode = 'xa'
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))
jid_file = jid.replace('/', '_')
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:
show_media = self.settings[jid]['media'] or self.settings['default']['media']
show_media = Config.get_setting_value(self.settings, jid, 'media')
if not num:
num = self.settings[jid]['quantum'] or self.settings['default']['quantum']
num = Config.get_setting_value(self.settings, jid, 'quantum')
else:
num = int(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(' ', ' ')
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)
summary = summary[:length] + " […]"
# 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
# news_item = ("\n{}\n{}\n{} [{}]\n").format(str(title), str(link),
# 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,
title=title,
summary=summary,
link=link,
ix=ix)
news_item = news_item.replace('\\n', '\n')
# news_item = news_item.replace('\\n', '\n')
return news_item
@ -502,11 +523,9 @@ def list_search_results(query, results):
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
logger.debug('{}: db_file: {} query: {}'
.format(function_name, db_file, query))
results = sqlite.search_feeds(db_file, query)
logger.debug('{}'.format(function_name))
message = ('Feeds containing "{}":\n\n```'
.format(query))
for result in results:
@ -549,10 +568,10 @@ async def list_options(self, jid_bare):
# value = "Default"
# values.extend([value])
value_archive = self.settings[jid_bare]['archive'] or self.settings['default']['archive']
value_interval = self.settings[jid_bare]['interval'] or self.settings['default']['interval']
value_quantum = self.settings[jid_bare]['quantum'] or self.settings['default']['quantum']
value_enabled = self.settings[jid_bare]['archive'] or self.settings['default']['enabled']
value_archive = Config.get_setting_value(self.settings, jid_bare, 'archive')
value_interval = Config.get_setting_value(self.settings, jid_bare, 'interval')
value_quantum = Config.get_setting_value(self.settings, jid_bare, 'quantum')
value_enabled = Config.get_setting_value(self.settings, jid_bare, 'enabled')
message = ("Options:"
"\n"
@ -647,13 +666,10 @@ def list_feeds(results):
logger.debug('{}'.format(function_name))
message = "\nList of subscriptions:\n\n```\n"
for result in results:
message += ("Name : {}\n"
"URL : {}\n"
# "Updated : {}\n"
# "Status : {}\n"
"ID : {}\n"
"\n"
.format(str(result[0]), str(result[1]), str(result[2])))
message += ("{} [{}]\n"
"{}\n"
"\n\n"
.format(str(result[1]), str(result[0]), str(result[2])))
if len(results):
message += ('```\nTotal of {} subscriptions.\n'
.format(len(results)))
@ -793,7 +809,7 @@ async def add_feed(self, jid_bare, db_file, url):
status_code=status_code,
updated=updated)
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 = feed_id[0]
if not old:
@ -804,9 +820,6 @@ async def add_feed(self, jid_bare, db_file, url):
'code' : status_code,
'error' : False,
'exist' : False}
response = ('> {}\nNews source "{}" has been '
'added to subscription list.'
.format(url, title))
break
# NOTE This elif statement be unnecessary
# when feedparser be supporting json feed.
@ -843,7 +856,7 @@ async def add_feed(self, jid_bare, db_file, url):
status_code=status_code,
updated=updated)
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:
feed_id = sqlite.get_feed_id(db_file, url)
feed_id = feed_id[0]
@ -854,9 +867,6 @@ async def add_feed(self, jid_bare, db_file, url):
'code' : status_code,
'error' : False,
'exist' : False}
response = ('> {}\nNews source "{}" has been '
'added to subscription list.'
.format(url, title))
break
else:
# 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,
'error' : True,
'exist' : False}
response = ('> {}\nFailed to load URL. Reason: {}'
.format(url, status_code))
break
else:
ix = exist[0]
@ -898,9 +906,6 @@ async def add_feed(self, jid_bare, db_file, url):
'code' : None,
'error' : False,
'exist' : True}
response = ('> {}\nNews source "{}" is already '
'listed in the subscription list at '
'index {}'.format(url, name, ix))
break
return result_final
@ -1254,7 +1259,7 @@ async def scan(self, jid_bare, db_file, url):
return
# new_entry = 0
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"):
date = entry.published
date = dt.rfc2822_to_iso8601(date)
@ -1481,6 +1486,7 @@ async def extract_image_from_html(url):
'contains(@src, "emoji") or '
'contains(@src, "icon") or '
'contains(@src, "logo") or '
'contains(@src, "letture") or '
'contains(@src, "search") or '
'contains(@src, "share") or '
'contains(@src, "smiley")'
@ -1614,7 +1620,7 @@ async def remove_nonexistent_entries(self, jid_bare, db_file, url, feed):
feed_id = feed_id[0]
items = sqlite.get_entries_of_feed(db_file, feed_id)
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:
ix = item[0]
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]
items = sqlite.get_entries_of_feed(db_file, feed_id)
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:
ix = item[0]
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]]
title = "About"
subtitle = "Slixfeed news bot"
[[about]]
info = ["""
[about]
info = """
Slixfeed is a news broker bot for syndicated news which aims to be \
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.
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 \
computer, server, and even from a Linux phone (i.e. Droidian, Kupfer, \
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.
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]]
name = "err-rssreader"
info = ["A port of old Brutal's RSS Reader for Errbot"]
url = "https://github.com/errbotters/err-rssreader"
[authors]
info = """
Schimon Zackary
Laura Lapina
"""
[[friends]]
name = "feed-to-muc"
info = ["""
An XMPP bot which posts to a MUC (groupchat) if there is an update in newsfeeds.
"""]
url = "https://salsa.debian.org/mdosch/feed-to-muc"
[contributors]
info = """
Guus der Kinderen
grym (from #python IRC channel)
Stephen Paul Weber
"""
[[friends]]
name = "JabRSS (fork)"
info = ["""
Never miss a headline again! JabRSS is a simple RSS (RDF Site Summary) \
headline notification service for Jabber.
[bots]
info = """
Syndication bots made by our counterparts.
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 \
show plugins).
"""]
url = "http://www.jotwewe.de/de/xmpp/jabrss/jabrss_en.htm"
feed-to-muc
https://salsa.debian.org/mdosch/feed-to-muc
[[friends]]
name = "JabRSS"
info = ["""
A simple RSS (RDF Site Summary) headline notification service for Jabber/XMPP.
JabRSS
http://www.jotwewe.de/de/xmpp/jabrss/jabrss_en.htm
A public instance of the bot is available via xmpp:jabrss@cmeerw.net
"""]
url = "https://dev.cmeerw.org/Projects/jabrss"
JabRSS
https://dev.cmeerw.org/Projects/jabrss
[[friends]]
name = "Morbot"
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"
err-rssreader"
https://github.com/errbotters/err-rssreader
[[legal]]
title = "Legal"
subtitle = "Legal Notice"
XMPP Bot
https://github.com/nioc/xmpp-bot
"""
[[legal]]
info = ["""
[legal]
info = """
Slixfeed is free software; you can redistribute it and/or modify it under the \
terms of the MIT License.
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 \
A PARTICULAR PURPOSE. See the MIT License for more details.
"""]
link = "https://gitgud.io/sjehuda/slixfeed"
[[license]]
title = "License"
subtitle = "MIT License"
https://gitgud.io/sjehuda/slixfeed
"""
[[license]]
license = ["""
Copyright 2022 - 2024 Schimon Zackary Jehudah
[license]
info = """
Copyright 2022 - 2024 Schimon Jehudah Zackary
Permission is hereby granted, free of charge, to any person obtaining a copy \
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 \
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS \
IN THE SOFTWARE.
"""]
owner = "Schimon Zackary"
"""
[[support]]
title = "Support"
subtitle = "Slixfeed Support Groupchat"
[support]
info = """
Slixfeed Support Groupchat
xmpp:slixfeed@chat.woodpeckersnest.space?join
"""
[[support]]
jid = "xmpp:slixfeed@chat.woodpeckersnest.space?join"
[[thanks]]
title = "Thanks"
subtitle = """
[thanks]
info = """
From SalixOS to Gajim. A journey of 15 years. \
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]]
name = "Alixander Court"
country = "Utah"
url = "https://alixandercourt.com"
[operators]
info = """
No operator was specified for this instance.
"""
[[thanks]]
name = "Arne-Brün Vogelsang"
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 = ["""
[terms]
info = """
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]]
name = "Cheogram"
info = "XMPP client for mobile"
url = "https://cheogram.com"
[privacy]
info = """
All your data belongs to us.
"""
# [[clients]]
# name = "Conversations"
# info = "XMPP client for mobile"
# url = "https://conversations.im"
[clients]
info = """
Recommended Clients:
[[clients]]
name = "Converse"
info = "XMPP client for desktop and mobile"
url = "https://conversejs.org"
Cheogram
https://cheogram.com
# [[clients]]
# name = "Gajim"
# info = "XMPP client for desktop"
# url = "https://gajim.org"
Converse
https://conversejs.org
# [[clients]]
# name = "Monal IM"
# info = "XMPP client for desktop and mobile"
# url = "https://monal-im.org"
Gajim
https://gajim.org
[[clients]]
name = "monocles chat"
info = "XMPP client for mobile"
url = "https://monocles.chat"
monocles chat
https://monocles.chat
[[clients]]
name = "Movim"
info = "XMPP client for desktop and mobile"
url = "https://mov.im"
Movim
https://mov.im
# [[clients]]
# name = "Moxxy"
# info = "XMPP client for mobile"
# url = "https://moxxy.org"
Poezio
https://poez.io
"""
[[clients]]
name = "Psi"
info = "XMPP client for desktop"
url = "https://psi-im.org"
[services]
info = """
Recommended Syndication Services
[[clients]]
name = "Psi+"
info = "XMPP client for desktop"
url = "https://psi-plus.com"
Feed Creator
https://www.fivefilters.org/feed-creator/"
# [[clients]]
# name = "Swift"
# info = "XMPP client for desktop"
# url = "https://swift.im"
Kill the Newsletter
https://kill-the-newsletter.com
# [[clients]]
# name = "yaxim"
# info = "XMPP client for mobile"
# url = "https://yaxim.org"
Open RSS
https://openrss.org
[[services]]
title = "Recommended News Services"
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.
"""]
RSS-Bridge
https://rss-bridge.org/bridge01/
[[services]]
name = "Feed Creator"
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/"
RSSHub
https://docs.rsshub.app
"""
[[services]]
name = "Kill the Newsletter"
info = "Kill the Newsletter converts email newsletters into Web feeds."
link = "https://kill-the-newsletter.com"
[software]
info = """
Recommended News Software
[[services]]
name = "Open RSS"
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"
CommaFeed
https://commafeed.com
[[services]]
name = "RSS-Bridge"
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/"
FreshRSS
https://freshrss.org
[[services]]
name = "RSSHub"
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"
Liferea
https://lzone.de/liferea/
[[software]]
title = "Recommended News Software"
subtitle = ["""
Take back control of your news. With free, quality, software for your \
desktop, home and mobile devices.
"""]
NetNewsWire
https://netnewswire.com
[[software]]
name = "CommaFeed"
info = ["""
A self-hosted RSS reader, based on Dropwizard and React/TypeScript.
"""]
link = "https://commafeed.com"
os = "Any (HTML)"
Newsboat
https://newsboat.org
[[software]]
name = "FreshRSS"
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)"
Spot-On
https://textbrowser.github.io/spot-on/
[[software]]
name = "Liferea"
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"
Vienna RSS
https://vienna-rss.com
"""
[[software]]
name = "NetNewsWire"
info = ["""
NetNewsWire shows you articles from your favorite blogs and news sites and \
keeps track of what youve read.
[resources]
info = """
Useful Resources:
This means you can stop going from page to page in your browser looking for \
new articles to read. Do it the easy way instead: let NetNewsWire bring you \
the news.
feedparser
https://pythonhosted.org/feedparser
And, if youve been getting your news via the commercial Social Networks \
with their ads, algorithms, user tracking, outrage, and misinformation you \
can switch to NetNewsWire to get news directly and more reliably from the \
sites you trust.
"""]
link = "https://netnewswire.com"
os = "MacOS"
Slixmpp
https://slixmpp.readthedocs.io
[[software]]
name = "Newsboat"
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"
XMPP
https://xmpp.org/about
"""
[[software]]
name = "Spot-On"
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 = ["""
[rss-task-force]
info = """
The RSS Task Force (previously known as The Syndication Society) is an \
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.
The RSS Task Force was founded by two taxicab drivers in 2018.
"""]
"""
[[sleekxmpp]]
title = "About Project SleekXMPP"
subtitle = "SleekXMPP XMPP Library"
[[sleekxmpp]]
info = ["""
[sleekxmpp]
info = """
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, \
Remko Tronçon, and Peter Saint-Andre.
"""]
url = "https://codeberg.org/fritzy/SleekXMPP"
[[slixmpp]]
title = "About Project Slixmpp"
subtitle = "Slixmpp XMPP Library"
https://codeberg.org/fritzy/SleekXMPP
"""
[[slixmpp]]
info = ["""
[slixmpp]
info = """
Slixmpp is an MIT licensed XMPP library for Python 3.7+. It is a fork of \
SleekXMPP.
Slixmpp's goals is to only rewrite the core of the SleekXMPP library \
(the low level socket handling, the timers, the events dispatching) \
in order to remove all threads.
"""]
url = "https://codeberg.org/poezio/slixmpp"
[[xmpp]]
title = "About XMPP"
subtitle = "Previously known as Jabber"
https://codeberg.org/poezio/slixmpp
"""
[[xmpp]]
info = ["""
[xmpp]
info = """
XMPP is the Extensible Messaging and Presence Protocol, a set of open \
technologies for instant messaging, presence, multi-party chat, voice and \
video calls, collaboration, lightweight middleware, content syndication, and \
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:
def add_settings_default(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
settings_default = get_values('settings.toml', 'settings')
settings['default'] = settings_default
# TODO Open SQLite file once
def add_settings_jid(settings, jid_bare, db_file):
settings[jid_bare] = {}
for key in ('archive', 'enabled', 'filter', 'formatting', 'interval',
'length', 'media', 'old', 'quantum'):
value = sqlite.get_setting_value(db_file, key)
if value: value = 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
if value: settings[jid_bare][key] = value[0]
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):
key = key.lower()
@ -81,73 +76,12 @@ class Config:
await sqlite.set_setting_value(db_file, key_val)
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]
else:
value = settings['default'][key]
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:
def __init__(self, settings):
settings['network'] = {}
@ -167,6 +101,19 @@ class ConfigJabberID:
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):
value = sqlite.get_setting_value(db_file, key)
if value:
@ -299,9 +246,7 @@ def get_value(filename, section, keys):
for key in keys:
if key in section_res:
value = section_res[key]
logging.debug(
"Found value {} for key {}".format(value, key)
)
logging.debug("Found value {} for key {}".format(value, key))
else:
value = ''
logging.debug("Missing key:", key)
@ -310,9 +255,7 @@ def get_value(filename, section, keys):
key = keys
if key in section_res:
result = section_res[key]
logging.debug(
"Found value {} for key {}".format(result, key)
)
logging.debug("Found value {} for key {}".format(result, key))
else:
result = ''
# logging.error("Missing key:", key)

View file

@ -119,10 +119,11 @@ async def http(url):
msg: list or str
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')
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)
async with ClientSession(headers=headers) as session:
# async with ClientSession(trust_env=True) as session:

View file

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

View file

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

View file

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

View file

@ -55,7 +55,7 @@ import slixfeed.xmpp.profile as profile
from slixfeed.xmpp.roster import XmppRoster
# import slixfeed.xmpp.service as service
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 time
@ -70,6 +70,7 @@ import logging
import os
import slixfeed.action as action
import slixfeed.config as config
from slixfeed.config import Config
import slixfeed.crawl as crawl
import slixfeed.dt as dt
import slixfeed.fetch as fetch
@ -123,14 +124,18 @@ class Slixfeed(slixmpp.ClientXMPP):
self.task_ping_instance = {}
# Handler for configuration
self.settings = {}
# Populate handler
Config.add_settings_default(self.settings)
Config.add_settings_xmpp(self.settings)
self.settings = config.get_values('settings.toml')
# Handler for operators
self.operators = config.get_values('accounts.toml', 'xmpp')['operators']
# self.settings = {}
# # Populate dict handler
# Config.add_settings_default(self.settings)
# Handlers for connection events
self.connection_attempts = 0
self.max_connection_attempts = 10
self.reconnect_timeout = config.get_values('accounts.toml', 'xmpp')['settings']['reconnect_timeout']
self.add_event_handler("session_start",
self.on_session_start)
@ -261,16 +266,14 @@ class Slixfeed(slixmpp.ClientXMPP):
function_name = sys._getframe().f_code.co_name
message_log = '{}'
logger.debug(message_log.format(function_name))
jid_operator = config.get_value('accounts', 'XMPP', 'operator')
if jid_operator:
status_message = ('Wait while Slixfeed {} is being loaded...'
.format(__version__))
XmppPresence.send(self, jid_operator, status_message)
status_message = 'Slixfeed version {}'.format(__version__)
for operator in self.operators:
XmppPresence.send(self, operator['jid'], status_message)
await profile.update(self)
profile.set_identity(self, 'client')
await self['xep_0115'].update_caps()
# self.send_presence()
# await self.get_roster()
await self.get_roster()
bookmarks = await self.plugin['xep_0048'].get_bookmarks()
XmppGroupchat.autojoin(self, bookmarks)
# XmppCommand.adhoc_commands(self)
@ -335,16 +338,16 @@ class Slixfeed(slixmpp.ClientXMPP):
XmppPresence.send(self, jid_bare, status_message)
else:
# 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')
# 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')
await process.message(self, message)
# chat_type = message["type"]
# message_body = message["body"]
@ -671,13 +674,16 @@ class Slixfeed(slixmpp.ClientXMPP):
# )
# 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',
name='📰️ Browse',
handler=self._handle_recent)
self['xep_0050'].add_command(node='subscription',
name='🪶️ Subscribe',
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',
name='🎫️ Subscriptions',
handler=self._handle_subscriptions)
@ -712,6 +718,42 @@ class Slixfeed(slixmpp.ClientXMPP):
# Special interface
# 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):
jid_full = str(session['from'])
function_name = sys._getframe().f_code.co_name
@ -747,42 +789,42 @@ class Slixfeed(slixmpp.ClientXMPP):
value=unread)
form.add_field(ftype='fixed',
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)
form.add_field(label='Archive',
ftype='text-single',
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)
form.add_field(label='Enabled',
ftype='text-single',
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)
form.add_field(label='Interval',
ftype='text-single',
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)
form.add_field(label='Length',
ftype='text-single',
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)
form.add_field(label='Media',
ftype='text-single',
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)
form.add_field(label='Old',
ftype='text-single',
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)
form.add_field(label='Quantum',
ftype='text-single',
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 = 60 * int(update_interval)
last_update_time = sqlite.get_last_update_time(db_file)
@ -797,7 +839,7 @@ class Slixfeed(slixmpp.ClientXMPP):
else:
next_update = 'n/a'
else:
last_update_time = 'n/a'
last_update = 'n/a'
next_update = 'n/a'
form.add_field(ftype='fixed',
value='Schedule')
@ -1394,8 +1436,10 @@ class Slixfeed(slixmpp.ClientXMPP):
.format(function_name, jid_full))
jid_bare = session['from'].bare
chat_type = await get_chat_type(self, jid_bare)
moderator = None
if chat_type == 'groupchat':
moderator = is_moderator(self, jid_bare, jid_full)
# moderator = moderator if moderator else None
if chat_type == 'chat' or moderator:
form = self['xep_0004'].make_form('form', 'Discover & Search')
form['instructions'] = 'Discover news subscriptions of all kinds'
@ -1557,10 +1601,10 @@ class Slixfeed(slixmpp.ClientXMPP):
desc=('Select a subscription to edit.'),
required=True)
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:
title = subscription[0]
url = subscription[1]
title = subscription[1]
url = subscription[2]
options.addOption(title, url)
session['has_next'] = True
session['next'] = self._handle_subscription_editor
@ -1576,10 +1620,10 @@ class Slixfeed(slixmpp.ClientXMPP):
desc=('Select subscriptions to remove.'),
required=True)
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:
title = subscription[0]
ix = str(subscription[2])
title = subscription[1]
ix = str(subscription[0])
options.addOption(title, ix)
session['cancel'] = self._handle_cancel
session['has_next'] = False
@ -1693,8 +1737,8 @@ class Slixfeed(slixmpp.ClientXMPP):
# subscriptions = set(subscriptions)
categorized_subscriptions = {}
for subscription in subscriptions:
title = subscription[0]
url = subscription[1]
title = subscription[1]
url = subscription[2]
try:
letter = title[0].capitalize()
if letter not in categorized_subscriptions:
@ -1906,10 +1950,10 @@ class Slixfeed(slixmpp.ClientXMPP):
jid_file = jid_bare
db_file = config.get_pathname_to_database(jid_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:
title = subscription[0]
url = subscription[1]
title = subscription[1]
url = subscription[2]
options.addOption(title, url)
# options = form.add_field(var='action',
# ftype='list-single',
@ -1949,7 +1993,7 @@ class Slixfeed(slixmpp.ClientXMPP):
options.addOption('Import', 'import')
options.addOption('Export', 'export')
jid = session['from'].bare
if jid == self.settings['xmpp']['operator']:
if is_operator(self, jid):
options.addOption('Administration', 'admin')
session['payload'] = form
session['next'] = self._handle_advanced_result
@ -1976,7 +2020,7 @@ class Slixfeed(slixmpp.ClientXMPP):
# NOTE Even though this check is already conducted on previous
# form, this check is being done just in case.
jid_bare = session['from'].bare
if jid_bare == self.settings['xmpp']['operator']:
if is_operator(self, jid_bare):
if self.is_component:
# NOTE This will be changed with XEP-0222 XEP-0223
text_info = ('Subscriber management options are '
@ -2076,9 +2120,10 @@ class Slixfeed(slixmpp.ClientXMPP):
options = form.add_field(var='option',
ftype='list-single',
label='About',
required=True)
required=True,
value='about')
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)
for entry in entries:
label = entries[entry][0]['title']
@ -2096,7 +2141,7 @@ class Slixfeed(slixmpp.ClientXMPP):
logger.debug('{}: jid_full: {}'
.format(function_name, jid_full))
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)
entry_key = payload['values']['option']
# case 'terms':
@ -2174,7 +2219,7 @@ class Slixfeed(slixmpp.ClientXMPP):
cmds = tomllib.load(commands)
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'
# for cmd in cmds:
@ -2350,7 +2395,7 @@ class Slixfeed(slixmpp.ClientXMPP):
jid_file = jid_bare
db_file = config.get_pathname_to_database(jid_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')
match payload['values']['action']:
case 'bookmarks':
@ -2680,7 +2725,7 @@ class Slixfeed(slixmpp.ClientXMPP):
Config.add_settings_jid(self.settings, jid_bare, db_file)
form = self['xep_0004'].make_form('form', '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 = int(value)
if value:
@ -2692,7 +2737,7 @@ class Slixfeed(slixmpp.ClientXMPP):
label='Enabled',
desc='Enable news updates.',
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 = int(value)
if value:
@ -2704,7 +2749,7 @@ class Slixfeed(slixmpp.ClientXMPP):
desc='Send audio, images or videos if found.',
label='Display media',
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 = int(value)
if value:
@ -2717,7 +2762,7 @@ class Slixfeed(slixmpp.ClientXMPP):
# label='Send only new items',
label='Include old news',
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 = int(value)
value = value/60
@ -2738,7 +2783,7 @@ class Slixfeed(slixmpp.ClientXMPP):
i += 6
else:
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)
options = form.add_field(var='quantum',
ftype='list-single',
@ -2752,7 +2797,7 @@ class Slixfeed(slixmpp.ClientXMPP):
x = str(i)
options.addOption(x, x)
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)
options = form.add_field(var='archive',
ftype='list-single',
@ -2807,7 +2852,7 @@ class Slixfeed(slixmpp.ClientXMPP):
if val < 1: val = 1
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
val == 1 and

View file

@ -36,7 +36,7 @@ from slixfeed.log import Logger
from slixfeed.version import __version__
from slixfeed.xmpp.connect import XmppConnect
# NOTE MUC is possible for component
# from slixfeed.xmpp.muc import XmppGroupchat
from slixfeed.xmpp.muc import XmppGroupchat
from slixfeed.xmpp.message import XmppMessage
import slixfeed.xmpp.process as process
import slixfeed.xmpp.profile as profile
@ -46,7 +46,7 @@ from slixfeed.xmpp.presence import XmppPresence
# from slixmpp.xmlstream import ET
# from slixmpp.xmlstream.handler import Callback
# 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 time
@ -115,14 +115,18 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
self.task_ping_instance = {}
# Handler for configuration
self.settings = {}
# Populate handler
Config.add_settings_default(self.settings)
Config.add_settings_xmpp(self.settings)
self.settings = config.get_values('settings.toml')
# Handler for operators
self.operators = config.get_values('accounts.toml', 'xmpp')['operators']
# self.settings = {}
# # Populate dict handler
# Config.add_settings_default(self.settings)
# Handlers for connection events
self.connection_attempts = 0
self.max_connection_attempts = 10
self.reconnect_timeout = config.get_values('accounts.toml', 'xmpp')['settings']['reconnect_timeout']
self.add_event_handler("session_start",
self.on_session_start)
@ -155,10 +159,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
self.add_event_handler("message",
self.on_message)
# self.add_event_handler("groupchat_invite",
# self.on_groupchat_invite) # XEP_0045
# self.add_event_handler("groupchat_direct_invite",
# self.on_groupchat_direct_invite) # XEP_0249
self.add_event_handler("groupchat_invite",
self.on_groupchat_invite) # XEP_0045
self.add_event_handler("groupchat_direct_invite",
self.on_groupchat_direct_invite) # XEP_0249
# self.add_event_handler("groupchat_message", self.message)
# self.add_event_handler("disconnected", self.reconnect)
@ -184,19 +188,19 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
self.on_session_end)
# async def on_groupchat_invite(self, message):
async def on_groupchat_invite(self, message):
# logging.warning("on_groupchat_invite")
# inviter = message['from'].bare
# muc_jid = message['groupchat_invite']['jid']
# await muc.join(self, inviter, muc_jid)
inviter = message['from'].bare
muc_jid = message['groupchat_invite']['jid']
XmppGroupchat.join(self, inviter, muc_jid)
# await bookmark.add(self, muc_jid)
# NOTE Tested with Gajim and Psi
# async def on_groupchat_direct_invite(self, message):
# inviter = message['from'].bare
# muc_jid = message['groupchat_invite']['jid']
# await muc.join(self, inviter, muc_jid)
async def on_groupchat_direct_invite(self, message):
inviter = message['from'].bare
muc_jid = message['groupchat_invite']['jid']
XmppGroupchat.join(self, inviter, muc_jid)
# await bookmark.add(self, muc_jid)
@ -234,10 +238,9 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
task.task_ping(self)
# bookmarks = await self.plugin['xep_0048'].get_bookmarks()
# XmppGroupchat.autojoin(self, bookmarks)
jid_operator = config.get_value('accounts', 'XMPP', 'operator')
if jid_operator:
status_message = 'Slixfeed version {}'.format(__version__)
XmppPresence.send(self, jid_operator, status_message)
for operator in self.operators:
XmppPresence.send(self, operator['jid'], status_message)
time_end = time.time()
difference = time_end - time_begin
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']:
# XmppPresence.subscription(self, jid, 'subscribe')
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'
XmppPresence.send(self, jid_bare, status_message)
message_subject = 'RSS News Bot'
@ -418,9 +421,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# status_message = None
XmppMessage.send(self, jid_bare, message_body, 'chat')
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')
XmppRoster.remove(self, jid_bare)
# XmppRoster.remove(self, jid_bare)
time_end = time.time()
difference = time_end - time_begin
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
# if jid == self.settings['xmpp']['operator']:
# if is_operator(self, jid_bare):
self['xep_0050'].add_command(node='recent',
name='📰️ Browse',
handler=self._handle_recent)
self['xep_0050'].add_command(node='subscription',
name='🪶️ Subscribe',
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',
name='🎫️ Subscriptions',
handler=self._handle_subscriptions)
@ -672,6 +679,42 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# Special interface
# 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):
jid_full = str(session['from'])
function_name = sys._getframe().f_code.co_name
@ -707,42 +750,42 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
value=unread)
form.add_field(ftype='fixed',
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)
form.add_field(label='Archive',
ftype='text-single',
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)
form.add_field(label='Enabled',
ftype='text-single',
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)
form.add_field(label='Interval',
ftype='text-single',
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)
form.add_field(label='Length',
ftype='text-single',
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)
form.add_field(label='Media',
ftype='text-single',
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)
form.add_field(label='Old',
ftype='text-single',
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)
form.add_field(label='Quantum',
ftype='text-single',
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 = 60 * int(update_interval)
last_update_time = sqlite.get_last_update_time(db_file)
@ -757,7 +800,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
else:
next_update = 'n/a'
else:
last_update_time = 'n/a'
last_update = 'n/a'
next_update = 'n/a'
form.add_field(ftype='fixed',
value='Schedule')
@ -1354,8 +1397,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
.format(function_name, jid_full))
jid_bare = session['from'].bare
chat_type = await get_chat_type(self, jid_bare)
moderator = None
if chat_type == 'groupchat':
moderator = is_moderator(self, jid_bare, jid_full)
# moderator = moderator if moderator else None
if chat_type == 'chat' or moderator:
form = self['xep_0004'].make_form('form', 'Discover & Search')
form['instructions'] = 'Discover news subscriptions of all kinds'
@ -1517,10 +1562,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
desc=('Select a subscription to edit.'),
required=True)
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:
title = subscription[0]
url = subscription[1]
title = subscription[1]
url = subscription[2]
options.addOption(title, url)
session['has_next'] = True
session['next'] = self._handle_subscription_editor
@ -1536,10 +1581,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
desc=('Select subscriptions to remove.'),
required=True)
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:
title = subscription[0]
ix = str(subscription[2])
title = subscription[1]
ix = str(subscription[0])
options.addOption(title, ix)
session['cancel'] = self._handle_cancel
session['has_next'] = False
@ -1653,8 +1698,8 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# subscriptions = set(subscriptions)
categorized_subscriptions = {}
for subscription in subscriptions:
title = subscription[0]
url = subscription[1]
title = subscription[1]
url = subscription[2]
try:
letter = title[0].capitalize()
if letter not in categorized_subscriptions:
@ -1866,10 +1911,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
jid_file = jid_bare
db_file = config.get_pathname_to_database(jid_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:
title = subscription[0]
url = subscription[1]
title = subscription[1]
url = subscription[2]
options.addOption(title, url)
# options = form.add_field(var='action',
# ftype='list-single',
@ -1909,7 +1954,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
options.addOption('Import', 'import')
options.addOption('Export', 'export')
jid = session['from'].bare
if jid == self.settings['xmpp']['operator']:
if is_operator(self, jid):
options.addOption('Administration', 'admin')
session['payload'] = form
session['next'] = self._handle_advanced_result
@ -1936,7 +1981,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# NOTE Even though this check is already conducted on previous
# form, this check is being done just in case.
jid_bare = session['from'].bare
if jid_bare == self.settings['xmpp']['operator']:
if is_operator(self, jid_bare):
if self.is_component:
# NOTE This will be changed with XEP-0222 XEP-0223
text_info = ('Subscriber management options are '
@ -2036,9 +2081,10 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
options = form.add_field(var='option',
ftype='list-single',
label='About',
required=True)
required=True,
value='about')
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)
for entry in entries:
label = entries[entry][0]['title']
@ -2056,7 +2102,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
logger.debug('{}: jid_full: {}'
.format(function_name, jid_full))
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)
entry_key = payload['values']['option']
# case 'terms':
@ -2086,7 +2132,6 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
e_key = e_key.capitalize()
# form.add_field(ftype='fixed',
# value=e_val)
print(type(e_val))
if e_key == 'Name':
form.add_field(ftype='fixed',
value=e_val)
@ -2135,7 +2180,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
cmds = tomllib.load(commands)
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'
# for cmd in cmds:
@ -2311,7 +2356,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
jid_file = jid_bare
db_file = config.get_pathname_to_database(jid_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')
match payload['values']['action']:
case 'bookmarks':
@ -2641,7 +2686,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
Config.add_settings_jid(self.settings, jid_bare, db_file)
form = self['xep_0004'].make_form('form', '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 = int(value)
if value:
@ -2653,7 +2698,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
label='Enabled',
desc='Enable news updates.',
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 = int(value)
if value:
@ -2665,7 +2710,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
desc='Send audio, images or videos if found.',
label='Display media',
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 = int(value)
if value:
@ -2678,7 +2723,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
# label='Send only new items',
label='Include old news',
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 = int(value)
value = value/60
@ -2699,7 +2744,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
i += 6
else:
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)
options = form.add_field(var='quantum',
ftype='list-single',
@ -2713,7 +2758,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
x = str(i)
options.addOption(x, x)
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)
options = form.add_field(var='archive',
ftype='list-single',
@ -2768,7 +2813,7 @@ class SlixfeedComponent(slixmpp.ComponentXMPP):
if val < 1: val = 1
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
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
1) Check interval, and if no connection is establish after 30 seconds
@ -38,21 +45,23 @@ class XmppConnect:
None.
"""
jid_from = str(self.boundjid) if self.is_component else None
if not jid:
jid = self.boundjid.bare
while True:
rtt = None
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)
except IqError as e:
logging.info('Error pinging %s: %s',
jid,
logging.error('Error pinging %s: %s', jid,
e.iq['error']['condition'])
except IqTimeout:
logging.info('No response from %s', jid)
logging.warning('No response from %s', jid)
if not rtt:
logging.info('Disconnecting...')
logging.warning('Disconnecting...')
self.disconnect()
break
await asyncio.sleep(60 * 1)
@ -68,7 +77,7 @@ class XmppConnect:
# print(current_time(),"Maximum connection attempts exceeded.")
# logging.error("Maximum connection attempts exceeded.")
print(current_time(), 'Attempt number', self.connection_attempts)
seconds = (get_value('accounts', 'XMPP', 'reconnect_timeout')) or 30
seconds = self.reconnect_timeout or 30
seconds = int(seconds)
print(current_time(), 'Next attempt within', seconds, 'seconds')
# 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):
jid_from = str(self.boundjid) if self.is_component else None
self.send_message(mto=jid,
mfrom=self.boundjid.bare,
mfrom=jid_from,
mbody=message_body,
mtype=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,
mfrom=self.boundjid.bare,
mfrom=jid_from,
# mtype='headline',
msubject=message_subject,
mbody=message_body,
@ -58,13 +60,14 @@ class XmppMessage:
# }
# return saxutils.escape(raw_string, escape_map)
def send_oob(self, jid, url, chat_type):
jid_from = str(self.boundjid) if self.is_component else None
url = saxutils.escape(url)
# try:
html = (
f'<body xmlns="http://www.w3.org/1999/xhtml">'
f'<a href="{url}">{url}</a></body>')
message = self.make_message(mto=jid,
mfrom=self.boundjid.bare,
mfrom=jid_from,
mbody=url,
mhtml=html,
mtype=chat_type)

View file

@ -42,8 +42,10 @@ class XmppGroupchat:
conference["nick"] = self.alias
logging.error('Alias (i.e. Nicknname) is missing for '
'bookmark {}'.format(conference['name']))
# jid_from = str(self.boundjid) if self.is_component else None
self.plugin['xep_0045'].join_muc(conference["jid"],
conference["nick"],
# pfrom=jid_from,
# If a room password is needed, use:
# password=the_room_password,
)
@ -83,8 +85,10 @@ class XmppGroupchat:
'JID : {}\n'
'Inviter : {}\n'
.format(jid, inviter))
jid_from = str(self.boundjid) if self.is_component else None
self.plugin['xep_0045'].join_muc(jid,
self.alias,
pfrom=jid_from
# If a room password is needed, use:
# 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 logging
import os
@ -40,9 +43,14 @@ from slixfeed.xmpp.muc import XmppGroupchat
from slixfeed.xmpp.message import XmppMessage
from slixfeed.xmpp.presence import XmppPresence
from slixfeed.xmpp.upload import XmppUpload
from slixfeed.xmpp.utility import get_chat_type, is_moderator
from slixfeed.xmpp.utility import get_chat_type, is_moderator, is_operator
import time
try:
import tomllib
except:
import tomli as tomllib
# for task in main_task:
# task.cancel()
@ -181,7 +189,7 @@ async def message(self, message):
response = None
match message_lowercase:
# case 'breakpoint':
# if jid == get_value('accounts', 'XMPP', 'operator'):
# if is_operator(self, jid_bare):
# breakpoint()
# print('task_manager[jid]')
# print(task_manager[jid])
@ -244,21 +252,25 @@ async def message(self, message):
'or command key & name')
XmppMessage.send_reply(self, message, response)
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'
'```\n{}\n```\n'
'Usage: `info <option>`'
.format(command_list))
.format(', '.join(entries)))
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('info'):
command = message_text[5:].lower()
command_list = action.manual('information.toml', command)
if command_list:
entry = message_text[5:].lower()
config_dir = config.get_default_config_directory()
with open(config_dir + '/' + 'information.toml', mode="rb") as information:
entries = tomllib.load(information)
if entry in entries:
# command_list = '\n'.join(command_list)
response = (command_list)
response = (entries[entry]['info'])
else:
response = ('KeyError for {}'
.format(command))
.format(entry))
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase in ['greetings', 'hallo', 'hello',
'hey', 'hi', 'hola', 'holla',
@ -293,7 +305,7 @@ async def message(self, message):
# response = 'Activation code is not valid.'
# else:
# 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.
message_text = message_text[4:]
url = message_text.split(' ')[0]
@ -338,7 +350,7 @@ async def message(self, message):
'Missing URL.')
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('allow +'):
key = message_text[:5]
key = message_text[:5].lower()
val = message_text[7:]
if val:
db_file = config.get_pathname_to_database(jid_file)
@ -359,7 +371,7 @@ async def message(self, message):
'Missing keywords.')
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('allow -'):
key = message_text[:5]
key = message_text[:5].lower()
val = message_text[7:]
if val:
db_file = config.get_pathname_to_database(jid_file)
@ -379,8 +391,8 @@ async def message(self, message):
'\n'
'Missing keywords.')
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('archive'):
key = message_text[:7]
case _ if message_lowercase.startswith('archive '):
key = message_text[:7].lower()
val = message_text[8:]
if val:
try:
@ -406,7 +418,7 @@ async def message(self, message):
'Missing value.')
XmppMessage.send_reply(self, message, response)
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:]
await XmppBookmark.add(self, jid=muc_jid)
response = ('Groupchat {} has been added to bookmarks.'
@ -416,7 +428,7 @@ async def message(self, message):
'Type: adding bookmarks.')
XmppMessage.send_reply(self, message, response)
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:]
await XmppBookmark.remove(self, muc_jid)
response = ('Groupchat {} has been removed from bookmarks.'
@ -446,14 +458,14 @@ async def message(self, message):
response = 'Filter {} has been purged.'.format(key)
XmppMessage.send_reply(self, message, response)
case 'bookmarks':
if jid_bare == config.get_value('accounts', 'XMPP', 'operator'):
if is_operator(self, jid_bare):
response = await action.list_bookmarks(self)
else:
response = ('This action is restricted. '
'Type: viewing bookmarks.')
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('deny +'):
key = message_text[:4]
key = message_text[:4].lower()
val = message_text[6:]
if val:
db_file = config.get_pathname_to_database(jid_file)
@ -474,7 +486,7 @@ async def message(self, message):
'Missing keywords.')
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('deny -'):
key = message_text[:4]
key = message_text[:4].lower()
val = message_text[6:]
if val:
db_file = config.get_pathname_to_database(jid_file)
@ -494,7 +506,7 @@ async def message(self, message):
'\n'
'Missing keywords.')
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('export'):
case _ if message_lowercase.startswith('export '):
ext = message_text[7:]
if ext in ('md', 'opml'): # html xbel
status_type = 'dnd'
@ -687,7 +699,8 @@ async def message(self, message):
if query:
if len(query) > 3:
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:
response = 'Enter at least 4 characters to search'
else:
@ -702,8 +715,8 @@ async def message(self, message):
else:
response = 'This command is valid in groupchat only.'
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('interval'):
key = message_text[:8]
case _ if message_lowercase.startswith('interval '):
key = message_text[:8].lower()
val = message_text[9:]
if val:
try:
@ -738,8 +751,8 @@ async def message(self, message):
'XMPP URI is not valid.'
.format(message_text))
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('length'):
key = message_text[:6]
case _ if message_lowercase.startswith('length '):
key = message_text[:6].lower()
val = message_text[7:]
if val:
try:
@ -832,7 +845,7 @@ async def message(self, message):
case 'options':
response = 'Options:\n```'
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)
steps = 11 - len(key)
pulse = ''
@ -842,8 +855,8 @@ async def message(self, message):
print(response)
response += '\n```'
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('quantum'):
key = message_text[:7]
case _ if message_lowercase.startswith('quantum '):
key = message_text[:7].lower()
val = message_text[8:]
if val:
try:
@ -872,7 +885,7 @@ async def message(self, message):
# NOTE sqlitehandler.get_entry_unread
response = 'Updates will be sent by random order.'
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('read'):
case _ if message_lowercase.startswith('read '):
data = message_text[5:]
data = data.split()
url = data[0]
@ -914,7 +927,7 @@ async def message(self, message):
XmppMessage.send_reply(self, message, response)
key_list = ['status']
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:]
if num:
try:
@ -934,12 +947,15 @@ async def message(self, message):
'\n'
'Missing value.')
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 = ix_url.split(' ')
if ix_url:
for i in ix_url:
if i:
db_file = config.get_pathname_to_database(jid_file)
try:
ix = int(ix_url)
ix = int(i)
url = sqlite.get_feed_url(db_file, ix)
if url:
url = url[0]
@ -954,7 +970,7 @@ async def message(self, message):
response = ('No news source with index {}.'
.format(ix))
except:
url = ix_url
url = i
feed_id = sqlite.get_feed_id(db_file, url)
if feed_id:
feed_id = feed_id[0]
@ -972,12 +988,12 @@ async def message(self, message):
# 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:
response = ('No action has been taken.'
'\n'
'Missing argument. '
'Enter feed URL or index number.')
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('reset'):
# TODO Reset also by ID
ix_url = message_text[6:]
@ -1028,7 +1044,7 @@ async def message(self, message):
XmppMessage.send_reply(self, message, response)
key_list = ['status']
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:]
if query:
if len(query) > 1:
@ -1080,7 +1096,7 @@ async def message(self, message):
XmppMessage.send_reply(self, message, response)
key_list = ['status']
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:]
feed_id = message_text.split(' ')[0]
name = ' '.join(message_text.split(' ')[1:])
@ -1117,7 +1133,7 @@ async def message(self, message):
'Missing argument. '
'Enter subscription Id and name.')
XmppMessage.send_reply(self, message, response)
case _ if message_lowercase.startswith('enable'):
case _ if message_lowercase.startswith('enable '):
feed_id = message_text[7:]
db_file = config.get_pathname_to_database(jid_file)
try:

View file

@ -26,7 +26,8 @@ TODO
"""
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
# import logging
import os
@ -42,7 +43,7 @@ async def update(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):
config_dir = '/usr/share/slixfeed/'
filename = glob.glob(config_dir + '/image.*')
@ -89,18 +90,8 @@ def set_identity(self, category):
async def set_vcard(self):
vcard = self.plugin['xep_0054'].make_vcard()
fields = {
'BDAY': 'birthday',
'DESC': 'description',
'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])
profile = config.get_values('accounts.toml', 'xmpp')['profile']
for key in profile:
vcard[key] = profile[key]
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 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):
alias = jid_full[jid_full.index('/')+1:]
role = self.plugin['xep_0045'].get_jid_property(jid_bare, alias, 'role')
if role == 'moderator':
return True
result = True
else:
return False
result = False
return result
# TODO Rename to get_jid_type