Add Comments and Emoticons modules
|
@ -10,6 +10,9 @@ DEBUG = True
|
||||||
# Unique name of your site, e.g. "kirsle.net"
|
# Unique name of your site, e.g. "kirsle.net"
|
||||||
SITE_NAME = "example.com"
|
SITE_NAME = "example.com"
|
||||||
|
|
||||||
|
# E-mail addresses for site notifications (i.e. new comments).
|
||||||
|
NOTIFY_ADDRESS = ["root@localhost"]
|
||||||
|
|
||||||
# Secret key used for session cookie signing. Make this long and hard to guess.
|
# Secret key used for session cookie signing. Make this long and hard to guess.
|
||||||
#
|
#
|
||||||
# Tips for creating a strong secret key:
|
# Tips for creating a strong secret key:
|
||||||
|
@ -36,6 +39,19 @@ REDIS_PORT = 6379
|
||||||
REDIS_DB = 0
|
REDIS_DB = 0
|
||||||
REDIS_PREFIX = "rophako:"
|
REDIS_PREFIX = "rophako:"
|
||||||
|
|
||||||
|
# Mail settings
|
||||||
|
MAIL_METHOD = "smtp" # or "sendmail", not yet implemented
|
||||||
|
MAIL_SERVER = "localhost"
|
||||||
|
MAIL_PORT = 25
|
||||||
|
MAIL_SENDER = "Rophako CMS <no-reply@rophako.kirsle.net>"
|
||||||
|
|
||||||
|
# Emoticon theme used for blog posts and comments. Should exist at the URL
|
||||||
|
# "/static/smileys" from your document root, and have a file named
|
||||||
|
# "emoticons.json" inside. If you add a custom theme to your private site
|
||||||
|
# folder, then also change EMOTICON_ROOT_PRIVATE to look there instead.
|
||||||
|
EMOTICON_THEME = "tango"
|
||||||
|
EMOTICON_ROOT_PRIVATE = os.path.join(_basedir, "rophako", "www", "static", "smileys")
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
## Blog Settings ##
|
## Blog Settings ##
|
||||||
################################################################################
|
################################################################################
|
||||||
|
@ -64,3 +80,13 @@ PHOTO_TIME_FORMAT = BLOG_TIME_FORMAT
|
||||||
PHOTO_WIDTH_LARGE = 800 # Max width of full size photos.
|
PHOTO_WIDTH_LARGE = 800 # Max width of full size photos.
|
||||||
PHOTO_WIDTH_THUMB = 256 # Max square width of photo thumbnails.
|
PHOTO_WIDTH_THUMB = 256 # Max square width of photo thumbnails.
|
||||||
PHOTO_WIDTH_AVATAR = 96 # Square width of photo avatars.
|
PHOTO_WIDTH_AVATAR = 96 # Square width of photo avatars.
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
## Comment Settings ##
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
COMMENT_TIME_FORMAT = "%A, %B %d %Y @ %I:%M %p"
|
||||||
|
|
||||||
|
# We use Gravatar for comments if the user provides an e-mail address. Specify
|
||||||
|
# the URL to a fallback image to use in case they don't have a gravatar.
|
||||||
|
COMMENT_DEFAULT_AVATAR = ""
|
|
@ -19,10 +19,14 @@ from rophako.modules.admin import mod as AdminModule
|
||||||
from rophako.modules.account import mod as AccountModule
|
from rophako.modules.account import mod as AccountModule
|
||||||
from rophako.modules.blog import mod as BlogModule
|
from rophako.modules.blog import mod as BlogModule
|
||||||
from rophako.modules.photo import mod as PhotoModule
|
from rophako.modules.photo import mod as PhotoModule
|
||||||
|
from rophako.modules.comment import mod as CommentModule
|
||||||
|
from rophako.modules.emoticons import mod as EmoticonsModule
|
||||||
app.register_blueprint(AdminModule)
|
app.register_blueprint(AdminModule)
|
||||||
app.register_blueprint(AccountModule)
|
app.register_blueprint(AccountModule)
|
||||||
app.register_blueprint(BlogModule)
|
app.register_blueprint(BlogModule)
|
||||||
app.register_blueprint(PhotoModule)
|
app.register_blueprint(PhotoModule)
|
||||||
|
app.register_blueprint(CommentModule)
|
||||||
|
app.register_blueprint(EmoticonsModule)
|
||||||
|
|
||||||
# Custom Jinja handler to support custom- and default-template folders for
|
# Custom Jinja handler to support custom- and default-template folders for
|
||||||
# rendering templates.
|
# rendering templates.
|
||||||
|
@ -34,6 +38,10 @@ app.jinja_loader = jinja2.ChoiceLoader([
|
||||||
app.jinja_env.globals["csrf_token"] = rophako.utils.generate_csrf_token
|
app.jinja_env.globals["csrf_token"] = rophako.utils.generate_csrf_token
|
||||||
app.jinja_env.globals["include_page"] = rophako.utils.include
|
app.jinja_env.globals["include_page"] = rophako.utils.include
|
||||||
|
|
||||||
|
# Preload the emoticon data.
|
||||||
|
import rophako.model.emoticons as Emoticons
|
||||||
|
Emoticons.load_theme()
|
||||||
|
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def before_request():
|
def before_request():
|
||||||
|
|
|
@ -117,6 +117,11 @@ def read_json(path):
|
||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
raise Exception("Can't read JSON file {}: file not found!".format(path))
|
raise Exception("Can't read JSON file {}: file not found!".format(path))
|
||||||
|
|
||||||
|
# Don't allow any fishy looking paths.
|
||||||
|
if ".." in path:
|
||||||
|
logger.error("ERROR: JsonDB tried to read a path with two dots: {}".format(path))
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
# Open and lock the file.
|
# Open and lock the file.
|
||||||
fh = codecs.open(path, 'r', 'utf-8')
|
fh = codecs.open(path, 'r', 'utf-8')
|
||||||
flock(fh, LOCK_SH)
|
flock(fh, LOCK_SH)
|
||||||
|
@ -138,6 +143,11 @@ def write_json(path, data):
|
||||||
"""Write a JSON document."""
|
"""Write a JSON document."""
|
||||||
path = str(path)
|
path = str(path)
|
||||||
|
|
||||||
|
# Don't allow any fishy looking paths.
|
||||||
|
if ".." in path:
|
||||||
|
logger.error("ERROR: JsonDB tried to write a path with two dots: {}".format(path))
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
logger.debug("JsonDB: WRITE > {}".format(path))
|
logger.debug("JsonDB: WRITE > {}".format(path))
|
||||||
|
|
||||||
# Open and lock the file.
|
# Open and lock the file.
|
||||||
|
|
234
rophako/model/comment.py
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Commenting models."""
|
||||||
|
|
||||||
|
from flask import g, url_for
|
||||||
|
import time
|
||||||
|
import hashlib
|
||||||
|
import urllib
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
|
||||||
|
import config
|
||||||
|
import rophako.jsondb as JsonDB
|
||||||
|
import rophako.model.user as User
|
||||||
|
import rophako.model.emoticons as Emoticons
|
||||||
|
from rophako.utils import send_email
|
||||||
|
from rophako.log import logger
|
||||||
|
|
||||||
|
|
||||||
|
def add_comment(thread, uid, name, subject, message, url, time, ip, image=None):
|
||||||
|
"""Add a comment to a comment thread.
|
||||||
|
|
||||||
|
* uid is 0 if it's a guest post, otherwise the UID of the user.
|
||||||
|
* name is the commenter's name (if a guest)
|
||||||
|
* subject is for the e-mails that are sent out
|
||||||
|
* message is self explanatory.
|
||||||
|
* url is the URL where the comment can be read.
|
||||||
|
* time, epoch time of comment.
|
||||||
|
* ip is the IP address of the commenter.
|
||||||
|
* image is a Gravatar image URL etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Get the comments for this thread.
|
||||||
|
comments = get_comments(thread)
|
||||||
|
|
||||||
|
# Make up a unique ID for the comment.
|
||||||
|
cid = random_hash()
|
||||||
|
while cid in comments:
|
||||||
|
cid = random_hash()
|
||||||
|
|
||||||
|
# Add the comment.
|
||||||
|
comments[cid] = dict(
|
||||||
|
uid=uid,
|
||||||
|
name=name or "Anonymous",
|
||||||
|
image=image or "",
|
||||||
|
message=message,
|
||||||
|
time=time or int(time.time()),
|
||||||
|
ip=ip,
|
||||||
|
)
|
||||||
|
write_comments(thread, comments)
|
||||||
|
|
||||||
|
# Get info about the commenter.
|
||||||
|
if uid > 0:
|
||||||
|
user = User.get_user(uid=uid)
|
||||||
|
if user:
|
||||||
|
name = user["name"]
|
||||||
|
|
||||||
|
# Send the e-mail to the site admins.
|
||||||
|
send_email(
|
||||||
|
to=config.NOTIFY_ADDRESS,
|
||||||
|
subject="New comment: {}".format(subject),
|
||||||
|
message="""{name} has left a comment on: {subject}
|
||||||
|
|
||||||
|
{message}
|
||||||
|
|
||||||
|
To view this comment, please go to {url}
|
||||||
|
|
||||||
|
=====================
|
||||||
|
|
||||||
|
This e-mail was automatically generated. Do not reply to it.""".format(
|
||||||
|
name=name,
|
||||||
|
subject=subject,
|
||||||
|
message=message,
|
||||||
|
url=url,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Notify any subscribers.
|
||||||
|
subs = get_subscribers(thread)
|
||||||
|
for sub in subs.keys():
|
||||||
|
# Make the unsubscribe link.
|
||||||
|
unsub = url_for("comment.unsubscribe", thread=thread, who=sub, _external=True)
|
||||||
|
|
||||||
|
send_email(
|
||||||
|
to=sub,
|
||||||
|
subject="New Comment: {}".format(subject),
|
||||||
|
message="""Hello,
|
||||||
|
|
||||||
|
You are currently subscribed to the comment thread '{thread}', and somebody has
|
||||||
|
just added a new comment!
|
||||||
|
|
||||||
|
{name} has left a comment on: {subject}
|
||||||
|
|
||||||
|
{message}
|
||||||
|
|
||||||
|
To view this comment, please go to {url}
|
||||||
|
|
||||||
|
=====================
|
||||||
|
|
||||||
|
This e-mail was automatically generated. Do not reply to it.
|
||||||
|
|
||||||
|
If you wish to unsubscribe from this comment thread, please visit the following
|
||||||
|
URL: {unsub}""".format(
|
||||||
|
thread=thread,
|
||||||
|
name=name,
|
||||||
|
subject=subject,
|
||||||
|
message=message,
|
||||||
|
url=url,
|
||||||
|
unsub=unsub,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_comment(thread, cid):
|
||||||
|
"""Delete a comment from a thread."""
|
||||||
|
comments = get_comments(thread)
|
||||||
|
del comments[cid]
|
||||||
|
write_comments(thread, comments)
|
||||||
|
|
||||||
|
|
||||||
|
def count_comments(thread):
|
||||||
|
"""Count the comments on a thread."""
|
||||||
|
comments = get_comments(thread)
|
||||||
|
return len(comments.keys())
|
||||||
|
|
||||||
|
|
||||||
|
def add_subscriber(thread, email):
|
||||||
|
"""Add a subscriber to a thread."""
|
||||||
|
if not "@" in email:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Sanity check: only subscribe to threads that exist.
|
||||||
|
if not JsonDB.exists("comments/threads/{}".format(thread)):
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("Subscribe e-mail {} to thread {}".format(email, thread))
|
||||||
|
subs = get_subscribers(thread)
|
||||||
|
subs[email] = int(time.time())
|
||||||
|
write_subscribers(thread, subs)
|
||||||
|
|
||||||
|
|
||||||
|
def unsubscribe(thread, email):
|
||||||
|
"""Unsubscribe an e-mail address from a thread.
|
||||||
|
|
||||||
|
If `thread` is `*`, the e-mail is unsubscribed from all threads."""
|
||||||
|
|
||||||
|
# Which threads to unsubscribe from?
|
||||||
|
threads = []
|
||||||
|
if thread == "*":
|
||||||
|
threads = JsonDB.list_docs("comments/subscribers")
|
||||||
|
else:
|
||||||
|
threads = [thread]
|
||||||
|
|
||||||
|
# Remove them as a subscriber.
|
||||||
|
for thread in threads:
|
||||||
|
if JsonDB.exists("comments/subscribers/{}".format(thread)):
|
||||||
|
logger.info("Unsubscribe e-mail address {} from comment thread {}".format(email, thread))
|
||||||
|
db = get_subscribers(thread)
|
||||||
|
del db[email]
|
||||||
|
write_subscribers(thread, db)
|
||||||
|
|
||||||
|
|
||||||
|
def format_message(message):
|
||||||
|
"""HTML sanitize the message and format it for display."""
|
||||||
|
# We basically want to escape HTML symbols (like what Flask does for us
|
||||||
|
# automatically), but we want line breaks to translate to literal <br> tags.
|
||||||
|
message = re.sub(r'&', '&', message)
|
||||||
|
message = re.sub(r'<', '<', message)
|
||||||
|
message = re.sub(r'>', '>', message)
|
||||||
|
message = re.sub(r'"', '"', message)
|
||||||
|
message = re.sub(r"'", ''', message)
|
||||||
|
message = re.sub(r'\n', '<br>', message)
|
||||||
|
message = re.sub(r'\r', '', message)
|
||||||
|
|
||||||
|
# Process emoticons.
|
||||||
|
message = Emoticons.render(message)
|
||||||
|
return message
|
||||||
|
|
||||||
|
|
||||||
|
def get_comments(thread):
|
||||||
|
"""Get the comment thread."""
|
||||||
|
doc = "comments/threads/{}".format(thread)
|
||||||
|
print doc
|
||||||
|
if JsonDB.exists(doc):
|
||||||
|
return JsonDB.get(doc)
|
||||||
|
print "NOT EXIST"
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def write_comments(thread, comments):
|
||||||
|
"""Save the comments DB."""
|
||||||
|
if len(comments.keys()) == 0:
|
||||||
|
return JsonDB.delete("comments/threads/{}".format(thread))
|
||||||
|
return JsonDB.commit("comments/threads/{}".format(thread), comments)
|
||||||
|
|
||||||
|
|
||||||
|
def get_subscribers(thread):
|
||||||
|
"""Get the subscribers to a comment thread."""
|
||||||
|
doc = "comments/subscribers/{}".format(thread)
|
||||||
|
if JsonDB.exists(doc):
|
||||||
|
return JsonDB.get(doc)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def write_subscribers(thread, subs):
|
||||||
|
"""Save the subscribers to the DB."""
|
||||||
|
if len(subs.keys()) == 0:
|
||||||
|
return JsonDB.delete("comments/subscribers/{}".format(thread))
|
||||||
|
return JsonDB.commit("comments/subscribers/{}".format(thread), subs)
|
||||||
|
|
||||||
|
|
||||||
|
def random_hash():
|
||||||
|
"""Get a short random hash to use as the ID for a comment."""
|
||||||
|
md5 = hashlib.md5()
|
||||||
|
md5.update(str(random.randint(0, 1000000)))
|
||||||
|
return md5.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def gravatar(email):
|
||||||
|
"""Generate a Gravatar link for an email address."""
|
||||||
|
if "@" in email:
|
||||||
|
# Default avatar?
|
||||||
|
default = config.COMMENT_DEFAULT_AVATAR
|
||||||
|
|
||||||
|
# Construct the URL.
|
||||||
|
params = {
|
||||||
|
"s": "96", # size
|
||||||
|
}
|
||||||
|
if default:
|
||||||
|
params["d"] = default
|
||||||
|
url = "http://www.gravatar.com/avatar/" + hashlib.md5(email.lower()).hexdigest() + "?"
|
||||||
|
url += urllib.urlencode(params)
|
||||||
|
return url
|
||||||
|
return ""
|
81
rophako/model/emoticons.py
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Emoticon models."""
|
||||||
|
|
||||||
|
from flask import g, url_for
|
||||||
|
import os
|
||||||
|
import codecs
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
import config
|
||||||
|
import rophako.jsondb as JsonDB
|
||||||
|
from rophako.log import logger
|
||||||
|
|
||||||
|
|
||||||
|
_cache = {}
|
||||||
|
|
||||||
|
|
||||||
|
def load_theme():
|
||||||
|
"""Pre-load and cache the emoticon theme. This happens on startup."""
|
||||||
|
theme = config.EMOTICON_THEME
|
||||||
|
global _cache
|
||||||
|
|
||||||
|
# Cached?
|
||||||
|
if _cache:
|
||||||
|
return _cache
|
||||||
|
|
||||||
|
# Only if the theme file exists.
|
||||||
|
settings = os.path.join(config.EMOTICON_ROOT_PRIVATE, theme, "emoticons.json")
|
||||||
|
if not os.path.isfile(settings):
|
||||||
|
logger.error("Failed to load smiley theme {}: not found!")
|
||||||
|
|
||||||
|
# Try the default (tango).
|
||||||
|
theme = "tango"
|
||||||
|
settings = os.path.join(config.EMOTICON_ROOT_PRIVATE, theme, "emoticons.json")
|
||||||
|
if os.path.isfile(settings):
|
||||||
|
logger.info("Falling back to default theme: tango")
|
||||||
|
else:
|
||||||
|
# Give up.
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Read it.
|
||||||
|
fh = codecs.open(settings, "r", "utf-8")
|
||||||
|
text = fh.read()
|
||||||
|
fh.close()
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(text)
|
||||||
|
except Exception, e:
|
||||||
|
logger.error("Couldn't load JSON from emoticon file: {}".format(e))
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
# Cache and return it.
|
||||||
|
_cache = data
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def render(message):
|
||||||
|
"""Render the emoticons into a message.
|
||||||
|
|
||||||
|
The message should already be stripped of HTML and otherwise be 'safe' to
|
||||||
|
embed on a web page. The output of this function includes `<img>` tags and
|
||||||
|
these won't work otherwise."""
|
||||||
|
|
||||||
|
# Get the smileys config.
|
||||||
|
smileys = load_theme()
|
||||||
|
|
||||||
|
# Process all smileys.
|
||||||
|
for img in sorted(smileys["map"]):
|
||||||
|
for trigger in smileys["map"][img]:
|
||||||
|
if trigger in message:
|
||||||
|
# Substitute it.
|
||||||
|
sub = """<img src="{url}" alt="{trigger}" title="{trigger}">""".format(
|
||||||
|
url="/static/smileys/{}/{}".format(config.EMOTICON_THEME, img),
|
||||||
|
trigger=trigger,
|
||||||
|
)
|
||||||
|
pattern = r'([^A-Za-z0-9:\-]|^){}([^A-Za-z0-9:\-]|$)'.format(re.escape(trigger))
|
||||||
|
result = r'\1{}\2'.format(sub)
|
||||||
|
message = re.sub(pattern, result, message)
|
||||||
|
|
||||||
|
return message
|
|
@ -9,6 +9,8 @@ import calendar
|
||||||
|
|
||||||
import rophako.model.user as User
|
import rophako.model.user as User
|
||||||
import rophako.model.blog as Blog
|
import rophako.model.blog as Blog
|
||||||
|
import rophako.model.comment as Comment
|
||||||
|
import rophako.model.emoticons as Emoticons
|
||||||
from rophako.utils import template, pretty_time, login_required
|
from rophako.utils import template, pretty_time, login_required
|
||||||
from rophako.log import logger
|
from rophako.log import logger
|
||||||
from config import *
|
from config import *
|
||||||
|
@ -40,6 +42,10 @@ def entry(fid):
|
||||||
post = Blog.get_entry(post_id)
|
post = Blog.get_entry(post_id)
|
||||||
post["post_id"] = post_id
|
post["post_id"] = post_id
|
||||||
|
|
||||||
|
# Render emoticons.
|
||||||
|
if post["emoticons"]:
|
||||||
|
post["body"] = Emoticons.render(post["body"])
|
||||||
|
|
||||||
# Get the author's information.
|
# Get the author's information.
|
||||||
post["profile"] = User.get_user(uid=post["author"])
|
post["profile"] = User.get_user(uid=post["author"])
|
||||||
post["photo"] = User.get_picture(uid=post["author"])
|
post["photo"] = User.get_picture(uid=post["author"])
|
||||||
|
@ -48,8 +54,8 @@ def entry(fid):
|
||||||
# Pretty-print the time.
|
# Pretty-print the time.
|
||||||
post["pretty_time"] = pretty_time(BLOG_TIME_FORMAT, post["time"])
|
post["pretty_time"] = pretty_time(BLOG_TIME_FORMAT, post["time"])
|
||||||
|
|
||||||
# TODO: count the comments for this post
|
# Count the comments for this post
|
||||||
post["comment_count"] = 0
|
post["comment_count"] = Comment.count_comments("blog-{}".format(post_id))
|
||||||
|
|
||||||
g.info["post"] = post
|
g.info["post"] = post
|
||||||
return template("blog/entry.html")
|
return template("blog/entry.html")
|
||||||
|
@ -286,6 +292,10 @@ def partial_index():
|
||||||
|
|
||||||
post["post_id"] = post_id
|
post["post_id"] = post_id
|
||||||
|
|
||||||
|
# Render emoticons.
|
||||||
|
if post["emoticons"]:
|
||||||
|
post["body"] = Emoticons.render(post["body"])
|
||||||
|
|
||||||
# Get the author's information.
|
# Get the author's information.
|
||||||
post["profile"] = User.get_user(uid=post["author"])
|
post["profile"] = User.get_user(uid=post["author"])
|
||||||
post["photo"] = User.get_picture(uid=post["author"])
|
post["photo"] = User.get_picture(uid=post["author"])
|
||||||
|
@ -293,8 +303,8 @@ def partial_index():
|
||||||
|
|
||||||
post["pretty_time"] = pretty_time(BLOG_TIME_FORMAT, post["time"])
|
post["pretty_time"] = pretty_time(BLOG_TIME_FORMAT, post["time"])
|
||||||
|
|
||||||
# TODO: count the comments for this post
|
# Count the comments for this post
|
||||||
post["comment_count"] = 0
|
post["comment_count"] = Comment.count_comments("blog-{}".format(post_id))
|
||||||
|
|
||||||
selected.append(post)
|
selected.append(post)
|
||||||
g.info["count"] += 1
|
g.info["count"] += 1
|
||||||
|
|
186
rophako/modules/comment.py
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Endpoints for the commenting subsystem."""
|
||||||
|
|
||||||
|
from flask import Blueprint, g, request, redirect, url_for, session, flash
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
import rophako.model.user as User
|
||||||
|
import rophako.model.comment as Comment
|
||||||
|
from rophako.utils import template, pretty_time, login_required, sanitize_name
|
||||||
|
from rophako.log import logger
|
||||||
|
from config import *
|
||||||
|
|
||||||
|
mod = Blueprint("comment", __name__, url_prefix="/comments")
|
||||||
|
|
||||||
|
## TODO: emoticon support
|
||||||
|
|
||||||
|
@mod.route("/")
|
||||||
|
def index():
|
||||||
|
return template("blog/index.html")
|
||||||
|
|
||||||
|
|
||||||
|
@mod.route("/preview", methods=["POST"])
|
||||||
|
def preview():
|
||||||
|
# Get the form fields.
|
||||||
|
form = get_comment_form(request.form)
|
||||||
|
|
||||||
|
# Trap fields.
|
||||||
|
trap1 = request.form.get("website", "x") != "http://"
|
||||||
|
trap2 = request.form.get("email", "x") != ""
|
||||||
|
if trap1 or trap2:
|
||||||
|
flash("Wanna try that again?")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
# Validate things.
|
||||||
|
if len(form["message"]) == 0:
|
||||||
|
flash("You must provide a message with your comment.")
|
||||||
|
return redirect(form["url"])
|
||||||
|
|
||||||
|
# Gravatar.
|
||||||
|
g.info["gravatar"] = Comment.gravatar(form.get("contact", ""))
|
||||||
|
g.info["preview"] = Comment.format_message(form.get("message", ""))
|
||||||
|
|
||||||
|
g.info.update(form)
|
||||||
|
return template("comment/preview.html")
|
||||||
|
|
||||||
|
|
||||||
|
@mod.route("/post", methods=["POST"])
|
||||||
|
def post():
|
||||||
|
# Get the form fields.
|
||||||
|
form = get_comment_form(request.form)
|
||||||
|
thread = sanitize_name(form["thread"])
|
||||||
|
|
||||||
|
# Gravatar?
|
||||||
|
gravatar = Comment.gravatar(form.get("contact"))
|
||||||
|
|
||||||
|
# Validate things.
|
||||||
|
if len(form["message"]) == 0:
|
||||||
|
flash("You must provide a message with your comment.")
|
||||||
|
return redirect(form["url"])
|
||||||
|
|
||||||
|
Comment.add_comment(
|
||||||
|
thread=thread,
|
||||||
|
uid=g.info["session"]["uid"],
|
||||||
|
ip=request.remote_addr,
|
||||||
|
time=int(time.time()),
|
||||||
|
image=gravatar,
|
||||||
|
name=form["name"],
|
||||||
|
subject=form["subject"],
|
||||||
|
message=form["message"],
|
||||||
|
url=form["url"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Are we subscribing to the thread?
|
||||||
|
if form.get("subscribe", "false") == "true":
|
||||||
|
email = form.get("contact", "")
|
||||||
|
if "@" in email:
|
||||||
|
Comment.add_subscriber(thread, email)
|
||||||
|
flash("You have been subscribed to future comments on this page.")
|
||||||
|
|
||||||
|
flash("Your comment has been added!")
|
||||||
|
return redirect(form["url"])
|
||||||
|
|
||||||
|
|
||||||
|
@mod.route("/delete/<thread>/<cid>")
|
||||||
|
@login_required
|
||||||
|
def delete(thread, cid):
|
||||||
|
"""Delete a comment."""
|
||||||
|
url = request.args.get("url")
|
||||||
|
Comment.delete_comment(thread, cid)
|
||||||
|
flash("Comment deleted!")
|
||||||
|
return redirect(url or url_for("index"))
|
||||||
|
|
||||||
|
|
||||||
|
@mod.route("/privacy")
|
||||||
|
def privacy():
|
||||||
|
"""The privacy policy and global unsubscribe page."""
|
||||||
|
return template("comment/privacy.html")
|
||||||
|
|
||||||
|
|
||||||
|
@mod.route("/unsubscribe", methods=["GET", "POST"])
|
||||||
|
def unsubscribe():
|
||||||
|
"""Unsubscribe an e-mail from a comment thread (or all threads)."""
|
||||||
|
|
||||||
|
# This endpoint can be called with either method. For the unsubscribe links
|
||||||
|
# inside the e-mails, it uses GET. For the global out-opt, it uses POST.
|
||||||
|
thread, email = None, None
|
||||||
|
if request.method == "POST":
|
||||||
|
thread = request.form.get("thread", "")
|
||||||
|
email = request.form.get("email", "")
|
||||||
|
|
||||||
|
# Spam check.
|
||||||
|
trap1 = request.form.get("url", "x") != "http://"
|
||||||
|
trap2 = request.form.get("message", "x") != ""
|
||||||
|
if trap1 or trap2:
|
||||||
|
flash("Wanna try that again?")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
else:
|
||||||
|
thread = request.args.get("thread", "")
|
||||||
|
email = request.args.get("who", "")
|
||||||
|
|
||||||
|
# Input validation.
|
||||||
|
if not thread:
|
||||||
|
flash("Comment thread not found.")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
if not email:
|
||||||
|
flash("E-mail address not provided.")
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
|
# Do the unsubscribe. If thread is *, this means a global unsubscribe from
|
||||||
|
# all threads.
|
||||||
|
Comment.unsubscribe(thread, email)
|
||||||
|
|
||||||
|
g.info["thread"] = thread
|
||||||
|
g.info["email"] = email
|
||||||
|
return template("comment/unsubscribed.html")
|
||||||
|
|
||||||
|
|
||||||
|
def partial_index(thread, subject, header=True):
|
||||||
|
"""Partial template for including the index view of a comment thread."""
|
||||||
|
|
||||||
|
comments = Comment.get_comments(thread)
|
||||||
|
|
||||||
|
# Sort the comments by most recent on bottom.
|
||||||
|
sorted_cids = [ x for x in sorted(comments, key=lambda y: comments[y]["time"]) ]
|
||||||
|
sorted_comments = []
|
||||||
|
for cid in sorted_cids:
|
||||||
|
comment = comments[cid]
|
||||||
|
comment["id"] = cid
|
||||||
|
|
||||||
|
# Was the commenter logged in?
|
||||||
|
if comment["uid"] > 0:
|
||||||
|
user = User.get_user(uid=comment["uid"])
|
||||||
|
avatar = User.get_picture(uid=comment["uid"])
|
||||||
|
comment["name"] = user["name"]
|
||||||
|
comment["username"] = user["username"]
|
||||||
|
comment["image"] = avatar
|
||||||
|
|
||||||
|
# Add the pretty time.
|
||||||
|
comment["pretty_time"] = pretty_time(COMMENT_TIME_FORMAT, comment["time"])
|
||||||
|
|
||||||
|
# Format the message for display.
|
||||||
|
comment["formatted_message"] = Comment.format_message(comment["message"])
|
||||||
|
|
||||||
|
sorted_comments.append(comment)
|
||||||
|
|
||||||
|
g.info["header"] = header
|
||||||
|
g.info["thread"] = thread
|
||||||
|
g.info["subject"] = subject
|
||||||
|
g.info["url"] = request.url
|
||||||
|
g.info["comments"] = sorted_comments
|
||||||
|
g.info["photo_url"] = PHOTO_ROOT_PUBLIC
|
||||||
|
return template("comment/index.inc.html")
|
||||||
|
|
||||||
|
|
||||||
|
def get_comment_form(form):
|
||||||
|
return dict(
|
||||||
|
thread = request.form.get("thread", ""),
|
||||||
|
url = request.form.get("url", ""),
|
||||||
|
subject = request.form.get("subject", "[No Subject]"),
|
||||||
|
name = request.form.get("name", ""),
|
||||||
|
contact = request.form.get("contact", ""),
|
||||||
|
message = request.form.get("message", ""),
|
||||||
|
subscribe = request.form.get("subscribe", "false"),
|
||||||
|
)
|
30
rophako/modules/emoticons.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Endpoints for the commenting subsystem."""
|
||||||
|
|
||||||
|
from flask import Blueprint, g
|
||||||
|
|
||||||
|
import rophako.model.emoticons as Emoticons
|
||||||
|
from rophako.utils import template
|
||||||
|
from rophako.log import logger
|
||||||
|
from config import *
|
||||||
|
|
||||||
|
mod = Blueprint("emoticons", __name__, url_prefix="/emoticons")
|
||||||
|
|
||||||
|
|
||||||
|
@mod.route("/")
|
||||||
|
def index():
|
||||||
|
"""List the available emoticons."""
|
||||||
|
theme = Emoticons.load_theme()
|
||||||
|
|
||||||
|
smileys = []
|
||||||
|
for img in sorted(theme["map"]):
|
||||||
|
smileys.append({
|
||||||
|
"img": img,
|
||||||
|
"triggers": theme["map"][img],
|
||||||
|
})
|
||||||
|
|
||||||
|
g.info["theme"] = EMOTICON_THEME
|
||||||
|
g.info["theme_name"] = theme["name"]
|
||||||
|
g.info["smileys"] = smileys
|
||||||
|
return template("emoticons/index.html")
|
|
@ -7,8 +7,10 @@ import datetime
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
import importlib
|
import importlib
|
||||||
|
import smtplib
|
||||||
|
|
||||||
from rophako.log import logger
|
from rophako.log import logger
|
||||||
|
from config import *
|
||||||
|
|
||||||
|
|
||||||
def login_required(f):
|
def login_required(f):
|
||||||
|
@ -52,6 +54,29 @@ def template(name, **kwargs):
|
||||||
return html
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
def send_email(to, subject, message, sender=None):
|
||||||
|
"""Send an e-mail out."""
|
||||||
|
if sender is None:
|
||||||
|
sender = MAIL_SENDER
|
||||||
|
|
||||||
|
if type(to) != list:
|
||||||
|
to = [to]
|
||||||
|
|
||||||
|
logger.info("Send email to {}".format(to))
|
||||||
|
if MAIL_METHOD == "smtp":
|
||||||
|
# Send mail with SMTP.
|
||||||
|
for email in to:
|
||||||
|
server = smtplib.SMTP(MAIL_SERVER, MAIL_PORT)
|
||||||
|
server.set_debuglevel(1)
|
||||||
|
msg = """From: {}
|
||||||
|
To: {}
|
||||||
|
Subject: {}
|
||||||
|
|
||||||
|
{}""".format(sender, email, subject, message)
|
||||||
|
server.sendmail(sender, email, msg)
|
||||||
|
server.quit()
|
||||||
|
|
||||||
|
|
||||||
def generate_csrf_token():
|
def generate_csrf_token():
|
||||||
"""Generator for CSRF tokens."""
|
"""Generator for CSRF tokens."""
|
||||||
if "_csrf" not in session:
|
if "_csrf" not in session:
|
||||||
|
@ -82,4 +107,4 @@ def sanitize_name(name):
|
||||||
"""Sanitize a name that may be used in the filesystem.
|
"""Sanitize a name that may be used in the filesystem.
|
||||||
|
|
||||||
Only allows numbers, letters, and some symbols."""
|
Only allows numbers, letters, and some symbols."""
|
||||||
return re.sub(r'[^A-Za-z0-9 .-_]+', '', name)
|
return re.sub(r'[^A-Za-z0-9 .\-_]+', '', name)
|
|
@ -67,5 +67,13 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
]
|
]
|
||||||
</div>
|
</div>
|
||||||
|
<p>
|
||||||
|
|
||||||
|
{% if from != "index" %}
|
||||||
|
{{ include_page("comment.partial_index",
|
||||||
|
thread="blog-"+post["post_id"]|string,
|
||||||
|
subject=post["subject"],
|
||||||
|
) | safe }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endmacro %}
|
{% endmacro %}
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
<strong>Body:</strong><br>
|
<strong>Body:</strong><br>
|
||||||
<textarea cols="80" rows="12" name="body">{{ body }}</textarea><br>
|
<textarea cols="80" rows="12" name="body">{{ body }}</textarea><br>
|
||||||
<a href="/emoticons" target="_blank">Emoticon reference</a> (opens in new window)<p>
|
<a href="{{ url_for('emoticons.index') }}" target="_blank">Emoticon reference</a> (opens in new window)<p>
|
||||||
|
|
||||||
<strong>Avatar:</strong><br>
|
<strong>Avatar:</strong><br>
|
||||||
<span id="avatar-preview"></span>
|
<span id="avatar-preview"></span>
|
||||||
|
|
90
rophako/www/comment/index.inc.html
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
{% if header %}
|
||||||
|
<h1>Comments</h1>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
There {% if comments|length == 1 %}is{% else %}are{% endif %}
|
||||||
|
{{ comments|length }} comment{% if comments|length != 1 %}s{% endif %}
|
||||||
|
on this page.<p>
|
||||||
|
|
||||||
|
{% for comment in comments %}
|
||||||
|
<div class="comment">
|
||||||
|
<div class="comment-author">
|
||||||
|
{% if comment["image"] and (comment["image"].startswith('http:') or comment["image"].startswith('https:')) %}
|
||||||
|
<img src="{{ comment['image'] }}" alt="Avatar" width="96" height="96">
|
||||||
|
{% elif comment["image"] %}
|
||||||
|
<img src="{{ photo_url }}/{{ comment['image'] }}" alt="Avatar" width="96" height="96">
|
||||||
|
{% else %}
|
||||||
|
<img src="/static/avatars/default.png" alt="guest" width="96" height="96">
|
||||||
|
{% endif %}<br>
|
||||||
|
<strong>{% if comment['username'] %}{{ comment['username'] }}{% else %}guest{% endif %}</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<strong>Posted on {{ comment["pretty_time"] }} by {{ comment["name"] }}.</strong><p>
|
||||||
|
|
||||||
|
{{ comment["formatted_message"]|safe }}
|
||||||
|
|
||||||
|
<div class="clear">
|
||||||
|
{% if session["login"] %}
|
||||||
|
[IP: {{ comment["ip"] }} | <a href="{{ url_for('comment.delete', thread=thread, cid=comment['id'], url=url) }}" onclick="return window.confirm('Are you sure?')">Delete</a>]
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div><p>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<h2>Add a Comment</h2>
|
||||||
|
|
||||||
|
<form name="comment" action="{{ url_for('comment.preview') }}" method="POST">
|
||||||
|
<input type="hidden" name="token" value="{{ csrf_token() }}">
|
||||||
|
<input type="hidden" name="thread" value="{{ thread }}">
|
||||||
|
<input type="hidden" name="url" value="{{ url }}">
|
||||||
|
<input type="hidden" name="subject" value="{{ subject }}">
|
||||||
|
<table border="0" cellspacing="2" cellpadding="2">
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="middle">
|
||||||
|
Your name:
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="middle">
|
||||||
|
{% if session["login"] %}
|
||||||
|
<strong>{{ session["name"] }}</strong>
|
||||||
|
{% else %}
|
||||||
|
<input type="text" size="40" name="name">
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="middle">
|
||||||
|
Your Email:
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="middle">
|
||||||
|
<input type="text" size="40" name="contact"> <small>(optional)</small>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
Message:
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="top">
|
||||||
|
<textarea cols="40" rows="8" name="message" style="width: 100%"></textarea><br>
|
||||||
|
<small>You can use <a href="{{ url_for('emoticons.index') }}" target="_blank">emoticons</a>
|
||||||
|
in your comment. <em>(opens in a new window)</em></small>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" align="left" valign="top">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="subscribe" value="true">
|
||||||
|
Notify me of future comments on this page via e-mail
|
||||||
|
(<a href="{{ url_for('comment.privacy') }}" target="_blank">Privacy Policy</a>)
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table><p>
|
||||||
|
|
||||||
|
<div style="display: none">
|
||||||
|
If you can see this, don't touch the following fields.<br>
|
||||||
|
<input type="text" name="website" value="http://"><br>
|
||||||
|
<input type="text" name="email" value="">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit">Leave Comment</button>
|
||||||
|
</form>
|
32
rophako/www/comment/preview.html
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block title %}Comment Preview{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>Comment Preview</h1>
|
||||||
|
|
||||||
|
This is a preview of what your comment is going to look like once posted.<p>
|
||||||
|
|
||||||
|
<hr><p>
|
||||||
|
|
||||||
|
{{ preview|safe }}<p>
|
||||||
|
|
||||||
|
<hr><p>
|
||||||
|
|
||||||
|
{% if subscribe == "true" and contact %}
|
||||||
|
You will be subscribed to future comments on this thread. Notification
|
||||||
|
e-mails will be sent to {{ contact }}.<p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form name="preview" action="{{ url_for('comment.post') }}" method="POST">
|
||||||
|
<input type="hidden" name="token" value="{{ csrf_token() }}">
|
||||||
|
<input type="hidden" name="thread" value="{{ thread }}">
|
||||||
|
<input type="hidden" name="url" value="{{ url }}">
|
||||||
|
<input type="hidden" name="subject" value="{{ subject }}">
|
||||||
|
<input type="hidden" name="name" value="{{ name }}">
|
||||||
|
<input type="hidden" name="message" value="{{ message }}">
|
||||||
|
<input type="hidden" name="contact" value="{{ contact }}">
|
||||||
|
<input type="hidden" name="subscribe" value="{{ subscribe }}">
|
||||||
|
<button type="submit">Publish Comment</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
61
rophako/www/comment/privacy.html
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block title %}Comment Subscriptions{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>Subscribing to Comments</h1>
|
||||||
|
|
||||||
|
When posting a comment on this site, you can optionally subscribe to future
|
||||||
|
comments on the same page (so you can get an e-mail notification when somebody
|
||||||
|
answers your questions, for example).<p>
|
||||||
|
|
||||||
|
You can unsubscribe from these e-mails in the future by clicking a link in the
|
||||||
|
e-mail. Or, you can unsubscribe from all comment threads by entering your
|
||||||
|
e-mail address in the form below.<p>
|
||||||
|
|
||||||
|
<h2>Privacy Policy</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Your e-mail address that you use when you post the comment will only be
|
||||||
|
used for sending you notifications via e-mail when somebody else replies
|
||||||
|
to the comment thread and for showing a
|
||||||
|
<a href="http://www.gravatar.com/" target="_blank">Gravatar</a> next to
|
||||||
|
your comment.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
Your e-mail will not be visible to anybody else on this site.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
Your e-mail won't be given to any spammers so you don't need to worry
|
||||||
|
about junk mail.
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
You can unsubscribe from individual comment threads by using the link
|
||||||
|
provided in the notification e-mail. You can unsubscribe from ALL
|
||||||
|
threads by using the form on this page.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Unsubscribe from All Comment Threads</h2>
|
||||||
|
|
||||||
|
<form name="unsubscribe" action="{{ url_for('comment.unsubscribe') }}" method="POST">
|
||||||
|
<input type="hidden" name="token" value="{{ csrf_token() }}">
|
||||||
|
<input type="hidden" name="thread" value="*">
|
||||||
|
|
||||||
|
Enter the e-mail address to be unsubscribed from all threads:<br>
|
||||||
|
<input type="email" size="40" name="email"><p>
|
||||||
|
|
||||||
|
<button type="submit">Unsubscribe</button>
|
||||||
|
|
||||||
|
<div style="display: none">
|
||||||
|
If you can see this, do not touch these fields.<br>
|
||||||
|
<input type="text" name="url" value="http://"><br>
|
||||||
|
<input type="text" name="message" value="">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
14
rophako/www/comment/unsubscribed.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block title %}Comment Subscriptions{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>You have been unsubscribed</h1>
|
||||||
|
|
||||||
|
The e-mail address <strong>{{ email }}</strong> has been unsubscribed
|
||||||
|
{% if thread == "*" %}
|
||||||
|
from all comment threads on this site.
|
||||||
|
{% else %}
|
||||||
|
from the comment thread "{{ thread }}".
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
30
rophako/www/emoticons/index.html
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block title %}Emoticons{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>Emoticon Theme: {{ theme_name }}</h1>
|
||||||
|
|
||||||
|
<table class="table" cellspacing="0" cellpadding="2">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Emoticon</th>
|
||||||
|
<th>Trigger Text</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for img in smileys %}
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="middle">
|
||||||
|
<img src="/static/smileys/{{ theme }}/{{ img['img'] }}">
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="middle">
|
||||||
|
{% for trigger in img['triggers'] %}
|
||||||
|
{{ trigger }}
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
BIN
rophako/www/static/smileys/tango/act-up.png
Normal file
After Width: | Height: | Size: 934 B |
BIN
rophako/www/static/smileys/tango/airplane.png
Normal file
After Width: | Height: | Size: 868 B |
BIN
rophako/www/static/smileys/tango/alien.png
Normal file
After Width: | Height: | Size: 999 B |
BIN
rophako/www/static/smileys/tango/angel.png
Normal file
After Width: | Height: | Size: 974 B |
BIN
rophako/www/static/smileys/tango/angry.png
Normal file
After Width: | Height: | Size: 942 B |
BIN
rophako/www/static/smileys/tango/arrogant.png
Normal file
After Width: | Height: | Size: 920 B |
BIN
rophako/www/static/smileys/tango/bad.png
Normal file
After Width: | Height: | Size: 858 B |
BIN
rophako/www/static/smileys/tango/bashful.png
Normal file
After Width: | Height: | Size: 920 B |
BIN
rophako/www/static/smileys/tango/beat-up.png
Normal file
After Width: | Height: | Size: 942 B |
BIN
rophako/www/static/smileys/tango/beauty.png
Normal file
After Width: | Height: | Size: 936 B |
BIN
rophako/www/static/smileys/tango/beer.png
Normal file
After Width: | Height: | Size: 936 B |
BIN
rophako/www/static/smileys/tango/blowkiss.png
Normal file
After Width: | Height: | Size: 903 B |
BIN
rophako/www/static/smileys/tango/bomb.png
Normal file
After Width: | Height: | Size: 877 B |
BIN
rophako/www/static/smileys/tango/bowl.png
Normal file
After Width: | Height: | Size: 865 B |
BIN
rophako/www/static/smileys/tango/boy.png
Normal file
After Width: | Height: | Size: 838 B |
BIN
rophako/www/static/smileys/tango/brb.png
Normal file
After Width: | Height: | Size: 757 B |
BIN
rophako/www/static/smileys/tango/bye.png
Normal file
After Width: | Height: | Size: 933 B |
BIN
rophako/www/static/smileys/tango/cake.png
Normal file
After Width: | Height: | Size: 865 B |
BIN
rophako/www/static/smileys/tango/call-me.png
Normal file
After Width: | Height: | Size: 937 B |
BIN
rophako/www/static/smileys/tango/camera.png
Normal file
After Width: | Height: | Size: 777 B |
BIN
rophako/www/static/smileys/tango/can.png
Normal file
After Width: | Height: | Size: 752 B |
BIN
rophako/www/static/smileys/tango/car.png
Normal file
After Width: | Height: | Size: 998 B |
BIN
rophako/www/static/smileys/tango/cat.png
Normal file
After Width: | Height: | Size: 970 B |
BIN
rophako/www/static/smileys/tango/chicken.png
Normal file
After Width: | Height: | Size: 876 B |
BIN
rophako/www/static/smileys/tango/clap.png
Normal file
After Width: | Height: | Size: 916 B |
BIN
rophako/www/static/smileys/tango/clock.png
Normal file
After Width: | Height: | Size: 939 B |
BIN
rophako/www/static/smileys/tango/cloudy.png
Normal file
After Width: | Height: | Size: 810 B |
BIN
rophako/www/static/smileys/tango/clover.png
Normal file
After Width: | Height: | Size: 914 B |
BIN
rophako/www/static/smileys/tango/clown.png
Normal file
After Width: | Height: | Size: 956 B |
BIN
rophako/www/static/smileys/tango/coffee.png
Normal file
After Width: | Height: | Size: 940 B |
BIN
rophako/www/static/smileys/tango/coins.png
Normal file
After Width: | Height: | Size: 905 B |
BIN
rophako/www/static/smileys/tango/computer.png
Normal file
After Width: | Height: | Size: 770 B |
BIN
rophako/www/static/smileys/tango/confused.png
Normal file
After Width: | Height: | Size: 918 B |
BIN
rophako/www/static/smileys/tango/console.png
Normal file
After Width: | Height: | Size: 893 B |
BIN
rophako/www/static/smileys/tango/cow.png
Normal file
After Width: | Height: | Size: 911 B |
BIN
rophako/www/static/smileys/tango/cowboy.png
Normal file
After Width: | Height: | Size: 1006 B |
BIN
rophako/www/static/smileys/tango/crying.png
Normal file
After Width: | Height: | Size: 937 B |
BIN
rophako/www/static/smileys/tango/curl-lip.png
Normal file
After Width: | Height: | Size: 914 B |
BIN
rophako/www/static/smileys/tango/curse.png
Normal file
After Width: | Height: | Size: 915 B |
BIN
rophako/www/static/smileys/tango/cute.png
Normal file
After Width: | Height: | Size: 910 B |
BIN
rophako/www/static/smileys/tango/dance.png
Normal file
After Width: | Height: | Size: 946 B |
BIN
rophako/www/static/smileys/tango/dazed.png
Normal file
After Width: | Height: | Size: 914 B |
BIN
rophako/www/static/smileys/tango/desire.png
Normal file
After Width: | Height: | Size: 959 B |
BIN
rophako/www/static/smileys/tango/devil.png
Normal file
After Width: | Height: | Size: 988 B |
BIN
rophako/www/static/smileys/tango/disapointed.png
Normal file
After Width: | Height: | Size: 886 B |
BIN
rophako/www/static/smileys/tango/disdain.png
Normal file
After Width: | Height: | Size: 939 B |
BIN
rophako/www/static/smileys/tango/doctor.png
Normal file
After Width: | Height: | Size: 961 B |
BIN
rophako/www/static/smileys/tango/dog.png
Normal file
After Width: | Height: | Size: 952 B |
BIN
rophako/www/static/smileys/tango/doh.png
Normal file
After Width: | Height: | Size: 901 B |
BIN
rophako/www/static/smileys/tango/dont-know.png
Normal file
After Width: | Height: | Size: 910 B |
BIN
rophako/www/static/smileys/tango/drink.png
Normal file
After Width: | Height: | Size: 871 B |
BIN
rophako/www/static/smileys/tango/drool.png
Normal file
After Width: | Height: | Size: 925 B |
BIN
rophako/www/static/smileys/tango/eat.png
Normal file
After Width: | Height: | Size: 931 B |
BIN
rophako/www/static/smileys/tango/embarrassed.png
Normal file
After Width: | Height: | Size: 909 B |
328
rophako/www/static/smileys/tango/emoticons.json
Normal file
|
@ -0,0 +1,328 @@
|
||||||
|
{
|
||||||
|
"map" : {
|
||||||
|
"neutral.png" : [
|
||||||
|
":-|",
|
||||||
|
":|"
|
||||||
|
],
|
||||||
|
"rain.png" : [
|
||||||
|
"(st)"
|
||||||
|
],
|
||||||
|
"mail.png" : [
|
||||||
|
"(E)",
|
||||||
|
"(e)"
|
||||||
|
],
|
||||||
|
"tongue.png" : [
|
||||||
|
":-P",
|
||||||
|
":P",
|
||||||
|
":-p",
|
||||||
|
":p"
|
||||||
|
],
|
||||||
|
"computer.png" : [
|
||||||
|
"(co)"
|
||||||
|
],
|
||||||
|
"love-over.png" : [
|
||||||
|
"(U)",
|
||||||
|
"(u)"
|
||||||
|
],
|
||||||
|
"good.png" : [
|
||||||
|
"(Y)",
|
||||||
|
"(y)"
|
||||||
|
],
|
||||||
|
"in-love.png" : [
|
||||||
|
"*IN",
|
||||||
|
"LOVE*"
|
||||||
|
],
|
||||||
|
"coffee.png" : [
|
||||||
|
"(C)",
|
||||||
|
"(c)"
|
||||||
|
],
|
||||||
|
"secret.png" : [
|
||||||
|
":-X",
|
||||||
|
":-x"
|
||||||
|
],
|
||||||
|
"wilt.png" : [
|
||||||
|
"|-0"
|
||||||
|
],
|
||||||
|
"yin-yang.png" : [
|
||||||
|
"(%)"
|
||||||
|
],
|
||||||
|
"yawn.png" : [
|
||||||
|
"|-)"
|
||||||
|
],
|
||||||
|
"glasses-nerdy.png" : [
|
||||||
|
"8-|"
|
||||||
|
],
|
||||||
|
"laugh.png" : [
|
||||||
|
":-D",
|
||||||
|
":D",
|
||||||
|
":d",
|
||||||
|
":-d"
|
||||||
|
],
|
||||||
|
"skywalker.png" : [
|
||||||
|
"C:-)",
|
||||||
|
"c:-)",
|
||||||
|
"C:)",
|
||||||
|
"c:)"
|
||||||
|
],
|
||||||
|
"car.png" : [
|
||||||
|
"(au)"
|
||||||
|
],
|
||||||
|
"pizza.png" : [
|
||||||
|
"(pi)"
|
||||||
|
],
|
||||||
|
"thunder.png" : [
|
||||||
|
"(li)"
|
||||||
|
],
|
||||||
|
"teeth.png" : [
|
||||||
|
"8o|"
|
||||||
|
],
|
||||||
|
"moon.png" : [
|
||||||
|
"(S)"
|
||||||
|
],
|
||||||
|
"smile-big.png" : [
|
||||||
|
":-))",
|
||||||
|
":))"
|
||||||
|
],
|
||||||
|
"sarcastic.png" : [
|
||||||
|
"^o)"
|
||||||
|
],
|
||||||
|
"confused.png" : [
|
||||||
|
":-S",
|
||||||
|
":S",
|
||||||
|
":s",
|
||||||
|
":-s"
|
||||||
|
],
|
||||||
|
"film.png" : [
|
||||||
|
"(~)"
|
||||||
|
],
|
||||||
|
"party.png" : [
|
||||||
|
"<:o)"
|
||||||
|
],
|
||||||
|
"turtle.png" : [
|
||||||
|
"(tu)"
|
||||||
|
],
|
||||||
|
"beer.png" : [
|
||||||
|
"(B)",
|
||||||
|
"(b)"
|
||||||
|
],
|
||||||
|
"clock.png" : [
|
||||||
|
"(O)",
|
||||||
|
"(o)"
|
||||||
|
],
|
||||||
|
"plate.png" : [
|
||||||
|
"(pl)"
|
||||||
|
],
|
||||||
|
"highfive.png" : [
|
||||||
|
"(h5)"
|
||||||
|
],
|
||||||
|
"angry.png" : [
|
||||||
|
":-@",
|
||||||
|
":@",
|
||||||
|
">:o",
|
||||||
|
">:O"
|
||||||
|
],
|
||||||
|
"rose.png" : [
|
||||||
|
"(F)",
|
||||||
|
"(f)"
|
||||||
|
],
|
||||||
|
"sick.png" : [
|
||||||
|
":-!",
|
||||||
|
":!",
|
||||||
|
"+o(",
|
||||||
|
"+O("
|
||||||
|
],
|
||||||
|
"victory.png" : [
|
||||||
|
"*BRAVO*",
|
||||||
|
":BRAVO:",
|
||||||
|
":bravo:",
|
||||||
|
":clapping:"
|
||||||
|
],
|
||||||
|
"angel.png" : [
|
||||||
|
"O:-)",
|
||||||
|
"O:)",
|
||||||
|
"(A)",
|
||||||
|
"(a)"
|
||||||
|
],
|
||||||
|
"thinking.png" : [
|
||||||
|
"*-)"
|
||||||
|
],
|
||||||
|
"island.png" : [
|
||||||
|
"(ip)"
|
||||||
|
],
|
||||||
|
"smile.png" : [
|
||||||
|
":-)",
|
||||||
|
":)"
|
||||||
|
],
|
||||||
|
"rose-dead.png" : [
|
||||||
|
"(W)",
|
||||||
|
"(w)"
|
||||||
|
],
|
||||||
|
"msn.png" : [
|
||||||
|
"(M)",
|
||||||
|
"(m)"
|
||||||
|
],
|
||||||
|
"umbrella.png" : [
|
||||||
|
"(um)"
|
||||||
|
],
|
||||||
|
"hug-left.png" : [
|
||||||
|
"({)"
|
||||||
|
],
|
||||||
|
"shock.png" : [
|
||||||
|
":-O",
|
||||||
|
":-o",
|
||||||
|
":O",
|
||||||
|
":o",
|
||||||
|
"=-O",
|
||||||
|
"=-o"
|
||||||
|
],
|
||||||
|
"rotfl.png" : [
|
||||||
|
"*ROFL*",
|
||||||
|
"*rofl*"
|
||||||
|
],
|
||||||
|
"airplane.png" : [
|
||||||
|
"(ap)"
|
||||||
|
],
|
||||||
|
"sun.png" : [
|
||||||
|
"(#)"
|
||||||
|
],
|
||||||
|
"goat.png" : [
|
||||||
|
"(nah)"
|
||||||
|
],
|
||||||
|
"crying.png" : [
|
||||||
|
":'("
|
||||||
|
],
|
||||||
|
"sad.png" : [
|
||||||
|
":-(",
|
||||||
|
":("
|
||||||
|
],
|
||||||
|
"cat.png" : [
|
||||||
|
"(@)"
|
||||||
|
],
|
||||||
|
"bomb.png" : [
|
||||||
|
"@="
|
||||||
|
],
|
||||||
|
"glasses-cool.png" : [
|
||||||
|
"(H)",
|
||||||
|
"(h)"
|
||||||
|
],
|
||||||
|
"hypnotized.png" : [
|
||||||
|
"%)",
|
||||||
|
"%-)"
|
||||||
|
],
|
||||||
|
"girl.png" : [
|
||||||
|
"(X)",
|
||||||
|
"(x)"
|
||||||
|
],
|
||||||
|
"bowl.png" : [
|
||||||
|
"(||)"
|
||||||
|
],
|
||||||
|
"bad.png" : [
|
||||||
|
"(N)",
|
||||||
|
"(n)"
|
||||||
|
],
|
||||||
|
"mobile.png" : [
|
||||||
|
"(mp)"
|
||||||
|
],
|
||||||
|
"monkey.png" : [
|
||||||
|
":-(|)"
|
||||||
|
],
|
||||||
|
"love.png" : [
|
||||||
|
"(L)",
|
||||||
|
"(l)"
|
||||||
|
],
|
||||||
|
"phone.png" : [
|
||||||
|
"(T)",
|
||||||
|
"(t)"
|
||||||
|
],
|
||||||
|
"quiet.png" : [
|
||||||
|
":-#"
|
||||||
|
],
|
||||||
|
"eyeroll.png" : [
|
||||||
|
"8-)",
|
||||||
|
"8)"
|
||||||
|
],
|
||||||
|
"vampire.png" : [
|
||||||
|
":-[",
|
||||||
|
":["
|
||||||
|
],
|
||||||
|
"dog.png" : [
|
||||||
|
"(&)"
|
||||||
|
],
|
||||||
|
"camera.png" : [
|
||||||
|
"(P)",
|
||||||
|
"(p)"
|
||||||
|
],
|
||||||
|
"dance.png" : [
|
||||||
|
"*DANCE*",
|
||||||
|
":dance:"
|
||||||
|
],
|
||||||
|
"snail.png" : [
|
||||||
|
"(sn)"
|
||||||
|
],
|
||||||
|
"soccerball.png" : [
|
||||||
|
"(so)"
|
||||||
|
],
|
||||||
|
"star.png" : [
|
||||||
|
"(*)"
|
||||||
|
],
|
||||||
|
"lamp.png" : [
|
||||||
|
"(I)",
|
||||||
|
"(i)"
|
||||||
|
],
|
||||||
|
"sheep.png" : [
|
||||||
|
"(bah)"
|
||||||
|
],
|
||||||
|
"boy.png" : [
|
||||||
|
"(Z)",
|
||||||
|
"(z)"
|
||||||
|
],
|
||||||
|
"dont-know.png" : [
|
||||||
|
":^)"
|
||||||
|
],
|
||||||
|
"rainbow.png" : [
|
||||||
|
"(R)",
|
||||||
|
"(r)"
|
||||||
|
],
|
||||||
|
"coins.png" : [
|
||||||
|
"(mo)"
|
||||||
|
],
|
||||||
|
"hug-right.png" : [
|
||||||
|
"(})"
|
||||||
|
],
|
||||||
|
"embarrassed.png" : [
|
||||||
|
":-$",
|
||||||
|
":$"
|
||||||
|
],
|
||||||
|
"devil.png" : [
|
||||||
|
"(6)"
|
||||||
|
],
|
||||||
|
"kiss.png" : [
|
||||||
|
":-*",
|
||||||
|
"(K)",
|
||||||
|
"(k)"
|
||||||
|
],
|
||||||
|
"brb.png" : [
|
||||||
|
"(brb)"
|
||||||
|
],
|
||||||
|
"present.png" : [
|
||||||
|
"(G)",
|
||||||
|
"(g)"
|
||||||
|
],
|
||||||
|
"cake.png" : [
|
||||||
|
"(^)"
|
||||||
|
],
|
||||||
|
"drink.png" : [
|
||||||
|
"(D)",
|
||||||
|
"(d)"
|
||||||
|
],
|
||||||
|
"musical-note.png" : [
|
||||||
|
"(8)"
|
||||||
|
],
|
||||||
|
"wink.png" : [
|
||||||
|
";-)",
|
||||||
|
";)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"source" : "http://digsbies.org/site/content/project/tango-emoticons-big-pack",
|
||||||
|
"name" : "Tango"
|
||||||
|
}
|
86
rophako/www/static/smileys/tango/emoticons.txt
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
smile.png :-) :) +) =) :smile:
|
||||||
|
smile-big.png *LOL* :-)) :)) =)) +)) :-))) :))) LOL lol LOL! lol! :lol:
|
||||||
|
laugh.png :-D :D :d :-d +D =D :biggrin:
|
||||||
|
wink.png ;-) ;) ^_~ :wink:
|
||||||
|
shock.png :-O :-o :O :o
|
||||||
|
tongue.png :-P :P :-p :p +P =P +p =p :-b :b +b =b :tongue:
|
||||||
|
glasses-cool.png (H) (h)
|
||||||
|
angry.png :-@ :@ >:o >:O >+O >:o >+o :angry:
|
||||||
|
embarrassed.png :-$ :$
|
||||||
|
confused.png :-S :S :s :-s
|
||||||
|
sad.png :-( :( +( =( :-(( :(( +(( =(( :sad:
|
||||||
|
crying.png :'(
|
||||||
|
neutral.png :-| :|
|
||||||
|
devil.png ]:-> }:-> ]:> }:> >:-] >:] (6) :diablo: *DIABLO*
|
||||||
|
angel.png O:-) O:) O+) O=) 0:-) 0:) 0+) 0=) (A) (a)
|
||||||
|
love.png (L) (l)
|
||||||
|
love-over.png (U) (u)
|
||||||
|
msn.png (M) (m)
|
||||||
|
cat.png (@)
|
||||||
|
dog.png (&)
|
||||||
|
moon.png (S)
|
||||||
|
star.png (*)
|
||||||
|
film.png (~)
|
||||||
|
musical-note.png (8)
|
||||||
|
mail.png (E) (e)
|
||||||
|
rose.png @}->-- @}-:-- @>}--,-`--- (F) (f)
|
||||||
|
rose-dead.png (W) (w)
|
||||||
|
clock.png (O) (o)
|
||||||
|
kiss.png :-* (K) (k)
|
||||||
|
secret.png :-X
|
||||||
|
present.png (G) (g)
|
||||||
|
cake.png (^)
|
||||||
|
camera.png (P) (p)
|
||||||
|
lamp.png (I) (i)
|
||||||
|
coffee.png (C) (c)
|
||||||
|
phone.png (T) (t)
|
||||||
|
hug-left.png ({)
|
||||||
|
hug-right.png (})
|
||||||
|
beer.png *DRINK* DRINK :drink: (B) (b)
|
||||||
|
drink.png (D) (d)
|
||||||
|
boy.png (Z) (z)
|
||||||
|
girl.png (X) (x)
|
||||||
|
good.png *THUMBS UP* (Y) (y)
|
||||||
|
bad.png (N) (n)
|
||||||
|
vampire.png :-[ :[ ;'> ;-. :blush:
|
||||||
|
goat.png (nah)
|
||||||
|
sun.png (#)
|
||||||
|
rainbow.png (R) (r)
|
||||||
|
quiet.png :-#
|
||||||
|
teeth.png 8o|
|
||||||
|
glasses-nerdy.png 8-|
|
||||||
|
sarcastic.png ^o)
|
||||||
|
sick.png :-! :! +o( :-~ ;-~ :(~ +(~ =(~ :bad:
|
||||||
|
snail.png (sn)
|
||||||
|
turtle.png (tu)
|
||||||
|
plate.png (pl)
|
||||||
|
bowl.png (||)
|
||||||
|
pizza.png (pi)
|
||||||
|
soccerball.png (so)
|
||||||
|
car.png (au)
|
||||||
|
airplane.png (ap)
|
||||||
|
umbrella.png (um)
|
||||||
|
island.png (ip)
|
||||||
|
computer.png (co)
|
||||||
|
mobile.png (mp)
|
||||||
|
brb.png (brb)
|
||||||
|
rain.png (st)
|
||||||
|
highfive.png (h5)
|
||||||
|
coins.png (mo)
|
||||||
|
sheep.png (bah)
|
||||||
|
dont-know.png :^)
|
||||||
|
thinking.png *-)
|
||||||
|
thunder.png (li)
|
||||||
|
party.png <:o)
|
||||||
|
eyeroll.png 8-) 8) B) :COOL: :cool: COOL cool COOL! COOL!! COOL!!!
|
||||||
|
yawn.png |-)
|
||||||
|
skywalker.png C:-) c:-) C:) c:)
|
||||||
|
monkey.png :-(|)
|
||||||
|
yin-yang.png (%)
|
||||||
|
wilt.png *TIRED* |-0 :boredom:
|
||||||
|
bomb.png @=
|
||||||
|
hypnotized.png %) %-) :-$ :$ :wacko: :WACKO:
|
||||||
|
rotfl.png *ROFL* :ROFL: :rofl: ROFL ROFL! rofl :-)))) :-))))) :-)))))) :)))) :))))) :)))))) =)))) =))))) =))))))
|
||||||
|
victory.png *BRAVO* :BRAVO: :bravo: :clapping:
|
||||||
|
dance.png *DANCE* :dance:
|
||||||
|
in-love.png *IN LOVE*
|
BIN
rophako/www/static/smileys/tango/excruciating.png
Normal file
After Width: | Height: | Size: 977 B |
BIN
rophako/www/static/smileys/tango/eyeroll.png
Normal file
After Width: | Height: | Size: 911 B |
BIN
rophako/www/static/smileys/tango/film.png
Normal file
After Width: | Height: | Size: 945 B |
BIN
rophako/www/static/smileys/tango/fingers-crossed.png
Normal file
After Width: | Height: | Size: 955 B |
BIN
rophako/www/static/smileys/tango/flag.png
Normal file
After Width: | Height: | Size: 624 B |
BIN
rophako/www/static/smileys/tango/foot-in-mouth.png
Normal file
After Width: | Height: | Size: 929 B |
BIN
rophako/www/static/smileys/tango/freaked-out.png
Normal file
After Width: | Height: | Size: 901 B |
BIN
rophako/www/static/smileys/tango/ghost.png
Normal file
After Width: | Height: | Size: 879 B |
BIN
rophako/www/static/smileys/tango/giggle.png
Normal file
After Width: | Height: | Size: 909 B |
BIN
rophako/www/static/smileys/tango/girl.png
Normal file
After Width: | Height: | Size: 741 B |
BIN
rophako/www/static/smileys/tango/glasses-cool.png
Normal file
After Width: | Height: | Size: 938 B |
BIN
rophako/www/static/smileys/tango/glasses-nerdy.png
Normal file
After Width: | Height: | Size: 987 B |
BIN
rophako/www/static/smileys/tango/go-away.png
Normal file
After Width: | Height: | Size: 918 B |
BIN
rophako/www/static/smileys/tango/goat.png
Normal file
After Width: | Height: | Size: 921 B |
BIN
rophako/www/static/smileys/tango/good.png
Normal file
After Width: | Height: | Size: 804 B |
BIN
rophako/www/static/smileys/tango/hammer.png
Normal file
After Width: | Height: | Size: 832 B |
BIN
rophako/www/static/smileys/tango/handcuffs.png
Normal file
After Width: | Height: | Size: 969 B |
BIN
rophako/www/static/smileys/tango/handshake.png
Normal file
After Width: | Height: | Size: 954 B |
BIN
rophako/www/static/smileys/tango/highfive.png
Normal file
After Width: | Height: | Size: 998 B |
BIN
rophako/www/static/smileys/tango/hug-left.png
Normal file
After Width: | Height: | Size: 945 B |
BIN
rophako/www/static/smileys/tango/hug-right.png
Normal file
After Width: | Height: | Size: 943 B |
BIN
rophako/www/static/smileys/tango/hungry.png
Normal file
After Width: | Height: | Size: 931 B |
BIN
rophako/www/static/smileys/tango/hypnotized.png
Normal file
After Width: | Height: | Size: 923 B |
BIN
rophako/www/static/smileys/tango/in-love.png
Normal file
After Width: | Height: | Size: 951 B |
BIN
rophako/www/static/smileys/tango/island.png
Normal file
After Width: | Height: | Size: 944 B |
BIN
rophako/www/static/smileys/tango/jump.png
Normal file
After Width: | Height: | Size: 922 B |
BIN
rophako/www/static/smileys/tango/kiss.png
Normal file
After Width: | Height: | Size: 921 B |
BIN
rophako/www/static/smileys/tango/knife.png
Normal file
After Width: | Height: | Size: 853 B |