Initial commit, lots of cleanup and stuff to do, it may not work.

This commit is contained in:
Storm Dragon
2025-01-15 23:43:44 -05:00
commit 3f0246a4f8
80 changed files with 11306 additions and 0 deletions

55
gumble/gumbleutil/acl.go Normal file
View File

@ -0,0 +1,55 @@
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
import (
"git.2mb.codes/~cmb/barnard/gumble/gumble"
)
// UserGroups fetches the group names the given user belongs to in the given
// channel. The slice of group names sent via the returned channel. On error,
// the returned channel is closed without without sending a slice.
func UserGroups(client *gumble.Client, user *gumble.User, channel *gumble.Channel) <-chan []string {
ch := make(chan []string)
if !user.IsRegistered() {
close(ch)
return ch
}
var detacher gumble.Detacher
listener := Listener{
Disconnect: func(e *gumble.DisconnectEvent) {
detacher.Detach()
close(ch)
},
ChannelChange: func(e *gumble.ChannelChangeEvent) {
if e.Channel == channel && e.Type.Has(gumble.ChannelChangeRemoved) {
detacher.Detach()
close(ch)
}
},
PermissionDenied: func(e *gumble.PermissionDeniedEvent) {
if e.Channel == channel && e.Type == gumble.PermissionDeniedPermission && (e.Permission&gumble.PermissionWrite) != 0 {
detacher.Detach()
close(ch)
}
},
ACL: func(e *gumble.ACLEvent) {
if e.ACL.Channel != channel {
return
}
var names []string
for _, g := range e.ACL.Groups {
if (g.UsersAdd[user.UserID] != nil || g.UsersInherited[user.UserID] != nil) && g.UsersRemove[user.UserID] == nil {
names = append(names, g.Name)
}
}
detacher.Detach()
ch <- names
close(ch)
},
}
detacher = client.Config.Attach(&listener)
channel.RequestACL()
return ch
}

View File

@ -0,0 +1,27 @@
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
import (
"time"
"git.2mb.codes/~cmb/barnard/gumble/gumble"
)
var autoBitrate = &Listener{
Connect: func(e *gumble.ConnectEvent) {
if e.MaximumBitrate != nil {
const safety = 5
interval := e.Client.Config.AudioInterval
dataBytes := (*e.MaximumBitrate / (8 * (int(time.Second/interval) + safety))) - 32 - 10
e.Client.Config.AudioDataBytes = dataBytes
}
},
}
// AutoBitrate is a gumble.EventListener that automatically sets the client's
// AudioDataBytes to suitable value, based on the server's bitrate.
var AutoBitrate gumble.EventListener
func init() {
AutoBitrate = autoBitrate
}

View File

@ -0,0 +1,18 @@
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
import (
"git.2mb.codes/~cmb/barnard/gumble/gumble"
)
// ChannelPath returns a slice of channel names, starting from the root channel
// to the given channel.
func ChannelPath(channel *gumble.Channel) []string {
var pieces []string
for ; channel != nil; channel = channel.Parent {
pieces = append(pieces, channel.Name)
}
for i := 0; i < (len(pieces) / 2); i++ {
pieces[len(pieces)-1-i], pieces[i] = pieces[i], pieces[len(pieces)-1-i]
}
return pieces
}

2
gumble/gumbleutil/doc.go Normal file
View File

@ -0,0 +1,2 @@
// Package gumbleutil provides extras that can make working with gumble easier.
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"

View File

