diff --git a/isso/db/comments.py b/isso/db/comments.py index 924a028..c23ad3c 100644 --- a/isso/db/comments.py +++ b/isso/db/comments.py @@ -81,6 +81,15 @@ class Comments: ' mode=1', 'WHERE id=? AND mode=2'], (id, )) + def unsubscribe(self, id): + """ + Turn off email notifications for replies to this comment. + """ + self.db.execute([ + 'UPDATE comments SET', + ' notification=0', + 'WHERE id=?'], (id, )) + def update(self, id, data): """ Update comment :param:`id` with values from :param:`data` and return diff --git a/isso/ext/notifications.py b/isso/ext/notifications.py index a075d80..1efd451 100644 --- a/isso/ext/notifications.py +++ b/isso/ext/notifications.py @@ -93,7 +93,7 @@ class SMTP(object): def __iter__(self): yield "comments.new:after-save", self.notify - def format(self, thread, comment, admin=False): + def format(self, thread, comment, comment_parent, admin=False): rv = io.StringIO() @@ -115,17 +115,23 @@ class SMTP(object): rv.write("Link to comment: %s\n" % (local("origin") + thread["uri"] + "#isso-%i" % comment["id"])) rv.write("\n") + rv.write("---\n") if admin: uri = local("host") + "/id/%i" % comment["id"] key = self.isso.sign(comment["id"]) - rv.write("---\n") rv.write("Delete comment: %s\n" % (uri + "/delete/" + key)) if comment["mode"] == 2: rv.write("Activate comment: %s\n" % (uri + "/activate/" + key)) + else: + uri = local("host") + "/id/%i" % comment_parent["id"] + key = self.isso.sign(('unsubscribe', comment_parent["id"])) + + rv.write("Unsubscribe from this conversation: %s\n" % (uri + "/unsubscribe/" + key)) + rv.seek(0) return rv.read() @@ -134,11 +140,11 @@ class SMTP(object): comment_parent = self.isso.db.comments.get(comment["parent"]) # Notify the author that a new comment is posted if requested if comment_parent and "email" in comment_parent and comment_parent["notification"]: - body = self.format(thread, comment, admin=False) + body = self.format(thread, comment, comment_parent, admin=False) subject = "Re: New comment posted on %s" % thread["title"] self.sendmail(subject, body, thread, comment, to=comment_parent["email"]) - body = self.format(thread, comment, admin=True) + body = self.format(thread, comment, None, admin=True) self.sendmail(thread["title"], body, thread, comment) def sendmail(self, subject, body, thread, comment, to=None): diff --git a/isso/views/comments.py b/isso/views/comments.py index c95026e..9096925 100644 --- a/isso/views/comments.py +++ b/isso/views/comments.py @@ -87,21 +87,22 @@ class API(object): ACCEPT = set(['text', 'author', 'website', 'email', 'parent', 'title', 'notification']) VIEWS = [ - ('fetch', ('GET', '/')), - ('new', ('POST', '/new')), - ('count', ('GET', '/count')), - ('counts', ('POST', '/count')), - ('view', ('GET', '/id/')), - ('edit', ('PUT', '/id/')), - ('delete', ('DELETE', '/id/')), - ('moderate', ('GET', '/id///')), - ('moderate', ('POST', '/id///')), - ('like', ('POST', '/id//like')), - ('dislike', ('POST', '/id//dislike')), - ('demo', ('GET', '/demo')), - ('preview', ('POST', '/preview')), - ('login', ('POST', '/login')), - ('admin', ('GET', '/admin')) + ('fetch', ('GET', '/')), + ('new', ('POST', '/new')), + ('count', ('GET', '/count')), + ('counts', ('POST', '/count')), + ('view', ('GET', '/id/')), + ('edit', ('PUT', '/id/')), + ('delete', ('DELETE', '/id/')), + ('unsubscribe', ('GET', '/id//unsubscribe/')), + ('moderate', ('GET', '/id///')), + ('moderate', ('POST', '/id///')), + ('like', ('POST', '/id//like')), + ('dislike', ('POST', '/id//dislike')), + ('demo', ('GET', '/demo')), + ('preview', ('POST', '/preview')), + ('login', ('POST', '/login')), + ('admin', ('GET', '/admin')) ] def __init__(self, isso, hasher): @@ -477,6 +478,66 @@ class API(object): resp.headers.add("X-Set-Cookie", cookie("isso-%i" % id)) return resp + """ + @api {get} /id/:id/key unsubscribe + @apiGroup Comment + @apiDescription + Opt out from getting any further email notifications about replies to a particular comment. In order to use this endpoint, the requestor needs a `key` that is usually obtained from an email sent out by isso. + + @apiParam {number} id + The id of the comment to unsubscribe from replies to. + @apiParam {string} key + The key to authenticate the subscriber. + + @apiExample {curl} Unsubscribe from replies to comment with id 13: + curl -X GET 'https://comments.example.com/id/13/unsubscribe/TODO-COMPUTE-HASH' + + @apiSuccessExample {html} Using GET: + <!DOCTYPE html> + <html> + <head> + <script> + if (confirm('Delete: Are you sure?')) { + xhr = new XMLHttpRequest; + xhr.open('POST', window.location.href); + xhr.send(null); + } + </script> + + @apiSuccessExample Using POST: + Yo + """ + + def unsubscribe(self, environ, request, id, key): + try: + rv = self.isso.unsign(key, max_age=2**32) + except (BadSignature, SignatureExpired): + raise Forbidden + + if rv[0] != 'unsubscribe' or rv[1] != id: + raise Forbidden + + item = self.comments.get(id) + + if item is None: + raise NotFound + + with self.isso.lock: + self.comments.unsubscribe(id) + + modal = ( + "" + "" + "" + " Successfully unsubscribed" + "" + "" + "

You have been unsubscribed from replies in the given conversation.

" + "" + "") + + return Response(modal, 200, content_type="text/html") + """ @api {post} /id/:id/:action/key moderate @apiGroup Comment