It is very raw, so I hope that people who might have use for this will improve on it. 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()