Compare commits

..

7 Commits

Author SHA1 Message Date
klmp200 3ea22c1951
Changed line ending 2018-07-25 14:36:53 +02:00
klmp200 e1d790e3fd
Fix CI 2018-07-25 14:12:52 +02:00
klmp200 d43ffcb445
Plugins in plugin folder 2018-07-25 13:00:14 +02:00
klmp200 9b5c849dcc
It sait it needed merge 2018-07-25 12:05:39 +02:00
KLIPFEL Arthur d5a7922779 merge 2018-07-25 11:57:15 +02:00
KLIPFEL Arthur 54c3bc8f59 plugin manager qui fonctionne 2018-07-25 11:23:11 +02:00
KLIPFEL Arthur 8301e1dfaa des trucs pas termine qui fonctionne plus ou moins 2018-07-24 14:04:08 +02:00
13 changed files with 448 additions and 221 deletions

View File

@ -4,6 +4,7 @@ pipeline:
group: build
commands:
- go get -v -d ./...
- sh build_plugins.sh
- go build .
test:
image: golang
@ -12,7 +13,8 @@ pipeline:
environment: [ test_api_token ]
commands:
- go get -v -d ./...
- go test ./...
- sh build_plugins.sh
- go test . ./commands ./settings ./plugin_manager/ ./shared/
publish:
image: plugins/docker
repo: klmp200/alfred

3
.gitignore vendored
View File

@ -16,5 +16,4 @@
settings_custom.json
history.json
users.json
chat_data.json
users.json

View File

