Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
304c645043 | ||
|
|
31fd9f9a56 | ||
|
|
e68503c72e | ||
|
|
32b5649465 | ||
|
|
e949a980fb | ||
|
|
9a0292c2fa | ||
|
|
d109349657 | ||
|
|
d4b3f5a5db |
Generated
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
"isupport",
|
"isupport",
|
||||||
"list"
|
"list"
|
||||||
]
|
]
|
||||||
revision = "b4c7c5145f2e2c430d6a74035751e6d59df7ec32"
|
revision = "c1e97eb9d2e383147662ddd649fcab1bdaf295b4"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/dgrijalva/jwt-go"
|
name = "github.com/dgrijalva/jwt-go"
|
||||||
|
|||||||
+17
-1
@@ -3,6 +3,7 @@ package bot
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.aiterp.net/gisle/irc"
|
"git.aiterp.net/gisle/irc"
|
||||||
@@ -78,7 +79,7 @@ func (bot *Bot) addChannel(channelName string) *Channel {
|
|||||||
return bot.channels[channelName]
|
return bot.channels[channelName]
|
||||||
}
|
}
|
||||||
|
|
||||||
channel := newChannel(bot.ctx, channelName, bot.client)
|
channel := newChannel(bot, channelName, bot.client)
|
||||||
go channel.loop()
|
go channel.loop()
|
||||||
|
|
||||||
bot.channels[channelName] = channel
|
bot.channels[channelName] = channel
|
||||||
@@ -93,6 +94,21 @@ func (bot *Bot) handlePost(channelName string, post ChannelPost) {
|
|||||||
channel.ch <- post
|
channel.ch <- post
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bot *Bot) runCommands(commands []string, target irc.Target, replacers map[string]string) {
|
||||||
|
if replacers == nil {
|
||||||
|
replacers = make(map[string]string)
|
||||||
|
}
|
||||||
|
replacers["me"] = bot.client.Nick()
|
||||||
|
|
||||||
|
for _, command := range commands {
|
||||||
|
for from, to := range replacers {
|
||||||
|
command = strings.Replace(command, "%"+from, to, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.client.EmitInput(command, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (bot *Bot) loop() {
|
func (bot *Bot) loop() {
|
||||||
bot.loopCtx, bot.loopCancel = context.WithCancel(bot.ctx)
|
bot.loopCtx, bot.loopCancel = context.WithCancel(bot.ctx)
|
||||||
|
|
||||||
|
|||||||
+45
-4
@@ -7,6 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.aiterp.net/gisle/irc"
|
"git.aiterp.net/gisle/irc"
|
||||||
|
"git.aiterp.net/rpdata/logbot3/internal/config"
|
||||||
"git.aiterp.net/rpdata/logbot3/internal/models/channels"
|
"git.aiterp.net/rpdata/logbot3/internal/models/channels"
|
||||||
"git.aiterp.net/rpdata/logbot3/internal/models/logs"
|
"git.aiterp.net/rpdata/logbot3/internal/models/logs"
|
||||||
"git.aiterp.net/rpdata/logbot3/internal/models/posts"
|
"git.aiterp.net/rpdata/logbot3/internal/models/posts"
|
||||||
@@ -23,15 +24,17 @@ type Channel struct {
|
|||||||
|
|
||||||
name string
|
name string
|
||||||
parentCtx context.Context
|
parentCtx context.Context
|
||||||
|
parent *Bot
|
||||||
client *irc.Client
|
client *irc.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func newChannel(parentCtx context.Context, name string, client *irc.Client) *Channel {
|
func newChannel(parent *Bot, name string, client *irc.Client) *Channel {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
return &Channel{
|
return &Channel{
|
||||||
name: name,
|
name: name,
|
||||||
parentCtx: parentCtx,
|
parent: parent,
|
||||||
|
parentCtx: parent.ctx,
|
||||||
client: client,
|
client: client,
|
||||||
ch: make(chan ChannelPost, 8),
|
ch: make(chan ChannelPost, 8),
|
||||||
|
|
||||||
@@ -47,6 +50,7 @@ func (channel *Channel) loop() {
|
|||||||
defer channel.cancel()
|
defer channel.cancel()
|
||||||
defer minutely.Stop()
|
defer minutely.Stop()
|
||||||
|
|
||||||
|
// In case of a crash, it should take ownership of sessions when rejoining.
|
||||||
session, err := logs.FindOpen(channel.parentCtx, channel.name)
|
session, err := logs.FindOpen(channel.parentCtx, channel.name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
minsUntilDeadline := 1 + int(((time.Hour * 2) - time.Since(session.LatestTime())).Minutes())
|
minsUntilDeadline := 1 + int(((time.Hour * 2) - time.Since(session.LatestTime())).Minutes())
|
||||||
@@ -60,12 +64,27 @@ func (channel *Channel) loop() {
|
|||||||
channel.lastPostTime = session.LatestTime()
|
channel.lastPostTime = session.LatestTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the bot is op, run the commands from the configuration.
|
||||||
|
go func() {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
commands := config.Get().Commands.OnJoinOp
|
||||||
|
target := channel.client.Channel(channel.name)
|
||||||
|
|
||||||
|
if len(commands) > 0 && target != nil {
|
||||||
|
me, ok := target.UserList().User(channel.client.Nick())
|
||||||
|
if ok && strings.ContainsRune(me.Modes, 'o') {
|
||||||
|
channel.parent.runCommands(commands, target, map[string]string{
|
||||||
|
"chan": channel.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case post := <-channel.ch:
|
case post := <-channel.ch:
|
||||||
{
|
{
|
||||||
log.Printf("Received %s post from %s", post.Kind, post.Nick)
|
|
||||||
|
|
||||||
// Handle bot commands, or add to queue otherwise.
|
// Handle bot commands, or add to queue otherwise.
|
||||||
if cmd := post.botCommand(); cmd != nil {
|
if cmd := post.botCommand(); cmd != nil {
|
||||||
switch cmd.Verb {
|
switch cmd.Verb {
|
||||||
@@ -107,6 +126,25 @@ func (channel *Channel) loop() {
|
|||||||
log.Println("Could not set event name:", err)
|
log.Println("Could not set event name:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "register":
|
||||||
|
{
|
||||||
|
target := channel.client.Channel(channel.name)
|
||||||
|
if target != nil {
|
||||||
|
me, ok := target.UserList().User(channel.client.Nick())
|
||||||
|
if !ok || !strings.ContainsRune(me.Modes, 'o') {
|
||||||
|
channel.client.Say(channel.name, "This unit require operator privileges to serve this request.")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
chanServNick := config.Get().Names.ChanServ
|
||||||
|
if chanServNick == "" {
|
||||||
|
channel.client.Say(channel.name, "This function is not enabled.")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.client.Sayf(chanServNick, "REGISTER %s", target.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
queue = append(queue, post)
|
queue = append(queue, post)
|
||||||
@@ -116,6 +154,9 @@ func (channel *Channel) loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Posts after here aren't going to be bot commands, so log its reception.
|
||||||
|
log.Printf("Received %s post from %s", post.Kind, post.Nick)
|
||||||
|
|
||||||
// Stop here if there's nothing to post.
|
// Stop here if there's nothing to post.
|
||||||
if len(queue) == 0 {
|
if len(queue) == 0 {
|
||||||
break
|
break
|
||||||
|
|||||||
+53
-11
@@ -2,8 +2,11 @@ package bot
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.aiterp.net/rpdata/logbot3/internal/util"
|
||||||
|
|
||||||
"git.aiterp.net/gisle/irc"
|
"git.aiterp.net/gisle/irc"
|
||||||
"git.aiterp.net/rpdata/logbot3/internal/config"
|
"git.aiterp.net/rpdata/logbot3/internal/config"
|
||||||
)
|
)
|
||||||
@@ -20,6 +23,8 @@ func handler(event *irc.Event, client *irc.Client) {
|
|||||||
{
|
{
|
||||||
log.Println("Client is ready!")
|
log.Println("Client is ready!")
|
||||||
go bot.loop()
|
go bot.loop()
|
||||||
|
|
||||||
|
bot.runCommands(config.Get().Commands.OnReady, bot.client.Status(), nil)
|
||||||
}
|
}
|
||||||
case "client.disconnect":
|
case "client.disconnect":
|
||||||
{
|
{
|
||||||
@@ -50,51 +55,88 @@ func handler(event *irc.Event, client *irc.Client) {
|
|||||||
|
|
||||||
case "ctcp.action", "packet.privmsg":
|
case "ctcp.action", "packet.privmsg":
|
||||||
{
|
{
|
||||||
|
// The echo-message polyfill will return to sender.
|
||||||
if event.Nick == bot.client.Nick() {
|
if event.Nick == bot.client.Nick() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only continue with channel messages, but log the others.
|
||||||
channel := event.ChannelTarget()
|
channel := event.ChannelTarget()
|
||||||
if channel == nil {
|
if channel == nil {
|
||||||
|
if event.Verb() == "action" {
|
||||||
|
log.Printf("Message: * %s %s", event.Nick, event.Text)
|
||||||
|
} else {
|
||||||
|
log.Printf("Message: <%s> %s", event.Nick, event.Text)
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse account, just in case it'll be useful one day.
|
||||||
account := ""
|
account := ""
|
||||||
if user, ok := bot.client.FindUser(event.Nick); ok {
|
if user, ok := bot.client.FindUser(event.Nick); ok {
|
||||||
account = user.Account
|
account = user.Account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the kind of psot this is.
|
||||||
kind := ""
|
kind := ""
|
||||||
if event.Verb() == "action" {
|
if strings.HasPrefix(event.Nick, "=") || strings.HasPrefix(event.Nick, "\x1F=") {
|
||||||
kind = "action"
|
|
||||||
} else {
|
|
||||||
if strings.HasPrefix(event.Nick, "=") {
|
|
||||||
kind = "scene"
|
kind = "scene"
|
||||||
|
} else if strings.ToLower(event.Verb()) == "action" {
|
||||||
|
kind = "action"
|
||||||
} else {
|
} else {
|
||||||
kind = "text"
|
kind = "text"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Format NPC posts' nick and text.
|
||||||
|
nick := event.Nick
|
||||||
|
text := event.Text
|
||||||
|
if strings.HasPrefix(event.Nick, "\x1F") || kind == "scene" {
|
||||||
|
nick = strings.Replace(event.Nick, "\x1F", "", 2)
|
||||||
|
text = util.RemoveNPCAttribution(event.Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
post := ChannelPost{
|
// Do not dispatch empty messages.
|
||||||
|
if len(text) < 1 || len(nick) < 1 || len(kind) < 1 {
|
||||||
|
log.Println("Ignoring empty message from:", event.Nick)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch it.
|
||||||
|
bot.handlePost(channel.Name(), ChannelPost{
|
||||||
Account: account,
|
Account: account,
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
Nick: event.Nick,
|
Nick: nick,
|
||||||
Time: event.Time,
|
Time: event.Time,
|
||||||
Text: event.Text,
|
Text: text,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
bot.handlePost(channel.Name(), post)
|
// Log notices
|
||||||
|
case "packet.notice":
|
||||||
|
{
|
||||||
|
log.Printf("Message: -%s- %s", event.Nick, event.Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log initial numerics for debugging's sake
|
// Log initial numerics for debugging
|
||||||
case "packet.001", "packet.002", "packet.003", "packet.251", "packet.255", "packet.265", "packet.266", "packet.250", "packet.375", "packet.372", "packet.376":
|
case "packet.001", "packet.002", "packet.003", "packet.251", "packet.255", "packet.265", "packet.266", "packet.250", "packet.375", "packet.372", "packet.376":
|
||||||
{
|
{
|
||||||
log.Printf("(%s) %s\n", event.Verb(), event.Text)
|
log.Printf("(%s) %s\n", event.Verb(), event.Text)
|
||||||
}
|
}
|
||||||
case "packet.005", "packet.254":
|
case "packet.005", "packet.cap", "packet.254":
|
||||||
{
|
{
|
||||||
log.Printf("(%s) %s %s\n", event.Verb(), strings.Join(event.Args, " "), event.Text)
|
log.Printf("(%s) %s %s\n", event.Verb(), strings.Join(event.Args[1:], " "), event.Text)
|
||||||
}
|
}
|
||||||
|
case "packet.error":
|
||||||
|
{
|
||||||
|
log.Printf("(%s) %s\n", event.Verb(), event.Text)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always log error numerics.
|
||||||
|
if len(event.Verb()) == 3 && event.Verb()[0] == '4' || event.Verb()[0] == '9' {
|
||||||
|
log.Printf("(%s) %s %s\n", event.Verb(), strings.Join(event.Args, " "), event.Text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,13 @@ type Config struct {
|
|||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
} `json:"key"`
|
} `json:"key"`
|
||||||
} `json:"api"`
|
} `json:"api"`
|
||||||
|
Commands struct {
|
||||||
|
OnJoinOp []string `json:"onJoinOp"`
|
||||||
|
OnReady []string `json:"onReady"`
|
||||||
|
} `json:"commands"`
|
||||||
|
Names struct {
|
||||||
|
ChanServ string `json:"chanserv"`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var mutex sync.Mutex
|
var mutex sync.Mutex
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RemoveNPCAttribution removes NPC attribution.
|
||||||
|
func RemoveNPCAttribution(str string) string {
|
||||||
|
lastOpen := strings.LastIndex(str, "(")
|
||||||
|
lastClose := strings.LastIndex(str, ")")
|
||||||
|
ending := len(str) - 1
|
||||||
|
|
||||||
|
if lastOpen > 0 && str[lastOpen-1] != '(' && (lastClose == -1 || lastClose == ending) {
|
||||||
|
cutoff := lastOpen
|
||||||
|
for cutoff > 1 && str[cutoff-1] == ' ' {
|
||||||
|
cutoff--
|
||||||
|
}
|
||||||
|
|
||||||
|
return str[:cutoff]
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package util_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.aiterp.net/rpdata/logbot3/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemoveNPCAttribution(t *testing.T) {
|
||||||
|
table := []struct {
|
||||||
|
Input string
|
||||||
|
Output string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Input: "Character does stuff. (Player_Name)",
|
||||||
|
Output: "Character does stuff.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "Text (with some clarification) about stuff.",
|
||||||
|
Output: "Text (with some clarification) about stuff.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "Text without anything.",
|
||||||
|
Output: "Text without anything.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "((*Correction))",
|
||||||
|
Output: "((*Correction))",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: "((Remove :01 goofs!*))",
|
||||||
|
Output: "((Remove :01 goofs!*))",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, row := range table {
|
||||||
|
t.Run(fmt.Sprintf("Row_%d", i), func(t *testing.T) {
|
||||||
|
result := util.RemoveNPCAttribution(row.Input)
|
||||||
|
|
||||||
|
if result != row.Output {
|
||||||
|
t.Errorf("Row %d failed", i)
|
||||||
|
t.Errorf("Expected: %#+v", row.Output)
|
||||||
|
t.Errorf("Result: %#+v", result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user