#!/usr/bin/env python3.0 """ License under the GPLv3. If you do not have a copy of it, you can read it at http://www.gnu.org/licenses/gpl-3.0.html . Instructions for use are located two comment strings down. If you make any changes, please append a log of your name, date, contact information, and changes or other information here: James Royston. May 30, 2009. purpleposeidon at gmail.com. Initial version. I got the idea from the documentation from... some python CMS project. Unfortunately, I can't find it. It would be good if someone could, because they did a munch better job of it. Scratch that, this is cooler. It was written using python3.0 (originally python2.5) on a FreeBSD machine. """ """ TODO: Better captcha? Maybe use a REAL database? Email-notification? """ """ HOW TO SET UP A BOOK [0] First, change the variable 'src' to whatever is appropriate for you. [1] Create a directory for the html files in your book, under the 'src' directory defined in this program. Make the directory write-able by the server, and also every sub-directory. [2] Next, edit each html page. See the "How to Markup a File", below. You must provide for something like the value of "INSERTSTYLE", (defined way below in the program), to be included in the header. Then, you must mark out where the comments will be inserted. The file is going to have comments, then it must have a COM, and an ENDCOM. If you don't provide labels for the COM's, they'll be provided automatically, and counted off as '0', '1', '2'... If you need to re-structure the comments, uhm, remember this. If you just insert an un-labeled COM section, everything below will be shoved down. If you are merely adding a new comment section, you can just give it a unique tag-name. The tagnames are used to refer to the comment in the html, and in the databases, so be sure to not use excessive or odd characters. [3] Once you've marked up all of your files, you'll probably want to create a .index file. In the directory, create a file, named .index, that has the name of the file you want the user to be sent to. (You might want to provide a link inside the file reffered to be .index that points up a level...) HOW TO MARKUP A FILE These texts must be on a line, lonely and all bythemselves, just like me su zo'osi. INSERTSTYLE - Put this inside the header somewhere, it is replaced with the required CSS and javascript INSERTCSS - Put this inside the the header somewhere, it is where the CSS is embedded INSERTJS - Put this inside the header somewhere, it is where the javascript is embedded COM label - Use this to start a comment, giving it the tag 'label' COMSEP label - Use this to end the previous comment, and start a new one ENDCOM - Use this to end the comment You don't have to include the label. If it is excluded, it is replaced with the comment-number. These commands can (and probably should) be enclosed in comments, like so: """ ### Configuration variables src = '/home/public/docsrc' basename = '/docs' src_basename = '/docsrc/' admin_passwords = '/home/protected/jbo_ralju' bad_words = "lol rofl lmao wtf roflmao damn fuck shit cunt".split()+['lojban sucks'] allowed_tags = "b, u, i, strike, big, small, p, br, table, thead, tfoot, tbody, tr, td, ol, ul, li, blockquote, br, center, cite, code, dl, dt, dd, em, h1, h2, h3, h4, h5, h6, del, ins, em, strong, dfn, code, samp, kbd, pre, var, cite, blockquote, hr" ###And now the code begins import time start = time.time() import shelve import cgi import cgitb; cgitb.enable() import os import sys import re form = cgi.FieldStorage() use_html = False html_message = "Content-type: text/html" if 'HTTP_USER_AGENT' in os.environ: ua = os.environ['HTTP_USER_AGENT'] crap_ie = 'MSIE' in ua #crap_ie = 'MSIE 6' in ua or 'MSIE 5' in ua else: crap_ie = False def html(): global use_html use_html = True print(html_message) print("Cache-Control: public") #XXX - make this work with NFS's caching servers... print() def message(msg, newurl, t): html() print(""" %s %s %s """ % (msg, t, INSERTSTYLE, newurl, msg)) raise SystemExit def redirect(x): print(html_message) print("Location: %s" % x) print() print( 'Go here.' %(x, x)) raise SystemExit() def relocate(x): print("Location: " % x) def error404(): print("Status: 404 Not Found") print(html_message) print() print("

404 - object not found

