#!/usr/bin/python # -*- coding: utf-8 -*- # This program is free software. It comes without any warranty, to # the extent permitted by applicable law. You can redistribute it # and/or modify it under the terms of the Do What The Fuck You Want # To Public License, Version 2, as published by Sam Hocevar. See # http://sam.zoy.org/wtfpl/COPYING for more details. # Authors: Stefan Ritter <xeno@thehappy.de> # Adrian Vondendriesch <disco-stu@disco-stu.de> # Pascal Turbing <pascal@turbing.de> # Description: A simple blogging software # TODO: # * Complete Atom Feed (like RSS) # * Fix broken charset in outgoing mails (needs some testing) import cgi, os, time, glob, re, md5, sys, random, smtplib import ConfigParser # A wonderful place for doing some regexp ;) no_break = re.compile('^\s*(<ul|</ul>|<li|</li>|<ol|</ol>|<table|</table>|<tr|</tr>|<td|</td>|<th|</th>|<p|</p>).*$') line_start_hyphen = re.compile('^-.*$') line_start_plus = re.compile('^\+.*$') def generate_uuid(string): string_md5sum = md5.new(string).hexdigest() string = str.join('-', (string_md5sum[0:8], string_md5sum[8:12], string_md5sum[12:16], string_md5sum[16:20], string_md5sum[20:32])) return string def errorpage(string): print 'Content-type: text/html\n' print '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"' print ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' print '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">' print '<head>' print ' <title>Error!</title>' print '</head>' print '<body>' print ' ' + string print '</body>' print '</html>' sys.exit() def document_header(string): if string == "xhtml-transitional": print 'Content-type: text/html\n' print '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"' print ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' print '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">' if string == "xhtml-strict": print 'Content-type: text/html\n' print '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"' print ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' print '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">' if string == "atom": print 'Content-type: application/atom+xml\n' print '<?xml version="1.0" encoding="utf-8"?>' print '<feed xmlns="http://www.w3.org/2005/Atom">' if string == "rss": print 'Content-type: application/rss+xml\n' print '<?xml version="1.0" encoding="utf-8"?>' print '<rss version="2.0">' configuration = ConfigParser.ConfigParser() # Look for a configuration: if os.path.exists('../blogthonrc'): configuration.read('../blogthonrc') elif os.path.exists('../.blogthonrc'): configuration.read('../.blogthonrc') elif os.path.exists('configuration'): configuration.read('configuration') else: errorpage('No suitable configuration found!') sys.exit() try: blog_title = configuration.get('personal', 'blog_title') except: errorpage('"blog_title" is missing in configuration!') try: blog_subtitle = configuration.get('personal', 'blog_subtitle') except: errorpage('"blog_subtitle" is missing in configuration!') try: blog_url = configuration.get('personal', 'blog_url') except: errorpage('"blog_url" is missing in configuration!') try: keywords = configuration.get('personal', 'keywords') except: errorpage('"keywords" is missing in configuration!') try: entries_dir = configuration.get('personal', 'entries_dir') except: errorpage('"entries_dir" is missing in configuration!') if not os.path.exists(entries_dir): errorpage('"entries_dir" does not exist!') try: entries_suffix = configuration.get('personal', 'entries_suffix') except: errorpage('"entries_suffix" is missing in configuration!') try: staticpages_dir = configuration.get('personal', 'staticpages_dir') except: errorpage('"staticpages_dir" is missing in configuration!') if not os.path.exists(staticpages_dir): errorpage('"staticpages_dir" does not exist!') try: plugins_dir = configuration.get('personal', 'plugins_dir') except: errorpage('"plugins_dir" is missing in configuration!') if not os.path.exists(plugins_dir): errorpage('"plugins_dir" does not exist!') try: style = configuration.get('look', 'style') except: errorpage('"style" is missing in configuration!') try: entries_per_page = configuration.getint('look', 'entries_per_page') except: errorpage('"entries_per_page" is missing in configuration!') try: monthlist = configuration.get('look', 'monthlist') except: errorpage('"monthlist" is missing in configuration!') try: staticpages = configuration.get('look', 'staticpages') except: errorpage('"staticpages" is missing in configuration!') try: linklist = configuration.get('look', 'linklist') except: errorpage('"linklist" is missing in configuration!') if not os.path.exists("linklist"): errorpage('"linklist" does not exist!') try: permalinks = configuration.get('look', 'permalinks') except: errorpage('"permalinks" is missing in configuration!') try: comments = configuration.get('look', 'comments') except: errorpage('"comments" is missing in configuration!') try: newest_first = configuration.get('look', 'newest_first') except: errorpage('"newest_first" is missing in configuration!') try: new_comment_mail = configuration.get('smtp', 'new_comment_mail') except: errorpage('"new_comment_mail" is missing in configuration!') try: mail_to = configuration.get('smtp', 'mail_to') except: errorpage('"mail_to" is missing in configuration!') try: smtp_host = configuration.get('smtp', 'smtp_host') except: errorpage('"smtp_host" is missing in configuration!') try: feed_preview = configuration.get('feed', 'feed_preview') except: errorpage('"feed_preview" is missing or empty in configuration!') # Read POST Variables action = cgi.FieldStorage() month_display = action.getvalue('m') static_display = action.getvalue('s') if static_display: static_display = static_display.replace('/', '') post_display = action.getvalue('p') if post_display: post_display = post_display.replace(' ', '-').replace('/', '') allentries_display = action.getvalue('a') feed_display = action.getvalue('feed') if not month_display: month_display = "" if not post_display: post_display = "" if not static_display: static_display = "" if not allentries_display: allentries_display = "" if not feed_display: feed_display = "" # Commentstuff ctitle = action.getvalue('ctitle') cname = action.getvalue('cname') ctext = action.getvalue('ctext') cquiz = action.getvalue('cquiz') cquizv = action.getvalue('cquizv') if not ctitle: ctitle = "" if not cname: cname = "" if not ctext: ctext = "" if not cquiz: cquiz = "" if not cquizv: cquizv = "" # Comment to commit? if cname and ctext and ctitle: # Prevent XSS hacks cname = cname.replace('<', '<').replace('>', '>').replace('\'', '"') ctext = ctext.replace('<', '<').replace('>', '>').replace('\'', '"') # Add comment if not cquiz == cquizv: errorpage("Brainmode") else: comments_file = glob.glob(entries_dir + ctitle + '.comments') if not comments_file: try: content = open(entries_dir + ctitle + '.comments', "w") content.close() except: errorpage('"' + entries_dir + '" isn\'t writable!') try: content = open(entries_dir + ctitle + '.comments', "a") content.write("-." + cname + "\n") content.write("+." + time.asctime() + "\n") ctext = ctext.split("\n") for line in ctext: content.write("." + line + "\n") content.close() # Send mail? if not new_comment_mail == 'False': msg = 'From: Blogthon\nTo: ' + mail_to + '\nSubject: New comment on ' + blog_title + '\n\nSomeone wrote a comment to this entry: ' + blog_url + '?p=' + ctitle.replace(' ', '-') smtp = smtplib.SMTP(smtp_host) smtp.sendmail(blog_title, mail_to, msg) smtp.quit() except: errorpage('Comment cannot be written!') # Read entries and store their title and timestamp entries = [] entries_list = glob.glob(entries_dir + '*.' + entries_suffix) for entry in entries_list: timestamp = os.stat(entry) timestamp = time.localtime(timestamp[8]) entry = timestamp, entry entries.append(entry) if newest_first: entries.sort(reverse=True) else: entries.sort() # Generate atom feed if feed_display == "atom": date = entries[0][0] blog_title_md5sum = generate_uuid(blog_title) # Append 0 to the beginning if len of integer is 1 (value<10) month = '%(#)02d' % {'#': int(date[1])} day = '%(#)02d' % {'#': int(date[2])} hour = '%(#)02d' % {'#':int(date[3])} min = '%(#)02d' % {'#': int(date[4])} sec = '%(#)02d' % {'#': int(date[5])} document_header("atom") print '<link href="' + blog_url + '/?feed=atom" rel="self" type="application/atom+xml"/>' print ' <author>' print ' <name>' + blog_title + '</name>' print ' </author>' print ' <title>' + blog_title + '</title>' print ' <id>urn:uuid:' + blog_title_md5sum + '</id>' print ' <updated>' + str(date[0]) + '-' + month + '-' + day + 'T' + hour + ':' + min + ':' + sec + 'Z</updated>' print '' j = len(entries) if j > 10: j = 10 for i in xrange(0, j): title = str(entries[i][1]).replace('entries/', '', 1).replace('.' + entries_suffix, '') date = entries[i][0] title_md5sum = generate_uuid(title) print ' <entry>' print ' <title>' + title + '</title>' print ' <link href="' + blog_url + '?p=' + title + '"/>' print ' <id>urn:uuid:' + title_md5sum + '</id>' print ' <updated>' + str(date[0]) + '-' + month + '-' + day + 'T' + hour + ':' + min + ':' + sec + 'Z</updated>' print ' <summary>' content = open(str(entries[i][1]), 'r') for h in xrange(0, int(feed_preview)): rss_line = content.readline().strip() if rss_line != '': print ' ' + rss_line content.close() print ' </summary>' print ' </entry>' print '</feed>' # Generate rss 2.0 feed elif feed_display == "rss": document_header("rss") print ' <channel>' print ' <title>' + blog_title + '</title>' print ' <link>' + blog_url + '</link>' print ' <description>' + blog_subtitle + '</description>' date = time.strftime("%a, %d %b %Y %H:%M:%S", time.gmtime(time.mktime(entries[0][0]))) print ' <pubDate>' + date + '</pubDate>' print '' j = len(entries) if j > 10: j = 10 for i in xrange(0, j): title = str(entries[i][1]).replace('entries/', '', 1).replace('.' + entries_suffix, '') date = time.strftime("%a, %d %b %Y %H:%M:%S %z", time.gmtime(time.mktime(entries[i][0]))) title_md5sum = generate_uuid(title) print ' <item>' print ' <title>' + title + '</title>' print ' <link>' + blog_url + '?p=' + title + '</link>' print ' <guid>' + title_md5sum + '</guid>' print ' <pubDate>' + date + '</pubDate>' content = open(str(entries[i][1]), 'r') rss_description= '' for h in xrange(0, int(feed_preview)): line = content.readline().strip() if line: rss_description = rss_description + line + '<br />' content.close() print ' <description><![CDATA[' + rss_description + ']]></description>' print ' </item>' print ' </channel>' print '</rss>' # Generate regular page else: document_header("xhtml-strict") print ' <head>' print ' <title>' + blog_title + '</title>' print ' <meta http-equiv="content-type" content="text/html; charset=utf-8" />' print ' <meta name="keywords" content="' + keywords + '" />' print ' <meta name="description" content="' + blog_title + '" />' print ' <link rel="stylesheet" type="text/css" href="styles/' + style + '/' + style + '.css" />' print ' </head>' print ' <body>' print '' # Plugins sys.path.append(plugins_dir) for plugin in glob.glob(plugins_dir + '*.py'): __import__ (plugin.split('/')[1].replace('.py', '')) # Site header print ' <div class="header">' print ' <div class="header_title">' print ' <a href="?" class="header_link">' + blog_title + '</a>' print ' </div>' print ' <div class="header_subtitle">' print ' <span class="header_subtitle">' + blog_subtitle + '</span>' print ' </div>' print ' </div>' print '' # RSS feed print ' <div class="rss">' print ' <a href="?feed=rss" class="rss_link">rss</a>' print ' </div>' print '' # Atom feed print ' <div class="atom">' print ' <a href="?feed=atom" class="atom_link">atom</a>' print ' </div>' print '' # Staticpages if staticpages == "True": staticpages = [] staticpages_list = glob.glob(staticpages_dir + '*') staticpages_list.sort() print ' <div class="pages">' print ' <div class="pages_title">pages</div>' print ' <div class="pages_list">' print ' <ul class="pages_list">' for staticpage in staticpages_list: file = open(staticpage, 'r') header = file.readline() if header.split(':', 1)[0] == 'extern_link': link = header.split(':', 1)[1].strip() else: link = re.sub('\w+?\/', '', staticpage) link = '?s=' + link file.close() title = re.sub('\w+?\/\d+?-', '', staticpage) print ' <li class="pages_list_entry"><a href="' + link + '" class="pages_list_entry">' + title + '</a></li>' print ' </ul>' print ' </div>' print ' <div class="pages_footer"></div>' print ' </div>' print '' # Monthlist if monthlist == "True": olddate = "" print ' <div class="months">' print ' <div class="months_title">months</div>' print ' <div class="months_list">' print ' <ul class="months_list">' for entry in entries: date = time.strftime("%m%Y", entry[0]) date_display = time.strftime("%h %Y", entry[0]) if not olddate == date: print ' <li class="months_list_entry"><a href="?m=' + date + '" class="months_list_entry">' + date_display + '</a></li>' olddate = date print ' </ul>' print ' </div>' print ' <div class="months_footer"></div>' print ' </div>' print '' # Linklist if linklist == "True": print ' <div class="linklist">' print ' <div class="linklist_title">links</div>' print ' <div class="linklist_list">' print ' <ul class="linklist_list">' try: content = open("linklist", "r") for line in content: if line.strip() is "": print '<br />' else: print ' <li class="linklist_list_entry"><a href="' + line.split(" ")[0] + '" target="_blank" class="months_list_entry">' + line.split(" ", 1)[1].strip() + '</a></li>' content.close() except: print '' print ' </ul>' print ' </div>' print ' <div class="linklist_footer"></div>' print ' </div>' print '' print ' <div class="entries">' print '' # Staticpage if static_display != "": content = open(staticpages_dir + static_display, "r") print ' <div class="entry">' print ' <div class="entry_title">' + re.sub('\d+?-', '', static_display) + '</div>' print ' <div class="entry_content">' print ' <p>' for line in content: if no_break.match(line): print ' ' + line.strip() else: print ' ' + line.strip() + '<br />' print ' </p>' print ' </div>' print ' <div class="entry_footer"></div>' print ' <div class="entry_border_left"></div>' print ' <div class="entry_border_right"></div>' print ' <div class="entry_border_top"></div>' print ' <div class="entry_border_bottom"></div>' print ' </div>' print '' content.close() # Entry else: entry_counter = 0 for entry in entries: date = time.strftime("%c", entry[0]) date_to_compare = time.strftime("%m%Y", entry[0]) # Needed for permalinks entry = entry[1] title = entry.replace('entries/', '', 1) title = title.replace('.' + entries_suffix, '') if month_display == date_to_compare or not month_display: if post_display == title.replace(' ', '-') or not post_display: if allentries_display == "1" or entry_counter < entries_per_page: content = open(entry, "r") print ' <div class="entry">' if permalinks: print ' <div class="entry_title"><a href="?p=' + title.replace(' ', '-') + '" class="entry_title">' + title + '</a></div>' else: print ' <div class="entry_title">' + title + '</div>' print ' <div class="entry_date">' + date + '</div>' print ' <div class="entry_content">' for line in content: if no_break.match(line): print ' ' + line.strip() else: print ' ' + line.strip() + '<br />' print ' </div>' print ' <div class="entry_footer"></div>' print ' <div class="entry_border_left"></div>' print ' <div class="entry_border_right"></div>' print ' <div class="entry_border_top"></div>' print ' <div class="entry_border_bottom"></div>' # Comments... # ... are shown when post_display and comments_file isn't false comments_file = glob.glob(entries_dir + title + '.comments') if post_display: if comments_file: comments_file = glob.glob(entries_dir + title + '.comments') comments_content = open(comments_file[0], "r") print ' </div>' print ' </div>' print '' print ' <div class="comments">' notfirstline = 0 # Ugly fix for closing comment containers label_count = 0 for line in comments_content: if line_start_hyphen.match(line): if notfirstline == 1: print ' </div>' print ' </div>' notfirstline = 0; print ' <div class="comment">' #Label for each comment label_count += 1 print ' <a name="' + str(label_count) + '"></a>' print ' <div class="comment_author">' + line.split(".", 1)[1].strip() + '</div>' elif line_start_plus.match(line): print ' <div class="comment_date">' + line.split(".", 1)[1].strip() + '</div>' print ' <div class="comment_content">' else: notfirstline = 1; line = line.split(".", 1)[1] print ' ' + line.strip() + '<br />' print '' print ' </div>' print ' </div>' comments_content.close() else: print ' </div>' print ' </div>' print ' <div class="comments">' # Form for adding comments if comments == "True": random_int_a = random.randint(1,9) random_int_b = random.randint(1,9) cquizv = random_int_a + random_int_b print ' <div class="submit_comment">' print ' <form action="" method="post">' print ' <input type="hidden" name="ctitle" value="' + title + '" />' print ' <input type="hidden" name="cquizv" value="' + str(cquizv) + '" />' print ' <label class="submit_comment_name">name:</label><input class="submit_comment_name_input" type="text" id="cname" name="cname" />' print ' <br /><label class="submit_comment_text">text:</label><textarea class="submit_comment_textarea" id="ctext" name="ctext"></textarea>' print ' <br /><label class="submit_comment_quiz">' + str(random_int_a) + '+' + str(random_int_b) + '=</label><input class="submit_comment_quiz_input" type="text" id="cquiz" name="cquiz" />' print ' <br /><input class="submit_comment_button" type="submit" id="submit" value="post comment" />' print ' </form>' print ' </div>' else: print ' <div class="submit_border_bottom"></div>' print '' if comments == "True": comments_file = glob.glob(entries_dir + title + '.comments') if not comments_file and not post_display: print ' <div class="entry_comment">' print ' <a href="?p=' + title.replace(' ','-') + '" class="entry_comment">no comments</a>' print ' </div>' print ' </div>' print '' elif comments_file and not post_display: comments_content = open(comments_file[0], "r") comments_counter = 0 for line in comments_content: if line.split(".", 1)[0] == "-": comments_counter += 1 print ' <div class="entry_comment">' print ' <a href="?p=' + title.replace(' ', '-') + '" class="entry_comment">comments (' + str(comments_counter) + ')</a>' print ' </div>' print ' </div>' print '' comments_content.close() else: print ' </div>' print '' content.close() entry_counter += 1 if not month_display and not post_display and not allentries_display and entry_counter == entries_per_page: # Display pagelist print ' <div class="entry"><a href=?a=1>View all entries...</a></div>' print ' </div>' print '' print ' </body>' print '</html>' # vim: set tw=0 ts=4: