Compare commits
10 Commits
4c2227f0e8
...
8b1f959084
Author | SHA1 | Date |
---|---|---|
Oliver Kennedy | 8b1f959084 | |
Oliver Kennedy | 7b3eb24c2d | |
Oliver Kennedy | e70ddafe37 | |
Oliver Kennedy | bbb8504087 | |
Oliver Kennedy | 30d9d44ec2 | |
Oliver Kennedy | a96c89c9dd | |
Oliver Kennedy | bb3c1ed828 | |
Oliver Kennedy | e17e802987 | |
Oliver Kennedy | 871fa5b878 | |
Oliver Kennedy | 09595d2283 |
2
build.sc
2
build.sc
|
@ -13,12 +13,14 @@ object shingle extends ScalaModule
|
|||
ivy"com.lihaoyi::ujson:2.0.0",
|
||||
ivy"com.lihaoyi::upickle:2.0.0",
|
||||
ivy"com.lihaoyi::requests:0.7.0",
|
||||
ivy"com.lihaoyi::cask:0.8.3",
|
||||
ivy"org.fusesource.mqtt-client:mqtt-client:1.16",
|
||||
ivy"net.thejavashop:javampd:6.0.0",
|
||||
ivy"org.quartz-scheduler:quartz:2.3.2",
|
||||
ivy"org.slf4j:slf4j-api:1.7.36",
|
||||
ivy"ch.qos.logback:logback-classic:1.2.10",
|
||||
ivy"com.typesafe.scala-logging::scala-logging::3.9.4",
|
||||
ivy"org.scala-lang::scala3-compiler::3.1.2"
|
||||
)
|
||||
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -c
|
||||
|
||||
HOST=heracles.xthemage.net
|
||||
DIR=Documents/shingle/
|
||||
|
||||
mill shingle.assembly
|
||||
scp out/shingle/assembly.dest/out.jar $HOST:$DIR/shingle.jar
|
||||
ssh $HOST 'systemctl --user restart shingle'
|
||||
ssh $HOST 'journalctl --user -efu shingle'
|
||||
# ssh -t $HOST 'sudo systemctl restart shingle & sudo journalctl -efu shingle'
|
||||
|
|
|
@ -3,325 +3,134 @@ package net.okennedy.shingle
|
|||
import scala.io.Source
|
||||
import java.io.File
|
||||
import net.okennedy.shingle.module.Module
|
||||
import java.util.Calendar
|
||||
import cask.main.Routes
|
||||
import net.okennedy.shingle.cron.Cron
|
||||
import net.okennedy.shingle.module.Notification
|
||||
import net.okennedy.shingle.module.Notifications
|
||||
import net.okennedy.shingle.cron.TimeRangeTriggerable
|
||||
|
||||
object Shingle
|
||||
extends cask.Main
|
||||
{
|
||||
|
||||
override def allRoutes: Seq[Routes] = Seq(Webserver)
|
||||
override def port: Int = 4000
|
||||
|
||||
val config = ujson.read(Source.fromFile(
|
||||
System.getProperty("user.home") + File.separator + ".shingle"
|
||||
).getLines.mkString("\n"))
|
||||
|
||||
def main(args: Array[String]): Unit =
|
||||
override def main(args: Array[String]): Unit =
|
||||
{
|
||||
println("Welcome to Shingle\nInitializing libraries...")
|
||||
|
||||
println("...Mqtt")
|
||||
Mqtt.start()
|
||||
println("...MPD")
|
||||
MPD
|
||||
println("...Hass")
|
||||
Hass
|
||||
println("...Weather")
|
||||
Weather
|
||||
println("...Cron")
|
||||
Cron.handler.start()
|
||||
println("Installing user modules...")
|
||||
Module.init()
|
||||
println("Shingle is running.")
|
||||
|
||||
Module("TIME_INTENTS") { implicit ctx =>
|
||||
var kitchenTimer: Timer = new Timer(Mqtt("timer/kitchen"))
|
||||
|
||||
kitchenTimer.done.trigger {
|
||||
_ => Hermes.say("The timer is done")
|
||||
}
|
||||
|
||||
Hermes.registerIntent("SetTimer"){ slots =>
|
||||
val t = slots("count").num * slots("unit").num
|
||||
|
||||
val (duration, _) = Seq(
|
||||
(3600, "hour"),
|
||||
(60, "minute"),
|
||||
(1, "second")
|
||||
).foldLeft ( (Seq[String](), t.toLong) ) {
|
||||
case ( (accum, remainder), (unit, label) ) =>
|
||||
if(remainder > unit){
|
||||
val diff = (remainder / unit).toLong
|
||||
(
|
||||
accum :+ s"$diff ${label}${if(diff > 1){ "s" } else { "" }}",
|
||||
remainder % unit
|
||||
)
|
||||
} else {
|
||||
(accum, remainder)
|
||||
}
|
||||
}
|
||||
|
||||
Hermes.say(s"Setting timer for ${duration.mkString(", ")}")
|
||||
|
||||
kitchenTimer.go(
|
||||
end = java.time.LocalDateTime.now.plusSeconds(t.toLong)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Module("LIGHT_INTENTS") { implicit ctx =>
|
||||
val lights = Map(
|
||||
"living room light" -> ("light", "living_room_lamp"),
|
||||
"armory light" -> ("light", "living_room_lamp"),
|
||||
// "office light" -> ("switch", "office_light_relay")
|
||||
)
|
||||
|
||||
val VALID_STATE = Set("on", "off")
|
||||
|
||||
Hermes.registerIntent("ChangeLightState") { slots =>
|
||||
val (domain, entity) = lights(slots("name").str)
|
||||
val state = slots("state").str
|
||||
|
||||
if(!VALID_STATE(state)){
|
||||
println(s"Invalid state: $state for $domain.$entity")
|
||||
return
|
||||
}
|
||||
|
||||
println(entity+" -> "+slots("state").str)
|
||||
|
||||
Hass.service(
|
||||
domain,
|
||||
s"turn_$state",
|
||||
"entity_id" -> s"$domain.$entity"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Module("CREEPY_SOUND") { implicit ctx =>
|
||||
val sounds = Seq(
|
||||
"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
|
||||
"ooooooooooooooooooooooooooooooooooooooooooooooooyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
|
||||
"rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr",
|
||||
)
|
||||
|
||||
Hermes.registerIntent("CreepySound")
|
||||
{ _ => Hermes.say(scala.util.Random.shuffle(sounds).head) }
|
||||
}
|
||||
|
||||
Module("GARAGE_SENSORS") { implicit ctx =>
|
||||
def notify(msg: String) =
|
||||
{
|
||||
Hermes.say(msg)
|
||||
Hass.service("notify", "matrix_home", "message" -> msg)
|
||||
}
|
||||
|
||||
Mqtt("sensor/garage/door")
|
||||
.onAnyChange
|
||||
.asString
|
||||
.trigger {
|
||||
// temporary bug in the sensor code flips the states...
|
||||
case "open" => notify("The garage door is closed")
|
||||
case "closed" => notify("The garage door is open")
|
||||
case _ => ()
|
||||
}
|
||||
|
||||
Mqtt("sensor/garage/distance")
|
||||
.asString
|
||||
.map { _.toInt }
|
||||
.map {
|
||||
case x if x.toInt < 200 => "parked"
|
||||
case x if x.toInt < 250 => "parking-near"
|
||||
case x if x.toInt < 300 => "parking-far"
|
||||
case _ => "empty"
|
||||
}
|
||||
.aliasAsString("sensor/garage/parking")
|
||||
.onAnyChange
|
||||
.asJson[String]
|
||||
.debounce(5000)
|
||||
.trigger {
|
||||
case "parked" => notify("The car is parked")
|
||||
case "empty" => notify("The car has departed")
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
Module("GARAGE_LIGHT"){ implicit ctx =>
|
||||
val lights = Seq(
|
||||
"red", "yellow", "green"
|
||||
).map { x => Mqtt(s"light/garage/tower/$x") }
|
||||
def setLight(target: Int) =
|
||||
for((light, idx) <- lights.zipWithIndex){
|
||||
if(idx == target) { light << "on" }
|
||||
else { light << "off" }
|
||||
}
|
||||
Mqtt("sensor/garage/parking")
|
||||
.asString
|
||||
.onAnyChange
|
||||
.join(Mqtt("sensor/garage/door").onAnyChange.asString)
|
||||
.trigger {
|
||||
case ("parked" , "open") => setLight(0)
|
||||
case ("parking-near", "open") => setLight(1)
|
||||
case ("parking-far" , "open") => setLight(2)
|
||||
case (_ , _) => setLight(3)
|
||||
}
|
||||
}
|
||||
|
||||
Module("MPD_INTENTS") { implicit ctx =>
|
||||
val standardPlayer = "media_player.stereo_armory"
|
||||
|
||||
// Stereo Off
|
||||
Hermes.registerIntent("StereoOff") { slots =>
|
||||
Hermes.say("Stereo Off")
|
||||
|
||||
Seq(
|
||||
"media_player.stereo_armory",
|
||||
"media_player.stereo_living_room",
|
||||
).foreach { entity =>
|
||||
Hass.service("media_player", "turn_off",
|
||||
"entity_id" -> ujson.Str(entity)
|
||||
)
|
||||
}
|
||||
|
||||
Hass.service("media_player", "media_pause",
|
||||
"entity_id" -> ujson.Str("media_player.mpd")
|
||||
)
|
||||
}
|
||||
|
||||
// Play Standard
|
||||
Hermes.registerIntent("PlayMusic"){ _ =>
|
||||
Hermes.say("Playing Music")
|
||||
super.main(args)
|
||||
|
||||
|
||||
Hass.service("media_player", "turn_on",
|
||||
"entity_id" -> ujson.Str(standardPlayer)
|
||||
)
|
||||
// Module("TIME_INTENTS") { implicit ctx =>
|
||||
// var kitchenTimer: Timer = new Timer(Mqtt("timer/kitchen"))
|
||||
|
||||
Hass.service("media_player", "select_source",
|
||||
"entity_id" -> ujson.Str(standardPlayer),
|
||||
"source" -> ujson.Str("AUDIO1")
|
||||
)
|
||||
// kitchenTimer.done.trigger {
|
||||
// _ => Hermes.say("The timer is done")
|
||||
// }
|
||||
|
||||
Hass.service("media_player", "turn_on",
|
||||
"entity_id" -> ujson.Str("media_player.mpd")
|
||||
)
|
||||
// Hermes.registerIntent("SetTimer"){ slots =>
|
||||
// val t = slots("count").num * slots("unit").num
|
||||
|
||||
Hass.service("media_player", "media_play",
|
||||
"entity_id" -> ujson.Str("media_player.mpd")
|
||||
)
|
||||
}
|
||||
// val (duration, _) = Seq(
|
||||
// (3600, "hour"),
|
||||
// (60, "minute"),
|
||||
// (1, "second")
|
||||
// ).foldLeft ( (Seq[String](), t.toLong) ) {
|
||||
// case ( (accum, remainder), (unit, label) ) =>
|
||||
// if(remainder > unit){
|
||||
// val diff = (remainder / unit).toLong
|
||||
// (
|
||||
// accum :+ s"$diff ${label}${if(diff > 1){ "s" } else { "" }}",
|
||||
// remainder % unit
|
||||
// )
|
||||
// } else {
|
||||
// (accum, remainder)
|
||||
// }
|
||||
// }
|
||||
|
||||
// Play Radio
|
||||
Hermes.registerIntent("PlayRadio"){ _ =>
|
||||
Hermes.say("Playing N.P.R.")
|
||||
// Hermes.say(s"Setting timer for ${duration.mkString(", ")}")
|
||||
|
||||
Hass.service("media_player", "turn_on",
|
||||
"entity_id" -> ujson.Str(standardPlayer)
|
||||
)
|
||||
// kitchenTimer.go(
|
||||
// end = java.time.LocalDateTime.now.plusSeconds(t.toLong)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
Hass.service("media_player", "select_source",
|
||||
"entity_id" -> ujson.Str(standardPlayer),
|
||||
"source" -> ujson.Str("TUNER")
|
||||
)
|
||||
// Module("GARAGE_SENSORS") { implicit ctx =>
|
||||
// def notify(msg: String) =
|
||||
// {
|
||||
// Hermes.say(msg)
|
||||
// Hass.service("notify", "matrix_home", "message" -> msg)
|
||||
// }
|
||||
|
||||
Hass.service("media_player", "media_pause",
|
||||
"entity_id" -> ujson.Str("media_player.mpd")
|
||||
)
|
||||
}
|
||||
// // Mqtt("sensor/garage/door")
|
||||
// // .onAnyChange
|
||||
// // .asString
|
||||
// // .trigger {
|
||||
// // // temporary bug in the sensor code flips the states...
|
||||
// // case "closed" => notify("The garage door is closed")
|
||||
// // case "open" => notify("The garage door is open")
|
||||
// // case _ => ()
|
||||
// // }
|
||||
|
||||
// Play Song
|
||||
Hermes.registerIntent("PlaySong"){ slots =>
|
||||
val request = slots("music").str
|
||||
// Mqtt("sensor/garage/distance")
|
||||
// .asString
|
||||
// .map {
|
||||
// case "borked" => "unknown"
|
||||
// case x if x.toInt < 200 => "parked"
|
||||
// case x if x.toInt < 250 => "parking-near"
|
||||
// case x if x.toInt < 300 => "parking-far"
|
||||
// case _ => "empty"
|
||||
// }
|
||||
// .aliasAsString("sensor/garage/parking")
|
||||
// .onAnyChange
|
||||
// .asJson[String]
|
||||
// .debounce(5000)
|
||||
// .trigger {
|
||||
// case "parked" => notify("The car is parked")
|
||||
// case "empty" => notify("The car has departed")
|
||||
// case _ => ()
|
||||
// }
|
||||
// }
|
||||
|
||||
val playlist =
|
||||
MPD.savedPlaylists
|
||||
.find { request.equalsIgnoreCase(_) }
|
||||
.getOrElse {
|
||||
Hermes.say(s"I don't know how to play $request")
|
||||
return
|
||||
}
|
||||
|
||||
Hermes.say(s"Playing $request")
|
||||
|
||||
Hass.service("media_player", "turn_on",
|
||||
"entity_id" -> ujson.Str(standardPlayer)
|
||||
)
|
||||
|
||||
Hass.service("media_player", "select_source",
|
||||
"entity_id" -> ujson.Str(standardPlayer),
|
||||
"source" -> ujson.Str("AUDIO1")
|
||||
)
|
||||
|
||||
MPD.clear()
|
||||
for(item <- MPD.savedPlaylist(playlist)) {
|
||||
MPD.append(item.getFile)
|
||||
}
|
||||
MPD.play()
|
||||
}
|
||||
}
|
||||
|
||||
Module("WEATHER"){ implicit owner =>
|
||||
Hermes.registerIntent("GetTemperature"){ _ =>
|
||||
val forecast = Weather.forecast
|
||||
|
||||
Hermes.say("One moment please.")
|
||||
Hermes.say(s"The weather for ${forecast(0).name} is ${forecast(0).shortForecast} with temperatures around ${forecast(0).temperature.toInt} degrees.")
|
||||
Hermes.say(s"For ${forecast(1).name}, ${forecast(1).shortForecast} with temperatures around ${forecast(1).temperature.toInt} degrees.")
|
||||
}
|
||||
}
|
||||
|
||||
Module("DAD"){ implicit owner =>
|
||||
Hermes.registerIntent("TellAJoke"){ _ =>
|
||||
Hermes.say("Let's think")
|
||||
val resp = requests.get(
|
||||
"https://icanhazdadjoke.com/",
|
||||
headers = Map(
|
||||
"User-Agent" -> "Shingle (https://github.com/okennedy)",
|
||||
"Accept" -> "text/plain"
|
||||
)
|
||||
)
|
||||
|
||||
if(resp.statusCode != 200)
|
||||
{
|
||||
println(s"Joke Error: ${resp.statusCode} -> ${resp.string()}")
|
||||
Hermes.say("I'm not feeling funny right now.")
|
||||
} else {
|
||||
println(resp.string())
|
||||
Hermes.say(resp.string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Module("NOTIFICATIONS"){ implicit owner =>
|
||||
Notifications("garage_door") <<
|
||||
Mqtt("sensor/garage/door")
|
||||
.onAnyChangeIncludingFirst
|
||||
.asString
|
||||
.map {
|
||||
case door if door == "open" =>
|
||||
Some(Notification(
|
||||
body = "The garage door is open",
|
||||
status = Notifications.Warning,
|
||||
icon = "transportation/car"
|
||||
))
|
||||
case door =>
|
||||
None
|
||||
}
|
||||
Notifications("kitchen_timer") <<
|
||||
Mqtt("timer/kitchen/progress")
|
||||
.onAnyChangeIncludingFirst
|
||||
.asJson[TimerStatus]
|
||||
.map {
|
||||
case timer if timer.progress < 1.0 =>
|
||||
Some(Notification(
|
||||
body = s"${timer.timeRemaining} on timer",
|
||||
status = Notifications.Info,
|
||||
icon = "alerts/alarm-clock"
|
||||
))
|
||||
|
||||
}
|
||||
Notifications("garbage") <<
|
||||
TimeRangeTriggerable.cron(
|
||||
// s m h dom mo dow
|
||||
start = "0 0 12 ? * 5",
|
||||
end = "0 0 0 ? * 6",
|
||||
).map {
|
||||
case true => println("Garbage On"); Some(Notification(
|
||||
body = "Take out the trash",
|
||||
status = Notifications.Warning,
|
||||
icon = "ecology/recycling-1"
|
||||
))
|
||||
case false => println("Garbage Off"); None
|
||||
}
|
||||
}
|
||||
// Module("GARAGE_LIGHT"){ implicit ctx =>
|
||||
// val lights = Seq(
|
||||
// "red", "yellow", "green"
|
||||
// ).map { x => Mqtt(s"light/garage/tower/$x") }
|
||||
// def setLight(target: Int) =
|
||||
// for((light, idx) <- lights.zipWithIndex){
|
||||
// if(idx == target) { light << "on" }
|
||||
// else { light << "off" }
|
||||
// }
|
||||
// Mqtt("sensor/garage/parking")
|
||||
// .asString
|
||||
// .onAnyChange
|
||||
// .join(Mqtt("sensor/garage/door").onAnyChange.asString)
|
||||
// .trigger {
|
||||
// case ("parked" , "open") => setLight(0)
|
||||
// case ("parking-near", "open") => setLight(1)
|
||||
// case ("parking-far" , "open") => setLight(2)
|
||||
// case (_ , _) => setLight(3)
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@ import upickle.default._
|
|||
import net.okennedy.shingle.cron.IntervalCronEvent
|
||||
import java.time.Duration
|
||||
import net.okennedy.shingle.cron.Cron
|
||||
import scala.util.Success
|
||||
import scala.util.Failure
|
||||
|
||||
object Weather
|
||||
{
|
||||
|
@ -21,6 +23,10 @@ object Weather
|
|||
reader[Seq[WeatherPrediction]]
|
||||
)
|
||||
|
||||
api.get match {
|
||||
case Success(r) => state << r
|
||||
case Failure(exception) => s"Error on initial weather fetch: ${exception}"
|
||||
}
|
||||
api.poll(state)(Owner.global)
|
||||
}
|
||||
case class WeatherPrediction(
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package net.okennedy.shingle
|
||||
|
||||
import net.okennedy.shingle.module.Module
|
||||
|
||||
object Webserver extends cask.Routes
|
||||
{
|
||||
@cask.get("/")
|
||||
def root() = {
|
||||
"Hello World"
|
||||
}
|
||||
|
||||
@cask.get("api/modules")
|
||||
def listModules() = {
|
||||
Module.list.mkString("\n")
|
||||
}
|
||||
|
||||
@cask.get("api/modules/:id")
|
||||
def readModule(id: String) = {
|
||||
Module.read(id)
|
||||
}
|
||||
|
||||
@cask.get("api/load/:id")
|
||||
def saveModule(id: String) = {
|
||||
Module.reload(id)
|
||||
}
|
||||
|
||||
@cask.get("api/unload/:id")
|
||||
def rmModule(id: String) = {
|
||||
Module.unload(id)
|
||||
}
|
||||
|
||||
@cask.get("api/active")
|
||||
def activeModules() = {
|
||||
Module.active.mkString("\n")
|
||||
}
|
||||
|
||||
initialize()
|
||||
}
|
|
@ -26,8 +26,11 @@ trait IntervalCronEvent(interval: Duration) extends CronEvent
|
|||
|
||||
override def fireTrigger(): Unit =
|
||||
{
|
||||
trigger()
|
||||
nextTrigger = Clock.systemDefaultZone.instant.plus(interval)
|
||||
try {
|
||||
trigger()
|
||||
} finally {
|
||||
nextTrigger = Clock.systemDefaultZone.instant.plus(interval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,96 @@
|
|||
package net.okennedy.shingle.module
|
||||
|
||||
|
||||
import scala.collection.mutable
|
||||
import dotty.tools.repl.ScriptEngine
|
||||
import java.io.File
|
||||
import scala.io.Source
|
||||
import java.io.FileWriter
|
||||
|
||||
object Module
|
||||
{
|
||||
val active = mutable.HashMap[String, Owner]()
|
||||
val activeModules = mutable.HashMap[String, Owner]()
|
||||
|
||||
def apply(module: String)(f: Owner => Unit) =
|
||||
{
|
||||
active.remove(module).foreach { _.cleanup() }
|
||||
unload(module)
|
||||
val owner = new Owner(module)
|
||||
f(owner)
|
||||
owner.postInit()
|
||||
active(module) = owner
|
||||
activeModules(module) = owner
|
||||
}
|
||||
|
||||
val SCRIPTS = new File(
|
||||
System.getProperty("user.home") + File.separator + ".shingle-scripts"
|
||||
)
|
||||
val SUFFIX = ".scala"
|
||||
|
||||
def init(): Unit =
|
||||
{
|
||||
for(module <- list){
|
||||
println(s"... installing $module")
|
||||
try {
|
||||
install(module, read(module))
|
||||
} catch {
|
||||
case t: Throwable =>
|
||||
println(s"Error installing $module")
|
||||
t.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def active: Seq[String] =
|
||||
activeModules.keys.toSeq
|
||||
|
||||
def list: Seq[String] =
|
||||
if(!SCRIPTS.exists()){ Seq.empty }
|
||||
else { SCRIPTS.listFiles
|
||||
.map { _.getName }
|
||||
.filter { _.endsWith(SUFFIX) }
|
||||
.map { _.dropRight(SUFFIX.length) } }
|
||||
|
||||
def read(module: String): String =
|
||||
{
|
||||
val f = Source.fromFile(new File(SCRIPTS, module+SUFFIX))
|
||||
f.getLines().mkString
|
||||
}
|
||||
|
||||
def save(module: String, script: String): Unit =
|
||||
{
|
||||
val w = new FileWriter(new File(SCRIPTS, module+SUFFIX))
|
||||
w.write(script)
|
||||
w.close()
|
||||
install(module, script)
|
||||
}
|
||||
|
||||
def delete(module: String): Unit =
|
||||
{
|
||||
unload(module)
|
||||
new File(SCRIPTS, module+SUFFIX).delete()
|
||||
}
|
||||
|
||||
def reload(module: String): Unit =
|
||||
{
|
||||
install(module, read(module))
|
||||
}
|
||||
|
||||
def unload(module: String): Unit =
|
||||
{
|
||||
activeModules.remove(module).foreach { _.cleanup() }
|
||||
}
|
||||
|
||||
def install(module: String, script: String): Unit =
|
||||
{
|
||||
val engine = new ScriptEngine()
|
||||
engine.eval(
|
||||
s"""net.okennedy.shingle.module.Module("$module") { implicit ctx =>
|
||||
| import net.okennedy.shingle._;
|
||||
| import net.okennedy.shingle.cron.Cron
|
||||
| import net.okennedy.shingle.module.Notification
|
||||
| import net.okennedy.shingle.module.Notifications
|
||||
| import net.okennedy.shingle.cron.TimeRangeTriggerable
|
||||
| $script
|
||||
|}
|
||||
|""".stripMargin,
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue