linkulator2/linkulator

307 lines
10 KiB
Plaintext
Raw Normal View History

2019-11-15 19:45:01 +00:00
#!/usr/bin/env python3
## If this script contains bugs, blame cmccabe.
import glob
import getpass
import os
from pathlib import Path
from shutil import which
2019-11-18 17:49:54 +00:00
import signal
2019-11-15 19:45:01 +00:00
import sys
2019-11-18 12:22:31 +00:00
import subprocess
import time
2019-11-15 19:45:01 +00:00
username = getpass.getuser()
browser = "lynx"
help_text = """
options: -h or --help; -p or --post; or no option to browse links.
Linkulator is a minimalist, commandline link aggregator for small, trusting shell communities.
A few important points about Linkulator:
* Your username is associated with everything you post. No real anonymity.
* You may ignore other users by adding their username to your ~/.linkulator/ignore file,
followed by an optional description of why you're ignoring them.
* Link post categories are created dynamically. Think before you create a new category and
don't litter the board.
* No files are stored centrally. Each users' contributions are stored in ~/.linkulator/,
meaning that you may always edit or delete your own files. Please don't use this ability
to deceive others.
* Link/reply threads disappear if the original link poster deletes their post; or if you
put their username in your ignore file.
* Your ~/.linkulator/linkulator_data file must be readable by others, or nobody else will
see your contributions.
* Linkulator may not work outside of Linux systems.
"""
2019-11-15 19:45:01 +00:00
2019-11-18 17:49:54 +00:00
pipe_count = 4 ## A PROPERLY FORMATED LINE IN linkulator.data HAS EXACTLY FOUR PIPES.
2019-11-16 12:21:31 +00:00
link_data = []
## username, datestamp, parent-id, category, link-url, link-title
2019-11-16 12:21:31 +00:00
categories = []
category_counts = {}
ignore_names = []
# READS THE CURRENT USER'S IGNORE FILE AND ADDS ANY ENTRIES TO THE GLOBAL VARIABLE
# IGNORE NAMES.
def parse_ignore_file():
global ignore_names
p = Path(Path.home(), '.linkulator/ignore')
if p.exists():
s = p.read_text()
l = s.splitlines()
for line in l:
name = line.split(" ")[0]
ignore_names.append(name)
2019-11-16 12:21:31 +00:00
def build_menu():
2019-11-16 12:21:31 +00:00
## WHENEVER THIS FUNCTION IS CALLED, THE DATA IS REFRESHED FROM FILES. SINCE
## DISK IO IS PROBABLY THE HEAVIEST PART OF THIS SCRIPT, DON'T DO THIS OFTEN.
2019-11-15 19:45:01 +00:00
linkulator_files = glob.glob('/home/*/.linkulator/linkulator.data')
if len(linkulator_files) == 0:
print("It looks link there are no links yet. Run 'linkulator -p' to add one.")
2019-11-18 03:04:57 +00:00
exit()
2019-11-15 19:45:01 +00:00
else:
2019-11-16 12:21:31 +00:00
global link_data
2019-11-15 19:45:01 +00:00
linkulator_lines = []
2019-11-16 12:21:31 +00:00
for filename in linkulator_files:
with open(filename) as f:
file_owner = filename.split('/')[2]
if file_owner in ignore_names:
continue ## IGNORE NAMES IN ignore_file
2019-11-15 19:45:01 +00:00
for line in f:
2019-11-18 17:49:54 +00:00
if line.count("|") != pipe_count:
continue ## IGNORE LINES THAT AREN'T FORMATTED PROPERLY.
2019-11-16 12:21:31 +00:00
line = line.rstrip('\n')
split_line = line.split('|')
split_line.insert(0,file_owner)
linkulator_lines.append(split_line) ## creating a list of lists
2019-11-18 03:04:57 +00:00
i=1
for idx, line in enumerate(linkulator_lines):
if line[2] == "": # CREATE/INSERT PARENT ID:
linkulator_lines[idx].insert(0, i)
i=i+1
else: ## NOT PARENT, SO NO PARENT ID
linkulator_lines[idx].insert(0,"")
2019-11-16 12:21:31 +00:00
link_data = linkulator_lines
## THIS IS SUPPOSED TO SORT ALL LINKS BY CREATION DATE. NEED TO CONFIRM THAT IT WORKS.
link_data.sort(key=lambda x: x[2])
2019-11-16 12:21:31 +00:00
global categories
global category_counts
categories = []
category_counts.clear() ## CLEAR SO WE DON'T DOUBLE-COUNT IF FNC RUN MORE THAN ONCE.
2019-11-16 12:21:31 +00:00
for line in link_data:
if line[4] not in categories and line[4] != "":
categories.append(line[4])
category_counts[line[4]] = 1
elif line[4] in categories:
category_counts[line[4]] = category_counts[line[4]] + 1
2019-11-18 03:04:57 +00:00
def print_categories():
2019-11-16 12:21:31 +00:00
print("Current link post categories include: ")
for i in categories:
print(" * " + i + "(" + str(category_counts[i]) + ")")
# print(categories)
2019-11-16 12:21:31 +00:00
view_category_contents()
2019-11-16 12:21:31 +00:00
def view_category_contents():
view_cat = ""
while view_cat not in categories:
view_cat = input("View category (or hit [Enter] to quit): ")
2019-11-17 20:13:01 +00:00
if view_cat == "":
2019-11-18 03:04:57 +00:00
graceful_exit()
2019-11-16 12:21:31 +00:00
if view_cat not in categories:
print("Sorry, that category does not exist. Try again.")
2019-11-18 03:04:57 +00:00
2019-11-16 12:21:31 +00:00
for line in link_data:
if line[4] == view_cat:
print("ID:", line[0], "|", line[6], "|", line[5], "|", line[1])
2019-11-18 03:04:57 +00:00
2019-11-18 12:22:31 +00:00
pid = input("Enter a post ID to see its thread, \"m\" to return to the main menu, or hit [Enter] to quit: ")
2019-11-18 03:04:57 +00:00
if pid == "": ## HARMLESS BUT UNINTENDED
graceful_exit() ## ABILITY HERE IS THAT USERS
2019-11-18 12:22:31 +00:00
elif pid =="m" or pid == "M": ## CAN PUT ANY PID IN, NOT JUST
print_categories() ## FROM WITHIN THIS CATEGORY.
return()
else:
view_thread(pid)
def view_thread(post_id):
parent_id = ""
2019-11-18 12:22:31 +00:00
url = ""
for line in link_data:
if str(line[0]) == post_id:
parent_id = line[1] + "+" + str(line[2])
parent_user = line[1]
parent_timestamp = line[2]
2019-11-18 12:22:31 +00:00
url = line[5]
2019-11-18 03:04:57 +00:00
if parent_id == "":
print("Sorry, no thread found with that ID.")
print_categories() ## THIS IS NOT A GOOD END POINT. SHOULD ASK USER TO RE-ENTER THEIR CHOICE.
for line in link_data:
if line[1] == parent_user and line[2] == parent_timestamp:
ftime = time.strftime("%b %d %Y", time.gmtime(float(parent_timestamp))) ## UGGHH...
print("\nLink: ", line[6] + " (" + line[5] + "),\n posted by " + line[1] + " on " + ftime)
print("\nReplies:")
i = 0
for line in link_data:
if line[3] == parent_id:
print(line[1] + ": " + line[6])
i = i+1
if i == 0:
print("(No replies yet. Be the first!)\n")
next_step = input("Type 'R' to reply, 'B' to view in " + browser + ", 'M' for main menu, or anything else to quit: ")
if next_step == "M" or next_step == "m":
print_categories()
2019-11-18 12:22:31 +00:00
elif next_step =="B" or next_step == "b":
view_link_in_browser(url, post_id)
elif next_step == "R" or next_step == "r":
reply(parent_user, parent_timestamp, post_id)
else:
2019-11-18 03:04:57 +00:00
graceful_exit()
2019-11-15 19:45:01 +00:00
2019-11-18 12:22:31 +00:00
def view_link_in_browser(url, post_id):
if which(browser) is None:
print("Sorry, " + browser + " is not installed on your system. Ask your sysadmin to install it.")
view_thread(post_id)
2019-11-18 12:22:31 +00:00
if url.startswith("gopher://") or url.startswith("https://") or url.startswith("http://"):
subprocess.call(['lynx', url])
else:
print("Sorry, that url doesn't start with gopher://, http:// or https://")
tryAnyway = input("Do you want to try it in", browser, "anyway? Y/[N] ")
2019-11-18 17:49:54 +00:00
if tryAnyway == "Y" or tryAnyway == "y":
subprocess.call(['lynx', url])
2019-11-18 12:22:31 +00:00
view_thread(post_id)
2019-11-15 19:45:01 +00:00
def post_link():
2019-11-18 17:49:54 +00:00
print("Enter link information here. Leaving any field blank aborts post.")
link_url = ""
while link_url == "":
link_url = input("URL: ")
if "|" in link_url:
print("Pipes, \"|\", are illegal characters in Linkulator. Please try again.")
link_url = ""
elif link_url == "":
graceful_exit()
link_category = ""
while link_category == "":
link_category = input("Category: ")
if "|" in link_category:
print("Pipes, \"|\", are illegal characters in Linkulator. Please try again.")
link_category = ""
elif link_category == "":
graceful_exit()
link_title = ""
while link_title == "":
link_title = input("Title: ")
if "|" in link_title:
print("Pipes, \"|\", are illegal characters in Linkulator. Please try again.")
link_title = ""
elif link_title == "":
graceful_exit()
2019-11-15 19:45:01 +00:00
timestamp = str(time.time())
filename = '/home/' + username + '/.linkulator/linkulator.data'
if os.path.exists(filename):
append_write = 'a' # append if already exists
else:
append_write = 'w+' # make a new file if not
2019-11-15 19:45:01 +00:00
with open(filename, append_write) as file:
file.write(timestamp + '||' + link_category + '|' + link_url + '|' + link_title + "\n")
2019-11-18 03:04:57 +00:00
print("Link added!")
graceful_exit()
2019-11-18 17:49:54 +00:00
2019-11-15 19:45:01 +00:00
def reply(owner, tstamp, post_id):
global username
comment = input("Enter your comment: ")
filename = '/home/' + username + '/.linkulator/linkulator.data'
if os.path.exists(filename):
append_write = 'a' # append if already exists
else:
append_write = 'w+' # make a new file if not
with open(filename, append_write) as file:
timestamp = str(time.time())
file.write(timestamp + '|' + owner + "+" + tstamp + '|||' + comment + "\r")
x = input('Reply added. Hit [Enter] to return to thread.')
build_menu()
view_thread(post_id)
2019-11-16 12:21:31 +00:00
2019-11-19 14:52:35 +00:00
def search(keyword):
print("Doesn't work yet. Would be searching title, category, comment for ", keyword)
2019-11-19 20:19:28 +00:00
## PSEUDOCODE:
## results_found = ""
## for line in link_data:
## if keyword in link_data[title] or keyword in link_data[category] or keyword in link_data[comment]:
## results_found = "yes"
## if line is parent post:
## print line
## elif line is reply:
## get parentID
## print(line[parentID])
## if results_found == "":
## print("No results found")
## else:
## next_step = input("Enter ID to view thread, "M" for main menu, or [Enter] to quit: ")
## if next_step...
2019-11-18 03:04:57 +00:00
def graceful_exit():
print("\n\nThank you for linkulating. Goodbye.\n")
2019-11-18 17:49:54 +00:00
exit(0)
def signal_handler(sig, frame):
graceful_exit()
signal.signal(signal.SIGINT, signal_handler)
2019-11-18 03:04:57 +00:00
2019-11-16 12:21:31 +00:00
2019-11-15 19:45:01 +00:00
def parse_command():
args = sys.argv[1:]
if not len(args):
print("----------")
print("LINKULATOR")
print("----------")
parse_ignore_file()
build_menu()
print_categories()
2019-11-15 19:45:01 +00:00
elif args[0] in ["-h", "--help", "help"]:
print(help_text)
elif args[0] in ["-p", "--post", "-p"]:
post_link()
else:
print("Unknown command: {}".format(args[0]))
if __name__ == '__main__':
parse_command()