@ -2,13 +2,14 @@
* @Author: Bartuccio Antoine
* @Date: 2018-07-23 15:24:22
* @Last Modified by: klmp200
* @Last Modified time: 2018-07-27 16:13:32
* @Last Modified time: 2018-07-25 14:31:13
*/
package main
import (
"./commands"
"./plugin_manager"
"./settings"
"./shared"
tb "gopkg.in/tucnak/telebot.v2"
@ -18,7 +19,7 @@ import (
func main() {
registered_commands := map[string]func(*tb.Message){
tb.OnText: commands.OnText,
// tb.OnText: commands.OnText,
"/hello": commands.Hello,
"/sponge": commands.Sponge,
"/git": commands.Git,
@ -26,9 +27,6 @@ func main() {
"/setgender": commands.SetGender,
"/gender": commands.Gender,
"/roll": commands.Dice,
"/trump": commands.LastTrumpTweet,
"/trends": commands.TwitterTrends,
"/chaos": commands.TwitterSJW,
}
if err := settings.LoadSettings("settings.json", "settings_custom.json"); err != nil {
@ -40,8 +38,6 @@ func main() {
settings.Settings["history file"].(string))
log.Println("Initialize users infos")
shared.InitUsers(settings.Settings["users file"].(string))
log.Println("Initialize chat data")
shared.InitChatData(settings.Settings["chat data file"].(string))
log.Println("Bot initialisation")
b, err := tb.NewBot(tb.Settings{
@ -58,6 +54,9 @@ func main() {
b.Handle(key, value)
}
plugin_manager.Init("plugin", b)
b.Handle(tb.OnText, plugin_manager.HandleMessage)
plugin_manager.StartPlugins()
log.Println("Starting bot")
b.Start()
}

12
build_plugins.sh Executable file
View File

@ -0,0 +1,12 @@
# @Author: Bartuccio Antoine
# @Date: 2018-07-25 12:47:23
# @Last Modified by: klmp200
# @Last Modified time: 2018-07-25 12:58:04
#!/bin/sh
cd plugin
rm -f *.so
FILES=`ls *.go`
for FILE in $FILES
do
go build -buildmode=plugin $FILE
done

View File

@ -2,7 +2,7 @@
* @Author: Bartuccio Antoine
* @Date: 2018-07-24 14:55:33
* @Last Modified by: klmp200
* @Last Modified time: 2018-07-25 01:29:53
* @Last Modified time: 2018-07-24 20:29:36
*/
package commands
@ -24,10 +24,6 @@ func SetGender(m *tb.Message) {
return
}
data := strings.Join(split, " ")
if data == "" {
shared.Bot.Send(m.Chat, "Attention, votre genre est vide. Ce n'est pas enregistrable.")
return
}
shared.Users.Set(m.Sender.Username, "gender", data)
shared.Bot.Send(m.Chat, "Votre genre est enregistré, je vous considère maintenant comme « "+data+" ».")
}
@ -56,12 +52,13 @@ func Gender(m *tb.Message) {
func cleanGender(slice []string) []string {
for i := range slice {
slice[i] = strings.Replace(slice[i], "\\", "", -1)
slice[i] = strings.Replace(slice[i], "@", "", -1)
clean := false
for !clean {
clean = true
if strings.HasPrefix(slice[i], "/") {
if strings.HasPrefix(slice[i], "@") {
slice[i] = strings.Replace(slice[i], "@", "", 1)
clean = false
} else if strings.HasPrefix(slice[i], "/") {
slice[i] = strings.Replace(slice[i], "/", "", 1)
clean = false
}

View File

@ -1,114 +0,0 @@
/*
* @Author: Bartuccio Antoine
* @Date: 2018-07-25 18:51:38
* @Last Modified by: klmp200
* @Last Modified time: 2018-07-27 16:49:59
*/
package commands
import (
"../settings"
"../shared"
"github.com/dghubble/go-twitter/twitter"
"github.com/dghubble/oauth1"
tb "gopkg.in/tucnak/telebot.v2"
"strconv"
"strings"
"time"
)
var client *twitter.Client
func initTwitter() {
config := oauth1.NewConfig(
settings.Settings["twitter consumer key"].(string),
settings.Settings["twitter consumer secret"].(string),
)
token := oauth1.NewToken(
settings.Settings["twitter access token"].(string),
settings.Settings["twitter access secret"].(string),
)
http_client := config.Client(oauth1.NoContext, token)
client = twitter.NewClient(http_client)
}
func testOrInitTwitter() {
if client == nil {
initTwitter()
}
}
func twitterCommunicationError(m *tb.Message) {
shared.Bot.Send(m.Chat, "Désolé, les serveurs de twitter sont injoignables.")
}
func LastTrumpTweet(m *tb.Message) {
testOrInitTwitter()
user, _, err := client.Users.Show(&twitter.UserShowParams{ScreenName: "realDonaldTrump"})
if err != nil {
twitterCommunicationError(m)
return
}
timeline, _, err := client.Timelines.UserTimeline(&twitter.UserTimelineParams{ScreenName: "realDonaldTrump"})
if err != nil {
twitterCommunicationError(m)
return
}
response := []string{
user.Name,
"\nFollowers : ",
strconv.Itoa(user.FollowersCount),
"\nStatus : ",
user.Description,
"\n ---",
"\n" + timeline[0].Text,
"\n ---",
"\n" + timeline[0].Source,
}
shared.Bot.Send(m.Chat, strings.Join(response, " "))
}
func TwitterTrends(m *tb.Message) {
testOrInitTwitter()
trends, _, err := client.Trends.Place(int64(615702), nil)
if err != nil {
twitterCommunicationError(m)
return
}
message := "Voici les dernières tendances en France"
for _, trend := range trends[0].Trends {
message += "\n" + trend.Name
}
shared.Bot.Send(m.Chat, message)
}
func TwitterSJW(m *tb.Message) {
testOrInitTwitter()
last_use, exists := shared.ChatData.Get(m.Chat.ID, "last chaos use")
if exists {
var date time.Time
if _, is_string := last_use.(string); is_string {
date, _ = time.Parse(time.RFC3339, last_use.(string))
} else {
date = last_use.(time.Time)
}
if time.Now().Before(date.Add(time.Hour * 24)) {
shared.Bot.Send(m.Chat, "Arioch ne répondra pas à votre appel.")
return
}
}
shared.ChatData.Set(m.Chat.ID, "last chaos use", time.Now())
tweets, _, err := client.Search.Tweets(&twitter.SearchTweetParams{
Query: "#SJW",
})
if err != nil {
twitterCommunicationError(m)
return
}
for _, tweet := range tweets.Statuses {
shared.Bot.Send(m.Chat, tweet.Text)
}
}

68
plugin/plugin.go Normal file
View File

@ -0,0 +1,68 @@
package main
import (
"../plugin_manager"
tb "gopkg.in/tucnak/telebot.v2"
)
type plugin string
func (g plugin) GetCommands() []string {
return []string{"plugin"}
}
func (g plugin) HandleCommand(bot *tb.Bot, msg *tb.Message, cmd string, args []string) {
if cmd == "plugin" {
ok := false
if len(args) >= 1 {
if args[0] == "list" {
lst := ""
for _, pName := range plugin_manager.GetPluginList() {
lst = lst + "-" + pName + " (status: "
if plugin_manager.IsPluginEnable(pName) {
lst += "enable, "
} else {
lst += "disable, "
}
if plugin_manager.IsPluginRunning(pName) {
lst += "running"
} else {
lst += "stopped"
}
lst += ")\n"
}
bot.Send(msg.Chat, "liste des plugins disponible:\n"+lst)
ok = true
}
if args[0] == "enable" && len(args) >= 2 {
for _, name := range args[1:] {
if plugin_manager.ExistPlugin(name) {
plugin_manager.EnablePlugin(name, true)
bot.Send(msg.Chat, "enable plugin "+name)
}
}
ok = true
}
if args[0] == "disable" && len(args) >= 2 {
for _, name := range args[1:] {
if plugin_manager.ExistPlugin(name) {
plugin_manager.EnablePlugin(name, false)
bot.Send(msg.Chat, "disable plugin "+name)
}
}
ok = true
}
}
if !ok {
bot.Send(msg.Chat, "command inconnue\n"+
"liste de plugin:\n"+
"/plugin list\n"+
"activer un/des plugins\n"+
"/plugin enable nom_plugin\n"+
"désactiver un/des plugins\n"+
"/plugin disable nom_plugin")
}
}
}
var Plugin plugin

40
plugin/test.go Normal file
View File

@ -0,0 +1,40 @@
package main
import (
tb "gopkg.in/tucnak/telebot.v2"
"log"
)
type plugin string
func (g plugin) GetCommands() []string {
return []string{"ping", "test"}
}
func (g plugin) Load() bool {
log.Println("plugin test loaded!")
return true
}
func (g plugin) HandleMessage(bot *tb.Bot, msg *tb.Message) {
log.Println("plugin test message: " + msg.Text)
}
func (g plugin) HandleCommand(bot *tb.Bot, msg *tb.Message, cmd string, args []string) {
if cmd == "ping" {
bot.Send(msg.Chat, "pong!")
}
argsS := ""
for _, arg := range args {
argsS = argsS + " " + arg
}
log.Print("plugin test cmd: " + cmd + " (args:" + argsS + ")")
}
func (g plugin) Unload() bool {
log.Println("plugin test unloaded!")
return true
}
var Plugin plugin

22
plugin/test2.go Normal file
View File

@ -0,0 +1,22 @@
package main
import (
tb "gopkg.in/tucnak/telebot.v2"
"log"
)
type plugin string
func (g plugin) Load() {
log.Println("plugin test2 loaded!")
}
func (g plugin) HandleMessage(bot *tb.Bot, msg string) {
log.Println("test2 message: " + msg)
}
func (g plugin) Unload() {
log.Println("plugin test2 unloaded!")
}
var Plugin plugin

162
plugin_manager/manager.go Normal file
View File

@ -0,0 +1,162 @@
/**
* @Author: KLIPFEL Arthur
* @Date: 2018-08-24 12:17:17
*/
package plugin_manager
import (
tb "gopkg.in/tucnak/telebot.v2"
"log"
"path/filepath"
"strings"
"sync"
)
type PluginCtrl struct {
plugin Plugin
mux sync.Mutex
running bool
enable bool
}
var pluginDir string
var pluginsRunning bool
var plugins map[string]PluginCtrl
var context *tb.Bot
func Init(_pluginDir string, bot *tb.Bot) {
pluginDir = _pluginDir
pluginsRunning = false
plugins = make(map[string]PluginCtrl)
context = bot
for _, fileName := range GetSoFiles(pluginDir) {
var p PluginCtrl
p.plugin = LoadSoFile(filepath.Join(pluginDir, fileName))
if p.plugin != nil {
p.running = false
p.enable = true
plugins[fileName[:len(fileName)-3]] = p
}
}
}
func GetPluginList() []string {
var lst []string
for name, _ := range plugins {
lst = append(lst, name)
}
return lst
}
func IsPluginRunning(name string) bool {
if p, ok := plugins[name]; ok {
return p.running
}
return false
}
func ExistPlugin(name string) bool {
if _, ok := plugins[name]; ok {
return true
}
return false
}
func GetPlugin(name string) Plugin {
if p, ok := plugins[name]; ok {
return p
}
return nil
}
func IsPluginEnable(name string) bool {
if p, ok := plugins[name]; ok {
return p.enable
}
return false
}
func EnablePlugin(name string, enable bool) {
if p, ok := plugins[name]; ok {
if enable != p.enable {
p.enable = enable
plugins[name] = p
if pluginsRunning {
if enable {
if !p.running {
startPlugin(name)
}
} else {
if p.running {
stopPlugin(name)
}
}
}
}
} else {
log.Fatal("error: plugin " + name + " not founded")
}
}
func StopPlugins() {
for k, _ := range plugins {
stopPlugin(k)
}
pluginsRunning = false
}
func HandleMessage(msg *tb.Message) {
for _, val := range plugins {
if val.enable && val.running {
if strings.HasPrefix(msg.Text, "/") {
split := strings.Split(msg.Text, " ")
split[0] = split[0][1:]
if Contains(split[0], ExecGetCommands(val.plugin)) {
ExecHandleCommand(val.plugin, context, msg, split[0], split[1:])
}
} else {
ExecHandleMessage(val.plugin, context, msg)
}
}
}
}
func StartPlugins() {
for k, val := range plugins {
if val.enable {
startPlugin(k)
}
}
pluginsRunning = true
}
func startPlugin(name string) {
if p, ok := plugins[name]; ok {
//p.mux.Lock()
if !p.running && p.enable {
p.running = ExecLoad(p.plugin)
plugins[name] = p
}
//p.mux.Unlock()
}
}
func stopPlugin(name string) {
if p, ok := plugins[name]; ok {
//p.mux.Lock()
if p.running {
p.running = false
plugins[name] = p
ExecUnload(p.plugin)
}
//p.mux.Unlock()
}
}
func Exit() {
for _, p := range plugins {
if p.running {
ExecUnload(p.plugin)
}
}
}

127
plugin_manager/tools.go Normal file
View File

@ -0,0 +1,127 @@
/**
* @Author: KLIPFEL Arthur
* @Date: 2018-08-24 12:17:17
*/
package plugin_manager
import (
tb "gopkg.in/tucnak/telebot.v2"
"io/ioutil"
"log"
"plugin"
"strings"
)
type TestGetCommands interface {
GetCommands() []string
}
type TestLoad interface {
Load() bool
}
type TestHandleMessage interface {
HandleMessage(bot *tb.Bot, msg *tb.Message)
}
type TestHandleCommand interface {
HandleCommand(bot *tb.Bot, msg *tb.Message, cmd string, args []string)
}
type TestUnload interface {
Unload() bool
}
type Plugin interface { /*
GetCommandes() []string
Load() bool
HandleMessage(bot *tb.Bot, msg *tb.Message)
HandleCommand(bot *tb.Bot, msg *tb.Message, cmd string, args []string)
Unload() bool*/
}
func GetSoFiles(dir string) []string {
var slice []string
files, err := ioutil.ReadDir(dir)
if err != nil {
log.Fatal(err)
} else {
for _, f := range files {
if strings.HasSuffix(f.Name(), ".so") {
slice = append(slice, f.Name())
}
}
}
return slice
}
func LoadSoFile(file string) Plugin {
plug, err := plugin.Open(file)
if err != nil {
log.Fatal(err)
return nil
}
symPlugin, err := plug.Lookup("Plugin")
if err != nil {
log.Fatal(err)
return nil
}
var plugin Plugin
//plugin, _ = symPlugin.(Plugin)
plugin, ok := symPlugin.(Plugin)
if !ok {
log.Fatal(file + ": unexpected type from module symbol")
return nil
}
return plugin
}
func ExecGetCommands(plugin Plugin) []string {
p, ok := plugin.(TestGetCommands)
if ok {
return p.GetCommands()
}
return []string{}
}
func ExecLoad(plugin Plugin) bool {
p, ok := plugin.(TestLoad)
if ok {
return p.Load()
}
return true
}
func ExecHandleMessage(plugin Plugin, bot *tb.Bot, msg *tb.Message) {
p, ok := plugin.(TestHandleMessage)
if ok {
p.HandleMessage(bot, msg)
}
}
func ExecHandleCommand(plugin Plugin, bot *tb.Bot, msg *tb.Message, cmd string, args []string) {
p, ok := plugin.(TestHandleCommand)
if ok {
p.HandleCommand(bot, msg, cmd, args)
}
}
func ExecUnload(plugin Plugin) bool {
p, ok := plugin.(TestUnload)
if ok {
return p.Unload()
}
return true
}
func Contains(e string, s []string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}

View File

@ -1,11 +1,6 @@
{
"token": "INSERT TOKEN HERE",
"twitter access token": "INSERT TOKEN HERE",
"twitter access secret": "INSERT TOKEN HERE",
"twitter consumer key": "INSERT TOKEN HERE",
"twitter consumer secret": "INSERT TOKEN HERE",
"history size": 10,
"history file": "history.json",
"users file": "users.json",
"chat data file": "chat_data.json"
}
"users file": "users.json"
}

View File

@ -1,82 +0,0 @@
/*
* @Author: Bartuccio Antoine
* @Date: 2018-07-27 15:37:59
* @Last Modified by: klmp200
* @Last Modified time: 2018-07-27 16:06:51
*/
package shared
import (
"encoding/json"
"io/ioutil"
"sync"
)
// General purpose chat info storage
type chatData struct {
mutex sync.Mutex
data map[int64]map[string]interface{}
}
type chatDataFile struct {
mutex sync.Mutex
path string
}
var ChatData chatData
var cdf chatDataFile
// Init chat data meant to store infos about a chat.
func InitChatData(path string) {
cdf = chatDataFile{path: path}
ChatData = chatData{data: make(map[int64]map[string]interface{})}
ChatData.mutex.Lock()
defer ChatData.mutex.Unlock()
cdf.read()
}
func (c chatData) Set(chat int64, key string, data interface{}) {
c.mutex.Lock()
defer c.mutex.Unlock()
if _, exists := c.data[chat]; !exists {
c.data[chat] = make(map[string]interface{})
}
c.data[chat][key] = data
go cdf.write()
}
func (c chatData) Get(chat int64, key string) (interface{}, bool) {
c.mutex.Lock()
defer c.mutex.Unlock()
m, exists := c.data[chat]
if !exists {
return nil, false
}
data, ok := m[key]
if !ok {
return nil, false
}
return data, true
}
func (c chatDataFile) read() {
c.mutex.Lock()
defer c.mutex.Unlock()
data, err := ioutil.ReadFile(c.path)
if err != nil {
// File doesn't exist, skip import
return
}
json.Unmarshal(data, &ChatData.data)
}
func (c chatDataFile) write() {
c.mutex.Lock()
defer c.mutex.Unlock()
ChatData.mutex.Lock()
defer ChatData.mutex.Unlock()
data, _ := json.Marshal(ChatData.data)
ioutil.WriteFile(c.path, data, 0770)
}