@ -0,0 +1,100 @@
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
import (
"git.2mb.codes/~cmb/barnard/gumble/gumble"
)
// Listener is a struct that implements the gumble.EventListener interface. The
// corresponding event function in the struct is called if it is non-nil.
type Listener struct {
Connect func(e *gumble.ConnectEvent)
Disconnect func(e *gumble.DisconnectEvent)
TextMessage func(e *gumble.TextMessageEvent)
UserChange func(e *gumble.UserChangeEvent)
ChannelChange func(e *gumble.ChannelChangeEvent)
PermissionDenied func(e *gumble.PermissionDeniedEvent)
UserList func(e *gumble.UserListEvent)
ACL func(e *gumble.ACLEvent)
BanList func(e *gumble.BanListEvent)
ContextActionChange func(e *gumble.ContextActionChangeEvent)
ServerConfig func(e *gumble.ServerConfigEvent)
}
var _ gumble.EventListener = (*Listener)(nil)
// OnConnect implements gumble.EventListener.OnConnect.
func (l Listener) OnConnect(e *gumble.ConnectEvent) {
if l.Connect != nil {
l.Connect(e)
}
}
// OnDisconnect implements gumble.EventListener.OnDisconnect.
func (l Listener) OnDisconnect(e *gumble.DisconnectEvent) {
if l.Disconnect != nil {
l.Disconnect(e)
}
}
// OnTextMessage implements gumble.EventListener.OnTextMessage.
func (l Listener) OnTextMessage(e *gumble.TextMessageEvent) {
if l.TextMessage != nil {
l.TextMessage(e)
}
}
// OnUserChange implements gumble.EventListener.OnUserChange.
func (l Listener) OnUserChange(e *gumble.UserChangeEvent) {
if l.UserChange != nil {
l.UserChange(e)
}
}
// OnChannelChange implements gumble.EventListener.OnChannelChange.
func (l Listener) OnChannelChange(e *gumble.ChannelChangeEvent) {
if l.ChannelChange != nil {
l.ChannelChange(e)
}
}
// OnPermissionDenied implements gumble.EventListener.OnPermissionDenied.
func (l Listener) OnPermissionDenied(e *gumble.PermissionDeniedEvent) {
if l.PermissionDenied != nil {
l.PermissionDenied(e)
}
}
// OnUserList implements gumble.EventListener.OnUserList.
func (l Listener) OnUserList(e *gumble.UserListEvent) {
if l.UserList != nil {
l.UserList(e)
}
}
// OnACL implements gumble.EventListener.OnACL.
func (l Listener) OnACL(e *gumble.ACLEvent) {
if l.ACL != nil {
l.ACL(e)
}
}
// OnBanList implements gumble.EventListener.OnBanList.
func (l Listener) OnBanList(e *gumble.BanListEvent) {
if l.BanList != nil {
l.BanList(e)
}
}
// OnContextActionChange implements gumble.EventListener.OnContextActionChange.
func (l Listener) OnContextActionChange(e *gumble.ContextActionChangeEvent) {
if l.ContextActionChange != nil {
l.ContextActionChange(e)
}
}
// OnServerConfig implements gumble.EventListener.OnServerConfig.
func (l Listener) OnServerConfig(e *gumble.ServerConfigEvent) {
if l.ServerConfig != nil {
l.ServerConfig(e)
}
}

View File

@ -0,0 +1,80 @@
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
import (
"git.2mb.codes/~cmb/barnard/gumble/gumble"
)
// ListenerFunc is a single listener function that implements the
// gumble.EventListener interface. This is useful if you would like to use a
// type-switch for handling the different event types.
//
// Example:
// handler := func(e interface{}) {
// switch e.(type) {
// case *gumble.ConnectEvent:
// println("Connected")
// case *gumble.DisconnectEvent:
// println("Disconnected")
// // ...
// }
// }
//
// client.Attach(gumbleutil.ListenerFunc(handler))
type ListenerFunc func(e interface{})
var _ gumble.EventListener = ListenerFunc(nil)
// OnConnect implements gumble.EventListener.OnConnect.
func (lf ListenerFunc) OnConnect(e *gumble.ConnectEvent) {
lf(e)
}
// OnDisconnect implements gumble.EventListener.OnDisconnect.
func (lf ListenerFunc) OnDisconnect(e *gumble.DisconnectEvent) {
lf(e)
}
// OnTextMessage implements gumble.EventListener.OnTextMessage.
func (lf ListenerFunc) OnTextMessage(e *gumble.TextMessageEvent) {
lf(e)
}
// OnUserChange implements gumble.EventListener.OnUserChange.
func (lf ListenerFunc) OnUserChange(e *gumble.UserChangeEvent) {
lf(e)
}
// OnChannelChange implements gumble.EventListener.OnChannelChange.
func (lf ListenerFunc) OnChannelChange(e *gumble.ChannelChangeEvent) {
lf(e)
}
// OnPermissionDenied implements gumble.EventListener.OnPermissionDenied.
func (lf ListenerFunc) OnPermissionDenied(e *gumble.PermissionDeniedEvent) {
lf(e)
}
// OnUserList implements gumble.EventListener.OnUserList.
func (lf ListenerFunc) OnUserList(e *gumble.UserListEvent) {
lf(e)
}
// OnACL implements gumble.EventListener.OnACL.
func (lf ListenerFunc) OnACL(e *gumble.ACLEvent) {
lf(e)
}
// OnBanList implements gumble.EventListener.OnBanList.
func (lf ListenerFunc) OnBanList(e *gumble.BanListEvent) {
lf(e)
}
// OnContextActionChange implements gumble.EventListener.OnContextActionChange.
func (lf ListenerFunc) OnContextActionChange(e *gumble.ContextActionChangeEvent) {
lf(e)
}
// OnServerConfig implements gumble.EventListener.OnServerConfig.
func (lf ListenerFunc) OnServerConfig(e *gumble.ServerConfigEvent) {
lf(e)
}

