Formatted emails

This commit is contained in:
Yuriy Glukhov 2016-08-09 18:32:27 +03:00
commit 82ce6a5ffc
2 changed files with 88 additions and 75 deletions

View file

@ -8,8 +8,8 @@
import import
os, strutils, times, md5, strtabs, cgi, math, db_sqlite, matchers, os, strutils, times, md5, strtabs, cgi, math, db_sqlite, matchers,
rst, rstgen, captchas, scgi, jester, asyncdispatch, asyncnet, cache, sequtils, captchas, scgi, jester, asyncdispatch, asyncnet, cache, sequtils,
parseutils, utils, htmlparser, xmltree, streams, random parseutils, utils, random, rst
when not defined(windows): when not defined(windows):
import bcrypt # TODO import bcrypt # TODO
@ -75,7 +75,6 @@ type
var var
db: TDbConn db: TDbConn
docConfig: StringTableRef
isFTSAvailable: bool isFTSAvailable: bool
config: Config config: Config
@ -436,70 +435,6 @@ proc isPreview(c: TForumData): bool =
proc isDelete(c: TForumData): bool = proc isDelete(c: TForumData): bool =
result = c.req.params.hasKey("delete") result = c.req.params.hasKey("delete")
proc processGT(n: XmlNode, tag: string): (int, XmlNode, string) =
result = (0, newElement(tag), tag)
if n.kind == xnElement and len(n) == 1 and n[0].kind == xnElement:
return processGT(n[0], if n[0].kind == xnElement: n[0].tag else: tag)
var countGT = true
for c in items(n):
case c.kind
of xnText:
if c.text == ">" and countGT:
result[0].inc()
else:
countGT = false
result[1].add(newText(c.text))
else:
result[1].add(c)
proc blockquoteFinish(currentBlockquote, newNode: var XmlNode, n: XmlNode) =
if currentBlockquote.len > 0:
#echo(currentBlockquote.repr)
newNode.add(currentBlockquote)
currentBlockquote = newElement("blockquote")
newNode.add(n)
proc rstToHtml(content: string): string =
result = rstgen.rstToHtml(content, {roSupportSmilies, roSupportMarkdown},
docConfig)
# Bolt on quotes.
# TODO: Yes, this is ugly. I wrote it quickly. PRs welcome ;)
try:
var node = parseHtml(newStringStream(result))
var newNode = newElement("div")
if node.kind == xnElement:
var currentBlockquote = newElement("blockquote")
for n in items(node):
case n.kind
of xnElement:
case n.tag
of "p":
let (nesting, contentNode, tag) = processGT(n, "p")
if nesting > 0:
var bq = currentBlockquote
for i in 1 .. <nesting:
var newBq = bq.child("blockquote")
if newBq.isNil:
newBq = newElement("blockquote")
bq.add(newBq)
bq = newBq
bq.insert(contentNode, if bq.len == 0: 0 else: bq.len)
else:
blockquoteFinish(currentBlockquote, newNode, n)
else:
blockquoteFinish(currentBlockquote, newNode, n)
of xnText:
if n.text[0] == '\10':
newNode.add(n)
else:
blockquoteFinish(currentBlockquote, newNode, n)
else:
blockquoteFinish(currentBlockquote, newNode, n)
result = $newNode
except:
echo("[WARNING] Could not parse rst html.")
proc validateRst(c: var TForumData, content: string): bool = proc validateRst(c: var TForumData, content: string): bool =
result = true result = true
try: try:
@ -678,6 +613,9 @@ proc rateLimitCheck(c: var TForumData): bool =
if last300s > 6: return true if last300s > 6: return true
return false return false
proc makeThreadURL(c: var TForumData): string =
c.req.makeUri("/t/" & $c.threadId)
proc reply(c: var TForumData): bool = proc reply(c: var TForumData): bool =
# reply to an existing thread # reply to an existing thread
checkLogin(c) checkLogin(c)
@ -695,7 +633,7 @@ proc reply(c: var TForumData): bool =
exec(db, sql"update thread set modified = DATETIME('now') where id = ?", exec(db, sql"update thread set modified = DATETIME('now') where id = ?",
$c.threadId) $c.threadId)
asyncCheck sendMailToMailingList(c.config, c.username, c.email, asyncCheck sendMailToMailingList(c.config, c.username, c.email,
subject, content, threadId=c.threadId, postId=c.postID, is_reply=true) subject, content, threadId=c.threadId, postId=c.postID, is_reply=true, threadUrl=c.makeThreadURL())
result = true result = true
proc newThread(c: var TForumData): bool = proc newThread(c: var TForumData): bool =
@ -720,7 +658,7 @@ proc newThread(c: var TForumData): bool =
discard tryExec(db, sql"insert into post_fts(post_fts) values('optimize')") discard tryExec(db, sql"insert into post_fts(post_fts) values('optimize')")
discard tryExec(db, sql"insert into post_fts(thread_fts) values('optimize')") discard tryExec(db, sql"insert into post_fts(thread_fts) values('optimize')")
asyncCheck sendMailToMailingList(c.config, c.username, c.email, asyncCheck sendMailToMailingList(c.config, c.username, c.email,
subject, content, threadId=c.threadID, postId=c.postID, is_reply=false) subject, content, threadId=c.threadID, postId=c.postID, is_reply=false, threadUrl=c.makeThreadURL())
result = true result = true
proc login(c: var TForumData, name, pass: string): bool = proc login(c: var TForumData, name, pass: string): bool =
@ -828,7 +766,7 @@ proc genPagenumNav(c: var TForumData, stats: TForumStats): string =
lastUrl = c.req.makeUri("/page/" & $(totalPages)) lastUrl = c.req.makeUri("/page/" & $(totalPages))
nextUrl = c.req.makeUri("/page/" & $(c.pageNum+1)) nextUrl = c.req.makeUri("/page/" & $(c.pageNum+1))
else: else:
firstUrl = c.req.makeUri("/t/" & $c.threadId) firstUrl = c.makeThreadURL()
if c.pageNum == 1: if c.pageNum == 1:
prevUrl = firstUrl prevUrl = firstUrl
else: else:
@ -1456,8 +1394,6 @@ routes:
textPage "static/search-help" textPage "static/search-help"
when isMainModule: when isMainModule:
docConfig = rstgen.defaultConfig()
docConfig["doc.smiley_format"] = "/images/smilieys/$1.png"
randomize() randomize()
db = open(connection="nimforum.db", user="postgres", password="", db = open(connection="nimforum.db", user="postgres", password="",
database="nimforum") database="nimforum")