") # print("How do you write a proper 404 error?") raise SystemExit() def noban(): #Blacklist to keep spammers/trolls from writing comments try: bh = open('banhammer').read() except: return if os.environ['REMOTE_ADDR'] in bh: html() print("You are banned. GTFO.") raise SystemExit def str_comment(file_path, tag, com_num, com): #name, text, date, IP #pack = list(cgi.escape(field) for field in com) name, text, date, ip, has_errata, *padding = com #This line is why I converted to 3.0 zo'o....nai name, text, date, ip = (cgi.escape(field) for field in (name, text, date, ip)) text = format_comment(com[1]) head = '%s (%s) on %s' % (name, ip, date) if has_errata: head += ' has noted an error' body = '
%s
' % (text) head += ': Δ' % (file_path, tag, com_num) return head+body def open_paragraph(f, tag, comments): has_error = error_comment(comments) print('
' % (" has_error"*has_error), end='') print('%s' % (tag, tag, len(comments))) print('
' % (tag)) print('
') print(' ' % (tag, tag)) if comments: i = 0 for com in comments: print(str_comment(f, tag, i, com)) i += 1 else: print("(No comment)") print("
") comment_form(f, tag) print("
") print("
") def close_paragraph(): print("
") def file_dir(): d = os.walk(src).send(None)[2] #All the files. Only files. for item in d: if item.startswith('.') or '/' in item or '@' in item: continue yield item def safe_file_dir(): v = os.getcwd() os.chdir(src) for set in os.walk('./'): dir_name = set[0].replace('./', '') if dir_name and ('@' in dir_name or dir_name[0] == '.'): continue for f in set[2]: if '@' in f or f[0] == '.': continue yield os.path.join(dir_name, f) for d in set[1]: if '@' in d or d[0] == '.': continue yield os.path.join(dir_name, d)+'/' os.chdir(v) def dir_dir(): d = os.walk(src).send(None)[1] #All of the directories for item in d: a = os.walk(os.path.join(src, item)).send(None) if len(a[1])+len(a[2]) and not item.startswith('.'): yield item def doc_dir(subpath=''): try: if subpath.endswith('/') or 1: index_file = os.path.join(src, '.index') use_this_file = open(index_file).read().strip() if use_this_file in safe_file_dir(): show_file(use_this_file) return else: html() print("Hey! The .index file is invalid.") print("
")
        print("The index link is: ", repr(use_this_file))
        print("Valid index links:")
        for i in safe_file_dir():
            print('   ', i)
        print("
") return except IOError: pass #Print list of documents html() link = basename if subpath: link = os.path.join(link, subpath) print(""" Lojban Book Discussion %s """ % INSERTSTYLE) file_headers = False dir_headers = False for d in dir_dir(): if not dir_headers: print('

Directories:

') if d and subpath: print('Parent Directory
') dir_headers = True try: #if 1: name = d detail = '' f = open(os.path.join(src, d)+'/.name').read().strip() f = f.split('\n') name = f[0] detail = f[1] except: pass print('%s%s
' % (link, d, name, detail)) if not dir_headers and subpath: print('Parent Directory') for d in file_dir(): if not file_headers: print("

Files:

") file_headers = True print('%s
' % (link, d, d)) print("""

Formatting Comments

Simple tags are allowed: %s. They can't have attributes, because I'm too lazy to write something that allows that. Two newlines are replaced with line breaks.

The system attempts to prevent useless posts.

Other Things

Print source code
Browse data sources (On andgasm, at least)

The comments are stored using shelve. You can open a comments database in python by using shelve.open.

""" % (allowed_tags, basename, src_basename) ) def load_comments(f): if not '.comments' in f: n = os.path.split(f) name = os.path.join(n[0], '.'+n[1]+'.comments') else: if '.db' in f: f = f.replace('.db', '') name = f try: return shelve.open(os.path.join(src, name), writeback=True) except Exception as e: print("Unable to open database for comments! Send your site admin some love, and it will be fixed.") print("Erruh:", e) print("File:", os.path.join(os.path.join(src, name))) raise SystemExit allowed = allowed_tags.split(', ') def replace_urls(text): #http://stackoverflow.com/questions/476478/python-regular-expression-to-add-links-to-urls #I am so lazy. I love it. r1 = r"(\b(http|https)://([-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|]))" r2 = r"((^|\b)www\.([-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_()|]))" return re.sub(r2,r'\1',re.sub(r1,r'\1',text)) def format_comment(v): v = cgi.escape(v) return v #XXXXXXXXXXXX oh god what did I do #urlre = re.compile(r"https?://([-\w\.]+)+(:\d+)?(/([\w/_\.]*(\?\S+)?)?)?") a = [] for i in allowed: a.append("<%s>" % i) a.append("" % i) #a.append("<%s />" % i) #a.append("<%s/>" % i) v = cgi.escape(v) for i in a: v = v.replace(cgi.escape(i), i) v = v.replace('\r', '') v = v.replace('\n\n', '
') v = replace_urls(v) return v def label(_for, lbl): #Because IE sucks immensely. >:( #http://www.adobe.com/cfusion/communityengine/index.cfm?event=showdetails&postId=161&productId=1 #Ain't workin' if not crap_ie: print('') else: print(lbl) def comment_form(f, tag): f = os.path.basename(f) print('
' %(f)) label("x_name"+tag, "Name: ") print('
' % (tag)) print('
') label("x_oner"+tag, 'Write "1"') print(' ' %(tag)) label("x_err"+tag, "This text has a mistake") print('
' % (tag)) label("x_pw"+tag, "Password ") print(' (Optional. Used to edit your comment. Please don\'t use anything important.)
' % (tag)) print('
') print('' % (tag)) print('
') #TODO: Deny the password if it can be used with the given name to login gmail zo'o JS = """ """ #TODO: Open a file, hmm? Or, better yet... CSS = """ """ INSERTSTYLE = CSS + JS def show_file(f): html() line_no = 0 started_coms = False pure_lojban = ... com_db = load_comments(f) for line in open(os.path.join(src, f)): if '' in line: line = line.replace("", '') if (pure_lojban in (True, ...)) and (line.strip().startswith("i ") or line.strip().startswith("ni'o") or line.strip().startswith(".i ")): pure_lojban = True if started_coms: close_paragraph() else: #Begining of a pure-lojban text. It needs formatting. started_coms = True print(""" %s %s """ % (f, INSERTSTYLE)) #Should probably zoi-quote the html or something line_no += 1 tag = str(line_no) if tag in com_db: c = com_db[tag] else: c = [] open_paragraph(basename+'/'+f, tag, c) print(line, '
') elif line.startswith('COM'): if pure_lojban == True: raise Exception("I do not want you to mix lojban with non-lojban!") pure_lojban = False if line.startswith("COMSEP"): if started_coms: tag = line.strip()[6:].strip() close_paragraph() else: tag = line.strip()[3:] if tag == 'END': #Not really correct. close_paragraph() if not tag: tag = str(line_no) line_no += 1 if tag in com_db: c = com_db[tag] else: c = [] open_paragraph(basename+'/'+f, tag, c) started_coms = True elif 'ENDCOM' == line.strip(): if pure_lojban == True: raise Exception("I do not want you to mix lojban with non-lojban!") close_paragraph() elif 'INSERTSTYLE' == line.strip(): if pure_lojban == True: raise Exception("I do not want you to mix lojban with non-lojban!") print(INSERTSTYLE) elif 'INSERTJS' == line.strip(): if pure_lojban == True: raise Exception("I do not want you to mix lojban with non-lojban!") print(JS) elif 'INSERTCSS' == line.strip(): if pure_lojban == True: raise Exception("I do not want you to mix lojban with non-lojban!") print(CSS) else: print(line.replace('\n', '')) if pure_lojban == True: print("
") if pure_lojban == True: #Stop the formatting! Aghh! close_paragraph() print(""" """) def comment_strip(s): #And please don't game the system. :( return s.strip().lower().replace(' ', '').replace('\t', '').replace('\n', '').replace('"', '').replace("'", '').replace('&', '') def save_comment(f, tag, comment, who, err, password=None): noban() #Block banned addresses from commenting. if len(comment) > 1024: message("You can't save a comment that long.", "http://www.gutenberg.org/etext/2600", 4) ip = os.environ["REMOTE_ADDR"] now = time.asctime() db = load_comments(f) if comment_strip(comment) in bad_words: redirect("http://images.google.com/images?q=%s" % cgi.escape(comment)) if not tag in db: val = [] else: val = db[tag] naked_comment = comment_strip(comment) for entry in val: if comment_strip(entry[1]) == naked_comment: html() print("That has already been said here.") if entry[1] == comment and entry[0] == who: print("Silly you!") raise SystemExit #No redirection, except maybe javascript history... val.append([who, comment, now, ip, err, password]) db[tag] = val db.sync() #name, text, date, IP, has_an_error def error_comment(com_list): for com in com_list: if com[4]: return True return False def delete_comment(db, tag, num): comment_list = db[tag] comment_list.pop(num) db[tag] = comment_list db.sync() def edit_comment(db, tag, com_num, value, by_admin): if by_admin: append_text = "\n
Edited %s by admin." % (time.asctime()) else: ip = os.environ["REMOTE_ADDR"] append_text = "\n
Edited %s from %s." % (time.asctime(), ip) value += append_text db[tag][com_num][1] = value db.sync() def modify_comment(f, link): db = load_comments(f) if 'tag' not in form: html() print("What? There is no line selected.") raise SystemExit() tag = cgi.escape(form['tag'].value) #A string comment_list = db[tag] if 'com_num' not in form: html() print("What? There is no comment selected.") raise SystemExit() try: com_num = int(form['com_num'].value) #MUST be an int except ValuError: html() print(".u'iru'e ga'i") raise SystemExit() if 'password' in form: password = form['password'].value real_password = comment_list[com_num][5] time.sleep(2) by_admin = False #Who edited it? if password != real_password: if password not in open(admin_passwords).read().split('\n'): message("The password you entered is invalid.", link, 5) #print("The password you entered is invalid.") #raise SystemExit() else: by_admin = True if "change" in form and not ("del" in form and form['del'].value == 'y'): #Modify the comment. edit_comment(db, tag, com_num, form["change"].value, by_admin) message("Comment changed!", link, 5) #print("Comment changed!") #raise SystemExit() else: delete_comment(db, tag, com_num) message("Dacheated!", link, 3) #print("Dacheated!") #raise SystemExit() else: html() print(""" Delete comment? %s

Edit Comment:

""" % (INSERTSTYLE, link, cgi.escape(comment_list[com_num][1]), tag, int(com_num)) ) def main(): if 'PATH_INFO' in os.environ: e = os.environ['PATH_INFO'].replace('doc', '') #.replace('/', '') if e.startswith('/'): e = e[1:] if '#' in e: e = e[:e.find('#')] if e == '@SOURCE': print("Content-type: text/plain;\n") print(open('doc.cgi').read()) raise SystemExit if e in safe_file_dir(): #We can trust the given path if e.endswith('/'): #List a directory subpath = '' global src e = e.strip('/') src = os.path.join(src, e) subpath = e doc_dir(subpath) else: #Show a file... if 'modify' in form: modify_comment(e, basename+'/'+e) elif 'line' in form: if not 'human' in form: html() print("You have not proven yourself to be human. Put a '1' in that little box.") raise SystemExit elif form['human'].value != '1': html() print("It seems you've forgotten your humanity.") raise SystemExit if not 'say' in form: html() print("What were you going to say?") raise SystemExit else: say = str(form['say'].value) if not 'name' in form: name = 'Anon' else: name = str(form['name'].value) err = False if 'err' in form: if form['err'].value.strip() == '1': err = True password = None if 'password' in form: password = form['password'].value tag = str(form['line'].value) sys.stdout.flush() save_comment(e, tag, say, name, err, password=password) redirect(basename+'/'+str(e)+'#'+str(tag)) else: show_file(e) raise SystemExit elif e.replace('/', '') == '': doc_dir() else: error404() print('Content-type: text/plain;\n') print("That doesn't seem safe.\n") print("Here's a list of all the files and directories that may be accessed:") for i in list(safe_file_dir()): print(' *', i) else: doc_dir() def show_comment_item(section, val): username, text, date, ip, has_err, password, *other = val print("----\n%r (%s) on %s, section %s. Error: %s. Password: %r"%(username, ip, date, section, has_err, password)) print(" >>",text) def comment_dump(f): db = load_comments(f) if not len(db): #print("No items in", f) return else: print("\n\nItems in", f, ': =============================') for key in db: for com in db[key]: show_comment_item(key, com) if __name__ == '__main__': if len(sys.argv) != 1: print("") import glob os.chdir(src) for arg in sys.argv[1:]: arg = os.path.join(src, arg) g = glob.glob(arg) for arg2 in g: comment_dump(arg2) raise SystemExit _e = None if 1: # try: main() # except Exception as e: # _e = e # finally: if use_html and 1: print("If you tried /doc_static/ instead, you could browse faster") # print("Approximate script execution time:", time.time() - start) # print("

Warning: Still developing features and such, don't become too attatched to your comments.

") # print(crap_ie) print() if _e: raise _e