mirror of
https://github.com/42wim/matterbridge.git
synced 2024-11-29 06:02:01 -08:00
Implement soulseek bridge
This commit is contained in:
parent
d16645c952
commit
cbae29b5dd
87
bridge/soulseek/handlers.go
Normal file
87
bridge/soulseek/handlers.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package bsoulseek
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *Bsoulseek) handleMessage(msg soulseekMessageResponse) {
|
||||||
|
if msg != nil {
|
||||||
|
b.Log.Debugf("Handling message: %v", msg)
|
||||||
|
}
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case loginMessageResponseSuccess, loginMessageResponseFailure:
|
||||||
|
b.loginResponse <- msg
|
||||||
|
case joinRoomMessageResponse:
|
||||||
|
b.joinRoomResponse <- msg
|
||||||
|
case kickedMessageResponse:
|
||||||
|
b.fatalErrors <- fmt.Errorf("Logged in somewhere else")
|
||||||
|
case privateMessageReceive:
|
||||||
|
b.handleDM(msg)
|
||||||
|
case sayChatroomMessageReceive:
|
||||||
|
b.handleChatMessage(msg)
|
||||||
|
case userJoinedRoomMessage:
|
||||||
|
b.handleJoinMessage(msg)
|
||||||
|
case userLeftRoomMessage:
|
||||||
|
b.handleLeaveMessage(msg)
|
||||||
|
default:
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bsoulseek) handleChatMessage(msg sayChatroomMessageReceive) {
|
||||||
|
b.Log.Debugf("Handle chat message: %v", msg)
|
||||||
|
if msg.Username == b.Config.GetString("Nick") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bridgeMessage := config.Message{
|
||||||
|
Account: b.Account,
|
||||||
|
Text: msg.Message,
|
||||||
|
Channel: msg.Room,
|
||||||
|
Username: msg.Username,
|
||||||
|
}
|
||||||
|
b.local <- bridgeMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bsoulseek) handleJoinMessage(msg userJoinedRoomMessage) {
|
||||||
|
b.Log.Debugf("Handle join message: %v", msg)
|
||||||
|
if msg.Username == b.Config.GetString("Nick") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bridgeMessage := config.Message{
|
||||||
|
Account: b.Account,
|
||||||
|
Event: config.EventJoinLeave,
|
||||||
|
Text: fmt.Sprintf("%s has joined the room", msg.Username),
|
||||||
|
Channel: msg.Room,
|
||||||
|
Username: "system",
|
||||||
|
}
|
||||||
|
b.local <- bridgeMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bsoulseek) handleLeaveMessage(msg userLeftRoomMessage) {
|
||||||
|
b.Log.Debugf("Handle leave message: %v", msg)
|
||||||
|
if msg.Username == b.Config.GetString("Nick") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bridgeMessage := config.Message{
|
||||||
|
Account: b.Account,
|
||||||
|
Event: config.EventJoinLeave,
|
||||||
|
Text: fmt.Sprintf("%s has left the room", msg.Username),
|
||||||
|
Channel: msg.Room,
|
||||||
|
Username: "system",
|
||||||
|
}
|
||||||
|
b.local <- bridgeMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bsoulseek) handleDM(msg privateMessageReceive) {
|
||||||
|
b.Log.Debugf("Received private message: %+v", msg)
|
||||||
|
if msg.Username == "server" {
|
||||||
|
b.Log.Infof("Received system message: %s", msg.Message)
|
||||||
|
if strings.HasPrefix(msg.Message, "System Message: You have been banned") {
|
||||||
|
b.Log.Errorf("Banned from server. Message: %s", msg.Message)
|
||||||
|
b.doDisconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
366
bridge/soulseek/messages.go
Normal file
366
bridge/soulseek/messages.go
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
package bsoulseek
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type soulseekMessage interface {}
|
||||||
|
type soulseekMessageResponse interface {}
|
||||||
|
|
||||||
|
const (
|
||||||
|
loginMessageCode uint32 = 1
|
||||||
|
sayInChatRoomMessageCode uint32 = 13
|
||||||
|
joinRoomMessageCode uint32 = 14
|
||||||
|
userJoinedRoomMessageCode uint32 = 16
|
||||||
|
userLeftRoomMessageCode uint32 = 17
|
||||||
|
privateMessageCode uint32 = 22
|
||||||
|
kickedMessageCode uint32 = 41
|
||||||
|
changePasswordMessageCode uint32 = 142
|
||||||
|
)
|
||||||
|
|
||||||
|
var ignoreMessageCodes = map[uint32]bool {
|
||||||
|
7: true,
|
||||||
|
64: true,
|
||||||
|
69: true,
|
||||||
|
83: true,
|
||||||
|
84: true,
|
||||||
|
104: true,
|
||||||
|
113: true,
|
||||||
|
114: true,
|
||||||
|
115: true,
|
||||||
|
130: true,
|
||||||
|
133: true,
|
||||||
|
139: true,
|
||||||
|
140: true,
|
||||||
|
145: true,
|
||||||
|
146: true,
|
||||||
|
148: true,
|
||||||
|
160: true,
|
||||||
|
1003: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 1: Login
|
||||||
|
type loginMessage struct {
|
||||||
|
Code uint32
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
Version uint32
|
||||||
|
Hash string
|
||||||
|
MinorVersion uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type loginMessageResponseSuccess struct {
|
||||||
|
Greet string
|
||||||
|
Address uint32
|
||||||
|
Hash string
|
||||||
|
IsSupporter bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type loginMessageResponseFailure struct {
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 13: Say in chatroom
|
||||||
|
type sayChatroomMessage struct {
|
||||||
|
Code uint32
|
||||||
|
Room string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
type sayChatroomMessageReceive struct {
|
||||||
|
Room string
|
||||||
|
Username string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 14: Join room
|
||||||
|
type joinRoomMessage struct {
|
||||||
|
Code uint32
|
||||||
|
Room string
|
||||||
|
Private uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type userStat struct {
|
||||||
|
AvgSpeed uint32
|
||||||
|
UploadNum uint64
|
||||||
|
Files uint32
|
||||||
|
Dirs uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type joinRoomMessageResponse struct {
|
||||||
|
Room string
|
||||||
|
Users []string
|
||||||
|
Statuses []uint32
|
||||||
|
Stats []userStat
|
||||||
|
SlotsFree []uint32
|
||||||
|
Countries []uint32
|
||||||
|
Owner string
|
||||||
|
Operators []string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 16: User joined room
|
||||||
|
type userJoinedRoomMessage struct {
|
||||||
|
Room string
|
||||||
|
Username string
|
||||||
|
Status uint32
|
||||||
|
AvgSpeed uint32
|
||||||
|
UploadNum uint64
|
||||||
|
Files uint32
|
||||||
|
Dirs uint32
|
||||||
|
SlotsFree uint32
|
||||||
|
CountryCode string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 16: User left room
|
||||||
|
type userLeftRoomMessage struct {
|
||||||
|
Room string
|
||||||
|
Username string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 22: Private message (sometimes used by server to tell us errors)
|
||||||
|
type privateMessageReceive struct {
|
||||||
|
ID uint32
|
||||||
|
Timestamp uint32
|
||||||
|
Username string
|
||||||
|
Message string
|
||||||
|
NewMessage bool
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 41: Kicked from server (relogged)
|
||||||
|
type kickedMessageResponse struct {}
|
||||||
|
|
||||||
|
|
||||||
|
// 142: Change password
|
||||||
|
type changePasswordMessage struct {
|
||||||
|
Code uint32
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
type changePasswordMessageResponse struct {
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func packMessage(message soulseekMessage) ([]byte, error) {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
var length uint32 = 0
|
||||||
|
binary.Write(buf, binary.LittleEndian, length) // placeholder
|
||||||
|
v := reflect.ValueOf(message)
|
||||||
|
var err error
|
||||||
|
for i := range v.NumField() {
|
||||||
|
val := v.Field(i).Interface()
|
||||||
|
switch val := val.(type) {
|
||||||
|
case string:
|
||||||
|
s_len := uint32(len(val))
|
||||||
|
err = binary.Write(buf, binary.LittleEndian, s_len)
|
||||||
|
buf.WriteString(val)
|
||||||
|
length += s_len + 4
|
||||||
|
case bool, uint8:
|
||||||
|
length += 1
|
||||||
|
err = binary.Write(buf, binary.LittleEndian, val)
|
||||||
|
case uint16:
|
||||||
|
length += 2
|
||||||
|
err = binary.Write(buf, binary.LittleEndian, val)
|
||||||
|
case uint32:
|
||||||
|
length += 4
|
||||||
|
err = binary.Write(buf, binary.LittleEndian, val)
|
||||||
|
case uint64:
|
||||||
|
length += 8
|
||||||
|
err = binary.Write(buf, binary.LittleEndian, val)
|
||||||
|
default:
|
||||||
|
panic("Unsupported struct field type")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bytes := buf.Bytes()
|
||||||
|
binary.LittleEndian.PutUint32(bytes, length)
|
||||||
|
return bytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpackStructField(reader io.Reader, field reflect.Value) error {
|
||||||
|
switch field.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
for i := range field.NumField() {
|
||||||
|
field := field.Field(i)
|
||||||
|
err := unpackStructField(reader, field)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
var length uint32
|
||||||
|
err := binary.Read(reader, binary.LittleEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ilen := int(length)
|
||||||
|
newval := reflect.MakeSlice(field.Type(), ilen, ilen)
|
||||||
|
field.Set(newval)
|
||||||
|
for j := range ilen {
|
||||||
|
err := unpackStructField(reader, field.Index(j))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
var length uint32
|
||||||
|
err := binary.Read(reader, binary.LittleEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
val := make([]byte, length)
|
||||||
|
_, err = reader.Read(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetString(string(val))
|
||||||
|
case reflect.Bool:
|
||||||
|
var val bool
|
||||||
|
err := binary.Read(reader, binary.LittleEndian, &val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetBool(val)
|
||||||
|
case reflect.Uint8:
|
||||||
|
var val uint8
|
||||||
|
err := binary.Read(reader, binary.LittleEndian, &val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetUint(uint64(val))
|
||||||
|
case reflect.Uint16:
|
||||||
|
var val uint16
|
||||||
|
err := binary.Read(reader, binary.LittleEndian, &val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetUint(uint64(val))
|
||||||
|
case reflect.Uint32:
|
||||||
|
var val uint32
|
||||||
|
err := binary.Read(reader, binary.LittleEndian, &val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetUint(uint64(val))
|
||||||
|
case reflect.Uint64:
|
||||||
|
var val uint64
|
||||||
|
err := binary.Read(reader, binary.LittleEndian, &val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetUint(val)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("Unsupported struct field type: %d", field.Kind()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpackMessage[T soulseekMessage](reader io.Reader) (T, error) {
|
||||||
|
var data T
|
||||||
|
v := reflect.ValueOf(&data).Elem()
|
||||||
|
err := unpackStructField(reader, v)
|
||||||
|
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readMessage(reader io.Reader) (soulseekMessage, error) {
|
||||||
|
var length uint32
|
||||||
|
err := binary.Read(reader, binary.LittleEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, int(length))
|
||||||
|
_, err = io.ReadAtLeast(reader, buf, len(buf))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reader = bytes.NewReader(buf)
|
||||||
|
|
||||||
|
var code uint32
|
||||||
|
err = binary.Read(reader, binary.LittleEndian, &code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch code {
|
||||||
|
case loginMessageCode:
|
||||||
|
// login message is special; has two possible responses
|
||||||
|
var success bool
|
||||||
|
err := binary.Read(reader, binary.LittleEndian, &success)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if success {
|
||||||
|
return unpackMessage[loginMessageResponseSuccess](reader)
|
||||||
|
} else {
|
||||||
|
return unpackMessage[loginMessageResponseFailure](reader)
|
||||||
|
}
|
||||||
|
case sayInChatRoomMessageCode:
|
||||||
|
return unpackMessage[sayChatroomMessageReceive](reader)
|
||||||
|
case joinRoomMessageCode:
|
||||||
|
return unpackMessage[joinRoomMessageResponse](reader)
|
||||||
|
case kickedMessageCode:
|
||||||
|
return unpackMessage[kickedMessageResponse](reader)
|
||||||
|
case userJoinedRoomMessageCode:
|
||||||
|
return unpackMessage[userJoinedRoomMessage](reader)
|
||||||
|
case userLeftRoomMessageCode:
|
||||||
|
return unpackMessage[userLeftRoomMessage](reader)
|
||||||
|
case changePasswordMessageCode:
|
||||||
|
return unpackMessage[changePasswordMessageResponse](reader)
|
||||||
|
case privateMessageCode:
|
||||||
|
return unpackMessage[privateMessageReceive](reader)
|
||||||
|
default:
|
||||||
|
_, ignore := ignoreMessageCodes[code]
|
||||||
|
if ignore {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Unknown message code: %d", code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeLoginMessage(username string, password string) soulseekMessage {
|
||||||
|
hash := md5.Sum([]byte(username + password))
|
||||||
|
msg := loginMessage{
|
||||||
|
loginMessageCode,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
160,
|
||||||
|
hex.EncodeToString(hash[:]),
|
||||||
|
1,
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeJoinRoomMessage(room string) joinRoomMessage {
|
||||||
|
return joinRoomMessage{
|
||||||
|
joinRoomMessageCode,
|
||||||
|
room,
|
||||||
|
0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSayChatroomMessage(room string, text string) sayChatroomMessage {
|
||||||
|
return sayChatroomMessage{
|
||||||
|
sayInChatRoomMessageCode,
|
||||||
|
room,
|
||||||
|
text,
|
||||||
|
}
|
||||||
|
}
|
231
bridge/soulseek/soulseek.go
Normal file
231
bridge/soulseek/soulseek.go
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
package bsoulseek
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/42wim/matterbridge/bridge"
|
||||||
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Bsoulseek struct {
|
||||||
|
conn net.Conn
|
||||||
|
messagesToSend chan soulseekMessage
|
||||||
|
local chan config.Message
|
||||||
|
loginResponse chan soulseekMessageResponse
|
||||||
|
joinRoomResponse chan joinRoomMessageResponse
|
||||||
|
fatalErrors chan error
|
||||||
|
disconnect chan bool
|
||||||
|
firstConnectResponse chan error
|
||||||
|
|
||||||
|
*bridge.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *bridge.Config) bridge.Bridger {
|
||||||
|
b := &Bsoulseek{}
|
||||||
|
b.Config = cfg
|
||||||
|
b.messagesToSend = make(chan soulseekMessage, 256)
|
||||||
|
b.local = make(chan config.Message, 256)
|
||||||
|
b.loginResponse = make(chan soulseekMessageResponse)
|
||||||
|
b.joinRoomResponse = make(chan joinRoomMessageResponse)
|
||||||
|
b.fatalErrors = make(chan error)
|
||||||
|
b.disconnect = make(chan bool)
|
||||||
|
b.firstConnectResponse = make(chan error)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bsoulseek) receiveMessages() {
|
||||||
|
for {
|
||||||
|
msg, err := readMessage(b.conn)
|
||||||
|
if err != nil {
|
||||||
|
b.fatalErrors <- fmt.Errorf("Reading message failed: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.handleMessage(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sliceEqual(s []string) bool {
|
||||||
|
// Return true if every element in s is equal to each other
|
||||||
|
if len(s) <= 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, x := range(s) {
|
||||||
|
if x != s[0] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bsoulseek) sendMessages() {
|
||||||
|
lastFourChatMessages := []string {"1", "2", "3", ""}
|
||||||
|
for {
|
||||||
|
message, more := <-b.messagesToSend
|
||||||
|
if !more {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg, is_say := message.(sayChatroomMessage)
|
||||||
|
if is_say {
|
||||||
|
// can't send 5 of the same message in a row or we get banned
|
||||||
|
if (sliceEqual(append(lastFourChatMessages, msg.Message))) {
|
||||||
|
b.Log.Warnf("Dropping message: %s", msg.Message)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data, err := packMessage(message)
|
||||||
|
if err != nil {
|
||||||
|
b.fatalErrors <- fmt.Errorf("Packing message failed: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = b.conn.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
b.fatalErrors <- fmt.Errorf("Sending message failed: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.Log.Debugf("Sent message: %v", message)
|
||||||
|
if is_say {
|
||||||
|
lastFourChatMessages = append(lastFourChatMessages[1:], msg.Message)
|
||||||
|
time.Sleep(3500 * time.Millisecond) // rate limit so less than 20 can be sent per min
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bsoulseek) sendLocalToRemote() {
|
||||||
|
for {
|
||||||
|
message, more := <-b.local
|
||||||
|
if !more {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.Remote <- message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bsoulseek) loginLoop() {
|
||||||
|
firstConnect := true
|
||||||
|
for {
|
||||||
|
if !firstConnect {
|
||||||
|
// Cleanup as we are making new sender/receiver routines
|
||||||
|
b.fatalErrors = make(chan error)
|
||||||
|
close(b.messagesToSend)
|
||||||
|
}
|
||||||
|
// Connect to slsk server
|
||||||
|
server := b.GetString("Server")
|
||||||
|
b.Log.Infof("Connecting %s", server)
|
||||||
|
conn, err := net.Dial("tcp", server)
|
||||||
|
b.conn = conn
|
||||||
|
if err != nil {
|
||||||
|
if firstConnect {
|
||||||
|
b.firstConnectResponse <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init sender and receiver
|
||||||
|
go b.receiveMessages()
|
||||||
|
go b.sendMessages()
|
||||||
|
go b.sendLocalToRemote()
|
||||||
|
|
||||||
|
// Attempt login
|
||||||
|
b.messagesToSend <- makeLoginMessage(b.GetString("Nick"), b.GetString("Password"))
|
||||||
|
var msg soulseekMessageResponse
|
||||||
|
connected := false
|
||||||
|
select {
|
||||||
|
case msg = <-b.loginResponse:
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case loginMessageResponseSuccess:
|
||||||
|
if firstConnect {
|
||||||
|
b.firstConnectResponse <- nil
|
||||||
|
}
|
||||||
|
connected = true
|
||||||
|
case loginMessageResponseFailure:
|
||||||
|
if firstConnect {
|
||||||
|
b.firstConnectResponse <- fmt.Errorf("Login failed: %s", msg.Reason)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.Log.Errorf("Login failed: %s", msg.Reason)
|
||||||
|
default:
|
||||||
|
panic("Unreachable")
|
||||||
|
}
|
||||||
|
case err := <-b.fatalErrors:
|
||||||
|
// error
|
||||||
|
if firstConnect {
|
||||||
|
b.firstConnectResponse <- fmt.Errorf("Login failed: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.Log.Errorf("Login failed: %s", err)
|
||||||
|
case <-time.After(30 * time.Second):
|
||||||
|
// timeout
|
||||||
|
if firstConnect {
|
||||||
|
b.firstConnectResponse <- fmt.Errorf("Login failed: timeout")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.Log.Errorf("Login failed: timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !connected {
|
||||||
|
// If we reach here, we are not logged in and
|
||||||
|
// it is not the first connect, so we should try again
|
||||||
|
b.Log.Info("Retrying in 30s")
|
||||||
|
time.Sleep(30 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we are connected
|
||||||
|
select {
|
||||||
|
case err = <- b.fatalErrors:
|
||||||
|
b.Log.Errorf("%s", err)
|
||||||
|
// Retry connect
|
||||||
|
continue
|
||||||
|
case <- b.disconnect:
|
||||||
|
// We are done
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bsoulseek) Connect() error {
|
||||||
|
go b.loginLoop()
|
||||||
|
err := <-b.firstConnectResponse
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (b *Bsoulseek) JoinChannel(channel config.ChannelInfo) error {
|
||||||
|
b.messagesToSend <- makeJoinRoomMessage(channel.Name)
|
||||||
|
select {
|
||||||
|
case <-b.joinRoomResponse:
|
||||||
|
b.Log.Infof("Joined room: '%s'", channel.Name)
|
||||||
|
return nil
|
||||||
|
case <-time.After(30 * time.Second):
|
||||||
|
return fmt.Errorf("Could not join room '%s': timeout", channel.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (b *Bsoulseek) Send(msg config.Message) (string, error) {
|
||||||
|
// Only process text messages
|
||||||
|
b.Log.Debugf("=> Received local message %v", msg)
|
||||||
|
if msg.Event != "" && msg.Event != config.EventUserAction && msg.Event != config.EventJoinLeave {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
b.messagesToSend <- makeSayChatroomMessage(msg.Channel, msg.Username + msg.Text)
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (b *Bsoulseek) doDisconnect() error {
|
||||||
|
b.disconnect <- true
|
||||||
|
close(b.messagesToSend)
|
||||||
|
close(b.joinRoomResponse)
|
||||||
|
close(b.loginResponse)
|
||||||
|
close(b.local)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (b *Bsoulseek) Disconnect() error {
|
||||||
|
b.doDisconnect()
|
||||||
|
return nil
|
||||||
|
}
|
11
gateway/bridgemap/bsoulseek.go
Normal file
11
gateway/bridgemap/bsoulseek.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// +build !nosoulseek
|
||||||
|
|
||||||
|
package bridgemap
|
||||||
|
|
||||||
|
import (
|
||||||
|
bsoulseek "github.com/42wim/matterbridge/bridge/soulseek"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
FullMap["soulseek"] = bsoulseek.New
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user