4
0
mirror of https://github.com/cwinfo/matterbridge.git synced 2025-06-28 07:59:24 +00:00

Add scripting (tengo) support for every incoming message (#731)

TengoModifyMessage allows you to specify the location of a tengo (https://github.com/d5/tengo/) script.
This script will receive every incoming message and can be used to modify the Username and the Text of that message.
The script will have the following global variables:
to modify: msgUsername and msgText
to read: msgChannel and msgAccount

The script is reloaded on every message, so you can modify the script on the fly.

Example script can be found in https://github.com/42wim/matterbridge/tree/master/gateway/bench.tengo
and https://github.com/42wim/matterbridge/tree/master/contrib/example.tengo

The example below will check if the text contains blah and if so, it'll replace the text and the username of that message.
text := import("text")
if text.re_match("blah",msgText) {
    msgText="replaced by this"
    msgUsername="fakeuser"
}

More information about tengo on: https://github.com/d5/tengo/blob/master/docs/tutorial.md and
https://github.com/d5/tengo/blob/master/docs/stdlib.md
This commit is contained in:
Wim
2019-02-23 16:39:44 +01:00
committed by GitHub
parent 3190703dc8
commit 1bb39eba87
147 changed files with 14201 additions and 0 deletions

113
vendor/github.com/d5/tengo/script/compiled.go generated vendored Normal file
View File

@ -0,0 +1,113 @@
package script
import (
"context"
"fmt"
"github.com/d5/tengo/compiler"
"github.com/d5/tengo/objects"
"github.com/d5/tengo/runtime"
)
// Compiled is a compiled instance of the user script.
// Use Script.Compile() to create Compiled object.
type Compiled struct {
symbolTable *compiler.SymbolTable
machine *runtime.VM
}
// Run executes the compiled script in the virtual machine.
func (c *Compiled) Run() error {
return c.machine.Run()
}
// RunContext is like Run but includes a context.
func (c *Compiled) RunContext(ctx context.Context) (err error) {
ch := make(chan error, 1)
go func() {
ch <- c.machine.Run()
}()
select {
case <-ctx.Done():
c.machine.Abort()
<-ch
err = ctx.Err()
case err = <-ch:
}
return
}
// IsDefined returns true if the variable name is defined (has value) before or after the execution.
func (c *Compiled) IsDefined(name string) bool {
symbol, _, ok := c.symbolTable.Resolve(name)
if !ok {
return false
}
v := c.machine.Globals()[symbol.Index]
if v == nil {
return false
}
return *v != objects.UndefinedValue
}
// Get returns a variable identified by the name.
func (c *Compiled) Get(name string) *Variable {
value := &objects.UndefinedValue
symbol, _, ok := c.symbolTable.Resolve(name)
if ok && symbol.Scope == compiler.ScopeGlobal {
value = c.machine.Globals()[symbol.Index]
if value == nil {
value = &objects.UndefinedValue
}
}
return &Variable{
name: name,
value: value,
}
}
// GetAll returns all the variables that are defined by the compiled script.
func (c *Compiled) GetAll() []*Variable {
var vars []*Variable
for _, name := range c.symbolTable.Names() {
symbol, _, ok := c.symbolTable.Resolve(name)
if ok && symbol.Scope == compiler.ScopeGlobal {
value := c.machine.Globals()[symbol.Index]
if value == nil {
value = &objects.UndefinedValue
}
vars = append(vars, &Variable{
name: name,
value: value,
})
}
}
return vars
}
// Set replaces the value of a global variable identified by the name.
// An error will be returned if the name was not defined during compilation.
func (c *Compiled) Set(name string, value interface{}) error {
obj, err := objects.FromInterface(value)
if err != nil {
return err
}
symbol, _, ok := c.symbolTable.Resolve(name)
if !ok || symbol.Scope != compiler.ScopeGlobal {
return fmt.Errorf("'%s' is not defined", name)
}
c.machine.Globals()[symbol.Index] = &obj
return nil
}

33
vendor/github.com/d5/tengo/script/conversion.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
package script
import (
"github.com/d5/tengo/objects"
)
func objectToInterface(o objects.Object) interface{} {
switch val := o.(type) {
case *objects.Array:
return val.Value
case *objects.Map:
return val.Value
case *objects.Int:
return val.Value
case *objects.Float:
return val.Value
case *objects.Bool:
if val == objects.TrueValue {
return true
}
return false
case *objects.Char:
return val.Value
case *objects.String:
return val.Value
case *objects.Bytes:
return val.Value
case *objects.Undefined:
return nil
}
return o
}

180
vendor/github.com/d5/tengo/script/script.go generated vendored Normal file
View File

@ -0,0 +1,180 @@
package script
import (
"context"
"fmt"
"github.com/d5/tengo/compiler"
"github.com/d5/tengo/compiler/parser"
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/objects"
"github.com/d5/tengo/runtime"
"github.com/d5/tengo/stdlib"
)
// Script can simplify compilation and execution of embedded scripts.
type Script struct {
variables map[string]*Variable
removedBuiltins map[string]bool
removedStdModules map[string]bool
userModuleLoader compiler.ModuleLoader
input []byte
}
// New creates a Script instance with an input script.
func New(input []byte) *Script {
return &Script{
variables: make(map[string]*Variable),
input: input,
}
}
// Add adds a new variable or updates an existing variable to the script.
func (s *Script) Add(name string, value interface{}) error {
obj, err := objects.FromInterface(value)
if err != nil {
return err
}
s.variables[name] = &Variable{
name: name,
value: &obj,
}
return nil
}
// Remove removes (undefines) an existing variable for the script.
// It returns false if the variable name is not defined.
func (s *Script) Remove(name string) bool {
if _, ok := s.variables[name]; !ok {
return false
}
delete(s.variables, name)
return true
}
// DisableBuiltinFunction disables a builtin function.
func (s *Script) DisableBuiltinFunction(name string) {
if s.removedBuiltins == nil {
s.removedBuiltins = make(map[string]bool)
}
s.removedBuiltins[name] = true
}
// DisableStdModule disables a standard library module.
func (s *Script) DisableStdModule(name string) {
if s.removedStdModules == nil {
s.removedStdModules = make(map[string]bool)
}
s.removedStdModules[name] = true
}
// SetUserModuleLoader sets the user module loader for the compiler.
func (s *Script) SetUserModuleLoader(loader compiler.ModuleLoader) {
s.userModuleLoader = loader
}
// Compile compiles the script with all the defined variables, and, returns Compiled object.
func (s *Script) Compile() (*Compiled, error) {
symbolTable, stdModules, globals, err := s.prepCompile()
if err != nil {
return nil, err
}
fileSet := source.NewFileSet()
srcFile := fileSet.AddFile("(main)", -1, len(s.input))
p := parser.NewParser(srcFile, s.input, nil)
file, err := p.ParseFile()
if err != nil {
return nil, fmt.Errorf("parse error: %s", err.Error())
}
c := compiler.NewCompiler(srcFile, symbolTable, nil, stdModules, nil)
if s.userModuleLoader != nil {
c.SetModuleLoader(s.userModuleLoader)
}
if err := c.Compile(file); err != nil {
return nil, err
}
return &Compiled{
symbolTable: symbolTable,
machine: runtime.NewVM(c.Bytecode(), globals, nil),
}, nil
}
// Run compiles and runs the scripts.
// Use returned compiled object to access global variables.
func (s *Script) Run() (compiled *Compiled, err error) {
compiled, err = s.Compile()
if err != nil {
return
}
err = compiled.Run()
return
}
// RunContext is like Run but includes a context.
func (s *Script) RunContext(ctx context.Context) (compiled *Compiled, err error) {
compiled, err = s.Compile()
if err != nil {
return
}
err = compiled.RunContext(ctx)
return
}
func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules map[string]bool, globals []*objects.Object, err error) {
var names []string
for name := range s.variables {
names = append(names, name)
}
symbolTable = compiler.NewSymbolTable()
for idx, fn := range objects.Builtins {
if !s.removedBuiltins[fn.Name] {
symbolTable.DefineBuiltin(idx, fn.Name)
}
}
stdModules = make(map[string]bool)
for name := range stdlib.Modules {
if !s.removedStdModules[name] {
stdModules[name] = true
}
}
globals = make([]*objects.Object, runtime.GlobalsSize, runtime.GlobalsSize)
for idx, name := range names {
symbol := symbolTable.Define(name)
if symbol.Index != idx {
panic(fmt.Errorf("wrong symbol index: %d != %d", idx, symbol.Index))
}
globals[symbol.Index] = s.variables[name].value
}
return
}
func (s *Script) copyVariables() map[string]*Variable {
vars := make(map[string]*Variable)
for n, v := range s.variables {
vars[n] = v
}
return vars
}

149
vendor/github.com/d5/tengo/script/variable.go generated vendored Normal file
View File

@ -0,0 +1,149 @@
package script
import (
"errors"
"github.com/d5/tengo/objects"
)
// Variable is a user-defined variable for the script.
type Variable struct {
name string
value *objects.Object
}
// NewVariable creates a Variable.
func NewVariable(name string, value interface{}) (*Variable, error) {
obj, err := objects.FromInterface(value)
if err != nil {
return nil, err
}
return &Variable{
name: name,
value: &obj,
}, nil
}
// Name returns the name of the variable.
func (v *Variable) Name() string {
return v.name
}
// Value returns an empty interface of the variable value.
func (v *Variable) Value() interface{} {
return objectToInterface(*v.value)
}
// ValueType returns the name of the value type.
func (v *Variable) ValueType() string {
return (*v.value).TypeName()
}
// Int returns int value of the variable value.
// It returns 0 if the value is not convertible to int.
func (v *Variable) Int() int {
c, _ := objects.ToInt(*v.value)
return c
}
// Int64 returns int64 value of the variable value.
// It returns 0 if the value is not convertible to int64.
func (v *Variable) Int64() int64 {
c, _ := objects.ToInt64(*v.value)
return c
}
// Float returns float64 value of the variable value.
// It returns 0.0 if the value is not convertible to float64.
func (v *Variable) Float() float64 {
c, _ := objects.ToFloat64(*v.value)
return c
}
// Char returns rune value of the variable value.
// It returns 0 if the value is not convertible to rune.
func (v *Variable) Char() rune {
c, _ := objects.ToRune(*v.value)
return c
}
// Bool returns bool value of the variable value.
// It returns 0 if the value is not convertible to bool.
func (v *Variable) Bool() bool {
c, _ := objects.ToBool(*v.value)
return c
}
// Array returns []interface value of the variable value.
// It returns 0 if the value is not convertible to []interface.
func (v *Variable) Array() []interface{} {
switch val := (*v.value).(type) {
case *objects.Array:
var arr []interface{}
for _, e := range val.Value {
arr = append(arr, objectToInterface(e))
}
return arr
}
return nil
}
// Map returns map[string]interface{} value of the variable value.
// It returns 0 if the value is not convertible to map[string]interface{}.
func (v *Variable) Map() map[string]interface{} {
switch val := (*v.value).(type) {
case *objects.Map:
kv := make(map[string]interface{})
for mk, mv := range val.Value {
kv[mk] = objectToInterface(mv)
}
return kv
}
return nil
}
// String returns string value of the variable value.
// It returns 0 if the value is not convertible to string.
func (v *Variable) String() string {
c, _ := objects.ToString(*v.value)
return c
}
// Bytes returns a byte slice of the variable value.
// It returns nil if the value is not convertible to byte slice.
func (v *Variable) Bytes() []byte {
c, _ := objects.ToByteSlice(*v.value)
return c
}
// Error returns an error if the underlying value is error object.
// If not, this returns nil.
func (v *Variable) Error() error {
err, ok := (*v.value).(*objects.Error)
if ok {
return errors.New(err.String())
}
return nil
}
// Object returns an underlying Object of the variable value.
// Note that returned Object is a copy of an actual Object used in the script.
func (v *Variable) Object() objects.Object {
return *v.value
}
// IsUndefined returns true if the underlying value is undefined.
func (v *Variable) IsUndefined() bool {
return *v.value == objects.UndefinedValue
}