bk/src/bk.nim

188 lines
6.2 KiB
Nim
Raw Normal View History

2020-09-29 09:12:03 +02:00
import htmlparser, json, net, options, os, strscans, times, unicode, uri, xmltree
from strutils import ffDecimal, formatFloat, replace
import cligen, colorize, elvis
2020-09-27 15:51:44 +02:00
from bakalari as baka import Grade, newBakalari
2020-09-04 11:32:45 +02:00
type
Config = object
website: string
username: string
refreshToken: string
const
defaultConfigFile = getConfigDir() / "bk.json"
2020-09-04 20:43:05 +02:00
dayNames = [
1: "Mon",
2: "Tue",
3: "Wed",
4: "Thu",
5: "Fri",
6: "Sat",
7: "Sun",
]
2020-09-04 11:32:45 +02:00
proc error(message: string) {.noReturn.} =
stderr.writeLine("Error: " & message)
quit QuitFailure
2020-09-04 11:32:45 +02:00
proc loadConfig(configFile = defaultConfigFile): Config =
2020-09-07 17:18:43 +02:00
try:
configFile.readFile.parseJson.to(Config)
2020-09-07 17:18:43 +02:00
except IOError:
error "Can't read the config file. Are you signed in?"
2020-09-07 17:18:43 +02:00
except JsonParsingError, JsonKindError:
error "Invalid config file. Make sure it's in JSON format."
2020-09-07 17:18:43 +02:00
except KeyError:
error "The config file doesn't contain all necessary fields."
2020-09-04 11:32:45 +02:00
proc saveConfig(configFile = defaultConfigFile, config: Config) =
2020-09-07 17:18:43 +02:00
try:
configFile.writeFile((%*config).pretty)
except IOError:
error "Can't write the config file."
2020-09-04 11:32:45 +02:00
template withBakalari(configFile: string, body: untyped): untyped =
var config = configFile.loadConfig
let bakalari {.inject.} = newBakalari(config.website.parseUri, config.refreshToken)
try:
body
finally:
config.refreshToken = bakalari.refreshToken
configFile.saveConfig(config)
2020-09-04 11:32:45 +02:00
proc signin(
2020-09-21 09:44:32 +02:00
website: string,
username: string,
password: string,
configFile = defaultConfigFile,
2020-09-04 11:32:45 +02:00
) =
2020-09-04 13:10:32 +02:00
## sign in to Bakaláři and save the credentials
2020-09-04 11:32:45 +02:00
let
bakalari = newBakalari(website.parseUri, username, password)
config = Config(
website: website,
username: username,
refreshToken: bakalari.refreshToken,
)
configFile.saveConfig(config)
2020-09-27 15:51:44 +02:00
proc average(grades: seq[Grade]): float =
var sum, weightSum: float
for grade in grades:
var value: int
if scanf(grade.text, "$i-", value):
sum += (value.float + 0.5) * grade.weight.float
weightSum += grade.weight.float
elif scanf(grade.text, "$i", value):
sum += value.float * grade.weight.float
weightSum += grade.weight.float
return weightSum ? (sum / weightSum) ! 0.0
proc grades(
configFile = defaultConfigFile,
) =
## display the list of grades and calculate averages
withBakalari(configFile):
for subject in baka.grades(bakalari):
stdout.writeLine subject.name.fgLightYellow & " " & subject.grades.average.formatFloat(ffDecimal, 2)
for grade in subject.grades:
var line = ""
line &= grade.text.align(2).fgLightMagenta
line &= " "
line &= ($grade.weight).align(2).fgLightCyan
line &= " "
line &= grade.caption
stdout.writeLine line
2020-09-07 17:51:22 +02:00
proc homework(
configFile = defaultConfigFile,
) =
## display the list of homework
withBakalari(configFile):
for homework in baka.homework(bakalari):
stdout.writeLine "----------------------------------------------------------------".fgLightGray
stdout.writeLine (homework.startTime.format("yyyy-MM-dd") & " / " & homework.endTime.format("yyyy-MM-dd")).fgLightCyan
stdout.writeLine homework.subject.fgLightYellow
stdout.writeLine homework.teacher.fgLightMagenta
stdout.writeLine homework.content
2020-09-07 17:51:22 +02:00
2020-09-27 12:22:14 +02:00
proc messages(
configFile = defaultConfigFile,
) =
withBakalari(defaultConfigFile):
for message in baka.messages(bakalari):
stdout.writeLine "----------------------------------------------------------------".fgLightGray
stdout.writeLine message.sentTime.format("yyyy-MM-dd") .fgLightCyan
stdout.writeLine message.title.fgLightYellow
stdout.writeLine message.sender.fgLightMagenta
2020-09-29 09:08:59 +02:00
stdout.writeLine message.text.replace("<br />", "{{br}}").parseHtml.innerText.replace("{{br}}", "\n")
2020-09-27 12:22:14 +02:00
2020-09-04 13:10:32 +02:00
proc timetable(
2020-09-04 11:32:45 +02:00
configFile = defaultConfigFile,
date = "",
nextWeek = false,
2020-09-04 11:32:45 +02:00
oneDay = false,
permanent = false,
) =
2020-09-04 13:10:32 +02:00
## display the timetable (for the current week by default)
let date = if ?date:
try:
date.parse("yyyyMMdd")
except TimeParseError:
error "Can't parse the date. Make sure it's in YYYYMMDD format."
else:
nextWeek ? (now() + 1.weeks) ! now()
withBakalari(configFile):
let timetable = baka.timetable(bakalari, permanent, some(date))
for day in timetable.days:
if oneDay:
if date.format("yyyyMMdd") != day.date.format("yyyyMMdd"):
continue
stdout.writeLine dayNames[day.dayOfWeek] & " " & day.date.format("yyyy-MM-dd")
for lesson in day.lessons:
var line = ""
line &= lesson.hour.number
line &= ". "
line &= lesson.hour.beginTime.align(5, '0'.Rune).fgLightCyan
line &= "-".fgLightCyan
line &= lesson.hour.endTime.align(5, '0'.Rune).fgLightCyan
line &= " "
line &= lesson.subject.abbrev.align(4).fgLightYellow
line &= " "
line &= lesson.teacher.abbrev.align(4).fgLightMagenta
line &= " "
line &= lesson.room.abbrev.align(4).fgLightGreen
stdout.writeLine line
if lesson.change.isSome:
stdout.writeLine ("^ " & lesson.change.unsafeGet.fgLightRed).fgLightRed
2020-09-04 13:10:32 +02:00
2020-09-07 17:18:43 +02:00
try:
dispatchMulti(
[signin, help = {
"website": "the URL for Bakaláři (e.g. https://bakalari.myschool.cz)",
"username": "your username",
"password": "your password (will not be stored anywhere)",
"config-file": "where to store the credentials",
}],
2020-09-27 15:51:44 +02:00
[grades, help = {
"config-file": "where the credentials are stored",
}],
2020-09-07 17:51:22 +02:00
[homework, help = {
"config-file": "where the credentials are stored",
}],
2020-09-27 12:22:14 +02:00
[messages, help = {
"config-file": "where the credentials are stored",
}],
2020-09-07 17:18:43 +02:00
[timetable, help = {
"config-file": "where the credentials are stored",
"date": "any date inside the week you want to display, in YYYYMMDD format (defaults to today)",
"nextWeek": "display the next week rather that the current week (overriden by --date)",
2020-09-07 17:18:43 +02:00
"one-day": "display only the specified day",
"permanent": "display the permanent timetable",
}],
)
except OSError:
error "Generic OS error. Check your internet connection."
2020-09-22 11:48:16 +02:00
except SslError:
error "Connection interrupted. Check your internet connection."