Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modernise NimBot #15

Merged
merged 5 commits into from
Jun 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions src/irclog.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,37 @@ from xmltree import escape
import json except to

type
TLogger* = object of TObject # Items get erased when new day starts.
startTime*: TTimeInfo
TLogger* = object of RootObj # Items get erased when new day starts.
startTime*: DateTime
logFilepath*: string
logFile*: TFile
logFile*: File
PLogger* = ref TLogger

const
webFP = {fpUserRead, fpUserWrite, fpUserExec,
fpGroupRead, fpGroupExec, fpOthersRead, fpOthersExec}

proc loadLogger*(f: string): PLogger =
new(result)
new result
let logs = readFile(f)
let lines = logs.splitLines()
# Line 1: Start time
result.startTime = fromSeconds(to[float](lines[0])).getGMTime()
result.startTime = fromUnixFloat(to[float](lines[0])).utc()
if not open(result.logFile, f, fmAppend):
echo("Warning: Could not open logger: " & f)
result.logFilepath = f.splitFile.dir

proc writeFlush(file: TFile, s: string) =
proc writeFlush(file: File, s: string) =
file.write(s)
file.flushFile()

proc newLogger*(logFilepath: string): PLogger =
let startTime = getTime().getGMTime()
let log = logFilepath / startTime.format("dd'-'MM'-'yyyy'.logs'")
let startTime = getTime().utc()
let log = logFilepath / startTime.format("dd'-'MM'-'yyyy'.json'")
if existsFile(log):
result = loadLogger(log)
else:
new(result)
new result
result.startTime = startTime
result.logFilepath = logFilepath
doAssert open(result.logFile, log, fmAppend)
Expand All @@ -45,30 +45,30 @@ proc `$`(s: seq[string]): string =
strutils.escape(x)
result = "[" & join(escaped, ",") & "]"

proc writeLog(logger: PLogger, msg: TIRCEvent) =
logger.logFile.writeFlush($$(time: getTime(), msg: msg) & "\n")
proc writeLog(logger: PLogger, msg: IRCEvent) =
logger.logFile.writeFlush($(%*{"time": getTime(), "msg": msg}) & "\n")

proc log*(logger: PLogger, msg: TIRCEvent) =
proc log*(logger: PLogger, msg: IRCEvent) =
if msg.origin != "#nim" and msg.cmd notin {MQuit, MNick}: return
if getTime().getGMTime().yearday != logger.startTime.yearday:
if getTime().utc().yearday != logger.startTime.yearday:
# It's time to cycle to next day.
# Reset logger.
logger.logFile.close()
logger.startTime = getTime().getGMTime()
let log = logger.logFilepath / logger.startTime.format("dd'-'MM'-'yyyy'.logs'")
logger.startTime = getTime().utc()
let log = logger.logFilepath / logger.startTime.format("dd'-'MM'-'yyyy'.json'")
doAssert open(logger.logFile, log, fmAppend)
# Write start time
logger.logFile.writeFlush($epochTime() & "\n")

case msg.cmd
of MPrivMsg, MJoin, MPart, MNick, MQuit: # TODO: MTopic? MKick?
#logger.items.add((getTime(), msg))
#logger.save(logger.logFilepath / logger.startTime.format("dd'-'MM'-'yyyy'.json'"))
writeLog(logger, msg)
else: nil
else: discard

proc log*(logger: PLogger, nick, msg, chan: string) =
var m: TIRCEvent
var m: IRCEvent
m.typ = EvMsg
m.cmd = MPrivMsg
m.params = @[chan, msg]
Expand Down
164 changes: 148 additions & 16 deletions src/irclogrender.nim
Original file line number Diff line number Diff line change
@@ -1,41 +1,172 @@
import irc, htmlgen, times, strutils, marshal, os, xmltree, re
from jester import PRequest, makeUri
import irc, htmlgen, times, strutils, marshal, os, xmltree, re, json
from jester import Request, makeUri
import irclog
import strtabs

