|
@ -2,10 +2,12 @@ |
|
|
package riotbot |
|
|
package riotbot |
|
|
|
|
|
|
|
|
import ( |
|
|
import ( |
|
|
|
|
|
"bytes" |
|
|
"io/ioutil" |
|
|
"io/ioutil" |
|
|
"log" |
|
|
"log" |
|
|
"path/filepath" |
|
|
"path/filepath" |
|
|
"runtime" |
|
|
"runtime" |
|
|
|
|
|
"text/template" |
|
|
"time" |
|
|
"time" |
|
|
|
|
|
|
|
|
yaml "gopkg.in/yaml.v2" |
|
|
yaml "gopkg.in/yaml.v2" |
|
@ -22,16 +24,18 @@ type Service struct { |
|
|
// TutorialFlow represents the tutorial flow / steps
|
|
|
// TutorialFlow represents the tutorial flow / steps
|
|
|
type TutorialFlow struct { |
|
|
type TutorialFlow struct { |
|
|
ResourcesBaseURL string `yaml:"resources_base_url"` |
|
|
ResourcesBaseURL string `yaml:"resources_base_url"` |
|
|
BotName string `yaml:"bot_name"` |
|
|
|
|
|
|
|
|
Templates map[string]string `yaml:"templates"` |
|
|
InitialDelay time.Duration `yaml:"initial_delay"` |
|
|
InitialDelay time.Duration `yaml:"initial_delay"` |
|
|
Tutorial struct { |
|
|
Tutorial struct { |
|
|
Steps []struct { |
|
|
|
|
|
|
|
|
Steps []TutorialStep `yaml:"steps"` |
|
|
|
|
|
} `yaml:"tutorial"` |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
type TutorialStep struct { |
|
|
Type string `yaml:"type"` |
|
|
Type string `yaml:"type"` |
|
|
Body string `yaml:"text"` |
|
|
|
|
|
|
|
|
Body string `yaml:"body"` |
|
|
Src string `yaml:"src"` |
|
|
Src string `yaml:"src"` |
|
|
Delay time.Duration `yaml:"delay"` |
|
|
Delay time.Duration `yaml:"delay"` |
|
|
} `yaml:"steps"` |
|
|
|
|
|
} `yaml:"tutorial"` |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// ServiceType of the Riotbot service
|
|
|
// ServiceType of the Riotbot service
|
|
@ -50,16 +54,18 @@ type Tutorial struct { |
|
|
currentStep int |
|
|
currentStep int |
|
|
timer *time.Timer |
|
|
timer *time.Timer |
|
|
cli *gomatrix.Client |
|
|
cli *gomatrix.Client |
|
|
|
|
|
templates map[string]string |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// NewTutorial creates a new Tutorial instance
|
|
|
// NewTutorial creates a new Tutorial instance
|
|
|
func NewTutorial(roomID string, userID string, cli *gomatrix.Client) Tutorial { |
|
|
|
|
|
|
|
|
func NewTutorial(roomID string, userID string, cli *gomatrix.Client, templates map[string]string) Tutorial { |
|
|
t := Tutorial{ |
|
|
t := Tutorial{ |
|
|
roomID: roomID, |
|
|
roomID: roomID, |
|
|
userID: userID, |
|
|
userID: userID, |
|
|
currentStep: -1, |
|
|
currentStep: -1, |
|
|
timer: nil, |
|
|
timer: nil, |
|
|
cli: cli, |
|
|
cli: cli, |
|
|
|
|
|
templates: templates, |
|
|
} |
|
|
} |
|
|
return t |
|
|
return t |
|
|
} |
|
|
} |
|
@ -77,6 +83,7 @@ func (t *Tutorial) queueNextStep(delay time.Duration) { |
|
|
t.timer.Stop() |
|
|
t.timer.Stop() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
log.Printf("Queueing next step of tutorial for user %s (current step %d) to run in %dms", t.userID, t.currentStep, delay) |
|
|
if delay > 0 { |
|
|
if delay > 0 { |
|
|
t.timer = time.NewTimer(time.Millisecond * delay) |
|
|
t.timer = time.NewTimer(time.Millisecond * delay) |
|
|
<-t.timer.C |
|
|
<-t.timer.C |
|
@ -88,6 +95,7 @@ func (t *Tutorial) queueNextStep(delay time.Duration) { |
|
|
|
|
|
|
|
|
func (t Tutorial) nextStep() { |
|
|
func (t Tutorial) nextStep() { |
|
|
t.currentStep++ |
|
|
t.currentStep++ |
|
|
|
|
|
log.Printf("Performing next step (%d) of tutorial for %s", t.currentStep, t.userID) |
|
|
// Check that there is a valid mtutorial step to process
|
|
|
// Check that there is a valid mtutorial step to process
|
|
|
if t.currentStep < len(tutorialFlow.Tutorial.Steps) { |
|
|
if t.currentStep < len(tutorialFlow.Tutorial.Steps) { |
|
|
base := tutorialFlow.ResourcesBaseURL |
|
|
base := tutorialFlow.ResourcesBaseURL |
|
@ -95,45 +103,69 @@ func (t Tutorial) nextStep() { |
|
|
// Check message type
|
|
|
// Check message type
|
|
|
switch step.Type { |
|
|
switch step.Type { |
|
|
case "image": |
|
|
case "image": |
|
|
|
|
|
body := t.renderBody(step) |
|
|
msg := gomatrix.ImageMessage{ |
|
|
msg := gomatrix.ImageMessage{ |
|
|
MsgType: "m.image", |
|
|
MsgType: "m.image", |
|
|
Body: step.Body, |
|
|
|
|
|
|
|
|
Body: body, |
|
|
URL: base + step.Src, |
|
|
URL: base + step.Src, |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if _, e := t.cli.SendMessageEvent(t.roomID, "m.room.message", msg); e != nil { |
|
|
if _, e := t.cli.SendMessageEvent(t.roomID, "m.room.message", msg); e != nil { |
|
|
log.Print("Failed to send Image message") |
|
|
log.Print("Failed to send Image message") |
|
|
|
|
|
} else { |
|
|
|
|
|
log.Printf("Seinding Image message - %s", body) |
|
|
} |
|
|
} |
|
|
case "notice": |
|
|
case "notice": |
|
|
|
|
|
body := t.renderBody(step) |
|
|
msg := gomatrix.TextMessage{ |
|
|
msg := gomatrix.TextMessage{ |
|
|
MsgType: "m.notice", |
|
|
MsgType: "m.notice", |
|
|
Body: step.Body, |
|
|
|
|
|
|
|
|
Body: body, |
|
|
} |
|
|
} |
|
|
if _, e := t.cli.SendMessageEvent(t.roomID, "m.room.message", msg); e != nil { |
|
|
if _, e := t.cli.SendMessageEvent(t.roomID, "m.room.message", msg); e != nil { |
|
|
log.Printf("Failed to send Notice message - %s", step.Body) |
|
|
|
|
|
|
|
|
log.Printf("Failed to send Notice message - %s", body) |
|
|
|
|
|
} else { |
|
|
|
|
|
log.Printf("Seinding Notice message - %s", body) |
|
|
} |
|
|
} |
|
|
default: // text
|
|
|
default: // text
|
|
|
|
|
|
body := t.renderBody(step) |
|
|
msg := gomatrix.TextMessage{ |
|
|
msg := gomatrix.TextMessage{ |
|
|
MsgType: "m.text", |
|
|
MsgType: "m.text", |
|
|
Body: step.Body, |
|
|
|
|
|
|
|
|
Body: body, |
|
|
} |
|
|
} |
|
|
if _, e := t.cli.SendMessageEvent(t.roomID, "m.room.message", msg); e != nil { |
|
|
if _, e := t.cli.SendMessageEvent(t.roomID, "m.room.message", msg); e != nil { |
|
|
log.Printf("Failed to send Text message - %s", step.Body) |
|
|
|
|
|
|
|
|
log.Printf("Failed to send Text message - %s", body) |
|
|
|
|
|
} else { |
|
|
|
|
|
log.Printf("Seinding Text message - %s", body) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// TODO -- If last step, clean up tutorial instance
|
|
|
// TODO -- If last step, clean up tutorial instance
|
|
|
|
|
|
|
|
|
// Set up timer for next step
|
|
|
// Set up timer for next step
|
|
|
if step.Delay > 0 { |
|
|
|
|
|
t.timer = time.NewTimer(time.Millisecond * tutorialFlow.InitialDelay) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
t.queueNextStep(step.Delay) |
|
|
} else { |
|
|
} else { |
|
|
log.Println("Tutorial instance ended") |
|
|
log.Println("Tutorial instance ended") |
|
|
// End of tutorial -- TODO remove tutorial instance
|
|
|
// End of tutorial -- TODO remove tutorial instance
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (t Tutorial) renderBody(ts TutorialStep) string { |
|
|
|
|
|
if ts.Body != "" { |
|
|
|
|
|
tmpl, err := template.New("message").Parse(ts.Body) |
|
|
|
|
|
if err != nil { |
|
|
|
|
|
log.Print("Failed to create message template") |
|
|
|
|
|
} |
|
|
|
|
|
var msg bytes.Buffer |
|
|
|
|
|
if err = tmpl.Execute(&msg, t.templates); err != nil { |
|
|
|
|
|
log.Print("Failed to execute template substitution") |
|
|
|
|
|
return "" |
|
|
|
|
|
} |
|
|
|
|
|
return msg.String() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return "" |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Commands supported:
|
|
|
// Commands supported:
|
|
|
// !start
|
|
|
// !start
|
|
|
// Starts the tutorial.
|
|
|
// Starts the tutorial.
|
|
@ -162,7 +194,7 @@ func initTutorialFlow(cli *gomatrix.Client, roomID string, userID string) string |
|
|
log.Print("Existing tutorial instance not found for this user") |
|
|
log.Print("Existing tutorial instance not found for this user") |
|
|
|
|
|
|
|
|
// Start a new instance of the riot tutorial
|
|
|
// Start a new instance of the riot tutorial
|
|
|
tutorial := NewTutorial(roomID, userID, cli) |
|
|
|
|
|
|
|
|
tutorial := NewTutorial(roomID, userID, cli, tutorialFlow.Templates) |
|
|
tutorials = append(tutorials, tutorial) |
|
|
tutorials = append(tutorials, tutorial) |
|
|
go tutorial.queueNextStep(tutorialFlow.InitialDelay) |
|
|
go tutorial.queueNextStep(tutorialFlow.InitialDelay) |
|
|
log.Printf("Starting Riot tutorial: %v", tutorial) |
|
|
log.Printf("Starting Riot tutorial: %v", tutorial) |
|
|