Browse Source

Add RSS feed, contact page, Kirsle.net stuff

pull/2/head
Noah Petherbridge 6 years ago
parent
commit
9a4f74844d
10 changed files with 350 additions and 34 deletions
  1. +14
    -0
      config-sample.py
  2. +4
    -1
      rophako/__init__.py
  3. +11
    -18
      rophako/model/blog.py
  4. +121
    -12
      rophako/modules/blog.py
  5. +0
    -1
      rophako/modules/comment.py
  6. +67
    -0
      rophako/modules/contact.py
  7. +65
    -1
      rophako/modules/kirsle_legacy.py
  8. +19
    -0
      rophako/www/blog/categories.inc.html
  9. +1
    -1
      rophako/www/blog/nav-links.inc.html
  10. +48
    -0
      rophako/www/contact/index.html

+ 14
- 0
config-sample.py View File

@@ -4,6 +4,7 @@

import os
_basedir = os.path.abspath(os.path.dirname(__file__))
import datetime

DEBUG = True

@@ -63,6 +64,19 @@ BLOG_DEFAULT_PRIVACY = "public"
BLOG_TIME_FORMAT = "%A, %B %d %Y @ %I:%M:%S %p" # "Weekday, Month dd yyyy @ hh:mm:ss AM"
BLOG_ALLOW_COMMENTS = True

# RSS feed settings.
RSS_TITLE = "Kirsle.net"
RSS_LINK = "http://www.kirsle.net/"
RSS_LANGUAGE = "en"
RSS_DESCRIPTION = "The web blog of Kirsle"
RSS_COPYRIGHT = "Copyright {}, Kirsle.net".format(str(datetime.datetime.now().strftime("%Y")))
RSS_WEBMASTER = NOTIFY_ADDRESS[0]
RSS_IMAGE_TITLE = RSS_TITLE
RSS_IMAGE_URL = "http://www.kirsle.net/static/avatars/casey.png"
RSS_IMAGE_WIDTH = 96
RSS_IMAGE_HEIGHT = 96
RSS_IMAGE_DESCRIPTION = "Kirsle's Avatar"

################################################################################
## Photo Settings ##
################################################################################


+ 4
- 1
rophako/__init__.py View File

@@ -21,12 +21,14 @@ from rophako.modules.blog import mod as BlogModule
from rophako.modules.photo import mod as PhotoModule
from rophako.modules.comment import mod as CommentModule
from rophako.modules.emoticons import mod as EmoticonsModule
from rophako.modules.contact import mod as ContactModule
app.register_blueprint(AdminModule)
app.register_blueprint(AccountModule)
app.register_blueprint(BlogModule)
app.register_blueprint(PhotoModule)
app.register_blueprint(CommentModule)
app.register_blueprint(EmoticonsModule)
app.register_blueprint(ContactModule)

# Custom Jinja handler to support custom- and default-template folders for
# rendering templates.
@@ -114,6 +116,8 @@ def catchall(path):
return send_file(abspath)
elif not "." in path and os.path.isfile(abspath + ".html"):
return rophako.utils.template(path + ".html")
elif not "." in path and os.path.isfile(abspath + "/index.html"):
return rophako.utils.template(path + "/index.html")

return not_found("404")

@@ -125,7 +129,6 @@ def index():

@app.errorhandler(404)
def not_found(error):
print "NOT FOUND"
return render_template('errors/404.html', **g.info), 404




+ 11
- 18
rophako/model/blog.py View File

@@ -46,26 +46,19 @@ def get_index():
return db


def __get_categories():
"""Get the blog categories cache.
def get_categories():
"""Get the blog categories and their popularity."""
index = get_index()

The category cache is in the following format:
# Group by tags.
tags = {}
for post, data in index.iteritems():
for tag in data["categories"]:
if not tag in tags:
tags[tag] = 0
tags[tag] += 1

```
{
'category_name': {
'post_id': 'friendly_id',
...
},
...
}
```
"""

