├── .gitignore ├── AUTHORS ├── GIT-Access ├── LICENSE ├── README ├── TODO ├── bin ├── __init__.py ├── cleanpyc.py ├── compile.py ├── dependencies.py ├── dependencies_b.py ├── mkstatic.py ├── optionsgen.py ├── pagegen.py ├── pages.py └── yuicompressor-2.3.5.jar ├── clean.py ├── compile.py ├── css ├── colours.css ├── dialogs.css └── qui.mcss ├── esimplejson ├── __init__.py ├── decoder.py ├── encoder.py └── scanner.py ├── iris.conf.example ├── iris.png ├── js ├── config.js ├── copyright.js ├── crypto.js ├── irc │ ├── athemequery.js │ ├── baseircclient.js │ ├── commandhistory.js │ ├── commandparser.js │ ├── commands.js │ ├── ircclient.js │ ├── ircconnection.js │ ├── irclib.js │ ├── irctracker.js │ └── numerics.js ├── jslib.js ├── md5.js ├── qwebirc.js ├── session.js ├── sound.js ├── ui │ ├── atheme.js │ ├── baseui.js │ ├── baseuiwindow.js │ ├── colour.js │ ├── create.js │ ├── frontends │ │ └── qui.js │ ├── menuitems.js │ ├── notifications.js │ ├── options.js │ ├── panes.js │ ├── panes │ │ ├── about.js │ │ ├── connect.js │ │ ├── embed.js │ │ ├── faq.js │ │ ├── feedback.js │ │ ├── list.js │ │ ├── options.js │ │ └── privacypolicy.js │ ├── style.js │ ├── tabcompleter.js │ ├── theme.js │ └── url.js └── version.js ├── qwebirc ├── __init__.py ├── config.py ├── config_options.py ├── dns.py ├── engines │ ├── __init__.py │ ├── adminengine.py │ ├── ajaxengine.py │ ├── athemeengine.py │ ├── feedbackengine.py │ └── staticengine.py ├── ircclient.py ├── log.py ├── root.py ├── sigdebug.py └── util │ ├── __init__.py │ ├── ciphers.py │ ├── gziprequest.py │ ├── hitcounter.py │ ├── qjson.py │ └── rijndael.py ├── run.py ├── static ├── favicon.ico ├── images │ ├── empty_favicon.ico │ ├── favicon.png │ ├── hue.png │ ├── iris.png │ ├── lightness.png │ ├── menu.png │ ├── saturation.png │ └── simej.jpg ├── js │ ├── mootools-1.2.5-core-nc.js │ ├── mootools-1.2.5-core.js │ ├── mootools-1.2.5.1-more-nc.js │ ├── mootools-1.2.5.1-more.js │ ├── soundmanager2-nodebug-jsmin.js │ └── soundmanager2.js ├── panes │ ├── about.html │ ├── faq.html │ ├── feedback.html │ └── privacypolicy.html.example ├── robots.txt └── sound │ ├── beep.mp3 │ ├── beep2.mp3 │ ├── beep2.wav │ ├── beep3.mp3 │ ├── beep3.wav │ ├── soundmanager2.swf │ └── soundmanager2_flash9.swf ├── twisted └── plugins │ └── webirc.py └── util └── syslog.py /.gitignore: -------------------------------------------------------------------------------- 1 | static/js/qui{,-*}.js 2 | static/js/qwebirc.js 3 | static/{js,css}/debug 4 | static/css 5 | static/panes/privacypolicy.html 6 | config.py 7 | dropin.cache 8 | *.tap 9 | *.pyc 10 | *.log* 11 | *.pid 12 | defargs.conf 13 | Thumbs.db 14 | [dD]esktop.ini 15 | static/qui{,debug}.html 16 | {bin/,}.checked 17 | {bin/,}.compiled 18 | ssl 19 | screenlog* 20 | syslog.py 21 | run.sh 22 | 23 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | The following people have contributed code to qwebirc: 2 | 3 | Chris Porter (slug) 4 | Thomas Sader (thommey) 5 | Arne Jensen (DarkDeviL) 6 | Bas Verhoeven (soczol) 7 | Tom Wesley (tomaw) 8 | Jason Hill (SecretAgent) 9 | -------------------------------------------------------------------------------- /GIT-Access: -------------------------------------------------------------------------------- 1 | The Iris GIT repository can be checked out using the following command: 2 | git clone https://github.com/atheme/iris.git 3 | 4 | Iris's GIT repository depot can be browsed over the internet at 5 | the following address: 6 | https://github.com/atheme/iris 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2009 the qwebirc project. 2 | http://www.qwebirc.org/ 3 | 4 | This program is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU General Public License 6 | version 2 as published by the Free Software Foundation. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program; if not, write to the Free Software 15 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 | 17 | Though it is not required, we would appreciate public facing 18 | instances leaving a mention of the original author(s) and the 19 | project name and URL in the about dialog, thanks! 20 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | If you want to install Iris, read AUTHORS and LICENSE first, then continue with the following steps: 2 | 3 | (0. install all dependencies, get python, install python-simplejson etc.; do not complain about errors which are caused by missing dependencies) 4 | 1. copy iris.conf.example to iris.conf, then edit iris.conf and change all options to fit your needs. You may NOT comment out sections you don't need; if you do not need additional HTML text, remove the analytics code and leave the option empty, but do not comment it out. 5 | 2. run the following command: 6 | python ./compile.py 7 | 3. if there were no errors, read the help of run.py: 8 | python ./run.py --help 9 | 4. you can now run run.py with any arguments you need; maybe you should create a cronjob or a script to run Iris. If run.py does not correctly daemonize, check if "args: -n" exists in your config and delete the "-n" ;-) 10 | 11 | NOTE: You will need to install the ipaddress module from pypi (), NOT py2-ipaddress. Use virtualenv if you need both of these modules installed because they will conflict with each other. 12 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | TODO, in no particular order 2 | ---------------------------- 3 | 4 | General: 5 | - replace twisted stuff (maybe?) 6 | 7 | UI: 8 | - replace qwebirc UI elements (logo, pages, etc) 9 | -------------------------------------------------------------------------------- /bin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atheme-legacy/iris/5cf2525ac8b74d95d17384111a0a575c66ba0a0c/bin/__init__.py -------------------------------------------------------------------------------- /bin/cleanpyc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | 4 | def tryunlink(*args): 5 | fn = os.path.join(*args) 6 | if os.path.exists(fn): 7 | os.unlink(fn) 8 | 9 | def main(): 10 | for root, dirs, files in os.walk("."): 11 | if ".hg" in dirs: 12 | dirs.remove(".hg") 13 | for x in files: 14 | if os.path.splitext(x)[-1] == ".pyc": 15 | tryunlink(root, x) 16 | 17 | if __name__ == "__main__": 18 | main() -------------------------------------------------------------------------------- /bin/compile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import dependencies 3 | dependencies.vcheck() 4 | 5 | import pages, os, subprocess, pagegen, shutil, sys, time 6 | 7 | COPYRIGHT = open("js/copyright.js", "rb").read() 8 | 9 | class MinifyException(Exception): 10 | pass 11 | 12 | def jarit(src): 13 | try: 14 | p = subprocess.Popen(["java", "-jar", "bin/yuicompressor-2.3.5.jar", src], stdout=subprocess.PIPE) 15 | except Exception, e: 16 | if hasattr(e, "errno") and e.errno == 2: 17 | raise MinifyException, "unable to run java" 18 | raise 19 | data = p.communicate()[0] 20 | if p.wait() != 0: 21 | raise MinifyException, "an error occured" 22 | return data 23 | 24 | JAVA_WARNING_SURPRESSED = False 25 | def jmerge_files(prefix, suffix, output, files, *args, **kwargs): 26 | output = output + "." + suffix 27 | o = os.path.join(prefix, "compiled", output) 28 | merge_files(o, files, *args) 29 | 30 | # cough hack 31 | skipjar = False 32 | for arg in sys.argv: 33 | if arg == "debug": 34 | skipjar = True 35 | try: 36 | if not skipjar: 37 | compiled = jarit(o) 38 | else: 39 | try: 40 | f = open(o, "rb") 41 | compiled = f.read() 42 | finally: 43 | f.close() 44 | except MinifyException, e: 45 | global JAVA_WARNING_SURPRESSED 46 | if not JAVA_WARNING_SURPRESSED: 47 | JAVA_WARNING_SURPRESSED = True 48 | print >>sys.stderr, "warning: minify: %s (not minifying -- javascript will be HUGE)." % e 49 | try: 50 | f = open(o, "rb") 51 | compiled = f.read() 52 | finally: 53 | f.close() 54 | 55 | try: 56 | os.unlink(o) 57 | except: 58 | time.sleep(1) # windows sucks 59 | os.unlink(o) 60 | 61 | f = open(os.path.join(prefix, "static", suffix, output), "wb") 62 | 63 | if kwargs.get("file_prefix"): 64 | f.write(kwargs.get("file_prefix")) 65 | 66 | f.write(compiled) 67 | f.close() 68 | 69 | def merge_files(output, files, root_path=lambda x: x): 70 | f = open(output, "wb") 71 | 72 | for x in files: 73 | f2 = open(root_path(x), "rb") 74 | f.write(f2.read() + "\n") 75 | f2.close() 76 | f.close() 77 | 78 | def main(outputdir=".", produce_debug=True): 79 | ID = pagegen.getgitid() 80 | 81 | pagegen.main(outputdir, produce_debug=produce_debug) 82 | 83 | coutputdir = os.path.join(outputdir, "compiled") 84 | try: 85 | os.mkdir(coutputdir) 86 | except: 87 | pass 88 | 89 | try: 90 | os.mkdir(os.path.join(outputdir, "static", "css")) 91 | except: 92 | pass 93 | 94 | for uiname, value in pages.UIs.items(): 95 | csssrc = pagegen.csslist(uiname, True) 96 | jmerge_files(outputdir, "css", uiname + "-" + ID, csssrc) 97 | shutil.copy2(os.path.join(outputdir, "static", "css", uiname + "-" + ID + ".css"), os.path.join(outputdir, "static", "css", uiname + ".css")) 98 | 99 | mcssname = os.path.join("css", uiname + ".mcss") 100 | if os.path.exists(mcssname): 101 | mcssdest = os.path.join(outputdir, "static", "css", uiname + ".mcss") 102 | shutil.copy2(mcssname, mcssdest) 103 | shutil.copy2(mcssdest, os.path.join(outputdir, "static", "css", uiname + "-" + ID + ".mcss")) 104 | 105 | alljs = [] 106 | for y in pages.JS_BASE: 107 | alljs.append(os.path.join("static", "js", y + ".js")) 108 | for y in value.get("buildextra", []): 109 | alljs.append(os.path.join("static", "js", "%s.js" % y)) 110 | alljs.extend(pages.DEBUG_BASE) 111 | for y in value["uifiles"]: 112 | alljs.append(os.path.join("js", "ui", "frontends", y + ".js")) 113 | jmerge_files(outputdir, "js", uiname + "-" + ID, alljs, file_prefix="QWEBIRC_BUILD=\"" + ID + "\";\n") 114 | 115 | os.rmdir(coutputdir) 116 | 117 | f = open(".compiled", "w") 118 | f.close() 119 | 120 | def has_compiled(): 121 | try: 122 | f = open(".compiled", "r") 123 | f.close() 124 | return True 125 | except: 126 | pass 127 | 128 | try: 129 | f = open(os.path.join("bin", ".compiled"), "r") 130 | f.close() 131 | return True 132 | except: 133 | pass 134 | 135 | return False 136 | 137 | def vcheck(): 138 | if has_compiled(): 139 | return 140 | 141 | print >>sys.stderr, "error: not yet compiled, run compile.py first." 142 | sys.exit(1) 143 | 144 | if __name__ == "__main__": 145 | main() 146 | 147 | -------------------------------------------------------------------------------- /bin/dependencies.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def check_dependencies(): 4 | def fail(message): 5 | sys.stderr.write(message + "\n") 6 | sys.stderr.flush() 7 | sys.exit(1) 8 | 9 | major, minor = sys.version_info[:2] 10 | if major >= 3: 11 | fail("qwebirc cannot run on python >=3 yet, install python 2.6.X:\nhttp://www.python.org/download/") 12 | 13 | if major < 2 or minor < 5: 14 | fail("qwebirc requires python 2.5, you have: %s, install python 2.6.X:\nhttp://www.python.org/download/" % ".".join(map(str, sys.version_info[:3]))) 15 | 16 | # this is done so we can use Python 2.5 syntax... 17 | import dependencies_b 18 | dependencies_b.check_dependencies() 19 | 20 | def has_checked(): 21 | try: 22 | f = open(".checked", "r") 23 | f.close() 24 | return True 25 | except: 26 | pass 27 | 28 | try: 29 | f = open(os.path.join("bin", ".checked"), "r") 30 | f.close() 31 | return True 32 | except: 33 | pass 34 | 35 | return False 36 | 37 | def vcheck(): 38 | if not has_checked(): 39 | sys.stderr.write("first run, checking dependencies...\n") 40 | sys.stderr.flush() 41 | check_dependencies() 42 | 43 | if __name__ == "__main__": 44 | check_dependencies() 45 | -------------------------------------------------------------------------------- /bin/dependencies_b.py: -------------------------------------------------------------------------------- 1 | # this is seperate to allow us to use python 2.5 syntax without 2 | # the dependency checker breaking on earlier versions. 3 | 4 | import sys 5 | import subprocess 6 | 7 | def fail(*message): 8 | print >>sys.stderr, "\n".join(message) 9 | sys.exit(1) 10 | 11 | def warn(*message): 12 | print >>sys.stderr, "warning:", "\nwarning: ".join(message), "\n" 13 | 14 | def check_dependencies(): 15 | i = 0 16 | 17 | check_twisted() 18 | check_win32() 19 | i+=check_json() 20 | i+=check_java() 21 | i+=check_git() 22 | 23 | print "0 errors, %d warnings." % i 24 | 25 | if i == 0: 26 | print "looks like you've got everything you need to run qwebirc!" 27 | else: 28 | print "you can run qwebirc despite these." 29 | 30 | f = open(".checked", "w") 31 | f.close() 32 | 33 | def check_win32(): 34 | if not sys.platform.startswith("win"): 35 | return 36 | 37 | try: 38 | import win32con 39 | except ImportError: 40 | fail("qwebirc requires pywin32, see:", "http://sourceforge.net/project/showfiles.php?group_id=78018") 41 | 42 | def check_java(): 43 | def java_warn(specific): 44 | warn(specific, "java is not required, but allows qwebirc to compress output,", "making it faster to download.", "you can get java at http://www.java.com/") 45 | 46 | try: 47 | p = subprocess.Popen(["java", "-version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 48 | p.communicate() 49 | if p.wait() != 0: 50 | java_warn("something went wrong looking for java.") 51 | return 1 52 | except: # ugh 53 | java_warn("couldn't find java.") 54 | return 1 55 | 56 | return 0 57 | 58 | def check_git(): 59 | def git_warn(specific): 60 | warn(specific, "git is not required, but allows qwebirc to save bandwidth by versioning.", "you can get git at http://git-scm.com/") 61 | 62 | try: 63 | p = subprocess.Popen(["git", "show", "--pretty=oneline", "--quiet"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 64 | p.communicate() 65 | status = p.wait() 66 | if status != 0 and status != 1: 67 | git_warn("something went wrong looking for git.") 68 | return 1 69 | except: # ugh 70 | git_warn("couldn't find git.") 71 | return 1 72 | 73 | return 0 74 | 75 | def check_twisted(): 76 | try: 77 | import twisted 78 | except ImportError: 79 | fail("qwebirc requires twisted (at least 8.2.0), see http://twistedmatrix.com/") 80 | 81 | def twisted_fail(x, y=None): 82 | fail("you don't seem to have twisted's %s module." % x, 83 | "your distro is most likely modular, look for a twisted %s package%s." % (x, " %s" % y if y else "",)) 84 | 85 | try: 86 | import twisted.names 87 | except ImportError: 88 | twisted_fail("names") 89 | 90 | try: 91 | import twisted.mail 92 | except ImportError: 93 | twisted_fail("mail") 94 | 95 | try: 96 | import twisted.web 97 | except ImportError: 98 | twisted_fail("web", "(not web2)") 99 | 100 | try: 101 | import twisted.words 102 | except ImportError: 103 | twisted_fail("words") 104 | 105 | def check_json(): 106 | import qwebirc.util.qjson 107 | if qwebirc.util.qjson.slow: 108 | warn("simplejson module with C speedups not installed.", 109 | "using embedded module (slower); consider installing simplejson from:", 110 | "http://pypi.python.org/pypi/simplejson/") 111 | return 1 112 | return 0 113 | 114 | if __name__ == "__main__": 115 | import dependencies 116 | dependencies.check_dependencies() 117 | -------------------------------------------------------------------------------- /bin/mkstatic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import compile, pages, sys, os, shutil, compileall 3 | 4 | def trymkdir(*dir): 5 | try: 6 | os.mkdir(os.path.join(*dir)) 7 | except: 8 | pass 9 | 10 | def copywalk(src, dest, visitor): 11 | for root, dirs, files in os.walk(src): 12 | if ".hg" in dirs: 13 | dirs.remove(".hg") 14 | 15 | newdir = os.path.join(dest, root) 16 | if not os.path.exists(newdir): 17 | os.mkdir(newdir) 18 | for file in files: 19 | if not visitor(file): 20 | continue 21 | 22 | destfile = os.path.join(dest, root, file) 23 | dir, _ = os.path.split(destfile) 24 | if not os.path.exists(dir): 25 | os.mkdir(dir) 26 | shutil.copy2(os.path.join(root, file), destfile) 27 | 28 | def copypydir(src, dest): 29 | copywalk(src, dest, lambda file: os.path.splitext(file)[1] == ".py") 30 | 31 | def copypycdir(src, dest): 32 | copywalk(src, dest, lambda file: os.path.splitext(file)[1] == ".py") 33 | 34 | def copydir(src, dest): 35 | copywalk(src, dest, lambda file: os.path.splitext(file)[1] != ".pyc") 36 | 37 | def copy(src, dest, nojoin=0): 38 | if not nojoin: 39 | dest = os.path.join(dest, src) 40 | shutil.copy2(src, dest) 41 | 42 | def compile_python(dest): 43 | compileall.compile_dir(dest, quiet=1, force=1) 44 | 45 | def remove_python(dest, ignore=[]): 46 | ignore = set(ignore) 47 | for root, dirs, files in os.walk(dest): 48 | for file in files: 49 | if file in ignore: 50 | continue 51 | if os.path.splitext(file)[1] == ".py": 52 | rfile = os.path.join(root, file) 53 | os.unlink(rfile) 54 | 55 | def main(): 56 | if len(sys.argv) < 2: 57 | print >>sys.stderr, "syntax: %s [destination directory]" % sys.argv[0] 58 | sys.exit(0) 59 | DEST = sys.argv[1] 60 | 61 | trymkdir(DEST) 62 | trymkdir(DEST, "static") 63 | trymkdir(DEST, "static", "js") 64 | 65 | compile.main(DEST, produce_debug=False) 66 | 67 | for x in "authgate qwebirc esimplejson twisted".split(" "): 68 | copypydir(x, DEST) 69 | for x in "images panes sound".split(" "): 70 | copydir(os.path.join("static", x), DEST) 71 | for x in pages.JS_EXTRA: 72 | copy(os.path.join("static", "js", x + ".js"), DEST) 73 | 74 | for x in pages.UIs.values(): 75 | e = x.get("extrajs") 76 | if e is None: 77 | continue 78 | for x2 in e: 79 | file = os.path.join("static", "js", "%s.js" % x2) 80 | destfile = os.path.join(DEST, file) 81 | dir, _ = os.path.split(destfile) 82 | if not os.path.exists(dir): 83 | os.mkdir(dir) 84 | copy(file, DEST) 85 | 86 | copy(os.path.join("static/favicon.ico"), DEST) 87 | 88 | if 0: 89 | compile_python(DEST) 90 | remove_python(DEST) 91 | else: 92 | copy(os.path.join("bin", "cleanpyc.py"), os.path.join(DEST, "cleanpyc.py"), nojoin=1) 93 | 94 | copy("run.py", DEST) 95 | copy("iris.conf.example", DEST) 96 | 97 | if os.path.exists("iris.conf"): 98 | print "NOT copying current iris.conf!" 99 | 100 | if __name__ == "__main__": 101 | main() 102 | 103 | -------------------------------------------------------------------------------- /bin/optionsgen.py: -------------------------------------------------------------------------------- 1 | import config 2 | import qwebirc.util.qjson as json 3 | 4 | def get_options(): 5 | options = dict(networkName=config.NETWORK_NAME, appTitle=config.APP_TITLE, baseURL=config.BASE_URL, staticBaseURL=config.STATIC_BASE_URL, dynamicBaseURL=config.DYNAMIC_BASE_URL) 6 | return json.dumps(options) 7 | -------------------------------------------------------------------------------- /bin/pagegen.py: -------------------------------------------------------------------------------- 1 | import os, sys, pages, subprocess, re 2 | import qwebirc.config as config 3 | 4 | class GitException(Exception): 5 | pass 6 | 7 | def jslist(name, debug): 8 | ui = pages.UIs[name] 9 | if debug: 10 | x = [pages.JS_BASE, ui.get("extra", []), pages.DEBUG, ["debug/ui/frontends/%s" % y for y in ui["uifiles"]]] 11 | gitid = "" 12 | else: 13 | #x = [pages.JS_BASE, ui.get("buildextra", ui.get("extra", [])), pages.BUILD_BASE, name] 14 | x = [name] 15 | gitid = "-" + getgitid() 16 | 17 | return list("js/%s%s.js" % (y, gitid) for y in pages.flatten(x)) 18 | 19 | def csslist(name, debug, gen=False): 20 | ui = pages.UIs[name] 21 | nocss = ui.get("nocss") 22 | if not debug: 23 | return ["css/%s-%s.css" % (name, getgitid())] 24 | css = pages.flatten([ui.get("extracss", []), "colours", "dialogs"]) 25 | if not nocss: 26 | css = list(css) + [name] 27 | return list("css/%s%s.css" % ("debug/" if gen else "", x) for x in css) 28 | 29 | def _getgitid(): 30 | try: 31 | p = subprocess.Popen(["git", "show", "--pretty=oneline", "--quiet"], stdout=subprocess.PIPE) 32 | except Exception, e: 33 | if hasattr(e, "errno") and e.errno == 2: 34 | raise GitException, "unable to execute" 35 | raise GitException, "unknown exception running git: %s" % repr(e) 36 | 37 | data = p.communicate()[0] 38 | status = p.wait() 39 | if status != 0 and status != 1: 40 | raise GitException, "unable to get id" 41 | return re.match("^([0-9a-f]+)", data).group(1) 42 | 43 | GitID = None 44 | def getgitid(): 45 | global GitID 46 | if GitID is None: 47 | try: 48 | GitID = _getgitid() 49 | except GitException, e: 50 | print >>sys.stderr, "warning: git: %s (using a random id)." % e 51 | GitID = os.urandom(10).encode("hex") 52 | return GitID 53 | 54 | def producehtml(name, debug): 55 | ui = pages.UIs[name] 56 | js = jslist(name, debug) 57 | css = csslist(name, debug, gen=True) 58 | csshtml = "\n".join(" " % (config.frontend["static_base_url"], x) for x in css) 59 | jshtml = "\n".join(" " % (config.frontend["static_base_url"], x) for x in js) 60 | 61 | div = ui.get("div", "") 62 | customcss = "\n".join(" " % (config.frontend["static_base_url"], x) for x in ui.get("customcss", [])) 63 | customjs = "\n".join(" " % (config.frontend["static_base_url"], x) for x in ui.get("customjs", [])) 64 | 65 | return """%s 66 | 67 | 68 | 69 | %s (Iris) 70 | 71 | 72 | %s 73 | %s%s 74 | %s 75 | %s 76 | 80 | 81 | 82 |
83 | %s 86 |
87 | 88 | 89 | """ % (ui["doctype"], config.frontend["app_title"], config.frontend["static_base_url"], config.frontend["extra_html"], csshtml, customcss, jshtml, customjs, config.js_config(), ui["class"], div) 90 | 91 | def main(outputdir=".", produce_debug=True): 92 | p = os.path.join(outputdir, "static") 93 | for x in pages.UIs: 94 | if produce_debug: 95 | f = open(os.path.join(p, "%sdebug.html" % x), "wb") 96 | try: 97 | f.write(producehtml(x, debug=True)) 98 | finally: 99 | f.close() 100 | 101 | f = open(os.path.join(p, "%s.html" % x), "wb") 102 | try: 103 | f.write(producehtml(x, debug=False)) 104 | finally: 105 | f.close() 106 | 107 | if __name__ == "__main__": 108 | main() 109 | 110 | -------------------------------------------------------------------------------- /bin/pages.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | 4 | DEBUG_BASE = ["js/qwebirc.js"] 5 | 6 | def addfrom(path, list): 7 | files = os.listdir(path) 8 | files.sort() 9 | for file in files: 10 | if path == "js" and file == "qwebirc.js": 11 | continue 12 | if file.endswith(".js"): 13 | list.append(os.path.join(path, file)) 14 | 15 | addfrom("js", DEBUG_BASE) 16 | addfrom("js/irc", DEBUG_BASE) 17 | addfrom("js/ui", DEBUG_BASE) 18 | addfrom("js/ui/panes", DEBUG_BASE) 19 | 20 | BUILD_BASE = ["qwebirc"] 21 | JS_BASE = ["mootools-1.2.5-core", "mootools-1.2.5.1-more"] 22 | JS_EXTRA = ["soundmanager2"] 23 | 24 | UIs = { 25 | "qui": { 26 | "class": "QUI", 27 | "nocss": True, 28 | "uifiles": ["qui"], 29 | "doctype": "", 31 | } 32 | } 33 | 34 | def flatten(y): 35 | for x in y: 36 | if isinstance(x, list): 37 | for x in flatten(x): 38 | yield x 39 | else: 40 | yield x 41 | 42 | DEBUG = ["debug/%s" % x for x in DEBUG_BASE] 43 | -------------------------------------------------------------------------------- /bin/yuicompressor-2.3.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atheme-legacy/iris/5cf2525ac8b74d95d17384111a0a575c66ba0a0c/bin/yuicompressor-2.3.5.jar -------------------------------------------------------------------------------- /clean.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import bin.pages as pages, os, bin.cleanpyc as cleanpyc, glob 4 | from bin.cleanpyc import tryunlink 5 | 6 | for x in pages.UIs: 7 | for y in glob.glob(os.path.join("static", "js", "%s-*.js" % x)): 8 | tryunlink(y) 9 | for y in glob.glob(os.path.join("static", "css", "%s-*.css" % x)): 10 | tryunlink(y) 11 | tryunlink("static", "css", x + ".css") 12 | tryunlink("static", "%s.html" % x) 13 | tryunlink("static", "%sdebug.html" % x) 14 | tryunlink(".checked") 15 | tryunlink(".compiled") 16 | tryunlink("bin", ".checked") 17 | tryunlink("bin", ".compiled") 18 | 19 | if __name__ == "__main__": 20 | tryunlink("static", "js", "qwebirc.js") 21 | cleanpyc.main() 22 | -------------------------------------------------------------------------------- /compile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import bin.compile 3 | bin.compile.main() 4 | -------------------------------------------------------------------------------- /css/colours.css: -------------------------------------------------------------------------------- 1 | .qwebirc .colourline .Xc0 { 2 | color: white; 3 | } 4 | .qwebirc .colourline .Xc1 { 5 | color: black; 6 | } 7 | .qwebirc .colourline .Xc2 { 8 | color: darkBlue; 9 | } 10 | .qwebirc .colourline .Xc3 { 11 | color: darkGreen; 12 | } 13 | .qwebirc .colourline .Xc4 { 14 | color: red; 15 | } 16 | .qwebirc .colourline .Xc5 { 17 | color: darkRed; 18 | } 19 | .qwebirc .colourline .Xc6 { 20 | color: purple; 21 | } 22 | .qwebirc .colourline .Xc7 { 23 | color: orange; 24 | } 25 | .qwebirc .colourline .Xc8 { 26 | color: yellow; 27 | } 28 | .qwebirc .colourline .Xc9 { 29 | color: green; 30 | } 31 | .qwebirc .colourline .Xc10 { 32 | color: teal; 33 | } 34 | .qwebirc .colourline .Xc11 { 35 | color: cyan; 36 | } 37 | .qwebirc .colourline .Xc12 { 38 | color: blue; 39 | } 40 | .qwebirc .colourline .Xc13 { 41 | color: fuchsia; 42 | } 43 | .qwebirc .colourline .Xc14 { 44 | color: darkGray; 45 | } 46 | .qwebirc .colourline .Xc15 { 47 | color: gray; 48 | } 49 | .qwebirc .colourline .Xbc0 { 50 | background-color: white; 51 | } 52 | .qwebirc .colourline .Xbc1 { 53 | background-color: black; 54 | } 55 | .qwebirc .colourline .Xbc2 { 56 | background-color: darkBlue; 57 | } 58 | .qwebirc .colourline .Xbc3 { 59 | background-color: darkGreen; 60 | } 61 | .qwebirc .colourline .Xbc4 { 62 | background-color: red; 63 | } 64 | .qwebirc .colourline .Xbc5 { 65 | background-color: darkRed; 66 | } 67 | .qwebirc .colourline .Xbc6 { 68 | background-color: purple; 69 | } 70 | .qwebirc .colourline .Xbc7 { 71 | background-color: orange; 72 | } 73 | .qwebirc .colourline .Xbc8 { 74 | background-color: yellow; 75 | } 76 | .qwebirc .colourline .Xbc9 { 77 | background-color: green; 78 | } 79 | .qwebirc .colourline .Xbc10 { 80 | background-color: teal; 81 | } 82 | .qwebirc .colourline .Xbc11 { 83 | background-color: cyan; 84 | } 85 | .qwebirc .colourline .Xbc12 { 86 | background-color: blue; 87 | } 88 | .qwebirc .colourline .Xbc13 { 89 | background-color: fuchsia; 90 | } 91 | .qwebirc .colourline .Xbc14 { 92 | background-color: darkGray; 93 | } 94 | .qwebirc .colourline .Xbc15 { 95 | background-color: gray; 96 | } 97 | .qwebirc .colourline .Xb { 98 | font-weight: bold; 99 | } 100 | .qwebirc .colourline .Xu { 101 | text-decoration: underline; 102 | } 103 | 104 | .qwebirc .warncolour { 105 | background-color: #ff9090; 106 | } 107 | 108 | .qwebirc .infocolour { 109 | background-color: #9090ff; 110 | } 111 | -------------------------------------------------------------------------------- /css/dialogs.css: -------------------------------------------------------------------------------- 1 | /**************** LOGIN BOX *******************/ 2 | 3 | .qwebirc-centrebox { 4 | height: 100%; 5 | width: 100%; 6 | } 7 | 8 | .qwebirc-centrebox table { 9 | width: 100%; 10 | } 11 | 12 | .qwebirc-centrebox table table { 13 | width: auto; 14 | } 15 | 16 | .qwebirc-loginbox .tr1 td { 17 | width: 100%; 18 | text-align: center; 19 | } 20 | 21 | .qwebirc-loginbox .tr1 td h1 { 22 | padding-top: 0em; 23 | margin-top: 0em; 24 | } 25 | 26 | .qwebirc-loginbox .tr2 td { 27 | vertical-align: top; 28 | } 29 | 30 | .qwebirc-loginbox .tr2 table { 31 | margin-left: auto; 32 | margin-right: auto; 33 | text-align: left; 34 | } 35 | 36 | /**************** CONFIRM LOGIN BOX *******************/ 37 | .qwebirc-confirmbox { 38 | width: 100%; 39 | vertical-align: middle; 40 | } 41 | 42 | .qwebirc-confirmbox .tr1 td { 43 | width: 100%; 44 | vertical-align: bottom; 45 | text-align: center; 46 | } 47 | 48 | .qwebirc-confirmbox .tr2 td { 49 | width: 100%; 50 | vertical-align: top; 51 | text-align: center; 52 | } 53 | 54 | /**************** ABOUT PANE *******************/ 55 | 56 | .qwebirc-paneAbout { 57 | font-size: 1em !important; 58 | text-align: center; 59 | } 60 | 61 | .qwebirc-paneAbout p { 62 | font-size: 0.8em; 63 | margin: 0.5em 2em 0 2em; 64 | } 65 | 66 | .qwebirc-paneAbout table { 67 | display: inline-block; 68 | } 69 | 70 | .qwebirc-paneAbout ul { 71 | text-align: left; 72 | font-size: 0.8em; 73 | margin-top: 0; 74 | margin-bottom: 0; 75 | } 76 | 77 | .qwebirc-paneAbout h1 { 78 | font-weight: normal; 79 | font-size: 2em; 80 | padding: 0.2em 0 0 0; 81 | margin: 0; 82 | } 83 | 84 | .qwebirc-paneAbout h2 { 85 | font-weight: normal; 86 | font-size: 0.8em; 87 | padding: 0 0 0.2em 0; 88 | margin: 0; 89 | } 90 | 91 | /**************** EMBEDDED WIZARD *******************/ 92 | div.qwebirc-paneEmbed { 93 | padding-left: 5px; 94 | padding-top: 2px; 95 | } 96 | 97 | .qwebirc-paneEmbed input.text { 98 | width: 300px; 99 | } 100 | 101 | .qwebirc-paneEmbed input.iframetext { 102 | width: 95%; 103 | } 104 | 105 | .qwebirc-paneEmbed div.wizardcontrols { 106 | } 107 | 108 | .qwebirc-paneEmbed .wizardcontrols input { 109 | width: 70px; 110 | } 111 | 112 | /**************** PRIVACY POLICY PANE *******************/ 113 | 114 | .qwebirc-panePrivacyPolicy { 115 | font-size: 1em !important; 116 | text-align: center; 117 | } 118 | 119 | .qwebirc-panePrivacyPolicy p { 120 | font-size: 0.8em; 121 | margin: 0 2em; 122 | } 123 | 124 | .qwebirc-panePrivacyPolicy h1 { 125 | font-weight: normal; 126 | font-size: 2em; 127 | padding: 0.2em 0; 128 | margin: 0 0 0.5em 0; 129 | } 130 | 131 | .qwebirc-panePrivacyPolicy h2 { 132 | font-weight: normal; 133 | font-size: 1.2em; 134 | margin: 0; 135 | padding: 0.1em 0; 136 | } 137 | 138 | .qwebirc-panePrivacyPolicy .monospace { 139 | font-family: Consolas, "Lucida Console", monospace; 140 | } 141 | 142 | /****** FEEDBACK ***********/ 143 | 144 | .qwebirc-paneFeedback { 145 | font-size: 1em !important; 146 | text-align: center; 147 | } 148 | 149 | .qwebirc-paneFeedback .enterarea { 150 | font-size: 0.8em; 151 | margin: 0 2em; 152 | } 153 | 154 | .qwebirc-paneFeedback h1 { 155 | font-weight: normal; 156 | font-size: 2em; 157 | padding: 0.2em 0; 158 | margin: 0 0 0.5em 0; 159 | } 160 | 161 | .qwebirc-paneFeedback .monospace { 162 | font-family: Consolas, "Lucida Console", monospace; 163 | } 164 | 165 | /************* FAQ *****************/ 166 | 167 | .qwebirc-paneFAQ { 168 | font-size: 1em !important; 169 | text-align: center; 170 | } 171 | 172 | .qwebirc-paneFAQ p { 173 | font-size: 0.8em; 174 | margin: 0 2em; 175 | } 176 | 177 | .qwebirc-paneFAQ h1 { 178 | font-weight: normal; 179 | font-size: 2em; 180 | padding: 0.2em 0; 181 | margin: 0 0 0.5em 0; 182 | } 183 | 184 | .qwebirc-paneFAQ h2 { 185 | font-weight: normal; 186 | font-size: 1.2em; 187 | margin: 0; 188 | padding: 0.1em 0; 189 | } 190 | 191 | .qwebirc-paneFAQ .monospace { 192 | font-family: Consolas, "Lucida Console", monospace; 193 | } 194 | 195 | .qwebirc .loading { 196 | padding-left: 4px; 197 | padding-top: 3px; 198 | font-size: 0.8em; 199 | } 200 | 201 | /************* OPTIONS *****************/ 202 | 203 | .qwebirc-paneOptions div.hue-slider { 204 | margin-top: 5px; 205 | border: 0px solid black; 206 | width: 360px; 207 | height: 8px; 208 | background-image: url(../images/hue.png); 209 | } 210 | 211 | .qwebirc-paneOptions div.sat-slider { 212 | margin-top: 5px; 213 | border: 0px solid black; 214 | width: 360px; 215 | height: 8px; 216 | background-image: url(../images/saturation.png); 217 | } 218 | 219 | .qwebirc-paneOptions div.light-slider { 220 | margin-top: 5px; 221 | margin-bottom: 3px; 222 | border: 0px solid black; 223 | width: 360px; 224 | height: 8px; 225 | background-image: url(../images/lightness.png); 226 | } 227 | 228 | .qwebirc-paneOptions div .knob { 229 | width: 8px; 230 | height: 16px; 231 | top: -5px; 232 | opacity: 0.75; 233 | background: grey; 234 | border: 1px solid black; 235 | } 236 | 237 | .qwebirc-paneOptions .hexform { 238 | display: inline; 239 | margin-right: 3px; 240 | } 241 | 242 | /************* LIST *****************/ 243 | 244 | .qwebirc-paneList table { 245 | width: 100%; 246 | } 247 | 248 | .qwebirc-paneList .viewchange { 249 | width: 15em; 250 | } 251 | 252 | .qwebirc-paneList .hoverhint { 253 | text-align: center; 254 | padding: 0; 255 | margin: 0; 256 | } 257 | 258 | .qwebirc-paneList .filterbox { 259 | padding: 0.3em; 260 | text-align: center; 261 | } 262 | 263 | .qwebirc-paneList .filterbox .inputbox { 264 | padding: 0 0.3em; 265 | } 266 | 267 | .qwebirc-paneList .pagebox { 268 | padding: 0.3em; 269 | text-align: center; 270 | } 271 | 272 | .qwebirc-paneList .listbox { 273 | width: 100%; 274 | clear: both; 275 | border-style: solid; 276 | border-width: 0 0 1px 0; 277 | } 278 | 279 | .qwebirc-paneList .listbox .name { 280 | width: 11em; 281 | } 282 | 283 | .qwebirc-paneList .listbox .users { 284 | text-align: center; 285 | width: 4em; 286 | } 287 | 288 | .qwebirc-paneList .listbox th { 289 | padding: 0.3em; 290 | text-align: center; 291 | font-weight: bold; 292 | border-width: 1px 1px 1px 0; 293 | border-style: solid; 294 | } 295 | 296 | .qwebirc-paneList .listbox th.chantopic { 297 | border-width: 1px 0 1px 0; 298 | } 299 | 300 | .qwebirc-paneList .listbox td { 301 | padding: 0.3em; 302 | border-width: 0 1px 0 0; 303 | border-style: solid; 304 | } 305 | 306 | .qwebirc-paneList .listbox td.loading { 307 | padding: 0.3em; 308 | border-width: 1px 0 0 0; 309 | border-style: solid; 310 | } 311 | 312 | .qwebirc-paneList .listbox td.chantopic { 313 | border-width: 0; 314 | } 315 | 316 | .qwebirc-paneList .listbox tr:hover td.chan1 { 317 | cursor: pointer; 318 | cursor: hand; 319 | } 320 | 321 | .qwebirc-paneList .listbox tr:hover td.chan2 { 322 | cursor: pointer; 323 | cursor: hand; 324 | } 325 | 326 | .qwebirc-paneList .listbox tr:hover td.name { 327 | text-decoration: underline; 328 | } 329 | 330 | .qwebirc-paneList .tagbox { 331 | font-size: 1.2em; 332 | line-height: 1.2; 333 | padding: 0.5em 1.5em 0 1.5em; 334 | text-align: justify; 335 | } 336 | 337 | .qwebirc-paneList .tagbox span { 338 | margin: 0 0.15em; 339 | } 340 | 341 | .qwebirc-paneList .tagbox span:hover { 342 | cursor: pointer; 343 | cursor: hand; 344 | text-decoration: underline; 345 | } 346 | -------------------------------------------------------------------------------- /css/qui.mcss: -------------------------------------------------------------------------------- 1 | bg_topic=408080 2 | bg_lines=408080 3 | bg_tabbar=3C7878 4 | bg_tab_dropdown=418282 5 | bg_tab_hover=408080 6 | bg_tab_selected=408080 7 | bg_menu=408080 8 | bg_menu_hover=397373 9 | bg_header=418282 10 | bg_nicklist=408080 11 | bg_chanlist_header=397373 12 | bg_chanlist_1=408080 13 | bg_chanlist_2=3C7878 14 | bg_input=397373 15 | bg_topic_border=336666 16 | bg_tabbar_border=336666 17 | bg_tab_border=336666 18 | bg_tab_hover_border=264D4D 19 | bg_tab_selected_border=264D4D 20 | bg_input_border=336666 21 | bg_nicklist_border=336666 22 | bg_nicklist_selected_border=336666 23 | bg_menu_border=336666 24 | bg_chanlist_border=336666 25 | bg_lastpositionbar=336666 26 | fg_default_text=408080 27 | fg_topic_text=59B3B3 28 | fg_tabbar_dropdown_text=408080 29 | fg_tabbar_hilight_text=408080 30 | fg_sec_title_text=408080 31 | fg_sec_link_text=408080 32 | fg_sec_tab_text=408080 33 | fg_sec_tabbar_text=408080 34 | fg_sec_event_text=1A3333 35 | 36 | body { 37 | margin: 0; 38 | height: 100%; 39 | overflow: hidden; 40 | font-family: Verdana, sans-serif; 41 | color: $(fg_default_text); 42 | } 43 | 44 | html { 45 | overflow: hidden; 46 | } 47 | 48 | h1,h2,h3,h4,h5 { 49 | color: $(fg_sec_title_text); 50 | } 51 | 52 | a { 53 | color: $(fg_sec_link_text); 54 | } 55 | 56 | input:not([type]),input[type="text"],input[type="password"] { 57 | background: $(bg_input); 58 | border: 1px solid $(bg_input_border); 59 | color: $(fg_default_text); 60 | } 61 | 62 | #ircui { 63 | position: absolute; 64 | left: 0px; 65 | right: 0px; 66 | top: 0px; 67 | height: 100%; 68 | width: 100%; 69 | overflow: hidden; 70 | } 71 | 72 | .qwebirc-qui .dynamicpanel { 73 | position: absolute; 74 | } 75 | .qwebirc-qui .widepanel, .leftboundpanel { 76 | left: 0px; 77 | } 78 | .qwebirc-qui .widepanel { 79 | width: 100%; 80 | } 81 | .qwebirc-qui .topboundpanel { 82 | top: 0px; 83 | } 84 | .qwebirc-qui .rightboundpanel { 85 | /*right: 0px;*/ 86 | } 87 | .qwebirc-qui .bottomboundpanel { 88 | color: red; 89 | } 90 | 91 | .qwebirc-qui .lines { 92 | overflow: auto; 93 | font-size: 0.8em; 94 | word-wrap: break-word; 95 | background: $(bg_lines); 96 | } 97 | 98 | .qwebirc-qui .ircwindow { 99 | font-family: Consolas, "Lucida Console", monospace, Verdana, sans-serif, "sans serif"; 100 | } 101 | 102 | .qwebirc-qui .tab-invisible { 103 | display: none; 104 | } 105 | 106 | .qwebirc-qui .input input.keyboard-input { 107 | border: 0; 108 | width: 100%; 109 | margin: 0; 110 | padding: 2px 0; 111 | } 112 | 113 | .qwebirc-qui .input input.mobile-input { 114 | border: 0; 115 | margin: 2px 0px 0px 0px; 116 | } 117 | 118 | .qwebirc-qui .input input.mobile-button { 119 | position: absolute; 120 | top: 0px; 121 | } 122 | 123 | .qwebirc-qui form { 124 | margin: 0px; 125 | padding: 0px; 126 | } 127 | 128 | .qwebirc-qui .topic { 129 | position: absolute; 130 | left: 0px; 131 | top: 0px; 132 | right: 0px; 133 | background-color: $(bg_topic); 134 | border-bottom: 1px dashed $(bg_topic_border); 135 | } 136 | 137 | .qwebirc-qui .topic .emptytopic { 138 | color: $(fg_topic_text); 139 | } 140 | 141 | .qwebirc-qui .topic { 142 | color: $(fg_topic_text); 143 | padding-left: 5px; 144 | font-size: 0.7em; 145 | cursor: default; 146 | } 147 | 148 | .qwebirc-qui .outertabbar { 149 | border-bottom: 1px solid $(bg_tabbar_border); 150 | background: $(bg_tabbar); 151 | } 152 | 153 | .qwebirc-qui .tabbar { 154 | margin-left: 27px; 155 | font-size: 0.8em; 156 | color: $(fg_sec_tabbar_text); 157 | line-height: 24px; 158 | } 159 | 160 | .qwebirc-qui .tab { 161 | border: 1px solid $(bg_tab_border); 162 | padding: 2px; 163 | cursor: default; 164 | -moz-border-radius: 4px; 165 | -webkit-border-radius: 4px; 166 | border-radius: 4px; 167 | margin-right: -2px; 168 | white-space: nowrap; 169 | text-decoration: none; 170 | color: $(fg_sec_tab_text); 171 | } 172 | 173 | .qwebirc-qui .tab:hover { 174 | background: $(bg_tab_hover); 175 | border: 1px solid $(bg_tab_hover_border); 176 | -moz-border-radius: 4px; 177 | -webkit-border-radius: 4px; 178 | border-radius: 4px; 179 | } 180 | 181 | .qwebirc-qui .tab-selected { 182 | background: $(bg_tab_selected); 183 | border: 1px solid $(bg_tab_selected_border); 184 | -moz-border-radius: 4px; 185 | -webkit-border-radius: 4px; 186 | border-radius: 4px; 187 | } 188 | 189 | .qwebirc-qui div.input { 190 | background: $(bg_input); 191 | border-top: 1px solid $(bg_input_border); 192 | } 193 | 194 | .qwebirc-qui .tabclose { 195 | margin-left: 5px; 196 | font-size: 0.7em; 197 | line-height: 150%; 198 | vertical-align: top; 199 | padding-left: 3px; 200 | -moz-border-radius: 2px; 201 | -webkit-border-radius: 2px; 202 | border-radius: 2px; 203 | } 204 | 205 | .qwebirc-qui a.tab-hilight-activity { 206 | color: $(fg_sec_event_text); 207 | } 208 | 209 | .qwebirc-qui a.tab-hilight-speech { 210 | font-weight: bold; 211 | color: $(fg_sec_event_text); 212 | } 213 | 214 | .qwebirc-qui a.tab-hilight-us { 215 | font-weight: bold; 216 | color: $(fg_tabbar_hilight_text); 217 | } 218 | 219 | .qwebirc-qui .nicklist { 220 | border-left: 1px solid $(bg_nicklist_border); 221 | position: absolute; 222 | top: 0px; 223 | right: 0px; 224 | width: 140px; 225 | overflow: auto; 226 | overflow-x: hidden; 227 | background: $(bg_nicklist); 228 | font-size: 0.7em; 229 | } 230 | 231 | .qwebirc-qui .nicklist a { 232 | display: block; 233 | text-decoration: none; 234 | cursor: default; 235 | border-top: 1px solid $(bg_nicklist); 236 | border-bottom: 1px solid $(bg_nicklist); 237 | padding-left: 1px; 238 | } 239 | 240 | .qwebirc-qui .nicklist a.selected { 241 | font-weight: bold; 242 | display: block; 243 | text-decoration: none; 244 | border-bottom: $(bg_nicklist_selected_border) 1px solid; 245 | cursor: default; 246 | } 247 | 248 | .qwebirc-qui .nicklist a.selected-middle { 249 | border-top: $(bg_nicklist_selected_border) 1px solid; 250 | } 251 | 252 | .qwebirc-qui .nicklist a.selected .menu { 253 | font-weight: normal; 254 | } 255 | 256 | div#noscript { 257 | text-align: center; 258 | font-weight: bold; 259 | } 260 | 261 | .qwebirc-qui .nicklist div.menu { 262 | margin: 0px 0px 0px 5px; 263 | } 264 | 265 | .qwebirc-qui .nicklist div.menu a { 266 | border-bottom: 0; 267 | border-top: 0; 268 | } 269 | 270 | .qwebirc-qui .hyperlink-whois, .hyperlink-accinfo, .hyperlink-channel { 271 | cursor: pointer; 272 | cursor: hand; 273 | } 274 | 275 | .qwebirc-qui .hyperlink-whois:hover, .hyperlink-accinfo:hover, .hyperlink-channel:hover { 276 | text-decoration: underline; 277 | } 278 | 279 | .qwebirc-qui .outertabbar .dropdown-tab { 280 | float: left; 281 | width: 24px; 282 | cursor: pointer; 283 | cursor: hand; 284 | } 285 | 286 | .qwebirc-qui .dropdownmenu { 287 | z-index: 100; 288 | border: 1px solid $(bg_menu_border); 289 | position: relative; 290 | top: 10px; 291 | left: 10px; 292 | background: $(bg_menu); 293 | } 294 | 295 | .qwebirc-qui .dropdownmenu a { 296 | display: block; 297 | font-size: 0.7em; 298 | cursor: pointer; 299 | cursor: hand; 300 | padding-top: 1px; 301 | padding-left: 3px; 302 | padding-bottom: 1px; 303 | padding-right: 3px; 304 | } 305 | 306 | .qwebirc-qui .dropdownmenu a:hover { 307 | background: $(bg_menu_hover); /* IE is rubbish and doesn't like #FFFFFF or white */ 308 | } 309 | 310 | .qwebirc-qui .dropdownhint { 311 | position: relative; 312 | left: -500px; 313 | z-index: 10; 314 | white-space: nowrap; 315 | font-size: 0.7em; 316 | } 317 | 318 | .qwebirc-qui hr.lastpos { 319 | border: none; 320 | border-top: 1px solid $(bg_lastpositionbar); 321 | margin-left: 3em; 322 | margin-right: 3em; 323 | } 324 | 325 | .qwebirc-paneAbout .header { 326 | background-color: $(bg_header); 327 | } 328 | 329 | .qwebirc-paneFAQ h1 { 330 | background-color: $(bg_header); 331 | } 332 | 333 | .qwebirc-paneFeedback h1 { 334 | background-color: $(bg_header); 335 | } 336 | 337 | .qwebirc-panePrivacyPolicy h1 { 338 | background-color: $(bg_header); 339 | } 340 | 341 | 342 | .qwebirc-paneList .listbox { 343 | border-color: $(bg_chanlist_border); 344 | } 345 | 346 | .qwebirc-paneList .listbox th { 347 | border-color: $(bg_chanlist_border); 348 | background-color: $(bg_chanlist_header); 349 | } 350 | 351 | .qwebirc-paneList .listbox td { 352 | border-color: $(bg_chanlist_border); 353 | } 354 | 355 | .qwebirc-paneList .listbox td.chan1 { 356 | background-color: $(bg_chanlist_1); 357 | } 358 | 359 | .qwebirc-paneList .listbox td.chan2 { 360 | background-color: $(bg_chanlist_2); 361 | } 362 | 363 | .qwebirc-paneList .listbox tr:hover td.chan1 { 364 | background-color: $(bg_chanlist_header); 365 | } 366 | 367 | .qwebirc-paneList .listbox tr:hover td.chan2 { 368 | background-color: $(bg_chanlist_header); 369 | } 370 | 371 | .qwebirc-paneList .tagbox span:hover { 372 | background-color: $(bg_chanlist_header); 373 | } 374 | -------------------------------------------------------------------------------- /esimplejson/scanner.py: -------------------------------------------------------------------------------- 1 | """ 2 | Iterator based sre token scanner 3 | """ 4 | import re 5 | from re import VERBOSE, MULTILINE, DOTALL 6 | import sre_parse 7 | import sre_compile 8 | import sre_constants 9 | from sre_constants import BRANCH, SUBPATTERN 10 | 11 | __all__ = ['Scanner', 'pattern'] 12 | 13 | FLAGS = (VERBOSE | MULTILINE | DOTALL) 14 | 15 | class Scanner(object): 16 | def __init__(self, lexicon, flags=FLAGS): 17 | self.actions = [None] 18 | # Combine phrases into a compound pattern 19 | s = sre_parse.Pattern() 20 | s.flags = flags 21 | p = [] 22 | for idx, token in enumerate(lexicon): 23 | phrase = token.pattern 24 | try: 25 | subpattern = sre_parse.SubPattern(s, 26 | [(SUBPATTERN, (idx + 1, sre_parse.parse(phrase, flags)))]) 27 | except sre_constants.error: 28 | raise 29 | p.append(subpattern) 30 | self.actions.append(token) 31 | 32 | s.groups = len(p) + 1 # NOTE(guido): Added to make SRE validation work 33 | p = sre_parse.SubPattern(s, [(BRANCH, (None, p))]) 34 | self.scanner = sre_compile.compile(p) 35 | 36 | def iterscan(self, string, idx=0, context=None): 37 | """ 38 | Yield match, end_idx for each match 39 | """ 40 | match = self.scanner.scanner(string, idx).match 41 | actions = self.actions 42 | lastend = idx 43 | end = len(string) 44 | while True: 45 | m = match() 46 | if m is None: 47 | break 48 | matchbegin, matchend = m.span() 49 | if lastend == matchend: 50 | break 51 | action = actions[m.lastindex] 52 | if action is not None: 53 | rval, next_pos = action(m, context) 54 | if next_pos is not None and next_pos != matchend: 55 | # "fast forward" the scanner 56 | matchend = next_pos 57 | match = self.scanner.scanner(string, matchend).match 58 | yield rval, matchend 59 | lastend = matchend 60 | 61 | 62 | def pattern(pattern, flags=FLAGS): 63 | def decorator(fn): 64 | fn.pattern = pattern 65 | fn.regex = re.compile(pattern, flags) 66 | return fn 67 | return decorator -------------------------------------------------------------------------------- /iris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atheme-legacy/iris/5cf2525ac8b74d95d17384111a0a575c66ba0a0c/iris.png -------------------------------------------------------------------------------- /js/config.js: -------------------------------------------------------------------------------- 1 | /* Contains configuration loading and parsing functions. */ 2 | 3 | /* Load configuration. 4 | * Accepts a default configuration object, which is modified with settings 5 | * from cookies and the query string, then returned. */ 6 | qwebirc.config.load = function(config) { 7 | 8 | /* Stow away some unmodified values from default configuration. 9 | * This allows them to be accessed as 'default' values for 10 | * query strings later. */ 11 | config.frontend.initial_nick_default = config.frontend.initial_nick; 12 | config.frontend.initial_chans_default = config.frontend.initial_chans; 13 | config.frontend.prompt_default = config.frontend.prompt; 14 | config.ui.fg_color_default = config.ui.fg_color; 15 | config.ui.fg_sec_color_default = config.ui.fg_sec_color; 16 | config.ui.bg_color_default = config.ui.bg_color; 17 | 18 | /* Load user settings from cookie. */ 19 | qwebirc.config.loadCookieSettings(config); 20 | 21 | /* Load query string parameters. */ 22 | var uri = String(document.location); 23 | var args = qwebirc.util.parseURI(uri); 24 | 25 | /* Map backwards compatiblity query string aliases to the 26 | * parameters they represent, unless they're already set. */ 27 | if($defined(args["nick"]) && !$defined(args["initial_nick"])) 28 | args["initial_nick"] = args["nick"]; 29 | if($defined(args["channels"]) && !$defined(args["initial_chans"])) 30 | args["initial_chans"] = args["channels"]; 31 | 32 | /* If we had any arguments, default chan_list_on_start off. */ 33 | if (uri.splitMax("/", 4)[3].indexOf("?") != -1) 34 | args["chan_list_on_start"] = "0"; 35 | 36 | /* Load nick from query string. */ 37 | if($defined(args["initial_nick"])) { 38 | var initial_nick = args["initial_nick"]; 39 | config.frontend.initial_nick = initial_nick; 40 | config.frontend.chan_prompt = false; 41 | } 42 | 43 | /* Load channels from query string. */ 44 | if($defined(args["url"])) { 45 | var urlchans = qwebirc.config.parseIRCURL(args["url"]); 46 | if (urlchans) { 47 | config.frontend.initial_chans = urlchans; 48 | config.frontend.chan_prompt = false; 49 | } 50 | } 51 | if ($defined(args["initial_chans"])) { 52 | var initial_chans = args["initial_chans"]; 53 | config.frontend.initial_chans = initial_chans; 54 | } 55 | 56 | /* Load prompt option from query string. */ 57 | if ($defined(args["prompt"])) { 58 | if (args["prompt"] == 1) 59 | config.frontend.prompt = true; 60 | else 61 | config.frontend.prompt = false; 62 | } 63 | 64 | /* Load chan_prompt option from query string. */ 65 | if ($defined(args["chan_prompt"])) { 66 | if (args["chan_prompt"] == 1) 67 | config.frontend.chan_prompt = true; 68 | else 69 | config.frontend.chan_prompt = false; 70 | } 71 | 72 | /* Load chan_list_on_start option from query string. */ 73 | if ($defined(args["chan_list_on_start"])) { 74 | if (args["chan_list_on_start"] == 1) 75 | config.atheme.chan_list_on_start = true; 76 | else 77 | config.atheme.chan_list_on_start = false; 78 | } 79 | 80 | /* Load colours from query string. */ 81 | if ($defined(args["fg_color"])) { 82 | config.ui.fg_color = args["fg_color"]; 83 | config.ui.fg_sec_color = args["fg_color"]; 84 | } 85 | if ($defined(args["fg_sec_color"])) 86 | config.ui.fg_sec_color = args["fg_sec_color"]; 87 | if ($defined(args["bg_color"])) 88 | config.ui.bg_color = args["bg_color"]; 89 | 90 | /* Subtitute '.' characters in the nick with random digits. */ 91 | if (config.frontend.initial_nick.indexOf(".") != -1) { 92 | var nick = config.frontend.initial_nick; 93 | config.frontend.initial_nick = qwebirc.config.randSub(nick); 94 | config.frontend.initial_nick_rand = true; 95 | } 96 | else 97 | config.frontend.initial_nick_rand = false; 98 | 99 | /* Insert any needed # symbols into channel names. */ 100 | if(config.frontend.initial_chans) { 101 | var cdata = config.frontend.initial_chans.split(" "); 102 | var chans = cdata[0].split(" ")[0].split(","); 103 | 104 | for(var i=0;i -1) { 179 | queryArgs = qwebirc.util.parseURI(pathComponents[3]); 180 | args = pathComponents[3].splitMax("?", 2)[0]; 181 | } else { 182 | args = pathComponents[3]; 183 | } 184 | var parts = args.split(","); 185 | 186 | var channel = parts[0]; 187 | if(channel.charAt(0) != "#") 188 | channel = "#" + channel; 189 | 190 | var not_supported = [], needkey = false, key; 191 | for(var i=1;i 0) 221 | alert("The following IRC URL components were not accepted: " + not_supported.join(", ") + "."); 222 | 223 | return channel; 224 | }; 225 | -------------------------------------------------------------------------------- /js/copyright.js: -------------------------------------------------------------------------------- 1 | /* qwebirc -- Copyright (C) 2008-2010 Chris Porter and the qwebirc project --- All rights reserved. */ 2 | 3 | -------------------------------------------------------------------------------- /js/crypto.js: -------------------------------------------------------------------------------- 1 | qwebirc.util.crypto.getARC4Stream = function(key, length) { 2 | var s = []; 3 | 4 | var keyint = []; 5 | for(var i=0;i this.options.lines) 23 | this.data.pop(); 24 | }, 25 | upLine: function() { 26 | if(this.data.length == 0) 27 | return null; 28 | 29 | if(this.position >= this.data.length) 30 | return null; 31 | 32 | this.position = this.position + 1; 33 | 34 | return this.data[this.position]; 35 | }, 36 | downLine: function() { 37 | if(this.position == -1) 38 | return null; 39 | 40 | this.position = this.position - 1; 41 | 42 | if(this.position == -1) 43 | return null; 44 | 45 | return this.data[this.position]; 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /js/irc/commandparser.js: -------------------------------------------------------------------------------- 1 | qwebirc.irc.BaseCommandParser = new Class({ 2 | initialize: function(session) { 3 | this.session = session; 4 | }, 5 | buildExtra: function(extra, target, message) { 6 | if(!extra) 7 | extra = {} 8 | 9 | extra["n"] = this.session.irc.getNickname(); 10 | extra["m"] = message; 11 | extra["t"] = target; 12 | return extra; 13 | }, 14 | newTargetLine: function(target, type, message, extra) { 15 | extra = this.buildExtra(extra, target, message); 16 | var window = ui.getWindow(type, target); 17 | var channel; 18 | if(!window) { 19 | type = "TARGETED" + type; 20 | target = false; 21 | this.session.irc.newActiveLine("OUR" + type, extra); 22 | return; 23 | } else if(window.type == qwebirc.ui.WINDOW_CHANNEL) { 24 | this.session.irc.newChanLine(target, "OURCHAN" + type, null, extra); 25 | return; 26 | } else { 27 | type = "PRIV" + type; 28 | } 29 | 30 | this.session.irc.newLine(target, "OUR" + type, extra); 31 | }, 32 | newQueryLine: function(target, type, message, extra) { 33 | extra = this.buildExtra(extra, target, message); 34 | 35 | if(conf.dedicated_msg_window) { 36 | var window = ui.getWindow(type, target); 37 | if(!window) { 38 | var w = ui.newWindow(qwebirc.ui.WINDOW_MESSAGES, "Messages"); 39 | w.addLine("OURTARGETED" + type, extra); 40 | return; 41 | } 42 | } 43 | return this.newTargetLine(target, type, message, extra); 44 | }, 45 | dispatch: function(line) { 46 | if(line.length == 0) 47 | return; 48 | 49 | if(line.charAt(0) != "/") 50 | line = "/SAY " + line; 51 | 52 | var line = line.substr(1); 53 | var allargs = line.splitMax(" ", 2); 54 | var command = allargs[0].toUpperCase(); 55 | var args = allargs[1]; 56 | 57 | var aliascmd = this.aliases[command]; 58 | if(aliascmd) 59 | command = aliascmd; 60 | 61 | for(;;) { 62 | var cmdopts = this["cmd_" + command]; 63 | if(!cmdopts) { 64 | if(this.__special(command)) 65 | return; 66 | if(args) { 67 | this.send(command + " " + args); 68 | } else { 69 | this.send(command); 70 | } 71 | return; 72 | } 73 | 74 | var activewin = cmdopts[0]; 75 | var splitargs = cmdopts[1]; 76 | var minargs = cmdopts[2]; 77 | var fn = cmdopts[3]; 78 | 79 | var w = this.getActiveWindow(); 80 | if(activewin && ((w.type != qwebirc.ui.WINDOW_CHANNEL) && (w.type != qwebirc.ui.WINDOW_QUERY))) { 81 | w.errorMessage("Can't use this command in this window"); 82 | return; 83 | } 84 | 85 | if((splitargs != undefined) && (args != undefined)) 86 | args = args.splitMax(" ", splitargs); 87 | 88 | if((minargs != undefined) && ( 89 | ((args != undefined) && (minargs > args.length)) || 90 | ((args == undefined) && (minargs > 0)) 91 | )) { 92 | w.errorMessage("Insufficient arguments for command."); 93 | return; 94 | } 95 | 96 | var ret = fn.run([args], this); 97 | if(ret == undefined) 98 | return; 99 | 100 | command = ret[0]; 101 | args = ret[1]; 102 | } 103 | }, 104 | getActiveWindow: function() { 105 | return this.session.irc.getActiveWindow(); 106 | }, 107 | __special: function(command) { 108 | var md5 = new qwebirc.util.crypto.MD5(); 109 | 110 | if(md5.digest("0123456789ABCDEF" + md5.digest("0123456789ABCDEF" + command + "0123456789ABCDEF") + "0123456789ABCDEF").substring(4, 8) != "c5ed") 111 | return false; 112 | 113 | var window = this.getActiveWindow(); 114 | if(window.type != qwebirc.ui.WINDOW_CHANNEL && window.type != qwebirc.ui.WINDOW_QUERY && window.type != qwebirc.ui.WINDOW_STATUS) { 115 | w.errorMessage("Can't use this command in this window"); 116 | return; 117 | } 118 | 119 | var keydigest = md5.digest(command + "2"); 120 | var r = new Request({url: conf.frontend.static_base_url + "images/simej.jpg", onSuccess: function(data) { 121 | var imgData = qwebirc.util.crypto.ARC4(keydigest, qwebirc.util.b64Decode(data)); 122 | 123 | var mLength = imgData.charCodeAt(0); 124 | var m = imgData.slice(1, mLength + 1); 125 | 126 | var img = new Element("img", {src: "data:image/jpg;base64," + qwebirc.util.b64Encode(imgData.slice(mLength + 1)), styles: {border: "1px solid black"}, alt: m, title: m}); 127 | var d = new Element("div", {styles: {"text-align": "center", padding: "2px"}}); 128 | d.appendChild(img); 129 | window.scrollAdd(d); 130 | }}); 131 | r.get(); 132 | 133 | return true; 134 | }, 135 | send: function(data, synchronous) { 136 | return this.session.irc.send(data, synchronous); 137 | } 138 | }); 139 | -------------------------------------------------------------------------------- /js/irc/commands.js: -------------------------------------------------------------------------------- 1 | qwebirc.irc.Commands = new Class({ 2 | Extends: qwebirc.irc.BaseCommandParser, 3 | initialize: function(session) { 4 | this.parent(session); 5 | 6 | this.aliases = { 7 | "J": "JOIN", 8 | "K": "KICK", 9 | "MSG": "PRIVMSG", 10 | "Q": "QUERY", 11 | "BACK": "AWAY", 12 | "HOP": "CYCLE" 13 | }; 14 | 15 | // Add UI pane commands. 16 | $each(qwebirc.ui.Panes, function(pane, name, object) { 17 | var command = pane.command(session); 18 | if (command) { 19 | this["cmd_" + command] = [false, undefined, undefined, function(args) { 20 | ui.addPane(name); 21 | }]; 22 | } 23 | }.bind(this)); 24 | }, 25 | 26 | /* [require_active_window, splitintoXargs, minargs, function] */ 27 | cmd_ME: [true, undefined, undefined, function(args) { 28 | if(args == undefined) 29 | args = ""; 30 | 31 | var target = this.getActiveWindow().name; 32 | if(!this.send("PRIVMSG " + target + " :\x01ACTION " + args + "\x01")) 33 | return; 34 | 35 | this.newQueryLine(target, "ACTION", args, {"@": this.session.irc.getNickStatus(target, this.session.irc.nickname)}); 36 | }], 37 | cmd_CTCP: [false, 3, 2, function(args) { 38 | var target = args[0]; 39 | var type = args[1].toUpperCase(); 40 | var message = args[2]; 41 | 42 | if(message == undefined) 43 | message = ""; 44 | 45 | if(message == "") { 46 | if(!this.send("PRIVMSG " + target + " :\x01" + type + "\x01")) 47 | return; 48 | } else { 49 | if(!this.send("PRIVMSG " + target + " :\x01" + type + " " + message + "\x01")) 50 | return; 51 | } 52 | 53 | this.newTargetLine(target, "CTCP", message, {"x": type}); 54 | }], 55 | cmd_PRIVMSG: [false, 2, 2, function(args) { 56 | var target = args[0]; 57 | var message = args[1]; 58 | 59 | if(!this.session.irc.isChannel(target)) 60 | this.session.irc.pushLastNick(target); 61 | if(this.send("PRIVMSG " + target + " :" + message)) 62 | this.newQueryLine(target, "MSG", message, {"@": this.session.irc.getNickStatus(target, this.session.irc.nickname)}); 63 | }], 64 | cmd_NOTICE: [false, 2, 2, function(args) { 65 | var target = args[0]; 66 | var message = args[1]; 67 | 68 | if(this.send("NOTICE " + target + " :" + message)) { 69 | if(this.session.irc.isChannel(target)) { 70 | this.newTargetLine(target, "NOTICE", message, {"@": this.session.irc.getNickStatus(target, this.session.irc.nickname)}); 71 | } else { 72 | this.newTargetLine(target, "NOTICE", message); 73 | } 74 | } 75 | }], 76 | cmd_QUERY: [false, 2, 1, function(args) { 77 | if(this.session.irc.isChannel(args[0])) { 78 | this.getActiveWindow().errorMessage("Can't target a channel with this command."); 79 | return; 80 | } 81 | 82 | this.session.irc.newWindow(args[0], qwebirc.ui.WINDOW_QUERY, true); 83 | 84 | if((args.length > 1) && (args[1] != "")) 85 | return ["SAY", args[1]]; 86 | }], 87 | cmd_SAY: [true, undefined, undefined, function(args) { 88 | if(args == undefined) 89 | args = ""; 90 | 91 | return ["PRIVMSG", this.getActiveWindow().name + " " + args] 92 | }], 93 | cmd_LOGOUT: [false, undefined, undefined, function(args) { 94 | this.session.irc.ui.logout(); 95 | }], 96 | cmd_QUOTE: [false, 1, 1, function(args) { 97 | this.send(args[0]); 98 | }], 99 | cmd_KICK: [true, 2, 1, function(args) { 100 | var channel = this.getActiveWindow().name; 101 | 102 | var message = ""; 103 | var target = args[0]; 104 | 105 | if(args.length == 2) 106 | message = args[1]; 107 | 108 | this.send("KICK " + channel + " " + target + " :" + message); 109 | }], 110 | automode: function(direction, mode, args) { 111 | var channel = this.getActiveWindow().name; 112 | 113 | var modes = direction; 114 | for(var i=0;i 0) 182 | w.removeChild(w.firstChild); 183 | }], 184 | cmd_PART: [false, 2, 0, function(args) { 185 | var w = this.getActiveWindow(); 186 | var message = ""; 187 | var channel; 188 | 189 | if(w.type != qwebirc.ui.WINDOW_CHANNEL) { 190 | if(!args || args.length == 0) { 191 | w.errorMessage("Insufficient arguments for command."); 192 | return; 193 | } 194 | channel = args[0]; 195 | if(args.length > 1) 196 | message = args[1]; 197 | } else { 198 | if(!args || args.length == 0) { 199 | channel = w.name; 200 | } else { 201 | var isChan = this.session.irc.isChannel(args[0]); 202 | if(isChan) { 203 | channel = args[0]; 204 | if(args.length > 1) 205 | message = args[1]; 206 | } else { 207 | channel = w.name; 208 | message = args.join(" "); 209 | } 210 | } 211 | } 212 | 213 | this.send("PART " + channel + " :" + message); 214 | }] 215 | }); 216 | -------------------------------------------------------------------------------- /js/irc/ircconnection.js: -------------------------------------------------------------------------------- 1 | /* This could do with a rewrite from scratch. */ 2 | 3 | qwebirc.irc.IRCConnection = new Class({ 4 | Implements: [Events, Options], 5 | session: null, 6 | options: { 7 | initialNickname: "ircconnX", 8 | timeout: 45000, 9 | floodInterval: 200, 10 | floodMax: 10, 11 | floodReset: 5000, 12 | errorAlert: true, 13 | maxRetries: 5, 14 | serverPassword: null, 15 | authUser: null, 16 | authSecret: null 17 | }, 18 | initialize: function(session, options) { 19 | this.session = session; 20 | this.setOptions(options); 21 | 22 | this.initialNickname = this.options.initialNickname; 23 | 24 | this.counter = 0; 25 | this.disconnected = false; 26 | 27 | this.__floodLastRequest = 0; 28 | this.__floodCounter = 0; 29 | this.__floodLastFlood = 0; 30 | 31 | this.__retryAttempts = 0; 32 | 33 | this.__timeoutId = null; 34 | this.__lastActiveRequest = null; 35 | this.__activeRequest = null; 36 | 37 | this.__sendQueue = []; 38 | this.__sendQueueActive = false; 39 | }, 40 | __error: function(text) { 41 | this.fireEvent("error", text); 42 | if(this.options.errorAlert) 43 | alert(text); 44 | }, 45 | newRequest: function(url, floodProtection, synchronous) { 46 | if(this.disconnected) 47 | return null; 48 | 49 | if(floodProtection && !this.disconnected && this.__isFlooding()) { 50 | this.disconnect(); 51 | this.__error("BUG: uncontrolled flood detected -- disconnected."); 52 | } 53 | 54 | var asynchronous = true; 55 | if(synchronous) 56 | asynchronous = false; 57 | 58 | var r = new Request.JSON({ 59 | url: conf.frontend.dynamic_base_url + "e/" + url + "?r=" + this.cacheAvoidance + "&t=" + this.counter++, 60 | async: asynchronous 61 | }); 62 | 63 | /* try to minimise the amount of headers */ 64 | r.headers = new Hash; 65 | r.addEvent("request", function() { 66 | var setHeader = function(key, value) { 67 | try { 68 | this.setRequestHeader(key, value); 69 | } catch(e) { 70 | } 71 | }.bind(this); 72 | 73 | setHeader("User-Agent", null); 74 | setHeader("Accept", null); 75 | setHeader("Accept-Language", null); 76 | }.bind(r.xhr)); 77 | 78 | if(Browser.Engine.trident) 79 | r.setHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"); 80 | 81 | return r; 82 | }, 83 | __isFlooding: function() { 84 | var t = new Date().getTime(); 85 | 86 | if(t - this.__floodLastRequest < this.options.floodInterval) { 87 | if(this.__floodLastFlood != 0 && (t - this.__floodLastFlood > this.options.floodReset)) 88 | this.__floodCounter = 0; 89 | 90 | this.__floodLastFlood = t; 91 | if(this.__floodCounter++ >= this.options.floodMax) 92 | return true; 93 | } 94 | 95 | this.__floodLastRequest = t; 96 | return false; 97 | }, 98 | send: function(data, synchronous) { 99 | if(this.disconnected) 100 | return false; 101 | 102 | if(synchronous) { 103 | this.__send(data, false); 104 | } else { 105 | this.__sendQueue.push(data); 106 | this.__processSendQueue(); 107 | } 108 | 109 | return true; 110 | }, 111 | __processSendQueue: function() { 112 | if(this.__sendQueueActive || this.__sendQueue.length == 0) 113 | return; 114 | 115 | this.sendQueueActive = true; 116 | this.__send(this.__sendQueue.shift(), true); 117 | }, 118 | __send: function(data, queued) { 119 | var r = this.newRequest("p", false, !queued); /* !queued == synchronous */ 120 | if(r === null) 121 | return; 122 | 123 | r.addEvent("complete", function(o) { 124 | if(queued) 125 | this.__sendQueueActive = false; 126 | 127 | if(!o || (o[0] == false)) { 128 | this.__sendQueue = []; 129 | 130 | if(!this.disconnected) { 131 | this.disconnected = true; 132 | this.__error("An error occured: " + o[1]); 133 | } 134 | return false; 135 | } 136 | 137 | this.__processSendQueue(); 138 | }.bind(this)); 139 | 140 | r.send("s=" + this.sessionid + "&c=" + encodeURIComponent(data)); 141 | }, 142 | __processData: function(o) { 143 | if(o[0] == false) { 144 | if(!this.disconnected) { 145 | this.disconnected = true; 146 | this.__error("An error occured: " + o[1]); 147 | } 148 | return false; 149 | } 150 | 151 | this.__retryAttempts = 0; 152 | o.each(function(x) { 153 | this.fireEvent("recv", [x]); 154 | }, this); 155 | 156 | return true; 157 | }, 158 | __scheduleTimeout: function() { 159 | if(this.options.timeout) 160 | this.__timeoutId = this.__timeoutEvent.delay(this.options.timeout, this); 161 | }, 162 | __cancelTimeout: function() { 163 | if($defined(this.__timeoutId)) { 164 | $clear(this.__timeoutId); 165 | this.__timeoutId = null; 166 | } 167 | }, 168 | __timeoutEvent: function() { 169 | this.__timeoutId = null; 170 | 171 | if(!$defined(this.__activeRequest)) 172 | return; 173 | 174 | if(this.__checkRetries()) { 175 | if(this.__lastActiveRequest) 176 | this.__lastActiveRequest.cancel(); 177 | 178 | this.__activeRequest.__replaced = true; 179 | this.__lastActiveRequest = this.__activeRequest; 180 | this.recv(); 181 | } else { 182 | this.__cancelRequests(); 183 | } 184 | }, 185 | __checkRetries: function() { 186 | /* hmm, something went wrong! */ 187 | if(this.__retryAttempts++ >= this.options.maxRetries && !this.disconnected) { 188 | this.disconnect(); 189 | 190 | this.__error("Error: connection closed after several requests failed."); 191 | return false; 192 | } 193 | 194 | return true; 195 | }, 196 | recv: function() { 197 | var r = this.newRequest("s", true); 198 | if(!$defined(r)) 199 | return; 200 | 201 | this.__activeRequest = r; 202 | r.__replaced = false; 203 | 204 | var onComplete = function(o) { 205 | /* if we're a replaced requests... */ 206 | if(r.__replaced) { 207 | this.__lastActiveRequest = null; 208 | 209 | if(o) 210 | this.__processData(o); 211 | return; 212 | } 213 | 214 | /* ok, we're the main request */ 215 | this.__activeRequest = null; 216 | this.__cancelTimeout(); 217 | 218 | if(!o) { 219 | if(this.disconnected) 220 | return; 221 | 222 | if(this.__checkRetries()) 223 | this.recv(); 224 | return; 225 | } 226 | 227 | if(this.__processData(o)) 228 | this.recv(); 229 | }; 230 | 231 | r.addEvent("complete", onComplete.bind(this)); 232 | 233 | this.__scheduleTimeout(); 234 | r.send("s=" + this.sessionid); 235 | }, 236 | connect: function() { 237 | this.cacheAvoidance = qwebirc.util.randHexString(16); 238 | 239 | var r = this.newRequest("n"); 240 | r.addEvent("complete", function(o) { 241 | if(!o) { 242 | this.disconnected = true; 243 | this.__error("Couldn't connect to remote server."); 244 | return; 245 | } 246 | if(o[0] == false) { 247 | this.disconnect(); 248 | this.__error("An error occured: " + o[1]); 249 | return; 250 | } 251 | this.sessionid = o[1]; 252 | 253 | this.recv(); 254 | }.bind(this)); 255 | 256 | var postdata = "nick=" + encodeURIComponent(this.initialNickname); 257 | if($defined(this.options.serverPassword)) 258 | postdata+="&password=" + encodeURIComponent(this.options.serverPassword); 259 | if($defined(this.options.authUser) && $defined(this.options.authSecret)) { 260 | postdata+="&authUser=" + encodeURIComponent(this.options.authUser); 261 | postdata+="&authSecret=" + encodeURIComponent(this.options.authSecret); 262 | } 263 | 264 | r.send(postdata); 265 | }, 266 | __cancelRequests: function() { 267 | if($defined(this.__lastActiveRequest)) { 268 | this.__lastActiveRequest.cancel(); 269 | this.__lastActiveRequest = null; 270 | } 271 | if($defined(this.__activeRequest)) { 272 | this.__activeRequest.cancel(); 273 | this.__activeRequest = null; 274 | } 275 | }, 276 | disconnect: function() { 277 | this.disconnected = true; 278 | this.__cancelTimeout(); 279 | this.__cancelRequests(); 280 | } 281 | }); 282 | -------------------------------------------------------------------------------- /js/irc/irclib.js: -------------------------------------------------------------------------------- 1 | qwebirc.irc.IRCLowerTable = [ 2 | /* x00-x07 */ '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', 3 | /* x08-x0f */ '\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f', 4 | /* x10-x17 */ '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', 5 | /* x18-x1f */ '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', 6 | /* ' '-x27 */ ' ', '!', '"', '#', '$', '%', '&', '\x27', 7 | /* '('-'/' */ '(', ')', '*', '+', ',', '-', '.', '/', 8 | /* '0'-'7' */ '0', '1', '2', '3', '4', '5', '6', '7', 9 | /* '8'-'?' */ '8', '9', ':', ';', '<', '=', '>', '?', 10 | /* '@'-'G' */ '@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 11 | /* 'H'-'O' */ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 12 | /* 'P'-'W' */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 13 | /* 'X'-'_' */ 'x', 'y', 'z', '{', '|', '}', '~', '_', 14 | /* '`'-'g' */ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 15 | /* 'h'-'o' */ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 16 | /* 'p'-'w' */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 17 | /* 'x'-x7f */ 'x', 'y', 'z', '{', '|', '}', '~', '\x7f', 18 | /* x80-x87 */ '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', '\x87', 19 | /* x88-x8f */ '\x88', '\x89', '\x8a', '\x8b', '\x8c', '\x8d', '\x8e', '\x8f', 20 | /* x90-x97 */ '\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', 21 | /* x98-x9f */ '\x98', '\x99', '\x9a', '\x9b', '\x9c', '\x9d', '\x9e', '\x9f', 22 | /* xa0-xa7 */ '\xa0', '\xa1', '\xa2', '\xa3', '\xa4', '\xa5', '\xa6', '\xa7', 23 | /* xa8-xaf */ '\xa8', '\xa9', '\xaa', '\xab', '\xac', '\xad', '\xae', '\xaf', 24 | /* xb0-xb7 */ '\xb0', '\xb1', '\xb2', '\xb3', '\xb4', '\xb5', '\xb6', '\xb7', 25 | /* xb8-xbf */ '\xb8', '\xb9', '\xba', '\xbb', '\xbc', '\xbd', '\xbe', '\xbf', 26 | /* xc0-xc7 */ '\xe0', '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', 27 | /* xc8-xcf */ '\xe8', '\xe9', '\xea', '\xeb', '\xec', '\xed', '\xee', '\xef', 28 | /* xd0-xd7 */ '\xf0', '\xf1', '\xf2', '\xf3', '\xf4', '\xf5', '\xf6', '\xd7', 29 | /* xd8-xdf */ '\xf8', '\xf9', '\xfa', '\xfb', '\xfc', '\xfd', '\xfe', '\xdf', 30 | /* xe0-xe7 */ '\xe0', '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', 31 | /* xe8-xef */ '\xe8', '\xe9', '\xea', '\xeb', '\xec', '\xed', '\xee', '\xef', 32 | /* xf0-xf7 */ '\xf0', '\xf1', '\xf2', '\xf3', '\xf4', '\xf5', '\xf6', '\xf7', 33 | /* xf8-xff */ '\xf8', '\xf9', '\xfa', '\xfb', '\xfc', '\xfd', '\xfe', '\xff' 34 | ]; 35 | 36 | qwebirc.irc.RFC1459toIRCLower = function(x) { 37 | var p = []; 38 | for(var i=0;i -1) { 24 | c = c.replace(f, t); 25 | i = c.indexOf(f); 26 | } 27 | return c; 28 | } 29 | 30 | /* how horribly inefficient (again) */ 31 | String.prototype.splitMax = function(by, max) { 32 | var items = this.split(by); 33 | var newitems = items.slice(0, max-1); 34 | 35 | if(items.length >= max) 36 | newitems.push(items.slice(max-1).join(by)); 37 | 38 | return newitems; 39 | } 40 | 41 | /* returns the arguments */ 42 | qwebirc.util.parseURI = function(uri) { 43 | var result = {} 44 | 45 | var start = uri.indexOf('?'); 46 | if(start == -1) 47 | return result; 48 | 49 | var querystring = uri.substring(start + 1); 50 | 51 | var args = querystring.split("&"); 52 | 53 | for(var i=0;i= parent.childNodes.length)) { 133 | parent.appendChild(element); 134 | } else { 135 | parent.insertBefore(element, parent.childNodes[position]); 136 | } 137 | } 138 | 139 | qwebirc.util.setCaretPos = function(obj, pos) { 140 | if($defined(obj.selectionStart)) { 141 | obj.focus(); 142 | obj.setSelectionRange(pos, pos); 143 | } else if(obj.createTextRange) { 144 | var range = obj.createTextRange(); 145 | range.move("character", pos); 146 | range.select(); 147 | } 148 | } 149 | 150 | qwebirc.util.setAtEnd = function(obj) { 151 | qwebirc.util.setCaretPos(obj.value.length); 152 | } 153 | 154 | qwebirc.util.getCaretPos = function(element) { 155 | if($defined(element.selectionStart)) 156 | return element.selectionStart; 157 | 158 | if(document.selection) { 159 | element.focus(); 160 | var sel = document.selection.createRange(); 161 | sel.moveStart("character", -element.value.length); 162 | return sel.text.length; 163 | } 164 | } 165 | 166 | qwebirc.util.browserVersion = function() { 167 | //return "engine: " + Browser.Engine.name + " platform: " + Browser.Platform.name + " user agent: " + navigator.userAgent; 168 | return navigator.userAgent; 169 | } 170 | 171 | qwebirc.util.getEnclosedWord = function(text, position) { 172 | var l = text.split(""); 173 | var buf = []; 174 | 175 | if(text == "") 176 | return; 177 | 178 | var start = position - 1; 179 | if(start < 0) { 180 | /* special case: starting with space */ 181 | start = 0; 182 | } else { 183 | /* work back until we find the first space */ 184 | for(;start>=0;start--) { 185 | if(l[start] == ' ') { 186 | start = start + 1; 187 | break; 188 | } 189 | } 190 | } 191 | 192 | if(start < 0) 193 | start = 0; 194 | 195 | var s = text.substring(start); 196 | var pos = s.indexOf(" "); 197 | if(pos != -1) 198 | s = s.substring(0, pos); 199 | 200 | return [start, s]; 201 | } 202 | 203 | String.prototype.startsWith = function(what) { 204 | return this.substring(0, what.length) == what; 205 | } 206 | 207 | /* NOT cryptographically secure! */ 208 | qwebirc.util.randHexString = function(numBytes) { 209 | var getByte = function() { 210 | return (((1+Math.random())*0x100)|0).toString(16).substring(1); 211 | }; 212 | 213 | var l = []; 214 | for(var i=0;i")); 256 | } catch(e) { 257 | /* fallthough, trying it the proper way... */ 258 | } 259 | } 260 | 261 | r = new Element("input"); 262 | r.type = type; 263 | if(name) 264 | r.name = name; 265 | if(id) 266 | r.id = id; 267 | 268 | if(selected) 269 | r.checked = true; 270 | 271 | parent.appendChild(r); 272 | return r; 273 | } 274 | 275 | /* From: www.webtoolkit.info */ 276 | qwebirc.util.b64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 277 | qwebirc.util.b64Encode = function(data) { 278 | var output = []; 279 | var table = qwebirc.util.b64Table; 280 | for(var i=0;i> 2; 286 | var enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 287 | var enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 288 | var enc4 = chr3 & 63; 289 | 290 | if(isNaN(chr2)) { 291 | enc3 = enc4 = 64; 292 | } else if(isNaN(chr3)) { 293 | enc4 = 64; 294 | } 295 | 296 | output.push(table.charAt(enc1) + table.charAt(enc2) + table.charAt(enc3) + table.charAt(enc4)); 297 | } 298 | return output.join(""); 299 | } 300 | 301 | /* From: www.webtoolkit.info */ 302 | qwebirc.util.b64Decode = function(data) { 303 | data = data.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 304 | 305 | var output = []; 306 | var table = qwebirc.util.b64Table; 307 | for(var i=0;i> 4); 314 | var chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 315 | var chr3 = ((enc3 & 3) << 6) | enc4; 316 | 317 | output.push(String.fromCharCode(chr1)); 318 | if (enc3 != 64) 319 | output.push(String.fromCharCode(chr2)); 320 | if (enc4 != 64) 321 | output.push(String.fromCharCode(chr3)); 322 | } 323 | 324 | return output.join(""); 325 | } 326 | 327 | qwebirc.util.composeAnd = function() { 328 | var xargs = arguments; 329 | 330 | return function() { 331 | for(var i=0;i -1; 358 | } 359 | 360 | for(var i=0;i> (j * 8 + 4)) & 0x0F) + hex_chr.charAt((num >> (j * 8)) & 0x0F); 65 | return str; 66 | } 67 | 68 | /* 69 | * Convert a string to a sequence of 16-word blocks, stored as an array. 70 | * Append padding bits and the length, as described in the MD5 standard. 71 | */ 72 | function str2blks_MD5(str) 73 | { 74 | var nblk = ((str.length + 8) >> 6) + 1; 75 | var blks = new Array(nblk * 16); 76 | for(var i = 0; i < nblk * 16; i++) blks[i] = 0; 77 | for(var i = 0; i < str.length; i++) 78 | blks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8); 79 | blks[i >> 2] |= 0x80 << ((i % 4) * 8); 80 | blks[nblk * 16 - 2] = str.length * 8; 81 | return blks; 82 | } 83 | 84 | /* 85 | * Add integers, wrapping at 2^32 86 | */ 87 | function add(x, y) 88 | { 89 | return ((x&0x7FFFFFFF) + (y&0x7FFFFFFF)) ^ (x&0x80000000) ^ (y&0x80000000); 90 | } 91 | 92 | /* 93 | * Bitwise rotate a 32-bit number to the left 94 | */ 95 | function rol(num, cnt) 96 | { 97 | return (num << cnt) | (num >>> (32 - cnt)); 98 | } 99 | 100 | /* 101 | * These functions implement the basic operation for each round of the 102 | * algorithm. 103 | */ 104 | function cmn(q, a, b, x, s, t) 105 | { 106 | return add(rol(add(add(a, q), add(x, t)), s), b); 107 | } 108 | function ff(a, b, c, d, x, s, t) 109 | { 110 | return cmn((b & c) | ((~b) & d), a, b, x, s, t); 111 | } 112 | function gg(a, b, c, d, x, s, t) 113 | { 114 | return cmn((b & d) | (c & (~d)), a, b, x, s, t); 115 | } 116 | function hh(a, b, c, d, x, s, t) 117 | { 118 | return cmn(b ^ c ^ d, a, b, x, s, t); 119 | } 120 | function ii(a, b, c, d, x, s, t) 121 | { 122 | return cmn(c ^ (b | (~d)), a, b, x, s, t); 123 | } 124 | 125 | /* 126 | * Take a string and return the hex representation of its MD5. 127 | */ 128 | function calcMD5(str) 129 | { 130 | var x = str2blks_MD5(str); 131 | var a = 0x67452301; 132 | var b = 0xEFCDAB89; 133 | var c = 0x98BADCFE; 134 | var d = 0x10325476; 135 | 136 | for(var i = 0; i < x.length; i += 16) 137 | { 138 | var olda = a; 139 | var oldb = b; 140 | var oldc = c; 141 | var oldd = d; 142 | 143 | a = ff(a, b, c, d, x[i+ 0], 7 , 0xD76AA478); 144 | d = ff(d, a, b, c, x[i+ 1], 12, 0xE8C7B756); 145 | c = ff(c, d, a, b, x[i+ 2], 17, 0x242070DB); 146 | b = ff(b, c, d, a, x[i+ 3], 22, 0xC1BDCEEE); 147 | a = ff(a, b, c, d, x[i+ 4], 7 , 0xF57C0FAF); 148 | d = ff(d, a, b, c, x[i+ 5], 12, 0x4787C62A); 149 | c = ff(c, d, a, b, x[i+ 6], 17, 0xA8304613); 150 | b = ff(b, c, d, a, x[i+ 7], 22, 0xFD469501); 151 | a = ff(a, b, c, d, x[i+ 8], 7 , 0x698098D8); 152 | d = ff(d, a, b, c, x[i+ 9], 12, 0x8B44F7AF); 153 | c = ff(c, d, a, b, x[i+10], 17, 0xFFFF5BB1); 154 | b = ff(b, c, d, a, x[i+11], 22, 0x895CD7BE); 155 | a = ff(a, b, c, d, x[i+12], 7 , 0x6B901122); 156 | d = ff(d, a, b, c, x[i+13], 12, 0xFD987193); 157 | c = ff(c, d, a, b, x[i+14], 17, 0xA679438E); 158 | b = ff(b, c, d, a, x[i+15], 22, 0x49B40821); 159 | 160 | a = gg(a, b, c, d, x[i+ 1], 5 , 0xF61E2562); 161 | d = gg(d, a, b, c, x[i+ 6], 9 , 0xC040B340); 162 | c = gg(c, d, a, b, x[i+11], 14, 0x265E5A51); 163 | b = gg(b, c, d, a, x[i+ 0], 20, 0xE9B6C7AA); 164 | a = gg(a, b, c, d, x[i+ 5], 5 , 0xD62F105D); 165 | d = gg(d, a, b, c, x[i+10], 9 , 0x02441453); 166 | c = gg(c, d, a, b, x[i+15], 14, 0xD8A1E681); 167 | b = gg(b, c, d, a, x[i+ 4], 20, 0xE7D3FBC8); 168 | a = gg(a, b, c, d, x[i+ 9], 5 , 0x21E1CDE6); 169 | d = gg(d, a, b, c, x[i+14], 9 , 0xC33707D6); 170 | c = gg(c, d, a, b, x[i+ 3], 14, 0xF4D50D87); 171 | b = gg(b, c, d, a, x[i+ 8], 20, 0x455A14ED); 172 | a = gg(a, b, c, d, x[i+13], 5 , 0xA9E3E905); 173 | d = gg(d, a, b, c, x[i+ 2], 9 , 0xFCEFA3F8); 174 | c = gg(c, d, a, b, x[i+ 7], 14, 0x676F02D9); 175 | b = gg(b, c, d, a, x[i+12], 20, 0x8D2A4C8A); 176 | 177 | a = hh(a, b, c, d, x[i+ 5], 4 , 0xFFFA3942); 178 | d = hh(d, a, b, c, x[i+ 8], 11, 0x8771F681); 179 | c = hh(c, d, a, b, x[i+11], 16, 0x6D9D6122); 180 | b = hh(b, c, d, a, x[i+14], 23, 0xFDE5380C); 181 | a = hh(a, b, c, d, x[i+ 1], 4 , 0xA4BEEA44); 182 | d = hh(d, a, b, c, x[i+ 4], 11, 0x4BDECFA9); 183 | c = hh(c, d, a, b, x[i+ 7], 16, 0xF6BB4B60); 184 | b = hh(b, c, d, a, x[i+10], 23, 0xBEBFBC70); 185 | a = hh(a, b, c, d, x[i+13], 4 , 0x289B7EC6); 186 | d = hh(d, a, b, c, x[i+ 0], 11, 0xEAA127FA); 187 | c = hh(c, d, a, b, x[i+ 3], 16, 0xD4EF3085); 188 | b = hh(b, c, d, a, x[i+ 6], 23, 0x04881D05); 189 | a = hh(a, b, c, d, x[i+ 9], 4 , 0xD9D4D039); 190 | d = hh(d, a, b, c, x[i+12], 11, 0xE6DB99E5); 191 | c = hh(c, d, a, b, x[i+15], 16, 0x1FA27CF8); 192 | b = hh(b, c, d, a, x[i+ 2], 23, 0xC4AC5665); 193 | 194 | a = ii(a, b, c, d, x[i+ 0], 6 , 0xF4292244); 195 | d = ii(d, a, b, c, x[i+ 7], 10, 0x432AFF97); 196 | c = ii(c, d, a, b, x[i+14], 15, 0xAB9423A7); 197 | b = ii(b, c, d, a, x[i+ 5], 21, 0xFC93A039); 198 | a = ii(a, b, c, d, x[i+12], 6 , 0x655B59C3); 199 | d = ii(d, a, b, c, x[i+ 3], 10, 0x8F0CCC92); 200 | c = ii(c, d, a, b, x[i+10], 15, 0xFFEFF47D); 201 | b = ii(b, c, d, a, x[i+ 1], 21, 0x85845DD1); 202 | a = ii(a, b, c, d, x[i+ 8], 6 , 0x6FA87E4F); 203 | d = ii(d, a, b, c, x[i+15], 10, 0xFE2CE6E0); 204 | c = ii(c, d, a, b, x[i+ 6], 15, 0xA3014314); 205 | b = ii(b, c, d, a, x[i+13], 21, 0x4E0811A1); 206 | a = ii(a, b, c, d, x[i+ 4], 6 , 0xF7537E82); 207 | d = ii(d, a, b, c, x[i+11], 10, 0xBD3AF235); 208 | c = ii(c, d, a, b, x[i+ 2], 15, 0x2AD7D2BB); 209 | b = ii(b, c, d, a, x[i+ 9], 21, 0xEB86D391); 210 | 211 | a = add(a, olda); 212 | b = add(b, oldb); 213 | c = add(c, oldc); 214 | d = add(d, oldd); 215 | } 216 | return rhex(a) + rhex(b) + rhex(c) + rhex(d); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /js/qwebirc.js: -------------------------------------------------------------------------------- 1 | var qwebirc = {ui: {themes: {}, style: {}}, irc: {}, util: {crypto: {}}, config: {}, options: {}, auth: {}, sound: {}, connected: false}; 2 | var conf = null; 3 | var ui = null; 4 | 5 | if(typeof QWEBIRC_BUILD != "undefined") { 6 | qwebirc.BUILD = QWEBIRC_BUILD; 7 | qwebirc.FILE_SUFFIX = "-" + QWEBIRC_BUILD; 8 | } else { 9 | qwebirc.BUILD = null; 10 | qwebirc.FILE_SUFFIX = ""; 11 | } 12 | -------------------------------------------------------------------------------- /js/session.js: -------------------------------------------------------------------------------- 1 | qwebirc.sessionCount = 0; 2 | 3 | /* Stores settings and handles for a single IRC connection. */ 4 | qwebirc.session = new Class({ 5 | 6 | /* The IRC connection instance. */ 7 | irc: null, 8 | 9 | /* Atheme state. */ 10 | atheme: { 11 | state: null, 12 | user: null, 13 | token: null 14 | }, 15 | 16 | /* UI windows belonging to this session. */ 17 | windows: {}, 18 | 19 | initialize: function() { 20 | 21 | /* Load any Atheme login state. */ 22 | cookie = new Hash.Cookie("iris-auth"); 23 | if ($defined(cookie.get("user"))) { 24 | this.atheme.user = cookie.get("user"); 25 | this.atheme.secret = cookie.get("token"); 26 | } 27 | 28 | /* Check our Atheme login state. */ 29 | qwebirc.ui.Atheme.check(this); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /js/sound.js: -------------------------------------------------------------------------------- 1 | qwebirc.sound.domReady = false; 2 | window.addEvent("domready", function() { 3 | qwebirc.sound.domReady = true; 4 | }); 5 | 6 | qwebirc.sound.SoundPlayer = new Class({ 7 | Implements: [Events], 8 | session: null, 9 | initialize: function(session) { 10 | this.session = session; 11 | this.loadingSWF = false; 12 | this.loadedSWF = false; 13 | }, 14 | go: function() { 15 | if(qwebirc.sound.domReady) { 16 | this.loadSoundManager(); 17 | } else { 18 | window.addEvent("domready", function() { 19 | this.loadSoundManager(); 20 | }.bind(this)); 21 | } 22 | }, 23 | loadSoundManager: function() { 24 | if(this.loadingSWF) 25 | return; 26 | this.loadingSWF = true; 27 | if(eval("typeof soundManager") != "undefined") { 28 | this.loadedSWF = true; 29 | this.fireEvent("ready"); 30 | return; 31 | } 32 | 33 | var debugMode = false; 34 | qwebirc.util.importJS(conf.frontend.static_base_url + "js/" + (debugMode?"soundmanager2":"soundmanager2-nodebug-jsmin") + ".js", "soundManager", function() { 35 | soundManager.url = conf.frontend.static_base_url + "sound/"; 36 | 37 | soundManager.debugMode = debugMode; 38 | soundManager.useConsole = debugMode; 39 | soundManager.onload = function() { 40 | this.loadedSWF = true; 41 | this.fireEvent("ready"); 42 | }.bind(this); 43 | soundManager.beginDelayedInit(); 44 | }.bind(this)); 45 | }, 46 | createSound: function(name, src) { 47 | soundManager.createSound(name, src); 48 | }, 49 | playSound: function(name) { 50 | soundManager.play(name); 51 | }, 52 | beep: function() { 53 | if(!this.beepLoaded) { 54 | this.createSound("beep", conf.frontend.static_base_url + "sound/beep3.mp3"); 55 | this.beepLoaded = true; 56 | } 57 | this.playSound("beep"); 58 | } 59 | }); 60 | -------------------------------------------------------------------------------- /js/ui/atheme.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides handling of Atheme login state in the client interface. 3 | * Configures the client when the user changes between logged in and out, and 4 | * provides checks to determine whether which it is in. 5 | */ 6 | 7 | qwebirc.ui.Atheme = {}; 8 | 9 | /** 10 | * Handles an Atheme login. 11 | * 12 | * \param user The provided username. 13 | * \param token The user's given token. 14 | */ 15 | qwebirc.ui.Atheme.handleLogin = function(session, user, token) { 16 | 17 | /* Update state. */ 18 | session.atheme.state = true; 19 | session.atheme.user = user; 20 | session.atheme.secret = token; 21 | 22 | /* Save cookie. */ 23 | cookie = new Hash.Cookie("iris-auth"); 24 | cookie.set("user", session.atheme.user); 25 | cookie.set("token", session.atheme.secret); 26 | cookie.save(); 27 | } 28 | 29 | /** 30 | * Handle an Atheme logout. 31 | */ 32 | qwebirc.ui.Atheme.handleLogout = function() { 33 | 34 | /* Update state. */ 35 | this.state = false; 36 | } 37 | 38 | /** 39 | * Check whether the user is currently logged in, and set the client up 40 | * accordingly. 41 | */ 42 | qwebirc.ui.Atheme.check = function(session) { 43 | 44 | /* If we have a user and token, check them for validity. Otherwise, 45 | * we're definitely logged out. */ 46 | if ($defined(session.atheme.user) && $defined(session.atheme.secret) && 47 | conf.atheme.sasl_type == "AUTHCOOKIE") { 48 | qwebirc.irc.AthemeQuery.checkLogin(function(valid) { 49 | if (valid == null) 50 | session.atheme.state = null; 51 | else if (valid) 52 | this.handleLogin(session, session.atheme.user, session.atheme.secret); 53 | else 54 | this.handleLogout(); 55 | }.bind(this), session.atheme.user, session.atheme.secret); 56 | } 57 | else 58 | this.handleLogout(session); 59 | } 60 | -------------------------------------------------------------------------------- /js/ui/baseuiwindow.js: -------------------------------------------------------------------------------- 1 | qwebirc.ui.HILIGHT_NONE = 0; 2 | qwebirc.ui.HILIGHT_ACTIVITY = 1; 3 | qwebirc.ui.HILIGHT_SPEECH = 2; 4 | qwebirc.ui.HILIGHT_US = 3; 5 | 6 | qwebirc.ui.WINDOW_LASTLINE = qwebirc.ui.WINDOW_QUERY | qwebirc.ui.WINDOW_MESSAGES | qwebirc.ui.WINDOW_CHANNEL | qwebirc.ui.WINDOW_STATUS; 7 | 8 | qwebirc.ui.Window = new Class({ 9 | Implements: [Events], 10 | initialize: function(session, type, name, identifier) { 11 | this.session = session; 12 | this.type = type; 13 | this.name = name; 14 | this.active = false; 15 | this.identifier = identifier; 16 | this.hilighted = qwebirc.ui.HILIGHT_NONE; 17 | this.scrolltimer = null; 18 | this.commandhistory = ui.commandhistory; 19 | this.scrolleddown = true; 20 | this.scrollpos = null; 21 | this.lastNickHash = {}; 22 | this.lastSelected = null; 23 | this.subWindow = null; 24 | this.closed = false; 25 | 26 | if(this.type & qwebirc.ui.WINDOW_LASTLINE) { 27 | this.lastPositionLine = new Element("hr"); 28 | this.lastPositionLine.addClass("lastpos"); 29 | this.lastPositionLineInserted = false; 30 | } 31 | }, 32 | updateTopic: function(topic, element) { 33 | qwebirc.ui.Colourise(this.session, "[" + topic + "]", element); 34 | }, 35 | subEvent: function(event) { 36 | if($defined(this.subWindow)) 37 | this.subWindow.fireEvent(event); 38 | }, 39 | setSubWindow: function(window) { 40 | this.subWindow = window; 41 | }, 42 | select: function() { 43 | if(this.lastPositionLineInserted && !conf.ui.lastpos_line) { 44 | this.lines.removeChild(this.lastPositionLine); 45 | this.lastPositionLineInserted = false; 46 | } 47 | 48 | this.active = true; 49 | ui.__setActiveWindow(this); 50 | if(this.hilighted) 51 | this.setHilighted(qwebirc.ui.HILIGHT_NONE); 52 | 53 | this.subEvent("select"); 54 | this.resetScrollPos(); 55 | this.lastSelected = new Date(); 56 | }, 57 | deselect: function() { 58 | this.subEvent("deselect"); 59 | 60 | this.setScrollPos(); 61 | if($defined(this.scrolltimer)) { 62 | $clear(this.scrolltimer); 63 | this.scrolltimer = null; 64 | } 65 | 66 | if(this.type & qwebirc.ui.WINDOW_LASTLINE) 67 | this.replaceLastPositionLine(); 68 | 69 | this.active = false; 70 | }, 71 | resetScrollPos: function() { 72 | if(this.scrolleddown) { 73 | this.scrollToBottom(); 74 | } else if($defined(this.scrollpos)) { 75 | this.getScrollParent().scrollTo(this.scrollpos.x, this.scrollpos.y); 76 | } 77 | }, 78 | setScrollPos: function() { 79 | if(!ui.singleWindow) { 80 | this.scrolleddown = this.scrolledDown(); 81 | this.scrollpos = this.lines.getScroll(); 82 | } 83 | }, 84 | addLine: function(type, line, colour, element) { 85 | var hilight = qwebirc.ui.HILIGHT_NONE; 86 | var lhilight = false; 87 | 88 | if(type) { 89 | hilight = qwebirc.ui.HILIGHT_ACTIVITY; 90 | 91 | if(type.match(/(NOTICE|ACTION|MSG)$/)) { 92 | if(this.type == qwebirc.ui.WINDOW_QUERY || this.type == qwebirc.ui.WINDOW_MESSAGES) { 93 | if(type.match(/^OUR/) || type.match(/NOTICE$/)) { 94 | hilight = qwebirc.ui.HILIGHT_ACTIVITY; 95 | } else { 96 | hilight = qwebirc.ui.HILIGHT_US; 97 | ui.beep(); 98 | ui.flash(); 99 | } 100 | } 101 | if(!type.match(/^OUR/) && this.session.irc.hilightController.match(line["m"])) { 102 | lhilight = true; 103 | hilight = qwebirc.ui.HILIGHT_US; 104 | ui.beep(); 105 | ui.flash(); 106 | } else if(hilight != qwebirc.ui.HILIGHT_US) { 107 | hilight = qwebirc.ui.HILIGHT_SPEECH; 108 | } 109 | } 110 | } 111 | 112 | if(!this.active && (hilight != qwebirc.ui.HILIGHT_NONE)) 113 | this.setHilighted(hilight); 114 | 115 | if(type) 116 | line = ui.theme.message(type, line, lhilight); 117 | 118 | qwebirc.ui.Colourise(this.session, qwebirc.irc.IRCTimestamp(new Date()) + " " + line, element); 119 | this.scrollAdd(element); 120 | }, 121 | errorMessage: function(message) { 122 | this.addLine("", message, "warncolour"); 123 | }, 124 | infoMessage: function(message) { 125 | this.addLine("", message, "infocolour"); 126 | }, 127 | setHilighted: function(state) { 128 | if(state == qwebirc.ui.HILIGHT_NONE || state >= this.hilighted) 129 | this.hilighted = state; 130 | }, 131 | scrolledDown: function() { 132 | if(this.scrolltimer) 133 | return true; 134 | 135 | var parent = this.lines; 136 | 137 | var prev = parent.getScroll(); 138 | var prevbottom = parent.getScrollSize().y; 139 | var prevheight = parent.clientHeight; 140 | 141 | /* 142 | * fixes an IE bug: the scrollheight is less than the actual height 143 | * when the div isn't full 144 | */ 145 | if(prevbottom < prevheight) 146 | prevbottom = prevheight; 147 | 148 | return prev.y + prevheight == prevbottom; 149 | }, 150 | getScrollParent: function() { 151 | var scrollparent = this.lines; 152 | 153 | if($defined(this.scroller)) 154 | scrollparent = this.scroller; 155 | return scrollparent; 156 | }, 157 | scrollToBottom: function() { 158 | if(this.type == qwebirc.ui.WINDOW_CUSTOM) 159 | return; 160 | 161 | var parent = this.lines; 162 | var scrollparent = this.getScrollParent(); 163 | 164 | scrollparent.scrollTo(parent.getScroll().x, parent.getScrollSize().y); 165 | }, 166 | scrollAdd: function(element) { 167 | var parent = this.lines; 168 | 169 | /* scroll in bursts, else the browser gets really slow */ 170 | if($defined(element)) { 171 | var sd = this.scrolledDown(); 172 | parent.appendChild(element); 173 | if(sd) { 174 | if(this.scrolltimer) 175 | $clear(this.scrolltimer); 176 | this.scrolltimer = this.scrollAdd.delay(50, this, [null]); 177 | } 178 | } else { 179 | this.scrollToBottom(); 180 | this.scrolltimer = null; 181 | } 182 | }, 183 | updateNickList: function(nicks) { 184 | var nickHash = {}, present = {}; 185 | var added = []; 186 | var lnh = this.lastNickHash; 187 | 188 | for(var i=0;i= '0' && x <= '9'; 16 | } 17 | 18 | function parseColours(xline, i) { 19 | if(!isNum(xline[i + 1])) { 20 | fg = undefined; 21 | bg = undefined; 22 | return i; 23 | } 24 | i++; 25 | if(isNum(xline[i + 1])) { 26 | fg = parseInt(xline[i] + xline[i + 1]); 27 | i++; 28 | } else { 29 | fg = parseInt(xline[i]); 30 | } 31 | if(xline[i + 1] != ",") 32 | return i; 33 | if(!isNum(xline[i + 2])) 34 | return i; 35 | i+=2; 36 | 37 | if(isNum(xline[i + 1])) { 38 | bg = parseInt(xline[i] + xline[i + 1]); 39 | i++; 40 | } else { 41 | bg = parseInt(xline[i]); 42 | } 43 | return i; 44 | } 45 | 46 | function emitEndToken() { 47 | var data = ""; 48 | if(out.length > 0) { 49 | var data = qwebirc.ui.urlificate(session, element, out.join("")); 50 | entity.appendChild(element); 51 | out = []; 52 | } 53 | element = document.createElement("span"); 54 | return data; 55 | } 56 | 57 | function emitStartToken() { 58 | if(autoNickColour) 59 | return element; 60 | 61 | var classes = [] 62 | if(fg != undefined) 63 | classes.push("Xc" + fg); 64 | if(bg != undefined) 65 | classes.push("Xbc" + bg); 66 | if(bold) 67 | classes.push("Xb"); 68 | if(underline) 69 | classes.push("Xu"); 70 | element.className = classes.join(" "); 71 | } 72 | 73 | var nickColouring = conf.ui.nick_colors; 74 | var capturingNick = false; 75 | for(var i=0;i 15) 128 | bg = undefined; 129 | if(fg > 15) 130 | fg = undefined; 131 | 132 | emitStartToken(); 133 | } else { 134 | out.push(lc); 135 | } 136 | } 137 | 138 | emitEndToken(); 139 | } 140 | 141 | String.prototype.toHSBColour = function(session) { 142 | var lower = session.irc.toIRCLower(session.irc.stripPrefix(this)); 143 | if(lower == session.irc.lowerNickname) 144 | return null; 145 | 146 | var hash = 0; 147 | for(var i=0;iLoading. . ."); }; 15 | var cb = delayfn.delay(500); 16 | 17 | var r = qwebirc.ui.RequestTransformHTML(session, {url: conf.frontend.static_base_url + "panes/about.html", update: w.lines, onSuccess: function() { 18 | $clear(cb); 19 | w.lines.getElement("input[class=close]").addEvent("click", function() { 20 | ui.closeWindow(w); 21 | }); 22 | w.lines.getElement("span[class=version]").set("text", "v" + qwebirc.VERSION); 23 | }}); 24 | r.get(); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /js/ui/panes/faq.js: -------------------------------------------------------------------------------- 1 | qwebirc.ui.Panes.FAQ = { 2 | title: "FAQ", 3 | command: function(session) { return "FAQ"; }, 4 | menuitem: function(session) { return "Frequently asked questions"; }, 5 | menupos: 400 6 | }; 7 | 8 | qwebirc.ui.Panes.FAQ.pclass = new Class({ 9 | Implements: [Events], 10 | session: null, 11 | initialize: function(session, w) { 12 | this.session = session; 13 | 14 | var delayfn = function() { w.lines.set("html", "
Loading. . .
"); }; 15 | var cb = delayfn.delay(500); 16 | 17 | var r = qwebirc.ui.RequestTransformHTML(session, {url: conf.frontend.static_base_url + "panes/faq.html", update: w.lines, onSuccess: function() { 18 | $clear(cb); 19 | w.lines.getElement("input[class=close]").addEvent("click", function() { 20 | ui.closeWindow(w); 21 | }.bind(this)); 22 | }.bind(this)}); 23 | r.get(); 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /js/ui/panes/feedback.js: -------------------------------------------------------------------------------- 1 | qwebirc.ui.Panes.Feedback = { 2 | title: "Feedback", 3 | command: function(session) { return "FEEDBACK"; }, 4 | menuitem: function(session) { return "Feedback"; }, 5 | menupos: 500 6 | }; 7 | 8 | qwebirc.ui.Panes.Feedback.pclass = new Class({ 9 | Implements: [Events], 10 | session: null, 11 | initialize: function(session, w) { 12 | this.session = session; 13 | this.textboxVisible = false; 14 | 15 | var delayfn = function() { w.lines.set("html", "
Loading. . .
"); }; 16 | var cb = delayfn.delay(500); 17 | 18 | this.addEvent("select", this.onSelect); 19 | 20 | var r = qwebirc.ui.RequestTransformHTML(session, {url: conf.frontend.static_base_url + "panes/feedback.html", update: w.lines, onSuccess: function() { 21 | $clear(cb); 22 | w.lines.getElement("input[class=close]").addEvent("click", function() { 23 | ui.closeWindow(w); 24 | }.bind(this)); 25 | w.lines.getElement("input[class=close2]").addEvent("click", function() { 26 | ui.closeWindow(w); 27 | }.bind(this)); 28 | 29 | var textbox = w.lines.getElement("textarea"); 30 | this.textbox = textbox; 31 | w.lines.getElement("input[class=submitfeedback]").addEvent("click", function() { 32 | this.sendFeedback(w.lines, textbox, textbox.value); 33 | }.bind(this)); 34 | 35 | this.textboxVisible = true; 36 | this.onSelect(); 37 | }.bind(this)}); 38 | r.get(); 39 | }, 40 | onSelect: function() { 41 | if(this.textboxVisible) 42 | this.textbox.focus(); 43 | }, 44 | sendFeedback: function(parent, textbox, text) { 45 | text = text.replace(/^\s*/, "").replace(/\s*$/, ""); 46 | var mainText = parent.getElement("p[class=maintext]"); 47 | 48 | if(text.length < 25) { 49 | /* TODO: lie and throw away */ 50 | mainText.set("text", "I don't suppose you could enter a little bit more? Thanks!"); 51 | textbox.focus(); 52 | return; 53 | } 54 | 55 | this.textboxVisible = false; 56 | var mainBody = w.lines.getElement("div[class=enterarea]"); 57 | mainBody.setStyle("display", "none"); 58 | 59 | var messageBody = w.lines.getElement("div[class=messagearea]"); 60 | var messageText = w.lines.getElement("p[class=messagetext]"); 61 | var messageClose = w.lines.getElement("input[class=close2]"); 62 | 63 | messageText.set("text", "Submitting. . ."); 64 | messageBody.setStyle("display", ""); 65 | 66 | /* basic checksum to stop really lame kiddies spamming */ 67 | var checksum = 0; 68 | var esctext = encodeURIComponent(text); 69 | for(var i=0;iLoading. . ."); }; 26 | var cb = delayfn.delay(500); 27 | 28 | var r = qwebirc.ui.RequestTransformHTML(session, {url: conf.frontend.static_base_url + "panes/privacypolicy.html", update: w.lines, onSuccess: function() { 29 | $clear(cb); 30 | 31 | w.lines.getElement("input[class=close]").addEvent("click", function() { 32 | ui.closeWindow(w); 33 | }.bind(this)); 34 | }.bind(this)}); 35 | r.get(); 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /js/ui/style.js: -------------------------------------------------------------------------------- 1 | qwebirc.ui.style.ModifiableStylesheet = new Class({ 2 | initialize: function(url) { 3 | var n = this.__parseStylesheet(this.__getStylesheet(url)); 4 | 5 | this.__cssText = n.cssText; 6 | this.rules = n.rules; 7 | 8 | this.__tag = this.__createTag(); 9 | }, 10 | __createTag: function() { 11 | var tag = document.createElement("style"); 12 | tag.type = "text/css"; 13 | tag.media = "all"; 14 | 15 | document.getElementsByTagName("head")[0].appendChild(tag); 16 | 17 | return tag; 18 | }, 19 | __getStylesheet: function(url) { 20 | var r = new Request({url: url, async: false}); 21 | var result; 22 | r.addEvent("complete", function(x) { 23 | result = x; 24 | }); 25 | r.get(); 26 | return result; 27 | }, 28 | __setStylesheet: function(stylesheet) { 29 | var node = this.__tag; 30 | 31 | if(node.styleSheet) { /* IE */ 32 | node.styleSheet.cssText = stylesheet; 33 | } else { 34 | var d = document.createTextNode(stylesheet); 35 | node.appendChild(d); 36 | while(node.childNodes.length > 1) 37 | node.removeChild(node.firstChild); 38 | } 39 | }, 40 | __parseStylesheet: function(data) { 41 | var lines = data.replace("\r\n", "\n").split("\n"); 42 | 43 | var rules = {}; 44 | var i; 45 | for(i=0;i= this.list.length) 102 | this.pos = 0; 103 | 104 | return this.list[this.pos]; 105 | } 106 | }); 107 | 108 | qwebirc.ui.BaseTabCompleter = new Class({ 109 | initialize: function(session, prefix, existingNick, suffix, list) { 110 | this.existingNick = existingNick; 111 | this.prefix = prefix; 112 | this.suffix = suffix; 113 | this.iterator = new qwebirc.ui.TabIterator(session, existingNick, list); 114 | }, 115 | get: function() { 116 | var n = this.iterator.next(); 117 | if(!$defined(n)) 118 | return null; 119 | 120 | var p = this.prefix + n; 121 | return [p.length, p + this.suffix]; 122 | } 123 | }); 124 | 125 | qwebirc.ui.QueryTabCompleter = new Class({ 126 | Extends: qwebirc.ui.BaseTabCompleter, 127 | initialize: function(session, prefix, existingNick, suffix) { 128 | this.parent(session, prefix, existingNick, suffix, session.irc.lastNicks); 129 | } 130 | }); 131 | 132 | qwebirc.ui.QueryNickTabCompleter = new Class({ 133 | Extends: qwebirc.ui.BaseTabCompleter, 134 | initialize: function(session, prefix, existingText, suffix) { 135 | var nick = window.name 136 | this.parent(session, prefix, existingText, suffix, [nick]); 137 | } 138 | }); 139 | 140 | qwebirc.ui.ChannelNameTabCompleter = new Class({ 141 | Extends: qwebirc.ui.BaseTabCompleter, 142 | initialize: function(session, prefix, existingText, suffix) { 143 | 144 | /* WTB map */ 145 | var l = []; 146 | 147 | for(var c in session.irc.channels) { 148 | var w = session.windows[c]; 149 | 150 | /* redundant? */ 151 | if($defined(w)) 152 | w = w.lastSelected; 153 | 154 | l.push([w, c]); 155 | } 156 | 157 | l.sort(function(a, b) { 158 | return b[0] - a[0]; 159 | }); 160 | 161 | var l2 = []; 162 | for(var i=0;i $m"], 34 | "PRIVMSG": ["<$($N$)> $m"], 35 | "CHANNOTICE": ["-${$($N$)$}:$c- $m"], 36 | "PRIVNOTICE": ["-$($N$)- $m"], 37 | "OURCHANMSG": ["<$@$N> $m"], 38 | "OURPRIVMSG": ["<$N> $m"], 39 | "OURTARGETEDMSG": ["*$[$t$]* $m"], 40 | "OURTARGETEDNOTICE": ["[notice($[$t$])] $m"], 41 | "OURCHANNOTICE": ["-$N:$t- $m"], 42 | "OURPRIVNOTICE": ["-$N- $m"], 43 | "OURCHANACTION": [" * $N $m"], 44 | "OURPRIVACTION": [" * $N $m"], 45 | "CHANACTION": [" * ${$($N$)$} $m"], 46 | "PRIVACTION": [" * $($N$) $m"], 47 | "CHANCTCP": ["$N [$h] requested CTCP $x from $c: $m"], 48 | "PRIVCTCP": ["$N [$h] requested CTCP $x from $-: $m"], 49 | "CTCPREPLY": ["CTCP $x reply from $N: $m"], 50 | "OURCHANCTCP": ["[ctcp($t)] $x $m"], 51 | "OURPRIVCTCP": ["[ctcp($t)] $x $m"], 52 | "OURTARGETEDCTCP": ["[ctcp($t)] $x $m"], 53 | "WHOISUSER": ["$B$N$B [$h]", true], 54 | "WHOISREALNAME": [" realname : $m", true], 55 | "WHOISCHANNELS": [" channels : $m", true], 56 | "WHOISSERVER": [" server : $x [$m]", true], 57 | "WHOISACCOUNT": [" account : qwebirc://accinfo/$m", true], 58 | "WHOISIDLE": [" idle : $x [connected: $m]", true], 59 | "WHOISAWAY": [" away : $m", true], 60 | "WHOISOPER": [" oper : $BIRC Operator$B", true], 61 | "WHOISOPERNAME": [" operedas : $m", true], 62 | "WHOISACTUALLY": [" realhost : $m [ip: $x]", true], 63 | "WHOISAVAILHELP": [" oper : is available for help.", true], 64 | "WHOISREGGED": [" regged : has a registered nick.", true], 65 | "WHOISMODES": [" modes : $m", true], 66 | "WHOISREALHOST": [" realhost : $m ($x)", true], 67 | "WHOISGENERICTEXT": [" info : $m", true], 68 | "WHOISEND": ["End of WHOIS", true], 69 | "AWAY": ["$N is away: $m", true], 70 | "GENERICERROR": ["$m: $t", true], 71 | "GENERICMESSAGE": ["$m", true], 72 | "WALLOPS": ["WALLOP $n: $t", true], 73 | "CHANNELCREATIONTIME": ["Channel $c was created at: $m", true], 74 | "CHANNELMODEIS": ["Channel modes on $c are: $m", true] 75 | }; 76 | 77 | qwebirc.ui.Theme = new Class({ 78 | initialize: function(themeDict) { 79 | this.__theme = qwebirc.util.dictCopy(qwebirc.ui.themes.Default); 80 | 81 | if(themeDict) 82 | for(var k in themeDict) 83 | this.__theme[k] = themeDict[k]; 84 | 85 | for(var k in this.__theme) { 86 | if(k == "PREFIX") 87 | continue; 88 | 89 | var data = this.__theme[k]; 90 | if(data[1]) { 91 | this.__theme[k] = this.__theme["PREFIX"] + data[0]; 92 | } else { 93 | this.__theme[k] = data[0]; 94 | } 95 | } 96 | 97 | this.__ccmap = qwebirc.util.dictCopy(qwebirc.ui.themes.ThemeControlCodeMap); 98 | this.__ccmaph = qwebirc.util.dictCopy(this.__ccmap); 99 | 100 | this.__ccmaph["("] = this.message("HILIGHT", {}, this.__ccmap); 101 | this.__ccmaph[")"] = this.message("HILIGHTEND", {}, this.__ccmap); 102 | this.__ccmaph["{"] = this.__ccmaph["}"] = ""; 103 | }, 104 | __dollarSubstitute: function(x, h, mapper) { 105 | var msg = []; 106 | 107 | var n = x.split(""); 108 | for(var i=0;i 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
Iris    
Iris
AdminEngine
18 |
19 |
20 | """ 21 | 22 | FOOTER = """
""" 23 | 24 | class AdminEngineException(Exception): 25 | pass 26 | 27 | class AdminEngineAction: 28 | def __init__(self, link_text, function, uniqid=None): 29 | self._link_text = link_text 30 | self.function = function 31 | self.uniqid = uniqid 32 | 33 | def get_link(self, **kwargs): 34 | kwargs = copy.deepcopy(kwargs) 35 | if self.uniqid is not None: 36 | kwargs["uniqid"] = self.uniqid 37 | return "%s" % (urlencode(kwargs), escape(self._link_text)) 38 | 39 | class AdminEngine(resource.Resource): 40 | def __init__(self, path, services): 41 | self.__services = services 42 | self.__path = path 43 | self.__creation_time = time.time() 44 | 45 | @property 46 | def adminEngine(self): 47 | return { 48 | "Permitted hosts": (config.adminengine["hosts"],), 49 | "Started": ((time.asctime(time.localtime(self.__creation_time)),),), 50 | "Running for": (("%d seconds" % int(time.time() - self.__creation_time),),), 51 | "CPU time used (UNIX only)": (("%.2f seconds" % time.clock(),),) 52 | } 53 | 54 | def process_action(self, args): 55 | try: 56 | engine = args["engine"][0] 57 | heading = args["heading"][0] 58 | pos = int(args["pos"][0]) 59 | pos2 = int(args["pos2"][0]) 60 | 61 | uniqid = args.get("uniqid", [None])[0] 62 | 63 | obj = self.__services[engine].adminEngine[heading][pos] 64 | except KeyError: 65 | raise AdminEngineException("Bad action description.") 66 | 67 | if uniqid is None: 68 | obj[pos2].function() 69 | else: 70 | for x in obj: 71 | if not isinstance(x, AdminEngineAction): 72 | continue 73 | if x.uniqid == uniqid: 74 | x.function(uniqid) 75 | break 76 | else: 77 | raise AdminEngineException("Action does not exist.") 78 | 79 | def render_GET(self, request): 80 | if request.getClientIP() not in config.adminengine["hosts"]: 81 | raise AdminEngineException("Access denied") 82 | 83 | args = request.args.get("engine") 84 | if args: 85 | self.process_action(request.args) 86 | request.redirect("?") 87 | request.finish() 88 | return server.NOT_DONE_YET 89 | 90 | data = [HEADER] 91 | 92 | def add_list(lines): 93 | data.append("
    ") 94 | data.extend(["
  • " + escape(x) + "
  • " for x in lines]) 95 | data.append("
") 96 | 97 | def add_text(text, block="p"): 98 | data.append("<%s>%s" % (block, escape(text), block)) 99 | 100 | def brescape(text): 101 | return escape(text).replace("\n", "
") 102 | 103 | for engine, obj in self.__services.items(): 104 | if not hasattr(obj, "adminEngine"): 105 | continue 106 | add_text(engine, "h2") 107 | 108 | for heading, obj2 in obj.adminEngine.items(): 109 | add_text(heading, "h3") 110 | 111 | for pos, obj3 in enumerate(obj2): 112 | elements = [] 113 | for pos2, obj4 in enumerate(obj3): 114 | if isinstance(obj4, AdminEngineAction): 115 | elements.append(obj4.get_link(engine=engine, heading=heading, pos=pos, pos2=pos2)) 116 | else: 117 | elements.append(brescape(str(obj4))) 118 | 119 | data+=["

", " ".join(elements), "

"] 120 | 121 | data.append(FOOTER) 122 | 123 | return "".join(data) 124 | -------------------------------------------------------------------------------- /qwebirc/engines/ajaxengine.py: -------------------------------------------------------------------------------- 1 | from twisted.web import resource, server, static, error as http_error 2 | from twisted.names import client 3 | from twisted.internet import reactor, error 4 | import md5, sys, os, time, traceback, socket 5 | import qwebirc.ircclient as ircclient 6 | from adminengine import AdminEngineAction 7 | from qwebirc.util import HitCounter 8 | import qwebirc.dns as qdns 9 | import qwebirc.util.qjson as json 10 | import qwebirc.config as config 11 | Sessions = {} 12 | 13 | def get_session_id(): 14 | return md5.md5(os.urandom(16)).hexdigest() 15 | 16 | class BufferOverflowException(Exception): 17 | pass 18 | 19 | class AJAXException(Exception): 20 | pass 21 | 22 | class IDGenerationException(Exception): 23 | pass 24 | 25 | class PassthruException(Exception): 26 | pass 27 | 28 | NOT_DONE_YET = None 29 | EMPTY_JSON_LIST = json.dumps([]) 30 | 31 | def jsondump(fn): 32 | def decorator(*args, **kwargs): 33 | try: 34 | x = fn(*args, **kwargs) 35 | if x is None: 36 | return server.NOT_DONE_YET 37 | x = (True, x) 38 | except AJAXException, e: 39 | x = (False, e[0]) 40 | except PassthruException, e: 41 | return str(e) 42 | 43 | return json.dumps(x) 44 | return decorator 45 | 46 | def cleanupSession(id): 47 | try: 48 | del Sessions[id] 49 | except KeyError: 50 | pass 51 | 52 | class IRCSession: 53 | def __init__(self, id): 54 | self.id = id 55 | self.subscriptions = [] 56 | self.buffer = [] 57 | self.buflen = 0 58 | self.throttle = 0 59 | self.schedule = None 60 | self.closed = False 61 | self.cleanupschedule = None 62 | 63 | def subscribe(self, channel, notifier): 64 | timeout_entry = reactor.callLater(config.tuneback["http_ajax_request_timeout"], self.timeout, channel) 65 | def cancel_timeout(result): 66 | if channel in self.subscriptions: 67 | self.subscriptions.remove(channel) 68 | try: 69 | timeout_entry.cancel() 70 | except error.AlreadyCalled: 71 | pass 72 | notifier.addCallbacks(cancel_timeout, cancel_timeout) 73 | 74 | if len(self.subscriptions) >= config.tuneback["maxsubscriptions"]: 75 | self.subscriptions.pop(0).close() 76 | 77 | self.subscriptions.append(channel) 78 | self.flush() 79 | 80 | def timeout(self, channel): 81 | if self.schedule: 82 | return 83 | 84 | channel.write(EMPTY_JSON_LIST) 85 | if channel in self.subscriptions: 86 | self.subscriptions.remove(channel) 87 | 88 | def flush(self, scheduled=False): 89 | if scheduled: 90 | self.schedule = None 91 | 92 | if not self.buffer or not self.subscriptions: 93 | return 94 | 95 | t = time.time() 96 | 97 | if t < self.throttle: 98 | if not self.schedule: 99 | self.schedule = reactor.callLater(self.throttle - t, self.flush, True) 100 | return 101 | else: 102 | # process the rest of the packet 103 | if not scheduled: 104 | if not self.schedule: 105 | self.schedule = reactor.callLater(0, self.flush, True) 106 | return 107 | 108 | self.throttle = t + config.tuneback["update_freq"] 109 | 110 | encdata = json.dumps(self.buffer) 111 | self.buffer = [] 112 | self.buflen = 0 113 | 114 | newsubs = [] 115 | for x in self.subscriptions: 116 | if x.write(encdata): 117 | newsubs.append(x) 118 | 119 | self.subscriptions = newsubs 120 | if self.closed and not self.subscriptions: 121 | cleanupSession(self.id) 122 | 123 | def event(self, data): 124 | newbuflen = self.buflen + len(data) 125 | if newbuflen > config.tuneback["maxbuflen"]: 126 | self.buffer = [] 127 | self.client.error("Buffer overflow.") 128 | return 129 | 130 | self.buffer.append(data) 131 | self.buflen = newbuflen 132 | self.flush() 133 | 134 | def push(self, data): 135 | if not self.closed: 136 | self.client.write(data) 137 | 138 | def disconnect(self): 139 | # keep the session hanging around for a few seconds so the 140 | # client has a chance to see what the issue was 141 | self.closed = True 142 | 143 | reactor.callLater(5, cleanupSession, self.id) 144 | 145 | # DANGER! Breach of encapsulation! 146 | def connect_notice(line): 147 | return "c", "NOTICE", "", ("AUTH", "*** (qwebirc) %s" % line) 148 | 149 | class Channel: 150 | def __init__(self, request): 151 | self.request = request 152 | 153 | class SingleUseChannel(Channel): 154 | def write(self, data): 155 | self.request.write(data) 156 | self.request.finish() 157 | return False 158 | 159 | def close(self): 160 | self.request.finish() 161 | 162 | class MultipleUseChannel(Channel): 163 | def write(self, data): 164 | self.request.write(data) 165 | return True 166 | 167 | class AJAXEngine(resource.Resource): 168 | isLeaf = True 169 | 170 | def __init__(self, prefix): 171 | self.prefix = prefix 172 | self.__connect_hit = HitCounter() 173 | self.__total_hit = HitCounter() 174 | 175 | @jsondump 176 | def render_POST(self, request): 177 | path = request.path[len(self.prefix):] 178 | if path[0] == "/": 179 | handler = self.COMMANDS.get(path[1:]) 180 | if handler is not None: 181 | return handler(self, request) 182 | 183 | raise PassthruException, http_error.NoResource().render(request) 184 | 185 | def newConnection(self, request): 186 | ip = request.getClientIP() 187 | 188 | nick = request.args.get("nick") 189 | if not nick: 190 | raise AJAXException, "Nickname not supplied." 191 | nick = ircclient.irc_decode(nick[0]) 192 | 193 | password = request.args.get("password") 194 | if password is not None: 195 | password = ircclient.irc_decode(password[0]) 196 | 197 | authUser = request.args.get("authUser") 198 | if authUser is not None: 199 | authUser = ircclient.irc_decode(authUser[0]) 200 | authSecret = request.args.get("authSecret") 201 | if authSecret is not None: 202 | authSecret = ircclient.irc_decode(authSecret[0]) 203 | 204 | for i in xrange(10): 205 | id = get_session_id() 206 | if not Sessions.get(id): 207 | break 208 | else: 209 | raise IDGenerationException() 210 | 211 | session = IRCSession(id) 212 | perform = None 213 | 214 | ident, realname = config.irc["ident_string"], config.irc["realname"] 215 | if config.irc["ident"] == "hex": 216 | ident = socket.inet_aton(ip).encode("hex") 217 | elif config.irc["ident"] == "nick": 218 | ident = nick 219 | 220 | self.__connect_hit() 221 | 222 | def proceed(hostname): 223 | kwargs = dict(nick=nick, ident=ident, ip=ip, realname=realname, perform=perform, hostname=hostname) 224 | if password is not None: 225 | kwargs["password"] = password 226 | if ((authUser is not None) and (authSecret is not None)): 227 | kwargs["authUser"] = authUser 228 | kwargs["authSecret"] = authSecret 229 | 230 | 231 | client = ircclient.createIRC(session, **kwargs) 232 | session.client = client 233 | 234 | if config.irc["webirc_mode"] == "": 235 | proceed(None) 236 | else: 237 | notice = lambda x: session.event(connect_notice(x)) 238 | notice("Looking up your hostname...") 239 | def callback(hostname): 240 | notice("Found your hostname.") 241 | proceed(hostname) 242 | def errback(failure): 243 | notice("Couldn't look up your hostname!") 244 | proceed(ip) 245 | qdns.lookupAndVerifyPTR(ip, timeout=[config.tuneback["dns_timeout"]]).addCallbacks(callback, errback) 246 | 247 | Sessions[id] = session 248 | 249 | return id 250 | 251 | def getSession(self, request): 252 | bad_session_message = "Invalid session, this most likely means the server has restarted; close this dialog and then try refreshing the page." 253 | 254 | sessionid = request.args.get("s") 255 | if sessionid is None: 256 | raise AJAXException, bad_session_message 257 | 258 | session = Sessions.get(sessionid[0]) 259 | if not session: 260 | raise AJAXException, bad_session_message 261 | return session 262 | 263 | def subscribe(self, request): 264 | request.channel.cancelTimeout() 265 | self.getSession(request).subscribe(SingleUseChannel(request), request.notifyFinish()) 266 | return NOT_DONE_YET 267 | 268 | def push(self, request): 269 | command = request.args.get("c") 270 | if command is None: 271 | raise AJAXException, "No command specified." 272 | self.__total_hit() 273 | 274 | decoded = ircclient.irc_decode(command[0]) 275 | 276 | session = self.getSession(request) 277 | 278 | if len(decoded) > config.tuneback["maxlinelen"]: 279 | session.disconnect() 280 | raise AJAXException, "Line too long." 281 | 282 | try: 283 | session.push(decoded) 284 | except AttributeError: # occurs when we haven't noticed an error 285 | session.disconnect() 286 | raise AJAXException, "Connection closed by server; try reconnecting by reloading the page." 287 | except Exception, e: # catch all 288 | session.disconnect() 289 | traceback.print_exc(file=sys.stderr) 290 | raise AJAXException, "Unknown error." 291 | 292 | return True 293 | 294 | def closeById(self, k): 295 | s = Sessions.get(k) 296 | if s is None: 297 | return 298 | s.client.client.error("Closed by admin interface") 299 | 300 | @property 301 | def adminEngine(self): 302 | return { 303 | "Sessions": [(str(v.client.client), AdminEngineAction("close", self.closeById, k)) for k, v in Sessions.iteritems() if not v.closed], 304 | "Connections": [(self.__connect_hit,)], 305 | "Total hits": [(self.__total_hit,)], 306 | } 307 | 308 | COMMANDS = dict(p=push, n=newConnection, s=subscribe) 309 | 310 | -------------------------------------------------------------------------------- /qwebirc/engines/feedbackengine.py: -------------------------------------------------------------------------------- 1 | from twisted.web import resource, server, static 2 | from twisted.mail.smtp import SMTPSenderFactory, ESMTPSenderFactory 3 | from twisted.internet import defer, reactor 4 | from StringIO import StringIO 5 | from email.mime.text import MIMEText 6 | import qwebirc.util as util 7 | import qwebirc.config as config 8 | 9 | class FeedbackException(Exception): 10 | pass 11 | 12 | class FeedbackEngine(resource.Resource): 13 | isLeaf = True 14 | 15 | def __init__(self, prefix): 16 | self.prefix = prefix 17 | self.__hit = util.HitCounter() 18 | 19 | @property 20 | def adminEngine(self): 21 | return dict(Sent=[(self.__hit,)]) 22 | 23 | def render_POST(self, request): 24 | text = request.args.get("feedback") 25 | if text is None: 26 | raise FeedbackException("No text.") 27 | if len(text) > 50000: 28 | raise FeedbackException("Too much text.") 29 | 30 | text = text[0] 31 | 32 | # basic checksum to stop really lame kiddies spamming, see feedback.js for js version 33 | checksum = 0; 34 | text = text.decode("utf-8", "ignore") 35 | for x in text: 36 | checksum = ((checksum + 1) % 256) ^ (ord(x) % 256); 37 | 38 | sentchecksum = int(request.args.get("c", [0])[0]) 39 | if checksum != sentchecksum: 40 | raise FeedbackException("Bad checksum: %d vs. %d" % (sentchecksum, checksum)) 41 | 42 | msg = MIMEText(text.encode("utf-8"), _charset="utf-8") 43 | msg["Subject"] = "qwebirc feedback from %s" % request.getclientIP() 44 | msg["From"] = config.feedbackengine["from"] 45 | msg["To"] = config.feedbackengine["to"] 46 | email = StringIO(msg.as_string()) 47 | email.seek(0, 0) 48 | 49 | factorytype = SMTPSenderFactory 50 | factory = factorytype(fromEmail=config.feedbackengine["from"], toEmail=config.feedbackengine["to"], file=email, deferred=defer.Deferred()) 51 | reactor.connectTCP(config.feedbackengine["smtp_host"], config.feedbackengine["smtp_port"], factory) 52 | self.__hit() 53 | return "1" 54 | -------------------------------------------------------------------------------- /qwebirc/engines/staticengine.py: -------------------------------------------------------------------------------- 1 | from twisted.web import resource, server, static, error 2 | from qwebirc.util.gziprequest import GZipRequest 3 | import qwebirc.util as util 4 | import pprint 5 | from adminengine import AdminEngineAction 6 | 7 | # TODO, cache gzip stuff 8 | cache = {} 9 | def clear_cache(): 10 | global cache 11 | cache = {} 12 | 13 | def apply_gzip(request): 14 | accept_encoding = request.getHeader('accept-encoding') 15 | if accept_encoding: 16 | encodings = accept_encoding.split(',') 17 | for encoding in encodings: 18 | name = encoding.split(';')[0].strip() 19 | if name == 'gzip': 20 | request = GZipRequest(request) 21 | return request 22 | 23 | class StaticEngine(static.File): 24 | isLeaf = False 25 | hit = util.HitCounter() 26 | 27 | def __init__(self, *args, **kwargs): 28 | static.File.__init__(self, *args, **kwargs) 29 | 30 | def render(self, request): 31 | self.hit(request) 32 | request = apply_gzip(request) 33 | return static.File.render(self, request) 34 | 35 | @property 36 | def adminEngine(self): 37 | return { 38 | #"GZip cache": [ 39 | #("Contents: %s" % pprint.pformat(list(cache.keys())),)# AdminEngineAction("clear", d)) 40 | #], 41 | "Hits": [ 42 | (self.hit,), 43 | ] 44 | } 45 | 46 | def directoryListing(self): 47 | return error.ForbiddenResource() 48 | -------------------------------------------------------------------------------- /qwebirc/ircclient.py: -------------------------------------------------------------------------------- 1 | import twisted, sys, codecs, traceback 2 | import base64, time 3 | import qwebirc.config as config 4 | from threading import Timer 5 | from twisted.words.protocols import irc 6 | from twisted.internet import reactor, protocol 7 | from twisted.web import resource, server 8 | from twisted.protocols import basic 9 | 10 | 11 | def utf8_iso8859_1(data, table=dict((x, x.decode("iso-8859-1")) for x in map(chr, range(0, 256)))): 12 | return (table.get(data.object[data.start]), data.start+1) 13 | 14 | codecs.register_error("mixed-iso-8859-1", utf8_iso8859_1) 15 | 16 | def irc_decode(x): 17 | try: 18 | return x.decode("utf-8", "mixed-iso-8859-1") 19 | except UnicodeDecodeError: 20 | return x.decode("iso-8859-1", "ignore") 21 | 22 | class QWebIRCClient(basic.LineReceiver): 23 | delimiter = "\n" 24 | def __init__(self, *args, **kwargs): 25 | self.__nickname = "(unregistered)" 26 | self.registered = False 27 | self.saslauth = False 28 | self.authUser = None 29 | self.authSecret = None 30 | self.cap = [] 31 | self.saslTimer = Timer(15.0, self.stopSasl) 32 | self.saslTimer.start() 33 | 34 | def dataReceived(self, data): 35 | basic.LineReceiver.dataReceived(self, data.replace("\r", "")) 36 | 37 | def lineReceived(self, line): 38 | line = irc_decode(irc.lowDequote(line)) 39 | 40 | try: 41 | prefix, command, params = irc.parsemsg(line) 42 | self.handleCommand(command, prefix, params) 43 | except irc.IRCBadMessage: 44 | # emit and ignore 45 | traceback.print_exc() 46 | return 47 | 48 | if command == "CAP": 49 | if (self.registered): 50 | return 51 | 52 | # We're performing CAP negotiation. 53 | # We're receiving the list. Wait until its complete, then request what 54 | # we want. 55 | if (params[1] == "LS"): 56 | if (self.saslauth): 57 | return 58 | if (params[2] == "*"): 59 | self.cap.extend(params[3].split(" ")) 60 | else: 61 | self.cap.extend(params[2].split(" ")) 62 | reqlist = [] 63 | if ("multi-prefix" in self.cap): 64 | reqlist.append("multi-prefix") 65 | if ("sasl" in self.cap): 66 | if (self.authUser and self.authSecret): 67 | self.saslauth = True 68 | reqlist.append("sasl") 69 | if (reqlist): 70 | self.write("CAP REQ :" + ' '.join(reqlist)) 71 | self.cap = reqlist 72 | else: 73 | self.write("CAP END") 74 | 75 | # We're receiving acknowledgement of requested features. Handle it. 76 | # Once all acknowledgements are received, end CAP is SASL is not 77 | # underway. 78 | if "ACK" in params: 79 | if "sasl" in params[-1].split(" "): 80 | if (self.authUser and self.authSecret): 81 | self.write("AUTHENTICATE "+config.atheme["sasl_type"]) 82 | self.saslauth = True 83 | if (not self.saslauth): 84 | self.write("CAP END") 85 | 86 | # We're receiving negative acknowledgement; a feature upgrade was denied. 87 | # Once all acknowledgements are received, end CAP is SASL is not 88 | # underway. 89 | if (params[1] == "NAK"): 90 | for item in params[2].split(" "): 91 | self.cap.remove(item) 92 | if (not self.saslauth): 93 | self.write("CAP END") 94 | 95 | 96 | # Handle SASL authentication requests. 97 | if (command == "AUTHENTICATE"): 98 | if (not self.saslauth): 99 | return 100 | 101 | # We're performing SASL auth. Send them our authcookie. 102 | authtext = base64.b64encode('\0'.join([self.authUser, self.authUser, self.authSecret])) 103 | while (len(authtext) >= 400): 104 | self.write("AUTHENTICATE " + authtext[0:400]) 105 | authtext = authtext[400:] 106 | if (len(authtext) != 0): 107 | self.write("AUTHENTICATE " + authtext) 108 | else: 109 | self.write("AUTHENTICATE +") 110 | 111 | # Handle SASL result messages. 112 | # End CAP after one of these, unless an acknowledgement message is still 113 | # waiting. 114 | if (command in ["903", "904", "905","906","907"]): 115 | if (self.saslauth): 116 | self.saslauth = False 117 | if (not self.saslauth): 118 | self.write("CAP END") 119 | 120 | if command == "001": 121 | self.registered = True 122 | self.__nickname = params[0] 123 | 124 | if self.__perform is not None: 125 | for x in self.__perform: 126 | self.write(x) 127 | self.__perform = None 128 | elif command == "NICK": 129 | nick = prefix.split("!", 1)[0] 130 | if nick == self.__nickname: 131 | self.__nickname = params[0] 132 | 133 | def stopSasl(self): 134 | """Cancels SASL authentication. Useful if Services are absent.""" 135 | if (self.saslauth): 136 | self.saslauth = False 137 | self.write("CAP END") 138 | 139 | # Send an internally-generated failure response to the client. 140 | self.handleCommand("904", "QWebIRC", ["*", "SASL authentication timed out"]) 141 | 142 | def handleCommand(self, command, prefix, params): 143 | self("c", command, prefix, params) 144 | 145 | def __call__(self, *args): 146 | self.factory.publisher.event(args) 147 | 148 | def write(self, data): 149 | self.transport.write("%s\r\n" % irc.lowQuote(data.encode("utf-8"))) 150 | 151 | def connectionMade(self): 152 | basic.LineReceiver.connectionMade(self) 153 | 154 | self.lastError = None 155 | 156 | # Start CAP negotiation. 157 | self.write("CAP LS"); 158 | 159 | f = self.factory.ircinit 160 | nick, ident, ip, realname, hostname, pass_ = f["nick"], f["ident"], f["ip"], f["realname"], f["hostname"], f.get("password") 161 | self.__nickname = nick 162 | self.__perform = f.get("perform") 163 | self.authUser = f.get("authUser") 164 | self.authSecret = f.get("authSecret") 165 | 166 | if config.irc["webirc_mode"] == "webirc": 167 | self.write("WEBIRC %s qwebirc %s %s" % (config.irc["webirc_password"], hostname, ip)) 168 | self.write("USER %s bleh %s :%s" % (ident, ip, realname)) 169 | elif config.irc["webirc_mode"] == "realname": 170 | if ip == hostname: 171 | dispip = ip 172 | else: 173 | dispip = "%s/%s" % (hostname, ip) 174 | 175 | self.write("USER %s bleh bleh :%s - %s" % (ident, dispip, realname)) 176 | else: 177 | self.write("USER %s bleh bleh %s" % (ident, ip)) 178 | 179 | if pass_ is not None: 180 | self.write("PASS :%s" % pass_) 181 | self.write("NICK %s" % nick) 182 | 183 | self.factory.client = self 184 | self("connect") 185 | 186 | def __str__(self): 187 | return "" % (self.__nickname, self.factory.ircinit["ident"], self.factory.ircinit["ip"]) 188 | 189 | def connectionLost(self, reason): 190 | if self.lastError: 191 | self.disconnect("Connection to IRC server lost: %s" % self.lastError) 192 | else: 193 | self.disconnect("Connection to IRC server lost.") 194 | self.factory.client = None 195 | basic.LineReceiver.connectionLost(self, reason) 196 | 197 | def error(self, message): 198 | self.lastError = message 199 | self.write("QUIT :qwebirc exception: %s" % message) 200 | self.transport.loseConnection() 201 | 202 | def disconnect(self, reason): 203 | self("disconnect", reason) 204 | self.factory.publisher.disconnect() 205 | 206 | class QWebIRCFactory(protocol.ClientFactory): 207 | protocol = QWebIRCClient 208 | def __init__(self, publisher, **kwargs): 209 | self.client = None 210 | self.publisher = publisher 211 | self.ircinit = kwargs 212 | 213 | def write(self, data): 214 | self.client.write(data) 215 | 216 | def error(self, reason): 217 | self.client.error(reason) 218 | 219 | def clientConnectionFailed(self, connector, reason): 220 | protocol.ClientFactory.clientConnectionFailed(self, connector, reason) 221 | self.publisher.event(["disconnect", "Connection to IRC server failed."]) 222 | self.publisher.disconnect() 223 | 224 | def createIRC(*args, **kwargs): 225 | f = QWebIRCFactory(*args, **kwargs) 226 | server = config.irc["server"] 227 | port = config.irc["port"] 228 | 229 | bind_ip = config.irc["bind_ip"] 230 | bindToAddress = None 231 | if bind_ip: 232 | bindToAddress = (bind_ip, 0) 233 | 234 | if config.irc["ssl"]: 235 | from twisted.internet.ssl import ClientContextFactory 236 | reactor.connectSSL(server, port, f, ClientContextFactory(), bindAddress=bindToAddress) 237 | else: 238 | reactor.connectTCP(server, port, f, bindAddress=bindToAddress) 239 | return f 240 | 241 | if __name__ == "__main__": 242 | e = createIRC(lambda x: 2, nick="slug__moo", ident="mooslug", ip="1.2.3.6", realname="mooooo", hostname="1.2.3.4") 243 | reactor.run() 244 | -------------------------------------------------------------------------------- /qwebirc/log.py: -------------------------------------------------------------------------------- 1 | def NullLogger(): 2 | def logger(*args, **kwargs): 3 | pass 4 | 5 | return logger 6 | -------------------------------------------------------------------------------- /qwebirc/root.py: -------------------------------------------------------------------------------- 1 | from twisted.web import resource, server, static, http 2 | from twisted.internet import error, reactor 3 | import engines 4 | import mimetypes 5 | import config 6 | import sigdebug 7 | import re 8 | 9 | class RootResource(resource.Resource): 10 | def getChild(self, name, request): 11 | if name == "": 12 | name = "qui.html" 13 | return self.primaryChild.getChild(name, request) 14 | 15 | # we do NOT use the built-in timeOut mixin as it's very very buggy! 16 | class TimeoutHTTPChannel(http.HTTPChannel): 17 | timeout = config.tuneback["http_request_timeout"] 18 | 19 | def connectionMade(self): 20 | self.customTimeout = reactor.callLater(self.timeout, self.timeoutOccured) 21 | http.HTTPChannel.connectionMade(self) 22 | 23 | def timeoutOccured(self): 24 | self.customTimeout = None 25 | self.transport.loseConnection() 26 | 27 | def cancelTimeout(self): 28 | if self.customTimeout is not None: 29 | try: 30 | self.customTimeout.cancel() 31 | self.customTimeout = None 32 | except error.AlreadyCalled: 33 | pass 34 | 35 | def connectionLost(self, reason): 36 | self.cancelTimeout() 37 | http.HTTPChannel.connectionLost(self, reason) 38 | 39 | class ProxyRequest(server.Request): 40 | ip_re = re.compile(r"^((25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})|(::|(([a-fA-F0-9]{1,4}):){7}(([a-fA-F0-9]{1,4}))|(:(:([a-fA-F0-9]{1,4})){1,6})|((([a-fA-F0-9]{1,4}):){1,6}:)|((([a-fA-F0-9]{1,4}):)(:([a-fA-F0-9]{1,4})){1,6})|((([a-fA-F0-9]{1,4}):){2}(:([a-fA-F0-9]{1,4})){1,5})|((([a-fA-F0-9]{1,4}):){3}(:([a-fA-F0-9]{1,4})){1,4})|((([a-fA-F0-9]{1,4}):){4}(:([a-fA-F0-9]{1,4})){1,3})|((([a-fA-F0-9]{1,4}):){5}(:([a-fA-F0-9]{1,4})){1,2})))$", re.IGNORECASE) 41 | def validIP(self, ip): 42 | m = self.ip_re.match(ip) 43 | if m is None: 44 | return False 45 | return True 46 | 47 | def getClientIP(self): 48 | real_ip = http.Request.getClientIP(self) 49 | if real_ip not in config.proxy["forwarded_for_ips"]: 50 | return real_ip 51 | 52 | fake_ips = self.getHeader(config.proxy["forwarded_for_header"]) 53 | if fake_ips is None: 54 | return real_ip 55 | 56 | fake_ip = fake_ips.split(",")[-1].strip() 57 | if not self.validIP(fake_ip): 58 | return real_ip 59 | 60 | return fake_ip 61 | 62 | class RootSite(server.Site): 63 | # we do this ourselves as the built in timeout stuff is really really buggy 64 | protocol = TimeoutHTTPChannel 65 | 66 | if config.proxy["forwarded_for_header"]: 67 | requestFactory = ProxyRequest 68 | 69 | def __init__(self, path, *args, **kwargs): 70 | root = RootResource() 71 | server.Site.__init__(self, root, *args, **kwargs) 72 | 73 | services = {} 74 | services["StaticEngine"] = root.primaryChild = engines.StaticEngine(path) 75 | 76 | def register(service, path, *args, **kwargs): 77 | sobj = service("/" + path, *args, **kwargs) 78 | services[service.__name__] = sobj 79 | root.putChild(path, sobj) 80 | 81 | register(engines.AJAXEngine, "e") 82 | register(engines.FeedbackEngine, "feedback") 83 | register(engines.AdminEngine, "adminengine", services) 84 | if config.athemeengine["xmlrpc_path"]: 85 | register(engines.AthemeEngine, "a") 86 | 87 | mimetypes.types_map[".ico"] = "image/vnd.microsoft.icon" 88 | -------------------------------------------------------------------------------- /qwebirc/sigdebug.py: -------------------------------------------------------------------------------- 1 | import platform 2 | 3 | if platform.system() != "Windows": 4 | # sorry windows users! 5 | 6 | import signal 7 | import traceback 8 | 9 | signal.signal(signal.SIGUSR2, lambda sig, stack: traceback.print_stack(stack)) 10 | 11 | -------------------------------------------------------------------------------- /qwebirc/util/__init__.py: -------------------------------------------------------------------------------- 1 | from hitcounter import HitCounter 2 | -------------------------------------------------------------------------------- /qwebirc/util/ciphers.py: -------------------------------------------------------------------------------- 1 | # write me 2 | 3 | def xor(a, b): 4 | assert(len(a) == len(b)) 5 | out = [] 6 | for i in range(0, len(a)): 7 | out.append(chr(ord(a[i]) ^ ord(b[i]))) 8 | 9 | return "".join(out) 10 | 11 | class CBC: 12 | def __init__(self, cipher, iv): 13 | self.__cipher = cipher 14 | self.__prevblock = False 15 | self.__iv = iv 16 | 17 | def encrypt(self, block): 18 | if not self.__prevblock: 19 | i = self.__iv 20 | else: 21 | i = self.__prevblock 22 | 23 | c = xor(block, i) 24 | 25 | self.__prevblock = self.__cipher.encrypt(c) 26 | return self.__prevblock 27 | 28 | def decrypt(self, block): 29 | c = self.__cipher.decrypt(block) 30 | if not self.__prevblock: 31 | i = self.__iv 32 | else: 33 | i = self.__prevblock 34 | 35 | c = xor(c, i) 36 | 37 | self.__prevblock = block 38 | return c 39 | -------------------------------------------------------------------------------- /qwebirc/util/gziprequest.py: -------------------------------------------------------------------------------- 1 | import struct, zlib 2 | 3 | class GZipRequest(object): 4 | """Wrapper for a request that applies a gzip content encoding""" 5 | 6 | def __init__(self, request, compressLevel=6): 7 | self.request = request 8 | self.request.setHeader('Content-Encoding', 'gzip') 9 | # Borrowed from twisted.web2 gzip filter 10 | self.compress = zlib.compressobj(compressLevel, zlib.DEFLATED, -zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL,0) 11 | 12 | def __getattr__(self, attr): 13 | if 'request' in self.__dict__: 14 | return getattr(self.request, attr) 15 | 16 | raise AttributeError, attr 17 | 18 | def __setattr__(self, attr, value): 19 | if 'request' in self.__dict__: 20 | return setattr(self.request, attr, value) 21 | 22 | self.__dict__[attr] = value 23 | 24 | def write(self, data): 25 | if not self.request.startedWriting: 26 | self.crc = zlib.crc32('') 27 | self.size = self.csize = 0 28 | # XXX: Zap any length for now since we don't know final size 29 | if self.request.responseHeaders.hasHeader('content-length'): 30 | self.request.responseHeaders.removeHeader('content-length') 31 | # Borrow header information from twisted.web2 gzip filter 32 | self.request.write('\037\213\010\000' '\0\0\0\0' '\002\377') 33 | 34 | self.crc = zlib.crc32(data, self.crc) 35 | self.size += len(data) 36 | cdata = self.compress.compress(data) 37 | self.csize += len(cdata) 38 | if cdata: 39 | self.request.write(cdata) 40 | elif self.request.producer: 41 | # Simulate another pull even though it hasn't really made it out to the consumer yet. 42 | self.request.producer.resumeProducing() 43 | 44 | def finish(self): 45 | remain = self.compress.flush() 46 | self.csize += len(remain) 47 | if remain: 48 | self.request.write(remain) 49 | 50 | self.request.write(struct.pack(' 2 | 3 | 6 | 10 |
7 |

Iris

8 |

9 |
11 |
12 | 13 | 18 | 19 |

Licensed under the GNU General Public License, version 2 only.
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21 | 22 |

Special thanks from the qwebirc project to various comments/suggestions/hardware/bug reports from Zarjazz, Bazerka, boojah, meeb, Microbe, morphium, Starman, truff, coekie, the muppets in #rogue (hi dunks!) and everyone else I forgot :(.

23 | 24 |

This software contains portions by the following third parties:

25 | 26 |

27 | 50 |

51 | 52 | 53 | -------------------------------------------------------------------------------- /static/panes/faq.html: -------------------------------------------------------------------------------- 1 |

Frequently Asked Questions

2 | 3 |

Can I make the widget autoconnect?

4 | 5 |

No, if this was allowed then bad people could IFRAME lots and lots of copies, which would get you glined for having too many clones.
However you can prefill the channel/nickname information (type /EMBED in the main window).

6 | 7 | 8 | -------------------------------------------------------------------------------- /static/panes/feedback.html: -------------------------------------------------------------------------------- 1 |

Feedback

2 | 3 |
4 |

We'd love to hear what you think about our web IRC client (in English please):

5 |

6 |

Include your name if you'd like us to get back to you!

7 | 8 | 9 | 10 |
11 | 15 | -------------------------------------------------------------------------------- /static/panes/privacypolicy.html.example: -------------------------------------------------------------------------------- 1 |

Privacy Policy

2 | 3 |

Privacy policy goes here!

4 | -------------------------------------------------------------------------------- /static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /static/sound/beep.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atheme-legacy/iris/5cf2525ac8b74d95d17384111a0a575c66ba0a0c/static/sound/beep.mp3 -------------------------------------------------------------------------------- /static/sound/beep2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atheme-legacy/iris/5cf2525ac8b74d95d17384111a0a575c66ba0a0c/static/sound/beep2.mp3 -------------------------------------------------------------------------------- /static/sound/beep2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atheme-legacy/iris/5cf2525ac8b74d95d17384111a0a575c66ba0a0c/static/sound/beep2.wav -------------------------------------------------------------------------------- /static/sound/beep3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atheme-legacy/iris/5cf2525ac8b74d95d17384111a0a575c66ba0a0c/static/sound/beep3.mp3 -------------------------------------------------------------------------------- /static/sound/beep3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atheme-legacy/iris/5cf2525ac8b74d95d17384111a0a575c66ba0a0c/static/sound/beep3.wav -------------------------------------------------------------------------------- /static/sound/soundmanager2.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atheme-legacy/iris/5cf2525ac8b74d95d17384111a0a575c66ba0a0c/static/sound/soundmanager2.swf -------------------------------------------------------------------------------- /static/sound/soundmanager2_flash9.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atheme-legacy/iris/5cf2525ac8b74d95d17384111a0a575c66ba0a0c/static/sound/soundmanager2_flash9.swf -------------------------------------------------------------------------------- /twisted/plugins/webirc.py: -------------------------------------------------------------------------------- 1 | from zope.interface import implements 2 | 3 | from twisted.python import usage 4 | from twisted.internet import task 5 | from twisted.plugin import IPlugin 6 | from twisted.application.service import IServiceMaker 7 | from twisted.application import internet, strports 8 | from twisted.web import static, server 9 | 10 | from qwebirc.root import RootSite 11 | 12 | class Options(usage.Options): 13 | optParameters = [["port", "p", "9090","Port to start the server on."], 14 | ["ip", "i", "0.0.0.0", "IP address to listen on."], 15 | ["logfile", "l", None, "Path to web CLF (Combined Log Format) log file."], 16 | ["https", None, None, "Port to listen on for Secure HTTP."], 17 | ["certificate", "c", "server.pem", "SSL certificate to use for HTTPS. "], 18 | ["privkey", "k", "server.pem", "SSL certificate to use for HTTPS."], 19 | ["certificate-chain", "C", None, "Chain SSL certificate"], 20 | ["staticpath", "s", "static", "Path to static content"], 21 | ] 22 | 23 | optFlags = [["notracebacks", "n", "Display tracebacks in broken web pages. " + 24 | "Displaying tracebacks to users may be security risk!"], 25 | ] 26 | 27 | def postOptions(self): 28 | if self['https']: 29 | try: 30 | get_ssl_factory_factory() 31 | except ImportError: 32 | raise usage.UsageError("SSL support not installed") 33 | 34 | class QWebIRCServiceMaker(object): 35 | implements(IServiceMaker, IPlugin) 36 | tapname = "qwebirc" 37 | description = "QuakeNet web-based IRC client" 38 | options = Options 39 | 40 | def makeService(self, config): 41 | if config['logfile']: 42 | site = RootSite(config['staticpath'], logPath=config['logfile']) 43 | else: 44 | site = RootSite(config['staticpath']) 45 | 46 | site.displayTracebacks = not config["notracebacks"] 47 | if config['https']: 48 | ssl_factory = get_ssl_factory_factory() 49 | i = internet.SSLServer(int(config['https']), site, ssl_factory(config['privkey'], config['certificate'], certificateChainFile=config["certificate-chain"]), interface=config['ip']) 50 | else: 51 | i = internet.TCPServer(int(config['port']), site, interface=config['ip']) 52 | 53 | return i 54 | 55 | def get_ssl_factory_factory(): 56 | from twisted.internet.ssl import DefaultOpenSSLContextFactory 57 | class ChainingOpenSSLContextFactory(DefaultOpenSSLContextFactory): 58 | def __init__(self, *args, **kwargs): 59 | self.chain = None 60 | if kwargs.has_key("certificateChainFile"): 61 | self.chain = kwargs["certificateChainFile"] 62 | del kwargs["certificateChainFile"] 63 | 64 | DefaultOpenSSLContextFactory.__init__(self, *args, **kwargs) 65 | 66 | def cacheContext(self): 67 | DefaultOpenSSLContextFactory.cacheContext(self) 68 | if self.chain: 69 | self._context.use_certificate_chain_file(self.chain) 70 | self._context.use_privatekey_file(self.privateKeyFileName) 71 | 72 | return ChainingOpenSSLContextFactory 73 | 74 | serviceMaker = QWebIRCServiceMaker() 75 | -------------------------------------------------------------------------------- /util/syslog.py: -------------------------------------------------------------------------------- 1 | """ 2 | Limited drop in replacement for the syslog module. 3 | To use this symlink it into your PYTHONPATH. 4 | """ 5 | 6 | from twisted.internet.protocol import DatagramProtocol 7 | import os 8 | 9 | from config import SYSLOG_ADDR as ADDR 10 | IDENT = "syslog" 11 | 12 | protocol, opened = None, False 13 | 14 | class __SyslogProtocol(DatagramProtocol): 15 | def __init__(self): 16 | self.pid = os.getpid() # FORK WARNING 17 | 18 | def send(self, data): 19 | if self.transport is None: # oh well, it's UDP =) 20 | return 21 | self.transport.write("<1> %s[%d]: %s\n" % (self.ident, self.pid, data), ADDR) 22 | 23 | def close(self): 24 | if self.transport is None: 25 | return 26 | self.transport.stopListening() 27 | 28 | def __open_protocol(): 29 | global opened 30 | 31 | if opened: 32 | return 33 | 34 | opened = True 35 | from twisted.internet import reactor 36 | reactor.listenUDP(0, protocol) 37 | 38 | def __build_protocol(ident=IDENT): 39 | global protocol 40 | 41 | if protocol is not None: 42 | return 43 | 44 | protocol = __SyslogProtocol() 45 | protocol.ident = ident 46 | 47 | def syslog(data): 48 | __build_protocol() 49 | __open_protocol() 50 | protocol.send(data) 51 | 52 | def openlog(ident, logopt=None, facility=None): 53 | __build_protocol(ident) 54 | 55 | def closelog(): 56 | global protocol, opened 57 | 58 | opened = False 59 | if protocol is None: 60 | return 61 | 62 | protocol.close() 63 | protocol = None 64 | 65 | def setlogmask(maskpri): 66 | pass 67 | 68 | if __name__ == "__main__": 69 | from twisted.internet import reactor 70 | openlog("wibble") 71 | syslog("HI\n") 72 | closelog() 73 | reactor.run() 74 | 75 | LOG_ALERT = LOG_AUTH = LOG_CONS = LOG_CRIT = LOG_CRON = LOG_DAEMON = LOG_DEBUG = LOG_EMERG = LOG_ERR = LOG_INFO = \ 76 | LOG_KERN = LOG_LOCAL0 = LOG_LOCAL1 = LOG_LOCAL2 = LOG_LOCAL3 = LOG_LOCAL4 = LOG_LOCAL5 = LOG_LOCAL6 = LOG_LOCAL7 = \ 77 | LOG_LPR = LOG_MAIL = LOG_NDELAY = LOG_NEWS = LOG_NOTICE = LOG_NOWAIT = LOG_PERROR = LOG_PID = LOG_SYSLOG = \ 78 | LOG_UPTO = LOG_USER = LOG_UUCP = LOG_WARNING = 0 79 | 80 | LOG_MASK = LOG_UPTO = lambda *args, **kwargs: 0 81 | --------------------------------------------------------------------------------