You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
recentxx/recent++

279 lines
10 KiB

#!/usr/bin/env python3
# recent++ for rawtext.club by samhunter
# https://git.rawtext.club/samhunter/recentxx
# inspired by:
# https://git.rawtext.club/hannu/recent
import glob
import sys
import os
import time
import datetime
import sqlite3 as sql
import json
import subprocess
import getpass
import argparse
# default timespan shown (in days)
DAYS = 14
MARKER = "-" * 20
IGNORE = ["/home/mieum/public_gemini/rtc-recent",
"/home/mieum/public_gopher/rtc-recent",
"/home/mieum/public_html/rtc-recent",
"/home/hedy/public_gemini/recent.txt",
"/home/sloum/public_gemini/spacewalk.gmi"]
parser = argparse.ArgumentParser(description='Python rewrite of the venerable "recent" utility for RTC')
parser.add_argument("-c", "--compatible", help="Emulates behaviour of the standard 'recent'", action="store_true")
parser.add_argument("-e", "--exitcode", help="Returns RC>1 if no changes since the last check. No display.", action="store_true")
parser.add_argument("-m", "--mark", help="Set 'read' marker to current time.", action="store_true")
filters = parser.add_argument_group("filtering")
uparser = filters.add_mutually_exclusive_group()
uparser.add_argument("-u", "--user", help="Comma separated list of usernames to include in the report", type=lambda x:x.split(","))
uparser.add_argument("-U", "--exclude-user", metavar="USER", help="Comma separated list of usernames to exclude in the report", type=lambda x:x.split(","))
sparser = filters.add_mutually_exclusive_group()
sparser.add_argument("-s", "--service", help="Comma separated list of services to include in the report", type=lambda x:x.split(","))
sparser.add_argument("-S", "--exclude-service", metavar="SERVICE", help="Comma separated list of services to exclude in the report", type=lambda x:x.split(","))
filters.add_argument("--since", metavar="TIME", help="Show items newer than TIME, provided in Unix time format")
args = parser.parse_args()
def get_files():
services = dict([
('gab','.gab'),
('shlog','.shlog'),
('linkulator','.linkulator/linkulator.data'),
('iris','.iris.messages'),
('who-is','.who-is'),
('oneliner', '.oneliner'),
('fortune', '.fortune'),
('train','.choochoo'),
('library','books'),
('gopher','public_gopher'),
('gemini','public_gemini'),
('web','public_html'),
('spartan','public_spartan'),
('spacelaunch','.vroom'),
('gitbbs','.gitbbs'),
('plan','.plan'),
('project','.project'),
('poll','.rtc/polls.templates'),
('mail','Maildir/new'),
('botany','.botany/*_plant_data.json'),
('capitalist','.local/share/capitalist-scores'),
('wiki','.wiki'),
(MARKER,'.config/recentxx')
])
if args.service:
services = dict(filter(lambda x : x[0] in args.service, services.items()))
elif args.exclude_service:
services = dict(filter(lambda x : x[0] not in args.exclude_service, services.items()))
# add gab channels the current user was/is active in
if (not args.service or "gab" in args.service) and (not args.exclude_service or "gab" not in args.exclude_service):
files = glob.glob("/home/"+getpass.getuser()+"/.gab-*")
for filepath in files:
service_path = filepath.split('/')[-1]
if args.compatible == True:
service = "gab-" + service_path[5:10]
else:
service = "gab-" + service_path[5:]
services[service]=service_path
ret = []
users = [[x, "/home/{}".format(x)] for x in os.listdir("/home/") if os.access("/home/"+x, os.R_OK)]
if args.user:
users = filter(lambda x : x[0] in args.user, users)
elif args.exclude_user:
users = filter(lambda x : x[0] not in args.exclude_user, users)
for user in users:
if not os.path.isdir(user[1]):
pass
for service in services.keys():
if service == "mail" and user[0] != getpass.getuser():
continue
if service == MARKER and user[0] != getpass.getuser():
continue
files = glob.glob(user[1] + '/' + str(services[service]))
for filepath in files:
try:
if os.path.isdir(filepath):
filepath =max(glob.glob(filepath+'/*', recursive=True), key=os.path.getmtime)
ret.append( (filepath, user[0], service) )
except:
continue
return ret
if args.since:
if args.since[-1] == "d":
DAYS = 1 * int(args.since[0:-1])
ts = time.time() - (24 * 3600 * DAYS)
elif args.since[-1] == "w":
DAYS = 7 * int(args.since[0:-1])
ts = time.time() - (24 * 3600 * DAYS)
elif args.since[-1] == "m":
DAYS = 30 * int(args.since[0:-1])
ts = time.time() - (24 * 3600 * DAYS)
else:
ts = int(args.since)
else:
ts = time.time() - (24 * 3600 * DAYS)
out = []
timefmt = "%Y-%m-%d %H:%M"
for d in get_files():
service=d[2]
user=d[1]
if service[0:3] == "gab" and os.access(d[0], os.R_OK):
with open(d[0]) as f:
try:
ftim = float(f.readline().split("|")[0])
except:
continue
if ftim > ts:
st = datetime.datetime.fromtimestamp(ftim).strftime(timefmt)
out.append((st,d))
elif service == "linkulator":
with open(d[0]) as f:
try:
# read last line
last_line = f.readlines()[-1]
ftim = float(last_line.split("|")[0])
except:
continue
if ftim > ts:
st = datetime.datetime.fromtimestamp(ftim).strftime(timefmt)
out.append((st,d))
elif service == "mail": #XXX
with open(d[0]) as f:
ftim = os.stat(d[0]).st_mtime
try:
ftim = time.time()
lines = f.readlines()
for line in lines:
if line.startswith("Date:"):
fdat = line.strip()
fdat = fdat.replace('Date: ','')
try:
ftim = time.mktime(datetime.datetime.strptime(fdat, "%a, %d %b %Y %H:%M:%S %z").timetuple())
except:
ftim = os.stat(d[0]).st_mtime
if line.startswith("Return-Path:"):
try:
ffrom = line.strip()
ffrom = ffrom.replace('Return-Path: ','').replace("<","").replace(">","")
except:
ffrom ="---"
except:
continue
d = (d[0], ffrom, d[2])
#if ftim > ts:
st = datetime.datetime.fromtimestamp(ftim).strftime(timefmt)
out.append((st,d))
elif service == "botany":
try:
with open(d[0]) as f:
ftim = json.load(f)["last_watered"]
except subprocess.CalledProcessError as e:
continue
if ftim > ts:
st = datetime.datetime.fromtimestamp(ftim).strftime(timefmt)
out.append((st,d))
elif service == "gitbbs":
try:
ftim = float(subprocess.check_output( ['/usr/bin/git', '-C', d[0], 'log', '--pretty=format:%at', '-n1'], stderr=subprocess.STDOUT ).decode('ascii'))
except subprocess.CalledProcessError as e:
continue
if ftim > ts:
st = datetime.datetime.fromtimestamp(ftim).strftime(timefmt)
out.append((st,d))
elif service == "iris":
with open(d[0]) as f:
ftim = os.stat(d[0]).st_mtime
try:
# read last line
last_line = f.readlines()[-1]
# empty = new user
if last_line.startswith("[]"):
continue
except:
continue
if ftim > ts:
st = datetime.datetime.fromtimestamp(ftim).strftime(timefmt)
out.append((st,d))
else:
if d[0] not in IGNORE:
ftim = os.stat(d[0]).st_mtime
if ftim > ts:
st = datetime.datetime.fromtimestamp(ftim).strftime(timefmt)
out.append((st,d))
if (not args.service or "cspc" in args.service) and (not args.exclude_service or "cspc" not in args.exclude_service):
db_path = '/usr/local/share/cspc/db/cspc'
if os.access(db_path, os.R_OK):
query = '''SELECT max(last_updated), author, max(type) FROM (
SELECT
MAX(last_updated) AS last_updated,
author,
CASE type
WHEN 'topic' THEN 1
WHEN 'post' THEN 2
WHEN 'reply' THEN 3
END AS type
FROM data
WHERE last_updated > ''' +str(ts)+ '''
GROUP BY author, type)
GROUP BY last_updated'''
try:
conn = sql.connect(db_path)
c = conn.cursor()
c.execute(query)
for row in c:
st = datetime.datetime.fromtimestamp(float(row[0])).strftime(timefmt)
if (not args.user or row[1] in args.user) and (not args.exclude_user or row[1] not in args.exclude_user):
out.append((st, ("", row[1],"cspc")))
conn.close()
except:
pass
finally:
conn.close()
if args.compatible == True:
fmt = "{date:16s} {service:>11s} {user:14s}"
else:
fmt = "{date:16s} {service:20s} {user:14s}"
if args.mark == True:
markfile = "/home/"+getpass.getuser()+"/.config/recentxx"
os.utime(markfile)
if len(out) > 0:
if args.exitcode == False:
for item in sorted(out, reverse=True):
date=item[0]
user=item[1][1]
service=item[1][2]
print(fmt.format(date=date,user=user,service=service.split('/')[0]))
exit(0)
else:
if args.exitcode == False:
if args.since:
sys.stderr.write("No results in the specified period.\n")
else:
sys.stderr.write(
f'''No results in the last {DAYS} days.
Use --since <Unix time> to extend the search, or
--since 0 to disable filtering by time fully
''')
exit(1)