# Index doesn't exist?
if not JsonDB.exists("blog/tags"):
return {}
return JsonDB.get("blog/tags")
return tags


def get_entry(post_id):


+ 121
- 12
rophako/modules/blog.py View File

@@ -6,6 +6,8 @@ from flask import Blueprint, g, request, redirect, url_for, session, flash
import re
import datetime
import calendar
import time
from xml.dom.minidom import Document

import rophako.model.user as User
import rophako.model.blog as Blog
@@ -225,6 +227,85 @@ def delete():

return template("blog/delete.html")


@mod.route("/rss")
def rss():
"""RSS feed for the blog."""
doc = Document()

rss = doc.createElement("rss")
rss.setAttribute("version", "2.0")
rss.setAttribute("xmlns:blogChannel", "http://backend.userland.com/blogChannelModule")
doc.appendChild(rss)

channel = doc.createElement("channel")
rss.appendChild(channel)

rss_time = "%a, %d %b %Y %H:%M:%S GMT"

######
## Channel Information
######

today = time.strftime(rss_time, time.gmtime())

xml_add_text_tags(doc, channel, [
["title", RSS_TITLE],
["link", RSS_LINK],
["description", RSS_DESCRIPTION],
["language", RSS_LANGUAGE],
["copyright", RSS_COPYRIGHT],
["pubDate", today],
["lastBuildDate", today],
["webmaster", RSS_WEBMASTER],
])

######
## Image Information
######

image = doc.createElement("image")
channel.appendChild(image)
xml_add_text_tags(doc, image, [
["title", RSS_IMAGE_TITLE],
["url", RSS_IMAGE_URL],
["link", RSS_LINK],
["width", RSS_IMAGE_WIDTH],
["height", RSS_IMAGE_HEIGHT],
["description", RSS_IMAGE_DESCRIPTION],
])

######
## Add the blog posts
######

index = Blog.get_index()
posts = get_index_posts(index)
for post_id in posts[:BLOG_ENTRIES_PER_RSS]:
post = Blog.get_entry(post_id)
item = doc.createElement("item")
channel.appendChild(item)
xml_add_text_tags(doc, item, [
["title", post["subject"]],
["link", url_for("blog.entry", fid=post["fid"])],
["description", post["body"]],
["pubDate", time.strftime(rss_time, time.gmtime(post["time"]))],
])

return doc.toprettyxml(encoding="utf-8")


def xml_add_text_tags(doc, root_node, tags):
"""RSS feed helper function.

Add a collection of simple tag/text pairs to a root XML element."""
for pair in tags:
name, value = pair
channelTag = doc.createElement(name)
channelTag.appendChild(doc.createTextNode(str(value)))
root_node.appendChild(channelTag)


def partial_index():
"""Partial template for including the index view of the blog."""

@@ -249,18 +330,8 @@ def partial_index():
else:
pool = index

# Separate the sticky posts from the normal ones.
sticky, normal = set(), set()
for post_id, data in pool.iteritems():
if data["sticky"]:
sticky.add(post_id)
else:
normal.add(post_id)

# Sort the blog IDs by published time.
posts = []
posts.extend(sorted(sticky, key=lambda x: pool[x]["time"], reverse=True))
posts.extend(sorted(normal, key=lambda x: pool[x]["time"], reverse=True))
# Get the posts we want.
posts = get_index_posts(pool)

# Handle pagination.
offset = request.args.get("skip", 0)
@@ -313,3 +384,41 @@ def partial_index():
g.info["posts"] = selected

return template("blog/index.inc.html")


def get_index_posts(index):
"""Helper function to get data for the blog index page."""
# Separate the sticky posts from the normal ones.
sticky, normal = set(), set()
for post_id, data in index.iteritems():
if data["sticky"]:
sticky.add(post_id)
else:
normal.add(post_id)

# Sort the blog IDs by published time.
posts = []
posts.extend(sorted(sticky, key=lambda x: index[x]["time"], reverse=True))
posts.extend(sorted(normal, key=lambda x: index[x]["time"], reverse=True))
return posts