type
# These legacy types are required to properly marshal the old format so old
# logs can still be read. The only differences are the timestamps and the new
# json format for strtabs.
LegacyIrcEvent = object
case typ: IrcEventType
of EvConnected:
nil
of EvDisconnected:
nil
of EvTimeout:
nil
of EvMsg:
cmd: IrcMType
nick, user, host, servername: string
numeric: string
tags: LegacyStringTableRef
params: seq[string]
origin: string
raw: string
timestamp: int64
LegacyStringTableRef = ref StringTableObj
LegacyEntry = tuple[time: int64, msg: LegacyIRCEvent]

Entry = tuple[time: Time, msg: IRCEvent]
TLogRenderer = object of TLogger
items*: seq[tuple[time: TTime, msg: TIRCEvent]] ## Only used for HTML gen
items*: seq[Entry] ## Only used for HTML gen
PLogRenderer* = ref TLogRenderer

proc toNewEntry(entry: LegacyEntry): Entry =
result.time = fromUnix(entry.time)
result.msg = IRCEvent(
typ: entry.msg.typ
)
if result.msg.typ == EvMsg:
result.msg.cmd = entry.msg.cmd
result.msg.nick = entry.msg.nick
result.msg.user = entry.msg.user
result.msg.host = entry.msg.host
result.msg.servername = entry.msg.servername
result.msg.numeric = entry.msg.numeric
result.msg.tags = cast[StringTableRef](entry.msg.tags)
result.msg.params = entry.msg.params
result.msg.origin = entry.msg.origin
result.msg.raw = entry.msg.raw
result.msg.timestamp = entry.msg.timestamp.fromUnix

proc loadRenderer*(f: string): PLogRenderer =
new(result)
new result
result.items = @[]
let logs = readFile(f)
let lines = logs.splitLines()
var i = 1
# Line 1: Start time
result.startTime = fromSeconds(to[float](lines[0])).getGMTime()
result.startTime = fromUnixFloat(to[float](lines[0])).utc()

result.logFilepath = f.splitFile.dir
echo "Reading file: ", f, ": ", f.endsWith(".logs")
while i < lines.len:
if lines[i] != "":
result.items.add(to[tuple[time: TTime, msg: TIRCEvent]](lines[i]))
if f.endsWith(".logs"):
result.items.add(marshal.to[LegacyEntry](lines[i]).toNewEntry)
elif f.endsWith(".json"):
result.items.add(json.to(lines[i].parseJson, Entry))
inc i

const IRCColours = [
"#e6e6e6",
"#000000",
"#bd93f9",
"#50fa7b",
"#ff5555",
"#ff5555",
"#ff79c6",
"#ffb86c",
"#f1fa8c",
"#50fa7b",
"#8be9fd",
"#8be9fd",
"#bd93f9",
"#ff79c6",
"#b3b3b3",
"#cccccc"
]

proc colourMessage(msg: string): string =
var
currentChar = 0
c: char
openedTags = 0
currentStyle = ""
bold, italic, underline = false
template switchStyle(style: var bool, css: string) =
if not style:
currentStyle &= css
style = true
else:
result &= "</span>"
openedTags -= 1
style = false
while currentChar < msg.len:
c = msg[currentChar]
inc currentChar
case ord c:
of 0x02: switchStyle bold, "font-weight: bold;"
of 0x1D: switchStyle italic, "font-style: italic;"
of 0x1F: switchStyle underline, "text-decoration: underline;"
of 0x03:
let colourCode = to[int](msg[currentChar..^1])
currentStyle &= "color: " & IRCColours[colourCode] & ";"
currentChar += 2
of 0x0F:
result &= "</span>".repeat openedTags
openedTags = 0
bold = false
italic = false
underline = false
else:
if currentStyle.len != 0:
result &= "<span style=\"" & currentStyle & "\">"
openedTags += 1
currentStyle = ""
result &= c
result &= "</span>".repeat openedTags

const NickColours = [
"#6272a4",
"#8be9fd",
"#50fa7b",
"#ffb86c",
"#ff79c6",
"#bd93f9",
"#ff5555",
"#f1fa8c",
"#6272a4",
"#8be9fd",
"#50fa7b",
"#ffb86c",
"#ff79c6",
"#bd93f9",
"#ff5555",
"#f1fa8c"
]
proc colourNick(msg: string): string =
var hash = 0
for c in msg:
hash += ord c
"<span style=\"color: " & NickColours[hash mod 16] & "\">" & msg & "</span>"

