summaryrefslogtreecommitdiffstats
path: root/module/plugins/hoster/XDCC.py
diff options
context:
space:
mode:
Diffstat (limited to 'module/plugins/hoster/XDCC.py')
-rw-r--r--module/plugins/hoster/XDCC.py387
1 files changed, 386 insertions, 1 deletions
diff --git a/module/plugins/hoster/XDCC.py b/module/plugins/hoster/XDCC.py
index fc1a7e3c3..2aa08cd87 100644
--- a/module/plugins/hoster/XDCC.py
+++ b/module/plugins/hoster/XDCC.py
@@ -9,8 +9,326 @@ import time
from module.plugins.internal.Hoster import Hoster
from module.plugins.internal.misc import exists, fsjoin
+from module.plugins.Plugin import Abort
+# try:
+# no_socks = False
+# import socks
+#
+# except ImportError:
+# no_socks = True
+
+
+
+
+class IRC(object):
+ def __init__(self, plugin, nick, ident ,realname, ctcp_version):
+ self.plugin = plugin
+
+ self.nick = "pyload-%d" % (time.time() % 1000) if nick == "pyload" else nick #: last 3 digits
+ self.ident = ident
+ self.realname = realname
+ self.ctcp_version = ctcp_version
+
+ self.irc_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.receive_buffer = ""
+ self.lines = []
+
+ self.connected = False
+ self.host = ""
+ self.port = 0
+
+ self.xdcc_request_time = None
+
+
+ def _data_available(self):
+ fdset = select.select([self.irc_sock], [], [], 0)
+ return True if self.irc_sock in fdset[0] else False
+
+
+ def _get_response_line(self, timeout=5):
+ start_time = time.time()
+ while time.time()-start_time < timeout:
+ if self._data_available():
+ self.receive_buffer += self.irc_sock.recv(1024)
+ self.lines += self.receive_buffer.split("\r\n")
+ self.receive_buffer = self.lines.pop()
+
+ if self.lines:
+ return self.lines.pop(0)
+
+ else:
+ time.sleep(0.1)
+
+ return None
+
+
+ def _parse_irc_msg(self, line):
+ """
+ Breaks a message from an IRC server into its origin, command, and arguments.
+ """
+ origin = ''
+ if not line:
+ return None, None, None
+
+ if line[0] == ':':
+ origin, line = line[1:].split(' ', 1)
+
+ if line.find(' :') != -1:
+ line, trailing = line.split(' :', 1)
+ args = line.split()
+ args.append(trailing)
+
+ else:
+ args = line.split()
+
+ command = args.pop(0)
+
+ return origin, command, args
+
+
+ def _keep_alive(self):
+ if not self._data_available():
+ return
+
+ self.receive_buffer += self.irc_sock.recv(1024)
+ lines = self.receive_buffer.split("\n")
+ self.receive_buffer = lines.pop()
+
+ for line in lines:
+ line = line.rstrip()
+ first = line.split()
+ if first[0] == "PING":
+ self.irc_sock.send("PONG %s\r\n" % first[1])
+
+
+ def connect_server(self, host, port):
+ """
+ Connect to the IRC server and wait for RPL_WELCOME message
+ """
+ self.plugin.log_info(_("Connecting to: %s:%s") % (host, port))
+
+ self.irc_sock.settimeout(30)
+ self.irc_sock.connect((host, port))
+ self.irc_sock.settimeout(None)
+
+ self.irc_sock.send("NICK %s\r\n" % self.nick)
+ self.irc_sock.send("USER %s %s bla :%s\r\n" % (self.ident, host, self.realname))
+
+ start_time = time.time()
+ while time.time()-start_time < 30:
+ origin, command, args = self.get_irc_command()
+ if command == "001": #: RPL_WELCOME
+ self.connected = True
+ self.host = host
+ self.port = port
+
+ start_time = time.time()
+ while self._get_response_line() and time.time()-start_time < 30: #: Skip MOTD
+ pass
+
+ self.plugin.log_info(_("Successfully connected to %s:%s") % (host, port))
+
+ return True
+
+ self.plugin.log_error(_("Connection to %s:%s failed.") % (host, port))
+ return False
+
+
+ def disconnect_server(self):
+ if self.connected:
+ self.plugin.log_info(_("Diconnecting from %s:%s") % (self.host, self.port))
+ self.irc_sock.send("QUIT :byebye\r\n")
+ self.plugin.log_debug("QUIT: %s" % self._get_response_line())
+ self.plugin.log_info(_("Disconnected"))
+ else:
+ self.plugin.log_warning(_("Not connected to server, cannot disconnect"))
+
+ self.irc_sock.close()
+
+
+ def get_irc_command(self):
+ while True:
+ line = self._get_response_line()
+ origin, command, args = self._parse_irc_msg(line)
+
+ if command == "PING":
+ self.plugin.log_debug(_("[%s] PING") % args[0])
+ self.irc_sock.send("PONG %s\r\n" % args[0])
+
+ elif command == "PRIVMSG":
+ target = args[0]
+ text = args[1]
+ if target[0:len(self.nick)] == self.nick:
+ if text == "\x01VERSION\x01":
+ self.plugin.log_debug(_("Sending CTCP VERSION"))
+ self.irc_sock.send("NOTICE %s :%s\r\n" % (origin, self.ctcp_version))
+
+ elif text == "\x01TIME\x01":
+ self.plugin.log_debug(_("Sending CTCP TIME"))
+ self.irc_sock.send("NOTICE %s :%d\r\n" % (origin, time.time()))
+
+ elif text == "\x01LAG\x01":
+ pass #: don't know how to answer
+
+ else:
+ return origin, command, args
+
+ else:
+ return origin, command, args
+
+ else:
+ return origin, command, args
+
+
+ def join_channel(self, chan):
+ self.irc_sock.send("JOIN #%s\r\n" % chan)
+
+ def request_xdcc_pack(self, bot, pack):
+ self.plugin.log_info(_("Requesting pack #%s") % pack)
+ self.xdcc_request_time = time.time()
+ self.irc_sock.send("PRIVMSG %s :xdcc send #%s\r\n" % (bot, pack))
+
+
+class XDCCRequest(object):
+ def __init__(self, timeout=30, proxies={}):
+ self.proxies = proxies
+ self.timeout = timeout
+
+ self.file_size = 0
+ self.bytes_received = 0
+ self.speed = 0
+ self.abort = False
+
+
+ def create_socket(self):
+ # proxy_type = None
+ # proxy = None
+ #
+ # if self.proxies.has_key("socks5"):
+ # proxy_type = socks.PROXY_TYPE_SOCKS5
+ # proxy = self.proxies["socks5"]
+ #
+ # elif self.proxies.has_key("socks4"):
+ # proxy_type = socks.PROXY_TYPE_SOCKS4
+ # proxy = self.proxies["socks4"]
+ #
+ # if proxy_type:
+ # sock = socks.socksocket()
+ # t = _parse_proxy(proxy)
+ # sock.setproxy(proxy_type, addr=t[3].split(":")[0], port=int(t[3].split(":")[1]), username=t[1], password=t[2])
+ #
+ # else:
+ # sock = socket.socket()
+ #
+ # return sock
+
+ return socket.socket()
+
+
+ def download(self, ip, port, filename, progress_notify=None):
+ last_update = time.time()
+ received_bytes = 0
+
+ dcc_sock = self.create_socket()
+
+ dcc_sock.settimeout(self.timeout)
+ dcc_sock.connect((ip, port))
+
+ if exists(filename):
+ i = 0
+ nameParts = filename.rpartition(".")
+ while True:
+ new_filename = "%s-%d%s%s" % (nameParts[0], i, nameParts[1], nameParts[2])
+ i += 1
+
+ if not exists(new_filename):
+ filename = new_filename
+ break
+
+ fh = open(filename, "wb")
+
+ # recv loop for dcc socket
+ while True:
+ if self.abort:
+ dcc_sock.close()
+ fh.close()
+ os.remove(filename)
+ raise Abort()
+
+ self._keep_alive()
+
+ data = dcc_sock.recv(4096)
+ dataLen = len(data)
+ self.bytes_received += dataLen
+
+ received_bytes += dataLen
+
+ now = time.time()
+ timespan = now - last_update
+ if timespan > 1:
+ self.speed = received_bytes / timespan
+ received_bytes = 0
+ last_update = now
+
+ if progress_notify:
+ progress_notify(self.percent)
+
+ if not data:
+ break
+
+ fh.write(data)
+
+ # acknowledge data by sending number of recceived bytes
+ dcc_sock.send(struct.pack('!I', self.bytes_received))
+
+ dcc_sock.close()
+ fh.close()
+
+ return filename
+
+
+ def _data_availible(self, sock):
+ fdset = select.select([sock], [], [], 0)
+ return sock in fdset[0]
+
+
+ def _keep_alive(self):
+ if not self._data_availible(self.irc_sock):
+ return
+
+ self.irc_buffer += self.irc_sock.recv(1024)
+ lines = self.irc_buffer.split("\n")
+ self.irc_buffer = lines.pop()
+
+ for line in lines:
+ line = line.rstrip()
+ first = line.split()
+ if first[0] == "PING":
+ self.irc_sock.send("PONG %s\r\n" % first[1])
+
+
+ def abortDownloads(self):
+ self.abort = True
+
+ @property
+ def size(self):
+ return self.file_size
+
+ @property
+ def arrived(self):
+ return self.bytes_received
+
+ @property
+ def percent(self):
+ if not self.file_size: return 0
+ return (self.bytes_received * 100) / self.file_size
+
+ def close(self):
+ pass
+
+# =======================================================================
class XDCC(Hoster):
__name__ = "XDCC"
__type__ = "hoster"
@@ -35,8 +353,75 @@ class XDCC(Hoster):
def process(self, pyfile):
+ server, chan, bot, pack = re.match(self.__pattern__, pyfile.url).groups()
+ temp = server.split(':')
+ ln = len(temp)
+ if ln == 2:
+ host, port = temp[0], int(temp[1])
+
+ elif ln == 1:
+ host, port = temp[0], 6667
+
+ else:
+ self.fail(_("Invalid hostname for IRC Server: %s") % server)
+
+ nick = self.config.get('nick')
+ ident = self.config.get('ident')
+ realname = self.config.get('realname')
+ ctcp_version = self.config.get('ctcp_version')
+
#: Change request type
self.req = self.pyload.requestFactory.getRequest(self.classname, type="XDCC")
+ # self.req = XDCCRequest(proxies=self.pyload.requestFactory.getProxies())
+
+ irc_client = IRC(self, nick, ident, realname, ctcp_version)
+ for _i in xrange(0, 3):
+ try:
+ if irc_client.connect_server(host, port):
+ try:
+ irc_client.join_channel(chan)
+ while not self.pyfile.abort:
+ origin, command, args = irc_client.get_irc_command()
+ if origin[0:len(bot)] != bot \
+ or args[0][0:len(nick)] != nick \
+ or command not in ("PRIVMSG", "NOTICE"):
+ continue
+
+ text = args[1]
+ self.log_info(_("PrivMsg: <%s> %s") % (bot, text))
+
+ if "You already requested that pack" in text:
+ retry = time.time() + 300
+
+ elif "you must be on a known channel to request a pack" in text:
+ self.log_error(_("Invalid channel"))
+ self.fail(_("Invalid channel"))
+
+ finally:
+ irc_client.disconnect_server()
+ return
+ irc_client.request_xdcc_pack(bot, pack)
+
+ except socket.error, e:
+ if hasattr(e, "errno") and e.errno is not None:
+ err_no = e.errno
+
+ if err_no in (10054, 10061):
+ self.log_warning("Server blocked our ip, retry in 5 min")
+ self.wait(300)
+ continue
+
+ else:
+ self.log_error(_("Failed due to socket errors. Code: %s") % err_no)
+ self.fail(_("Failed due to socket errors. Code: %s") % err_no)
+
+ else:
+ err_msg = e.args[0]
+ self.log_error(_("Failed due to socket errors: '%s'") % err_msg)
+ self.fail(_("Failed due to socket errors: '%s'") % err_msg)
+
+ self.log_error(_("Server blocked our ip, retry again later manually"))
+ self.fail(_("Server blocked our ip, retry again later manually"))
for _i in xrange(0, 3):
try:
@@ -206,7 +591,7 @@ class XDCC(Hoster):
self.pyfile.setStatus("downloading")
- newname = self.req.download(ip, port, dl_file, sock, self.pyfile.setProgress)
+ newname = self.req.download(ip, port, dl_file, self.pyfile.setProgress)
if newname and newname != dl_file:
self.log_info(_("%(name)s saved as %(newname)s") % {'name': self.pyfile.name, 'newname': newname})
dl_file = newname