def partial_tags():
"""Get a listing of tags and their quantities for the nav bar."""
tags = Blog.get_categories()

# Sort the tags by popularity.
sort_tags = [ tag for tag in sorted(tags.keys(), key=lambda y: tags[y], reverse=True) ]
result = []
has_small = False
for tag in sort_tags:
result.append(dict(
category=tag,
count=tags[tag],
small=tags[tag] < 3, # TODO: make this configurable
))
if tags[tag] < 3:
has_small = True

g.info["tags"] = result
g.info["has_small"] = has_small
return template("blog/categories.inc.html")

+ 0
- 1
rophako/modules/comment.py View File

@@ -14,7 +14,6 @@ from config import *

mod = Blueprint("comment", __name__, url_prefix="/comments")

## TODO: emoticon support

@mod.route("/")
def index():


+ 67
- 0
rophako/modules/contact.py View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-

"""Endpoints for contacting the site owner."""

from flask import Blueprint, g, request, redirect, url_for, session, flash
import re
import time

from rophako.utils import template, send_email
from rophako.log import logger
from config import *

mod = Blueprint("contact", __name__, url_prefix="/contact")


@mod.route("/")
def index():
return template("contact/index.html")


@mod.route("/send", methods=["POST"])
def send():
"""Submitting the contact form."""
name = request.form.get("name", "") or "Anonymous"
email = request.form.get("email", "")
subject = request.form.get("subject", "") or "[No Subject]"
message = request.form.get("message", "")

# Spam traps.
trap1 = request.form.get("contact", "x") != ""
trap2 = request.form.get("website", "x") != "http://"
if trap1 or trap2:
flash("Wanna try that again?")
return redirect(url_for(".index"))

# Message is required.
if len(message) == 0:
flash("The message is required.")
return redirect(url_for(".index"))

# Send the e-mail.
send_email(
to=NOTIFY_ADDRESS,
subject="Contact Form on {}: {}".format(SITE_NAME, subject),
message="""A visitor to {site_name} has sent you a message!

IP Address: {ip}
User Agent: {ua}
Referrer: {referer}
Name: {name}
E-mail: {email}
Subject: {subject}

{message}""".format(
site_name=SITE_NAME,
ip=request.remote_addr,
ua=request.user_agent.string,
referer=request.headers.get("Referer", ""),
name=name,
email=email,
subject=subject,
message=message,
)
)

flash("Your message has been delivered.")
return redirect(url_for("index"))

+ 65
- 1
rophako/modules/kirsle_legacy.py View File

@@ -2,14 +2,21 @@

# Legacy endpoint compatibility from kirsle.net.

from flask import request, redirect, url_for
from flask import g, request, redirect, url_for, flash
import re
import os

from rophako import app
from rophako.utils import template
import rophako.model.blog as Blog
import rophako.jsondb as JsonDB


@app.route("/+")
def google_plus():
return redirect("https://plus.google.com/+NoahPetherbridge/posts")


@app.route("/blog.html")
def ancient_legacy_blog():
post_id = request.args.get("id", None)
@@ -24,10 +31,67 @@ def ancient_legacy_blog():

return redirect(url_for("blog.entry", fid=post["fid"]))


@app.route("/blog/kirsle/<fid>")
def legacy_blog(fid):
return redirect(url_for("blog.entry", fid=fid))


@app.route("/rss.cgi")
def legacy_rss():
return redirect(url_for("blog.rss"))


@app.route("/firered/<page>")
@app.route("/firered")
def legacy_firered(page=""):
g.info["page"] = str(page) or "1"
return template("firered.html")


@app.route("/download", methods=["GET", "POST"])
def legacy_download():
form = None
if request.method == "POST":
form = request.form
else:
form = request.args

method = form.get("method", "index")
project = form.get("project", "")
filename = form.get("file", "")

root = "/home/kirsle/www/projects"

