A Python content management system designed for kirsle.net featuring a blog, comments and photo albums. https://rophako.kirsle.net/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

comment.py 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. """Commenting models."""
  4. from flask import url_for
  5. import time
  6. import hashlib
  7. import urllib
  8. import random
  9. import re
  10. import sys
  11. from rophako.settings import Config
  12. import rophako.jsondb as JsonDB
  13. import rophako.model.user as User
  14. import rophako.model.emoticons as Emoticons
  15. from rophako.utils import send_email, render_markdown
  16. from rophako.log import logger
  17. def add_comment(thread, uid, name, subject, message, url, time, ip, image=None):
  18. """Add a comment to a comment thread.
  19. * uid is 0 if it's a guest post, otherwise the UID of the user.
  20. * name is the commenter's name (if a guest)
  21. * subject is for the e-mails that are sent out
  22. * message is self explanatory.
  23. * url is the URL where the comment can be read.
  24. * time, epoch time of comment.
  25. * ip is the IP address of the commenter.
  26. * image is a Gravatar image URL etc.
  27. """
  28. # Get the comments for this thread.
  29. comments = get_comments(thread)
  30. # Make up a unique ID for the comment.
  31. cid = random_hash()
  32. while cid in comments:
  33. cid = random_hash()
  34. # Add the comment.
  35. comments[cid] = dict(
  36. uid=uid,
  37. name=name or "Anonymous",
  38. image=image or "",
  39. message=message,
  40. time=time or int(time.time()),
  41. ip=ip,
  42. )
  43. write_comments(thread, comments)
  44. # Get info about the commenter.
  45. if uid > 0:
  46. user = User.get_user(uid=uid)
  47. if user:
  48. name = user["name"]
  49. # Send the e-mail to the site admins.
  50. send_email(
  51. to=Config.site.notify_address,
  52. subject="New comment: {}".format(subject),
  53. message="""{name} has left a comment on: {subject}
  54. {message}
  55. To view this comment, please go to {url}
  56. =====================
  57. This e-mail was automatically generated. Do not reply to it.""".format(
  58. name=name,
  59. subject=subject,
  60. message=message,
  61. url=url,
  62. ),
  63. )
  64. # Notify any subscribers.
  65. subs = get_subscribers(thread)
  66. for sub in subs.keys():
  67. # Make the unsubscribe link.
  68. unsub = url_for("comment.unsubscribe", thread=thread, who=sub, _external=True)
  69. send_email(
  70. to=sub,
  71. subject="New Comment: {}".format(subject),
  72. message="""Hello,
  73. You are currently subscribed to the comment thread '{thread}', and somebody has
  74. just added a new comment!
  75. {name} has left a comment on: {subject}
  76. {message}
  77. To view this comment, please go to {url}
  78. =====================
  79. This e-mail was automatically generated. Do not reply to it.
  80. If you wish to unsubscribe from this comment thread, please visit the following
  81. URL: {unsub}""".format(
  82. thread=thread,
  83. name=name,
  84. subject=subject,
  85. message=message,
  86. url=url,
  87. unsub=unsub,
  88. )
  89. )
  90. def get_comment(thread, cid):
  91. """Look up a specific comment."""
  92. comments = get_comments(thread)
  93. return comments.get(cid, None)
  94. def update_comment(thread, cid, data):
  95. """Update the data for a comment."""
  96. comments = get_comments(thread)
  97. if cid in comments:
  98. comments[cid].update(data)
  99. write_comments(thread, comments)
  100. def delete_comment(thread, cid):
  101. """Delete a comment from a thread."""
  102. comments = get_comments(thread)
  103. del comments[cid]
  104. write_comments(thread, comments)
  105. def count_comments(thread):
  106. """Count the comments on a thread."""
  107. comments = get_comments(thread)
  108. return len(comments.keys())
  109. def add_subscriber(thread, email):
  110. """Add a subscriber to a thread."""
  111. if not "@" in email:
  112. return
  113. # Sanity check: only subscribe to threads that exist.
  114. if not JsonDB.exists("comments/threads/{}".format(thread)):
  115. return
  116. logger.info("Subscribe e-mail {} to thread {}".format(email, thread))
  117. subs = get_subscribers(thread)
  118. subs[email] = int(time.time())
  119. write_subscribers(thread, subs)
  120. def unsubscribe(thread, email):
  121. """Unsubscribe an e-mail address from a thread.
  122. If `thread` is `*`, the e-mail is unsubscribed from all threads."""
  123. # Which threads to unsubscribe from?
  124. threads = []
  125. if thread == "*":
  126. threads = JsonDB.list_docs("comments/subscribers")
  127. else:
  128. threads = [thread]
  129. # Remove them as a subscriber.
  130. for thread in threads:
  131. if JsonDB.exists("comments/subscribers/{}".format(thread)):
  132. logger.info("Unsubscribe e-mail address {} from comment thread {}".format(email, thread))
  133. db = get_subscribers(thread)
  134. del db[email]
  135. write_subscribers(thread, db)
  136. def format_message(message):
  137. """HTML sanitize the message and format it for display."""
  138. # Comments use Markdown formatting, and HTML tags are escaped by default.
  139. message = render_markdown(message)
  140. # Process emoticons.
  141. message = Emoticons.render(message)
  142. return message
  143. def get_comments(thread):
  144. """Get the comment thread."""
  145. doc = "comments/threads/{}".format(thread)
  146. if JsonDB.exists(doc):
  147. return JsonDB.get(doc)
  148. return {}
  149. def write_comments(thread, comments):
  150. """Save the comments DB."""
  151. if len(comments.keys()) == 0:
  152. return JsonDB.delete("comments/threads/{}".format(thread))
  153. return JsonDB.commit("comments/threads/{}".format(thread), comments)
  154. def get_subscribers(thread):
  155. """Get the subscribers to a comment thread."""
  156. doc = "comments/subscribers/{}".format(thread)
  157. if JsonDB.exists(doc):
  158. return JsonDB.get(doc)
  159. return {}
  160. def write_subscribers(thread, subs):
  161. """Save the subscribers to the DB."""
  162. if len(subs.keys()) == 0:
  163. return JsonDB.delete("comments/subscribers/{}".format(thread))
  164. return JsonDB.commit("comments/subscribers/{}".format(thread), subs)
  165. def random_hash():
  166. """Get a short random hash to use as the ID for a comment."""
  167. md5 = hashlib.md5()
  168. md5.update(str(random.randint(0, 1000000)).encode("utf-8"))
  169. return md5.hexdigest()
  170. def gravatar(email):
  171. """Generate a Gravatar link for an email address."""
  172. if "@" in email:
  173. # Default avatar?
  174. default = Config.comment.default_avatar
  175. # Construct the URL.
  176. params = {
  177. "s": "96", # size
  178. }
  179. if default:
  180. params["d"] = default
  181. url = "//www.gravatar.com/avatar/" + hashlib.md5(email.lower().encode("utf-8")).hexdigest() + "?"
  182. # URL encode the params, the Python 2 & Python 3 way.
  183. if sys.version_info[0] < 3:
  184. url += urllib.urlencode(params)
  185. else:
  186. url += urllib.parse.urlencode(params)
  187. return url
  188. return ""