#!/usr/bin/python """ noets - A weblog-style site CGI. License: GPL 2; share and enjoy! Author: Sean B. Palmer, inamidst.com Modifications: Christopher Schmidt Date: 2003-10ish About: This script originally powered the noets website, which was written to from IRC on various different channels by any of its participants. In that respect, it was like Edd Dumbill's chump bot, but the input was oriented towards prose rather than line-by-line input, and so the output was more like a conventional weblog. If you want to use this yourself, you'll have to change all of the hardcoded site names, paths, &c. Modifications: RSS 1.1 output and /about support was added by Christopher Schmidt, 01-2005. Modified posting to require a % before each line, for more concentrated effort required to post, with the benefit that you don't accidentally post anything. Added additional metadata display to /archives view. """ import sys, os, re, cgi, time from cStringIO import StringIO base = '/noets' bloguri = 'http://crschmidt.net/noets/' blogname = 'nœts' password = 'password here' navigation = '\n' address = '
\n' address += 'Christopher Schmidt, \n' address += 'and other various and sundry characters.\n' address += '
\n' address += '
All code which runs this thing was stolen from sbp. Based on the original nœts site, circa 2003. Design by d8uv, xover, with help from the works of Eric Meyer. Any other people who were involved are also credited and thanked here, in this very small space: . Thank you.
' def quoteHTML(s): s = s.replace('&', '&') s = s.replace('<', '<') return s r_wlink = re.compile(r'(?]+)>(?"]+$') def wikiLinkify(s): def htmlify(m): title, uri = m.group(1), m.group(2) return '%s' % (uri, title) s = r_wlink.sub(htmlify, s) return s def unicodeify(s): i = int(s, 16) if i in (0x9, 0xA, 0xD) + tuple(xrange(0x20, 0x7E)): return chr(i) elif i > 0x10FFFF: raise "UnicodeError", "Codepoint exceeds U+10FFFF" return '&#x%s;' % s def wikiFormat(s): # @@ reserve \[A-Za-z]+{...} for future extensions # @@ use a proper parser, or catch the matches using a function result = '' pos = 0 while pos < len(s): m = r_wlink.match(s[pos:]) if m: span = m.span() result += wikiLinkify(s[pos:pos+span[1]]) pos += len(m.group(0)) else: m = re.compile(r'[A-Za-z0-9]--[A-Za-z0-9]').match(s[pos:]) if m: result += s[pos] + '—' + s[pos+3] pos += 4 else: m = r_uniquot.match(s[pos:]) if m: result += unicodeify(m.group(1)) pos += len(m.group(0)) elif s[pos:pos+4] == '\n..\n': result += '

\n\n

' pos += 4 elif s[pos] == '& ': result += '& ' pos += 1 elif s[pos] == '<': result += '<' pos += 1 else: result += s[pos] pos += 1 return result def html(title, body): s = '\n' s += '\n' s += '%s\n' % title s += '\n'%base s += '\n' s += '\n' s += body s += '\n' s += '' return s def getEntryIDs(): # Each entry's content will be stored as (digit).html # So we can use that as a kind of entry-index filenames = filter(lambda s: (s.endswith('.html') and s[:-len('.html')].isdigit), os.listdir('.')) filenames = [int(fn[:-len('.html')]) for fn in filenames] filenames.sort() return filenames def postEntry(): form = sys.stdin.read() form = cgi.parse_qs(form) form = dict([(item[0], ''.join(item[1])) for item in form.items()]) if form.get('pass') == password: # Compute the next available entry ID filenames = getEntryIDs() if filenames: nextid = filenames[-1] + 1 else: nextid = 1 # Metadata t = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) tags = "" try: tags = form.get(tags) except: pass meta = open('./%s.txt' % nextid, 'w') print >> meta, 'title:', form.get('title') print >> meta, 'datetime:', t print >> meta, 'nick:', form.get('nick') print >> meta, 'channel:', form.get('channel') if tags: print >> meta, 'tags:', tags meta.close() # Data content = str(form.get('content')) data = open('./%s.html' % nextid, 'w') print >> data, '

%s

' % wikiFormat(content) data.close() # Return the ID as a string return str(nextid) r_meta = re.compile('(?s)([^\n:]+): (.*?)(?=\n[^ \t]|\Z)') def getEntry(i): meta = dict(r_meta.findall(open('./%s.txt' % i, 'r').read())) data = open('./%s.html' % i, 'r').read() title = quoteHTML(meta.get('title')) datetime = meta.get('datetime') datetime = datetime.replace('T', ' ').replace('Z', ' UTC') nick = quoteHTML(meta.get('nick')) channel = quoteHTML(meta.get('channel')) tags = meta.get('tags') or '' entry = '
\n' entry += '

%s

\n' % title entry += data + '\n' entry += '
* Posted by %s on %s at \n\n' elif inSeq: break result += address return html('%s - archives for %s' % (blogname, month), result) def buildRSS(n=None): if n is None: n = 10 rss = '\n""" % bloguri rss += '%s\n' % blogname rss += '%s\n' % bloguri rss += 'by crschmidt and friends\n' rss += 'en\n' rss += 'Sean B. Palmer\n' t = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) rss += '%s\n' % t filenames = getEntryIDs()[-n:] # get the last ten or so entries filenames.reverse() rss += '\n' for filename in filenames: meta = dict(r_meta.findall(open('./%s.txt' % filename, 'r').read())) data = open('./%s.html' % filename, 'r').read() ordata = data data = data.replace(']]>', ']]>]]>\n' % (bloguri, filename) rss += '\t%s\n' % quoteHTML(meta.get('title')) rss += '\t%s%s\n' % (bloguri, filename) rss += '\t\n' % data rss += '\t%s\n' % meta.get('nick') rss += '\t%s' % meta.get('datetime') rss += '\t\n' % data rss += '\t%s\n' % ordata rss += '\n' rss += '\n' rss += '\n' return rss def buildRSS10(n=None): if n is None: n = 10 rss = '', ']]>]]>\n' % (bloguri, filename) rss += '\t%s\n' % quoteHTML(meta.get('title')) rss += '\t%s%s\n' % (bloguri, filename) rss += '\t\n' % data rss += '\t%s\n' % meta.get('nick') rss += '\t%s' % meta.get('datetime') rss += '\t\n' % data rss += '\n' rss += '\n' return rss def buildAbout(): result = navigation result += '

%s \n' % blogname result += '(by crschmidt & friends)

\n' result += """

Just notes. Really.

This is a place to collect my thoughts before moving them to other places. It's also a dumping ground for people in a variety of other channels. All content is posted via an IRC interface, in several different channels.

""" result += address return html("%s - the notes of various channels" % blogname, result) def buildIndex(n=None): if n is None: n = 10 result = navigation result += '

%s \n' % blogname result += '(by crschmidt & friends)

\n' filenames = getEntryIDs()[-n:] # get the last ten or so entries if filenames: filenames.reverse() for fn in filenames: title, entry = getEntry(fn) result += '
\n' + entry + '
\n\n' else: result += '

No entries yet.

\n' result += address return html("%s - the notes of various channels" % blogname, result) def main(env=None): if env is None: env = os.environ method = env.get('REQUEST_METHOD') if method not in ('GET', 'POST'): raise "UnsupportedMethodError", "Unsupported method: %s" % method uri = env.get('REQUEST_URI') assert uri.startswith(base) # @@ path = uri[len(base):] # Log the request t = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) f = open('/var/www/crschmidt.net/htdocs/noets/noets-%s.log' % t[:7], 'a') addr = env.get('REMOTE_ADDR') referer = env.get('HTTP_REFERER', '[direct]').replace(' ', '') print >> f, t, method, path, addr, referer f.close() if True: if (method == 'GET') and (path == '/0'): print "Content-Type: text/plain" print print re.sub("(?m)^password = '.+'$", "password = 'password here'", open('index.cgi').read()) return if (method == 'GET') and (path == '/rss'): print "Content-Type: application/rss+xml" print print buildRSS() return if (method == 'GET') and (path == '/rss10'): print "Content-Type: text/xml" print print buildRSS10() return print "Content-Type: text/html; charset=utf-8" print if (method == 'POST') and (path == '/'): print postEntry() elif (method == 'GET') and (path == '/'): print buildIndex() elif (method == 'GET') and (path == '/about'): print buildAbout() elif (method == 'GET') and (path == '/archives'): print buildArchives('') elif (method == 'GET') and ('/author' in path): try: print buildArchives(re.search("/author/(.*)", path).group(1)) except: print buildArchives('') elif (method == 'GET') and ('-' in path): print buildMonthArchive(path[1:]) elif method == 'GET': try: i = int(path[1:]) print buildEntry(i) except: print buildIndex() if __name__=="__main__": try: main() except: cgi.print_exception()