79
gumble/gumbleutil/main.go Normal file
View File

@ -0,0 +1,79 @@
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
import (
"crypto/tls"
"flag"
"fmt"
"net"
"os"
"strconv"
"git.2mb.codes/~cmb/barnard/gumble/gumble"
)
// Main aids in the creation of a basic command line gumble bot. It accepts the
// following flag arguments:
// --server
// --username
// --password
// --insecure
// --certificate
// --key
func Main(listeners ...gumble.EventListener) {
server := flag.String("server", "localhost:64738", "Mumble server address")
username := flag.String("username", "gumble-bot", "client username")
password := flag.String("password", "", "client password")
insecure := flag.Bool("insecure", false, "skip server certificate verification")
certificateFile := flag.String("certificate", "", "user certificate file (PEM)")
keyFile := flag.String("key", "", "user certificate key file (PEM)")
if !flag.Parsed() {
flag.Parse()
}
host, port, err := net.SplitHostPort(*server)
if err != nil {
host = *server
port = strconv.Itoa(gumble.DefaultPort)
}
keepAlive := make(chan bool)
config := gumble.NewConfig()
config.Username = *username
config.Password = *password
config.Address = net.JoinHostPort(host, port)
var tlsConfig tls.Config
if *insecure {
tlsConfig.InsecureSkipVerify = true
}
if *certificateFile != "" {
if *keyFile == "" {
keyFile = certificateFile
}
if certificate, err := tls.LoadX509KeyPair(*certificateFile, *keyFile); err != nil {
fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], err)
os.Exit(1)
} else {
tlsConfig.Certificates = append(tlsConfig.Certificates, certificate)
}
}
config.Attach(AutoBitrate)
for _, listener := range listeners {
config.Attach(listener)
}
config.Attach(Listener{
Disconnect: func(e *gumble.DisconnectEvent) {
keepAlive <- true
},
})
_, err = gumble.DialWithDialer(new(net.Dialer), config, &tlsConfig)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], err)
os.Exit(1)
}
<-keepAlive
}

View File

@ -0,0 +1,45 @@
package gumbleutil // import "git.2mb.codes/~cmb/barnard/gumble/gumbleutil"
import (
"bytes"
"encoding/xml"
"strings"
"git.2mb.codes/~cmb/barnard/gumble/gumble"
)
// PlainText returns the Message string without HTML tags or entities.
func PlainText(tm *gumble.TextMessage) string {
d := xml.NewDecoder(strings.NewReader(tm.Message))
d.Strict = false
d.AutoClose = xml.HTMLAutoClose
d.Entity = xml.HTMLEntity
var b bytes.Buffer
newline := false
for {
t, _ := d.Token()
if t == nil {
break
}
switch node := t.(type) {
case xml.CharData:
if len(node) > 0 {
b.Write(node)
newline = false
}
case xml.StartElement:
switch node.Name.Local {
case "address", "article", "aside", "audio", "blockquote", "canvas", "dd", "div", "dl", "fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "noscript", "ol", "output", "p", "pre", "section", "table", "tfoot", "ul", "video":
if !newline {
b.WriteByte('\n')
newline = true
}
case "br":
b.WriteByte('\n')
newline = true
}
}
}
return b.String()
}