From 91406f02b2a87e0dbfaf8a391edd049c25563848 Mon Sep 17 00:00:00 2001 From: sn4il Date: Tue, 27 Jun 2023 10:59:14 +0300 Subject: Initial commit --- bin/gab | 357 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 357 insertions(+) create mode 100755 bin/gab (limited to 'bin/gab') diff --git a/bin/gab b/bin/gab new file mode 100755 index 0000000..46d5fc7 --- /dev/null +++ b/bin/gab @@ -0,0 +1,357 @@ +#!/usr/bin/env python3 +import os +import sys +import time +from datetime import datetime +import subprocess as sp +from subprocess import call +import tempfile + +LIST_MESSAGES_LIMIT = "30" + +chan_dir = ".config/gab/channels" ## not used yet. todo: move all gab channel logs to this dir +current_user = os.environ.get('USER') +log = {} + +help_text = """ +GAB - A simple chat interface + +syntax: gab [flag] [value] + +flag value +-------------------- --------------- +-h, --help, help +-m, --msg, msg Quoted text with the msg being added to chat +-l, --list, list An integer representing the number of rows you'd like + to view, default 5 +-b, --block, block A username to block/ignore +-u, --unblock, unblock A username to unblock/unignore +-c, --channel, channel Name of the channel you would like to join or create. + An empty value lists the available channels. +-n, --new, new Check for new, unread messages. +-N, --new-verbose, new-verbose Check for new, unread messages, creating output + even when no new messages exist. + +Channels names ending in ! are private. They can be joined by anyone that +knows they exist, but will not be listed in the channel listings. To return +to the main/default channel, simply switch channels to 'gab': + gab channel gab + +Examples: + gab msg "Good afternoon!" + gab list 20 + gab block annoyingperson + gab channel my-secret-channel! + gab channel a-public-channel + gab unblock somedude +""" + +title = "\033[1mGAB v2.2\033[0m" + +def get_chan(): + fp = "/home/{}/.gab_chan".format(current_user) + + if not os.path.isfile(fp): + return "" + + with open(fp, 'r') as chanfile: + channel = chanfile.read().split("\n")[0].strip() + if channel: + channel = "-" + channel + return channel + +def blocked_users(): + fp = "/home/{}/.gab_block".format(current_user) + + if not os.path.isfile(fp): + return [] + + with open(fp, 'r') as blockfile: + return blockfile.read().split("\n") + +def get_files(channel): + return [[x, "/home/{}/.gab{}".format(x, channel)] for x in os.listdir("/home/") if x not in blocked_users() and os.access("/home/"+x, os.R_OK)] + + +def get_user_channels(user_dir): + chans = {"gab"} + for y in os.listdir(user_dir): + if len(y) > 5 and y[:5] == ".gab-" and y[-1] != "!": + chans.add(y[5:]) + return chans + + +def get_chan_list(): + chans = {"gab"} + for x in os.listdir("/home/"): + if not os.access("/home/" + x, os.R_OK): + continue + for y in os.listdir("/home/" + x): + if len(y) > 5 and y[:5] == ".gab-" and y[-1] != "!": + chans.add(y[5:]) + return chans + + +def list_channels(): + chans = get_chan_list() + chan = get_chan() + print(title) + print("\n\033[1mAvailable channels\033[0m:") + for x in chans: + if chan[1:] == x: + x = x + " *" + elif chan == "" and x == "gab": + x = x + " *" + print(x) + + +def read_file(user, path, num_lines): + global log + if os.path.isfile(path) and os.access(path, os.R_OK): + if "/home/" + current_user + "/" in path: + os.utime(path, None) ## update file's timestamp on each read + with open(path, "r") as f: + for line in range(num_lines): + msg = f.readline().split("|", 1) + dt = msg[0].strip() + if len(msg) == 2 and float(dt) < time.time(): + msg = msg[1].strip() + if msg[:3] == "/me": + log[dt] = "\033[7m * * * \033[0m \033[3m{}\033[0m".format(msg.replace("/me", user)) + else: + log[dt] = "\033[7m {} \033[0m {}\033[0m".format(user, msg) + + +def list_messages(count=LIST_MESSAGES_LIMIT): + global log + chan = get_chan() + files = get_files(chan) + + try: + count = int(count) + except ValueError: + print("Error parsing list number, using default: 5") + count = 5 + + for x in files: + read_file(x[0], x[1], count) + + sorted_keys = list(log.keys()) + if len(sorted_keys) > 0: + sorted_keys.sort(reverse=True) + last_time = float(sorted_keys[0]) + sorted_keys = sorted_keys[:min(len(sorted_keys), count)] + sorted_keys.sort(reverse=False) + print(title) + print("Current channel: \033[1m{}\033[0m".format(chan[1:] if chan else "gab")) + print("Last message: {}".format(diff_time_from_now(last_time))) + print("\n- - -") + for key in range(min(count, len(sorted_keys))): + print(log[sorted_keys[key]]) + print("- - -") + else: + print(title) + print("Current channel: \033[1m{}\033[0m".format(chan[1:] if chan else "gab")) + print() + print("There are no messages to display") + + +def diff_time_from_now(t, n=time.time()): + diff = n - t + diff = int(diff) + out = "" + + # Days + if diff // (3600 * 24) > 0: + days = diff // (3600 * 24) + out += "{}d ".format(days) + diff = diff - (days * 3600 * 24) + + # Hours + if diff // 3600 > 0: + out += "{}h ".format(diff // 3600) + diff = diff - (diff // 3600 * 3600) + + # Minutes + if diff // 60 > 0: + out += "{}m ".format(diff // 60) + diff = diff - (diff // 60 * 60) + + # Seconds + out += "{}s ago".format(diff) + + return out + + +def add_message(msg): + timestamp = str(time.time()) + output = "{}|{}\n".format(timestamp, msg.strip()) + fp = "/home/{}/.gab{}".format(current_user, get_chan()) + try: + with open(fp, 'r') as original: + data = original.read(12000) + with open(fp, 'w') as modified: + modified.write(output) + modified.write(data) + except FileNotFoundError: + with open(fp, 'a') as modified: + modified.write(output) + except: + print("Error adding text to chatlog") + return + + print("Successfully added text to chatlog") + + try: + os.chmod(fp, 0o644) + except PermissionError: + print("You do not have permission to modify the chatlog") + except FileNotFoundError: + print("The chatlog does not exist") + + +def block_user(user_to_block): + fp = "/home/{}/.gab_block".format(current_user) + block_fp = "/home/{}/".format(user_to_block) + if not os.path.isdir(block_fp): + print("User '{}' does not exist, no action taken".format(user_to_block)) + return + + with open(fp, 'a') as blockfile: + blockfile.write(user_to_block) + blockfile.write("\n") + + print("User '{}' has been blocked, you will not see their messages".format(user_to_block)) + return + + +def unblock_user(user_to_unblock): + fp = "/home/{}/.gab_block".format(current_user) + unblock_fp = "/home/{}/".format(user_to_unblock) + + if not os.path.isfile(fp): + print("You do not have any users on your block list, no action taken") + return + + if not os.path.isdir(unblock_fp): + print("User '{}' does not exist, no action taken".format(user_to_unblock)) + return + + blocked_users_list = [x for x in blocked_users() if x != user_to_unblock] + + with open(fp, 'w') as blocked: + blocked.write("\n".join(blocked_users_list)) + + print("User '{}' has been removed from your block list".format(user_to_unblock)) + + +def switch_channel(newchan): + chan = newchan + if newchan == "gab": + newchan = "" + + fp = "/home/{}/.gab_chan".format(current_user) + + with open(fp, 'w') as chanfile: + chanfile.write(newchan) + + print("You are now viewing the '{}' channel.".format(chan)) + list_messages() + + +def new_scan(silent=True): + unread_chans = [] + chans = get_user_channels("/home/" + current_user) + + for c in chans: + my_timestamp = chan_timestamp = 0.0 + if c == "gab": + files = get_files("") + else: + files = get_files("-" + c) + + for f in files: + if os.path.isfile(f[1]): + try: + with open(f[1]) as fh: + timestamp = float(fh.readline().split("|")[0]) + if timestamp > float(chan_timestamp): + chan_timestamp = timestamp + if f[0] == current_user: + atime= time.ctime(os.stat(f[1]).st_atime) + my_timestamp = time.mktime(datetime.strptime(atime, "%a %b %d %H:%M:%S %Y").timetuple()) + except: + continue + if my_timestamp - chan_timestamp < 0: + unread_chans.append(c) + + if len(unread_chans): + print("New gab messages in: " + ", ".join(unread_chans)) + else: + if silent == False: + print("gab: no unread messages.") + exit(1) + + +def parse_command(): + args = sys.argv[1:] + if not len(args): + list_messages() + elif args[0] in ["-h", "--help", "help"]: + print(help_text) + elif args[0] in ["list", "--list", "-l"]: + if len(args) == 2: + list_messages(args[1]) + else: + print("Invalid command input to 'list'") + elif args[0] in ["new", "--new", "-n"]: + new_scan() + elif args[0] in ["new-verbose", "--new-verbose", "-N"]: + new_scan(silent=False) + elif args[0] in ["-m", "--msg", "msg"]: + if len(args) < 2: + # code from https://stackoverflow.com/a/6309753 + EDITOR = os.environ.get('EDITOR') if os.environ.get('EDITOR') else 'vi' + with tempfile.NamedTemporaryFile(suffix=".tmp") as tf: + tf.flush() + call([EDITOR, tf.name]) + tf.seek(0) + edited_message = tf.read() + lines = edited_message.decode("utf-8").split("\n") + for line in lines: + if len(line.split()): + add_message(line) + + elif len(args) > 2: + print("Expected a message, but received too many arguments. Did you put your message in quotes?") + else: + add_message(args[1]) + elif args[0] in ["-b", "--block", "block"]: + if len(args) < 2: + print("Expected a user to block, but one was not received") + elif len(args) > 2: + print("Expected a user to block, but received too many arguments") + else: + block_user(args[1]) + elif args[0] in ["-u", "--unblock", "unblock"]: + if len(args) < 2: + print("Expected a user to unblock, but one was not received") + elif len(args) > 2: + print("Expected a user to unblock, but received too many arguments") + else: + unblock_user(args[1]) + elif args[0] in ["-c", "--channel", "channel"]: + if len(args) < 2: + # print("Returning to default channel.") + # switch_channel("gab") + list_channels() + elif len(args) > 2: + print("Expected a channel to join, but received too many arguments") + else: + switch_channel(args[1]) + else: + print("Unknown command: {}".format(args[0])) + + +if __name__ == '__main__': + parse_command() -- cgit v1.2.3