if project and filename:
# Filter the sections.
project = re.sub(r'[^A-Za-z0-9]', '', project) # Project name is alphanumeric only.
filename = re.sub(r'[^A-Za-z0-9\-_\.]', '', filename)

# Check that all the files exist.
if os.path.isdir(os.path.join(root, project)) and os.path.isfile(os.path.join(root, project, filename)):
# Hit counters.
hits = { "hits": 0 }
db = "data/downloads/{}-{}".format(project, filename)
if JsonDB.exists(db.format(project, filename)):
hits = JsonDB.get(db)

# Actually getting the file?
if method == "get":
# Up the hit counter.
hits["hits"] += 1
JsonDB.commit(db, hits)

g.info["method"] = method
g.info["project"] = project
g.info["file"] = filename
g.info["hits"] = hits["hits"]
return template("download.html")

flash("The file or project wasn't found.")
return redirect(url_for("index"))


@app.route("/<page>.html")
def legacy_url(page):
return "/{}".format(page)

+ 19
- 0
rophako/www/blog/categories.inc.html View File

@@ -0,0 +1,19 @@
{% for tag in tags %}
{% if not tag["small"] %}
&#0187; <a href="{{ url_for('blog.category', category=tag['category']) }}">{{ tag['category'] }}</a>
<small>({{ tag['count'] }})</small><br>
{% endif %}
{% endfor %}
{% if has_small %}
<div id="blog_show_more" style="display: none">
{% for tag in tags %}
{% if tag["small"] %}
&#0187; <a href="{{ url_for('blog.category', category=tag['category']) }}">{{ tag['category'] }}</a>
<small>({{ tag['count'] }})</small><br>
{% endif %}
{% endfor %}
</div>
<div id="blog_show_less" style="display: block">
&#0164; <a href="#" onClick="$('#blog_show_less').hide(); $('#blog_show_more').show(1000); return false">Show more...</a>
</div>
{% endif %}

+ 1
- 1
rophako/www/blog/nav-links.inc.html View File

@@ -3,7 +3,7 @@
{% if can_older or can_newer %}
<div class="right">
[
<a href="/rss">RSS Feed</a> | {# TODO! #}
<a href="{{ url_for('blog.rss') }}">RSS Feed</a> |
{% if can_earlier %}
{% if category %}
<a href="{{ url_for('blog.category', category=category) }}?skip={{ earlier }}">&lt; Newer</a>


+ 48
- 0
rophako/www/contact/index.html View File

@@ -0,0 +1,48 @@
{% extends "layout.html" %}
{% block title %}Contact Me{% endblock %}
{% block content %}

<h1>Contact Me</h1>

You can use the form below to send me an e-mail.<p>

<form name="contact" action="{{ url_for('contact.send') }}" method="POST">
<input type="hidden" name="token" value="{{ csrf_token() }}">
<table border="0" cellspacing="0" cellpadding="2">
<tr>
<td width="50%" align="left" valign="middle">
<strong>Your name:</strong><br>
<small>(so I know who you are)</small><br>
<input type="text" size="40" name="name">
</td>
<td width="50%" align="left" valign="middle">
<strong>Your email:</strong><br>
<small>(if you want a response)</small><br>
<input type="email" size="40" name="email">
</td>
</tr>
<tr>
<td colspan="2" align="left" valign="middle">
<strong>Message subject:</strong><br>
<small>(optional)</small><br>
<input type="text" size="40" name="subject" style="width: 100%"><p>

<strong>Message:</strong><br>
<small>(required)</small><br>
<textarea cols="40" rows="12" name="message" style="width: 100%"></textarea>
</td>
</tr>
<tr>
<td colspan="2" align="right" valign="middle">
<button type="submit">Send Message</button>
</td>
</tr>
</table>
<div style="display: none">
If you can see these boxes, don't touch them.<br>
<input type="text" size="40" name="contact" value=""><br>
<input type="text" size="40" name="website" value="http://">
</div>
</form>

{% endblock %}

Loading…
Cancel
Save