2020-12-21 00:32:39 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"compress/gzip"
|
2021-11-08 19:17:07 +00:00
|
|
|
"crypto/rand"
|
|
|
|
"encoding/hex"
|
2020-12-21 00:32:39 +00:00
|
|
|
"encoding/json"
|
|
|
|
"os"
|
|
|
|
"sync"
|
2021-06-28 22:01:26 +00:00
|
|
|
"time"
|
2020-12-21 00:32:39 +00:00
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2021-11-25 22:48:16 +00:00
|
|
|
|
2021-12-24 23:47:40 +00:00
|
|
|
"github.com/Luzifer/go_helpers/v2/str"
|
2021-11-25 22:48:16 +00:00
|
|
|
"github.com/Luzifer/twitch-bot/plugins"
|
2020-12-21 00:32:39 +00:00
|
|
|
)
|
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
const eventSubSecretLength = 32
|
|
|
|
|
2020-12-21 00:32:39 +00:00
|
|
|
type storageFile struct {
|
2021-08-19 13:33:56 +00:00
|
|
|
Counters map[string]int64 `json:"counters"`
|
|
|
|
Timers map[string]plugins.TimerEntry `json:"timers"`
|
|
|
|
Variables map[string]string `json:"variables"`
|
2020-12-21 00:32:39 +00:00
|
|
|
|
2021-10-03 13:35:58 +00:00
|
|
|
ModuleStorage map[string]json.RawMessage `json:"module_storage"`
|
|
|
|
|
2021-12-24 23:47:40 +00:00
|
|
|
GrantedScopes map[string][]string `json:"granted_scopes"`
|
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
EventSubSecret string `json:"event_sub_secret,omitempty"`
|
|
|
|
|
2021-06-28 22:01:26 +00:00
|
|
|
inMem bool
|
|
|
|
lock *sync.RWMutex
|
2020-12-21 00:32:39 +00:00
|
|
|
}
|
|
|
|
|
2021-06-28 22:01:26 +00:00
|
|
|
func newStorageFile(inMemStore bool) *storageFile {
|
2020-12-21 00:32:39 +00:00
|
|
|
return &storageFile{
|
2021-06-29 12:08:26 +00:00
|
|
|
Counters: map[string]int64{},
|
2021-08-19 13:33:56 +00:00
|
|
|
Timers: map[string]plugins.TimerEntry{},
|
2021-06-29 12:08:26 +00:00
|
|
|
Variables: map[string]string{},
|
2020-12-21 00:32:39 +00:00
|
|
|
|
2021-10-03 13:35:58 +00:00
|
|
|
ModuleStorage: map[string]json.RawMessage{},
|
|
|
|
|
2021-12-24 23:47:40 +00:00
|
|
|
GrantedScopes: map[string][]string{},
|
|
|
|
|
2021-06-28 22:01:26 +00:00
|
|
|
inMem: inMemStore,
|
|
|
|
lock: new(sync.RWMutex),
|
2020-12-21 00:32:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-24 23:47:40 +00:00
|
|
|
func (s *storageFile) DeleteGrantedScopes(user string) error {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
delete(s.GrantedScopes, user)
|
|
|
|
|
|
|
|
return errors.Wrap(s.Save(), "saving store")
|
|
|
|
}
|
|
|
|
|
2021-10-03 13:35:58 +00:00
|
|
|
func (s *storageFile) DeleteModuleStore(moduleUUID string) error {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
delete(s.ModuleStorage, moduleUUID)
|
|
|
|
|
|
|
|
return errors.Wrap(s.Save(), "saving store")
|
|
|
|
}
|
|
|
|
|
2020-12-21 00:32:39 +00:00
|
|
|
func (s *storageFile) GetCounterValue(counter string) int64 {
|
|
|
|
s.lock.RLock()
|
|
|
|
defer s.lock.RUnlock()
|
|
|
|
|
|
|
|
return s.Counters[counter]
|
|
|
|
}
|
|
|
|
|
2021-11-08 19:17:07 +00:00
|
|
|
func (s *storageFile) GetOrGenerateEventSubSecret() (string, string, error) {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
if s.EventSubSecret != "" {
|
|
|
|
return s.EventSubSecret, s.EventSubSecret[:5], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
key := make([]byte, eventSubSecretLength)
|
|
|
|
n, err := rand.Read(key)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", errors.Wrap(err, "generating random secret")
|
|
|
|
}
|
|
|
|
if n != eventSubSecretLength {
|
|
|
|
return "", "", errors.Errorf("read only %d of %d byte", n, eventSubSecretLength)
|
|
|
|
}
|
|
|
|
|
|
|
|
s.EventSubSecret = hex.EncodeToString(key)
|
|
|
|
|
|
|
|
return s.EventSubSecret, s.EventSubSecret[:5], errors.Wrap(s.Save(), "saving store")
|
|
|
|
}
|
|
|
|
|
2021-10-03 13:35:58 +00:00
|
|
|
func (s *storageFile) GetModuleStore(moduleUUID string, storedObject plugins.StorageUnmarshaller) error {
|
|
|
|
s.lock.RLock()
|
|
|
|
defer s.lock.RUnlock()
|
|
|
|
|
|
|
|
return errors.Wrap(
|
|
|
|
storedObject.UnmarshalStoredObject(s.ModuleStorage[moduleUUID]),
|
|
|
|
"unmarshalling stored object",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-06-29 12:08:26 +00:00
|
|
|
func (s *storageFile) GetVariable(key string) string {
|
|
|
|
s.lock.RLock()
|
|
|
|
defer s.lock.RUnlock()
|
|
|
|
|
|
|
|
return s.Variables[key]
|
|
|
|
}
|
|
|
|
|
2021-06-28 22:01:26 +00:00
|
|
|
func (s *storageFile) HasTimer(id string) bool {
|
|
|
|
s.lock.RLock()
|
|
|
|
defer s.lock.RUnlock()
|
|
|
|
|
|
|
|
return s.Timers[id].Time.After(time.Now())
|
|
|
|
}
|
|
|
|
|
2020-12-21 00:32:39 +00:00
|
|
|
func (s *storageFile) Load() error {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
2021-06-28 22:01:26 +00:00
|
|
|
if s.inMem {
|
|
|
|
// In-Memory store is active, do not load from disk
|
|
|
|
// for testing purposes only!
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-12-21 00:32:39 +00:00
|
|
|
f, err := os.Open(cfg.StorageFile)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
// Store init state
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return errors.Wrap(err, "open storage file")
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
zf, err := gzip.NewReader(f)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "create gzip reader")
|
|
|
|
}
|
|
|
|
defer zf.Close()
|
|
|
|
|
|
|
|
return errors.Wrap(
|
|
|
|
json.NewDecoder(zf).Decode(s),
|
|
|
|
"decode storage object",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *storageFile) Save() error {
|
|
|
|
// NOTE(kahlers): DO NOT LOCK THIS, all calling functions are
|
|
|
|
// modifying functions and must have locks in place
|
|
|
|
|
2021-06-28 22:01:26 +00:00
|
|
|
if s.inMem {
|
|
|
|
// In-Memory store is active, do not store to disk
|
|
|
|
// for testing purposes only!
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cleanup timers
|
|
|
|
var timerIDs []string
|
|
|
|
for id := range s.Timers {
|
|
|
|
timerIDs = append(timerIDs, id)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, i := range timerIDs {
|
|
|
|
if s.Timers[i].Time.Before(time.Now()) {
|
|
|
|
delete(s.Timers, i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write store to disk
|
2020-12-21 00:32:39 +00:00
|
|
|
f, err := os.Create(cfg.StorageFile)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "create storage file")
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
zf := gzip.NewWriter(f)
|
|
|
|
defer zf.Close()
|
|
|
|
|
|
|
|
return errors.Wrap(
|
|
|
|
json.NewEncoder(zf).Encode(s),
|
|
|
|
"encode storage object",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-12-24 23:47:40 +00:00
|
|
|
func (s *storageFile) SetGrantedScopes(user string, scopes []string) error {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
s.GrantedScopes[user] = scopes
|
|
|
|
|
|
|
|
return errors.Wrap(s.Save(), "saving store")
|
|
|
|
}
|
|
|
|
|
2021-10-03 13:35:58 +00:00
|
|
|
func (s *storageFile) SetModuleStore(moduleUUID string, storedObject plugins.StorageMarshaller) error {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
data, err := storedObject.MarshalStoredObject()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "marshalling stored object")
|
|
|
|
}
|
|
|
|
|
|
|
|
s.ModuleStorage[moduleUUID] = data
|
|
|
|
|
|
|
|
return errors.Wrap(s.Save(), "saving store")
|
|
|
|
}
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
func (s *storageFile) SetTimer(kind plugins.TimerType, id string, expiry time.Time) error {
|
2021-06-28 22:01:26 +00:00
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
2021-08-19 13:33:56 +00:00
|
|
|
s.Timers[id] = plugins.TimerEntry{Kind: kind, Time: expiry}
|
2021-06-28 22:01:26 +00:00
|
|
|
|
|
|
|
return errors.Wrap(s.Save(), "saving store")
|
|
|
|
}
|
|
|
|
|
2021-06-29 12:08:26 +00:00
|
|
|
func (s *storageFile) SetVariable(key, value string) error {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
s.Variables[key] = value
|
|
|
|
|
|
|
|
return errors.Wrap(s.Save(), "saving store")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *storageFile) RemoveVariable(key string) error {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
delete(s.Variables, key)
|
|
|
|
|
|
|
|
return errors.Wrap(s.Save(), "saving store")
|
|
|
|
}
|
|
|
|
|
2020-12-21 00:32:39 +00:00
|
|
|
func (s *storageFile) UpdateCounter(counter string, value int64, absolute bool) error {
|
|
|
|
s.lock.Lock()
|
|
|
|
defer s.lock.Unlock()
|
|
|
|
|
|
|
|
if !absolute {
|
|
|
|
value = s.Counters[counter] + value
|
|
|
|
}
|
|
|
|
|
|
|
|
s.Counters[counter] = value
|
|
|
|
|
|
|
|
return errors.Wrap(s.Save(), "saving store")
|
|
|
|
}
|
2021-12-24 23:47:40 +00:00
|
|
|
|
|
|
|
func (s *storageFile) UserHasGrantedAnyScope(user string, scopes ...string) bool {
|
|
|
|
s.lock.RLock()
|
|
|
|
defer s.lock.RUnlock()
|
|
|
|
|
|
|
|
grantedScopes, ok := s.GrantedScopes[user]
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, scope := range scopes {
|
|
|
|
if str.StringInSlice(scope, grantedScopes) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *storageFile) UserHasGrantedScopes(user string, scopes ...string) bool {
|
|
|
|
s.lock.RLock()
|
|
|
|
defer s.lock.RUnlock()
|
|
|
|
|
|
|
|
grantedScopes, ok := s.GrantedScopes[user]
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, scope := range scopes {
|
|
|
|
if !str.StringInSlice(scope, grantedScopes) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|