proc renderMessage(msg: string): string =
# Transforms anything that looks like a hyperlink into one in the HTML.
let pattern = re"(https?|ftp)://[^\s/$.?#].[^\s]*"
let pattern = re"(https?|ftp)://[^\s/$.?#].[^\s\x02\x1D\x1F\x03\x0F]*"
result = ""

var i = 0
while true:
let (first, last) = msg.findBounds(pattern, start = i)
if first == -1: break
echo(msg[i .. first-1], "|", msg[first .. last])
#echo(msg[i .. first-1], "|", msg[first .. last])
result.add(xmltree.escape(msg[i .. first-1]))
result.add(a(href=msg[first .. last], xmltree.escape(msg[first .. last])))
i = last+1
result.add(xmltree.escape(msg[i .. ^1]))
result = result.colourMessage()

proc renderItems(logger: PLogRenderer, isToday: bool): string =
result = ""
Expand All @@ -53,17 +184,17 @@ proc renderItems(logger: PLogRenderer, isToday: bool): string =
of MKick:
c = "kick"
else:
nil
discard
var message = i.msg.params[i.msg.params.len-1]
if message.startswith("\x01ACTION "):
c = "action"
message = message[8 .. ^2]

let timestamp = i.time.getGMTime().format("HH':'mm':'ss")
let timestamp = i.time.utc().format("HH':'mm':'ss")
let prefix = if isToday: logger.startTime.format("dd'-'MM'-'yyyy'.html'") & "#" else: "#"
if c == "":
result.add(tr(td(a(id=timestamp, href=prefix & timestamp, class="time", timestamp)),
td(class="nick", xmltree.escape(i.msg.nick)),
td(class="nick", xmltree.escape(i.msg.nick).colourNick),
td(id="M" & timestamp, class="msg", message.renderMessage)))
else:
case c
Expand All @@ -87,15 +218,15 @@ proc renderItems(logger: PLogRenderer, isToday: bool): string =
td(class="nick", "*"),
td(id="M" & timestamp, class="msg", xmltree.escape(message))))

proc renderHtml*(logger: PLogRenderer, req: jester.PRequest): string =
let today = getTime().getGMTime()
proc renderHtml*(logger: PLogRenderer, req: jester.Request): string =
let today = getTime().utc()
let isToday = logger.startTime.monthday == today.monthday and
logger.startTime.month == today.month and
logger.startTime.year == today.year
let previousDay = logger.startTime - (initInterval(days=1))
let previousDay = logger.startTime - (initTimeInterval(days=1))
let prevUrl = req.makeUri(previousDay.format("dd'-'MM'-'yyyy'.html'"),
absolute = false)
let nextDay = logger.startTime + (initInterval(days=1))
let nextDay = logger.startTime + (initTimeInterval(days=1))
let nextUrl =
if isToday: ""
else: req.makeUri(nextDay.format("dd'-'MM'-'yyyy'.html'"), absolute = false)
Expand All @@ -105,12 +236,13 @@ proc renderHtml*(logger: PLogRenderer, req: jester.PRequest): string =
meta(content="text/html; charset=UTF-8", `http-equiv` = "Content-Type"),
link(rel="stylesheet", href=req.makeUri("css/boilerplate.css", absolute = false)),
link(rel="stylesheet", href=req.makeUri("css/log.css", absolute = false)),
link(rel="stylesheet", href="https://fonts.googleapis.com/css?family=Lato:400,600,900", type="text/css"),
script(src="js/log.js", type="text/javascript")
),
body(
htmlgen.`div`(id="controls",
a(href=prevUrl, "<<"),
span(logger.startTime.format("dd'-'MM'-'yyyy")),
span(logger.startTime.format(" dd'-'MM'-'yyyy ")),
(if nextUrl == "": span(">>") else: a(href=nextUrl, ">>"))
),
hr(),
Expand Down
Loading