View file

@ -1,4 +1,5 @@
import asyncdispatch, smtp, strutils, json, os import asyncdispatch, smtp, strutils, json, os, rst, rstgen, xmltree, strtabs,
htmlparser, streams
from times import getTime, getGMTime, format from times import getTime, getGMTime, format
type type
@ -9,6 +10,11 @@ type
smtpPassword: string smtpPassword: string
mlistAddress: string mlistAddress: string
var docConfig: StringTableRef
docConfig = rstgen.defaultConfig()
docConfig["doc.smiley_format"] = "/images/smilieys/$1.png"
proc loadConfig*(filename = getCurrentDir() / "forum.json"): Config = proc loadConfig*(filename = getCurrentDir() / "forum.json"): Config =
result = Config(smtpAddress: "", smtpPort: 25, smtpUser: "", result = Config(smtpAddress: "", smtpPort: 25, smtpUser: "",
smtpPassword: "", mlistAddress: "") smtpPassword: "", mlistAddress: "")
@ -22,6 +28,70 @@ proc loadConfig*(filename = getCurrentDir() / "forum.json"): Config =
except: except:
echo("[WARNING] Couldn't read config file: ", filename) echo("[WARNING] Couldn't read config file: ", filename)
proc processGT(n: XmlNode, tag: string): (int, XmlNode, string) =
result = (0, newElement(tag), tag)
if n.kind == xnElement and len(n) == 1 and n[0].kind == xnElement:
return processGT(n[0], if n[0].kind == xnElement: n[0].tag else: tag)
var countGT = true
for c in items(n):
case c.kind
of xnText:
if c.text == ">" and countGT:
result[0].inc()
else:
countGT = false
result[1].add(newText(c.text))
else:
result[1].add(c)
proc blockquoteFinish(currentBlockquote, newNode: var XmlNode, n: XmlNode) =
if currentBlockquote.len > 0:
#echo(currentBlockquote.repr)
newNode.add(currentBlockquote)
currentBlockquote = newElement("blockquote")
newNode.add(n)
proc rstToHtml*(content: string): string =
result = rstgen.rstToHtml(content, {roSupportSmilies, roSupportMarkdown},
docConfig)
# Bolt on quotes.
# TODO: Yes, this is ugly. I wrote it quickly. PRs welcome ;)
try:
var node = parseHtml(newStringStream(result))
var newNode = newElement("div")
if node.kind == xnElement:
var currentBlockquote = newElement("blockquote")
for n in items(node):
case n.kind
of xnElement:
case n.tag
of "p":
let (nesting, contentNode, tag) = processGT(n, "p")
if nesting > 0:
var bq = currentBlockquote
for i in 1 .. <nesting:
var newBq = bq.child("blockquote")
if newBq.isNil:
newBq = newElement("blockquote")
bq.add(newBq)
bq = newBq
bq.insert(contentNode, if bq.len == 0: 0 else: bq.len)
else:
blockquoteFinish(currentBlockquote, newNode, n)
else:
blockquoteFinish(currentBlockquote, newNode, n)
of xnText:
if n.text[0] == '\10':
newNode.add(n)
else:
blockquoteFinish(currentBlockquote, newNode, n)
else:
blockquoteFinish(currentBlockquote, newNode, n)
result = $newNode
except:
echo("[WARNING] Could not parse rst html.")
proc sendMail(config: Config, subject, message, recipient: string, from_addr = "forum@nim-lang.org", otherHeaders:seq[(string, string)] = @[]) {.async.} = proc sendMail(config: Config, subject, message, recipient: string, from_addr = "forum@nim-lang.org", otherHeaders:seq[(string, string)] = @[]) {.async.} =
if config.smtpAddress.len == 0: if config.smtpAddress.len == 0:
echo("[WARNING] Cannot send mail: no smtp server configured (smtpAddress).") echo("[WARNING] Cannot send mail: no smtp server configured (smtpAddress).")
@ -42,7 +112,7 @@ proc sendMail(config: Config, subject, message, recipient: string, from_addr = "
await client.sendMail(from_addr, toList, $encoded) await client.sendMail(from_addr, toList, $encoded)
proc sendMailToMailingList*(config: Config, username, user_email_addr, subject, message: string, thread_id=0, post_id=0, is_reply=false) {.async.} = proc sendMailToMailingList*(config: Config, username, user_email_addr, subject, message: string, threadUrl: string, thread_id=0, post_id=0, is_reply=false) {.async.} =
# send message to a mailing list # send message to a mailing list
if config.mlistAddress.len == 0: if config.mlistAddress.len == 0:
echo("[WARNING] Cannot send mail: no mlistAddress configured.") echo("[WARNING] Cannot send mail: no mlistAddress configured.")
@ -67,7 +137,14 @@ proc sendMailToMailingList*(config: Config, username, user_email_addr, subject,
let msg_id = "<forum-tid-$#@nim-lang.org>" % $thread_id let msg_id = "<forum-tid-$#@nim-lang.org>" % $thread_id
otherHeaders.add(("Message-ID", msg_id)) otherHeaders.add(("Message-ID", msg_id))
await sendMail(config, subject, message, config.mlistAddress, from_addr=from_addr, otherHeaders=otherHeaders) var processedMsg: string
try:
processedMsg = rstToHTML(message) & "<hr/><a href=\"" & threadUrl & "\" style=\"font-size:small\">View thread on Nim forum</a>"
otherHeaders.add(("Content-Type", "text/html; charset=\"UTF-8\""))
except:
processedMsg = message
await sendMail(config, subject, processedMsg, config.mlistAddress, from_addr=from_addr, otherHeaders=otherHeaders)
proc sendPassReset*(config: Config, email, user, resetUrl: string) {.async.} = proc sendPassReset*(config: Config, email, user, resetUrl: string) {.async.} =
let message = """Hello $1, let message = """Hello $1,