Formatted emails
This commit is contained in:
parent
85f527ee4f
commit
82ce6a5ffc
2 changed files with 88 additions and 75 deletions
80
forum.nim
80
forum.nim
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
import
|
||||
os, strutils, times, md5, strtabs, cgi, math, db_sqlite, matchers,
|
||||
rst, rstgen, captchas, scgi, jester, asyncdispatch, asyncnet, cache, sequtils,
|
||||
parseutils, utils, htmlparser, xmltree, streams, random
|
||||
captchas, scgi, jester, asyncdispatch, asyncnet, cache, sequtils,
|
||||
parseutils, utils, random, rst
|
||||
|
||||
when not defined(windows):
|
||||
import bcrypt # TODO
|
||||
|
|
@ -75,7 +75,6 @@ type
|
|||
|
||||
var
|
||||
db: TDbConn
|
||||
docConfig: StringTableRef
|
||||
isFTSAvailable: bool
|
||||
config: Config
|
||||
|
||||
|
|
@ -436,70 +435,6 @@ proc isPreview(c: TForumData): bool =
|
|||
proc isDelete(c: TForumData): bool =
|
||||
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 =
|
||||
result = true
|
||||
try:
|
||||
|
|
@ -678,6 +613,9 @@ proc rateLimitCheck(c: var TForumData): bool =
|
|||
if last300s > 6: return true
|
||||
return false
|
||||
|
||||
proc makeThreadURL(c: var TForumData): string =
|
||||
c.req.makeUri("/t/" & $c.threadId)
|
||||
|
||||
proc reply(c: var TForumData): bool =
|
||||
# reply to an existing thread
|
||||
checkLogin(c)
|
||||
|
|
@ -695,7 +633,7 @@ proc reply(c: var TForumData): bool =
|
|||
exec(db, sql"update thread set modified = DATETIME('now') where id = ?",
|
||||
$c.threadId)
|
||||
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
|
||||
|
||||
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(thread_fts) values('optimize')")
|
||||
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
|
||||
|
||||
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))
|
||||
nextUrl = c.req.makeUri("/page/" & $(c.pageNum+1))
|
||||
else:
|
||||
firstUrl = c.req.makeUri("/t/" & $c.threadId)
|
||||
firstUrl = c.makeThreadURL()
|
||||
if c.pageNum == 1:
|
||||
prevUrl = firstUrl
|
||||
else:
|
||||
|
|
@ -1456,8 +1394,6 @@ routes:
|
|||
textPage "static/search-help"
|
||||
|
||||
when isMainModule:
|
||||
docConfig = rstgen.defaultConfig()
|
||||
docConfig["doc.smiley_format"] = "/images/smilieys/$1.png"
|
||||
randomize()
|
||||
db = open(connection="nimforum.db", user="postgres", password="",
|
||||
database="nimforum")
|
||||
|
|
|
|||
83
utils.nim
83
utils.nim
|
|
@ -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
|
||||
|
||||
type
|
||||
|
|
@ -9,6 +10,11 @@ type
|
|||
smtpPassword: string
|
||||
mlistAddress: string
|
||||
|
||||
var docConfig: StringTableRef
|
||||
|
||||
docConfig = rstgen.defaultConfig()
|
||||
docConfig["doc.smiley_format"] = "/images/smilieys/$1.png"
|
||||
|
||||
proc loadConfig*(filename = getCurrentDir() / "forum.json"): Config =
|
||||
result = Config(smtpAddress: "", smtpPort: 25, smtpUser: "",
|
||||
smtpPassword: "", mlistAddress: "")
|
||||
|
|
@ -22,6 +28,70 @@ proc loadConfig*(filename = getCurrentDir() / "forum.json"): Config =
|
|||
except:
|
||||
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.} =
|
||||
if config.smtpAddress.len == 0:
|
||||
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)
|
||||
|
||||
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
|
||||
if config.mlistAddress.len == 0:
|
||||
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
|
||||
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.} =
|
||||
let message = """Hello $1,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue