Slixfeed/slixfeed/sqlitehandler.py

1551 lines
41 KiB
Python
Raw Normal View History

2023-07-16 17:23:44 +02:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TODO
1) Table feeds:
category
type (atom, rdf, rss0.9. rss2 etc.)
2) Function mark_all_read for entries of given feed
3) Statistics
"""
from asyncio import Lock
2023-12-01 14:22:03 +01:00
from bs4 import BeautifulSoup
2023-07-16 17:23:44 +02:00
from datetime import date
# from slixfeed.confighandler import get_value_default
import confighandler as confighandler
# from slixfeed.datahandler import join_url
import datahandler as datahandler
from datetimehandler import current_time, rfc2822_to_iso8601
from sqlite3 import connect, Error
2023-07-16 17:23:44 +02:00
# from eliot import start_action, to_file
# # with start_action(action_type="list_feeds()", db=db_file):
# # with start_action(action_type="last_entries()", num=num):
# # with start_action(action_type="get_feeds()"):
# # with start_action(action_type="remove_entry()", source=source):
# # with start_action(action_type="search_entries()", query=query):
# # with start_action(action_type="check_entry()", link=link):
2023-07-16 17:23:44 +02:00
# aiosqlite
DBLOCK = Lock()
2023-07-16 17:23:44 +02:00
CURSORS = {}
def create_connection(db_file):
"""
Create a database connection to the SQLite database
2023-10-04 14:37:31 +02:00
specified by db_file.
Parameters
----------
db_file : str
Path to database file.
Returns
-------
conn : object
Connection object or None.
2023-07-16 17:23:44 +02:00
"""
conn = None
try:
conn = connect(db_file)
2023-07-16 17:23:44 +02:00
return conn
except Error as e:
print(e)
return conn
2023-07-16 17:23:44 +02:00
def create_tables(db_file):
2023-10-04 14:37:31 +02:00
"""
Create SQLite tables.
Parameters
----------
db_file : str
Path to database file.
2023-10-04 14:37:31 +02:00
"""
with create_connection(db_file) as conn:
feeds_table_sql =(
"CREATE TABLE IF NOT EXISTS feeds ("
"id INTEGER PRIMARY KEY,"
"name TEXT,"
"address TEXT NOT NULL,"
"enabled INTEGER NOT NULL,"
"scanned TEXT,"
"updated TEXT,"
"status INTEGER,"
"valid INTEGER"
");"
)
entries_table_sql = (
"CREATE TABLE IF NOT EXISTS entries ("
"id INTEGER PRIMARY KEY,"
"title TEXT NOT NULL,"
"summary TEXT NOT NULL,"
"link TEXT NOT NULL,"
"entry_id TEXT,"
"source TEXT NOT NULL,"
"timestamp TEXT,"
"read INTEGER"
");"
)
archive_table_sql = (
"CREATE TABLE IF NOT EXISTS archive ("
"id INTEGER PRIMARY KEY,"
"title TEXT NOT NULL,"
"summary TEXT NOT NULL,"
"link TEXT NOT NULL,"
"entry_id TEXT,"
"source TEXT NOT NULL,"
"timestamp TEXT,"
"read INTEGER"
");"
)
# statistics_table_sql = (
# "CREATE TABLE IF NOT EXISTS statistics ("
# "id INTEGER PRIMARY KEY,"
# "title TEXT NOT NULL,"
# "number INTEGER"
# ");"
# )
settings_table_sql = (
"CREATE TABLE IF NOT EXISTS settings ("
"id INTEGER PRIMARY KEY,"
"key TEXT NOT NULL,"
"value INTEGER"
");"
)
cur = conn.cursor()
# cur = get_cursor(db_file)
cur.execute(feeds_table_sql)
cur.execute(entries_table_sql)
cur.execute(archive_table_sql)
# cur.execute(statistics_table_sql)
cur.execute(settings_table_sql)
2023-07-16 17:23:44 +02:00
def get_cursor(db_file):
"""
Allocate a cursor to connection per database.
Parameters
----------
db_file : str
Path to database file.
Returns
-------
CURSORS[db_file] : object
Cursor.
2023-07-16 17:23:44 +02:00
"""
if db_file in CURSORS:
2023-07-16 17:23:44 +02:00
return CURSORS[db_file]
else:
with create_connection(db_file) as conn:
cur = conn.cursor()
CURSORS[db_file] = cur
return CURSORS[db_file]
2023-07-16 17:23:44 +02:00
async def insert_feed(db_file, url, title=None, status=None):
2023-07-16 17:23:44 +02:00
"""
Insert a new feed into the feeds table.
Parameters
----------
db_file : str
Path to database file.
url : str
URL.
title : str, optional
Feed Title. The default is None.
status : str, optional
HTTP status code. The default is None.
Returns
-------
msg : str
Message.
2023-07-16 17:23:44 +02:00
"""
#TODO consider async with DBLOCK
#conn = create_connection(db_file)
2023-07-16 17:23:44 +02:00
# with create_connection(db_file) as conn:
# #exist = await check_feed_exist(conn, url)
# exist = await check_feed_exist(db_file, url)
# if not exist:
# status = await main.download_feed(url)
# else:
# return "News source is already listed in the subscription list"
async with DBLOCK:
with create_connection(db_file) as conn:
cur = conn.cursor()
# title = feed["feed"]["title"]
feed = (title, url, 1, status, 1)
sql = (
"INSERT INTO feeds("
"name, address, enabled, status, valid"
")"
"VALUES(?, ?, ?, ?, ?) "
)
cur.execute(sql, feed)
source = title if title else '<' + url + '>'
msg = (
"> {}\nNews source \"{}\" has been added "
"to subscription list."
).format(url, source)
return msg
2023-07-16 17:23:44 +02:00
async def remove_feed(db_file, ix):
"""
Delete a feed by feed ID.
Parameters
----------
db_file : str
Path to database file.
ix : str
Index of feed.
Returns
-------
msg : str
Message.
2023-07-16 17:23:44 +02:00
"""
with create_connection(db_file) as conn:
async with DBLOCK:
cur = conn.cursor()
try:
sql = (
"SELECT address "
"FROM feeds "
"WHERE id = ?"
)
# cur
# for i in url:
# url = i[0]
url = cur.execute(sql, (ix,)).fetchone()[0]
sql = (
"SELECT name "
"FROM feeds "
"WHERE id = ?"
)
name = cur.execute(sql, (ix,)).fetchone()[0]
2023-07-16 17:23:44 +02:00
# NOTE Should we move DBLOCK to this line? 2022-12-23
sql = (
"DELETE "
"FROM entries "
"WHERE source = ?"
)
2023-07-16 17:23:44 +02:00
cur.execute(sql, (url,))
sql = (
"DELETE "
"FROM archive "
"WHERE source = ?"
)
cur.execute(sql, (url,))
sql = (
"DELETE FROM feeds "
"WHERE id = ?"
)
2023-07-16 17:23:44 +02:00
cur.execute(sql, (ix,))
msg = (
"> {}\nNews source \"{}\" has been removed "
"from subscription list."
).format(url, name)
except:
msg = (
"No news source with ID {}."
).format(ix)
return msg
2023-07-16 17:23:44 +02:00
async def check_feed_exist(db_file, url):
2023-07-16 17:23:44 +02:00
"""
2023-10-04 14:37:31 +02:00
Check whether a feed exists.
Query for feeds by given url.
Parameters
----------
db_file : str
Path to database file.
url : str
URL.
Returns
-------
result : list
List of ID and Name of feed.
2023-07-16 17:23:44 +02:00
"""
cur = get_cursor(db_file)
sql = (
"SELECT id, name "
"FROM feeds "
"WHERE address = ?"
)
result = cur.execute(sql, (url,)).fetchone()
return result
async def get_number_of_items(db_file, table):
"""
2023-10-04 14:37:31 +02:00
Return number of entries or feeds.
Parameters
----------
db_file : str
Path to database file.
table : str
"entries" or "feeds".
Returns
-------
count : ?
Number of rows.
"""
with create_connection(db_file) as conn:
cur = conn.cursor()
sql = (
"SELECT count(id) "
"FROM {}"
).format(table)
count = cur.execute(sql).fetchone()[0]
return count
async def get_number_of_feeds_active(db_file):
"""
Return number of active feeds.
Parameters
----------
db_file : str
Path to database file.
Returns
-------
count : ?
Number of rows.
"""
with create_connection(db_file) as conn:
cur = conn.cursor()
sql = (
"SELECT count(id) "
"FROM feeds "
"WHERE enabled = 1"
)
count = cur.execute(sql).fetchone()[0]
return count
2023-10-04 14:37:31 +02:00
async def get_number_of_entries_unread(db_file):
"""
Return number of unread items.
Parameters
----------
db_file : str
Path to database file.
Returns
-------
count : ?
Number of rows.
2023-10-04 14:37:31 +02:00
"""
with create_connection(db_file) as conn:
cur = conn.cursor()
sql = (
"SELECT "
"("
"SELECT count(id) "
"FROM entries "
"WHERE read = 0"
") "
"+ "
"("
"SELECT count(id) "
"FROM archive"
") "
"AS total_count"
)
count = cur.execute(sql).fetchone()[0]
2023-10-04 14:37:31 +02:00
return count
2023-07-16 17:23:44 +02:00
# TODO Read from entries and archives
async def get_entry_unread(db_file, num=None):
2023-07-16 17:23:44 +02:00
"""
Extract information from unread entries.
Parameters
----------
db_file : str
Path to database file.
num : str, optional
Number. The default is None.
Returns
-------
entry : str
News item message.
2023-07-16 17:23:44 +02:00
"""
if not num:
num = await get_settings_value(db_file, "quantum")
else:
num = int(num)
with create_connection(db_file) as conn:
cur = conn.cursor()
# sql = (
# "SELECT id "
# "FROM entries "
# "WHERE read = 0 "
# "LIMIT 1"
# )
# sql = ("SELECT id "
# "FROM entries "
# "WHERE read = 0 "
# "ORDER BY timestamp DESC "
# "LIMIT 1"
# )
# sql = (
# "SELECT id, title, summary, link "
# "FROM entries "
# "WHERE read = 0 "
# "ORDER BY timestamp "
# "DESC LIMIT :num"
# )
sql = (
2023-12-01 14:22:03 +01:00
"SELECT id, title, summary, link, source, timestamp "
"FROM entries "
"WHERE read = 0 "
"UNION ALL "
2023-12-01 14:22:03 +01:00
"SELECT id, title, summary, link, source, timestamp "
"FROM archive "
"ORDER BY timestamp "
"DESC LIMIT :num"
)
results = cur.execute(sql, (num,))
results = results.fetchall()
# TODO Add filtering
# TODO Do this when entry is added to list and mark it as read
# DONE!
# results = []
# if get_settings_value(db_file, "filter-deny"):
# while len(results) < num:
# result = cur.execute(sql).fetchone()
# blacklist = await get_settings_value(db_file, "filter-deny").split(",")
# for i in blacklist:
# if i in result[1]:
# continue
# print("rejected:", result[1])
# print("accepted:", result[1])
# results.extend([result])
# news_list = "You've got {} news items:\n".format(num)
news_list = ""
# NOTE Why doesn't this work without list?
# i.e. for result in results
# for result in results.fetchall():
for result in results:
ix = result[0]
title = result[1]
summary = result[2]
2023-12-01 14:22:03 +01:00
# Remove HTML tags
summary = BeautifulSoup(summary, "lxml").text
# TODO Limit text length
summary = summary.replace("\n\n\n", "\n\n")
length = await get_settings_value(db_file, "length")
summary = summary[:length] + " […]"
summary = summary.strip().split('\n')
summary = ["> " + line for line in summary]
summary = "\n".join(summary)
link = result[3]
2023-12-01 14:22:03 +01:00
sql = (
"SELECT name "
"FROM feeds "
"WHERE address = :source "
)
source = result[4]
feed = cur.execute(sql, (source,))
feed = feed.fetchone()[0]
if num > 1:
news_list += (
"\n{}\n{}\n"
).format(
str(title),
str(link)
)
else:
news_list = (
2023-12-01 14:22:03 +01:00
"{}\n\n{}\n\n{}\n{}"
).format(
str(title),
str(summary),
2023-12-01 14:22:03 +01:00
str(link),
str(feed)
)
2023-11-29 16:32:35 +01:00
# TODO While `async with DBLOCK` does work well from
# outside of functions, it would be better practice
# to place it within the functions.
async with DBLOCK:
# NOTE: We can use DBLOCK once for both
# functions, because, due to exclusive
# ID, only one can ever occur.
2023-11-29 16:32:35 +01:00
await mark_entry_as_read(cur, ix)
await delete_entry(cur, ix)
return news_list
2023-07-16 17:23:44 +02:00
2023-11-29 16:32:35 +01:00
async def mark_entry_as_read(cur, ix):
2023-07-16 17:23:44 +02:00
"""
2023-11-29 16:32:35 +01:00
Set read status of entry as read.
Parameters
----------
2023-11-29 16:32:35 +01:00
cur : object
Cursor object.
ix : str
Index of entry.
2023-07-16 17:23:44 +02:00
"""
sql = (
"UPDATE entries "
"SET summary = '', read = 1 "
"WHERE id = ?"
)
cur.execute(sql, (ix,))
2023-07-16 17:23:44 +02:00
2023-11-29 16:32:35 +01:00
async def mark_source_as_read(db_file, source):
"""
Set read status of entries of given source as read.
Parameters
----------
db_file : str
Path to database file.
source : str
URL.
"""
async with DBLOCK:
with create_connection(db_file) as conn:
cur = conn.cursor()
sql = (
"UPDATE entries "
"SET summary = '', read = 1 "
"WHERE source = ?"
)
cur.execute(sql, (source,))
async def mark_all_as_read(db_file):
"""
Set read status of all entries as read.
Parameters
----------
db_file : str
Path to database file.
"""
async with DBLOCK:
with create_connection(db_file) as conn:
cur = conn.cursor()
sql = (
"UPDATE entries "
"SET summary = '', read = 1 "
)
cur.execute(sql)
sql = (
"DELETE FROM archive"
)
cur.execute(sql)
async def delete_entry(cur, ix):
"""
Delete entry from table archive.
Parameters
----------
db_file : str
Path to database file.
ix : str
Index of entry.
"""
sql = (
"DELETE FROM archive "
"WHERE id = ?"
)
cur.execute(sql, (ix,))
2023-10-04 14:37:31 +02:00
async def statistics(db_file):
"""
Return table statistics.
Parameters
----------
db_file : str
Path to database file.
Returns
-------
msg : str
Statistics as message.
2023-10-04 14:37:31 +02:00
"""
feeds = await get_number_of_items(db_file, 'feeds')
active_feeds = await get_number_of_feeds_active(db_file)
2023-10-04 14:37:31 +02:00
entries = await get_number_of_items(db_file, 'entries')
unread_entries = await get_number_of_entries_unread(db_file)
# msg = """You have {} unread news items out of {} from {} news sources.
# """.format(unread_entries, entries, feeds)
with create_connection(db_file) as conn:
cur = conn.cursor()
keys = []
for key in ["enabled", "interval", "quantum"]:
sql = (
"SELECT value "
"FROM settings "
"WHERE key = ?"
)
keys.extend([cur.execute(sql, (key,)).fetchone()[0]])
msg = (
"```\n"
"News items : {} ({})\n"
"News sources : {} ({})\n"
"Update interval : {}\n"
"Items per update : {}\n"
"Operation status : {}\n"
"```"
).format(
unread_entries, entries,
active_feeds, feeds,
keys[1],
keys[2],
keys[0]
)
return msg
2023-10-04 14:37:31 +02:00
async def update_statistics(cur):
"""
Update table statistics.
Parameters
----------
cur : object
Cursor object.
2023-10-04 14:37:31 +02:00
"""
stat_dict = {}
stat_dict["feeds"] = await get_number_of_items(cur, 'feeds')
stat_dict["entries"] = await get_number_of_items(cur, 'entries')
stat_dict["unread"] = await get_number_of_entries_unread(cur=cur)
for i in stat_dict:
sql = (
"SELECT id "
"FROM statistics "
"WHERE title = ?"
)
2023-10-04 14:37:31 +02:00
cur.execute(sql, (i,))
if cur.fetchone():
sql = (
"UPDATE statistics "
"SET number = :num "
"WHERE title = :title"
)
cur.execute(sql, {
"title": i,
"num": stat_dict[i]
})
2023-10-04 14:37:31 +02:00
else:
sql = (
"SELECT count(id) "
"FROM statistics"
)
count = cur.execute(sql).fetchone()[0]
2023-10-04 14:37:31 +02:00
ix = count + 1
sql = (
"INSERT INTO statistics "
"VALUES(?,?,?)"
)
2023-10-04 14:37:31 +02:00
cur.execute(sql, (ix, i, stat_dict[i]))
2023-07-16 17:23:44 +02:00
async def toggle_status(db_file, ix):
"""
2023-10-04 14:37:31 +02:00
Toggle status of feed.
Parameters
----------
db_file : str
Path to database file.
ix : str
Index of entry.
Returns
-------
msg : str
Message.
2023-07-16 17:23:44 +02:00
"""
async with DBLOCK:
with create_connection(db_file) as conn:
cur = conn.cursor()
try:
#cur = get_cursor(db_file)
sql = (
"SELECT name "
"FROM feeds "
"WHERE id = :id"
)
title = cur.execute(sql, (ix,)).fetchone()[0]
sql = (
"SELECT enabled "
"FROM feeds "
"WHERE id = ?"
)
# NOTE [0][1][2]
status = cur.execute(sql, (ix,)).fetchone()[0]
# FIXME always set to 1
# NOTE Maybe because is not integer
# TODO Reset feed table before further testing
if status == 1:
status = 0
state = "disabled"
else:
status = 1
state = "enabled"
sql = (
"UPDATE feeds "
"SET enabled = :status "
"WHERE id = :id"
)
cur.execute(sql, {
"status": status,
"id": ix
})
msg = (
"Updates from '{}' are now {}."
).format(title, state)
except:
msg = (
"No news source with ID {}."
).format(ix)
return msg
2023-07-16 17:23:44 +02:00
async def set_date(cur, url):
"""
2023-10-04 14:37:31 +02:00
Set last update date of feed.
Parameters
----------
cur : object
Cursor object.
url : str
URL.
2023-07-16 17:23:44 +02:00
"""
today = date.today()
sql = (
"UPDATE feeds "
"SET updated = :today "
"WHERE address = :url"
)
# cur = conn.cursor()
cur.execute(sql, {
"today": today,
"url": url
})
2023-07-16 17:23:44 +02:00
async def add_entry_and_set_date(db_file, source, entry):
2023-10-04 14:37:31 +02:00
"""
Add entry to table entries and set date of source in table feeds.
Parameters
----------
db_file : str
Path to database file.
source : str
Feed URL.
entry : list
Entry properties.
2023-10-04 14:37:31 +02:00
"""
2023-11-29 16:32:35 +01:00
# TODO While `async with DBLOCK` does work well from
# outside of functions, it would be better practice
# to place it within the functions.
2023-07-16 17:23:44 +02:00
async with DBLOCK:
with create_connection(db_file) as conn:
cur = conn.cursor()
await add_entry(cur, entry)
await set_date(cur, source)
async def update_source_status(db_file, status, source):
2023-10-04 14:37:31 +02:00
"""
Set HTTP status of source in table feeds.
Parameters
----------
db_file : str
Path to database file.
source : str
Feed URL.
status : str
Status ID or message.
2023-10-04 14:37:31 +02:00
"""
sql = (
"UPDATE feeds "
"SET status = :status, scanned = :scanned "
"WHERE address = :url"
)
2023-07-16 17:23:44 +02:00
async with DBLOCK:
with create_connection(db_file) as conn:
cur = conn.cursor()
cur.execute(sql, {
"status" : status,
"scanned" : date.today(),
"url" : source
})
2023-07-16 17:23:44 +02:00
async def update_source_validity(db_file, source, valid):
2023-10-04 14:37:31 +02:00
"""
Set validity status of source in table feeds.
Parameters
----------
db_file : str
Path to database file.
source : str
Feed URL.
valid : boolean
0 or 1.
2023-10-04 14:37:31 +02:00
"""
sql = (
"UPDATE feeds "
"SET valid = :validity "
"WHERE address = :url"
)
2023-07-16 17:23:44 +02:00
async with DBLOCK:
with create_connection(db_file) as conn:
cur = conn.cursor()
cur.execute(sql, {
"validity": valid,
"url": source
})
2023-07-16 17:23:44 +02:00
2023-12-01 14:22:03 +01:00
"""
TODO
2023-07-16 17:23:44 +02:00
2023-12-01 14:22:03 +01:00
Investigate why causes entry[6] (date) to be int 0
"""
2023-07-16 17:23:44 +02:00
async def add_entry(cur, entry):
"""
Add a new entry row into the entries table.
Parameters
----------
cur : object
Cursor object.
entry : str
Entry properties.
2023-07-16 17:23:44 +02:00
"""
sql = (
"INSERT "
"INTO entries("
"title, "
"summary, "
"link, "
"entry_id, "
"source, "
"timestamp, "
"read"
") "
"VALUES(?, ?, ?, ?, ?, ?, ?)"
)
2023-12-01 14:22:03 +01:00
try:
cur.execute(sql, entry)
except:
print(await current_time(), "COROUTINE OBJECT NOW")
2023-12-01 14:22:03 +01:00
print(entry[6])
print(type(entry[6]))
print(entry)
print(type(entry))
print(await current_time(), "COROUTINE OBJECT NOW")
# breakpoint()
2023-07-16 17:23:44 +02:00
# NOTE See remove_nonexistent_entries
# NOTE This function doesn't work as expected with bbs and docuwiki feeds
2023-07-16 17:23:44 +02:00
async def remove_entry(db_file, source, length):
"""
2023-10-04 14:37:31 +02:00
Maintain list of entries equal to feed.
2023-07-16 17:23:44 +02:00
Check the number returned by feed and delete
2023-10-04 14:37:31 +02:00
existing entries up to the same returned amount.
Parameters
----------
db_file : str
Path to database file.
source : str
Feed URL.
length : str
Number.
2023-07-16 17:23:44 +02:00
"""
# FIXED
# Dino empty titles are not counted https://dino.im/index.xml
# SOLVED
# Add text if is empty
# title = '*** No title ***' if not entry.title else entry.title
async with DBLOCK:
with create_connection(db_file) as conn:
cur = conn.cursor()
sql = (
"SELECT count(id) "
"FROM entries "
"WHERE source = ?"
)
count = cur.execute(sql, (source,)).fetchone()[0]
limit = count - length
if limit:
limit = limit;
sql = (
"DELETE FROM entries "
"WHERE id "
"IN (SELECT id "
"FROM entries "
"WHERE source = :source "
"ORDER BY id "
"ASC LIMIT :limit)"
)
cur.execute(sql, {
"source": source,
"limit": limit
})
# TODO Move entries that don't exist into table archive.
# NOTE Entries that are read from archive are deleted.
# NOTE Unlike entries from table entries, entries from
# table archive are not marked as read.
async def remove_nonexistent_entries(db_file, feed, source):
"""
2023-10-04 14:37:31 +02:00
Remove entries that don't exist in a given parsed feed.
Check the entries returned from feed and delete read non
existing entries, otherwise move to table archive, if unread.
Parameters
----------
db_file : str
Path to database file.
feed : list
Parsed feed document.
source : str
Feed URL. URL of associated feed.
"""
with create_connection(db_file) as conn:
cur = conn.cursor()
sql = (
"SELECT id, title, link, entry_id, timestamp, read "
"FROM entries "
"WHERE source = ?"
)
items = cur.execute(sql, (source,)).fetchall()
entries = feed.entries
# breakpoint()
for item in items:
valid = False
for entry in entries:
title = None
link = None
time = None
# valid = False
# TODO better check and don't repeat code
if entry.has_key("id") and item[3]:
if entry.id == item[3]:
# print("compare1:", entry.id)
# print("compare2:", item[3])
# print("============")
valid = True
break
else:
if entry.has_key("title"):
title = entry.title
else:
title = feed["feed"]["title"]
if entry.has_key("link"):
link = await datahandler.join_url(source, entry.link)
else:
link = source
if entry.has_key("published") and item[4]:
# print("compare11:", title, link, time)
# print("compare22:", item[1], item[2], item[4])
# print("============")
time = await rfc2822_to_iso8601(entry.published)
if (item[1] == title and
item[2] == link and
item[4] == time):
valid = True
break
else:
if (item[1] == title and
item[2] == link):
# print("compare111:", title, link)
# print("compare222:", item[1], item[2])
# print("============")
valid = True
break
# TODO better check and don't repeat code
if not valid:
# print("id: ", item[0])
# if title:
# print("title: ", title)
# print("item[1]: ", item[1])
# if link:
# print("link: ", link)
# print("item[2]: ", item[2])
# if entry.id:
# print("last_entry:", entry.id)
# print("item[3]: ", item[3])
# if time:
# print("time: ", time)
# print("item[4]: ", item[4])
# print("read: ", item[5])
# breakpoint()
async with DBLOCK:
# TODO Send to table archive
# TODO Also make a regular/routine check for sources that
# have been changed (though that can only happen when
# manually editing)
ix = item[0]
# print(">>> SOURCE: ", source)
# print(">>> INVALID:", item[1])
# print("title:", item[1])
# print("link :", item[2])
# print("id :", item[3])
if item[5] == 1:
# print(">>> DELETING:", item[1])
sql = (
"DELETE "
"FROM entries "
"WHERE id = :ix"
)
cur.execute(sql, (ix,))
else:
# print(">>> ARCHIVING:", item[1])
sql = (
"INSERT "
"INTO archive "
"SELECT * "
"FROM entries "
"WHERE entries.id = :ix"
)
2023-11-23 17:55:36 +01:00
try:
cur.execute(sql, (ix,))
except:
print(
"ERROR DB inset from entries "
"into archive at index", ix
)
sql = (
"DELETE "
"FROM entries "
"WHERE id = :ix"
)
2023-11-23 17:55:36 +01:00
try:
cur.execute(sql, (ix,))
except:
print(
"ERROR DB deleting entries "
"from entries at index", ix
)
async def get_feeds(db_file):
"""
Query table feeds for Title, URL, Categories, Tags.
Parameters
----------
db_file : str
Path to database file.
Returns
-------
result : list
Title, URL, Categories, Tags of feeds.
"""
with create_connection(db_file) as conn:
cur = conn.cursor()
sql = (
"SELECT name, address, type, categories, tags "
"FROM feeds"
)
result = cur.execute(sql).fetchall()
return result
2023-07-16 17:23:44 +02:00
async def get_feeds_url(db_file):
2023-07-16 17:23:44 +02:00
"""
Query active feeds for URLs.
Parameters
----------
db_file : str
Path to database file.
Returns
-------
result : list
URLs of active feeds.
2023-07-16 17:23:44 +02:00
"""
with create_connection(db_file) as conn:
cur = conn.cursor()
sql = (
"SELECT address "
"FROM feeds "
"WHERE enabled = 1"
)
result = cur.execute(sql).fetchall()
return result
2023-07-16 17:23:44 +02:00
async def list_feeds(db_file):
2023-07-16 17:23:44 +02:00
"""
2023-10-04 14:37:31 +02:00
Query table feeds and list items.
Parameters
----------
db_file : str
Path to database file.
Returns
-------
msg : str
URLs of feeds as message.
2023-07-16 17:23:44 +02:00
"""
2023-10-04 14:37:31 +02:00
cur = get_cursor(db_file)
sql = (
"SELECT name, address, updated, enabled, id "
"FROM feeds"
)
2023-10-04 14:37:31 +02:00
results = cur.execute(sql)
feeds_list = "\nList of subscriptions:\n```\n"
counter = 0
for result in results:
counter += 1
feeds_list += (
"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
2023-07-16 17:23:44 +02:00
async def last_entries(db_file, num):
"""
2023-10-04 14:37:31 +02:00
Query entries
Parameters
----------
db_file : str
Path to database file.
num : str
Number.
Returns
-------
titles_list : str
List of recent N entries as message.
2023-07-16 17:23:44 +02:00
"""
num = int(num)
if num > 50:
num = 50
elif num < 1:
num = 1
2023-10-04 14:37:31 +02:00
cur = get_cursor(db_file)
# sql = (
# "SELECT title, link "
# "FROM entries "
# "ORDER BY ROWID DESC "
# "LIMIT :num"
# )
sql = (
"SELECT title, link "
"FROM entries "
"WHERE read = 0 "
"ORDER BY timestamp DESC "
"LIMIT :num "
)
2023-10-04 14:37:31 +02:00
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):
"""
Query feeds.
Parameters
----------
db_file : str
Path to database file.
query : str
Search query.
Returns
-------
titles_list : str
Feeds of specified keywords as message.
"""
cur = get_cursor(db_file)
sql = (
"SELECT name, address, id, enabled "
"FROM feeds "
"WHERE name LIKE ? "
"OR address LIKE ? "
"LIMIT 50"
)
results = cur.execute(sql, [f'%{query}%', f'%{query}%'])
results_list = (
"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)
2023-07-16 17:23:44 +02:00
async def search_entries(db_file, query):
"""
Query entries.
Parameters
----------
db_file : str
Path to database file.
query : str
Search query.
Returns
-------
titles_list : str
Entries of specified keywords as message.
2023-07-16 17:23:44 +02:00
"""
2023-10-04 14:37:31 +02:00
cur = get_cursor(db_file)
sql = (
"SELECT title, link "
"FROM entries "
"WHERE title LIKE ? "
"UNION ALL "
"SELECT title, link "
"FROM archive "
"WHERE title LIKE ? "
"LIMIT 50"
)
results = cur.execute(sql, (
f'%{query}%',
f'%{query}%'
))
results_list = (
"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)
"""
2023-12-01 14:22:03 +01:00
FIXME
Error due to missing date, but it appears that date is present:
2023-11-23 17:55:36 +01:00
ERROR DATE: source = https://blog.heckel.io/feed/
ERROR DATE: date = 2008-05-13T13:51:50+00:00
ERROR DATE: result = https://blog.heckel.io/feed/
2023-12-01 14:22:03 +01:00
19:32:05 ERROR DATE: source = https://mwl.io/feed
19:32:05 ERROR DATE: date = 2023-11-30T10:56:39+00:00
19:32:05 ERROR DATE: result = https://mwl.io/feed
19:32:05 ERROR DATE: source = https://mwl.io/feed
19:32:05 ERROR DATE: date = 2023-11-22T16:59:08+00:00
19:32:05 ERROR DATE: result = https://mwl.io/feed
19:32:06 ERROR DATE: source = https://mwl.io/feed
19:32:06 ERROR DATE: date = 2023-11-16T10:33:57+00:00
19:32:06 ERROR DATE: result = https://mwl.io/feed
19:32:06 ERROR DATE: source = https://mwl.io/feed
19:32:06 ERROR DATE: date = 2023-11-09T07:37:57+00:00
19:32:06 ERROR DATE: result = https://mwl.io/feed
"""
async def check_entry_exist(db_file, source, eid=None,
title=None, link=None, date=None):
2023-10-04 14:37:31 +02:00
"""
Check whether an entry exists.
If entry has an ID, check by ID.
If entry has timestamp, check by title, link and date.
Otherwise, check by title and link.
Parameters
----------
db_file : str
Path to database file.
source : str
Feed URL. URL of associated feed.
eid : str, optional
Entry ID. The default is None.
title : str, optional
Entry title. The default is None.
link : str, optional
Entry URL. The default is None.
date : str, optional
Entry Timestamp. The default is None.
Returns
-------
bool
True or None.
2023-07-16 17:23:44 +02:00
"""
cur = get_cursor(db_file)
if eid:
sql = (
"SELECT id "
"FROM entries "
"WHERE entry_id = :eid and source = :source"
)
result = cur.execute(sql, {
"eid": eid,
"source": source
}).fetchone()
elif date:
sql = (
"SELECT id "
"FROM entries "
"WHERE "
"title = :title and "
"link = :link and "
"timestamp = :date"
)
try:
result = cur.execute(sql, {
"title": title,
"link": link,
"timestamp": date
}).fetchone()
except:
print(await current_time(), "ERROR DATE: source =", source)
print(await current_time(), "ERROR DATE: date =", date)
else:
sql = (
"SELECT id "
"FROM entries "
"WHERE title = :title and link = :link"
)
result = cur.execute(sql, {
"title": title,
"link": link
}).fetchone()
try:
if result:
return True
else:
None
except:
print(await current_time(), "ERROR DATE: result =", source)
async def set_settings_value(db_file, key_value):
"""
Set settings value.
Parameters
----------
db_file : str
Path to database file.
key_value : list
key : str
enabled, filter-allow, filter-deny,
interval, masters, quantum, random.
value : int
Numeric value.
"""
# if isinstance(key_value, list):
# key = key_value[0]
# val = key_value[1]
# elif key_value == "enable":
# key = "enabled"
# val = 1
# else:
# key = "enabled"
# val = 0
key = key_value[0]
val = key_value[1]
async with DBLOCK:
with create_connection(db_file) as conn:
cur = conn.cursor()
await set_settings_value_default(cur, key)
sql = (
"UPDATE settings "
"SET value = :value "
"WHERE key = :key"
)
cur.execute(sql, {
"key": key,
"value": val
})
# TODO Place settings also in a file
async def set_settings_value_default(cur, key):
"""
Set default settings value, if no value found.
Parameters
----------
cur : object
Cursor object.
key : str
Key: enabled, interval, master, quantum, random.
Returns
-------
val : str
Numeric value.
"""
# async def set_settings_value_default(cur):
# keys = ["enabled", "interval", "quantum"]
# for i in keys:
# sql = "SELECT id FROM settings WHERE key = ?"
# cur.execute(sql, (i,))
# if not cur.fetchone():
# val = await settings.get_value_default(i)
# sql = "INSERT INTO settings(key,value) VALUES(?,?)"
# cur.execute(sql, (i, val))
sql = (
"SELECT id "
"FROM settings "
"WHERE key = ?"
)
cur.execute(sql, (key,))
if not cur.fetchone():
val = await confighandler.get_value_default(key)
sql = (
"INSERT "
"INTO settings(key,value) "
"VALUES(?,?)"
)
cur.execute(sql, (key, val))
return val
async def get_settings_value(db_file, key):
"""
Get settings value.
Parameters
----------
db_file : str
Path to database file.
key : str
Key: "enabled", "interval", "master", "quantum", "random".
Returns
-------
val : str
Numeric value.
"""
# try:
# with create_connection(db_file) as conn:
# cur = conn.cursor()
# sql = "SELECT value FROM settings WHERE key = ?"
# cur.execute(sql, (key,))
# result = cur.fetchone()
# except:
# result = await settings.get_value_default(key)
# if not result:
# result = await settings.get_value_default(key)
# return result
with create_connection(db_file) as conn:
try:
cur = conn.cursor()
sql = (
"SELECT value "
"FROM settings "
"WHERE key = ?"
)
val = cur.execute(sql, (key,)).fetchone()[0]
except:
val = await set_settings_value_default(cur, key)
if not val:
val = await set_settings_value_default(cur, key)
return val