Created
April 8, 2024 18:57
-
-
Save dontlaugh/715129e8d306a9df7296f6e140d5d5a0 to your computer and use it in GitHub Desktop.
xmpp connect and join muc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package xmpp | |
import ( | |
"context" | |
"encoding/xml" | |
"fmt" | |
"io" | |
"log" | |
"os" | |
"os/exec" | |
"path/filepath" | |
"strings" | |
"mellium.im/sasl" | |
"mellium.im/xmlstream" | |
"mellium.im/xmpp" | |
"mellium.im/xmpp/dial" | |
"mellium.im/xmpp/jid" | |
"mellium.im/xmpp/muc" | |
"mellium.im/xmpp/stanza" | |
) | |
// MessageBody is a message stanza that contains a body. It is normally used for | |
// chat messages. | |
type MessageBody struct { | |
stanza.Message | |
Body string `xml:"body"` | |
} | |
// Config models the required information to make an XMPP connection. | |
type Config struct { | |
// JID embeds a user and server: [email protected] | |
JID string `json:"jid"` | |
Password string `json:"password"` | |
} | |
// Client connects to an XMPP server and starts listening for bang "!" commands | |
type Client struct { | |
Ctx context.Context | |
conf *Config | |
session *xmpp.Session | |
mucClient *muc.Client | |
hostname string | |
} | |
func (c *Client) Start() error { | |
ctx := context.Background() | |
log.Println("dialing", c.conf.JID) | |
j := jid.MustParse(c.conf.JID) | |
// dd := dial.Dialer{NoLookup: true} | |
dd := dial.Dialer{NoLookup: true} | |
conn, err := dd.Dial(ctx, "tcp", j) | |
if err != nil { | |
return err | |
} | |
log.Println("starting session") | |
sess, err := xmpp.NewClientSession(ctx, j, conn, xmpp.BindResource(), xmpp.StartTLS(nil), | |
xmpp.SASL("", c.conf.Password, sasl.ScramSha1, sasl.Plain)) | |
if err != nil { | |
return err | |
} | |
c.session = sess | |
log.Println("send initial presence") | |
// Send initial presence to let the server know we want to receive messages. | |
err = c.session.Send(context.TODO(), stanza.Presence{Type: stanza.AvailablePresence}.Wrap(nil)) | |
if err != nil { | |
return fmt.Errorf("Error sending initial presence: %q", err) | |
} | |
roomJID, err := jid.Parse("[email protected]/botnickname") | |
if err != nil { | |
return err | |
} | |
mucClient := &muc.Client{} | |
go func() { | |
log.Println("join muc") | |
_, err = mucClient.Join(ctx, roomJID, c.session, muc.MaxHistory(0)) | |
if err != nil { | |
log.Fatalf("Failed to join chatroom: %v", err) | |
} | |
}() | |
c.mucClient = mucClient | |
log.Println("serve") | |
// Hmm... passing ourselves in is weird. But Client is what implements ServeXMPP, | |
// and that's what the session's Serve method wants. Keep an eye on this. | |
return c.session.Serve(c) | |
} | |
func (c *Client) Stop() error { | |
if err := c.session.Close(); err != nil { | |
return fmt.Errorf("close session: %w", err) | |
} | |
if err := c.session.Conn().Close(); err != nil { | |
return fmt.Errorf("close connection: %w", err) | |
} | |
return nil | |
} | |
// NewClient builds a client. | |
func NewClient(conf *Config) (*Client, error) { | |
// Build up this client, except for session that gets created in Start | |
var c Client | |
c.conf = conf | |
// Set hostname | |
hostname, err := os.Hostname() | |
if err != nil { | |
return nil, err | |
} | |
c.hostname = hostname | |
return &c, nil | |
} | |
func (c *Client) HandleXMPP(t xmlstream.TokenReadEncoder, start *xml.StartElement) error { | |
// https://codeberg.org/mellium/xmpp/pulls/172/files | |
d := xml.NewTokenDecoder(xmlstream.MultiReader(xmlstream.Token(*start), t)) | |
d.Token() | |
// Ignore anything that's not a message. In a real system we'd want to at | |
// least respond to IQs. | |
if start.Name.Local != "message" { | |
return nil | |
} | |
var msg MessageBody | |
err := d.DecodeElement(&msg, start) | |
if err != nil && err != io.EOF { | |
log.Printf("Error decoding message: %q", err) | |
return nil | |
} | |
// Ignore unless they are chat messages and have a body. | |
if msg.Body == "" || msg.Type != stanza.ChatMessage { | |
return nil | |
} | |
// features | |
// * package update | |
// * package install | |
// * uptime | |
ctx := context.Background() | |
log.Println("body", msg.Body) | |
/* take action here */ | |
if strings.HasPrefix(msg.Body, "!") { | |
splitted := strings.Fields(msg.Body) | |
if len(splitted) < 1 { | |
return nil | |
} | |
cmdPrefix := strings.TrimPrefix(splitted[0], "!") | |
var cmd *exec.Cmd | |
pathToScript := filepath.Join("exec", cmdPrefix) | |
// fmt.Printf("num args: %v\n", len(splitted)) | |
var args []string | |
if len(splitted) == 2 { | |
args = []string{splitted[1]} | |
cmd = exec.CommandContext(ctx, pathToScript, args...) | |
} else if len(splitted) > 2 { | |
args = splitted[1:len(splitted)] | |
cmd = exec.CommandContext(ctx, pathToScript, args...) | |
} else { | |
cmd = exec.CommandContext(ctx, pathToScript) | |
} | |
data, err := cmd.CombinedOutput() | |
if err != nil { | |
return err | |
} | |
reply := MessageBody{ | |
Message: stanza.Message{ | |
To: msg.From.Bare(), | |
}, | |
Body: strings.TrimSpace(string(data)), | |
} | |
if err := t.Encode(reply); err != nil { | |
log.Printf("Error responding to message %q: %q", msg.ID, err) | |
return err | |
} | |
} | |
return nil | |
} |
Mellium versions
% cat go.mod | grep mellium
mellium.im/reader v0.1.0 // indirect
mellium.im/sasl v0.3.1 // indirect
mellium.im/xmlstream v0.15.4 // indirect
mellium.im/xmpp v0.21.4 // indirect
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Logs from this demonstrate 1) a slow dial and 2) a forever-hanging call to Join