From 8b6c9697e02ee05983955dedaecef45312ac294c Mon Sep 17 00:00:00 2001 From: zgrep Date: Tue, 14 Aug 2018 03:24:50 -0400 Subject: [PATCH] The happybot revolves around the Earth. The Earth revolves around the God. The God gives you a world full of viral diseases. You curse God. You revolve around God. God revolves around a door. The door opens. This is going nowhere, you can stop reading now. I said you could stop. Okay, that's it, I'm really ending it now. --- color.py | 51 ++++++++ comics.py | 259 +++++++++++++++++++++++++++++++++++++++ happy | 3 + happybot/ichi.py | 24 ---- happybot/ichi.sh | 9 -- happys | 3 + hateweekend | 5 + hateweekstart | 5 + hhcdlog.sh | 4 + ichi.py | 77 ++++++++++++ iiundie | 1 + privmsg | 5 + rfk/rfk.py | 313 +++++++++++++++++++++++++++++++++++++++++++++++ urls.py | 140 +++++++++++++++++++++ 14 files changed, 866 insertions(+), 33 deletions(-) create mode 100644 color.py create mode 100644 comics.py create mode 100644 happy delete mode 100644 happybot/ichi.py delete mode 100644 happybot/ichi.sh create mode 100644 happys create mode 100644 hateweekend create mode 100644 hateweekstart create mode 100644 hhcdlog.sh create mode 100644 ichi.py create mode 100644 iiundie create mode 100644 privmsg create mode 100644 rfk/rfk.py create mode 100644 urls.py diff --git a/color.py b/color.py new file mode 100644 index 0000000..50bd40a --- /dev/null +++ b/color.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +special = { + 'heart': '\u2764', + 'zwsp': '\u200b', + } + +colors = {name: code for code, *names in map(str.split, ''' +00 white w +01 black b +02 blue navy navyblue +03 green +04 red 05 brown maroon +06 purple +07 orange olive +08 yellow +09 lightgreen lime +10 teal greenbluecyan +11 lightcyan cyan aqua +12 lightblue royalblue royal +13 pink lightpurple fuchsia +14 grey gray +15 lightgrey lightgray silver +99 default +'''.strip().split('\n')) for name in names} + +from sys import argv +chan = argv[1] +output = '' +for arg in argv[2:]: + try: + if arg[0] == '-': + arg = arg[1:] + if arg[0] == '-': + output += arg + elif arg in special: + output += special[arg] + elif arg[0] == 'x': + output += chr(int(arg[1:])) + elif ',' in arg: + a, b = arg.split(',') + output += '\x03' + colors[a] + ',' + colors[b] + else: + output += '\x03' + colors[arg] + else: + output += arg + except: + continue + +with open('/home/zgrep/offtopiabday/irc.freenode.net/' + chan + '/in', 'w') as fh: + fh.write(output + '\n') diff --git a/comics.py b/comics.py new file mode 100644 index 0000000..48fdf3c --- /dev/null +++ b/comics.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 + +from subprocess import Popen, PIPE +from urllib.request import urlopen, quote, Request +from json import loads as dejson +from re import compile as regex + +def cmd(args): + proc = Popen(args, stdout=PIPE) + while True: + line = proc.stdout.readline() + if line: + line = line[:-1] + yield str(line, 'utf-8', 'ignore') + else: + break + +# Imports are finished, as is the preparation for things later. +# Time to implement the searchers! + +def relevantxkcd(query): + try: + url = "https://relevantxkcd.appspot.com/process?action=xkcd&query=" + quote(query) + req = urlopen(url) + if req.code != 200: + return None + res = req.read().split() + sure = float(res[0]) + if sure < 0.07: + return None + num = int(res[2]) + return 'https://xkcd.com/' + str(num) + except: + return None + +def explainxkcd(query): + try: + url = "https://explainxkcd.com/wiki/api.php?action=query&list=search&srwhat=text&format=json&srsearch=" + quote(query) + url = Request(url, headers={'User-Agent': 'Mozilla'}) + req = urlopen(url) + if req.code != 200: + return None + res = dejson(req.read().decode()) + for item in res['query']['search']: + try: + num = int(item['title'].split(':')[0]) + return 'https://xkcd.com/' + str(num) + except: + pass + return None + except: + return None + +googleapi = 'AIzaSyDr1gkHH-18QheEJpdGwUMmhYYvtlIJ3bA' +googlecse = '017423361205507730360:p6-h8trjn5c' +googlereg = regex(r'xkcd\.com/(\d+)') + +def googlexkcd(query): + try: + url = 'https://www.googleapis.com/customsearch/v1?key=' + googleapi + '&cx=' + googlecse + '&q=' + quote(query) + req = urlopen(url) + if req.code != 200: + return None + print('| | Opened URL, status code 200.') + res = dejson(req.read().decode()) + print('| | Decoded JSON.') + for item in res['items']: + print('| | Checking:', item['link']) + match = googlereg.search(item['link']) + try: + num = int(match.group(1)) + print('| | Found a match:', num) + return 'https://xkcd.com/' + str(num) + except: + pass + return None + except: + return None + +tvtropesapi = 'AIzaSyCVAXiUzRYsML1Pv6RwSG1gunmMikTzQqY' +tvtropescse = '006443654034974345143:kc4pt9dnkle' +def googletvtropes(query): + try: + url = 'https://www.googleapis.com/customsearch/v1element?key=' + tvtropesapi + '&cx=' + tvtropescse + '&q=' + quote(query) + req = urlopen(url) + if req.code != 200: + return None + print('| | Opened URL, status code 200.') + res = dejson(req.read().decode()) + print('| | Decoded JSON.') + if 'results' in res.keys() and res['results']: + return(res['results'][0]['url']) + print('| | No results.') + return None + except: + return None + +def numberxkcd(segment): + query = '' + original = segment + while segment and segment[0].isdigit(): + query += segment[0] + segment = segment[1:] + if query: + return 'https://xkcd.com/' + query, '', segment + return '', '', original + +# Righty, so all our searching mechanisms are above us. Let's table 'em up in a dict. + +methods = { + 'xkcd': (relevantxkcd, googlexkcd, explainxkcd), +# 'smbc': (), +# 'satw': (), +# 'ssss': (), + 'tvtropes': (googletvtropes,), + } + +# They return answer, query, segment +special = { + 'xkcd': numberxkcd, + } + +# Now for matching quotes. + +matching = [ + ('"', '"'), + ("'", "'"), + ('“', '”'), + ('„', '”', '“'), + ('<', '>'), + ('«', '»'), + ('»', '«'), + ('‹', '›'), + ('《', '》'), + ('〈', '〉'), + ('「', '」'), + ('﹁', '﹂'), + ('『', '』'), + ('﹃', '﹄'), + ('(', ')'), + ('[', ']'), + ('{', '}'), + ('【', '】'), + ('〔', '〕'), + ('⦗', '⦘'), + ('〖', '〗'), + ('〘', '〙'), + ('‚', '’', '‘'), + ('lu', "li'u") + ] + + +# And for an attempt to extract a comic. + +def attempt(line): + output = [] + segment = line + + while segment: + i = len(segment) + c = '' + for comic in methods.keys(): + try: + n = segment.index(comic + '!') + if n < i: + i = n + c = comic + except: + pass + + if not c: + # We have not found any comic-related things in this line. Bye! + return None + print('Searching for ' + c + ':') + + i += len(c) + 1 # len(comic + '!') + segment = segment[i:] # Right, skippity skip. + query = '' + + # Special cases. + if c in special.keys(): + print("| There's a special case:") + result, query, segment = special[c](segment) + if result: + print("| | Result:", result) + output.append(result) + continue + if not query: + print("| | Special case did not match.") + + if not query: + # See if there's a quoted thing. + for pair in matching: + start, *ends = pair + l = len(start) + if segment[:l] != start: + continue + print('| Found matching initial quote:', start) + segment = segment[l:] + for end in ends: + try: + i = segment.index(end) + query = segment[:i] + segment = segment[i + len(end):] + print('| Found matching end:', end) + break + except: + pass + if query: + break + + if not query: + while segment: + if not segment[0].isspace(): + query += segment[0] + segment = segment[1:] + else: + break + if '_' in query: + print('| Replacing "_" with " ".') + query = query.replace('_', ' ') + elif '-' in query: + print('| Replacing "-" with " ".') + query = query.replace('-', ' ') + + if query: + query = query.strip() + print('| Searching for:', query) + for method in methods[c]: + print('| Search using:', method.__name__) + result = method(query) + if result is not None: + output.append(result) + print('| Result:', result) + break + + return ' '.join(output) + +# Connect it to happybot. + +from sys import argv + +if len(argv) != 3: + print('Usage: ./xkcd.py out in') + exit(1) + +for line in cmd(['tail', '-f', argv[1]]): + line = line.split(' ', 3)[3] + # ACTIONs shmacktions, I don't give a char 'bout that. + if line[:8] == '\x01ACTION ' and line[-1] == '\x01': + line = line[8:-1] + # Oh, oh noes! We can't look at these lines! HUMANS ARE EVIL! + if line.startswith('\u200b') or line.startswith('nolog:') or line.startswith('[nolog]'): + continue + # I'm trying... trying... + result = attempt(line) + if result: + with open(argv[2], 'w') as fh: + fh.write('\u200b' + result + '\n') diff --git a/happy b/happy new file mode 100644 index 0000000..96dd257 --- /dev/null +++ b/happy @@ -0,0 +1,3 @@ +#!/usr/bin/env sh + +echo -e "$(cat /home/zgrep/zwsp)\x02\x0311,01Happy $@! ♥" >> "/home/zgrep/offtopiabday/irc.freenode.net/#offtopia/in"; diff --git a/happybot/ichi.py b/happybot/ichi.py deleted file mode 100644 index 5f6bdb1..0000000 --- a/happybot/ichi.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 - -import requests as rq -from lxml import html - -from sys import argv -if len(argv) != 2: - print('Not enough arguments. Bug zgrep.') - exit(1) - -q = argv[1] - -r = rq.get('http://ichi.moe/cl/qr/', params={'q': q}) - -if r.status_code != 200: - print('Non-200 status code. Bug zgrep.') - exit() - -# tree -t = html.fromstring(r.content) - -ichi = ''.join(t.xpath('//span[@class="ds-text"]//text()')) - -print(ichi) diff --git a/happybot/ichi.sh b/happybot/ichi.sh deleted file mode 100644 index 4c2575d..0000000 --- a/happybot/ichi.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env ash - -. /home/zgrep/offtopiabday/happybot/common.sh - -irc | while read -r n m; do - if reg '^(happy|hate)bot[:,] ichi (.*)' "$m"; then - python3 happybot/ichi.py "$(m 2)" | say; - fi; -done; diff --git a/happys b/happys new file mode 100644 index 0000000..9f2277e --- /dev/null +++ b/happys @@ -0,0 +1,3 @@ +#!/usr/bin/env sh + +echo -e "$(cat /home/zgrep/zwsp)\x02\x0311,01Happy$@! ♥" >> "/home/zgrep/offtopiabday/irc.freenode.net/#offtopia/in"; diff --git a/hateweekend b/hateweekend new file mode 100644 index 0000000..fb49c35 --- /dev/null +++ b/hateweekend @@ -0,0 +1,5 @@ +#!/usr/bin/env sh + +rm /home/zgrep/offtopiabday/hateweekfile; +echo -e "$(cat /home/zgrep/zwsp)\x02\x0304,01I hate how hateweek is over!" >> "/home/zgrep/offtopiabday/irc.freenode.net/#offtopia/in"; +echo "/n happybot" >> "/home/zgrep/offtopiabday/irc.freenode.net/in"; diff --git a/hateweekstart b/hateweekstart new file mode 100644 index 0000000..69709ea --- /dev/null +++ b/hateweekstart @@ -0,0 +1,5 @@ +#!/usr/bin/env sh + +touch /home/zgrep/offtopiabday/hateweekfile; +echo "/n hatebot" >> "/home/zgrep/offtopiabday/irc.freenode.net/in"; +echo -e "$(cat /home/zgrep/zwsp)\x02\x0304,01Hateweek is here! Hate how it's only once a year!" >> "/home/zgrep/offtopiabday/irc.freenode.net/#offtopia/in"; diff --git a/hhcdlog.sh b/hhcdlog.sh new file mode 100644 index 0000000..24e1f2b --- /dev/null +++ b/hhcdlog.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +happy=':-*D|=D|(:|։|‎܃‎|‎܄‎|᛬|︰|᠃|᠉|⁚|‎׃‎|˸|꞉|∶|ː|ꓽ|⠨|⡁|⠅)(ᴅ|Ⅾ|ⅅ|𝐃|𝐷|𝑫|𝒟|𝓓|𝔇|𝔻|𝕯|𝖣|𝗗|𝘋|𝘿|𝙳|Ꭰ|ᗞ|ᗪ|ꓓ|ᴰ|Ɖ|Ð|⫐|𐌃|Ɒ)' +tail -n 200 "/home/zgrep/offtopiabday/irc.freenode.net/##:d/out" | awk '{if($3=="-!-")gsub(/\([^)]+\)/,"");if(/^\S+ \S+ \S+ :D$/){}else if(/'"$happy"'/){sub(/>.*/, "> [REDACTED: NON-CONFORMANT]")}else{sub(/>.*/, "> [REDACTED: NON-HAPPY]")};print}' diff --git a/ichi.py b/ichi.py new file mode 100644 index 0000000..c70b58b --- /dev/null +++ b/ichi.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 + +from sys import argv +import re +import requests as rq +from lxml import html +from subprocess import Popen, PIPE + +if len(argv) != 2: + print('Usage:', argv[0], '#channel') + exit(1) +chan = argv[1] + +def cmd(args): + proc = Popen(args, stdout=PIPE) + while True: + line = proc.stdout.readline() + if line: + try: + yield str(line[:-1], 'utf-8', 'ignore') + except: + pass + else: + break + +fdir = '/home/zgrep/offtopiabday/irc.freenode.net/' + chan +fin = fdir + '/in' +fout = fdir + '/out' + +def irc(): + for line in cmd(['tail', '-n', '0', '-f', fout]): + date, time, nick, line = line.split(' ', 3) + print(line) + m = re.match(r'(?i)((happy|hate)bot[:,]\s+ichi|ハピロボット[:,])\s(.+)', line) + if m: + q = m.group(3) + print('Matched!', q) + r = get(q) + print('Result!', r) + with open(fin, 'w') as fh: + fh.write(r + '\n') + +omitnum = 3 + +def ichi(q, extra=False): + r = rq.get('http://ichi.moe/cl/qr/', params={'q': q}) + if r.status_code != 200: + return 'Non-200 status code. Bug zgrep.' + t = html.fromstring(r.content) + ichi = ''.join(t.xpath('//span[@class="ds-text"]//text()')) + more = '' + if extra: + e = t.xpath('//span[@class="gloss-desc"]//text()') + more = ' - '.join(e[:omitnum]) + if len(e) > omitnum: + more += ' - \x1dmore omitted\x0f' + if more: + more = ' (' + more + ')' + return '"' + ichi + '"' + more + +def kanji(q): + r = rq.get('http://ichi.moe/cl/kanji/', params={'q': q}) + if r.status_code != 200: + return None + if 'No kanji found' in r.text: + return None + t = html.fromstring(r.content) + return 'Not yet implemented.' + +def get(q): + if len(q) == 1: +# k = kanji(q) +# if k: return k + return ichi(q, True) + return ichi(q) + +irc() diff --git a/iiundie b/iiundie new file mode 100644 index 0000000..d9eac02 --- /dev/null +++ b/iiundie @@ -0,0 +1 @@ +$(cat starter | sed '/load_modules/q') diff --git a/privmsg b/privmsg new file mode 100644 index 0000000..8a86aaa --- /dev/null +++ b/privmsg @@ -0,0 +1,5 @@ +#!/usr/bin/env sh + +nick="$1"; shift; + +echo "/privmsg $nick :$@" >> "/home/zgrep/offtopiabday/irc.freenode.net/in"; diff --git a/rfk/rfk.py b/rfk/rfk.py new file mode 100644 index 0000000..9d72a1f --- /dev/null +++ b/rfk/rfk.py @@ -0,0 +1,313 @@ +#!/usr/bin/env python3 + +from subprocess import Popen, PIPE +from random import choice, random +from time import sleep +from os import chdir + +map_url = 'https://zgrep.org/taxreturns/rfk.txt' + +# Log. + +def logging(msg): + with open('log.txt', 'a') as fh: + fh.write(msg.strip() + '\n') + +# Items. + +def get_items(): + with open('rfk.txt') as fh: + return [l.strip() for l in fh] + +def add_item(item): + items = set(get_items() + [item]) + with open('rfk.txt', 'w') as fh: + fh.write('\n'.join(items) + '\n') + logging('add item: ' + item) + return 'Added item: ' + item + +def del_item(item): + items = set(get_items()) + if item in items: + items.remove(item) + with open('rfk.txt', 'w') as fh: + fh.write('\n'.join(items) + '\n') + logging('del item: ' + item) + return 'Removed item: ' + item + else: + removal = None + for maybe in items: + if maybe.lower().startswith(item.lower()): + if removal: + break + else: + removal = maybe + else: + if removal: + items.remove(removal) + with open('rfk.txt', 'w') as fh: + fh.write('\n'.join(items) + '\n') + logging('del item: ' + removal) + return 'Removed item: ' + removal + else: + return 'No items matched: ' + item + return 'More than one item matched: ' + item + +# Robot finds kitten bits below. + +grid = {} +width, height = 30, 15 +rx, ry = width // 2, height // 2 +percent = 0.1 +blind = False +found = False + +def string(): # prints the grid as a string + global grid, rx, ry, width, height, found + result = '' + for y in range(height): + for x in range(width): + if (x, y) in grid: + item = grid[(x, y)] + if found and item[2]: + result += 'K' + else: + result += item[0] + elif x == rx and y == ry: + result += '#' + else: + result += ' ' + result += '\n' + return result + +def move(x, y): # teleports to location + global rx, ry, width, height, grid, found + if x < 0 or y < 0 or x >= width or y >= height: + return 'There is a wall in the way.' + elif (x, y) in grid: + result = grid[(x, y)] + if result[2]: + found = True + return result[1] + else: + rx, ry = x, y + return None + +up = lambda: move(rx, ry - 1) +down = lambda: move(rx, ry + 1) +left = lambda: move(rx - 1, ry) +right = lambda: move(rx + 1, ry) + +symbols = '~!@$%^&*()`[]{}=+\\/?|-_;:\'"<>,.abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + +def reset(): # Set up a new game. + global width, height, rx, ry, grid, percent, found + grid = {} + found = False + items = get_items() + rx, ry = width // 2, height // 2 + for y in range(height): + for x in range(width): + if random() <= percent and x != rx and y != ry: + grid[(x, y)] = (choice(symbols), choice(items), False) + if not grid: + kitten = (rx - 2, ry - 2) + else: + kitten = choice(list(grid)) + with open('found.txt') as fh: + messages = [f.strip('\n') for f in fh.read().strip('\n').split('\n%\n')] + grid[kitten] = (choice(symbols), choice(messages), True) + if len(grid) == 1: + objects = 'there is 1 object' + else: + objects = 'there are {} objects'.format(len(grid)) + if not blind: + append = 'Map: ' + map_url + else: + append = 'No map is being made, blind is set to true.' + return 'New game! The grid is {} by {}, and {}. You are in the center. {}'\ + .format(width, height, objects, append) + +# Parsing input. + +def tofile(): + global blind + if not blind: + with open('/home/zgrep/tax/rfk.txt', 'w') as fh: + fh.write(string()) + +def dedup(gen): + prev = None + for item in gen: + if item == prev: + continue + else: + prev = item + yield item + +def parse(cmd, normalcase): + global width, height, percent, blind + if all(c in 'udlrnsew^v<>←↑→↓' for c in cmd): + for c in cmd: + if c in 'u^n↑': + yield up() + elif c in 'dvs→': + yield down() + elif c in 'le↓': + yield right() + tofile() + elif cmd in ('up', 'north'): + yield up() + tofile() + elif cmd in ('down', 'south'): + yield down() + tofile() + elif cmd in ('left', 'west'): + yield left() + tofile() + elif cmd in ('right', 'east'): + yield right() + tofile() + elif cmd.startswith('size '): + cmd = cmd.replace('size ', '', 1) + try: + w, h = map(int, cmd.split('x')) + w = min(max(w, 5), 1000) + h = min(max(h, 5), 1000) + width, height = w, h + yield reset() + tofile() + yield topic() + except: + yield 'The correct format for size is 10x10.' + elif cmd.startswith('blind '): + cmd = cmd.replace('blind ', '', 1) + if ' ' not in cmd: + if cmd in ('true', 'yes', 'on', '1'): + blind = True + with open('/home/zgrep/tax/rfk.txt', 'w') as fh: + fh.write('Blind is turned on, no map is being generated.\n') + yield topic() + yield 'Blind has been set to true, no new maps with be generated.' + elif cmd in ('false', 'no', 'off', '0'): + blind = False + tofile() + yield topic() + yield 'Blind has been turned off, a map has been generated: ' + map_url + else: + yield 'Expected either true or false.' + elif cmd.startswith('prob '): + cmd = cmd.replace('prob ', '', 1) + try: + m = 1 + if cmd[-1] == '%': + cmd = cmd[:-1] + m = 0.01 + p = min(max(m*float(cmd), 0), 1) + percent = p + yield reset() + tofile() + yield topic() + except: + yield 'The correct format for prob is 0.1 or 10%.' + elif cmd.startswith('add item '): + yield add_item(normalcase[9:]) + elif cmd.startswith('del item '): + yield del_item(normalcase[9:]) + elif cmd in ('reset', 'new game', 'restart'): + yield reset() + tofile() + yield topic() + elif cmd == 'help': + yield 'Commands: size, prob, add item, del item, up (north), down (south), left (west), right (east), new game, blind, map, help.' + yield '^<>vudlrnsew becomes up left right down up down left right north south east west. Only the one-character short commands can be chained together in one IRC message.' + if not blind: + yield 'A map of the grid can be found at: {}'.format(map_url) + else: + yield 'Currently blind is set to true, and therefore no map of the grid is generated. But normally, it would be found here: {}'.format(map_url) + elif cmd in ('map', 'grid'): + yield map_url + elif cmd == 'size': + yield '{} by {}'.format(width, height) + elif cmd == 'prob': + s = 's' + if len(grid) == 1: s = '' + yield '{}%, with {} object{} on the current grid.'\ + .format(percent*100, len(grid), s) + elif cmd == 'blind': + yield ('blind is set to false, a map is being generated.', + 'blind is set to true, no map is being generated.')[blind] + +# IRC-specific bits below. + +def topic(): + global found, width, height, grid + s = 's' + if len(grid) == 1: s = '' + append = 'No map.' + if not blind: + append = 'Map: ' + map_url + finding = '' + if found is True: + finding = ' Kitten has been found!' + elif found: + finding = ' Kitten has been found by {}!'.format(found) + return '/t Robot finds kitten. To play, type "help". | The grid is {} by {}, and has {} object{}.{} {}'\ + .format(width, height, len(grid), s, finding, append) + +def cmd(args): + proc = Popen(args, stdout=PIPE) + while True: + line = proc.stdout.readline() + if line: + try: + yield str(line[:-1], 'utf-8', 'ignore') + except: + pass + else: + break + +def begin(fin, fout): + global found + + fin = '/home/zgrep/offtopiabday/' + fin + fout = '/home/zgrep/offtopiabday/' + fout + + def send(msg): + if msg: + with open(fin, 'w') as fh: + fh.write(msg.strip() + '\n') + + send(reset()) + tofile() + send(topic()) + + for line in cmd(['tail', '-n', '0', '-f', fout]): + _, _, nick, line = line.split(' ', 3) + nick = nick[1:-1] + if nick in ('happybot', 'hatebot'): + continue + output = list(dedup(parse(line.lower(), line))) + if found is True: + found = nick + with open('finders.txt', 'a') as fh: + fh.write(found + '\n') + send(topic()) + time = 0 + if len(output) > 3: + time = 0.5 + for line in output: + send(line) + sleep(time) + +from sys import argv + +if len(argv) != 3: + print('Usage: in out') + exit(1) + +chdir('/home/zgrep/offtopiabday/rfk') + +begin(*argv[1:]) diff --git a/urls.py b/urls.py new file mode 100644 index 0000000..1a69df4 --- /dev/null +++ b/urls.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 + +def sizeof_fmt(num, suffix='B'): + for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']: + if abs(num) < 1024.0: + return "%3.1f%s%s" % (num, unit, suffix) + num /= 1024.0 + return "%.1f%s%s" % (num, 'Yi', suffix) + +from re import compile as regex + +urls = regex(r'(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?') + +from subprocess import Popen, PIPE + +def cmd(*args): + proc = Popen(args, stdout=PIPE) + while True: + line = proc.stdout.readline() + if line: + try: + yield str(line[:-1], 'utf-8', 'ignore') + except: + pass + else: + break + +from sys import argv + +if len(argv) != 2: + print('Usage:', argv[0], '#channel') + exit(1) + +chan = '/home/zgrep/offtopiabday/irc.freenode.net/' + argv[1] + +from urllib.request import Request, urlopen + +def quote(url): + res = '' + for c in url: + if ord(c) > 127: + res += ''.join('%' + hex(b)[2:] for b in c.encode('utf-8')) + else: + res += c + return res + +irccloud_none = 'irccloud.com/pastebin/' +irccloud_with = irccloud_none + 'raw/' + +for line in cmd('tail', '-n', '0', '-f', chan + '/out'): + date, time, nick, line = line.split(' ', 3) + nick = nick[1:-1] + if nick in ('happybot', 'hatebot'): + continue + result = [] + print('Doing line:', line) + for url in urls.findall(line): + url = quote(url) + if irccloud_none in url and irccloud_with not in url: + result.append(url.replace(irccloud_none, irccloud_with, 1)) + continue + if url[-5:] == '.gifv': # hack for imgur gifv's + url = url[:-5] + print('| Got gifv:', url) + try: + r = urlopen(Request(url + '.mp4', method='HEAD')) + contenttype = r.getheader('content-type') + length1 = r.getheader('content-length') + r.close() + except: + print('| Could not get mp4.') + if 'video' not in contenttype.lower(): + print('| Video is not a video?') + result.append('???') + continue + try: + r = urlopen(Request(url + '.gif', method='HEAD')) + contenttype = r.getheader('content-type') + length2 = r.getheader('content-length') + r.close() + except: + print('| Could not get gif.') + if 'image' not in contenttype.lower(): + print('| Image is not an image?') + result.append('???') + continue + try: + length1 = int(length1) + length2 = int(length2) + except: + print('| Lengths are not ints.') + continue + if length1 <= length2: + url += '.mp4' + length = length1 + else: + url += '.gif' + length = length2 + result.append(url + ' ' + sizeof_fmt(length)) + continue + print('| Got URL:', url) + rq = Request(url, method='HEAD') + try: + r = urlopen(rq) + contenttype = r.getheader('content-type').lower() + length = r.getheader('content-length') + r.close() + print('| | HEAD request completed.') + download = 0 # 0 ignore, 1 get from HEAD, 2+ get from HEAD otherwise GET + if 'image' in contenttype: + download = 2 # download images, fine... + elif 'video' in contenttype: + download = 1 # Eh... I'll draw the line at videos. + if download > 0: + if length: + try: + b = int(length) + except: + b = -1 + elif download > 1: + try: + rq = Request(url, method='HEAD') + r = urlopen(rq) + b = len(r.read()) + r.close() + print('| | Normal request required and complete.') + except: + b = -1 + print('| | Normal request required and failed.') + if b < 0: + print('| | Failure.') + result.append('???') + else: + print('| | Success.') + result.append(sizeof_fmt(b)) + except: + print('| | Failure.') + if result: + with open(chan + '/in', 'w') as fh: + fh.write('[' + '] ['.join(result) + ']\n')