Image Board
changeset 0:520372852e36 tip
initial import
| author | Radomir Dopieralski <devel@sheep.art.pl> |
|---|---|
| date | Thu Mar 06 16:22:57 2008 +0100 (2008-03-06) |
| parents | |
| children | |
| files | .hgignore attach.py default_config.py message.py parser.py support/config.py support/mesg.cgi support/topic.cgi templates.py topic.py util.py |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/.hgignore Thu Mar 06 16:22:57 2008 +0100 1.3 @@ -0,0 +1,5 @@ 1.4 +syntax: glob 1.5 +.cookies.wget 1.6 +*.pyc 1.7 +*~ 1.8 +.*.swp
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/attach.py Thu Mar 06 16:22:57 2008 +0100 2.3 @@ -0,0 +1,64 @@ 2.4 +import os, stat 2.5 +import imghdr, urllib 2.6 + 2.7 +import util 2.8 +from config import Config 2.9 + 2.10 +class Attachment: 2.11 + def __init__(self, topic, number): 2.12 + self.topic = topic 2.13 + self.number = number 2.14 + self.name = topic + '.%d.' % number 2.15 + self.url = '' 2.16 + self.thumb = '' 2.17 + 2.18 + def save(self, filecontent): 2.19 + img = imghdr.what('', filecontent) 2.20 + if img in Config.filetypes: 2.21 + extension = img 2.22 + else: 2.23 + return 2.24 + self.filename = os.path.join(Config.file_dir, self.name.encode('utf-8')+extension) 2.25 + self.url = '/'.join([Config.file_url, urllib.quote(self.name.encode('utf-8'))+extension]) 2.26 + f = open(self.filename, 'wb') 2.27 + f.write(filecontent) 2.28 + f.close() 2.29 + os.chmod(self.filename, stat.S_IROTH | stat.S_IWUSR | stat.S_IRUSR) 2.30 + util.log('Saved "%s".' % self.filename) 2.31 + if img: 2.32 + self.make_thumb() 2.33 + else: 2.34 + self.thumb = '/'.join([Config.smiley_url, 'file.gif']) 2.35 + 2.36 + def make_thumb(self): 2.37 + try: 2.38 + import Image 2.39 + except: 2.40 + self.thumb = self.url 2.41 + return 2.42 + saved_path = os.path.join(Config.thumb_dir, self.name.encode('utf-8')+'png') 2.43 + self.thumb = '/'.join([Config.thumb_url, urllib.quote(self.name.encode('utf-8'))+'png']) 2.44 + im = Image.open(self.filename) 2.45 + im = im.convert('RGBA') 2.46 + width = int(Config.thumb_size) 2.47 + im.thumbnail((width,width), Image.ANTIALIAS) 2.48 + im.save(saved_path,'PNG') 2.49 + os.chmod(saved_path, stat.S_IROTH | stat.S_IWUSR | stat.S_IRUSR) 2.50 + util.log('Created "%s".' % saved_path) 2.51 + 2.52 + def delete(self): 2.53 + # Rewmove the files 2.54 + for extension in Config.filetypes: 2.55 + filename = os.path.join(Config.file_dir, self.name.encode('utf-8')+extension) 2.56 + try: 2.57 + os.remove(filename) 2.58 + util.log('Removed "%s".' % filename) 2.59 + except: 2.60 + pass 2.61 + # Remove the thumbnail 2.62 + thumb_path = os.path.join(Config.thumb_dir, self.name.encode('utf-8')+'png') 2.63 + try: 2.64 + os.remove(thumb_path) 2.65 + util.log('Removed thumbnail "%s".' % thumb_path) 2.66 + except: 2.67 + util.log('Not removed "%s".' % thumb_path)
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/default_config.py Thu Mar 06 16:22:57 2008 +0100 3.3 @@ -0,0 +1,33 @@ 3.4 + 3.5 + 3.6 +class DefaultConfig: 3.7 + 3.8 +# The URLs 3.9 + html_url = '' 3.10 + smiley_url = 'smiley/' 3.11 + file_url = 'image/' 3.12 + thumb_url = 'thumb/' 3.13 + style_url = 'style/' 3.14 + script_url = './' 3.15 + 3.16 +# Directories 3.17 + html_dir = './' 3.18 + file_dir = 'image/' 3.19 + thumb_dir = 'thumb/' 3.20 + 3.21 +# the main index page 3.22 + index_page = 'index.html' 3.23 + 3.24 +# Maximum size of messages 3.25 + char_limit = 2048 3.26 + line_limit = 80 3.27 + 3.28 +# Initially displayed in the message editor: 3.29 + default_message = '' 3.30 + 3.31 +# Title of the index page 3.32 + index_title = 'Message board' 3.33 + 3.34 + thumb_size = 128 3.35 + 3.36 + filetypes = ('gif', 'png', 'jpeg')
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 4.2 +++ b/message.py Thu Mar 06 16:22:57 2008 +0100 4.3 @@ -0,0 +1,168 @@ 4.4 +import re, codecs, time, crypt, os, urllib 4.5 + 4.6 +import util 4.7 +from config import Config 4.8 +from attach import Attachment 4.9 +from parser import Parser 4.10 +from topic import Topic 4.11 +from templates import Templates 4.12 + 4.13 +class Message: 4.14 + "Handles adding and removing messages" 4.15 + 4.16 + def __init__(self): 4.17 + text = util.formval('text') 4.18 + self.raw = '\n'.join(text[:Config.char_limit].split('\n')[:Config.line_limit]) 4.19 + self.topic = util.formval('topic') 4.20 + if self.topic: 4.21 + self.filename = os.path.join(Config.html_dir, self.topic + '.html') 4.22 + self.text = self.raw 4.23 + self.sig = util.formval('sig')[:32] 4.24 + self.link = util.formval('link')[:512] 4.25 + else: 4.26 + self.topic = None 4.27 + 4.28 + def read(self): 4.29 + "Read the current contents of topic page" 4.30 + mesg_re = re.compile(re.escape(Templates.message_part)) 4.31 + f = codecs.open(self.filename.encode('utf-8'), 'rb', 'utf-8') 4.32 + self.orig = ''.join(f.readlines()) 4.33 + f.close() 4.34 + self.number = len(mesg_re.findall(self.orig))+1 4.35 + 4.36 + def write(self, result): 4.37 + "Write the modified topic page" 4.38 + f = codecs.open(self.filename.encode('utf-8'), 'wb', 'utf-8') 4.39 + f.write(result) 4.40 + f.close() 4.41 + 4.42 + def goto(self): 4.43 + "Display the topic page" 4.44 + try: 4.45 + query = '?posts=%d' % self.number 4.46 + except: 4.47 + query = '' 4.48 + if self.topic: 4.49 + util.redirect( 4.50 + '/'.join([ 4.51 + Config.html_url, 4.52 + urllib.quote( 4.53 + self.topic.encode('utf-8')+'.html' 4.54 + ) + query 4.55 + ]) 4.56 + ) 4.57 + else: 4.58 + util.redirect('/'.join([Config.html_url, Config.index_page])) 4.59 + 4.60 + def makelink(self): 4.61 + "Make the sig link or just sig text" 4.62 + if not self.sig: 4.63 + self.sig = 'Anonymous' 4.64 + if self.link.startswith('http://') or self.link.startswith('https://'): 4.65 + return '<a href="%s">%s</a>' % (self.link, self.sig) 4.66 + elif self.link.find('@')!=-1: 4.67 + return '<a href="mailto:%s">%s</a>' % (self.link.replace('@','%40'), self.sig) 4.68 + else: 4.69 + return self.sig 4.70 + 4.71 + def create(self): 4.72 + "Append new message to the topic" 4.73 + if not self.topic: 4.74 + return 4.75 + date = time.strftime('%F, %X') 4.76 + text = Parser(self.raw).parse() 4.77 + self.read() 4.78 + self.tripsig() 4.79 + attach = '' 4.80 + try: 4.81 + content = util.form.getvalue('file') 4.82 + except: 4.83 + content = None 4.84 + if content: 4.85 + a = Attachment(self.topic, self.number) 4.86 + a.save(content) 4.87 + if a.url: 4.88 + attach = Templates.attachment % {'thumb':a.thumb, 'url':a.url} 4.89 + d = { 4.90 + 'number': '%d'%self.number, 4.91 + 'date': date, 4.92 + 'text': text, 4.93 + 'mark': Templates.marker, 4.94 + 'sign': self.makelink(), 4.95 + 'file': attach, 4.96 + } 4.97 + html = Templates.message % d 4.98 + result = self.orig.replace(Templates.marker, html) 4.99 + self.write(result) 4.100 + t = Topic(self.topic) 4.101 + if self.link=='sage': 4.102 + t.sage(self.number) 4.103 + util.log(('Topic "%s" saged.' % self.topic).encode('utf-8')) 4.104 + else: 4.105 + t.bump(self.number) 4.106 + util.log(('Topic "%s" bumped.' % self.topic).encode('utf-8')) 4.107 + 4.108 + 4.109 + def tripsig(self): 4.110 + "Convert tripcodes in the sig." 4.111 +# if(ereg("(#|!)(.*)",$names,$regs)){ 4.112 +# $cap = $regs[2]; 4.113 +# $cap=strtr($cap,"&", "&"); 4.114 +# $cap=strtr($cap,",", ","); 4.115 +# $name=ereg_replace("(#|!)(.*)","",$name); 4.116 +# //$name=ereg_replace(TRIPKEY,"",$name); //erase tripkeys in name 4.117 +# $salt=substr($cap."H.",1,2); 4.118 +# $salt=ereg_replace("[^\.-z]",".",$salt); 4.119 +# $salt=strtr($salt,":;<=>?@[\\]^_`","ABCDEFGabcdef"); 4.120 +# $name.=TRIPKEY.substr(crypt($cap,$salt),-10).""; 4.121 +# } 4.122 + parts = self.sig.split('!') 4.123 + if len(parts)<2: 4.124 + return 4.125 + tripcode = parts[1] 4.126 + tripcode.replace('&', '&') 4.127 + tripcode.replace(',', ',') 4.128 + salt = (tripcode + 'H..')[1:3] 4.129 + salt = re.sub(r'[^.-z]', '.', salt) 4.130 + salt.replace(':', 'A') 4.131 + salt.replace(';', 'B') 4.132 + salt.replace('<', 'C') 4.133 + salt.replace('=', 'D') 4.134 + salt.replace('>', 'E') 4.135 + salt.replace('?', 'F') 4.136 + salt.replace('@', 'G') 4.137 + salt.replace('[', 'a') 4.138 + salt.replace('\\', 'b') 4.139 + salt.replace(']', 'c') 4.140 + salt.replace('^', 'd') 4.141 + salt.replace('_', 'e') 4.142 + salt.replace('`', 'f') 4.143 + code = str(crypt.crypt(tripcode, salt))[-10:] 4.144 + self.sig = '!'.join([parts[0], code]) 4.145 + 4.146 + def delete(self): 4.147 + "Delete messages or topic." 4.148 + if not self.topic: 4.149 + return 4.150 + if self.link.strip()!=Config.password.strip(): 4.151 + return 4.152 + if self.raw.strip() == 'topic': 4.153 + self.read() 4.154 + for number in range(1, self.number): 4.155 + a = Attachment(self.topic, number) 4.156 + a.delete() 4.157 + t = Topic(self.topic) 4.158 + t.remove() 4.159 + self.topic = '' 4.160 + else: 4.161 + number = int(self.raw) 4.162 + a = Attachment(self.topic, number) 4.163 + a.delete() 4.164 + d = { 4.165 + 'number': '%d' % number, 4.166 + } 4.167 + message_re = re.compile(Templates.message_regex % d, re.X|re.I|re.U|re.M|re.S) 4.168 + html = Templates.deleted % d 4.169 + self.read() 4.170 + result = message_re.sub(html, self.orig, 1) 4.171 + self.write(result)
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 5.2 +++ b/parser.py Thu Mar 06 16:22:57 2008 +0100 5.3 @@ -0,0 +1,168 @@ 5.4 +import re 5.5 +from config import Config 5.6 + 5.7 +class Parser: 5.8 + smile_tab = { 5.9 + ':)': 'smile.gif', 5.10 + '^_^': 'smile.gif', 5.11 + 5.12 + ';)': 'wink.gif', 5.13 + '-_^': 'wink.gif', 5.14 + 5.15 + ':o': 'shock.gif', 5.16 + '8)': 'shock.gif', 5.17 + 'o_O': 'shock.gif', 5.18 + 'O_o': 'shock.gif', 5.19 + 5.20 + ':3': 'kitty.gif', 5.21 + ':*': 'kitty.gif', 5.22 + '^w^': 'kitty.gif', 5.23 + '^*^': 'kitty.gif', 5.24 + 5.25 + ':P': 'tongue.gif', 5.26 + ';P': 'tongue.gif', 5.27 + ':b': 'tongue.gif', 5.28 + 5.29 + ':D': 'grin.gif', 5.30 + ':]': 'grin.gif', 5.31 + '^__^': 'grin.gif', 5.32 + '^v^': 'grin.gif', 5.33 + 5.34 + ':(': 'frown.gif', 5.35 + "-_-": 'frown.gif', 5.36 + 5.37 + 'X)': 'x.gif', 5.38 + '>_<': 'x.gif', 5.39 + 5.40 + '>:(': 'angry.gif', 5.41 + '>:>': 'angry.gif', 5.42 + ']:>': 'angry.gif', 5.43 + '>)': 'angry.gif', 5.44 + "`_'": 'angry.gif', 5.45 + "-_-\#": 'angry.gif', 5.46 + 5.47 + '-_-;': 'embarass.gif', 5.48 + '_^_': 'embarass.gif', 5.49 + '(:': 'embarass.gif', 5.50 + 5.51 + ':^(': 'waah.gif', 5.52 + 'T_T': 'waah.gif', 5.53 + 'Q_Q': 'waah.gif', 5.54 + '@_@': 'waah.gif', 5.55 + } 5.56 + 5.57 + punctation = r'[!,;.?)\]}=/\\@#$%^\'*+-]' 5.58 + 5.59 + # From http://www.foad.org/~abigail/Perl/url3.regex 5.60 + url_regex = r'( http:\/\/ | https:\/\/ | ftp:\/\/ ) [\w.-]+ ((/[^\s]+|/)*([,.](?!\s|$)|[^,.\s]))? ' 5.61 + 5.62 + 5.63 + repl_tab = { 5.64 + 'dash': ( '–', 5.65 + r'((?<=\s)|^) -- (?=$|\s|%(punctation)s)',), 5.66 + 'ellip': ( '…', 5.67 + r'\.\.\.',), 5.68 + 'code': ( '<code>%(code_text)s</code>', 5.69 + r'(?P<code_head> `+ ) (?P<code_text> .+? ) (?P=code_head)',), 5.70 + 'pre': ( '</p><pre>%(pre_text)s</pre><p>', 5.71 + r'^(?P<pre_head> `+ )\s*$ (?P<pre_text> (.|\n)+? ) (?P=pre_head)\s*$',), 5.72 + 'em': ( '<em>%(em_text)s</em>', 5.73 + r'((?<=\s)|^) \* (?P<em_text> [^*\s][^*]*? ) \* (?=$|\s|%(punctation)s)',), 5.74 + 'quote': ( '</p>%(quote_special)s<p>', 5.75 + r'^ (?P<quote_head> [>]+ ) (?P<quote_text> [^\d].* ) $',), 5.76 + 'list': ( '</p>%(list_special)s<p>', 5.77 + r'^ (?P<list_head> [*]+ ) \s (?P<list_text> .* ) $\n?',), 5.78 + 'br': ( '<br />', 5.79 + r'\n',), 5.80 + 'ref': ( '<a href="#msg%(ref_text)s">>>%(ref_text)s</a>', 5.81 + r'((?<=\s)|^) [>][>] (?P<ref_text> \d+ )',), 5.82 + 'link': ( '<a href="%(link_text)s" class="ext" rel="nofollow">%(link_text)s</a>', 5.83 + r'(?P<link_text> %(url_regex)s )',), 5.84 + 'smile': ( '<img src="%(smile_img)s" alt="%(smile)s" width="22" height="18" />', 5.85 + r'(^|(?<=\s)) (%(smileys)s) (?=$|\s|%(punctation)s)',), 5.86 + 'par': ( '</p><p>', 5.87 + r'\n(\s*\n)+' ), 5.88 + 'chr': ( '%(chr)s', 5.89 + r'.'), 5.90 + } 5.91 + repl_order = ['ref', 'quote', 'pre', 'link', 'em', 'code', 'smile', 'dash', 'ellip', 'par', 'br', 'chr'] 5.92 + 5.93 + def escape(self, text): 5.94 + if not text: 5.95 + return text 5.96 + html = text.replace('&','&') 5.97 + html = html.replace('>','>') 5.98 + html = html.replace('<','<') 5.99 + return html 5.100 + 5.101 + def __init__(self, raw): 5.102 + self.raw = raw 5.103 + self.text = raw 5.104 + d = { 5.105 + 'smileys': r'|'.join([re.escape(s) for s in self.smile_tab.keys()]), 5.106 + 'punctation': self.punctation, 5.107 + 'url_regex': self.url_regex, 5.108 + } 5.109 + self.repl_re = re.compile(r'|'.join([ 5.110 + r'(?P<%s>%s)' % (k, (self.repl_tab[k][1] % d)) 5.111 + for k in self.repl_order 5.112 + ]), re.X|re.U|re.M) 5.113 + self.clear_re = re.compile('|'.join([ 5.114 + r'<br\s*/> (?= </p> )', 5.115 + r'<p> (\s|\n| <br\s*/> )* <\/p>', 5.116 + r'</li>(?=<ul>)', 5.117 + r'(?=<</li>)</p>(?=<ul>)', 5.118 + r'(?=<</li>)<p>(?=</ul>|<li>)', 5.119 + r'(?=<</ul>)</li>(?=<p>)(?!<li>|</ul>|</p><ul>)', 5.120 + r'</ul> (\s|\n| <br\s*/> )* <ul>', 5.121 + r'</blockquote> (\s|\n| <br\s*/> )* <blockquote>', 5.122 + ]), re.X|re.U|re.M) 5.123 + 5.124 + def repl(self, match): 5.125 + g = match.groupdict() 5.126 + # Special case with smile 5.127 + if g.get('smile'): 5.128 + smile = g.get('smile', '') 5.129 + g['smile_img'] = '/'.join([Config.smiley_url, self.smile_tab.get(smile, '')]) 5.130 + # Escape all the groups 5.131 + escaped = {} 5.132 + for k,v in g.iteritems(): 5.133 + escaped[k] = self.escape(v) 5.134 + g = escaped 5.135 + # Special case with quote 5.136 + if g.get('quote'): 5.137 + # Note that we divide by 4, because we are counting ">"s here. 5.138 + level = len(g.get('quote_head').strip())/4 or 1 5.139 + g['quote_special'] = ''.join([ 5.140 + u'<blockquote>' * level, 5.141 + u'<p>', 5.142 + g.get('quote_text', '').strip(), 5.143 + u'</p>', 5.144 + u'</blockquote>' * level, 5.145 + ]) 5.146 + # Special case with list 5.147 + if g.get('list'): 5.148 + level = len(g.get('list_head')) or 1 5.149 + g['list_special'] = ''.join([ 5.150 + u'<ul>' * level, 5.151 + u'<li>', 5.152 + g.get('list_text', '').strip(), 5.153 + u'</li>', 5.154 + u'</ul>' * level, 5.155 + u'</li>', 5.156 + ]) 5.157 + for k in self.repl_tab.keys(): 5.158 + if g.get(k, None): 5.159 + return self.repl_tab[k][0] % g 5.160 + return '?%s?' % self.escape(repr(g)) 5.161 + 5.162 + def parse(self): 5.163 + # Do the parsing 5.164 + newtext = u'<p>%s</p>' % self.repl_re.sub(self.repl, self.raw) 5.165 + # Remove unneeded tags 5.166 + oldtext = '' 5.167 + while oldtext != newtext: 5.168 + oldtext = newtext 5.169 + newtext = self.clear_re.sub('', oldtext) 5.170 + self.text = newtext 5.171 + return self.text
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 6.2 +++ b/support/config.py Thu Mar 06 16:22:57 2008 +0100 6.3 @@ -0,0 +1,27 @@ 6.4 +import sys, os 6.5 + 6.6 +src = '/path/to/all/the/board/code' 6.7 +sys.path.insert(0, src) 6.8 +from default_config import DefaultConfig 6.9 + 6.10 +class Config(DefaultConfig): 6.11 + src_dir = src 6.12 + password = 'your password' 6.13 + title = "board's title" 6.14 + 6.15 + name = 'support' 6.16 + base_dir = '/dir/to/your/public_html' 6.17 + base_url = 'http://yourhost/~youruser' 6.18 + 6.19 + html_dir = os.path.join(base_dir, name) 6.20 + file_dir = os.path.join(html_dir, 'image') 6.21 + thumb_dir = os.path.join(html_dir, 'thumb') 6.22 + 6.23 + html_url = '/'.join([base_url, name]) 6.24 + smiley_url = '/'.join([html_url, 'smiley']) 6.25 + file_url = '/'.join([html_url, 'image']) 6.26 + thumb_url = '/'.join([html_url, 'thumb']) 6.27 + style_url = '/'.join([html_url, 'style']) 6.28 + script_url = '/'.join([base_url, 'cgi-bin', name]) 6.29 + 6.30 + index_title = title
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 7.2 +++ b/support/mesg.cgi Thu Mar 06 16:22:57 2008 +0100 7.3 @@ -0,0 +1,21 @@ 7.4 +#!/usr/bin/env python 7.5 +import cgitb; cgitb.enable() 7.6 +import sys 7.7 + 7.8 +# Path to your MB config 7.9 +sys.path.insert(0, '/path/to/config/file') 7.10 + 7.11 + 7.12 +from config import Config 7.13 +sys.path.insert(0, Config.src_dir) 7.14 + 7.15 +from message import Message 7.16 +import urllib 7.17 + 7.18 +m = Message() 7.19 +if m.topic: 7.20 + if m.sig=='delete': 7.21 + m.delete() 7.22 + else: 7.23 + m.create() 7.24 +m.goto()
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 8.2 +++ b/support/topic.cgi Thu Mar 06 16:22:57 2008 +0100 8.3 @@ -0,0 +1,17 @@ 8.4 +#!/usr/bin/env python 8.5 +import cgitb; cgitb.enable() 8.6 +import sys 8.7 + 8.8 +# Path to your MB config 8.9 +sys.path.insert(0, '/path/to/config/file') 8.10 + 8.11 +from config import Config 8.12 +sys.path.insert(0, Config.src_dir) 8.13 + 8.14 +from topic import Topic 8.15 + 8.16 +t = Topic() 8.17 +if t.topic and not t.exists(): 8.18 + t.reset() 8.19 + t.insert() 8.20 +t.goto()
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 9.2 +++ b/templates.py Thu Mar 06 16:22:57 2008 +0100 9.3 @@ -0,0 +1,122 @@ 9.4 +#!/usr/bin/env python 9.5 + 9.6 +class Templates: 9.7 + """ 9.8 + Here are templates for generating the HTML code. 9.9 + Edit them to your liking. 9.10 + """ 9.11 + 9.12 +# For the topic list entry in the index.html 9.13 +# mark - the marker (new topics are added here) 9.14 +# url - the url of file where the topic is stored 9.15 +# topic - the title of the topic 9.16 +# posts - the number of posts on the topic 9.17 + item = u'''%(mark)s<li><a href="%(url)s?posts=%(posts)s">%(topic)s\n</a></li>''' 9.18 + 9.19 + 9.20 +# For the whole message text, used to delete messages. 9.21 +# number - the number of this message 9.22 + message_regex = r'[<]div\s*class=["]message["]\s*id=["]msg%(number)s["][>](.|\n)+?\s*[<]/div[>]\s*[<]/div[>]' 9.23 + deleted = '''<div class="message" id="msg%(number)s"> 9.24 + <div class="deleted"> 9.25 + <span class="number">%(number)s.</span> 9.26 + <span class="deleted">Message deleted.</span> 9.27 + </div> 9.28 +</div>''' 9.29 + 9.30 +# For the message text in topic file 9.31 +# number - the number of this message 9.32 +# sign - the user's signature, with optional link 9.33 +# date - the posting date 9.34 +# text - the html-formatted message text 9.35 +# mark - the marker (new messages are added here) 9.36 +# file - the markup for attached file, if any 9.37 + message = u'''<div class="message" id="msg%(number)s"> 9.38 + <div class="bar"> 9.39 + <span class="number">%(number)s.</span> 9.40 + <span class="sign">%(sign)s</span> 9.41 + <span class="date">%(date)s</span> 9.42 + </div> 9.43 + %(file)s 9.44 + <div class="text">%(text)s</div> 9.45 +</div> 9.46 +%(mark)s''' 9.47 + 9.48 +# Characteristic part of the message template 9.49 +# For counting the messages 9.50 + message_part = u'''<div class="message"''' 9.51 + 9.52 +# Characteristic part of the item template 9.53 + item_part = r'''<li><a href="[^"]+">%(topic)s\n</a></li>''' 9.54 + 9.55 +# For the index.html 9.56 +# title - the board's title 9.57 +# mark - the marker (topics are added here) 9.58 +# button - the "create topic" button text 9.59 +# script - the script to call to create topic 9.60 + index = u'''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 9.61 + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 9.62 +<html> 9.63 + <head> 9.64 + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> 9.65 + <title>%(title)s</title> 9.66 + <link rel="stylesheet" href="%(style)s/screen.css" media="screen" /> 9.67 + <link rel="stylesheet" href="%(style)s/print.css" media="print" /> 9.68 + </head> 9.69 + <body> 9.70 + <h1>%(title)s</h1> 9.71 + <ul class="topics"> 9.72 + %(mark)s 9.73 + </ul> 9.74 + <form action="%(script)s" method="post" id="topicform"> 9.75 + <div> 9.76 + <input name="topic" class="field" /> 9.77 + <input type="submit" value="%(button)s" class="button" /> 9.78 + </div> 9.79 + </form> 9.80 + </body> 9.81 +</html>''' 9.82 + 9.83 +# The marker inserted in various htmls 9.84 + marker = u'''<div id="here"></div>''' 9.85 + 9.86 + attachment = u'''<a href="%(url)s" class="file"><img src="%(thumb)s" alt="thumb" /></a>''' 9.87 + 9.88 +# For the topic html file 9.89 +# topic - the topic's title 9.90 +# bottom - the "jump to bottom" link text 9.91 +# mark - the marker (messages are added here) 9.92 +# default - the default message text 9.93 +# button - the "Post message" button text 9.94 +# script - the script called to save messages 9.95 +# index - the url of index page 9.96 + topic = u'''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 9.97 + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 9.98 +<html> 9.99 + <head> 9.100 + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> 9.101 + <title>%(topic)s</title> 9.102 + <link rel="stylesheet" href="%(style)s/screen.css" media="screen" /> 9.103 + <link rel="stylesheet" href="%(style)s/print.css" media="print" /> 9.104 + </head> 9.105 + <body> 9.106 + <h1>%(topic)s</h1> 9.107 + <p id="top"><a href="#bottom">%(bottom)s</a></p> 9.108 + <div class="topic"> 9.109 + %(mark)s 9.110 + </div> 9.111 + <p id="bottom"><a href="./">%(index)s</a></p> 9.112 + <form action="%(script)s" method="post" id="messageform" enctype="multipart/form-data"> 9.113 + <div> 9.114 + <input type="hidden" name="topic" value="%(topic)s" /> 9.115 + <textarea id="messagetext" name="text" rows="8" cols="40">%(default)s</textarea> 9.116 + <div id="formcontrols"> 9.117 + <label id="messagesig">Sig: <input name="sig" value="Anonymous" /></label> 9.118 + <label id="messagelink">Link: <input name="link" /></label> 9.119 + <input id="messagepost" type="submit" class="button" value="%(button)s" /> 9.120 + <label id="messagefile">Image: <input type="file" name="file" /></label> 9.121 + </div> 9.122 + </div> 9.123 + </form> 9.124 + </body> 9.125 +</html>'''
10.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 10.2 +++ b/topic.py Thu Mar 06 16:22:57 2008 +0100 10.3 @@ -0,0 +1,118 @@ 10.4 +#!/usr/bin/env python 10.5 + 10.6 +import re, urllib, os, stat, codecs 10.7 + 10.8 +import util 10.9 +from config import Config 10.10 +from templates import Templates 10.11 + 10.12 +class Topic: 10.13 + def __init__(self, topic=None): 10.14 + if not topic: 10.15 + self.topic = util.formval('topic')[:256] 10.16 + else: 10.17 + self.topic=topic 10.18 + topic_re = re.compile(r'''^[^\.\/~\s<>\"\'\`](\w|[ !,:;\.\?\&\#\$\+\(\)'-])+$''', re.U | re.I) 10.19 + if not topic_re.match(self.topic) or self.topic+'.html'==Config.index_page: 10.20 + self.topic = None 10.21 + if self.topic: 10.22 + name = self.topic + '.html' 10.23 + self.filename = os.path.join(Config.html_dir, name.encode('utf-8')) 10.24 + self.url = '/'.join([Config.html_url, urllib.quote(name.encode('utf-8'))]) 10.25 + self.ifile = os.path.join(Config.html_dir, Config.index_page) 10.26 + 10.27 + def exists(self): 10.28 + return self.topic and os.access(self.filename, os.R_OK) 10.29 + 10.30 + def goto(self): 10.31 + if self.topic: 10.32 + util.redirect(self.url) 10.33 + else: 10.34 + util.redirect('/'.join([Config.html_url, Config.index_page])) 10.35 + 10.36 + def item(self, posts=0): 10.37 + d = { 10.38 + 'mark': '', # The mark is not used 10.39 + 'url': self.url, 10.40 + 'topic': self.topic.replace('&','&'), 10.41 + 'posts': posts, 10.42 + } 10.43 + return Templates.item % d 10.44 + 10.45 + def insert(self): 10.46 + if not self.topic: 10.47 + return 10.48 + html = self.item() 10.49 + f = codecs.open(self.ifile, 'rwb', 'utf-8') 10.50 + orig = ''.join(f.readlines()) 10.51 + f.close() 10.52 + result = orig.replace(Templates.marker, Templates.marker + html) 10.53 + f = codecs.open(self.ifile, 'wb', 'utf-8') 10.54 + f.write(result) 10.55 + f.close() 10.56 + 10.57 + def bump(self, posts=0): 10.58 + if not self.topic: 10.59 + return 10.60 + html = self.item(posts) 10.61 + f = codecs.open(self.ifile, 'rwb', 'utf-8') 10.62 + orig = ''.join(f.readlines()) 10.63 + f.close() 10.64 + d= { 10.65 + 'topic': self.topic.replace('&','&'), 10.66 + } 10.67 + topic_re = re.compile(Templates.item_part % d) 10.68 + removed = topic_re.sub('', orig) 10.69 + result = removed.replace(Templates.marker, Templates.marker + html) 10.70 + f = codecs.open(self.ifile, 'wb', 'utf-8') 10.71 + f.write(result) 10.72 + f.close() 10.73 + 10.74 + def sage(self, posts=0): 10.75 + if not self.topic: 10.76 + return 10.77 + html = self.item(posts) 10.78 + f = codecs.open(self.ifile, 'rwb', 'utf-8') 10.79 + orig = ''.join(f.readlines()) 10.80 + f.close() 10.81 + d= { 10.82 + 'topic': self.topic.replace('&','&'), 10.83 + } 10.84 + topic_re = re.compile(Templates.item_part % d) 10.85 + result = topic_re.sub(html, orig) 10.86 + f = codecs.open(self.ifile, 'wb', 'utf-8') 10.87 + f.write(result) 10.88 + f.close() 10.89 + 10.90 + def reset(self): 10.91 + if not self.topic: 10.92 + return 10.93 + f = codecs.open(self.filename, 'wb','utf-8') 10.94 + d = { 10.95 + 'topic': self.topic.replace('&','&'), 10.96 + 'mark': Templates.marker, 10.97 + 'bottom': u'jump to last message', 10.98 + 'default': Config.default_message, 10.99 + 'button': 'post message', 10.100 + 'script': Config.script_url + '/mesg.cgi', 10.101 + 'index': 'topic index', 10.102 + 'style': Config.style_url, 10.103 + } 10.104 + html = Templates.topic %d 10.105 + f.write(html) 10.106 + os.chmod(self.filename, stat.S_IROTH | stat.S_IWUSR | stat.S_IRUSR) 10.107 + 10.108 + def remove(self): 10.109 + f = codecs.open(self.ifile, 'rwb', 'utf-8') 10.110 + orig = ''.join(f.readlines()) 10.111 + f.close() 10.112 + d= { 10.113 + 'topic': self.topic.replace('&','&'), 10.114 + } 10.115 + topic_re = re.compile(Templates.item_part % d) 10.116 + result = topic_re.sub('', orig) 10.117 + f = codecs.open(self.ifile, 'wb', 'utf-8') 10.118 + f.write(result) 10.119 + f.close() 10.120 + os.remove(self.filename) 10.121 +
11.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 11.2 +++ b/util.py Thu Mar 06 16:22:57 2008 +0100 11.3 @@ -0,0 +1,21 @@ 11.4 + 11.5 +import urllib, cgi, time, codecs 11.6 + 11.7 +def redirect(url): 11.8 + print 'Status: 302 Found\nLocation: %s\n\n' % url 11.9 + 11.10 +def formval(name): 11.11 + try: 11.12 + return unicode(form.getfirst(name), 'utf-8') 11.13 + except: 11.14 + return '' 11.15 + 11.16 +def log(text): 11.17 + 11.18 + logfile.write(time.strftime('%x %x ') + text + '\n') 11.19 + logfile.flush() 11.20 + 11.21 + 11.22 +form = cgi.FieldStorage() 11.23 +logfile = open('board.log', 'ab' ) 11.24 +
