diff --git a/slixfeed/__main__.py b/slixfeed/__main__.py
index 9590dbd..6802792 100644
--- a/slixfeed/__main__.py
+++ b/slixfeed/__main__.py
@@ -113,6 +113,7 @@ if __name__ == '__main__':
xmpp.register_plugin('xep_0004') # Data Forms
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0045') # Multi-User Chat
+ xmpp.register_plugin('xep_0048') # Bookmarks
xmpp.register_plugin('xep_0060') # PubSub
xmpp.register_plugin('xep_0199') # XMPP Ping
xmpp.register_plugin('xep_0249') # Multi-User Chat
diff --git a/slixfeed/confighandler.py b/slixfeed/confighandler.py
index 8e1e954..6727277 100644
--- a/slixfeed/confighandler.py
+++ b/slixfeed/confighandler.py
@@ -12,6 +12,7 @@ TODO
import os
import filehandler
+from random import randrange
async def get_value_default(key):
@@ -42,8 +43,10 @@ async def get_value_default(key):
result = 3
case "random":
result = 0
- case "read":
- result = "https://www.blacklistednews.com/rss.php"
+ case "masters":
+ result = randrange(100000, 999999)
+ case "token":
+ result = "none"
return result
diff --git a/slixfeed/datahandler.py b/slixfeed/datahandler.py
index 234cdd7..b6b98b2 100644
--- a/slixfeed/datahandler.py
+++ b/slixfeed/datahandler.py
@@ -21,7 +21,7 @@ import feedparser
import sqlitehandler
import confighandler
import datetimehandler
-import filterhandler
+import listhandler
from asyncio.exceptions import IncompleteReadError
from http.client import IncompleteRead
@@ -168,15 +168,15 @@ async def download_updates(db_file, url=None):
summary,
pathname
)
- allow_list = await filterhandler.is_listed(
+ allow_list = await listhandler.is_listed(
db_file,
- "allow",
+ "filter-allow",
string
)
if not allow_list:
- reject_list = await filterhandler.is_listed(
+ reject_list = await listhandler.is_listed(
db_file,
- "deny",
+ "filter-deny",
string
)
if reject_list:
@@ -208,7 +208,7 @@ async def download_updates(db_file, url=None):
# NOTE Why (if result[0]) and (if result[1] == 200)?
-async def view_feed(db_file, url):
+async def view_feed(url):
"""
Check feeds for new entries.
@@ -235,8 +235,7 @@ async def view_feed(db_file, url):
# "For more information, visit "
# "https://pythonhosted.org/feedparser/bozo.html"
# ).format(url)
- # msg = await probe_page(view_feed, url, result[0])
- msg = await probe_page(view_feed, url, result[0], db_file)
+ msg = await probe_page(view_feed, url, result[0])
return msg
except (
IncompleteReadError,
@@ -253,10 +252,7 @@ async def view_feed(db_file, url):
if result[1] == 200:
title = await get_title(url, result[0])
entries = feed.entries
- msg = "Extracted {} entries from {}:\n```\n".format(
- len(entries),
- title
- )
+ msg = "Preview of {}:\n```\n".format(title)
count = 0
for entry in entries:
count += 1
@@ -290,13 +286,11 @@ async def view_feed(db_file, url):
link,
count
)
+ if count > 4:
+ break
msg += (
- "```\n"
- "Source: {}\n"
- "Enter a number from 1 - {} using command `select` "
- "to view a specific item from the list."
- ).format(url, count)
- await sqlitehandler.set_settings_value(db_file, ["read", url])
+ "```\nSource: {}"
+ ).format(url)
else:
msg = (
">{}\nFailed to load URL. Reason: {}"
@@ -304,14 +298,38 @@ async def view_feed(db_file, url):
return msg
-async def view_entry(db_file, num):
- num = int(num) - 1
- url = await sqlitehandler.get_settings_value(db_file, "read")
+# NOTE Why (if result[0]) and (if result[1] == 200)?
+async def view_entry(url, num):
result = await download_feed(url)
+ if result[0]:
+ try:
+ feed = feedparser.parse(result[0])
+ if feed.bozo:
+ # msg = (
+ # ">{}\n"
+ # "WARNING: Bozo detected!\n"
+ # "For more information, visit "
+ # "https://pythonhosted.org/feedparser/bozo.html"
+ # ).format(url)
+ msg = await probe_page(view_entry, url, result[0], num)
+ return msg
+ except (
+ IncompleteReadError,
+ IncompleteRead,
+ error.URLError
+ ) as e:
+ # print(e)
+ # TODO Print error to log
+ msg = (
+ "> {}\n"
+ "Error: {}"
+ ).format(url, e)
+ breakpoint()
if result[1] == 200:
feed = feedparser.parse(result[0])
title = await get_title(url, result[0])
entries = feed.entries
+ num = int(num) - 1
entry = entries[num]
if entry.has_key("title"):
title = entry.title
@@ -328,9 +346,9 @@ async def view_entry(db_file, num):
if entry.has_key("summary"):
summary = entry.summary
# Remove HTML tags
- # summary = BeautifulSoup(summary, "lxml").text
+ summary = BeautifulSoup(summary, "lxml").text
# TODO Limit text length
- # summary = summary.replace("\n\n", "\n")
+ summary = summary.replace("\n\n\n", "\n\n")
else:
summary = "*** No summary ***"
if entry.has_key("link"):
@@ -346,11 +364,8 @@ async def view_entry(db_file, num):
"\n"
"{}\n"
"\n"
- "{}\n"
- "\n"
).format(
title,
- date,
summary,
link
)
@@ -453,7 +468,7 @@ async def add_feed(db_file, url):
# TODO callback for use with add_feed and view_feed
-async def probe_page(callback, url, doc, db_file=None):
+async def probe_page(callback, url, doc, num=None, db_file=None):
msg = None
try:
# tree = etree.fromstring(res[0]) # etree is for xml
@@ -483,6 +498,8 @@ async def probe_page(callback, url, doc, db_file=None):
url = msg[0]
if db_file:
return await callback(db_file, url)
+ elif num:
+ return await callback(url, num)
else:
return await callback(url)
diff --git a/slixfeed/filterhandler.py b/slixfeed/listhandler.py
similarity index 94%
rename from slixfeed/filterhandler.py
rename to slixfeed/listhandler.py
index ad68762..e5063c8 100644
--- a/slixfeed/filterhandler.py
+++ b/slixfeed/listhandler.py
@@ -17,9 +17,10 @@ TODO
import sqlitehandler
-async def set_filter(newwords, keywords):
+
+async def set_list(newwords, keywords):
"""
- Append new keywords to filter.
+ Append new keywords to list.
Parameters
----------
@@ -46,7 +47,8 @@ async def set_filter(newwords, keywords):
val = ",".join(keywords)
return val
-async def is_listed(db_file, type, string):
+
+async def is_listed(db_file, key, string):
"""
Check keyword match.
@@ -66,10 +68,9 @@ async def is_listed(db_file, type, string):
"""
# async def reject(db_file, string):
# async def is_blacklisted(db_file, string):
- filter_type = "filter-" + type
list = await sqlitehandler.get_settings_value(
db_file,
- filter_type
+ key
)
if list:
list = list.split(",")
diff --git a/slixfeed/sqlitehandler.py b/slixfeed/sqlitehandler.py
index fe54a2e..b766eda 100644
--- a/slixfeed/sqlitehandler.py
+++ b/slixfeed/sqlitehandler.py
@@ -1059,7 +1059,7 @@ async def list_feeds(db_file):
"FROM feeds"
)
results = cur.execute(sql)
- feeds_list = "\nList of subscriptions:\n```"
+ feeds_list = "\nList of subscriptions:\n```\n"
counter = 0
for result in results:
counter += 1
@@ -1329,7 +1329,7 @@ async def set_settings_value(db_file, key_value):
key_value : list
key : str
enabled, filter-allow, filter-deny,
- interval, master, quantum, random.
+ interval, masters, quantum, random.
value : int
Numeric value.
"""
diff --git a/slixfeed/taskhandler.py b/slixfeed/taskhandler.py
index 0527b4b..4adda25 100644
--- a/slixfeed/taskhandler.py
+++ b/slixfeed/taskhandler.py
@@ -198,11 +198,15 @@ async def send_update(self, jid, num=None):
if new:
# TODO Add while loop to assure delivery.
# print(await datetimehandler.current_time(), ">>> ACT send_message",jid)
+ if await xmpphandler.Slixfeed.is_muc(self, jid):
+ chat_type = "groupchat"
+ else:
+ chat_type = "chat"
xmpphandler.Slixfeed.send_message(
self,
mto=jid,
mbody=new,
- mtype="chat"
+ mtype=chat_type
)
# TODO Do not refresh task before
# verifying that it was completed.
diff --git a/slixfeed/xmpphandler.py b/slixfeed/xmpphandler.py
index 2e34fa2..3e70915 100644
--- a/slixfeed/xmpphandler.py
+++ b/slixfeed/xmpphandler.py
@@ -23,6 +23,15 @@ TODO
4) Do not send updates when busy or away.
See https://slixmpp.readthedocs.io/en/latest/event_index.html#term-changed_status
+5) XHTTML-IM
+ case _ if message_lowercase.startswith("html"):
+ message['html']="
Parse me!
"
+ self.send_message(
+ mto=jid,
+ mfrom=self.boundjid.bare,
+ mhtml=message
+ )
+
NOTE
1) Self presence
@@ -51,7 +60,7 @@ from slixmpp.plugins.xep_0363.http_upload import FileTooBig, HTTPError, UploadSe
import datahandler
import datetimehandler
import filehandler
-import filterhandler
+import listhandler
import sqlitehandler
import taskhandler
@@ -105,8 +114,8 @@ class Slixfeed(slixmpp.ClientXMPP):
self.add_event_handler("message", self.message)
self.add_event_handler("message", self.settle)
- self.add_event_handler("groupchat_invite", self.accept_muc_invite)
- self.add_event_handler("groupchat_direct_invite", self.accept_muc_invite)
+ self.add_event_handler("groupchat_invite", self.process_muc_invite) # XEP_0045
+ self.add_event_handler("groupchat_direct_invite", self.process_muc_invite) # XEP_0249
# self.add_event_handler("groupchat_message", self.message)
# self.add_event_handler("disconnected", self.reconnect)
@@ -190,27 +199,58 @@ class Slixfeed(slixmpp.ClientXMPP):
print("reactions")
print(message)
- async def accept_muc_invite(self, message):
- ctr = message["from"].bare
- jid = message['groupchat_invite']['jid']
- tkn = randrange(10000, 99999)
+ # async def accept_muc_invite(self, message, ctr=None):
+ # # if isinstance(message, str):
+ # if not ctr:
+ # ctr = message["from"].bare
+ # jid = message['groupchat_invite']['jid']
+ # else:
+ # jid = message
+ async def process_muc_invite(self, message):
+ # operator muc_chat
+ inviter = message["from"].bare
+ muc_jid = message['groupchat_invite']['jid']
+ await self.join_muc(inviter, muc_jid)
+
+
+ async def join_muc(self, inviter, muc_jid):
+ token = await filehandler.initdb(
+ muc_jid,
+ sqlitehandler.get_settings_value,
+ "token"
+ )
+ if token != "accepted":
+ token = randrange(10000, 99999)
+ await filehandler.initdb(
+ muc_jid,
+ sqlitehandler.set_settings_value,
+ ["token", token]
+ )
+ self.send_message(
+ mto=inviter,
+ mbody=(
+ "Send activation token {} to groupchat xmpp:{}?join."
+ ).format(token, muc_jid)
+ )
self.plugin['xep_0045'].join_muc(
- jid,
+ muc_jid,
"Slixfeed (RSS News Bot)",
# If a room password is needed, use:
# password=the_room_password,
)
- self.send_message(
- mto=ctr,
- mbody=(
- "Send activation token {} to groupchat xmpp:{}?join."
- ).format(tkn, jid)
- )
# self.add_event_handler(
# "muc::[room]::message",
# self.message
# )
+ # await self.get_bookmarks()
+ # bookmark = self.plugin['xep_0048'].instantiate_pep()
+ # print(bookmark)
+ # nick = "Slixfeed (RSS News Bot)"
+ # bookmark.add_bookmark(muc_jid, nick=nick)
+ # await self['xep_0048'].set_bookmarks(bookmark)
+ # print(bookmark)
+
async def on_session_end(self, event):
print(await datetimehandler.current_time(), "Session ended. Attempting to reconnect.")
@@ -341,6 +381,31 @@ class Slixfeed(slixmpp.ClientXMPP):
# await taskhandler.select_file()
+ async def is_muc(self, jid):
+ """
+ Check whether a JID is of MUC.
+
+ Parameters
+ ----------
+ jid : str
+ Jabber ID.
+
+ Returns
+ -------
+ boolean
+ True or False.
+ """
+ iqresult = await self["xep_0030"].get_info(jid=jid)
+ features = iqresult["disco_info"]["features"]
+ # identity = iqresult['disco_info']['identities']
+ # if 'account' in indentity:
+ # if 'conference' in indentity:
+ if 'http://jabber.org/protocol/muc' in features:
+ return True
+ else:
+ return False
+
+
async def settle(self, msg):
"""
Add JID to roster and settle subscription.
@@ -355,42 +420,46 @@ class Slixfeed(slixmpp.ClientXMPP):
None.
"""
jid = msg["from"].bare
- await self.get_roster()
- # Check whether JID is in roster; otherwise, add it.
- if jid not in self.client_roster.keys():
- self.send_presence_subscription(
- pto=jid,
- ptype="subscribe",
- pnick="Slixfeed RSS News Bot"
- )
- self.update_roster(
- jid,
- subscription="both"
- )
- # Check whether JID is subscribed; otherwise, ask for presence.
- if not self.client_roster[jid]["to"]:
- self.send_presence_subscription(
- pto=jid,
- pfrom=self.boundjid.bare,
- ptype="subscribe",
- pnick="Slixfeed RSS News Bot"
- )
- self.send_message(
- mto=jid,
- mtype="headline",
- msubject="RSS News Bot",
- mbody=("Accept subscription request to receive updates."),
- mfrom=self.boundjid.bare,
- mnick="Slixfeed RSS News Bot"
- )
- self.send_presence(
- pto=jid,
- pfrom=self.boundjid.bare,
- # Accept symbol 🉑️ 👍️ ✍
- pstatus="✒️ Accept subscription request to receive updates",
- # ptype="subscribe",
- pnick="Slixfeed RSS News Bot"
- )
+ if await self.is_muc(jid):
+ # Check whether JID is in bookmarks; otherwise, add it.
+ print(jid, "is muc")
+ else:
+ await self.get_roster()
+ # Check whether JID is in roster; otherwise, add it.
+ if jid not in self.client_roster.keys():
+ self.send_presence_subscription(
+ pto=jid,
+ ptype="subscribe",
+ pnick="Slixfeed RSS News Bot"
+ )
+ self.update_roster(
+ jid,
+ subscription="both"
+ )
+ # Check whether JID is subscribed; otherwise, ask for presence.
+ if not self.client_roster[jid]["to"]:
+ self.send_presence_subscription(
+ pto=jid,
+ pfrom=self.boundjid.bare,
+ ptype="subscribe",
+ pnick="Slixfeed RSS News Bot"
+ )
+ self.send_message(
+ mto=jid,
+ # mtype="headline",
+ msubject="RSS News Bot",
+ mbody="Accept subscription request to receive updates.",
+ mfrom=self.boundjid.bare,
+ mnick="Slixfeed RSS News Bot"
+ )
+ self.send_presence(
+ pto=jid,
+ pfrom=self.boundjid.bare,
+ # Accept symbol 🉑️ 👍️ ✍
+ pstatus="✒️ Accept subscription request to receive updates",
+ # ptype="subscribe",
+ pnick="Slixfeed RSS News Bot"
+ )
async def presence_unsubscribe(self, presence):
@@ -436,27 +505,36 @@ class Slixfeed(slixmpp.ClientXMPP):
action = 0
jid = msg["from"].bare
if msg["type"] == "groupchat":
- ctr = await filehandler.initdb(
+ # nick = msg["from"][msg["from"].index("/")+1:]
+ nick = str(msg["from"])
+ nick = nick[nick.index("/")+1:]
+ if (msg['muc']['nick'] == "Slixfeed (RSS News Bot)" or
+ not msg["body"].startswith("!")):
+ return
+ token = await filehandler.initdb(
jid,
sqlitehandler.get_settings_value,
- "masters"
+ "token"
)
- if (msg["from"][msg["from"].index("/")+1:] not in ctr
- or not msg["body"].startswith("!")):
- return
-
+ if token == "accepted":
+ operator = await filehandler.initdb(
+ jid,
+ sqlitehandler.get_settings_value,
+ "masters"
+ )
+ if operator:
+ if nick not in operator:
+ return
+
# # Begin processing new JID
# # Deprecated in favour of event "presence_available"
# db_dir = filehandler.get_default_dbdir()
# os.chdir(db_dir)
# if jid + ".db" not in os.listdir():
# await taskhandler.task_jid(jid)
- print(msg["body"])
- print(msg["body"].split())
message = " ".join(msg["body"].split())
if msg["type"] == "groupchat":
message = message[1:]
- print(message)
message_lowercase = message.lower()
print(await datetimehandler.current_time(), "ACCOUNT: " + str(msg["from"]))
@@ -482,6 +560,33 @@ class Slixfeed(slixmpp.ClientXMPP):
print(self.client_roster)
print("roster 2")
print(self.client_roster.keys())
+ print("jid")
+ print(jid)
+
+ case _ if message_lowercase.startswith("activate"):
+ if msg["type"] == "groupchat":
+ acode = message[9:]
+ token = await filehandler.initdb(
+ jid,
+ sqlitehandler.get_settings_value,
+ "token"
+ )
+ if int(acode) == token:
+ await filehandler.initdb(
+ jid,
+ sqlitehandler.set_settings_value,
+ ["masters", nick]
+ )
+ await filehandler.initdb(
+ jid,
+ sqlitehandler.set_settings_value,
+ ["token", "accepted"]
+ )
+ action = "{}, your are in command.".format(nick)
+ else:
+ action = "Activation code is not valid."
+ else:
+ action = "This command is valid for groupchat only."
case _ if message_lowercase.startswith("add"):
message = message[4:]
url = message.split(" ")[0]
@@ -510,7 +615,7 @@ class Slixfeed(slixmpp.ClientXMPP):
sqlitehandler.get_settings_value,
key
)
- val = await filterhandler.set_filter(
+ val = await listhandler.set_list(
val,
keywords
)
@@ -534,7 +639,7 @@ class Slixfeed(slixmpp.ClientXMPP):
sqlitehandler.get_settings_value,
key
)
- val = await filterhandler.set_filter(
+ val = await listhandler.set_list(
val,
keywords
)
@@ -629,6 +734,33 @@ class Slixfeed(slixmpp.ClientXMPP):
).format(val)
else:
action = "Missing value."
+ case _ if message_lowercase.startswith("join"):
+ muc = message[5:]
+ await self.join_muc(jid, muc)
+ case _ if message_lowercase.startswith("mastership"):
+ key = message[:7]
+ val = message[11:]
+ if val:
+ names = await filehandler.initdb(
+ jid,
+ sqlitehandler.get_settings_value,
+ key
+ )
+ val = await listhandler.set_list(
+ val,
+ names
+ )
+ await filehandler.initdb(
+ jid,
+ sqlitehandler.set_settings_value,
+ [key, val]
+ )
+ action = (
+ "Operators\n"
+ "```\n{}\n```"
+ ).format(val)
+ else:
+ action = "Missing value."
case _ if message_lowercase.startswith("next"):
num = message[5:]
await taskhandler.clean_tasks_xmpp(
@@ -675,16 +807,29 @@ class Slixfeed(slixmpp.ClientXMPP):
case "random":
action = "Updates will be sent randomly."
case _ if message_lowercase.startswith("read"):
- url = message[5:]
- if url.startswith("http"):
- # action = await datahandler.view_feed(url)
- action = await filehandler.initdb(
- jid,
- datahandler.view_feed,
- url
- )
- else:
- action = "Missing URL."
+ data = message[5:]
+ data = data.split()
+ url = data[0]
+ if url.startswith("feed:"):
+ url = await datahandler.feed_to_http(url)
+ match len(data):
+ case 1:
+ if url.startswith("http"):
+ action = await datahandler.view_feed(url)
+ else:
+ action = "Missing URL."
+ case 2:
+ num = data[1]
+ if url.startswith("http"):
+ action = await datahandler.view_entry(url, num)
+ else:
+ action = "Missing URL."
+ case _:
+ action = (
+ "Enter command as follows:\n"
+ "`read URL` or `read URL NUMBER`\n"
+ "URL must not contain white space."
+ )
case _ if message_lowercase.startswith("recent"):
num = message[7:]
if num:
@@ -759,16 +904,6 @@ class Slixfeed(slixmpp.ClientXMPP):
jid,
sqlitehandler.statistics
)
- case _ if message_lowercase.startswith("select"):
- num = message[7:]
- if num:
- action = await filehandler.initdb(
- jid,
- datahandler.view_entry,
- num
- )
- else:
- action = "Missing number."
case _ if message_lowercase.startswith("status "):
ix = message[7:]
action = await filehandler.initdb(
@@ -878,8 +1013,6 @@ def print_info():
" GNU General Public License for more details.\n"
"\n"
"NOTE\n"
- " Make Slixfeed your own.\n"
- "\n"
" You can run Slixfeed on your own computer, server, and\n"
" even on a Linux phone (i.e. Droidian, Mobian NixOS,\n"
" postmarketOS). You can also use Termux.\n"
@@ -919,30 +1052,34 @@ def print_help():
" For more information, visit https://xmpp.org/software/\n"
"\n"
"BASIC USAGE\n"
- " start\n"
- " Enable bot and send updates.\n"
- " stop\n"
- " Disable bot and stop updates.\n"
" URL\n"
" Add URL to subscription list.\n"
" add URL TITLE\n"
" Add URL to subscription list (without validity check).\n"
- " feeds\n"
- " List subscriptions.\n"
+ " join MUC\n"
+ " Join specified groupchat.\n"
+ " read URL\n"
+ " Display most recent 20 titles of given URL.\n"
+ " read URL N\n"
+ " Display specified entry number from given URL.\n"
+ "\n"
+ "MESSAGE OPTIONS\n"
+ " start\n"
+ " Enable bot and send updates.\n"
+ " stop\n"
+ " Disable bot and stop updates.\n"
" interval N\n"
" Set interval update to every N minutes.\n"
" next N\n"
" Send N next updates.\n"
" quantum N\n"
- " Set amount of updates for each interval.\n"
- " read URL\n"
- " Display most recent 20 titles of given URL.\n"
- " read URL NUM\n"
- " Display specified entry from given URL.\n"
+ " Set N amount of updates per interval.\n"
"\n"
"GROUPCHAT OPTIONS\n"
" ! (command initiation)\n"
" Use exclamation mark to initiate an actionable command.\n"
+ " activate CODE\n"
+ " Activate and command bot.\n"
" demaster NICKNAME\n"
" Remove master privilege.\n"
" mastership NICKNAME\n"
@@ -967,6 +1104,8 @@ def print_help():
" Toggle update status of feed.\n"
"\n"
"SEARCH OPTIONS\n"
+ " feeds\n"
+ " List all subscriptions.\n"
" feeds TEXT\n"
" Search subscriptions by given keywords.\n"
" search TEXT\n"