WIP
Add http proxy support. Add more functionality to handle bookmarks. Split into more modules. Remove callback function initdb. Tasked status messages are broken.
This commit is contained in:
parent
8e3e06b36b
commit
f65be8b5c8
16 changed files with 1553 additions and 1413 deletions
|
@ -55,7 +55,8 @@ TODO
|
||||||
Did you know that you can follow you favorite Mastodon feeds by just
|
Did you know that you can follow you favorite Mastodon feeds by just
|
||||||
sending the URL address?
|
sending the URL address?
|
||||||
Supported fediverse websites are:
|
Supported fediverse websites are:
|
||||||
Akkoma, HubZilla, Mastodon, Misskey, Pixelfed, Pleroma, Soapbox.
|
Akkoma, Firefish (Calckey), Friendica, HubZilla,
|
||||||
|
Mastodon, Misskey, Pixelfed, Pleroma, Socialhome, Soapbox.
|
||||||
|
|
||||||
15) Brand: News Broker, Newsman, Newsdealer, Laura Harbinger
|
15) Brand: News Broker, Newsman, Newsdealer, Laura Harbinger
|
||||||
|
|
||||||
|
@ -88,14 +89,13 @@ import os
|
||||||
# # with start_action(action_type="message()", msg=msg):
|
# # with start_action(action_type="message()", msg=msg):
|
||||||
|
|
||||||
#import slixfeed.irchandler
|
#import slixfeed.irchandler
|
||||||
|
from slixfeed.config import get_value
|
||||||
from slixfeed.xmpp.client import Slixfeed
|
from slixfeed.xmpp.client import Slixfeed
|
||||||
#import slixfeed.matrixhandler
|
#import slixfeed.matrixhandler
|
||||||
|
|
||||||
|
|
||||||
class Jabber:
|
class Jabber:
|
||||||
|
|
||||||
def __init__(self, jid, password, nick):
|
def __init__(self, jid, password, nick):
|
||||||
|
|
||||||
# Setup the Slixfeed and register plugins. Note that while plugins may
|
# Setup the Slixfeed and register plugins. Note that while plugins may
|
||||||
# have interdependencies, the order in which you register them does
|
# have interdependencies, the order in which you register them does
|
||||||
# not matter.
|
# not matter.
|
||||||
|
@ -104,11 +104,31 @@ class Jabber:
|
||||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||||
xmpp.register_plugin('xep_0045') # Multi-User Chat
|
xmpp.register_plugin('xep_0045') # Multi-User Chat
|
||||||
xmpp.register_plugin('xep_0048') # Bookmarks
|
xmpp.register_plugin('xep_0048') # Bookmarks
|
||||||
xmpp.register_plugin('xep_0060') # PubSub
|
xmpp.register_plugin('xep_0060') # Publish-Subscribe
|
||||||
xmpp.register_plugin('xep_0199', {'keepalive': True, 'frequency': 15}) # XMPP Ping
|
# xmpp.register_plugin('xep_0065') # SOCKS5 Bytestreams
|
||||||
|
xmpp.register_plugin('xep_0199', {'keepalive': True}) # XMPP Ping
|
||||||
xmpp.register_plugin('xep_0249') # Multi-User Chat
|
xmpp.register_plugin('xep_0249') # Multi-User Chat
|
||||||
xmpp.register_plugin('xep_0402') # PEP Native Bookmarks
|
xmpp.register_plugin('xep_0402') # PEP Native Bookmarks
|
||||||
|
|
||||||
|
# proxy_enabled = get_value("accounts", "XMPP Connect", "proxy_enabled")
|
||||||
|
# if proxy_enabled == '1':
|
||||||
|
# values = get_value("accounts", "XMPP Connect", [
|
||||||
|
# "proxy_host",
|
||||||
|
# "proxy_port",
|
||||||
|
# "proxy_username",
|
||||||
|
# "proxy_password"
|
||||||
|
# ])
|
||||||
|
# print("Proxy is enabled: {}:{}".format(values[0], values[1]))
|
||||||
|
# xmpp.use_proxy = True
|
||||||
|
# xmpp.proxy_config = {
|
||||||
|
# 'host': values[0],
|
||||||
|
# 'port': values[1],
|
||||||
|
# 'username': values[2],
|
||||||
|
# 'password': values[3]
|
||||||
|
# }
|
||||||
|
# proxy = {'socks5': (values[0], values[1])}
|
||||||
|
# xmpp.proxy = {'socks5': ('localhost', 9050)}
|
||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
xmpp.connect()
|
xmpp.connect()
|
||||||
xmpp.process()
|
xmpp.process()
|
||||||
|
|
|
@ -16,19 +16,62 @@ TODO
|
||||||
Refer to sqlitehandler.search_entries for implementation.
|
Refer to sqlitehandler.search_entries for implementation.
|
||||||
It is expected to be more complex than function search_entries.
|
It is expected to be more complex than function search_entries.
|
||||||
|
|
||||||
"""
|
5) Copy file from /etc/slixfeed/ or /usr/share/slixfeed/
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
import configparser
|
import configparser
|
||||||
# from file import get_default_confdir
|
# from file import get_default_confdir
|
||||||
import slixfeed.config as config
|
import slixfeed.config as config
|
||||||
import slixfeed.sqlite as sqlite
|
import slixfeed.sqlite as sqlite
|
||||||
import os
|
import os
|
||||||
from random import randrange
|
# from random import randrange
|
||||||
import sys
|
import sys
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
async def get_value_default(key, section):
|
|
||||||
|
def get_value(filename, section, keys):
|
||||||
|
"""
|
||||||
|
Get setting value.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
filename : str
|
||||||
|
INI filename.
|
||||||
|
keys : list or str
|
||||||
|
A single key as string or multiple keys as list.
|
||||||
|
section : str
|
||||||
|
INI Section.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
result : list or str
|
||||||
|
A single value as string or multiple values as list.
|
||||||
|
"""
|
||||||
|
config_res = configparser.RawConfigParser()
|
||||||
|
config_dir = config.get_default_confdir()
|
||||||
|
# if not os.path.isdir(config_dir):
|
||||||
|
# config_dir = '/usr/share/slixfeed/'
|
||||||
|
if not os.path.isdir(config_dir):
|
||||||
|
os.mkdir(config_dir)
|
||||||
|
config_file = os.path.join(config_dir, filename + ".ini")
|
||||||
|
config_res.read(config_file)
|
||||||
|
if config_res.has_section(section):
|
||||||
|
section_res = config_res[section]
|
||||||
|
if isinstance(keys, list):
|
||||||
|
result = []
|
||||||
|
for key in keys:
|
||||||
|
result.extend([section_res[key]])
|
||||||
|
elif isinstance(keys, str):
|
||||||
|
key = keys
|
||||||
|
result = section_res[key]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# TODO Store config file as an object in runtime, otherwise
|
||||||
|
# the file will be opened time and time again.
|
||||||
|
# TODO Copy file from /etc/slixfeed/ or /usr/share/slixfeed/
|
||||||
|
def get_value_default(filename, section, key):
|
||||||
"""
|
"""
|
||||||
Get settings default value.
|
Get settings default value.
|
||||||
|
|
||||||
|
@ -47,17 +90,14 @@ async def get_value_default(key, section):
|
||||||
config_dir = config.get_default_confdir()
|
config_dir = config.get_default_confdir()
|
||||||
if not os.path.isdir(config_dir):
|
if not os.path.isdir(config_dir):
|
||||||
config_dir = '/usr/share/slixfeed/'
|
config_dir = '/usr/share/slixfeed/'
|
||||||
config_file = os.path.join(config_dir, r"settings.ini")
|
config_file = os.path.join(config_dir, filename + ".ini")
|
||||||
config_res.read(config_file)
|
config_res.read(config_file)
|
||||||
if config_res.has_section(section):
|
if config_res.has_section(section):
|
||||||
result = config_res[section][key]
|
result = config_res[section][key]
|
||||||
isinstance(result, int)
|
|
||||||
isinstance(result, str)
|
|
||||||
breakpoint
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
async def get_list(filename):
|
def get_list(filename):
|
||||||
"""
|
"""
|
||||||
Get settings default value.
|
Get settings default value.
|
||||||
|
|
||||||
|
@ -149,7 +189,7 @@ def get_default_confdir():
|
||||||
return os.path.join(config_home, 'slixfeed')
|
return os.path.join(config_home, 'slixfeed')
|
||||||
|
|
||||||
|
|
||||||
async def initdb(jid, callback, message=None):
|
def get_pathname_to_database(jid):
|
||||||
"""
|
"""
|
||||||
Callback function to instantiate action on database.
|
Callback function to instantiate action on database.
|
||||||
|
|
||||||
|
@ -173,11 +213,12 @@ async def initdb(jid, callback, message=None):
|
||||||
os.mkdir(db_dir)
|
os.mkdir(db_dir)
|
||||||
db_file = os.path.join(db_dir, r"{}.db".format(jid))
|
db_file = os.path.join(db_dir, r"{}.db".format(jid))
|
||||||
sqlite.create_tables(db_file)
|
sqlite.create_tables(db_file)
|
||||||
|
return db_file
|
||||||
# await set_default_values(db_file)
|
# await set_default_values(db_file)
|
||||||
if message:
|
# if message:
|
||||||
return await callback(db_file, message)
|
# return await callback(db_file, message)
|
||||||
else:
|
# else:
|
||||||
return await callback(db_file)
|
# return await callback(db_file)
|
||||||
|
|
||||||
|
|
||||||
async def add_to_list(newwords, keywords):
|
async def add_to_list(newwords, keywords):
|
||||||
|
|
|
@ -14,7 +14,7 @@ TODO
|
||||||
|
|
||||||
2) Check also for HTML, not only feed.bozo.
|
2) Check also for HTML, not only feed.bozo.
|
||||||
|
|
||||||
3) Add "if is_feed(url, feed)" to view_entry and view_feed
|
3) Add "if utility.is_feed(url, feed)" to view_entry and view_feed
|
||||||
|
|
||||||
4) Refactor view_entry and view_feed - Why "if" twice?
|
4) Refactor view_entry and view_feed - Why "if" twice?
|
||||||
|
|
||||||
|
@ -24,17 +24,18 @@ from aiohttp import ClientError, ClientSession, ClientTimeout
|
||||||
from asyncio import TimeoutError
|
from asyncio import TimeoutError
|
||||||
from asyncio.exceptions import IncompleteReadError
|
from asyncio.exceptions import IncompleteReadError
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
import slixfeed.config as config
|
|
||||||
from slixfeed.datetime import now, rfc2822_to_iso8601
|
|
||||||
from email.utils import parseaddr
|
from email.utils import parseaddr
|
||||||
from feedparser import parse
|
from feedparser import parse
|
||||||
from http.client import IncompleteRead
|
from http.client import IncompleteRead
|
||||||
from lxml import html
|
from lxml import html
|
||||||
|
import slixfeed.config as config
|
||||||
|
from slixfeed.datetime import now, rfc2822_to_iso8601
|
||||||
|
import slixfeed.utility as utility
|
||||||
import slixfeed.sqlite as sqlite
|
import slixfeed.sqlite as sqlite
|
||||||
from slixfeed.url import complete_url, join_url, trim_url
|
from slixfeed.url import complete_url, join_url, trim_url
|
||||||
from urllib import error
|
from urllib import error
|
||||||
# from xml.etree.ElementTree import ElementTree, ParseError
|
# from xml.etree.ElementTree import ElementTree, ParseError
|
||||||
from urllib.parse import urljoin, urlsplit, urlunsplit
|
from urllib.parse import urlsplit, urlunsplit
|
||||||
|
|
||||||
# NOTE Why (if res[0]) and (if res[1] == 200)?
|
# NOTE Why (if res[0]) and (if res[1] == 200)?
|
||||||
async def download_updates(db_file, url=None):
|
async def download_updates(db_file, url=None):
|
||||||
|
@ -262,9 +263,9 @@ async def view_feed(url):
|
||||||
# breakpoint()
|
# breakpoint()
|
||||||
if result[1] == 200:
|
if result[1] == 200:
|
||||||
feed = parse(result[0])
|
feed = parse(result[0])
|
||||||
title = get_title(url, feed)
|
title = utility.get_title(url, feed)
|
||||||
entries = feed.entries
|
entries = feed.entries
|
||||||
msg = "Preview of {}:\n```\n".format(title)
|
msg = "Preview of {}:\n\n```\n".format(title)
|
||||||
counter = 0
|
counter = 0
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
counter += 1
|
counter += 1
|
||||||
|
@ -339,7 +340,7 @@ async def view_entry(url, num):
|
||||||
# breakpoint()
|
# breakpoint()
|
||||||
if result[1] == 200:
|
if result[1] == 200:
|
||||||
feed = parse(result[0])
|
feed = parse(result[0])
|
||||||
title = get_title(url, result[0])
|
title = utility.get_title(url, result[0])
|
||||||
entries = feed.entries
|
entries = feed.entries
|
||||||
num = int(num) - 1
|
num = int(num) - 1
|
||||||
entry = entries[num]
|
entry = entries[num]
|
||||||
|
@ -447,8 +448,8 @@ async def add_feed(db_file, url):
|
||||||
res = await download_feed(url)
|
res = await download_feed(url)
|
||||||
if res[0]:
|
if res[0]:
|
||||||
feed = parse(res[0])
|
feed = parse(res[0])
|
||||||
title = get_title(url, feed)
|
title = utility.get_title(url, feed)
|
||||||
if is_feed(url, feed):
|
if utility.is_feed(url, feed):
|
||||||
status = res[1]
|
status = res[1]
|
||||||
msg = await sqlite.insert_feed(
|
msg = await sqlite.insert_feed(
|
||||||
db_file,
|
db_file,
|
||||||
|
@ -533,17 +534,23 @@ async def download_feed(url):
|
||||||
Document or error message.
|
Document or error message.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
user_agent = await config.get_value_default("user-agent", "Network")
|
user_agent = config.get_value_default("settings", "Network", "user-agent")
|
||||||
except:
|
except:
|
||||||
user_agent = "Slixfeed/0.1"
|
user_agent = "Slixfeed/0.1"
|
||||||
if not len(user_agent):
|
if not len(user_agent):
|
||||||
user_agent = "Slixfeed/0.1"
|
user_agent = "Slixfeed/0.1"
|
||||||
|
proxy = config.get_value("settings", "Network", "http_proxy")
|
||||||
timeout = ClientTimeout(total=10)
|
timeout = ClientTimeout(total=10)
|
||||||
headers = {'User-Agent': user_agent}
|
headers = {'User-Agent': user_agent}
|
||||||
async with ClientSession(headers=headers) as session:
|
async with ClientSession(headers=headers) as session:
|
||||||
# async with ClientSession(trust_env=True) as session:
|
# async with ClientSession(trust_env=True) as session:
|
||||||
try:
|
try:
|
||||||
async with session.get(url, timeout=timeout) as response:
|
async with session.get(
|
||||||
|
url,
|
||||||
|
proxy=proxy,
|
||||||
|
# proxy_auth=(proxy_username, proxy_password)
|
||||||
|
timeout=timeout
|
||||||
|
) as response:
|
||||||
status = response.status
|
status = response.status
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
try:
|
try:
|
||||||
|
@ -584,31 +591,6 @@ async def download_feed(url):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def get_title(url, feed):
|
|
||||||
"""
|
|
||||||
Get title of feed.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
url : str
|
|
||||||
URL.
|
|
||||||
feed : dict
|
|
||||||
Parsed feed document.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
title : str
|
|
||||||
Title or URL hostname.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
title = feed["feed"]["title"]
|
|
||||||
except:
|
|
||||||
title = urlsplit(url).netloc
|
|
||||||
if not title:
|
|
||||||
title = urlsplit(url).netloc
|
|
||||||
return title
|
|
||||||
|
|
||||||
|
|
||||||
# TODO Improve scan by gradual decreasing of path
|
# TODO Improve scan by gradual decreasing of path
|
||||||
async def feed_mode_request(url, tree):
|
async def feed_mode_request(url, tree):
|
||||||
"""
|
"""
|
||||||
|
@ -630,7 +612,7 @@ async def feed_mode_request(url, tree):
|
||||||
"""
|
"""
|
||||||
feeds = {}
|
feeds = {}
|
||||||
parted_url = urlsplit(url)
|
parted_url = urlsplit(url)
|
||||||
paths = await config.get_list("lists.yaml")
|
paths = config.get_list("lists.yaml")
|
||||||
paths = paths["pathnames"]
|
paths = paths["pathnames"]
|
||||||
for path in paths:
|
for path in paths:
|
||||||
address = urlunsplit([
|
address = urlunsplit([
|
||||||
|
@ -650,7 +632,9 @@ async def feed_mode_request(url, tree):
|
||||||
title = '*** No Title ***'
|
title = '*** No Title ***'
|
||||||
feeds[address] = title
|
feeds[address] = title
|
||||||
# Check whether URL has path (i.e. not root)
|
# Check whether URL has path (i.e. not root)
|
||||||
if parted_url.path.split('/')[1]:
|
# Check parted_url.path to avoid error in case root wasn't given
|
||||||
|
# TODO Make more tests
|
||||||
|
if parted_url.path and parted_url.path.split('/')[1]:
|
||||||
paths.extend(
|
paths.extend(
|
||||||
[".atom", ".feed", ".rdf", ".rss"]
|
[".atom", ".feed", ".rdf", ".rss"]
|
||||||
) if '.rss' not in paths else -1
|
) if '.rss' not in paths else -1
|
||||||
|
@ -673,8 +657,9 @@ async def feed_mode_request(url, tree):
|
||||||
if len(feeds) > 1:
|
if len(feeds) > 1:
|
||||||
counter = 0
|
counter = 0
|
||||||
msg = (
|
msg = (
|
||||||
"RSS URL discovery has found {} feeds:\n```\n"
|
"RSS URL discovery has found {} feeds:\n\n```\n"
|
||||||
).format(len(feeds))
|
).format(len(feeds))
|
||||||
|
feed_mark = 0
|
||||||
for feed in feeds:
|
for feed in feeds:
|
||||||
try:
|
try:
|
||||||
feed_name = feeds[feed]["feed"]["title"]
|
feed_name = feeds[feed]["feed"]["title"]
|
||||||
|
@ -686,7 +671,6 @@ async def feed_mode_request(url, tree):
|
||||||
feed_amnt = len(feeds[feed].entries)
|
feed_amnt = len(feeds[feed].entries)
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
feed_mark = 0
|
|
||||||
if feed_amnt:
|
if feed_amnt:
|
||||||
# NOTE Because there could be many false positives
|
# NOTE Because there could be many false positives
|
||||||
# which are revealed in second phase of scan, we
|
# which are revealed in second phase of scan, we
|
||||||
|
@ -741,7 +725,7 @@ async def feed_mode_scan(url, tree):
|
||||||
feeds = {}
|
feeds = {}
|
||||||
# paths = []
|
# paths = []
|
||||||
# TODO Test
|
# TODO Test
|
||||||
paths = await config.get_list("lists.yaml")
|
paths = config.get_list("lists.yaml")
|
||||||
paths = paths["pathnames"]
|
paths = paths["pathnames"]
|
||||||
for path in paths:
|
for path in paths:
|
||||||
# xpath_query = "//*[@*[contains(.,'{}')]]".format(path)
|
# xpath_query = "//*[@*[contains(.,'{}')]]".format(path)
|
||||||
|
@ -794,8 +778,9 @@ async def feed_mode_scan(url, tree):
|
||||||
# breakpoint()
|
# breakpoint()
|
||||||
counter = 0
|
counter = 0
|
||||||
msg = (
|
msg = (
|
||||||
"RSS URL scan has found {} feeds:\n```\n"
|
"RSS URL scan has found {} feeds:\n\n```\n"
|
||||||
).format(len(feeds))
|
).format(len(feeds))
|
||||||
|
feed_mark = 0
|
||||||
for feed in feeds:
|
for feed in feeds:
|
||||||
# try:
|
# try:
|
||||||
# res = await download_feed(feed)
|
# res = await download_feed(feed)
|
||||||
|
@ -807,7 +792,6 @@ async def feed_mode_scan(url, tree):
|
||||||
feed_name = urlsplit(feed).netloc
|
feed_name = urlsplit(feed).netloc
|
||||||
feed_addr = feed
|
feed_addr = feed
|
||||||
feed_amnt = len(feeds[feed].entries)
|
feed_amnt = len(feeds[feed].entries)
|
||||||
feed_mark = 0
|
|
||||||
if feed_amnt:
|
if feed_amnt:
|
||||||
# NOTE Because there could be many false positives
|
# NOTE Because there could be many false positives
|
||||||
# which are revealed in second phase of scan, we
|
# which are revealed in second phase of scan, we
|
||||||
|
@ -872,7 +856,7 @@ async def feed_mode_auto_discovery(url, tree):
|
||||||
feeds = tree.xpath(xpath_query)
|
feeds = tree.xpath(xpath_query)
|
||||||
if len(feeds) > 1:
|
if len(feeds) > 1:
|
||||||
msg = (
|
msg = (
|
||||||
"RSS Auto-Discovery has found {} feeds:\n```\n"
|
"RSS Auto-Discovery has found {} feeds:\n\n```\n"
|
||||||
).format(len(feeds))
|
).format(len(feeds))
|
||||||
for feed in feeds:
|
for feed in feeds:
|
||||||
# # The following code works;
|
# # The following code works;
|
||||||
|
@ -896,46 +880,3 @@ async def feed_mode_auto_discovery(url, tree):
|
||||||
elif feeds:
|
elif feeds:
|
||||||
feed_addr = join_url(url, feeds[0].xpath('@href')[0])
|
feed_addr = join_url(url, feeds[0].xpath('@href')[0])
|
||||||
return [feed_addr]
|
return [feed_addr]
|
||||||
|
|
||||||
|
|
||||||
def is_feed(url, feed):
|
|
||||||
"""
|
|
||||||
Determine whether document is feed or not.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
url : str
|
|
||||||
URL.
|
|
||||||
feed : dict
|
|
||||||
Parsed feed.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
val : boolean
|
|
||||||
True or False.
|
|
||||||
"""
|
|
||||||
msg = None
|
|
||||||
if not feed.entries:
|
|
||||||
try:
|
|
||||||
feed["feed"]["title"]
|
|
||||||
val = True
|
|
||||||
msg = (
|
|
||||||
"Empty feed for {}"
|
|
||||||
).format(url)
|
|
||||||
except:
|
|
||||||
val = False
|
|
||||||
msg = (
|
|
||||||
"No entries nor title for {}"
|
|
||||||
).format(url)
|
|
||||||
elif feed.bozo:
|
|
||||||
val = False
|
|
||||||
msg = (
|
|
||||||
"Bozo detected for {}"
|
|
||||||
).format(url)
|
|
||||||
else:
|
|
||||||
val = True
|
|
||||||
msg = (
|
|
||||||
"Good feed for {}"
|
|
||||||
).format(url)
|
|
||||||
print(msg)
|
|
||||||
return val
|
|
||||||
|
|
|
@ -311,7 +311,7 @@ async def check_feed_exist(db_file, url):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
async def get_number_of_items(db_file, table):
|
def get_number_of_items(db_file, table):
|
||||||
"""
|
"""
|
||||||
Return number of entries or feeds.
|
Return number of entries or feeds.
|
||||||
|
|
||||||
|
@ -337,7 +337,7 @@ async def get_number_of_items(db_file, table):
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
async def get_number_of_feeds_active(db_file):
|
def get_number_of_feeds_active(db_file):
|
||||||
"""
|
"""
|
||||||
Return number of active feeds.
|
Return number of active feeds.
|
||||||
|
|
||||||
|
@ -362,7 +362,7 @@ async def get_number_of_feeds_active(db_file):
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
async def get_number_of_entries_unread(db_file):
|
def get_number_of_entries_unread(db_file):
|
||||||
"""
|
"""
|
||||||
Return number of unread items.
|
Return number of unread items.
|
||||||
|
|
||||||
|
@ -491,8 +491,8 @@ async def get_entry_unread(db_file, num=None):
|
||||||
# summary = ["> " + line for line in summary]
|
# summary = ["> " + line for line in summary]
|
||||||
# summary = "\n".join(summary)
|
# summary = "\n".join(summary)
|
||||||
link = result[2]
|
link = result[2]
|
||||||
link = await remove_tracking_parameters(link)
|
link = remove_tracking_parameters(link)
|
||||||
link = (await replace_hostname(link, "link")) or link
|
link = (replace_hostname(link, "link")) or link
|
||||||
sql = (
|
sql = (
|
||||||
"SELECT name "
|
"SELECT name "
|
||||||
"FROM feeds "
|
"FROM feeds "
|
||||||
|
@ -504,19 +504,11 @@ async def get_entry_unread(db_file, num=None):
|
||||||
if num > 1:
|
if num > 1:
|
||||||
news_list += (
|
news_list += (
|
||||||
"\n{}\n{}\n{}\n"
|
"\n{}\n{}\n{}\n"
|
||||||
).format(
|
).format(str(title), str(link), str(feed))
|
||||||
str(title),
|
|
||||||
str(link),
|
|
||||||
str(feed)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
news_list = (
|
news_list = (
|
||||||
"{}\n{}\n{}"
|
"{}\n{}\n{}"
|
||||||
).format(
|
).format(str(title), str(link), str(feed))
|
||||||
str(title),
|
|
||||||
str(link),
|
|
||||||
str(feed)
|
|
||||||
)
|
|
||||||
# TODO While `async with DBLOCK` does work well from
|
# TODO While `async with DBLOCK` does work well from
|
||||||
# outside of functions, it would be better practice
|
# outside of functions, it would be better practice
|
||||||
# to place it within the functions.
|
# to place it within the functions.
|
||||||
|
@ -625,53 +617,31 @@ async def statistics(db_file):
|
||||||
msg : str
|
msg : str
|
||||||
Statistics as message.
|
Statistics as message.
|
||||||
"""
|
"""
|
||||||
feeds = await get_number_of_items(db_file, 'feeds')
|
values = []
|
||||||
active_feeds = await get_number_of_feeds_active(db_file)
|
values.extend([get_number_of_entries_unread(db_file)])
|
||||||
entries = await get_number_of_items(db_file, 'entries')
|
entries = get_number_of_items(db_file, 'entries')
|
||||||
archive = await get_number_of_items(db_file, 'archive')
|
archive = get_number_of_items(db_file, 'archive')
|
||||||
unread_entries = await get_number_of_entries_unread(db_file)
|
values.extend([entries + archive])
|
||||||
|
values.extend([get_number_of_feeds_active(db_file)])
|
||||||
|
values.extend([get_number_of_items(db_file, 'feeds')])
|
||||||
# msg = """You have {} unread news items out of {} from {} news sources.
|
# msg = """You have {} unread news items out of {} from {} news sources.
|
||||||
# """.format(unread_entries, entries, feeds)
|
# """.format(unread_entries, entries, feeds)
|
||||||
with create_connection(db_file) as conn:
|
with create_connection(db_file) as conn:
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
vals = []
|
for key in ["archive", "interval",
|
||||||
for key in [
|
"quantum", "enabled"]:
|
||||||
"archive",
|
|
||||||
"interval",
|
|
||||||
"quantum",
|
|
||||||
"enabled"
|
|
||||||
]:
|
|
||||||
sql = (
|
sql = (
|
||||||
"SELECT value "
|
"SELECT value "
|
||||||
"FROM settings "
|
"FROM settings "
|
||||||
"WHERE key = ?"
|
"WHERE key = ?"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
val = cur.execute(sql, (key,)).fetchone()[0]
|
value = cur.execute(sql, (key,)).fetchone()[0]
|
||||||
except:
|
except:
|
||||||
print("Error for key:", key)
|
print("Error for key:", key)
|
||||||
val = "none"
|
value = "none"
|
||||||
vals.extend([val])
|
values.extend([value])
|
||||||
msg = (
|
return values
|
||||||
"```"
|
|
||||||
"\nSTATISTICS\n"
|
|
||||||
"News items : {} / {}\n"
|
|
||||||
"News sources : {} / {}\n"
|
|
||||||
"\nOPTIONS\n"
|
|
||||||
"Items to archive : {}\n"
|
|
||||||
"Update interval : {}\n"
|
|
||||||
"Items per update : {}\n"
|
|
||||||
"Operation status : {}\n"
|
|
||||||
"```"
|
|
||||||
).format(
|
|
||||||
unread_entries, entries + archive,
|
|
||||||
active_feeds, feeds,
|
|
||||||
vals[0],
|
|
||||||
vals[1],
|
|
||||||
vals[2],
|
|
||||||
vals[3]
|
|
||||||
)
|
|
||||||
return msg
|
|
||||||
|
|
||||||
|
|
||||||
async def update_statistics(cur):
|
async def update_statistics(cur):
|
||||||
|
@ -684,9 +654,9 @@ async def update_statistics(cur):
|
||||||
Cursor object.
|
Cursor object.
|
||||||
"""
|
"""
|
||||||
stat_dict = {}
|
stat_dict = {}
|
||||||
stat_dict["feeds"] = await get_number_of_items(cur, 'feeds')
|
stat_dict["feeds"] = get_number_of_items(cur, 'feeds')
|
||||||
stat_dict["entries"] = await get_number_of_items(cur, 'entries')
|
stat_dict["entries"] = get_number_of_items(cur, 'entries')
|
||||||
stat_dict["unread"] = await get_number_of_entries_unread(cur=cur)
|
stat_dict["unread"] = get_number_of_entries_unread(cur=cur)
|
||||||
for i in stat_dict:
|
for i in stat_dict:
|
||||||
sql = (
|
sql = (
|
||||||
"SELECT id "
|
"SELECT id "
|
||||||
|
@ -913,13 +883,14 @@ async def add_entry(cur, entry):
|
||||||
try:
|
try:
|
||||||
cur.execute(sql, entry)
|
cur.execute(sql, entry)
|
||||||
except:
|
except:
|
||||||
print(current_time(), "COROUTINE OBJECT NOW")
|
print("Unknown error for sqlite.add_entry")
|
||||||
|
# print(current_time(), "COROUTINE OBJECT NOW")
|
||||||
# for i in entry:
|
# for i in entry:
|
||||||
# print(type(i))
|
# print(type(i))
|
||||||
# print(i)
|
# print(i)
|
||||||
# print(type(entry))
|
# print(type(entry))
|
||||||
print(entry)
|
print(entry)
|
||||||
print(current_time(), "COROUTINE OBJECT NOW")
|
# print(current_time(), "COROUTINE OBJECT NOW")
|
||||||
# breakpoint()
|
# breakpoint()
|
||||||
|
|
||||||
|
|
||||||
|
@ -1147,7 +1118,7 @@ async def get_feeds_url(db_file):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
async def list_feeds(db_file):
|
async def get_feeds(db_file):
|
||||||
"""
|
"""
|
||||||
Query table feeds and list items.
|
Query table feeds and list items.
|
||||||
|
|
||||||
|
@ -1158,8 +1129,8 @@ async def list_feeds(db_file):
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
msg : str
|
msg : ???
|
||||||
URLs of feeds as message.
|
URLs of feeds.
|
||||||
"""
|
"""
|
||||||
cur = get_cursor(db_file)
|
cur = get_cursor(db_file)
|
||||||
sql = (
|
sql = (
|
||||||
|
@ -1167,37 +1138,11 @@ async def list_feeds(db_file):
|
||||||
"FROM feeds"
|
"FROM feeds"
|
||||||
)
|
)
|
||||||
results = cur.execute(sql)
|
results = cur.execute(sql)
|
||||||
feeds_list = "\nList of subscriptions:\n```\n"
|
print("type of resilts in sqlite.py is:")
|
||||||
counter = 0
|
print(type(results))
|
||||||
for result in results:
|
print(results)
|
||||||
counter += 1
|
breakpoint()
|
||||||
feeds_list += (
|
return results
|
||||||
"Name : {}\n"
|
|
||||||
"Address : {}\n"
|
|
||||||
"Updated : {}\n"
|
|
||||||
"Status : {}\n"
|
|
||||||
"ID : {}\n"
|
|
||||||
"\n"
|
|
||||||
).format(
|
|
||||||
str(result[0]),
|
|
||||||
str(result[1]),
|
|
||||||
str(result[2]),
|
|
||||||
str(result[3]),
|
|
||||||
str(result[4])
|
|
||||||
)
|
|
||||||
if counter:
|
|
||||||
return feeds_list + (
|
|
||||||
"```\nTotal of {} subscriptions.\n"
|
|
||||||
).format(counter)
|
|
||||||
else:
|
|
||||||
msg = (
|
|
||||||
"List of subscriptions is empty.\n"
|
|
||||||
"To add feed, send a URL\n"
|
|
||||||
"Try these:\n"
|
|
||||||
# TODO Pick random from featured/recommended
|
|
||||||
"https://reclaimthenet.org/feed/"
|
|
||||||
)
|
|
||||||
return msg
|
|
||||||
|
|
||||||
|
|
||||||
async def last_entries(db_file, num):
|
async def last_entries(db_file, num):
|
||||||
|
@ -1235,21 +1180,6 @@ async def last_entries(db_file, num):
|
||||||
"LIMIT :num "
|
"LIMIT :num "
|
||||||
)
|
)
|
||||||
results = cur.execute(sql, (num,))
|
results = cur.execute(sql, (num,))
|
||||||
titles_list = "Recent {} titles:\n```".format(num)
|
|
||||||
counter = 0
|
|
||||||
for result in results:
|
|
||||||
counter += 1
|
|
||||||
titles_list += (
|
|
||||||
"\n{}\n{}\n"
|
|
||||||
).format(
|
|
||||||
str(result[0]),
|
|
||||||
str(result[1])
|
|
||||||
)
|
|
||||||
if counter:
|
|
||||||
titles_list += "```\n"
|
|
||||||
return titles_list
|
|
||||||
else:
|
|
||||||
return "There are no news at the moment."
|
|
||||||
|
|
||||||
|
|
||||||
async def search_feeds(db_file, query):
|
async def search_feeds(db_file, query):
|
||||||
|
@ -1277,28 +1207,7 @@ async def search_feeds(db_file, query):
|
||||||
"LIMIT 50"
|
"LIMIT 50"
|
||||||
)
|
)
|
||||||
results = cur.execute(sql, [f'%{query}%', f'%{query}%'])
|
results = cur.execute(sql, [f'%{query}%', f'%{query}%'])
|
||||||
results_list = (
|
return results
|
||||||
"Feeds containing '{}':\n```"
|
|
||||||
).format(query)
|
|
||||||
counter = 0
|
|
||||||
for result in results:
|
|
||||||
counter += 1
|
|
||||||
results_list += (
|
|
||||||
"\nName : {}"
|
|
||||||
"\nURL : {}"
|
|
||||||
"\nIndex : {}"
|
|
||||||
"\nMode : {}"
|
|
||||||
"\n"
|
|
||||||
).format(
|
|
||||||
str(result[0]),
|
|
||||||
str(result[1]),
|
|
||||||
str(result[2]),
|
|
||||||
str(result[3])
|
|
||||||
)
|
|
||||||
if counter:
|
|
||||||
return results_list + "\n```\nTotal of {} feeds".format(counter)
|
|
||||||
else:
|
|
||||||
return "No feeds were found for: {}".format(query)
|
|
||||||
|
|
||||||
|
|
||||||
async def search_entries(db_file, query):
|
async def search_entries(db_file, query):
|
||||||
|
@ -1332,22 +1241,7 @@ async def search_entries(db_file, query):
|
||||||
f'%{query}%',
|
f'%{query}%',
|
||||||
f'%{query}%'
|
f'%{query}%'
|
||||||
))
|
))
|
||||||
results_list = (
|
return results
|
||||||
"Search results for '{}':\n```"
|
|
||||||
).format(query)
|
|
||||||
counter = 0
|
|
||||||
for result in results:
|
|
||||||
counter += 1
|
|
||||||
results_list += (
|
|
||||||
"\n{}\n{}\n"
|
|
||||||
).format(
|
|
||||||
str(result[0]),
|
|
||||||
str(result[1])
|
|
||||||
)
|
|
||||||
if counter:
|
|
||||||
return results_list + "```\nTotal of {} results".format(counter)
|
|
||||||
else:
|
|
||||||
return "No results were found for: {}".format(query)
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
FIXME
|
FIXME
|
||||||
|
@ -1471,7 +1365,7 @@ async def set_settings_value(db_file, key_value):
|
||||||
# key = "enabled"
|
# key = "enabled"
|
||||||
# val = 0
|
# val = 0
|
||||||
key = key_value[0]
|
key = key_value[0]
|
||||||
val = key_value[1]
|
value = key_value[1]
|
||||||
async with DBLOCK:
|
async with DBLOCK:
|
||||||
with create_connection(db_file) as conn:
|
with create_connection(db_file) as conn:
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
@ -1483,7 +1377,7 @@ async def set_settings_value(db_file, key_value):
|
||||||
)
|
)
|
||||||
cur.execute(sql, {
|
cur.execute(sql, {
|
||||||
"key": key,
|
"key": key,
|
||||||
"value": val
|
"value": value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -1509,7 +1403,7 @@ async def set_settings_value_default(cur, key):
|
||||||
# sql = "SELECT id FROM settings WHERE key = ?"
|
# sql = "SELECT id FROM settings WHERE key = ?"
|
||||||
# cur.execute(sql, (i,))
|
# cur.execute(sql, (i,))
|
||||||
# if not cur.fetchone():
|
# if not cur.fetchone():
|
||||||
# val = await settings.get_value_default(i)
|
# val = settings.get_value_default(i)
|
||||||
# sql = "INSERT INTO settings(key,value) VALUES(?,?)"
|
# sql = "INSERT INTO settings(key,value) VALUES(?,?)"
|
||||||
# cur.execute(sql, (i, val))
|
# cur.execute(sql, (i, val))
|
||||||
sql = (
|
sql = (
|
||||||
|
@ -1519,14 +1413,14 @@ async def set_settings_value_default(cur, key):
|
||||||
)
|
)
|
||||||
cur.execute(sql, (key,))
|
cur.execute(sql, (key,))
|
||||||
if not cur.fetchone():
|
if not cur.fetchone():
|
||||||
val = await config.get_value_default(key, "Settings")
|
value = config.get_value_default("settings", "Settings", key)
|
||||||
sql = (
|
sql = (
|
||||||
"INSERT "
|
"INSERT "
|
||||||
"INTO settings(key,value) "
|
"INTO settings(key,value) "
|
||||||
"VALUES(?,?)"
|
"VALUES(?,?)"
|
||||||
)
|
)
|
||||||
cur.execute(sql, (key, val))
|
cur.execute(sql, (key, value))
|
||||||
return val
|
return value
|
||||||
|
|
||||||
|
|
||||||
async def get_settings_value(db_file, key):
|
async def get_settings_value(db_file, key):
|
||||||
|
@ -1553,9 +1447,9 @@ async def get_settings_value(db_file, key):
|
||||||
# cur.execute(sql, (key,))
|
# cur.execute(sql, (key,))
|
||||||
# result = cur.fetchone()
|
# result = cur.fetchone()
|
||||||
# except:
|
# except:
|
||||||
# result = await settings.get_value_default(key)
|
# result = settings.get_value_default(key)
|
||||||
# if not result:
|
# if not result:
|
||||||
# result = await settings.get_value_default(key)
|
# result = settings.get_value_default(key)
|
||||||
# return result
|
# return result
|
||||||
with create_connection(db_file) as conn:
|
with create_connection(db_file) as conn:
|
||||||
try:
|
try:
|
||||||
|
@ -1636,7 +1530,7 @@ async def set_filters_value_default(cur, key):
|
||||||
)
|
)
|
||||||
cur.execute(sql, (key,))
|
cur.execute(sql, (key,))
|
||||||
if not cur.fetchone():
|
if not cur.fetchone():
|
||||||
val = await config.get_list("lists.yaml")
|
val = config.get_list("lists.yaml")
|
||||||
val = val[key]
|
val = val[key]
|
||||||
val = ",".join(val)
|
val = ",".join(val)
|
||||||
sql = (
|
sql = (
|
||||||
|
|
145
slixfeed/task.py
145
slixfeed/task.py
|
@ -44,7 +44,10 @@ import logging
|
||||||
import os
|
import os
|
||||||
import slixmpp
|
import slixmpp
|
||||||
|
|
||||||
from slixfeed.config import initdb, get_default_dbdir, get_value_default
|
from slixfeed.config import (
|
||||||
|
get_pathname_to_database,
|
||||||
|
get_default_dbdir,
|
||||||
|
get_value_default)
|
||||||
from slixfeed.datetime import current_time
|
from slixfeed.datetime import current_time
|
||||||
from slixfeed.fetch import download_updates
|
from slixfeed.fetch import download_updates
|
||||||
from slixfeed.sqlite import (
|
from slixfeed.sqlite import (
|
||||||
|
@ -55,6 +58,7 @@ from slixfeed.sqlite import (
|
||||||
)
|
)
|
||||||
# from xmpp import Slixfeed
|
# from xmpp import Slixfeed
|
||||||
import slixfeed.xmpp.client as xmpp
|
import slixfeed.xmpp.client as xmpp
|
||||||
|
import slixfeed.xmpp.utility as utility
|
||||||
|
|
||||||
main_task = []
|
main_task = []
|
||||||
jid_tasker = {}
|
jid_tasker = {}
|
||||||
|
@ -87,16 +91,13 @@ async def start_tasks_xmpp(self, jid, tasks):
|
||||||
match task:
|
match task:
|
||||||
case "check":
|
case "check":
|
||||||
task_manager[jid]["check"] = asyncio.create_task(
|
task_manager[jid]["check"] = asyncio.create_task(
|
||||||
check_updates(jid)
|
check_updates(jid))
|
||||||
)
|
|
||||||
case "status":
|
case "status":
|
||||||
task_manager[jid]["status"] = asyncio.create_task(
|
task_manager[jid]["status"] = asyncio.create_task(
|
||||||
send_status(self, jid)
|
send_status(self, jid))
|
||||||
)
|
|
||||||
case "interval":
|
case "interval":
|
||||||
task_manager[jid]["interval"] = asyncio.create_task(
|
task_manager[jid]["interval"] = asyncio.create_task(
|
||||||
send_update(self, jid)
|
send_update(self, jid))
|
||||||
)
|
|
||||||
# for task in task_manager[jid].values():
|
# for task in task_manager[jid].values():
|
||||||
# print("task_manager[jid].values()")
|
# print("task_manager[jid].values()")
|
||||||
# print(task_manager[jid].values())
|
# print(task_manager[jid].values())
|
||||||
|
@ -107,6 +108,7 @@ async def start_tasks_xmpp(self, jid, tasks):
|
||||||
# breakpoint()
|
# breakpoint()
|
||||||
# await task
|
# await task
|
||||||
|
|
||||||
|
|
||||||
async def clean_tasks_xmpp(jid, tasks):
|
async def clean_tasks_xmpp(jid, tasks):
|
||||||
# print("clean_tasks_xmpp", jid, tasks)
|
# print("clean_tasks_xmpp", jid, tasks)
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
|
@ -140,25 +142,19 @@ async def task_jid(self, jid):
|
||||||
jid : str
|
jid : str
|
||||||
Jabber ID.
|
Jabber ID.
|
||||||
"""
|
"""
|
||||||
enabled = await initdb(
|
db_file = get_pathname_to_database(jid)
|
||||||
jid,
|
enabled = await get_settings_value(db_file, "enabled")
|
||||||
get_settings_value,
|
|
||||||
"enabled"
|
|
||||||
)
|
|
||||||
# print(await current_time(), "enabled", enabled, jid)
|
# print(await current_time(), "enabled", enabled, jid)
|
||||||
if enabled:
|
if enabled:
|
||||||
# NOTE Perhaps we want to utilize super with keyword
|
# NOTE Perhaps we want to utilize super with keyword
|
||||||
# arguments in order to know what tasks to initiate.
|
# arguments in order to know what tasks to initiate.
|
||||||
task_manager[jid] = {}
|
task_manager[jid] = {}
|
||||||
task_manager[jid]["check"] = asyncio.create_task(
|
task_manager[jid]["check"] = asyncio.create_task(
|
||||||
check_updates(jid)
|
check_updates(jid))
|
||||||
)
|
|
||||||
task_manager[jid]["status"] = asyncio.create_task(
|
task_manager[jid]["status"] = asyncio.create_task(
|
||||||
send_status(self, jid)
|
send_status(self, jid))
|
||||||
)
|
|
||||||
task_manager[jid]["interval"] = asyncio.create_task(
|
task_manager[jid]["interval"] = asyncio.create_task(
|
||||||
send_update(self, jid)
|
send_update(self, jid))
|
||||||
)
|
|
||||||
await task_manager[jid]["check"]
|
await task_manager[jid]["check"]
|
||||||
await task_manager[jid]["status"]
|
await task_manager[jid]["status"]
|
||||||
await task_manager[jid]["interval"]
|
await task_manager[jid]["interval"]
|
||||||
|
@ -200,37 +196,22 @@ async def send_update(self, jid, num=None):
|
||||||
"""
|
"""
|
||||||
# print("Starting send_update()")
|
# print("Starting send_update()")
|
||||||
# print(jid)
|
# print(jid)
|
||||||
enabled = await initdb(
|
db_file = get_pathname_to_database(jid)
|
||||||
jid,
|
enabled = await get_settings_value(db_file, "enabled")
|
||||||
get_settings_value,
|
|
||||||
"enabled"
|
|
||||||
)
|
|
||||||
if enabled:
|
if enabled:
|
||||||
new = await initdb(
|
new = await get_entry_unread(db_file, num)
|
||||||
jid,
|
|
||||||
get_entry_unread,
|
|
||||||
num
|
|
||||||
)
|
|
||||||
if new:
|
if new:
|
||||||
# TODO Add while loop to assure delivery.
|
# TODO Add while loop to assure delivery.
|
||||||
# print(await current_time(), ">>> ACT send_message",jid)
|
# print(await current_time(), ">>> ACT send_message",jid)
|
||||||
chat_type = await xmpp.Slixfeed.is_muc(self, jid)
|
chat_type = await utility.jid_type(self, jid)
|
||||||
# NOTE Do we need "if statement"? See NOTE at is_muc.
|
# NOTE Do we need "if statement"? See NOTE at is_muc.
|
||||||
if chat_type in ("chat", "groupchat"):
|
if chat_type in ("chat", "groupchat"):
|
||||||
xmpp.Slixfeed.send_message(
|
xmpp.Slixfeed.send_message(
|
||||||
self,
|
self, mto=jid, mbody=new, mtype=chat_type)
|
||||||
mto=jid,
|
|
||||||
mbody=new,
|
|
||||||
mtype=chat_type
|
|
||||||
)
|
|
||||||
# TODO Do not refresh task before
|
# TODO Do not refresh task before
|
||||||
# verifying that it was completed.
|
# verifying that it was completed.
|
||||||
await refresh_task(
|
await refresh_task(
|
||||||
self,
|
self, jid, send_update, "interval")
|
||||||
jid,
|
|
||||||
send_update,
|
|
||||||
"interval"
|
|
||||||
)
|
|
||||||
# interval = await initdb(
|
# interval = await initdb(
|
||||||
# jid,
|
# jid,
|
||||||
# get_settings_value,
|
# get_settings_value,
|
||||||
|
@ -269,20 +250,13 @@ async def send_status(self, jid):
|
||||||
"""
|
"""
|
||||||
# print(await current_time(), "> SEND STATUS",jid)
|
# print(await current_time(), "> SEND STATUS",jid)
|
||||||
status_text="🤖️ Slixfeed RSS News Bot"
|
status_text="🤖️ Slixfeed RSS News Bot"
|
||||||
enabled = await initdb(
|
db_file = get_pathname_to_database(jid)
|
||||||
jid,
|
enabled = await get_settings_value(db_file, "enabled")
|
||||||
get_settings_value,
|
|
||||||
"enabled"
|
|
||||||
)
|
|
||||||
if not enabled:
|
if not enabled:
|
||||||
status_mode = "xa"
|
status_mode = "xa"
|
||||||
status_text = "📫️ Send \"Start\" to receive updates"
|
status_text = "📫️ Send \"Start\" to receive updates"
|
||||||
else:
|
else:
|
||||||
feeds = await initdb(
|
feeds = get_number_of_items(db_file, "feeds")
|
||||||
jid,
|
|
||||||
get_number_of_items,
|
|
||||||
"feeds"
|
|
||||||
)
|
|
||||||
# print(await current_time(), jid, "has", feeds, "feeds")
|
# print(await current_time(), jid, "has", feeds, "feeds")
|
||||||
if not feeds:
|
if not feeds:
|
||||||
print(">>> not feeds:", feeds, "jid:", jid)
|
print(">>> not feeds:", feeds, "jid:", jid)
|
||||||
|
@ -291,10 +265,7 @@ async def send_status(self, jid):
|
||||||
"📪️ Send a URL from a blog or a news website"
|
"📪️ Send a URL from a blog or a news website"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
unread = await initdb(
|
unread = await get_number_of_entries_unread(db_file)
|
||||||
jid,
|
|
||||||
get_number_of_entries_unread
|
|
||||||
)
|
|
||||||
if unread:
|
if unread:
|
||||||
status_mode = "chat"
|
status_mode = "chat"
|
||||||
status_text = (
|
status_text = (
|
||||||
|
@ -321,12 +292,7 @@ async def send_status(self, jid):
|
||||||
)
|
)
|
||||||
# await asyncio.sleep(60 * 20)
|
# await asyncio.sleep(60 * 20)
|
||||||
await refresh_task(
|
await refresh_task(
|
||||||
self,
|
self, jid, send_status, "status", "20")
|
||||||
jid,
|
|
||||||
send_status,
|
|
||||||
"status",
|
|
||||||
"20"
|
|
||||||
)
|
|
||||||
# loop.call_at(
|
# loop.call_at(
|
||||||
# loop.time() + 60 * 20,
|
# loop.time() + 60 * 20,
|
||||||
# loop.create_task,
|
# loop.create_task,
|
||||||
|
@ -349,11 +315,8 @@ async def refresh_task(self, jid, callback, key, val=None):
|
||||||
Value. The default is None.
|
Value. The default is None.
|
||||||
"""
|
"""
|
||||||
if not val:
|
if not val:
|
||||||
val = await initdb(
|
db_file = get_pathname_to_database(jid)
|
||||||
jid,
|
val = await get_settings_value(db_file, key)
|
||||||
get_settings_value,
|
|
||||||
key
|
|
||||||
)
|
|
||||||
# if task_manager[jid][key]:
|
# if task_manager[jid][key]:
|
||||||
if jid in task_manager:
|
if jid in task_manager:
|
||||||
try:
|
try:
|
||||||
|
@ -401,8 +364,9 @@ async def check_updates(jid):
|
||||||
"""
|
"""
|
||||||
while True:
|
while True:
|
||||||
# print(await current_time(), "> CHCK UPDATE",jid)
|
# print(await current_time(), "> CHCK UPDATE",jid)
|
||||||
await initdb(jid, download_updates)
|
db_file = get_pathname_to_database(jid)
|
||||||
val = await get_value_default("check", "Settings")
|
await download_updates(db_file)
|
||||||
|
val = get_value_default("settings", "Settings", "check")
|
||||||
await asyncio.sleep(60 * float(val))
|
await asyncio.sleep(60 * float(val))
|
||||||
# Schedule to call this function again in 90 minutes
|
# Schedule to call this function again in 90 minutes
|
||||||
# loop.call_at(
|
# loop.call_at(
|
||||||
|
@ -412,6 +376,55 @@ async def check_updates(jid):
|
||||||
# )
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
async def start_tasks(self, presence):
|
||||||
|
# print("def presence_available", presence["from"].bare)
|
||||||
|
jid = presence["from"].bare
|
||||||
|
if jid not in self.boundjid.bare:
|
||||||
|
await clean_tasks_xmpp(
|
||||||
|
jid, ["interval", "status", "check"])
|
||||||
|
await start_tasks_xmpp(
|
||||||
|
self, jid, ["interval", "status", "check"])
|
||||||
|
# await task_jid(self, jid)
|
||||||
|
# main_task.extend([asyncio.create_task(task_jid(jid))])
|
||||||
|
# print(main_task)
|
||||||
|
|
||||||
|
|
||||||
|
async def stop_tasks(self, presence):
|
||||||
|
if not self.boundjid.bare:
|
||||||
|
jid = presence["from"].bare
|
||||||
|
print(">>> unavailable:", jid)
|
||||||
|
await clean_tasks_xmpp(
|
||||||
|
jid, ["interval", "status", "check"])
|
||||||
|
|
||||||
|
|
||||||
|
async def check_readiness(self, presence):
|
||||||
|
"""
|
||||||
|
Begin tasks if available, otherwise eliminate tasks.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
presence : str
|
||||||
|
XML stanza .
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None.
|
||||||
|
"""
|
||||||
|
# print("def check_readiness", presence["from"].bare, presence["type"])
|
||||||
|
# # available unavailable away (chat) dnd xa
|
||||||
|
# print(">>> type", presence["type"], presence["from"].bare)
|
||||||
|
# # away chat dnd xa
|
||||||
|
# print(">>> show", presence["show"], presence["from"].bare)
|
||||||
|
|
||||||
|
jid = presence["from"].bare
|
||||||
|
if presence["show"] in ("away", "dnd", "xa"):
|
||||||
|
print(">>> away, dnd, xa:", jid)
|
||||||
|
await clean_tasks_xmpp(
|
||||||
|
jid, ["interval"])
|
||||||
|
await start_tasks_xmpp(
|
||||||
|
self, jid, ["status", "check"])
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
NOTE
|
NOTE
|
||||||
This is an older system, utilizing local storage instead of XMPP presence.
|
This is an older system, utilizing local storage instead of XMPP presence.
|
||||||
|
|
|
@ -31,7 +31,7 @@ from urllib.parse import (
|
||||||
# proxies.yaml. Perhaps a better practice would be to have
|
# proxies.yaml. Perhaps a better practice would be to have
|
||||||
# them separated. File proxies.yaml will remainas is in order
|
# them separated. File proxies.yaml will remainas is in order
|
||||||
# to be coordinated with the dataset of project LibRedirect.
|
# to be coordinated with the dataset of project LibRedirect.
|
||||||
async def replace_hostname(url, url_type):
|
def replace_hostname(url, url_type):
|
||||||
"""
|
"""
|
||||||
Replace hostname.
|
Replace hostname.
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ async def replace_hostname(url, url_type):
|
||||||
pathname = parted_url.path
|
pathname = parted_url.path
|
||||||
queries = parted_url.query
|
queries = parted_url.query
|
||||||
fragment = parted_url.fragment
|
fragment = parted_url.fragment
|
||||||
proxies = await config.get_list("proxies.yaml")
|
proxies = config.get_list("proxies.yaml")
|
||||||
for proxy in proxies:
|
for proxy in proxies:
|
||||||
proxy = proxies[proxy]
|
proxy = proxies[proxy]
|
||||||
if hostname in proxy["hostname"] and url_type in proxy["type"]:
|
if hostname in proxy["hostname"] and url_type in proxy["type"]:
|
||||||
|
@ -72,7 +72,7 @@ async def replace_hostname(url, url_type):
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
async def remove_tracking_parameters(url):
|
def remove_tracking_parameters(url):
|
||||||
"""
|
"""
|
||||||
Remove queries with tracking parameters.
|
Remove queries with tracking parameters.
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ async def remove_tracking_parameters(url):
|
||||||
pathname = parted_url.path
|
pathname = parted_url.path
|
||||||
queries = parse_qs(parted_url.query)
|
queries = parse_qs(parted_url.query)
|
||||||
fragment = parted_url.fragment
|
fragment = parted_url.fragment
|
||||||
trackers = await config.get_list("queries.yaml")
|
trackers = config.get_list("queries.yaml")
|
||||||
trackers = trackers["trackers"]
|
trackers = trackers["trackers"]
|
||||||
for tracker in trackers:
|
for tracker in trackers:
|
||||||
if tracker in queries: del queries[tracker]
|
if tracker in queries: del queries[tracker]
|
||||||
|
|
109
slixfeed/utility.py
Normal file
109
slixfeed/utility.py
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
1) is_feed: Look into the type ("atom", "rss2" etc.)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
|
|
||||||
|
def log_as_markdown(timestamp, filename, jid, message):
|
||||||
|
"""
|
||||||
|
Log message to file.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
timestamp : str
|
||||||
|
Time stamp.
|
||||||
|
filename : str
|
||||||
|
Jabber ID as name of file.
|
||||||
|
jid : str
|
||||||
|
Jabber ID.
|
||||||
|
message : str
|
||||||
|
Message content.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None.
|
||||||
|
|
||||||
|
"""
|
||||||
|
with open(filename + '.md', 'a') as file:
|
||||||
|
# entry = "{} {}:\n{}\n\n".format(timestamp, jid, message)
|
||||||
|
entry = (
|
||||||
|
"## {}\n"
|
||||||
|
"### {}\n\n"
|
||||||
|
"{}\n\n").format(jid, timestamp, message)
|
||||||
|
file.write(entry)
|
||||||
|
|
||||||
|
|
||||||
|
def get_title(url, feed):
|
||||||
|
"""
|
||||||
|
Get title of feed.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
url : str
|
||||||
|
URL.
|
||||||
|
feed : dict
|
||||||
|
Parsed feed document.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
title : str
|
||||||
|
Title or URL hostname.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
title = feed["feed"]["title"]
|
||||||
|
except:
|
||||||
|
title = urlsplit(url).netloc
|
||||||
|
if not title:
|
||||||
|
title = urlsplit(url).netloc
|
||||||
|
return title
|
||||||
|
|
||||||
|
|
||||||
|
def is_feed(url, feed):
|
||||||
|
"""
|
||||||
|
Determine whether document is feed or not.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
url : str
|
||||||
|
URL.
|
||||||
|
feed : dict
|
||||||
|
Parsed feed.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
val : boolean
|
||||||
|
True or False.
|
||||||
|
"""
|
||||||
|
msg = None
|
||||||
|
if not feed.entries:
|
||||||
|
try:
|
||||||
|
feed["feed"]["title"]
|
||||||
|
val = True
|
||||||
|
msg = (
|
||||||
|
"Empty feed for {}"
|
||||||
|
).format(url)
|
||||||
|
except:
|
||||||
|
val = False
|
||||||
|
msg = (
|
||||||
|
"No entries nor title for {}"
|
||||||
|
).format(url)
|
||||||
|
elif feed.bozo:
|
||||||
|
val = False
|
||||||
|
msg = (
|
||||||
|
"Bozo detected for {}"
|
||||||
|
).format(url)
|
||||||
|
else:
|
||||||
|
val = True
|
||||||
|
msg = (
|
||||||
|
"Good feed for {}"
|
||||||
|
).format(url)
|
||||||
|
print(msg)
|
||||||
|
return val
|
|
@ -40,6 +40,13 @@ async def add(self, muc_jid):
|
||||||
# await self['xep_0402'].publish(bm)
|
# await self['xep_0402'].publish(bm)
|
||||||
|
|
||||||
|
|
||||||
|
async def get(self):
|
||||||
|
result = await self.plugin['xep_0048'].get_bookmarks()
|
||||||
|
bookmarks = result["private"]["bookmarks"]
|
||||||
|
conferences = bookmarks["conferences"]
|
||||||
|
return conferences
|
||||||
|
|
||||||
|
|
||||||
async def remove(self, muc_jid):
|
async def remove(self, muc_jid):
|
||||||
result = await self.plugin['xep_0048'].get_bookmarks()
|
result = await self.plugin['xep_0048'].get_bookmarks()
|
||||||
bookmarks = result["private"]["bookmarks"]
|
bookmarks = result["private"]["bookmarks"]
|
||||||
|
|
|
@ -48,7 +48,7 @@ NOTE
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from slixfeed.config import add_to_list, initdb, get_list, remove_from_list
|
from slixfeed.config import add_to_list, get_list, remove_from_list
|
||||||
import slixfeed.fetch as fetcher
|
import slixfeed.fetch as fetcher
|
||||||
from slixfeed.datetime import current_time
|
from slixfeed.datetime import current_time
|
||||||
import logging
|
import logging
|
||||||
|
@ -69,10 +69,13 @@ import xmltodict
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
import slixfeed.xmpp.compose as compose
|
|
||||||
import slixfeed.xmpp.connect as connect
|
import slixfeed.xmpp.connect as connect
|
||||||
|
import slixfeed.xmpp.process as process
|
||||||
import slixfeed.xmpp.muc as muc
|
import slixfeed.xmpp.muc as muc
|
||||||
|
import slixfeed.xmpp.roster as roster
|
||||||
|
import slixfeed.xmpp.state as state
|
||||||
import slixfeed.xmpp.status as status
|
import slixfeed.xmpp.status as status
|
||||||
|
import slixfeed.xmpp.utility as utility
|
||||||
|
|
||||||
main_task = []
|
main_task = []
|
||||||
jid_tasker = {}
|
jid_tasker = {}
|
||||||
|
@ -108,42 +111,39 @@ class Slixfeed(slixmpp.ClientXMPP):
|
||||||
# and the XML streams are ready for use. We want to
|
# and the XML streams are ready for use. We want to
|
||||||
# listen for this event so that we we can initialize
|
# listen for this event so that we we can initialize
|
||||||
# our roster.
|
# our roster.
|
||||||
self.add_event_handler("session_start", self.start_session)
|
self.add_event_handler("session_start", self.on_session_start)
|
||||||
self.add_event_handler("session_resumed", self.start_session)
|
self.add_event_handler("session_resumed", self.on_session_resumed)
|
||||||
self.add_event_handler("session_start", self.autojoin_muc)
|
|
||||||
self.add_event_handler("session_resumed", self.autojoin_muc)
|
|
||||||
self.add_event_handler("got_offline", print("got_offline"))
|
self.add_event_handler("got_offline", print("got_offline"))
|
||||||
# self.add_event_handler("got_online", self.check_readiness)
|
# self.add_event_handler("got_online", self.check_readiness)
|
||||||
self.add_event_handler("changed_status", self.check_readiness)
|
self.add_event_handler("changed_status", self.on_changed_status)
|
||||||
self.add_event_handler("presence_unavailable", self.stop_tasks)
|
self.add_event_handler("presence_available", self.on_presence_available)
|
||||||
|
self.add_event_handler("presence_unavailable", self.on_presence_unavailable)
|
||||||
|
|
||||||
# self.add_event_handler("changed_subscription", self.check_subscription)
|
self.add_event_handler("changed_subscription", self.on_changed_subscription)
|
||||||
|
|
||||||
# self.add_event_handler("chatstate_active", self.check_chatstate_active)
|
self.add_event_handler("chatstate_active", self.on_chatstate_active)
|
||||||
# self.add_event_handler("chatstate_gone", self.check_chatstate_gone)
|
self.add_event_handler("chatstate_gone", self.on_chatstate_gone)
|
||||||
self.add_event_handler("chatstate_composing", self.check_chatstate_composing)
|
self.add_event_handler("chatstate_composing", self.check_chatstate_composing)
|
||||||
self.add_event_handler("chatstate_paused", self.check_chatstate_paused)
|
self.add_event_handler("chatstate_paused", self.check_chatstate_paused)
|
||||||
|
|
||||||
# The message event is triggered whenever a message
|
# The message event is triggered whenever a message
|
||||||
# stanza is received. Be aware that that includes
|
# stanza is received. Be aware that that includes
|
||||||
# MUC messages and error messages.
|
# MUC messages and error messages.
|
||||||
self.add_event_handler("message", self.process_message)
|
self.add_event_handler("message", self.on_message)
|
||||||
self.add_event_handler("message", self.settle)
|
|
||||||
|
|
||||||
self.add_event_handler("groupchat_invite", self.process_muc_invite) # XEP_0045
|
self.add_event_handler("groupchat_invite", self.on_groupchat_invite) # XEP_0045
|
||||||
self.add_event_handler("groupchat_direct_invite", self.process_muc_invite) # XEP_0249
|
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("groupchat_message", self.message)
|
||||||
|
|
||||||
# self.add_event_handler("disconnected", self.reconnect)
|
# self.add_event_handler("disconnected", self.reconnect)
|
||||||
# self.add_event_handler("disconnected", self.inspect_connection)
|
# self.add_event_handler("disconnected", self.inspect_connection)
|
||||||
|
|
||||||
self.add_event_handler("reactions", self.reactions)
|
self.add_event_handler("reactions", self.on_reactions)
|
||||||
self.add_event_handler("presence_available", self.presence_available)
|
self.add_event_handler("presence_error", self.on_presence_error)
|
||||||
self.add_event_handler("presence_error", self.presence_error)
|
self.add_event_handler("presence_subscribe", self.on_presence_subscribe)
|
||||||
self.add_event_handler("presence_subscribe", self.presence_subscribe)
|
self.add_event_handler("presence_subscribed", self.on_presence_subscribed)
|
||||||
self.add_event_handler("presence_subscribed", self.presence_subscribed)
|
self.add_event_handler("presence_unsubscribe", self.on_presence_unsubscribe)
|
||||||
self.add_event_handler("presence_unsubscribe", self.presence_unsubscribe)
|
self.add_event_handler("presence_unsubscribed", self.on_presence_unsubscribed)
|
||||||
self.add_event_handler("presence_unsubscribed", self.unsubscribe)
|
|
||||||
|
|
||||||
# Initialize event loop
|
# Initialize event loop
|
||||||
# self.loop = asyncio.get_event_loop()
|
# self.loop = asyncio.get_event_loop()
|
||||||
|
@ -154,110 +154,110 @@ class Slixfeed(slixmpp.ClientXMPP):
|
||||||
self.add_event_handler("connection_failed", self.on_connection_failed)
|
self.add_event_handler("connection_failed", self.on_connection_failed)
|
||||||
self.add_event_handler("session_end", self.on_session_end)
|
self.add_event_handler("session_end", self.on_session_end)
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
FIXME
|
async def on_groupchat_invite(self, message):
|
||||||
|
print("on_groupchat_invite")
|
||||||
This function is triggered even when status is dnd/away/xa.
|
await muc.accept_invitation(self, message)
|
||||||
This results in sending messages even when status is dnd/away/xa.
|
|
||||||
See function check_readiness.
|
|
||||||
|
|
||||||
NOTE
|
|
||||||
|
|
||||||
The issue occurs only at bot startup.
|
|
||||||
Once status is changed to dnd/away/xa, the interval stops - as expected.
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
Use "sleep()"
|
|
||||||
|
|
||||||
"""
|
|
||||||
async def presence_available(self, presence):
|
|
||||||
# print("def presence_available", presence["from"].bare)
|
|
||||||
jid = presence["from"].bare
|
|
||||||
print("presence_available", jid)
|
|
||||||
if jid not in self.boundjid.bare:
|
|
||||||
await task.clean_tasks_xmpp(
|
|
||||||
jid,
|
|
||||||
["interval", "status", "check"]
|
|
||||||
)
|
|
||||||
await task.start_tasks_xmpp(
|
|
||||||
self,
|
|
||||||
jid,
|
|
||||||
["interval", "status", "check"]
|
|
||||||
)
|
|
||||||
# await task_jid(self, jid)
|
|
||||||
# main_task.extend([asyncio.create_task(task_jid(jid))])
|
|
||||||
# print(main_task)
|
|
||||||
|
|
||||||
async def stop_tasks(self, presence):
|
|
||||||
if not self.boundjid.bare:
|
|
||||||
jid = presence["from"].bare
|
|
||||||
print(">>> unavailable:", jid)
|
|
||||||
await task.clean_tasks_xmpp(
|
|
||||||
jid,
|
|
||||||
["interval", "status", "check"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def presence_error(self, presence):
|
async def on_groupchat_direct_invite(self, message):
|
||||||
print("presence_error")
|
print("on_groupchat_direct_invite")
|
||||||
print(presence)
|
await muc.accept_invitation(self, message)
|
||||||
|
|
||||||
async def presence_subscribe(self, presence):
|
|
||||||
print("presence_subscribe")
|
|
||||||
print(presence)
|
|
||||||
|
|
||||||
async def presence_subscribed(self, presence):
|
|
||||||
print("presence_subscribed")
|
|
||||||
print(presence)
|
|
||||||
|
|
||||||
async def reactions(self, message):
|
|
||||||
print("reactions")
|
|
||||||
print(message)
|
|
||||||
|
|
||||||
# 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 muc.join_groupchat(self, inviter, muc_jid)
|
|
||||||
|
|
||||||
|
|
||||||
async def autojoin_muc(self, event):
|
|
||||||
result = await self.plugin['xep_0048'].get_bookmarks()
|
|
||||||
bookmarks = result["private"]["bookmarks"]
|
|
||||||
conferences = bookmarks["conferences"]
|
|
||||||
for conference in conferences:
|
|
||||||
if conference["autojoin"]:
|
|
||||||
muc_jid = conference["jid"]
|
|
||||||
print(current_time(), "Autojoining groupchat", muc_jid)
|
|
||||||
self.plugin['xep_0045'].join_muc(
|
|
||||||
muc_jid,
|
|
||||||
self.nick,
|
|
||||||
# If a room password is needed, use:
|
|
||||||
# password=the_room_password,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def on_session_end(self, event):
|
async def on_session_end(self, event):
|
||||||
print(current_time(), "Session ended. Attempting to reconnect.")
|
message = "Session has ended."
|
||||||
print(event)
|
await connect.recover_connection(self, event, message)
|
||||||
logging.warning("Session ended. Attempting to reconnect.")
|
|
||||||
await connect.recover_connection(self, event)
|
|
||||||
|
|
||||||
|
|
||||||
async def on_connection_failed(self, event):
|
async def on_connection_failed(self, event):
|
||||||
print(current_time(), "Connection failed. Attempting to reconnect.")
|
message = "Connection has failed."
|
||||||
print(event)
|
await connect.recover_connection(self, event, message)
|
||||||
logging.warning("Connection failed. Attempting to reconnect.")
|
|
||||||
await connect.recover_connection(self, event)
|
|
||||||
|
async def on_session_start(self, event):
|
||||||
|
await process.event(self, event)
|
||||||
|
await muc.autojoin(self, event)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_session_resumed(self, event):
|
||||||
|
await process.event(self, event)
|
||||||
|
await muc.autojoin(self, event)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO Request for subscription
|
||||||
|
async def on_message(self, message):
|
||||||
|
jid = message["from"].bare
|
||||||
|
if "chat" == await utility.jid_type(self, jid):
|
||||||
|
await roster.add(self, jid)
|
||||||
|
await state.request(self, jid)
|
||||||
|
# chat_type = message["type"]
|
||||||
|
# message_body = message["body"]
|
||||||
|
# message_reply = message.reply
|
||||||
|
await process.message(self, message)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_changed_status(self, presence):
|
||||||
|
await task.check_readiness(self, presence)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO Request for subscription
|
||||||
|
async def on_presence_subscribe(self, presence):
|
||||||
|
jid = presence["from"].bare
|
||||||
|
await state.request(self, jid)
|
||||||
|
print("on_presence_subscribe")
|
||||||
|
print(presence)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_presence_subscribed(self, presence):
|
||||||
|
jid = presence["from"].bare
|
||||||
|
process.greet(self, jid)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_presence_available(self, presence):
|
||||||
|
await task.start_tasks(self, presence)
|
||||||
|
print("on_presence_available")
|
||||||
|
print(presence)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_presence_unsubscribed(self, presence):
|
||||||
|
await state.unsubscribed(self, presence)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_presence_unavailable(self, presence):
|
||||||
|
await task.stop_tasks(self, presence)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_changed_subscription(self, presence):
|
||||||
|
print("on_changed_subscription")
|
||||||
|
print(presence)
|
||||||
|
jid = presence["from"].bare
|
||||||
|
# breakpoint()
|
||||||
|
|
||||||
|
|
||||||
|
async def on_presence_unsubscribe(self, presence):
|
||||||
|
print("on_presence_unsubscribe")
|
||||||
|
print(presence)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_presence_error(self, presence):
|
||||||
|
print("on_presence_error")
|
||||||
|
print(presence)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_reactions(self, message):
|
||||||
|
print("on_reactions")
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_chatstate_active(self, message):
|
||||||
|
print("on_chatstate_active")
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_chatstate_gone(self, message):
|
||||||
|
print("on_chatstate_gone")
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
async def check_chatstate_composing(self, message):
|
async def check_chatstate_composing(self, message):
|
||||||
|
@ -286,266 +286,3 @@ class Slixfeed(slixmpp.ClientXMPP):
|
||||||
20
|
20
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def check_readiness(self, presence):
|
|
||||||
"""
|
|
||||||
If available, begin tasks.
|
|
||||||
If unavailable, eliminate tasks.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
presence : str
|
|
||||||
XML stanza .
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
None.
|
|
||||||
"""
|
|
||||||
# print("def check_readiness", presence["from"].bare, presence["type"])
|
|
||||||
# # available unavailable away (chat) dnd xa
|
|
||||||
# print(">>> type", presence["type"], presence["from"].bare)
|
|
||||||
# # away chat dnd xa
|
|
||||||
# print(">>> show", presence["show"], presence["from"].bare)
|
|
||||||
|
|
||||||
jid = presence["from"].bare
|
|
||||||
if presence["show"] in ("away", "dnd", "xa"):
|
|
||||||
print(">>> away, dnd, xa:", jid)
|
|
||||||
await task.clean_tasks_xmpp(
|
|
||||||
jid,
|
|
||||||
["interval"]
|
|
||||||
)
|
|
||||||
await task.start_tasks_xmpp(
|
|
||||||
self,
|
|
||||||
jid,
|
|
||||||
["status", "check"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def resume(self, event):
|
|
||||||
print("def resume")
|
|
||||||
print(event)
|
|
||||||
self.send_presence()
|
|
||||||
await self.get_roster()
|
|
||||||
|
|
||||||
|
|
||||||
async def start_session(self, event):
|
|
||||||
"""
|
|
||||||
Process the session_start event.
|
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
|
||||||
requesting the roster and broadcasting an initial
|
|
||||||
presence stanza.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
event -- An empty dictionary. The session_start
|
|
||||||
event does not provide any additional
|
|
||||||
data.
|
|
||||||
"""
|
|
||||||
print("def start_session")
|
|
||||||
print(event)
|
|
||||||
self.send_presence()
|
|
||||||
await self.get_roster()
|
|
||||||
|
|
||||||
# for task in main_task:
|
|
||||||
# task.cancel()
|
|
||||||
|
|
||||||
# Deprecated in favour of event "presence_available"
|
|
||||||
# if not main_task:
|
|
||||||
# await select_file()
|
|
||||||
|
|
||||||
|
|
||||||
async def is_muc(self, jid):
|
|
||||||
"""
|
|
||||||
Check whether a JID is of MUC.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
jid : str
|
|
||||||
Jabber ID.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
str
|
|
||||||
"chat" or "groupchat.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
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 "groupchat"
|
|
||||||
# TODO elif <feature var='jabber:iq:gateway'/>
|
|
||||||
# NOTE Is it needed? We do not interact with gateways or services
|
|
||||||
else:
|
|
||||||
return "chat"
|
|
||||||
# TODO Test whether this exception is realized
|
|
||||||
except IqTimeout as e:
|
|
||||||
messages = [
|
|
||||||
("Timeout IQ"),
|
|
||||||
("IQ Stanza:", e),
|
|
||||||
("Jabber ID:", jid)
|
|
||||||
]
|
|
||||||
for message in messages:
|
|
||||||
print(current_time(), message)
|
|
||||||
logging.error(current_time(), message)
|
|
||||||
|
|
||||||
|
|
||||||
async def settle(self, msg):
|
|
||||||
"""
|
|
||||||
Add JID to roster and settle subscription.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
jid : str
|
|
||||||
Jabber ID.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
None.
|
|
||||||
"""
|
|
||||||
jid = msg["from"].bare
|
|
||||||
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=self.nick
|
|
||||||
)
|
|
||||||
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=self.nick
|
|
||||||
)
|
|
||||||
self.send_message(
|
|
||||||
mto=jid,
|
|
||||||
# mtype="headline",
|
|
||||||
msubject="RSS News Bot",
|
|
||||||
mbody=(
|
|
||||||
"Accept subscription request to receive updates."
|
|
||||||
),
|
|
||||||
mfrom=self.boundjid.bare,
|
|
||||||
mnick=self.nick
|
|
||||||
)
|
|
||||||
self.send_presence(
|
|
||||||
pto=jid,
|
|
||||||
pfrom=self.boundjid.bare,
|
|
||||||
# Accept symbol 🉑️ 👍️ ✍
|
|
||||||
pstatus=(
|
|
||||||
"✒️ Accept subscription request to receive updates."
|
|
||||||
),
|
|
||||||
# ptype="subscribe",
|
|
||||||
pnick=self.nick
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def presence_unsubscribe(self, presence):
|
|
||||||
print("presence_unsubscribe")
|
|
||||||
print(presence)
|
|
||||||
|
|
||||||
|
|
||||||
async def unsubscribe(self, presence):
|
|
||||||
jid = presence["from"].bare
|
|
||||||
self.send_presence(
|
|
||||||
pto=jid,
|
|
||||||
pfrom=self.boundjid.bare,
|
|
||||||
pstatus="🖋️ Subscribe to receive updates",
|
|
||||||
pnick=self.nick
|
|
||||||
)
|
|
||||||
self.send_message(
|
|
||||||
mto=jid,
|
|
||||||
mbody="You have been unsubscribed."
|
|
||||||
)
|
|
||||||
self.update_roster(
|
|
||||||
jid,
|
|
||||||
subscription="remove"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def process_message(self, message):
|
|
||||||
"""
|
|
||||||
Process incoming message stanzas. Be aware that this also
|
|
||||||
includes MUC messages and error messages. It is usually
|
|
||||||
a good practice to check the messages's type before
|
|
||||||
processing or sending replies.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
message : str
|
|
||||||
The received message stanza. See the documentation
|
|
||||||
for stanza objects and the Message stanza to see
|
|
||||||
how it may be used.
|
|
||||||
"""
|
|
||||||
# print("message")
|
|
||||||
# print(message)
|
|
||||||
if message["type"] in ("chat", "groupchat", "normal"):
|
|
||||||
jid = message["from"].bare
|
|
||||||
if message["type"] == "groupchat":
|
|
||||||
# nick = message["from"][message["from"].index("/")+1:]
|
|
||||||
nick = str(message["from"])
|
|
||||||
nick = nick[nick.index("/")+1:]
|
|
||||||
if (message['muc']['nick'] == self.nick or
|
|
||||||
not message["body"].startswith("!")):
|
|
||||||
return
|
|
||||||
# token = await initdb(
|
|
||||||
# jid,
|
|
||||||
# get_settings_value,
|
|
||||||
# "token"
|
|
||||||
# )
|
|
||||||
# if token == "accepted":
|
|
||||||
# operator = await initdb(
|
|
||||||
# jid,
|
|
||||||
# get_settings_value,
|
|
||||||
# "masters"
|
|
||||||
# )
|
|
||||||
# if operator:
|
|
||||||
# if nick not in operator:
|
|
||||||
# return
|
|
||||||
# approved = False
|
|
||||||
jid_full = str(message["from"])
|
|
||||||
role = self.plugin['xep_0045'].get_jid_property(
|
|
||||||
jid,
|
|
||||||
jid_full[jid_full.index("/")+1:],
|
|
||||||
"role")
|
|
||||||
if role != "moderator":
|
|
||||||
return
|
|
||||||
# if role == "moderator":
|
|
||||||
# approved = True
|
|
||||||
# TODO Implement a list of temporary operators
|
|
||||||
# Once an operator is appointed, the control would last
|
|
||||||
# untile the participant has been disconnected from MUC
|
|
||||||
# An operator is a function to appoint non moderators.
|
|
||||||
# Changing nickname is fine and consist of no problem.
|
|
||||||
# if not approved:
|
|
||||||
# operator = await initdb(
|
|
||||||
# jid,
|
|
||||||
# get_settings_value,
|
|
||||||
# "masters"
|
|
||||||
# )
|
|
||||||
# if operator:
|
|
||||||
# if nick in operator:
|
|
||||||
# approved = True
|
|
||||||
# if not approved:
|
|
||||||
# return
|
|
||||||
|
|
||||||
# # Begin processing new JID
|
|
||||||
# # Deprecated in favour of event "presence_available"
|
|
||||||
# db_dir = get_default_dbdir()
|
|
||||||
# os.chdir(db_dir)
|
|
||||||
# if jid + ".db" not in os.listdir():
|
|
||||||
# await task_jid(jid)
|
|
||||||
|
|
||||||
await compose.message(self, jid, message)
|
|
||||||
|
|
|
@ -2,698 +2,137 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
TODO Port functions insert_feed, remove_feed, get_entry_unread
|
||||||
TODO
|
|
||||||
|
|
||||||
1) Deprecate "add" (see above) and make it interactive.
|
|
||||||
Slixfeed: Do you still want to add this URL to subscription list?
|
|
||||||
See: case _ if message_lowercase.startswith("add"):
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from slixfeed.config import add_to_list, initdb, get_list, remove_from_list
|
import slixfeed.xmpp.bookmark as bookmark
|
||||||
from slixfeed.datetime import current_time
|
from slixfeed.url import remove_tracking_parameters, replace_hostname
|
||||||
import slixfeed.fetch as fetcher
|
|
||||||
import slixfeed.sqlite as sqlite
|
|
||||||
import slixfeed.task as task
|
|
||||||
import slixfeed.url as uri
|
|
||||||
import slixfeed.xmpp.status as status
|
|
||||||
import slixfeed.xmpp.text as text
|
|
||||||
|
|
||||||
async def message(self, jid, message):
|
|
||||||
action = None
|
|
||||||
message_text = " ".join(message["body"].split())
|
|
||||||
if message["type"] == "groupchat":
|
|
||||||
message_text = message_text[1:]
|
|
||||||
message_lowercase = message_text.lower()
|
|
||||||
|
|
||||||
print(current_time(), "ACCOUNT: " + str(message["from"]))
|
def list_search_results(query, results):
|
||||||
print(current_time(), "COMMAND:", message_text)
|
results_list = (
|
||||||
|
"Search results for '{}':\n\n```"
|
||||||
|
).format(query)
|
||||||
|
counter = 0
|
||||||
|
for result in results:
|
||||||
|
counter += 1
|
||||||
|
results_list += (
|
||||||
|
"\n{}\n{}\n"
|
||||||
|
).format(str(result[0]), str(result[1]))
|
||||||
|
if counter:
|
||||||
|
return results_list + "```\nTotal of {} results".format(counter)
|
||||||
|
else:
|
||||||
|
return "No results were found for: {}".format(query)
|
||||||
|
|
||||||
match message_lowercase:
|
|
||||||
case "commands":
|
|
||||||
action = text.print_cmd()
|
|
||||||
case "help":
|
|
||||||
action = text.print_help()
|
|
||||||
case "info":
|
|
||||||
action = text.print_info()
|
|
||||||
case _ if message_lowercase in [
|
|
||||||
"greetings", "hallo", "hello", "hey",
|
|
||||||
"hi", "hola", "holla", "hollo"]:
|
|
||||||
action = (
|
|
||||||
"Greeting!\n"
|
|
||||||
"I'm Slixfeed, an RSS News Bot!\n"
|
|
||||||
"Send \"help\" for instructions."
|
|
||||||
)
|
|
||||||
# print("task_manager[jid]")
|
|
||||||
# print(task_manager[jid])
|
|
||||||
await self.get_roster()
|
|
||||||
print("roster 1")
|
|
||||||
print(self.client_roster)
|
|
||||||
print("roster 2")
|
|
||||||
print(self.client_roster.keys())
|
|
||||||
print("jid")
|
|
||||||
print(jid)
|
|
||||||
await self.autojoin_muc()
|
|
||||||
|
|
||||||
# case _ if message_lowercase.startswith("activate"):
|
def list_feeds_by_query(query, results):
|
||||||
# if message["type"] == "groupchat":
|
results_list = (
|
||||||
# acode = message[9:]
|
"Feeds containing '{}':\n\n```"
|
||||||
# token = await initdb(
|
).format(query)
|
||||||
# jid,
|
counter = 0
|
||||||
# get_settings_value,
|
for result in results:
|
||||||
# "token"
|
counter += 1
|
||||||
# )
|
results_list += (
|
||||||
# if int(acode) == token:
|
"\nName : {}"
|
||||||
# await initdb(
|
"\nURL : {}"
|
||||||
# jid,
|
"\nIndex : {}"
|
||||||
# set_settings_value,
|
"\nMode : {}"
|
||||||
# ["masters", nick]
|
"\n"
|
||||||
# )
|
).format(str(result[0]), str(result[1]),
|
||||||
# await initdb(
|
str(result[2]), str(result[3]))
|
||||||
# jid,
|
if counter:
|
||||||
# set_settings_value,
|
return results_list + "\n```\nTotal of {} feeds".format(counter)
|
||||||
# ["token", "accepted"]
|
else:
|
||||||
# )
|
return "No feeds were found for: {}".format(query)
|
||||||
# action = "{}, your are in command.".format(nick)
|
|
||||||
# else:
|
|
||||||
# action = "Activation code is not valid."
|
def list_statistics(values):
|
||||||
# else:
|
"""
|
||||||
# action = "This command is valid for groupchat only."
|
Return table statistics.
|
||||||
case _ if message_lowercase.startswith("add"):
|
|
||||||
message_text = message_text[4:]
|
Parameters
|
||||||
url = message_text.split(" ")[0]
|
----------
|
||||||
title = " ".join(message_text.split(" ")[1:])
|
db_file : str
|
||||||
if url.startswith("http"):
|
Path to database file.
|
||||||
action = await initdb(
|
|
||||||
jid,
|
Returns
|
||||||
fetcher.add_feed_no_check,
|
-------
|
||||||
[url, title]
|
msg : str
|
||||||
)
|
Statistics as message.
|
||||||
old = await initdb(
|
"""
|
||||||
jid,
|
msg = (
|
||||||
sqlite.get_settings_value,
|
"```"
|
||||||
"old"
|
"\nSTATISTICS\n"
|
||||||
)
|
"News items : {}/{}\n"
|
||||||
if old:
|
"News sources : {}/{}\n"
|
||||||
await task.clean_tasks_xmpp(
|
"\nOPTIONS\n"
|
||||||
jid,
|
"Items to archive : {}\n"
|
||||||
["status"]
|
"Update interval : {}\n"
|
||||||
)
|
"Items per update : {}\n"
|
||||||
# await send_status(jid)
|
"Operation status : {}\n"
|
||||||
await task.start_tasks_xmpp(
|
"```"
|
||||||
self,
|
).format(values[0], values[1], values[2], values[3],
|
||||||
jid,
|
values[4], values[5], values[6], values[7])
|
||||||
["status"]
|
return msg
|
||||||
)
|
|
||||||
else:
|
|
||||||
await initdb(
|
async def list_last_entries(results, num):
|
||||||
jid,
|
titles_list = "Recent {} titles:\n\n```".format(num)
|
||||||
sqlite.mark_source_as_read,
|
counter = 0
|
||||||
url
|
for result in results:
|
||||||
)
|
counter += 1
|
||||||
else:
|
titles_list += (
|
||||||
action = "Missing URL."
|
"\n{}\n{}\n"
|
||||||
case _ if message_lowercase.startswith("allow +"):
|
).format(str(result[0]), str(result[1]))
|
||||||
key = "filter-" + message_text[:5]
|
if counter:
|
||||||
val = message_text[7:]
|
titles_list += "```\n"
|
||||||
if val:
|
return titles_list
|
||||||
keywords = await initdb(
|
else:
|
||||||
jid,
|
return "There are no news at the moment."
|
||||||
sqlite.get_filters_value,
|
|
||||||
key
|
|
||||||
)
|
async def list_feeds(results):
|
||||||
val = await add_to_list(
|
feeds_list = "\nList of subscriptions:\n\n```\n"
|
||||||
val,
|
counter = 0
|
||||||
keywords
|
for result in results:
|
||||||
)
|
counter += 1
|
||||||
await initdb(
|
feeds_list += (
|
||||||
jid,
|
"Name : {}\n"
|
||||||
sqlite.set_filters_value,
|
"Address : {}\n"
|
||||||
[key, val]
|
"Updated : {}\n"
|
||||||
)
|
"Status : {}\n"
|
||||||
action = (
|
"ID : {}\n"
|
||||||
"Approved keywords\n"
|
"\n"
|
||||||
"```\n{}\n```"
|
).format(str(result[0]), str(result[1]), str(result[2]),
|
||||||
).format(val)
|
str(result[3]), str(result[4]))
|
||||||
else:
|
if counter:
|
||||||
action = "Missing keywords."
|
return feeds_list + (
|
||||||
case _ if message_lowercase.startswith("allow -"):
|
"```\nTotal of {} subscriptions.\n"
|
||||||
key = "filter-" + message_text[:5]
|
).format(counter)
|
||||||
val = message_text[7:]
|
else:
|
||||||
if val:
|
msg = (
|
||||||
keywords = await initdb(
|
"List of subscriptions is empty.\n"
|
||||||
jid,
|
"To add feed, send a URL\n"
|
||||||
sqlite.get_filters_value,
|
"Try these:\n"
|
||||||
key
|
# TODO Pick random from featured/recommended
|
||||||
)
|
"https://reclaimthenet.org/feed/"
|
||||||
val = await remove_from_list(
|
)
|
||||||
val,
|
return msg
|
||||||
keywords
|
|
||||||
)
|
|
||||||
await initdb(
|
async def list_bookmarks(self):
|
||||||
jid,
|
conferences = bookmark.get(self)
|
||||||
sqlite.set_filters_value,
|
groupchat_list = "\nList of groupchats:\n\n```\n"
|
||||||
[key, val]
|
counter = 0
|
||||||
)
|
for conference in conferences:
|
||||||
action = (
|
counter += 1
|
||||||
"Approved keywords\n"
|
groupchat_list += (
|
||||||
"```\n{}\n```"
|
"{}\n"
|
||||||
).format(val)
|
"\n"
|
||||||
else:
|
).format(
|
||||||
action = "Missing keywords."
|
conference["jid"]
|
||||||
case _ if message_lowercase.startswith("archive"):
|
|
||||||
key = message_text[:7]
|
|
||||||
val = message_text[8:]
|
|
||||||
if val:
|
|
||||||
try:
|
|
||||||
if int(val) > 500:
|
|
||||||
action = "Value may not be greater than 500."
|
|
||||||
else:
|
|
||||||
await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.set_settings_value,
|
|
||||||
[key, val]
|
|
||||||
)
|
|
||||||
action = (
|
|
||||||
"Maximum archived items has been set to {}."
|
|
||||||
).format(val)
|
|
||||||
except:
|
|
||||||
action = "Enter a numeric value only."
|
|
||||||
else:
|
|
||||||
action = "Missing value."
|
|
||||||
case _ if message_lowercase.startswith("deny +"):
|
|
||||||
key = "filter-" + message_text[:4]
|
|
||||||
val = message_text[6:]
|
|
||||||
if val:
|
|
||||||
keywords = await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.get_filters_value,
|
|
||||||
key
|
|
||||||
)
|
|
||||||
val = await add_to_list(
|
|
||||||
val,
|
|
||||||
keywords
|
|
||||||
)
|
|
||||||
await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.set_filters_value,
|
|
||||||
[key, val]
|
|
||||||
)
|
|
||||||
action = (
|
|
||||||
"Rejected keywords\n"
|
|
||||||
"```\n{}\n```"
|
|
||||||
).format(val)
|
|
||||||
else:
|
|
||||||
action = "Missing keywords."
|
|
||||||
case _ if message_lowercase.startswith("deny -"):
|
|
||||||
key = "filter-" + message_text[:4]
|
|
||||||
val = message_text[6:]
|
|
||||||
if val:
|
|
||||||
keywords = await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.get_filters_value,
|
|
||||||
key
|
|
||||||
)
|
|
||||||
val = await remove_from_list(
|
|
||||||
val,
|
|
||||||
keywords
|
|
||||||
)
|
|
||||||
await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.set_filters_value,
|
|
||||||
[key, val]
|
|
||||||
)
|
|
||||||
action = (
|
|
||||||
"Rejected keywords\n"
|
|
||||||
"```\n{}\n```"
|
|
||||||
).format(val)
|
|
||||||
else:
|
|
||||||
action = "Missing keywords."
|
|
||||||
case _ if (message_lowercase.startswith("gemini") or
|
|
||||||
message_lowercase.startswith("gopher:")):
|
|
||||||
action = "Gemini and Gopher are not supported yet."
|
|
||||||
case _ if (message_lowercase.startswith("http") or
|
|
||||||
message_lowercase.startswith("feed:")):
|
|
||||||
url = message_text
|
|
||||||
await task.clean_tasks_xmpp(
|
|
||||||
jid,
|
|
||||||
["status"]
|
|
||||||
)
|
)
|
||||||
status_message = (
|
groupchat_list += (
|
||||||
"📫️ Processing request to fetch data from {}"
|
"```\nTotal of {} groupchats.\n"
|
||||||
).format(url)
|
).format(counter)
|
||||||
status.process_task_message(self, jid, status_message)
|
return groupchat_list
|
||||||
if url.startswith("feed:"):
|
|
||||||
url = uri.feed_to_http(url)
|
|
||||||
# url_alt = await uri.replace_hostname(url, "feed")
|
|
||||||
# if url_alt:
|
|
||||||
# url = url_alt
|
|
||||||
url = (await uri.replace_hostname(url, "feed")) or url
|
|
||||||
action = await initdb(
|
|
||||||
jid,
|
|
||||||
fetcher.add_feed,
|
|
||||||
url
|
|
||||||
)
|
|
||||||
await task.start_tasks_xmpp(
|
|
||||||
self,
|
|
||||||
jid,
|
|
||||||
["status"]
|
|
||||||
)
|
|
||||||
# action = "> " + message + "\n" + action
|
|
||||||
# FIXME Make the taskhandler to update status message
|
|
||||||
# await refresh_task(
|
|
||||||
# self,
|
|
||||||
# jid,
|
|
||||||
# send_status,
|
|
||||||
# "status",
|
|
||||||
# 20
|
|
||||||
# )
|
|
||||||
# NOTE This would show the number of new unread entries
|
|
||||||
old = await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.get_settings_value,
|
|
||||||
"old"
|
|
||||||
)
|
|
||||||
if old:
|
|
||||||
await task.clean_tasks_xmpp(
|
|
||||||
jid,
|
|
||||||
["status"]
|
|
||||||
)
|
|
||||||
# await send_status(jid)
|
|
||||||
await task.start_tasks_xmpp(
|
|
||||||
self,
|
|
||||||
jid,
|
|
||||||
["status"]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.mark_source_as_read,
|
|
||||||
url
|
|
||||||
)
|
|
||||||
case _ if message_lowercase.startswith("feeds"):
|
|
||||||
query = message_text[6:]
|
|
||||||
if query:
|
|
||||||
if len(query) > 3:
|
|
||||||
action = await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.search_feeds,
|
|
||||||
query
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
action = (
|
|
||||||
"Enter at least 4 characters to search"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
action = await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.list_feeds
|
|
||||||
)
|
|
||||||
case "goodbye":
|
|
||||||
if message["type"] == "groupchat":
|
|
||||||
await self.close_muc(jid)
|
|
||||||
else:
|
|
||||||
action = "This command is valid for groupchat only."
|
|
||||||
case _ if message_lowercase.startswith("interval"):
|
|
||||||
# FIXME
|
|
||||||
# The following error occurs only upon first attempt to set interval.
|
|
||||||
# /usr/lib/python3.11/asyncio/events.py:73: RuntimeWarning: coroutine 'Slixfeed.send_update' was never awaited
|
|
||||||
# self._args = None
|
|
||||||
# RuntimeWarning: Enable tracemalloc to get the object allocation traceback
|
|
||||||
key = message_text[:8]
|
|
||||||
val = message_text[9:]
|
|
||||||
if val:
|
|
||||||
# action = (
|
|
||||||
# "Updates will be sent every {} minutes."
|
|
||||||
# ).format(action)
|
|
||||||
await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.set_settings_value,
|
|
||||||
[key, val]
|
|
||||||
)
|
|
||||||
# NOTE Perhaps this should be replaced
|
|
||||||
# by functions clean and start
|
|
||||||
await task.refresh_task(
|
|
||||||
self,
|
|
||||||
jid,
|
|
||||||
task.send_update,
|
|
||||||
key,
|
|
||||||
val
|
|
||||||
)
|
|
||||||
action = (
|
|
||||||
"Updates will be sent every {} minutes."
|
|
||||||
).format(val)
|
|
||||||
else:
|
|
||||||
action = "Missing value."
|
|
||||||
case _ if message_lowercase.startswith("join"):
|
|
||||||
muc = uri.check_xmpp_uri(message_text[5:])
|
|
||||||
if muc:
|
|
||||||
"TODO probe JID and confirm it's a groupchat"
|
|
||||||
await self.join_muc(jid, muc)
|
|
||||||
action = (
|
|
||||||
"Joined groupchat {}"
|
|
||||||
).format(message_text)
|
|
||||||
else:
|
|
||||||
action = (
|
|
||||||
"> {}\nXMPP URI is not valid."
|
|
||||||
).format(message_text)
|
|
||||||
case _ if message_lowercase.startswith("length"):
|
|
||||||
key = message_text[:6]
|
|
||||||
val = message_text[7:]
|
|
||||||
if val:
|
|
||||||
try:
|
|
||||||
val = int(val)
|
|
||||||
await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.set_settings_value,
|
|
||||||
[key, val]
|
|
||||||
)
|
|
||||||
if val == 0:
|
|
||||||
action = (
|
|
||||||
"Summary length limit is disabled."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
action = (
|
|
||||||
"Summary maximum length "
|
|
||||||
"is set to {} characters."
|
|
||||||
).format(val)
|
|
||||||
except:
|
|
||||||
action = "Enter a numeric value only."
|
|
||||||
else:
|
|
||||||
action = "Missing value."
|
|
||||||
# case _ if message_lowercase.startswith("mastership"):
|
|
||||||
# key = message_text[:7]
|
|
||||||
# val = message_text[11:]
|
|
||||||
# if val:
|
|
||||||
# names = await initdb(
|
|
||||||
# jid,
|
|
||||||
# get_settings_value,
|
|
||||||
# key
|
|
||||||
# )
|
|
||||||
# val = await add_to_list(
|
|
||||||
# val,
|
|
||||||
# names
|
|
||||||
# )
|
|
||||||
# await initdb(
|
|
||||||
# jid,
|
|
||||||
# set_settings_value,
|
|
||||||
# [key, val]
|
|
||||||
# )
|
|
||||||
# action = (
|
|
||||||
# "Operators\n"
|
|
||||||
# "```\n{}\n```"
|
|
||||||
# ).format(val)
|
|
||||||
# else:
|
|
||||||
# action = "Missing value."
|
|
||||||
case "new":
|
|
||||||
await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.set_settings_value,
|
|
||||||
["old", 0]
|
|
||||||
)
|
|
||||||
action = (
|
|
||||||
"Only new items of newly added feeds will be sent."
|
|
||||||
)
|
|
||||||
# TODO Will you add support for number of messages?
|
|
||||||
case "next":
|
|
||||||
# num = message_text[5:]
|
|
||||||
await task.clean_tasks_xmpp(
|
|
||||||
jid,
|
|
||||||
["interval", "status"]
|
|
||||||
)
|
|
||||||
await task.start_tasks_xmpp(
|
|
||||||
self,
|
|
||||||
jid,
|
|
||||||
["interval", "status"]
|
|
||||||
)
|
|
||||||
# await refresh_task(
|
|
||||||
# self,
|
|
||||||
# jid,
|
|
||||||
# send_update,
|
|
||||||
# "interval",
|
|
||||||
# num
|
|
||||||
# )
|
|
||||||
# await refresh_task(
|
|
||||||
# self,
|
|
||||||
# jid,
|
|
||||||
# send_status,
|
|
||||||
# "status",
|
|
||||||
# 20
|
|
||||||
# )
|
|
||||||
# await refresh_task(jid, key, val)
|
|
||||||
case "old":
|
|
||||||
await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.set_settings_value,
|
|
||||||
["old", 1]
|
|
||||||
)
|
|
||||||
action = (
|
|
||||||
"All items of newly added feeds will be sent."
|
|
||||||
)
|
|
||||||
case _ if message_lowercase.startswith("quantum"):
|
|
||||||
key = message_text[:7]
|
|
||||||
val = message_text[8:]
|
|
||||||
if val:
|
|
||||||
try:
|
|
||||||
val = int(val)
|
|
||||||
# action = (
|
|
||||||
# "Every update will contain {} news items."
|
|
||||||
# ).format(action)
|
|
||||||
await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.set_settings_value,
|
|
||||||
[key, val]
|
|
||||||
)
|
|
||||||
action = (
|
|
||||||
"Next update will contain {} news items."
|
|
||||||
).format(val)
|
|
||||||
except:
|
|
||||||
action = "Enter a numeric value only."
|
|
||||||
else:
|
|
||||||
action = "Missing value."
|
|
||||||
case "random":
|
|
||||||
# TODO /questions/2279706/select-random-row-from-a-sqlite-table
|
|
||||||
# NOTE sqlitehandler.get_entry_unread
|
|
||||||
action = "Updates will be sent by random order."
|
|
||||||
case _ if message_lowercase.startswith("read"):
|
|
||||||
data = message_text[5:]
|
|
||||||
data = data.split()
|
|
||||||
url = data[0]
|
|
||||||
await task.clean_tasks_xmpp(
|
|
||||||
jid,
|
|
||||||
["status"]
|
|
||||||
)
|
|
||||||
status_message = (
|
|
||||||
"📫️ Processing request to fetch data from {}"
|
|
||||||
).format(url)
|
|
||||||
status.process_task_message(self, jid, status_message)
|
|
||||||
if url.startswith("feed:"):
|
|
||||||
url = uri.feed_to_http(url)
|
|
||||||
url = (await uri.replace_hostname(url, "feed")) or url
|
|
||||||
match len(data):
|
|
||||||
case 1:
|
|
||||||
if url.startswith("http"):
|
|
||||||
action = await fetcher.view_feed(url)
|
|
||||||
else:
|
|
||||||
action = "Missing URL."
|
|
||||||
case 2:
|
|
||||||
num = data[1]
|
|
||||||
if url.startswith("http"):
|
|
||||||
action = await fetcher.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."
|
|
||||||
)
|
|
||||||
await task.start_tasks_xmpp(
|
|
||||||
self,
|
|
||||||
jid,
|
|
||||||
["status"]
|
|
||||||
)
|
|
||||||
case _ if message_lowercase.startswith("recent"):
|
|
||||||
num = message_text[7:]
|
|
||||||
if num:
|
|
||||||
try:
|
|
||||||
num = int(num)
|
|
||||||
if num < 1 or num > 50:
|
|
||||||
action = "Value must be ranged from 1 to 50."
|
|
||||||
else:
|
|
||||||
action = await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.last_entries,
|
|
||||||
num
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
action = "Enter a numeric value only."
|
|
||||||
else:
|
|
||||||
action = "Missing value."
|
|
||||||
# NOTE Should people be asked for numeric value?
|
|
||||||
case _ if message_lowercase.startswith("remove"):
|
|
||||||
ix = message_text[7:]
|
|
||||||
if ix:
|
|
||||||
action = await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.remove_feed,
|
|
||||||
ix
|
|
||||||
)
|
|
||||||
# await refresh_task(
|
|
||||||
# self,
|
|
||||||
# jid,
|
|
||||||
# send_status,
|
|
||||||
# "status",
|
|
||||||
# 20
|
|
||||||
# )
|
|
||||||
await task.clean_tasks_xmpp(
|
|
||||||
jid,
|
|
||||||
["status"]
|
|
||||||
)
|
|
||||||
await task.start_tasks_xmpp(
|
|
||||||
self,
|
|
||||||
jid,
|
|
||||||
["status"]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
action = "Missing feed ID."
|
|
||||||
case _ if message_lowercase.startswith("reset"):
|
|
||||||
source = message_text[6:]
|
|
||||||
await task.clean_tasks_xmpp(
|
|
||||||
jid,
|
|
||||||
["status"]
|
|
||||||
)
|
|
||||||
status_message = (
|
|
||||||
"📫️ Marking entries as read..."
|
|
||||||
)
|
|
||||||
status.process_task_message(self, jid, status_message)
|
|
||||||
if source:
|
|
||||||
await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.mark_source_as_read,
|
|
||||||
source
|
|
||||||
)
|
|
||||||
action = (
|
|
||||||
"All entries of {} have been "
|
|
||||||
"marked as read.".format(source)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.mark_all_as_read
|
|
||||||
)
|
|
||||||
action = "All entries have been marked as read."
|
|
||||||
await task.start_tasks_xmpp(
|
|
||||||
self,
|
|
||||||
jid,
|
|
||||||
["status"]
|
|
||||||
)
|
|
||||||
case _ if message_lowercase.startswith("search"):
|
|
||||||
query = message_text[7:]
|
|
||||||
if query:
|
|
||||||
if len(query) > 1:
|
|
||||||
action = await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.search_entries,
|
|
||||||
query
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
action = (
|
|
||||||
"Enter at least 2 characters to search"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
action = "Missing search query."
|
|
||||||
case "start":
|
|
||||||
# action = "Updates are enabled."
|
|
||||||
key = "enabled"
|
|
||||||
val = 1
|
|
||||||
await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.set_settings_value,
|
|
||||||
[key, val]
|
|
||||||
)
|
|
||||||
# asyncio.create_task(task_jid(self, jid))
|
|
||||||
await task.start_tasks_xmpp(
|
|
||||||
self,
|
|
||||||
jid,
|
|
||||||
["interval", "status", "check"]
|
|
||||||
)
|
|
||||||
action = "Updates are enabled."
|
|
||||||
# print(current_time(), "task_manager[jid]")
|
|
||||||
# print(task_manager[jid])
|
|
||||||
case "stats":
|
|
||||||
action = await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.statistics
|
|
||||||
)
|
|
||||||
case _ if message_lowercase.startswith("status "):
|
|
||||||
ix = message_text[7:]
|
|
||||||
action = await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.toggle_status,
|
|
||||||
ix
|
|
||||||
)
|
|
||||||
case "stop":
|
|
||||||
# FIXME
|
|
||||||
# The following error occurs only upon first attempt to stop.
|
|
||||||
# /usr/lib/python3.11/asyncio/events.py:73: RuntimeWarning: coroutine 'Slixfeed.send_update' was never awaited
|
|
||||||
# self._args = None
|
|
||||||
# RuntimeWarning: Enable tracemalloc to get the object allocation traceback
|
|
||||||
# action = "Updates are disabled."
|
|
||||||
# try:
|
|
||||||
# # task_manager[jid]["check"].cancel()
|
|
||||||
# # task_manager[jid]["status"].cancel()
|
|
||||||
# task_manager[jid]["interval"].cancel()
|
|
||||||
# key = "enabled"
|
|
||||||
# val = 0
|
|
||||||
# action = await initdb(
|
|
||||||
# jid,
|
|
||||||
# set_settings_value,
|
|
||||||
# [key, val]
|
|
||||||
# )
|
|
||||||
# except:
|
|
||||||
# action = "Updates are already disabled."
|
|
||||||
# # print("Updates are already disabled. Nothing to do.")
|
|
||||||
# # await send_status(jid)
|
|
||||||
key = "enabled"
|
|
||||||
val = 0
|
|
||||||
await initdb(
|
|
||||||
jid,
|
|
||||||
sqlite.set_settings_value,
|
|
||||||
[key, val]
|
|
||||||
)
|
|
||||||
await task.clean_tasks_xmpp(
|
|
||||||
jid,
|
|
||||||
["interval", "status"]
|
|
||||||
)
|
|
||||||
self.send_presence(
|
|
||||||
pshow="xa",
|
|
||||||
pstatus="💡️ Send \"Start\" to receive Jabber news",
|
|
||||||
pto=jid,
|
|
||||||
)
|
|
||||||
action = "Updates are disabled."
|
|
||||||
case "support":
|
|
||||||
# TODO Send an invitation.
|
|
||||||
action = "Join xmpp:slixfeed@chat.woodpeckersnest.space?join"
|
|
||||||
case _ if message_lowercase.startswith("xmpp:"):
|
|
||||||
muc = uri.check_xmpp_uri(message_text)
|
|
||||||
if muc:
|
|
||||||
"TODO probe JID and confirm it's a groupchat"
|
|
||||||
await self.join_muc(jid, muc)
|
|
||||||
action = (
|
|
||||||
"Joined groupchat {}"
|
|
||||||
).format(message_text)
|
|
||||||
else:
|
|
||||||
action = (
|
|
||||||
"> {}\nXMPP URI is not valid."
|
|
||||||
).format(message_text)
|
|
||||||
case _:
|
|
||||||
action = (
|
|
||||||
"Unknown command. "
|
|
||||||
"Press \"help\" for list of commands"
|
|
||||||
)
|
|
||||||
# TODO Use message correction here
|
|
||||||
# NOTE This might not be a good idea if
|
|
||||||
# commands are sent one close to the next
|
|
||||||
if action: message.reply(action).send()
|
|
|
@ -1,11 +1,15 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from slixfeed.config import get_value
|
||||||
from slixfeed.datetime import current_time
|
from slixfeed.datetime import current_time
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
async def recover_connection(self, event):
|
async def recover_connection(self, event, message):
|
||||||
|
logging.warning(message)
|
||||||
|
print(current_time(), message, "Attempting to reconnect.")
|
||||||
self.connection_attempts += 1
|
self.connection_attempts += 1
|
||||||
# if self.connection_attempts <= self.max_connection_attempts:
|
# if self.connection_attempts <= self.max_connection_attempts:
|
||||||
# self.reconnect(wait=5.0) # wait a bit before attempting to reconnect
|
# self.reconnect(wait=5.0) # wait a bit before attempting to reconnect
|
||||||
|
@ -13,7 +17,7 @@ async def recover_connection(self, event):
|
||||||
# print(current_time(),"Maximum connection attempts exceeded.")
|
# print(current_time(),"Maximum connection attempts exceeded.")
|
||||||
# logging.error("Maximum connection attempts exceeded.")
|
# logging.error("Maximum connection attempts exceeded.")
|
||||||
print(current_time(), "Attempt number", self.connection_attempts)
|
print(current_time(), "Attempt number", self.connection_attempts)
|
||||||
seconds = 30
|
seconds = int(get_value("accounts", "XMPP Connect", "reconnect_timeout"))
|
||||||
print(current_time(), "Next attempt within", seconds, "seconds")
|
print(current_time(), "Next attempt within", seconds, "seconds")
|
||||||
# NOTE asyncio.sleep doesn't interval as expected
|
# NOTE asyncio.sleep doesn't interval as expected
|
||||||
# await asyncio.sleep(seconds)
|
# await asyncio.sleep(seconds)
|
||||||
|
|
|
@ -14,8 +14,39 @@ TODO
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import slixfeed.xmpp.bookmark as bookmark
|
import slixfeed.xmpp.bookmark as bookmark
|
||||||
|
import slixfeed.xmpp.process as process
|
||||||
|
from slixfeed.datetime import current_time
|
||||||
|
|
||||||
async def join_groupchat(self, inviter, muc_jid):
|
# 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 accept_invitation(self, message):
|
||||||
|
# operator muc_chat
|
||||||
|
inviter = message["from"].bare
|
||||||
|
muc_jid = message['groupchat_invite']['jid']
|
||||||
|
await join(self, inviter, muc_jid)
|
||||||
|
|
||||||
|
|
||||||
|
async def autojoin(self, event):
|
||||||
|
result = await self.plugin['xep_0048'].get_bookmarks()
|
||||||
|
bookmarks = result["private"]["bookmarks"]
|
||||||
|
conferences = bookmarks["conferences"]
|
||||||
|
for conference in conferences:
|
||||||
|
if conference["autojoin"]:
|
||||||
|
muc_jid = conference["jid"]
|
||||||
|
print(current_time(), "Autojoining groupchat", muc_jid)
|
||||||
|
self.plugin['xep_0045'].join_muc(
|
||||||
|
muc_jid,
|
||||||
|
self.nick,
|
||||||
|
# If a room password is needed, use:
|
||||||
|
# password=the_room_password,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def join(self, inviter, muc_jid):
|
||||||
# token = await initdb(
|
# token = await initdb(
|
||||||
# muc_jid,
|
# muc_jid,
|
||||||
# get_settings_value,
|
# get_settings_value,
|
||||||
|
@ -43,23 +74,10 @@ async def join_groupchat(self, inviter, muc_jid):
|
||||||
# password=the_room_password,
|
# password=the_room_password,
|
||||||
)
|
)
|
||||||
await bookmark.add(self, muc_jid)
|
await bookmark.add(self, muc_jid)
|
||||||
messages = [
|
process.greet(self, muc_jid, chat_type="groupchat")
|
||||||
"Greetings!",
|
|
||||||
"I'm {}, the news anchor.".format(self.nick),
|
|
||||||
"My job is to bring you the latest news "
|
|
||||||
"from sources you provide me with.",
|
|
||||||
"You may always reach me via "
|
|
||||||
"xmpp:{}?message".format(self.boundjid.bare)
|
|
||||||
]
|
|
||||||
for message in messages:
|
|
||||||
self.send_message(
|
|
||||||
mto=muc_jid,
|
|
||||||
mbody=message,
|
|
||||||
mtype="groupchat"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def close_groupchat(self, muc_jid):
|
async def leave(self, muc_jid):
|
||||||
messages = [
|
messages = [
|
||||||
"Whenever you need an RSS service again, "
|
"Whenever you need an RSS service again, "
|
||||||
"please don’t hesitate to contact me.",
|
"please don’t hesitate to contact me.",
|
||||||
|
|
757
slixfeed/xmpp/process.py
Normal file
757
slixfeed/xmpp/process.py
Normal file
|
@ -0,0 +1,757 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
1) Deprecate "add" (see above) and make it interactive.
|
||||||
|
Slixfeed: Do you still want to add this URL to subscription list?
|
||||||
|
See: case _ if message_lowercase.startswith("add"):
|
||||||
|
|
||||||
|
2) If subscription is inadequate (see state.request), send a message that says so.
|
||||||
|
|
||||||
|
elif not self.client_roster[jid]["to"]:
|
||||||
|
breakpoint()
|
||||||
|
message.reply("Share online status to activate bot.").send()
|
||||||
|
return
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from slixfeed.config import (
|
||||||
|
add_to_list,
|
||||||
|
get_default_dbdir,
|
||||||
|
get_value,
|
||||||
|
get_pathname_to_database,
|
||||||
|
remove_from_list)
|
||||||
|
from slixfeed.datetime import current_time
|
||||||
|
import slixfeed.fetch as fetcher
|
||||||
|
import slixfeed.sqlite as sqlite
|
||||||
|
import slixfeed.task as task
|
||||||
|
import slixfeed.utility as utility
|
||||||
|
import slixfeed.url as uri
|
||||||
|
import slixfeed.xmpp.bookmark as bookmark
|
||||||
|
import slixfeed.xmpp.compose as compose
|
||||||
|
import slixfeed.xmpp.muc as groupchat
|
||||||
|
import slixfeed.xmpp.status as status
|
||||||
|
import slixfeed.xmpp.text as text
|
||||||
|
|
||||||
|
|
||||||
|
async def event(self, event):
|
||||||
|
"""
|
||||||
|
Process the session_start event.
|
||||||
|
|
||||||
|
Typical actions for the session_start event are
|
||||||
|
requesting the roster and broadcasting an initial
|
||||||
|
presence stanza.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
event -- An empty dictionary. The session_start
|
||||||
|
event does not provide any additional
|
||||||
|
data.
|
||||||
|
"""
|
||||||
|
self.send_presence()
|
||||||
|
await self.get_roster()
|
||||||
|
|
||||||
|
# for task in main_task:
|
||||||
|
# task.cancel()
|
||||||
|
|
||||||
|
# Deprecated in favour of event "presence_available"
|
||||||
|
# if not main_task:
|
||||||
|
# await select_file()
|
||||||
|
|
||||||
|
|
||||||
|
async def message(self, message):
|
||||||
|
"""
|
||||||
|
Process incoming message stanzas. Be aware that this also
|
||||||
|
includes MUC messages and error messages. It is usually
|
||||||
|
a good practice to check the messages's type before
|
||||||
|
processing or sending replies.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
message : str
|
||||||
|
The received message stanza. See the documentation
|
||||||
|
for stanza objects and the Message stanza to see
|
||||||
|
how it may be used.
|
||||||
|
"""
|
||||||
|
# print("message")
|
||||||
|
# print(message)
|
||||||
|
if message["type"] in ("chat", "groupchat", "normal"):
|
||||||
|
jid = message["from"].bare
|
||||||
|
if message["type"] == "groupchat":
|
||||||
|
# nick = message["from"][message["from"].index("/")+1:]
|
||||||
|
nick = str(message["from"])
|
||||||
|
nick = nick[nick.index("/")+1:]
|
||||||
|
if (message['muc']['nick'] == self.nick or
|
||||||
|
not message["body"].startswith("!")):
|
||||||
|
return
|
||||||
|
# token = await initdb(
|
||||||
|
# jid,
|
||||||
|
# get_settings_value,
|
||||||
|
# "token"
|
||||||
|
# )
|
||||||
|
# if token == "accepted":
|
||||||
|
# operator = await initdb(
|
||||||
|
# jid,
|
||||||
|
# get_settings_value,
|
||||||
|
# "masters"
|
||||||
|
# )
|
||||||
|
# if operator:
|
||||||
|
# if nick not in operator:
|
||||||
|
# return
|
||||||
|
# approved = False
|
||||||
|
jid_full = str(message["from"])
|
||||||
|
role = self.plugin['xep_0045'].get_jid_property(
|
||||||
|
jid,
|
||||||
|
jid_full[jid_full.index("/")+1:],
|
||||||
|
"role")
|
||||||
|
if role != "moderator":
|
||||||
|
return
|
||||||
|
# if role == "moderator":
|
||||||
|
# approved = True
|
||||||
|
# TODO Implement a list of temporary operators
|
||||||
|
# Once an operator is appointed, the control would last
|
||||||
|
# untile the participant has been disconnected from MUC
|
||||||
|
# An operator is a function to appoint non moderators.
|
||||||
|
# Changing nickname is fine and consist of no problem.
|
||||||
|
# if not approved:
|
||||||
|
# operator = await initdb(
|
||||||
|
# jid,
|
||||||
|
# get_settings_value,
|
||||||
|
# "masters"
|
||||||
|
# )
|
||||||
|
# if operator:
|
||||||
|
# if nick in operator:
|
||||||
|
# approved = True
|
||||||
|
# if not approved:
|
||||||
|
# return
|
||||||
|
|
||||||
|
# # Begin processing new JID
|
||||||
|
# # Deprecated in favour of event "presence_available"
|
||||||
|
# db_dir = get_default_dbdir()
|
||||||
|
# os.chdir(db_dir)
|
||||||
|
# if jid + ".db" not in os.listdir():
|
||||||
|
# await task_jid(jid)
|
||||||
|
|
||||||
|
# await compose.message(self, jid, message)
|
||||||
|
|
||||||
|
message_text = " ".join(message["body"].split())
|
||||||
|
if message["type"] == "groupchat":
|
||||||
|
message_text = message_text[1:]
|
||||||
|
message_lowercase = message_text.lower()
|
||||||
|
|
||||||
|
print(current_time(), "ACCOUNT: " + str(message["from"]))
|
||||||
|
print(current_time(), "COMMAND:", message_text)
|
||||||
|
|
||||||
|
match message_lowercase:
|
||||||
|
case "breakpoint":
|
||||||
|
breakpoint()
|
||||||
|
case "commands":
|
||||||
|
response = text.print_cmd()
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case "help":
|
||||||
|
response = text.print_help()
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case "info":
|
||||||
|
response = text.print_info()
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase in [
|
||||||
|
"greetings", "hallo", "hello", "hey",
|
||||||
|
"hi", "hola", "holla", "hollo"]:
|
||||||
|
response = (
|
||||||
|
"Greeting!\n"
|
||||||
|
"I'm Slixfeed, an RSS News Bot!\n"
|
||||||
|
"Send \"help\" for instructions.\n"
|
||||||
|
)
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
# print("task_manager[jid]")
|
||||||
|
# print(task_manager[jid])
|
||||||
|
await self.get_roster()
|
||||||
|
print("roster 1")
|
||||||
|
print(self.client_roster)
|
||||||
|
print("roster 2")
|
||||||
|
print(self.client_roster.keys())
|
||||||
|
print("jid")
|
||||||
|
print(jid)
|
||||||
|
|
||||||
|
# case _ if message_lowercase.startswith("activate"):
|
||||||
|
# if message["type"] == "groupchat":
|
||||||
|
# acode = message[9:]
|
||||||
|
# token = await initdb(
|
||||||
|
# jid,
|
||||||
|
# get_settings_value,
|
||||||
|
# "token"
|
||||||
|
# )
|
||||||
|
# if int(acode) == token:
|
||||||
|
# await initdb(
|
||||||
|
# jid,
|
||||||
|
# set_settings_value,
|
||||||
|
# ["masters", nick]
|
||||||
|
# )
|
||||||
|
# await initdb(
|
||||||
|
# jid,
|
||||||
|
# set_settings_value,
|
||||||
|
# ["token", "accepted"]
|
||||||
|
# )
|
||||||
|
# response = "{}, your are in command.".format(nick)
|
||||||
|
# else:
|
||||||
|
# response = "Activation code is not valid."
|
||||||
|
# else:
|
||||||
|
# response = "This command is valid for groupchat only."
|
||||||
|
case _ if message_lowercase.startswith("add"):
|
||||||
|
message_text = message_text[4:]
|
||||||
|
url = message_text.split(" ")[0]
|
||||||
|
title = " ".join(message_text.split(" ")[1:])
|
||||||
|
if url.startswith("http"):
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
response = await fetcher.add_feed_no_check(db_file, [url, title])
|
||||||
|
old = await sqlite.get_settings_value(db_file, "old")
|
||||||
|
if old:
|
||||||
|
await task.clean_tasks_xmpp(jid, ["status"])
|
||||||
|
# await send_status(jid)
|
||||||
|
await task.start_tasks_xmpp(self, jid, ["status"])
|
||||||
|
else:
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
await sqlite.mark_source_as_read(db_file, url)
|
||||||
|
else:
|
||||||
|
response = "Missing URL."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase.startswith("allow +"):
|
||||||
|
key = "filter-" + message_text[:5]
|
||||||
|
val = message_text[7:]
|
||||||
|
if val:
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
keywords = await sqlite.get_filters_value(db_file, key)
|
||||||
|
val = await add_to_list(val, keywords)
|
||||||
|
await sqlite.set_filters_value(db_file, [key, val])
|
||||||
|
response = (
|
||||||
|
"Approved keywords\n"
|
||||||
|
"```\n{}\n```"
|
||||||
|
).format(val)
|
||||||
|
else:
|
||||||
|
response = "Missing keywords."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase.startswith("allow -"):
|
||||||
|
key = "filter-" + message_text[:5]
|
||||||
|
val = message_text[7:]
|
||||||
|
if val:
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
keywords = await sqlite.get_filters_value(db_file, key)
|
||||||
|
val = await remove_from_list(val, keywords)
|
||||||
|
await sqlite.set_filters_value(db_file, [key, val])
|
||||||
|
response = (
|
||||||
|
"Approved keywords\n"
|
||||||
|
"```\n{}\n```"
|
||||||
|
).format(val)
|
||||||
|
else:
|
||||||
|
response = "Missing keywords."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase.startswith("archive"):
|
||||||
|
key = message_text[:7]
|
||||||
|
val = message_text[8:]
|
||||||
|
if val:
|
||||||
|
try:
|
||||||
|
if int(val) > 500:
|
||||||
|
response = "Value may not be greater than 500."
|
||||||
|
else:
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
await sqlite.set_settings_value(db_file, [key, val])
|
||||||
|
response = (
|
||||||
|
"Maximum archived items has been set to {}."
|
||||||
|
).format(val)
|
||||||
|
except:
|
||||||
|
response = "Enter a numeric value only."
|
||||||
|
else:
|
||||||
|
response = "Missing value."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase.startswith("bookmark - "):
|
||||||
|
if jid == get_value("accounts", "XMPP", "operator"):
|
||||||
|
muc_jid = message_text[11:]
|
||||||
|
await bookmark.remove(self, muc_jid)
|
||||||
|
response = (
|
||||||
|
"Groupchat {} has been removed from bookmarks."
|
||||||
|
).format(muc_jid)
|
||||||
|
else:
|
||||||
|
response = (
|
||||||
|
"This response is restricted. "
|
||||||
|
"Type: removing bookmarks."
|
||||||
|
)
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case "bookmarks":
|
||||||
|
response = await compose.list_bookmarks(self)
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase.startswith("deny +"):
|
||||||
|
key = "filter-" + message_text[:4]
|
||||||
|
val = message_text[6:]
|
||||||
|
if val:
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
keywords = await sqlite.get_filters_value(db_file, key)
|
||||||
|
val = await add_to_list(val, keywords)
|
||||||
|
await sqlite.set_filters_value(db_file, [key, val])
|
||||||
|
response = (
|
||||||
|
"Rejected keywords\n"
|
||||||
|
"```\n{}\n```"
|
||||||
|
).format(val)
|
||||||
|
else:
|
||||||
|
response = "Missing keywords."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase.startswith("deny -"):
|
||||||
|
key = "filter-" + message_text[:4]
|
||||||
|
val = message_text[6:]
|
||||||
|
if val:
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
keywords = await sqlite.get_filters_value(db_file, key)
|
||||||
|
val = await remove_from_list(val, keywords)
|
||||||
|
await sqlite.set_filters_value(db_file, [key, val])
|
||||||
|
response = (
|
||||||
|
"Rejected keywords\n"
|
||||||
|
"```\n{}\n```"
|
||||||
|
).format(val)
|
||||||
|
else:
|
||||||
|
response = "Missing keywords."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if (message_lowercase.startswith("gemini") or
|
||||||
|
message_lowercase.startswith("gopher:")):
|
||||||
|
response = "Gemini and Gopher are not supported yet."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if (message_lowercase.startswith("http") or
|
||||||
|
message_lowercase.startswith("feed:")):
|
||||||
|
url = message_text
|
||||||
|
await task.clean_tasks_xmpp(jid, ["status"])
|
||||||
|
status_type = "dnd"
|
||||||
|
status_message = (
|
||||||
|
"📫️ Processing request to fetch data from {}"
|
||||||
|
).format(url)
|
||||||
|
send_status_message(self, jid, status_type, status_message)
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
if url.startswith("feed:"):
|
||||||
|
url = uri.feed_to_http(url)
|
||||||
|
# url_alt = await uri.replace_hostname(url, "feed")
|
||||||
|
# if url_alt:
|
||||||
|
# url = url_alt
|
||||||
|
url = (uri.replace_hostname(url, "feed")) or url
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
response = await fetcher.add_feed(db_file, url)
|
||||||
|
await task.start_tasks_xmpp(self, jid, ["status"])
|
||||||
|
# response = "> " + message + "\n" + response
|
||||||
|
# FIXME Make the taskhandler to update status message
|
||||||
|
# await refresh_task(
|
||||||
|
# self,
|
||||||
|
# jid,
|
||||||
|
# send_status,
|
||||||
|
# "status",
|
||||||
|
# 20
|
||||||
|
# )
|
||||||
|
# NOTE This would show the number of new unread entries
|
||||||
|
old = await sqlite.get_settings_value(db_file, "old")
|
||||||
|
if old:
|
||||||
|
await task.clean_tasks_xmpp(jid, ["status"])
|
||||||
|
# await send_status(jid)
|
||||||
|
await task.start_tasks_xmpp(self, jid, ["status"])
|
||||||
|
else:
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
await sqlite.mark_source_as_read(db_file, url)
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase.startswith("feeds"):
|
||||||
|
query = message_text[6:]
|
||||||
|
if query:
|
||||||
|
if len(query) > 3:
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
result = await sqlite.search_feeds(db_file, query)
|
||||||
|
response = compose.list_feeds_by_query(query, result)
|
||||||
|
else:
|
||||||
|
response = (
|
||||||
|
"Enter at least 4 characters to search"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
result = await sqlite.get_feeds(db_file)
|
||||||
|
response = compose.list_feeds(result)
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case "goodbye":
|
||||||
|
if message["type"] == "groupchat":
|
||||||
|
await groupchat.leave(self, jid)
|
||||||
|
else:
|
||||||
|
response = "This command is valid for groupchat only."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase.startswith("interval"):
|
||||||
|
# FIXME
|
||||||
|
# The following error occurs only upon first attempt to set interval.
|
||||||
|
# /usr/lib/python3.11/asyncio/events.py:73: RuntimeWarning: coroutine 'Slixfeed.send_update' was never awaited
|
||||||
|
# self._args = None
|
||||||
|
# RuntimeWarning: Enable tracemalloc to get the object allocation traceback
|
||||||
|
key = message_text[:8]
|
||||||
|
val = message_text[9:]
|
||||||
|
if val:
|
||||||
|
# response = (
|
||||||
|
# "Updates will be sent every {} minutes."
|
||||||
|
# ).format(response)
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
await sqlite.set_settings_value(db_file, [key, val])
|
||||||
|
# NOTE Perhaps this should be replaced
|
||||||
|
# by functions clean and start
|
||||||
|
await task.refresh_task(
|
||||||
|
self, jid, task.send_update, key, val)
|
||||||
|
response = (
|
||||||
|
"Updates will be sent every {} minutes."
|
||||||
|
).format(val)
|
||||||
|
else:
|
||||||
|
response = "Missing value."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase.startswith("join"):
|
||||||
|
muc_jid = uri.check_xmpp_uri(message_text[5:])
|
||||||
|
if muc_jid:
|
||||||
|
# TODO probe JID and confirm it's a groupchat
|
||||||
|
await groupchat.join(self, jid, muc_jid)
|
||||||
|
response = (
|
||||||
|
"Joined groupchat {}"
|
||||||
|
).format(message_text)
|
||||||
|
else:
|
||||||
|
response = (
|
||||||
|
"> {}\nXMPP URI is not valid."
|
||||||
|
).format(message_text)
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase.startswith("length"):
|
||||||
|
key = message_text[:6]
|
||||||
|
val = message_text[7:]
|
||||||
|
if val:
|
||||||
|
try:
|
||||||
|
val = int(val)
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
await sqlite.set_settings_value(db_file, [key, val])
|
||||||
|
if val == 0:
|
||||||
|
response = (
|
||||||
|
"Summary length limit is disabled."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
response = (
|
||||||
|
"Summary maximum length "
|
||||||
|
"is set to {} characters."
|
||||||
|
).format(val)
|
||||||
|
except:
|
||||||
|
response = "Enter a numeric value only."
|
||||||
|
else:
|
||||||
|
response = "Missing value."
|
||||||
|
# case _ if message_lowercase.startswith("mastership"):
|
||||||
|
# key = message_text[:7]
|
||||||
|
# val = message_text[11:]
|
||||||
|
# if val:
|
||||||
|
# names = await initdb(
|
||||||
|
# jid,
|
||||||
|
# get_settings_value,
|
||||||
|
# key
|
||||||
|
# )
|
||||||
|
# val = await add_to_list(
|
||||||
|
# val,
|
||||||
|
# names
|
||||||
|
# )
|
||||||
|
# await initdb(
|
||||||
|
# jid,
|
||||||
|
# set_settings_value,
|
||||||
|
# [key, val]
|
||||||
|
# )
|
||||||
|
# response = (
|
||||||
|
# "Operators\n"
|
||||||
|
# "```\n{}\n```"
|
||||||
|
# ).format(val)
|
||||||
|
# else:
|
||||||
|
# response = "Missing value."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case "new":
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
sqlite.set_settings_value(db_file, ["old", 0])
|
||||||
|
response = (
|
||||||
|
"Only new items of newly added feeds will be sent."
|
||||||
|
)
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
# TODO Will you add support for number of messages?
|
||||||
|
case "next":
|
||||||
|
# num = message_text[5:]
|
||||||
|
await task.clean_tasks_xmpp(jid, ["interval", "status"])
|
||||||
|
await task.start_tasks_xmpp(self, jid, ["interval", "status"])
|
||||||
|
# await refresh_task(
|
||||||
|
# self,
|
||||||
|
# jid,
|
||||||
|
# send_update,
|
||||||
|
# "interval",
|
||||||
|
# num
|
||||||
|
# )
|
||||||
|
# await refresh_task(
|
||||||
|
# self,
|
||||||
|
# jid,
|
||||||
|
# send_status,
|
||||||
|
# "status",
|
||||||
|
# 20
|
||||||
|
# )
|
||||||
|
# await refresh_task(jid, key, val)
|
||||||
|
case "old":
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
await sqlite.set_settings_value(db_file, ["old", 1])
|
||||||
|
response = (
|
||||||
|
"All items of newly added feeds will be sent."
|
||||||
|
)
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase.startswith("quantum"):
|
||||||
|
key = message_text[:7]
|
||||||
|
val = message_text[8:]
|
||||||
|
if val:
|
||||||
|
try:
|
||||||
|
val = int(val)
|
||||||
|
# response = (
|
||||||
|
# "Every update will contain {} news items."
|
||||||
|
# ).format(response)
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
await sqlite.set_settings_value(db_file, [key, val])
|
||||||
|
response = (
|
||||||
|
"Next update will contain {} news items."
|
||||||
|
).format(val)
|
||||||
|
except:
|
||||||
|
response = "Enter a numeric value only."
|
||||||
|
else:
|
||||||
|
response = "Missing value."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case "random":
|
||||||
|
# TODO /questions/2279706/select-random-row-from-a-sqlite-table
|
||||||
|
# NOTE sqlitehandler.get_entry_unread
|
||||||
|
response = "Updates will be sent by random order."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase.startswith("read"):
|
||||||
|
data = message_text[5:]
|
||||||
|
data = data.split()
|
||||||
|
url = data[0]
|
||||||
|
await task.clean_tasks_xmpp(jid, ["status"])
|
||||||
|
status_type = "dnd"
|
||||||
|
status_message = (
|
||||||
|
"📫️ Processing request to fetch data from {}"
|
||||||
|
).format(url)
|
||||||
|
send_status_message(self, jid, status_type, status_message)
|
||||||
|
if url.startswith("feed:"):
|
||||||
|
url = uri.feed_to_http(url)
|
||||||
|
url = (uri.replace_hostname(url, "feed")) or url
|
||||||
|
match len(data):
|
||||||
|
case 1:
|
||||||
|
if url.startswith("http"):
|
||||||
|
response = await fetcher.view_feed(url)
|
||||||
|
else:
|
||||||
|
response = "Missing URL."
|
||||||
|
case 2:
|
||||||
|
num = data[1]
|
||||||
|
if url.startswith("http"):
|
||||||
|
response = await fetcher.view_entry(url, num)
|
||||||
|
else:
|
||||||
|
response = "Missing URL."
|
||||||
|
case _:
|
||||||
|
response = (
|
||||||
|
"Enter command as follows:\n"
|
||||||
|
"`read <url>` or `read <url> <number>`\n"
|
||||||
|
"URL must not contain white space."
|
||||||
|
)
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
await task.start_tasks_xmpp(self, jid, ["status"])
|
||||||
|
case _ if message_lowercase.startswith("recent"):
|
||||||
|
num = message_text[7:]
|
||||||
|
if num:
|
||||||
|
try:
|
||||||
|
num = int(num)
|
||||||
|
if num < 1 or num > 50:
|
||||||
|
response = "Value must be ranged from 1 to 50."
|
||||||
|
else:
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
result = await sqlite.last_entries(db_file, num)
|
||||||
|
response = compose.list_last_entries(result, num)
|
||||||
|
except:
|
||||||
|
response = "Enter a numeric value only."
|
||||||
|
else:
|
||||||
|
response = "Missing value."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
# NOTE Should people be asked for numeric value?
|
||||||
|
case _ if message_lowercase.startswith("remove"):
|
||||||
|
ix = message_text[7:]
|
||||||
|
if ix:
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
response = await sqlite.remove_feed(db_file, ix)
|
||||||
|
# await refresh_task(
|
||||||
|
# self,
|
||||||
|
# jid,
|
||||||
|
# send_status,
|
||||||
|
# "status",
|
||||||
|
# 20
|
||||||
|
# )
|
||||||
|
await task.clean_tasks_xmpp(jid, ["status"])
|
||||||
|
await task.start_tasks_xmpp(self, jid, ["status"])
|
||||||
|
else:
|
||||||
|
response = "Missing feed ID."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase.startswith("reset"):
|
||||||
|
source = message_text[6:]
|
||||||
|
await task.clean_tasks_xmpp(jid, ["status"])
|
||||||
|
status_type = "dnd"
|
||||||
|
status_message = "📫️ Marking entries as read..."
|
||||||
|
send_status_message(self, jid, status_type, status_message)
|
||||||
|
if source:
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
await sqlite.mark_source_as_read(db_file, source)
|
||||||
|
response = (
|
||||||
|
"All entries of {} have been "
|
||||||
|
"marked as read.".format(source)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
await sqlite.mark_all_as_read(db_file)
|
||||||
|
response = "All entries have been marked as read."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
await task.start_tasks_xmpp(self, jid, ["status"])
|
||||||
|
case _ if message_lowercase.startswith("search"):
|
||||||
|
query = message_text[7:]
|
||||||
|
if query:
|
||||||
|
if len(query) > 1:
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
results = await sqlite.search_entries(db_file, query)
|
||||||
|
response = compose.list_search_results(query, results)
|
||||||
|
else:
|
||||||
|
response = (
|
||||||
|
"Enter at least 2 characters to search"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
response = "Missing search query."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case "start":
|
||||||
|
# response = "Updates are enabled."
|
||||||
|
key = "enabled"
|
||||||
|
val = 1
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
await sqlite.set_settings_value(db_file, [key, val])
|
||||||
|
# asyncio.create_task(task_jid(self, jid))
|
||||||
|
await task.start_tasks_xmpp(self, jid, ["interval", "status", "check"])
|
||||||
|
response = "Updates are enabled."
|
||||||
|
# print(current_time(), "task_manager[jid]")
|
||||||
|
# print(task_manager[jid])
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case "stats":
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
result = await sqlite.statistics(db_file)
|
||||||
|
response = compose.list_statistics(result)
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase.startswith("status "):
|
||||||
|
ix = message_text[7:]
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
response = await sqlite.toggle_status(db_file, ix)
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case "stop":
|
||||||
|
# FIXME
|
||||||
|
# The following error occurs only upon first attempt to stop.
|
||||||
|
# /usr/lib/python3.11/asyncio/events.py:73: RuntimeWarning: coroutine 'Slixfeed.send_update' was never awaited
|
||||||
|
# self._args = None
|
||||||
|
# RuntimeWarning: Enable tracemalloc to get the object allocation traceback
|
||||||
|
# response = "Updates are disabled."
|
||||||
|
# try:
|
||||||
|
# # task_manager[jid]["check"].cancel()
|
||||||
|
# # task_manager[jid]["status"].cancel()
|
||||||
|
# task_manager[jid]["interval"].cancel()
|
||||||
|
# key = "enabled"
|
||||||
|
# val = 0
|
||||||
|
# response = await initdb(
|
||||||
|
# jid,
|
||||||
|
# set_settings_value,
|
||||||
|
# [key, val]
|
||||||
|
# )
|
||||||
|
# except:
|
||||||
|
# response = "Updates are already disabled."
|
||||||
|
# # print("Updates are already disabled. Nothing to do.")
|
||||||
|
# # await send_status(jid)
|
||||||
|
key = "enabled"
|
||||||
|
val = 0
|
||||||
|
db_file = get_pathname_to_database(jid)
|
||||||
|
await sqlite.set_settings_value(db_file, [key, val])
|
||||||
|
await task.clean_tasks_xmpp(jid, ["interval", "status"])
|
||||||
|
response = "Updates are disabled."
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
status_type = "xa"
|
||||||
|
status_message = "💡️ Send \"Start\" to receive Jabber updates"
|
||||||
|
send_status_message(self, jid, status_type, status_message)
|
||||||
|
case "support":
|
||||||
|
# TODO Send an invitation.
|
||||||
|
response = (
|
||||||
|
"Join xmpp:slixfeed@chat.woodpeckersnest.space?join")
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _ if message_lowercase.startswith("xmpp:"):
|
||||||
|
muc_jid = uri.check_xmpp_uri(message_text)
|
||||||
|
if muc_jid:
|
||||||
|
# TODO probe JID and confirm it's a groupchat
|
||||||
|
await groupchat.join(self, jid, muc_jid)
|
||||||
|
response = (
|
||||||
|
"Joined groupchat {}"
|
||||||
|
).format(message_text)
|
||||||
|
else:
|
||||||
|
response = (
|
||||||
|
"> {}\nXMPP URI is not valid."
|
||||||
|
).format(message_text)
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
case _:
|
||||||
|
response = (
|
||||||
|
"Unknown command. "
|
||||||
|
"Press \"help\" for list of commands"
|
||||||
|
)
|
||||||
|
send_reply_message(self, message, response)
|
||||||
|
# TODO Use message correction here
|
||||||
|
# NOTE This might not be a good idea if
|
||||||
|
# commands are sent one close to the next
|
||||||
|
# if response: message.reply(response).send()
|
||||||
|
|
||||||
|
log_dir = get_default_dbdir()
|
||||||
|
if not os.path.isdir(log_dir):
|
||||||
|
os.mkdir(log_dir)
|
||||||
|
utility.log_as_markdown(
|
||||||
|
current_time(), os.path.join(log_dir, jid),
|
||||||
|
jid, message_text)
|
||||||
|
utility.log_as_markdown(
|
||||||
|
current_time(), os.path.join(log_dir, jid),
|
||||||
|
self.boundjid.bare, response)
|
||||||
|
|
||||||
|
|
||||||
|
def send_status_message(self, jid, status_type, status_message):
|
||||||
|
self.send_presence(
|
||||||
|
pshow=status_type,
|
||||||
|
pstatus=status_message,
|
||||||
|
pto=jid)
|
||||||
|
|
||||||
|
|
||||||
|
def send_reply_message(self, message, response):
|
||||||
|
message.reply(response).send()
|
||||||
|
|
||||||
|
|
||||||
|
# def greet(self, jid, chat_type="chat"):
|
||||||
|
# messages = [
|
||||||
|
# "Greetings!",
|
||||||
|
# "I'm {}, the news anchor.".format(self.nick),
|
||||||
|
# "My job is to bring you the latest news "
|
||||||
|
# "from sources you provide me with.",
|
||||||
|
# "You may always reach me via "
|
||||||
|
# "xmpp:{}?message".format(self.boundjid.bare)
|
||||||
|
# ]
|
||||||
|
# for message in messages:
|
||||||
|
# self.send_message(
|
||||||
|
# mto=jid,
|
||||||
|
# mbody=message,
|
||||||
|
# mtype=chat_type
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
def greet(self, jid, chat_type="chat"):
|
||||||
|
message = (
|
||||||
|
"Greetings!\n"
|
||||||
|
"I'm {}, the news anchor.\n"
|
||||||
|
"My job is to bring you the latest "
|
||||||
|
"news from sources you provide me with.\n"
|
||||||
|
"You may always reach me via xmpp:{}?message").format(
|
||||||
|
self.nick,
|
||||||
|
self.boundjid.bare
|
||||||
|
)
|
||||||
|
self.send_message(
|
||||||
|
mto=jid,
|
||||||
|
mbody=message,
|
||||||
|
mtype=chat_type
|
||||||
|
)
|
||||||
|
|
54
slixfeed/xmpp/roster.py
Normal file
54
slixfeed/xmpp/roster.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
1) remove_subscription (clean_roster)
|
||||||
|
Remove presence from contacts that don't share presence.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import slixfeed.xmpp.utility as utility
|
||||||
|
|
||||||
|
async def add(self, jid):
|
||||||
|
"""
|
||||||
|
Add JID to roster.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
jid : str
|
||||||
|
Jabber ID.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None.
|
||||||
|
"""
|
||||||
|
if await utility.jid_type(self, jid) == "groupchat":
|
||||||
|
# Check whether JID is in bookmarks; otherwise, add it.
|
||||||
|
print(jid, "is muc")
|
||||||
|
return
|
||||||
|
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=self.nick
|
||||||
|
)
|
||||||
|
self.update_roster(
|
||||||
|
jid,
|
||||||
|
subscription="both"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_subscription(self):
|
||||||
|
"""
|
||||||
|
Remove subscription from contacts that don't share their presence.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None.
|
||||||
|
"""
|
63
slixfeed/xmpp/state.py
Normal file
63
slixfeed/xmpp/state.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
async def request(self, jid):
|
||||||
|
"""
|
||||||
|
Ask contant to settle subscription.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
jid : str
|
||||||
|
Jabber ID.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
None.
|
||||||
|
"""
|
||||||
|
# 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=self.nick
|
||||||
|
)
|
||||||
|
self.send_message(
|
||||||
|
mto=jid,
|
||||||
|
# mtype="headline",
|
||||||
|
msubject="RSS News Bot",
|
||||||
|
mbody=(
|
||||||
|
"Share online status to receive updates."
|
||||||
|
),
|
||||||
|
mfrom=self.boundjid.bare,
|
||||||
|
mnick=self.nick
|
||||||
|
)
|
||||||
|
self.send_presence(
|
||||||
|
pto=jid,
|
||||||
|
pfrom=self.boundjid.bare,
|
||||||
|
# Accept symbol 🉑️ 👍️ ✍
|
||||||
|
pstatus=(
|
||||||
|
"✒️ Share online status to receive updates."
|
||||||
|
),
|
||||||
|
# ptype="subscribe",
|
||||||
|
pnick=self.nick
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def unsubscribed(self, presence):
|
||||||
|
jid = presence["from"].bare
|
||||||
|
self.send_message(
|
||||||
|
mto=jid,
|
||||||
|
mbody="You have been unsubscribed."
|
||||||
|
)
|
||||||
|
self.send_presence(
|
||||||
|
pto=jid,
|
||||||
|
pfrom=self.boundjid.bare,
|
||||||
|
pstatus="🖋️ Subscribe to receive updates",
|
||||||
|
pnick=self.nick
|
||||||
|
)
|
||||||
|
self.update_roster(
|
||||||
|
jid,
|
||||||
|
subscription="remove"
|
||||||
|
)
|
43
slixfeed/xmpp/utility.py
Normal file
43
slixfeed/xmpp/utility.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from slixfeed.datetime import current_time
|
||||||
|
from slixmpp.exceptions import IqTimeout
|
||||||
|
import logging
|
||||||
|
|
||||||
|
async def jid_type(self, jid):
|
||||||
|
"""
|
||||||
|
Check whether a JID is of MUC.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
jid : str
|
||||||
|
Jabber ID.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
"chat" or "groupchat.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
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 "groupchat"
|
||||||
|
# TODO elif <feature var='jabber:iq:gateway'/>
|
||||||
|
# NOTE Is it needed? We do not interact with gateways or services
|
||||||
|
else:
|
||||||
|
return "chat"
|
||||||
|
# TODO Test whether this exception is realized
|
||||||
|
except IqTimeout as e:
|
||||||
|
messages = [
|
||||||
|
("Timeout IQ"),
|
||||||
|
("IQ Stanza:", e),
|
||||||
|
("Jabber ID:", jid)
|
||||||
|
]
|
||||||
|
for message in messages:
|
||||||
|
print(current_time(), message)
|
||||||
|
logging.error(current_time(), message)
|
Loading…
Reference in a new issue