Skip to content

Commit

Permalink
feat: more changes to events (#4)
Browse files Browse the repository at this point in the history
* feat: more changes to events

* fix: show allerts

* feat: increase version
  • Loading branch information
skynet2 authored Mar 27, 2024
1 parent 94ed911 commit cf778ee
Show file tree
Hide file tree
Showing 16 changed files with 320 additions and 84 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/apimonkey.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ jobs:
with:
draft: false
prerelease: false
release_name: v.0.1.${{ github.run_number }}-apimonkey
tag_name: v.0.1.${{ github.run_number }}
release_name: v.0.2.${{ github.run_number }}-apimonkey
tag_name: v.0.2.${{ github.run_number }}
env:
GITHUB_TOKEN: ${{ github.token }}
- name: upload windows artifact
Expand Down
21 changes: 21 additions & 0 deletions .github/workflows/pull_request.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
on:
pull_request:
push:
branches:
- master
- qa
- uat

jobs:
lint:
runs-on: ubuntu-latest
container: golang:1.22-alpine
env:
ENVIRONMENT: ci
steps:
- uses: actions/checkout@v3
- uses: golangci/golangci-lint-action@v3
if: github.ref != 'refs/heads/master' && github.ref != 'refs/heads/qa' && github.ref != 'refs/heads/uat'
with:
version: latest
args: --timeout=5m --tests=false ./...
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ tidy-modules:
echo "Executing 'go mod tidy' in directory: $$dir_path"; \
(cd "$$dir_path" && GOPROXY=$(GOPROXY) go mod tidy) || exit 1; \
done

.PHONY: lint
lint:
golangci-lint run
165 changes: 113 additions & 52 deletions cmd/client/apimonkey/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import (
"os/exec"
"path/filepath"
"strings"
"sync"
"time"

"github.com/cockroachdb/errors"
"github.com/google/uuid"
"github.com/imroc/req/v3"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/tidwall/gjson"
"meow.tf/streamdeck/sdk"
Expand All @@ -20,6 +22,22 @@ type Instance struct {
cfg *config
contextApp string
lg zerolog.Logger
executor ScriptExecutor
ctx context.Context
ctxCancel context.CancelFunc
mut sync.Mutex
}

func NewInstance(
contextApp string,
executor ScriptExecutor,
) *Instance {
return &Instance{
contextApp: contextApp,
lg: lg.With().Str("context_id", contextApp).Logger(),
executor: executor,
mut: sync.Mutex{},
}
}

func (i *Instance) SetConfig(ctxId string, cfg *config) {
Expand Down Expand Up @@ -58,64 +76,114 @@ func (i *Instance) KeyPressed() {
}
}

func (i *Instance) Run() {
for context.Background().Err() == nil {
func (i *Instance) StartAsync() {
i.mut.Lock()
if i.ctxCancel != nil { // first cancel old routine
i.ctxCancel()
}

i.ctx, i.ctxCancel = context.WithCancel(context.Background())
i.mut.Unlock()

go i.run()
}

func (i *Instance) Stop() {
i.mut.Lock()

if i.ctxCancel != nil { // first cancel old routine
i.ctxCancel()
}
i.ctxCancel = nil

i.mut.Unlock()
}

func (i *Instance) run() {
ctx := i.ctx

for ctx.Err() == nil {
interval := 30
if i.cfg.IntervalSeconds > 0 {
interval = i.cfg.IntervalSeconds
}

func() {
apiUrl := runTemplate(i.cfg.ApiUrl, i.cfg)
lg.Debug().Msgf("sending request to %v", apiUrl)
httpReq := req.C().NewRequest()
newLogger := lg.With().Str("id", uuid.NewString()).Logger()
innerCtx, innerCancel := context.WithCancel(ctx)
innerCtx = newLogger.WithContext(innerCtx)

for k, v := range i.cfg.Headers {
httpReq.SetHeader(k, runTemplate(v, i.cfg))
}
processErr := i.sendAndProcess(innerCtx)
innerCancel()

resp, err := httpReq.Get(apiUrl)
if err != nil {
sdk.ShowAlert(i.contextApp)
lg.Err(errors.Wrap(err, "error sending request")).Send()
return
if processErr != nil {
lg.Err(errors.Wrap(processErr, "error processing response")).Send()
i.ShowAlert()
} else {
if i.cfg.ShowSuccessNotification {
sdk.ShowOk(i.contextApp)
}
}

lg.Debug().Msgf("got raw response %v", resp.String())
if err != nil {
sdk.ShowAlert(i.contextApp)
lg.Err(errors.Wrap(err, "error parsing request")).Send()
return
}
time.Sleep(time.Duration(interval) * time.Second)
}
}

value := resp.String()
if i.cfg.ResponseJSONSelector != "" {
selectorVal := gjson.Get(resp.String(), i.cfg.ResponseJSONSelector)
func (i *Instance) sendAndProcess(ctx context.Context) error {
apiUrl := runTemplate(i.cfg.ApiUrl, i.cfg)
httpReq := req.C().NewRequest()
httpReq = httpReq.SetContext(ctx)

if selectorVal.Type == gjson.Null {
sdk.ShowAlert(i.contextApp)
lg.Err(errors.New("no data found by ResponseJSONSelector")).Send()
return
}
for k, v := range i.cfg.Headers {
httpReq.SetHeader(k, runTemplate(v, i.cfg))
}

value = selectorVal.String()
zerolog.Ctx(ctx).Trace().Str("url", apiUrl).Msg("sending request")
resp, err := httpReq.Get(apiUrl)
if err != nil {
return errors.Wrap(err, "error sending request")
}

if value == "" {
sdk.ShowAlert(i.contextApp)
lg.Err(errors.Wrap(err, "empty value got from ResponseJSONSelector")).Send()
}
}
value := resp.String()
zerolog.Ctx(ctx).Debug().Str("response", value).Msg("got raw response")

lg.Debug().Msgf("got raw value %v", value)
if strings.TrimSpace(i.cfg.BodyScript) != "" {
zerolog.Ctx(ctx).Trace().Str("script", i.cfg.BodyScript).Msg("executing script")

i.handleResponse(value)
}()
scriptResult, scriptErr := i.executor.Execute(ctx, i.cfg.BodyScript, value, resp.StatusCode)
if scriptErr != nil {
return errors.Wrap(scriptErr, "error executing script")
}

time.Sleep(time.Duration(interval) * time.Second)
value = scriptResult

zerolog.Ctx(ctx).Trace().Str("result", value).Msg("script executed")
}

zerolog.Ctx(ctx).Debug().
Str("response", value).
Str("selector", i.cfg.ResponseJSONSelector).
Msg("post script processing")

if i.cfg.ResponseJSONSelector != "" {
selectorVal := gjson.Get(value, i.cfg.ResponseJSONSelector)

if selectorVal.Type == gjson.Null {
return errors.New("no data found by ResponseJSONSelector")
}

value = selectorVal.String()

if value == "" {
return errors.New("empty value got from ResponseJSONSelector")
}
}

zerolog.Ctx(ctx).Debug().Str("final_result", value).Msgf("final")

return i.handleResponse(ctx, value)
}

func (i *Instance) handleResponse(response string) {
func (i *Instance) handleResponse(_ context.Context, response string) error {
var sb strings.Builder
prefix := runTemplate(i.cfg.TitlePrefix, i.cfg)
if prefix != "" {
Expand All @@ -131,9 +199,8 @@ func (i *Instance) handleResponse(response string) {

sdk.SetTitle(i.contextApp, sb.String(), 0)
sdk.SetImage(i.contextApp, "", 0)
sdk.ShowOk(i.contextApp)

return
return nil
}

mapped, ok := i.cfg.ResponseMapper[response]
Expand All @@ -147,9 +214,8 @@ func (i *Instance) handleResponse(response string) {

sdk.SetTitle(i.contextApp, sb.String(), 0)
sdk.SetImage(i.contextApp, "", 0)
sdk.ShowAlert(i.contextApp)

return
return errors.Newf("response mapper not found for value - %v", response)
}

if strings.HasPrefix(mapped, "http") || strings.HasSuffix(mapped, ".png") || strings.HasSuffix(mapped, ".svg") {
Expand All @@ -161,10 +227,8 @@ func (i *Instance) handleResponse(response string) {
fileData, err := readFile(filepath.Join("images", mapped))

if err != nil {
lg.Err(errors.Wrap(err, "image file not found")).Send()
sdk.SetImage(i.contextApp, "", 0)
sdk.ShowAlert(i.contextApp)
return
return errors.Join(err, errors.New("image file not found"))
}

imageData := ""
Expand All @@ -176,14 +240,11 @@ func (i *Instance) handleResponse(response string) {

sdk.SetImage(i.contextApp, imageData, 0)
}

sdk.ShowOk(i.contextApp)
} else {
sb.WriteString(mapped)
sdk.SetTitle(i.contextApp, sb.String(), 0)
sdk.SetImage(i.contextApp, "", 0)
sdk.ShowOk(i.contextApp)

return
}

return nil
}
7 changes: 7 additions & 0 deletions cmd/client/apimonkey/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "context"

type ScriptExecutor interface {
Execute(ctx context.Context, script string, rawBody string, statusCode int) (string, error)
}
31 changes: 23 additions & 8 deletions cmd/client/apimonkey/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/valyala/fastjson"
"gopkg.in/natefinch/lumberjack.v2"
"meow.tf/streamdeck/sdk"

"github.com/ft-t/streamdeck/cmd/scripts"
)

var lg zerolog.Logger
Expand All @@ -19,7 +21,9 @@ var mut sync.Mutex
func setSettingsFromPayload(payload *fastjson.Value, ctxId string, instance *Instance) {
if instance == nil {
lg.Warn().Msgf("instance %v not found", ctxId)
return
}

settingsBytes := payload.MarshalTo(nil)
lg.Debug().Msgf("Got configuration: %v", string(settingsBytes))
var tempConfig config
Expand Down Expand Up @@ -51,19 +55,30 @@ func main() {

mut.Lock()
defer mut.Unlock()
instance, _ := instances[event.Context]

if instance == nil {
instance = &Instance{
contextApp: event.Context,
lg: lg.With().Str("context_id", event.Context).Logger(),
}
instance, ok := instances[event.Context]

if !ok {
instance = NewInstance(event.Context, scripts.NewLua())
instances[event.Context] = instance
}

setSettingsFromPayload(event.Payload.Get("settings"), event.Context, instance)
go instance.Run()
instance.StartAsync()
})

sdk.AddHandler(func(event *sdk.WillDisappearEvent) {
if event.Payload == nil {
return
}

mut.Lock()
defer mut.Unlock()
instance, ok := instances[event.Context]
if !ok {
return
}

instance.StartAsync()
})

sdk.AddHandler(func(event *sdk.ReceiveSettingsEvent) {
Expand Down
2 changes: 1 addition & 1 deletion cmd/client/apimonkey/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"os"
"text/template"

"github.com/pkg/errors"
"github.com/cockroachdb/errors"
)

func runTemplate(input string, cfg *config) string {
Expand Down
8 changes: 8 additions & 0 deletions cmd/client/apimonkey/resources/pi/pi.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,19 @@
<div class="sdpi-item-label">Interval (Sec)</div>
<input class="sdpi-item-value sdProperty" type="number" v-model="config.intervalSeconds">
</div>
<div class="sdpi-item">
<div class="sdpi-item-label">Show Success Notification</div>
<input class="sdpi-item-value sdProperty" type="checkbox" v-model="config.showSuccessNotification">
</div>
<hr>
<div class="sdpi-item">
<div class="sdpi-item-label">JSON Selector</div>
<input class="sdpi-item-value sdProperty" v-model="config.responseJSONSelector">
</div>
<div class="sdpi-item">
<div class="sdpi-item-label">Lua Script</div>
<input class="sdpi-item-value sdProperty" v-model="config.bodyScript">
</div>
<hr>
<div>
<div class="sdpi-item">
Expand Down
Loading

0 comments on commit cf778ee

Please sign in to comment.