[SHOW ML] IRC <-> MDL chatbox gateway written in python

Discussion in 'Scripting' started by vyvojar, Aug 30, 2016.

  1. vyvojar

    vyvojar MDL Novice

    Aug 10, 2016
    22
    13
    0
    #1 vyvojar, Aug 30, 2016
    Last edited by a moderator: Apr 20, 2017
    It is very raw, so I hope that people who might have use for this will improve on it.

    irc.png

    To do list if you want to try something real world with python:
    • you need python3 to run this, though py2 compatibility is easy to add
    • I made it work only with irssi, other clients untested and will likely break. for irssi, /server localhost 6677 PASSWORD NICK
    • Nickname is your mdl username, password field in irc client is your mdl password
    • /me and smileys need some polishing

    Example tweak of the script: Spaces in nicknames normally don't work (IRC can't do that), so we rewrite those to underscores.
    Tell IRC client you have 'Nick_Name' instead of 'Nick Name' and then
    replace 'vb_login_username': self.nick, line with 'vb_login_username': self.nick.replace('_',' '),
    (ie forum login username is rewriten back to space, but only for the purpose of login)


    Please no "why use IRC client" posts, it's just matter of personal preference.

    Code:
    proto = 'http://'
    server = 'forums.mydigitallife.net'
    chan = '#mdl'
    path = '/'
    login = proto + server + path + 'login.php?do=login'
    msgs = proto + server + path + 'misc.php?show=ccbmessages'
    misc = proto + server + path + 'misc.php'
    
    import socketserver as ss
    import socket
    import os
    import urllib.request as urllib2
    from urllib.parse import urlencode
    import urllib
    from http.cookiejar import CookieJar
    from hashlib import md5
    import re
    import html
    from bs4 import BeautifulSoup
    from bs4.element import *;
    
    
    repl = {
        'wink':';)',
        'biggrin':':D',
        'tongue':':p',
        'redface':':o',
        'frown':':(',
        'smile':':)',
        # are there others?
    }
    def parse_html(doc):
        soup = BeautifulSoup(doc, 'html.parser')
        res = []
        nicks = set()
        for n in soup.children:
            if not isinstance(n,Tag): continue
            isme = False
            spans = n.td.find_all('span')
            member = spans[0].find_all('a')[1].contents[0]
            msg = spans[3].font
            for m in msg.findAll('font'):
                if m['color'] == 'Red':
                    isme = True
                m.replaceWith(m.text[len(member)+3:])
            for t in ['b','i','font']:
                for m in msg.findAll(t):
                    m.unwrap()
            for m in msg.findAll('a'):
                m.replaceWith(m['href'])
            for m in msg.findAll('img'):
                sml = m['src'].split('/')[-1].split('.')[0]
                if sml in repl:
                    sml = repl[sml]
                else:
                    sml = ':'+sml+':'
                m.replaceWith(sml)
            member = member.replace(' ', '_')
            nicks.add(member)
            res.append((isme,member,html.unescape(msg.renderContents().decode('utf8'))))
        return res, nicks
    
    class Handler(ss.StreamRequestHandler):
        def s_write(self, msg):
            print(" >> " + msg)
            self.wfile.write(("%s\r\n" % msg).encode('utf8'))
        def s_notice(self, msg):
            self.s_write(":%s NOTICE %s :%s" %(server, self.nick, msg))
        def s_err(self, msg):
            self.error = 1
            self.s_write("ERROR :%s\r\n" % msg)
        def handle(self):
            r = self.rfile
            cj = CookieJar()
            self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
            self.user = None
            self.pw = None
            self.nick = None
            self.pfx = 'reg_'
            self.error = 0
            self.syncd = 0
            self.didreg = 0
            while not self.error:
                try:
                    l = r.readline().decode('utf8').strip()
                    print("<< " + l)
                except socket.timeout:
                    if self.syncd:
                        r.raw._timeout_occurred = False
                        self.update()
                        continue
                    return
                parts = l.split(':',1)
                if len(parts)==2:
                    parts = parts[0].strip().split() + [parts[1]]
                else:
                    parts = parts[0].split()
                cmd = parts[0].upper()
                if hasattr(self.__class__, self.pfx+cmd):
                    getattr(self.__class__, self.pfx+cmd)(self, *parts[1:])
                else:
                    print("unhandled "+cmd)
        def reg_USER(self, *args):
            self.user = 1
            self.check_reg()
        def reg_PASS(self, pw):
            self.pw = pw
            self.check_reg()
        def reg_NICK(self, nick):
            self.nick = nick
            self.check_reg()
        def load(self, url, data=None):
            print(url,data)
            if data:
                resp = self.opener.open(url, urlencode(data).encode('utf8'), 10)
            else:
                resp = self.opener.open(url, timeout=10)
            resp = resp.read()
            return resp.decode('utf8','ignore')
        def check_reg(self):
            if not (self.pw and self.user and self.nick and (not self.didreg)):
                return
            self.didreg = 1
            self.pfx = 'do_'
            self.s_notice("Logging in as '%s'" % self.nick)
            pwh = md5(self.pw.encode('utf8')).hexdigest()
            data = {
                    'cookieuser': '1',
                    'securitytoken': 'guest',
                    'do': 'login',
                    'vb_login_username': self.nick,
                    'vb_login_password': '',
                    'vb_login_md5password': pwh,
                    'vb_login_md5password_utf': pwh
            }
            resp = self.load(login, data)
            if 'You have entered an invalid username or password.' in resp:
                return self.s_err("Can't login, invalid password.")
            if 'Wrong username or password.' in resp:
                return self.s_err("Can't login, reached login quota.")
            self.s_notice("Logged in, redirecting")
            nurl = re.findall('window.location = "([^"]*)', resp)[0]
            print(nurl)
            resp = self.load(nurl)
            token = re.findall('var SECURITYTOKEN = "([^"]*)"', resp)
            if not token:
                return self.s_err("Didn't get security token; abort")
            self.token = token[0]
            self.s_notice("Got security token")
            self.chats, self.nicks = parse_html(self.load(msgs, {'securitytoken':self.token}))
            self.s_notice("Synchronized")
            self.syncd = 1
            nick = self.nick
            self.s_write(":%s 001 %s :Welcome %s!%s@%s" %(server, nick, nick, nick, server))
            self.nicks.add(nick)
            self.do_NAMES(chan)
            self.connection.settimeout(10)
        def update(self):
            chats2, nicks2 = parse_html(self.load(msgs, {'securitytoken':self.token}))
            nicks2.add(self.nick)
            delta = 0
            for n in (nicks2-self.nicks):
                self.s_write(":%s!%s@%s JOIN :%s" % (n, n, server, chan))
            for n in (self.nicks-nicks2):
                self.s_write(":%s!%s@%s PART %s :User idle for too long" % (n, n, server, chan))
            self.nicks = nicks2
            chats = self.chats
            try:
                for delta in range(0,len(chats2)):
                    if chats2[delta] == chats[0]:
                        for j in range(0,len(chats)):
                            if chats2[delta+j] != chats[j]:
                                break
                        if j == len(chats)-1:
                            raise IndexError
            except IndexError:
                for i in range(0,delta):
                    nk = chats2
                    nik = nk[1]
                    if nik != self.nick:
                        self.s_write(":%s!%s@%s PRIVMSG %s :%s" % (nik,nik, server, chan, nk[2]))
            self.chats = chats2
    
        def do_MODE(self,target,*args):
            nick = self.nick
            print("MODE ",target,args,len(args))
            if len(args) == 1:
                a0 = args[0]
                if target == self.nick:
                    self.s_write(":%s MODE %s %s" %(nick, nick, a0))
                    return
                if target == chan:
                    mm = a0.replace('+','')
                    if mm == 'b':
                        self.nc(368, "End of Channel Ban List")
                    if mm == 'I':
                        self.nc(347, "End of Channel Invite List")
                    if mm == 'e':
                        self.nc(349, "End of Channel Exception List")
                    else:
                        self.nc(324, '+')
            elif not args:
                if target == chan:
                    self.nc(324,'+')
        def do_WHO(self,target):
            for n in self.nicks:
                self.num(352, "%s %s %s %s %s H" %( chan, n, server, server, n), "0 "+ n)
            self.nc(315, "End of WHO list.")
        def do_PING(self,target):
            self.s_write(":%s PONG %s :%s" %(server, server, target))
        def do_PRIVMSG(self,target,msg):
            self.load(misc, {'do':'cb_postnew','vsacb_newmessage':msg,'securitytoken':self.token,'color':'#000000'})
            self.update()
        def do_NAMES(self,chan):
            na = list(self.nicks)
            nick = self.nick
            total = len(na)
            def chunks():
                l = None
                init = ":%s 353 %s = %s :" % (server, nick, chan)
                l = init
    
                for i in range(0, total):
                    pp = l
                    l += na + ' '
                    if (len(l) > 510):
                        yield pp
                        l = init + na + ' '
                    if i == total-1:
                        yield l
            for c in chunks():
                self.s_write(c)
            self.nc(366, "End of NAMES list.")
        def num(self,num,arg,msg):
            self.s_write(":%s %03d %s %s :%s" %( server, num, self.nick, arg, msg))
        def nc(self,num,msg):
            return self.num(num,chan,msg)
    
    class MyServer(ss.ThreadingTCPServer):
        allow_reuse_address = True
    
    svc = MyServer(('localhost', 6677), Handler)
    svc.serve_forever()