2020-09-05 22:29:24 +00:00
|
|
|
package layout
|
|
|
|
|
|
|
|
import (
|
2020-09-26 15:48:49 +00:00
|
|
|
"fmt"
|
2020-09-05 22:29:24 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2020-09-15 16:24:29 +00:00
|
|
|
"strings"
|
2020-09-05 22:29:24 +00:00
|
|
|
"text/template"
|
|
|
|
|
|
|
|
"github.com/goccy/go-yaml"
|
|
|
|
tele "gopkg.in/tucnak/telebot.v3"
|
|
|
|
)
|
|
|
|
|
2020-10-08 19:37:57 +00:00
|
|
|
type Settings struct {
|
|
|
|
URL string
|
|
|
|
Token string
|
|
|
|
Updates int
|
2020-09-05 22:29:24 +00:00
|
|
|
|
2021-07-25 11:15:42 +00:00
|
|
|
LocalesDir string `yaml:"locales_dir"`
|
2021-07-07 10:17:57 +00:00
|
|
|
TokenEnv string `yaml:"token_env"`
|
|
|
|
ParseMode string `yaml:"parse_mode"`
|
2020-09-05 22:29:24 +00:00
|
|
|
|
2021-07-07 10:17:57 +00:00
|
|
|
Webhook *tele.Webhook `yaml:"webhook"`
|
|
|
|
LongPoller *tele.LongPoller `yaml:"long_poller"`
|
2020-10-08 19:37:57 +00:00
|
|
|
}
|
2020-09-05 22:29:24 +00:00
|
|
|
|
|
|
|
func (lt *Layout) UnmarshalYAML(data []byte) error {
|
|
|
|
var aux struct {
|
|
|
|
Settings *Settings
|
|
|
|
Config map[string]interface{}
|
2020-09-26 15:48:49 +00:00
|
|
|
Buttons yaml.MapSlice
|
2020-09-05 22:29:24 +00:00
|
|
|
Markups yaml.MapSlice
|
2021-07-25 11:15:42 +00:00
|
|
|
Results yaml.MapSlice
|
2020-09-05 22:29:24 +00:00
|
|
|
Locales map[string]map[string]string
|
|
|
|
}
|
|
|
|
if err := yaml.Unmarshal(data, &aux); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-08 19:37:57 +00:00
|
|
|
lt.Config = &Config{
|
|
|
|
v: aux.Config,
|
|
|
|
}
|
2020-09-05 22:29:24 +00:00
|
|
|
|
|
|
|
if pref := aux.Settings; pref != nil {
|
|
|
|
lt.pref = &tele.Settings{
|
|
|
|
URL: pref.URL,
|
|
|
|
Token: pref.Token,
|
|
|
|
Updates: pref.Updates,
|
|
|
|
ParseMode: pref.ParseMode,
|
|
|
|
}
|
|
|
|
|
|
|
|
if pref.TokenEnv != "" {
|
|
|
|
lt.pref.Token = os.Getenv(pref.TokenEnv)
|
|
|
|
}
|
|
|
|
|
|
|
|
if pref.Webhook != nil {
|
|
|
|
lt.pref.Poller = pref.Webhook
|
|
|
|
} else if pref.LongPoller != nil {
|
|
|
|
lt.pref.Poller = pref.LongPoller
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-26 15:48:49 +00:00
|
|
|
lt.buttons = make(map[string]Button, len(aux.Buttons))
|
|
|
|
for _, item := range aux.Buttons {
|
2020-09-05 22:29:24 +00:00
|
|
|
k, v := item.Key.(string), item.Value
|
|
|
|
|
2020-09-26 15:48:49 +00:00
|
|
|
// 1. Shortened reply button
|
|
|
|
|
|
|
|
if v, ok := v.(string); ok {
|
|
|
|
lt.buttons[k] = Button{Text: v}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. Extended reply or inline button
|
|
|
|
|
2020-09-05 22:29:24 +00:00
|
|
|
data, err := yaml.Marshal(v)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-07-07 10:16:38 +00:00
|
|
|
var btn struct {
|
|
|
|
Button `yaml:",inline"`
|
2021-07-07 10:17:57 +00:00
|
|
|
Data interface{} `yaml:"data"`
|
2021-07-07 10:16:38 +00:00
|
|
|
}
|
2020-09-26 15:48:49 +00:00
|
|
|
if err := yaml.Unmarshal(data, &btn); err != nil {
|
|
|
|
return err
|
2020-09-05 22:29:24 +00:00
|
|
|
}
|
|
|
|
|
2021-07-07 10:16:38 +00:00
|
|
|
if btn.Data != nil {
|
|
|
|
if a, ok := btn.Data.([]interface{}); ok {
|
|
|
|
s := make([]string, len(a))
|
|
|
|
for i, v := range a {
|
|
|
|
s[i] = fmt.Sprint(v)
|
|
|
|
}
|
|
|
|
btn.Button.Data = strings.Join(s, "|")
|
|
|
|
} else if s, ok := btn.Data.(string); ok {
|
|
|
|
btn.Button.Data = s
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("telebot/layout: invalid callback_data for %s button", k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
lt.buttons[k] = btn.Button
|
2020-09-26 15:48:49 +00:00
|
|
|
}
|
2020-09-05 22:29:24 +00:00
|
|
|
|
2020-09-26 15:48:49 +00:00
|
|
|
lt.markups = make(map[string]Markup, len(aux.Markups))
|
|
|
|
for _, item := range aux.Markups {
|
|
|
|
k, v := item.Key.(string), item.Value
|
2020-09-05 22:29:24 +00:00
|
|
|
|
2020-09-26 15:48:49 +00:00
|
|
|
data, err := yaml.Marshal(v)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2020-09-05 22:29:24 +00:00
|
|
|
}
|
|
|
|
|
2020-09-26 15:48:49 +00:00
|
|
|
var shortenedMarkup [][]string
|
|
|
|
if yaml.Unmarshal(data, &shortenedMarkup) == nil {
|
|
|
|
// 1. Shortened reply or inline markup
|
2020-09-05 22:29:24 +00:00
|
|
|
|
2020-09-26 15:48:49 +00:00
|
|
|
kb := make([][]Button, len(shortenedMarkup))
|
|
|
|
for i, btns := range shortenedMarkup {
|
|
|
|
row := make([]Button, len(btns))
|
2020-09-05 22:29:24 +00:00
|
|
|
for j, btn := range btns {
|
2020-09-26 15:48:49 +00:00
|
|
|
b, ok := lt.buttons[btn]
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("telebot/layout: no %s button for %s markup", btn, k)
|
|
|
|
}
|
|
|
|
row[j] = b
|
2020-09-05 22:29:24 +00:00
|
|
|
}
|
|
|
|
kb[i] = row
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err := yaml.Marshal(kb)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-09-25 20:17:49 +00:00
|
|
|
tmpl, err := template.New(k).Funcs(lt.funcs).Parse(string(data))
|
2020-09-05 22:29:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-09-25 20:17:49 +00:00
|
|
|
markup := Markup{keyboard: tmpl}
|
2020-09-26 15:48:49 +00:00
|
|
|
for _, row := range kb {
|
|
|
|
for _, btn := range row {
|
2020-10-13 13:31:08 +00:00
|
|
|
inline := btn.URL != "" ||
|
|
|
|
btn.Unique != "" ||
|
2020-10-11 12:29:50 +00:00
|
|
|
btn.InlineQuery != "" ||
|
|
|
|
btn.InlineQueryChat != ""
|
|
|
|
|
2020-09-26 15:48:49 +00:00
|
|
|
if markup.inline == nil {
|
|
|
|
markup.inline = &inline
|
|
|
|
} else if *markup.inline != inline {
|
|
|
|
return fmt.Errorf("telebot/layout: mixed reply and inline buttons in %s markup", k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-25 20:17:49 +00:00
|
|
|
lt.markups[k] = markup
|
2020-09-26 15:48:49 +00:00
|
|
|
} else {
|
|
|
|
// 2. Extended reply markup
|
2020-09-05 22:29:24 +00:00
|
|
|
|
2020-09-26 15:48:49 +00:00
|
|
|
var markup struct {
|
|
|
|
Markup `yaml:",inline"`
|
2021-07-07 10:17:57 +00:00
|
|
|
Keyboard [][]string `yaml:"keyboard"`
|
2020-09-26 15:48:49 +00:00
|
|
|
}
|
|
|
|
if err := yaml.Unmarshal(data, &markup); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-09-05 22:29:24 +00:00
|
|
|
|
2020-09-26 15:48:49 +00:00
|
|
|
kb := make([][]tele.ReplyButton, len(markup.Keyboard))
|
|
|
|
for i, btns := range markup.Keyboard {
|
|
|
|
row := make([]tele.ReplyButton, len(btns))
|
|
|
|
for j, btn := range btns {
|
|
|
|
row[j] = *lt.buttons[btn].Reply()
|
|
|
|
}
|
|
|
|
kb[i] = row
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err := yaml.Marshal(kb)
|
2020-09-05 22:29:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-09-26 15:48:49 +00:00
|
|
|
tmpl, err := template.New(k).Funcs(lt.funcs).Parse(string(data))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2020-09-05 22:29:24 +00:00
|
|
|
}
|
2020-09-26 15:48:49 +00:00
|
|
|
|
|
|
|
markup.inline = new(bool)
|
|
|
|
markup.Markup.keyboard = tmpl
|
|
|
|
lt.markups[k] = markup.Markup
|
2020-09-05 22:29:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-25 11:15:42 +00:00
|
|
|
lt.results = make(map[string]Result, len(aux.Results))
|
|
|
|
for _, item := range aux.Results {
|
|
|
|
k, v := item.Key.(string), item.Value
|
|
|
|
|
|
|
|
data, err := yaml.Marshal(v)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
tmpl, err := template.New(k).Funcs(lt.funcs).Parse(string(data))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var result Result
|
|
|
|
if err := yaml.Unmarshal(data, &result); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
result.result = tmpl
|
|
|
|
lt.results[k] = result
|
|
|
|
}
|
|
|
|
|
2020-09-05 22:29:24 +00:00
|
|
|
if aux.Locales == nil {
|
2020-09-15 10:37:30 +00:00
|
|
|
if aux.Settings.LocalesDir == "" {
|
|
|
|
aux.Settings.LocalesDir = "locales"
|
|
|
|
}
|
2020-09-05 22:29:24 +00:00
|
|
|
return lt.parseLocales(aux.Settings.LocalesDir)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (lt *Layout) parseLocales(dir string) error {
|
2020-09-25 20:17:49 +00:00
|
|
|
lt.locales = make(map[string]*template.Template)
|
2020-09-15 10:31:54 +00:00
|
|
|
|
2020-09-05 22:29:24 +00:00
|
|
|
return filepath.Walk(dir, func(path string, fi os.FileInfo, _ error) error {
|
|
|
|
if fi == nil || fi.IsDir() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err := ioutil.ReadFile(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var texts map[string]string
|
|
|
|
if err := yaml.Unmarshal(data, &texts); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-09-15 16:24:29 +00:00
|
|
|
name := fi.Name()
|
|
|
|
name = strings.TrimSuffix(name, filepath.Ext(name))
|
2020-09-05 22:29:24 +00:00
|
|
|
|
2021-07-25 11:26:56 +00:00
|
|
|
tmpl := template.New(name).Funcs(lt.funcs)
|
2020-09-15 16:42:15 +00:00
|
|
|
for key, text := range texts {
|
2021-07-25 11:26:56 +00:00
|
|
|
_, err = tmpl.New(key).Parse(strings.Trim(text, "\r\n"))
|
2020-09-05 22:29:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-25 20:17:49 +00:00
|
|
|
lt.locales[name] = tmpl
|
2020-09-05 22:29:24 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|