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,"&amp;", "&");
   4.114 +#        $cap=strtr($cap,"&#44;", ",");
   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('&amp;', '&')
   4.127 +        tripcode.replace('&#44;', ',')
   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': ( '&ndash;',
    5.65 +                r'((?<=\s)|^) -- (?=$|\s|%(punctation)s)',),
    5.66 +        'ellip': ( '&hellip;',
    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">&gt;&gt;%(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('&','&amp;')
    5.97 +        html = html.replace('>','&gt;')
    5.98 +        html = html.replace('<','&lt;')
    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 "&gt;"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('&','&amp;'),
   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('&','&amp;'),
   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('&','&amp;'),
   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('&','&amp;'),
   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('&','&amp;'),
  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 +