|
|
@@ -1,327 +1,38 @@
|
|
|
-#!/usr/bin/env python3
|
|
|
-
|
|
|
-import random
|
|
|
-import threading
|
|
|
-import socket
|
|
|
-import subprocess
|
|
|
+import queue
|
|
|
+import shlex
|
|
|
import sys
|
|
|
-import time
|
|
|
-from unzipper import unar
|
|
|
-
|
|
|
-
|
|
|
-class IRCClient():
|
|
|
- HOST = "irc.irchighway.net"
|
|
|
- PORT = 6667
|
|
|
- NICK = ""
|
|
|
- IDENT = ""
|
|
|
- REALNAME = ""
|
|
|
- IGNORE = [
|
|
|
- "NOTICE",
|
|
|
- "PART",
|
|
|
- "QUIT",
|
|
|
- "332",
|
|
|
- "333",
|
|
|
- "372",
|
|
|
- "353",
|
|
|
- "366",
|
|
|
- "251",
|
|
|
- "252",
|
|
|
- "254",
|
|
|
- "255",
|
|
|
- "265",
|
|
|
- "266",
|
|
|
- "396"]
|
|
|
- CHANNEL = "#ebooks"
|
|
|
- STATUS = ""
|
|
|
- PROGRESS = 0
|
|
|
- TYPE = "SEARCH"
|
|
|
- OUTPUT = ""
|
|
|
- buffer = []
|
|
|
- readbuffer = b''
|
|
|
- NOT_EXITED = True
|
|
|
- EXTRA_OUTPUT = ""
|
|
|
- PATH = ""
|
|
|
- joined = False
|
|
|
- connected = False
|
|
|
-
|
|
|
- def __init__(self, book, extension, t, cb, logging=False, path="/tmp/"):
|
|
|
-
|
|
|
- self.TYPE = t
|
|
|
- self.EXTENSION = extension
|
|
|
- self.LOOKING_FOR = book
|
|
|
- self.LOGGING = logging
|
|
|
- self.PATH = path
|
|
|
-
|
|
|
- name = "bookbot" + random_hash()
|
|
|
- self.NICK = name
|
|
|
- self.IDENT = name
|
|
|
- self.REALNAME = name
|
|
|
- self.callback = cb
|
|
|
-
|
|
|
- self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
- self.socket.connect((self.HOST, self.PORT))
|
|
|
- self.handle_connect()
|
|
|
-
|
|
|
- nickstr = "NICK %s" % self.NICK
|
|
|
- userstr = "USER %s %s bla :%s" % (self.IDENT, self.HOST, self.REALNAME)
|
|
|
-
|
|
|
- self.t = threading.Thread(target=self.handle_read)
|
|
|
- self.t.start()
|
|
|
- self.send_queue(nickstr)
|
|
|
- self.send_queue(userstr)
|
|
|
-
|
|
|
- def handle_connect(self):
|
|
|
- self.log("connected")
|
|
|
- self.set_status("CONNECTED")
|
|
|
- self.connected = True
|
|
|
-
|
|
|
- def handle_close(self):
|
|
|
- self.NOT_EXITED = False
|
|
|
- self.log("closed")
|
|
|
- self.set_status("DISCONNECTED")
|
|
|
- self.connected = False
|
|
|
- self.socket.close()
|
|
|
-
|
|
|
- def do_timeout(self):
|
|
|
- if not self.NOT_EXITED:
|
|
|
- return
|
|
|
- self.NOT_EXITED = False
|
|
|
- self.log("Abort mission")
|
|
|
- self.set_status("TIMED OUT")
|
|
|
- self.connected = False
|
|
|
- self.socket.close()
|
|
|
-
|
|
|
- def handle_read(self):
|
|
|
- while self.NOT_EXITED:
|
|
|
- try:
|
|
|
- data = self.socket.recv(1024)
|
|
|
- except socket.error:
|
|
|
- break
|
|
|
- if len(data) < 2:
|
|
|
- continue
|
|
|
- if not (data[-1] == 10 and data[-2] == 13):
|
|
|
- self.readbuffer += data
|
|
|
- continue
|
|
|
-
|
|
|
- data = self.readbuffer + data
|
|
|
- print("DATA", data)
|
|
|
-
|
|
|
- # purge mojibake
|
|
|
- data = (data.replace(b'\x95', b'')
|
|
|
- .replace(b'0xc2', b'')
|
|
|
- .decode('utf-8', 'ignore'))
|
|
|
- self.readbuffer = b''
|
|
|
-
|
|
|
- lines = data.splitlines()
|
|
|
-
|
|
|
- for line in lines:
|
|
|
- words = line.split(' ')
|
|
|
- if len(words) < 2:
|
|
|
- continue
|
|
|
- msg_from = words[0]
|
|
|
- comm = words[1]
|
|
|
-
|
|
|
- if comm in self.IGNORE:
|
|
|
- continue
|
|
|
- if comm == "JOIN":
|
|
|
- if self.NICK not in msg_from: # msg "NICK joined the channel" not about me
|
|
|
- continue
|
|
|
- self.set_status("JOINED")
|
|
|
- self.log("Joined channel %s" % self.CHANNEL)
|
|
|
- self.log("Waiting 30s")
|
|
|
- time.sleep(31)
|
|
|
- self.run_query()
|
|
|
- self.joined = True
|
|
|
- continue
|
|
|
-
|
|
|
- if comm == "PRIVMSG":
|
|
|
- # private message not addressed to me
|
|
|
- if words[2] != self.NICK:
|
|
|
- continue
|
|
|
- self.parse_msg(line)
|
|
|
-
|
|
|
- if comm == "PING" or msg_from == "PING": # respond ping to avoid getting kicked
|
|
|
- self.pong(line)
|
|
|
- continue
|
|
|
- if comm == "376": # END MOTD
|
|
|
- # MOTD complete, lets join the channel
|
|
|
- self.join_channel(self.CHANNEL)
|
|
|
- continue
|
|
|
-
|
|
|
- if not self.joined: # waiting
|
|
|
- continue
|
|
|
-
|
|
|
- def run_query(self):
|
|
|
- self.set_status("WAITING")
|
|
|
- if self.TYPE == "SEARCH":
|
|
|
- self.search(self.LOOKING_FOR)
|
|
|
- return
|
|
|
-
|
|
|
- self.book(self.LOOKING_FOR)
|
|
|
-
|
|
|
- def parse_msg(self, msg):
|
|
|
- msg = msg.split(':')[2]
|
|
|
- msg = msg.replace("\x01", "")
|
|
|
- if not msg.startswith("DCC"):
|
|
|
- self.EXTRA_OUTPUT = msg
|
|
|
- self.log(msg)
|
|
|
- return
|
|
|
-
|
|
|
- args = msg.replace("DCC SEND ", "").split(" ")
|
|
|
- size = int(args.pop())
|
|
|
- port = int(args.pop())
|
|
|
- ip = ip_from_decimal(int(args.pop()))
|
|
|
- filename = "_".join(args).replace('"', '')
|
|
|
- self.set_status("RECEIVING")
|
|
|
-
|
|
|
- n = self.netcat(ip, port, size, filename)
|
|
|
- files = unar(n, self.PATH)
|
|
|
- print("Unarchived files", files)
|
|
|
|
|
|
- out = []
|
|
|
- for f in files:
|
|
|
- if "searchbot" in f.lower() or "searchook" in f.lower():
|
|
|
- out.append(self.list_books(f))
|
|
|
-
|
|
|
- print("Current output", out)
|
|
|
- self.OUTPUT = []
|
|
|
- if len(out) > 0:
|
|
|
- if isinstance(out[0], list):
|
|
|
- print("1")
|
|
|
- self.OUTPUT = [item for sublist in out for item in sublist]
|
|
|
- else:
|
|
|
- print("2")
|
|
|
- self.OUTPUT = out
|
|
|
- print("3")
|
|
|
- self.OUTPUT = list(set(self.OUTPUT))
|
|
|
- else:
|
|
|
- print("4")
|
|
|
- self.OUTPUT = files
|
|
|
-
|
|
|
- print(self.OUTPUT)
|
|
|
-
|
|
|
- if self.TYPE == "BOOK":
|
|
|
- self.log("Files: ")
|
|
|
- self.log(self.OUTPUT)
|
|
|
- for f in self.OUTPUT:
|
|
|
- if f.lower().endswith(".epub"):
|
|
|
- print("EPUB %s" % f)
|
|
|
- new_fname = f.replace("epub", "mobi")
|
|
|
- p = subprocess.Popen(["ebook-convert", f, new_fname])
|
|
|
- p.wait()
|
|
|
- self.OUTPUT.append(new_fname)
|
|
|
- break
|
|
|
-
|
|
|
- self.OUTPUT = [i.replace(self.PATH, "") for i in self.OUTPUT]
|
|
|
- self.handle_close()
|
|
|
- self.set_status("FINISHED")
|
|
|
-
|
|
|
- def list_books(self, f):
|
|
|
- f = open(f, "r")
|
|
|
- lines = f.readlines()
|
|
|
- lines = [l.strip().replace('\r', '') for l in lines if self.EXTENSION in l.lower(
|
|
|
- ) and l.startswith('!') and "htm" not in l.lower()]
|
|
|
- for l in lines:
|
|
|
- self.log(l)
|
|
|
- return lines
|
|
|
-
|
|
|
- def netcat(self, ip, port, size, filename):
|
|
|
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
- self.log("Receiving file")
|
|
|
- s.connect((ip, port))
|
|
|
- fname = ('%s/%s' % (self.PATH, filename)).replace("//", "/")
|
|
|
- f = open(fname, 'wb')
|
|
|
- count = 0
|
|
|
- while True:
|
|
|
- data = s.recv(4096)
|
|
|
- if len(data) == 0:
|
|
|
- self.log("empty")
|
|
|
- break
|
|
|
- count += len(data)
|
|
|
- f.write(data)
|
|
|
- perc = int(100 * count / size)
|
|
|
- self.PROGRESS = perc
|
|
|
- self.set_status("DOWNLOADING") # % perc #progress
|
|
|
-
|
|
|
- self.log("\r%d%%" % perc, instant=True)
|
|
|
- if count >= size:
|
|
|
- break
|
|
|
-
|
|
|
- s.close()
|
|
|
- f.close()
|
|
|
- self.log("") # newline
|
|
|
- return fname
|
|
|
-
|
|
|
- def search(self, book):
|
|
|
- self.log("searching for %s" % book)
|
|
|
- self.send_queue("PRIVMSG %s :@searchook %s " % (self.CHANNEL, book))
|
|
|
-
|
|
|
- def pong(self, data):
|
|
|
- msg = data.replace("PING ", "")
|
|
|
- self.send_queue("PONG %s" % msg)
|
|
|
-
|
|
|
- def join_channel(self, channel):
|
|
|
- self.send_queue("JOIN %s" % channel)
|
|
|
-
|
|
|
- def send_queue(self, msg):
|
|
|
- add = bytes(str(msg), "utf-8") + bytes([13, 10])
|
|
|
- self.socket.send(add)
|
|
|
-
|
|
|
- def book(self, book):
|
|
|
- self.log("Asking for '%s'" % book)
|
|
|
- self.send_queue("PRIVMSG %s :%s " % (self.CHANNEL, book))
|
|
|
-
|
|
|
- def who(self):
|
|
|
- self.send_queue("WHO %s" % self.CHANNEL)
|
|
|
-
|
|
|
- def log(self, val, instant=False):
|
|
|
- if self.LOGGING:
|
|
|
- if not instant:
|
|
|
- print(val)
|
|
|
- else:
|
|
|
- sys.stdout.write(val)
|
|
|
-
|
|
|
- def set_status(self, value):
|
|
|
- self.STATUS = value
|
|
|
- self.callback(value=="FINISHED")
|
|
|
-
|
|
|
-
|
|
|
-def ip_from_decimal(dec):
|
|
|
- return ".".join([str(int(b, 2)) for b in b_octets(bin(dec)[2:])])
|
|
|
-
|
|
|
-def b_octets(l):
|
|
|
- l = l.zfill(32)
|
|
|
- return [l[0:8], l[8:16], l[16:24], l[24:32]]
|
|
|
-
|
|
|
-def random_hash():
|
|
|
- return ("%032x" % random.getrandbits(128))[:10]
|
|
|
+from ircclient import IRCClient, MODE_SEARCH, MODE_BOOK
|
|
|
|
|
|
def usage():
|
|
|
print("USAGE:")
|
|
|
- print("\t %s SEARCH <BOOK> <FORMAT>" % sys.argv[0])
|
|
|
- print("\t %s BOOK <BOT COMMAND>" % sys.argv[0])
|
|
|
- sys.exit(1)
|
|
|
+ print("\t SEARCH <BOOK> <FORMAT>")
|
|
|
+ print("\t BOOK <BOT COMMAND>")
|
|
|
|
|
|
-if __name__ == "__main__":
|
|
|
- if len(sys.argv) == 4:
|
|
|
- if(sys.argv[1] != "SEARCH"):
|
|
|
- usage()
|
|
|
- mode = sys.argv[1]
|
|
|
- query = sys.argv[2]
|
|
|
- grep = sys.argv[3]
|
|
|
+def main():
|
|
|
+ q = queue.Queue()
|
|
|
+ client = IRCClient(q)
|
|
|
+ client.start()
|
|
|
+
|
|
|
+ for line in sys.stdin:
|
|
|
+ line = line.strip().lower()
|
|
|
+ split = shlex.split(line)
|
|
|
|
|
|
- elif len(sys.argv) == 3:
|
|
|
- if(sys.argv[1] != "BOOK"):
|
|
|
+ if len(split) < 2 or len(split) > 3 or split[0].lower() not in [MODE_SEARCH, MODE_BOOK]:
|
|
|
usage()
|
|
|
- mode = sys.argv[1]
|
|
|
- query = sys.argv[2]
|
|
|
- grep = ""
|
|
|
- else:
|
|
|
- usage()
|
|
|
+ continue
|
|
|
|
|
|
- if mode == "SEARCH":
|
|
|
- print("Looking for '%s' in format '%s'" % (query, grep))
|
|
|
- if mode == "BOOK":
|
|
|
- print("Downloading %s" % query)
|
|
|
+ mode = split[0]
|
|
|
+ query = split[1]
|
|
|
+ grep = ""
|
|
|
+ if len(split) == 3:
|
|
|
+ grep = split[2]
|
|
|
+ data = {'query': query, 'mode': mode, 'grep': grep}
|
|
|
+ q.put(data)
|
|
|
+ client.join()
|
|
|
|
|
|
- client = IRCClient(query, grep, mode, print, logging=True)
|
|
|
+if __name__ == "__main__":
|
|
|
+ try:
|
|
|
+ main()
|
|
|
+ except KeyboardInterrupt:
|
|
|
+ print("\nBye")
|