Skip to content

Commit 38ddbd9

Browse files
committed
feat(munch): ⭐
0 parents  commit 38ddbd9

18 files changed

+1661
-0
lines changed

.editorconfig

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
indent_style = space
6+
indent_size = 2
7+
end_of_line = lf
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true

.gitattributes

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

.gitignore

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# IDE
2+
/.idea/
3+
/munch.iml
4+
5+
# Artifacts
6+
/*.exe
7+
/*.exe~
8+
/*.dll
9+
/*.so
10+
/*.dylib
11+
/*.test
12+
/*.out
13+
14+
# Development
15+
/config.yml

LICENSE

+674
Large diffs are not rendered by default.

Makefile

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fmt:
2+
go fmt github.com/Whirlsplash/munch...
3+
4+
run: fmt
5+
go run munch.go
6+
7+
build: fmt
8+
go build

README.rst

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
:code:`munch`
2+
=====
3+
4+
A means of facilitating Worlds-to-Discord (and back) communication
5+
6+
Synopsis
7+
--------
8+
9+
Currently, Munch is no more than a simple proof-of-concept of how a dummy
10+
account can connect to a WorldServer and intercept information.
11+
12+
Planned Features:
13+
14+
- Worlds communication transmitted to Discord
15+
- Discord communication transmitted to Worlds
16+
- Webhook support
17+
18+
Once stable, the features of Munch will be integrated into the
19+
`Whirlsplash Discord bot <https://github.com/Whirlsplash/bot>`_.
20+
21+
Limitations
22+
^^^^^^^^^^^
23+
24+
Munch never sends any positional information to the RoomServer once connected,
25+
this means that she is never rendered as an avatar, this also means that she is
26+
technically never present within a room (?) so she cannot intercept TEXT
27+
commands, this will be fixed (keep in mind, this is just a proof-of-concept).
28+
29+
Notes
30+
^^^^^
31+
32+
Munch also paves the way for a system of bots enabled by the
33+
Whirlsplash-of-things. "What? ..."; Whirlsplash is planned to have an imperative
34+
"programming" language of sorts which can be used to create bots for Whirl with
35+
ease.
36+
37+
Development
38+
-----------
39+
40+
Docker support coming soon!
41+
42+
Running
43+
^^^^^^^
44+
45+
.. code-blocK:: shell
46+
47+
$ make run
48+
49+
Building
50+
^^^^^^^^
51+
52+
.. code-blocK:: shell
53+
54+
$ make build
55+
56+
License
57+
~~~~~~~
58+
59+
`GNU General Public License v3.0 <./LICENSE>`_

config.example.yml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
worlds:
2+
server: # These shouldn't change
3+
ip: 209.240.84.122
4+
port: 6650
5+
6+
account: # Change this to a spare (or main?) account's login information
7+
username: fuwn
8+
password: somethingsecure22
9+
avatar: Vamp.mov

go.mod

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/Whirlsplash/munch
2+
3+
go 1.16
4+
5+
require github.com/spf13/viper v1.8.1

go.sum

+573
Large diffs are not rendered by default.

munch.go

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (C) 2021-2021 The Whirlsplash Collective
2+
// SPDX-License-Identifier: GPL-3.0-only
3+
4+
package main
5+
6+
import (
7+
"github.com/Whirlsplash/munch/pkg/config"
8+
"github.com/Whirlsplash/munch/pkg/server"
9+
)
10+
11+
func main() {
12+
config.Setup()
13+
server.Do()
14+
}

pkg/config/config.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (C) 2021-2021 The Whirlsplash Collective
2+
// SPDX-License-Identifier: GPL-3.0-only
3+
4+
package config
5+
6+
import (
7+
"github.com/spf13/viper"
8+
"log"
9+
)
10+
11+
func Setup() {
12+
viper.SetConfigName("config.yml")
13+
viper.SetConfigType("yaml")
14+
viper.AddConfigPath(".")
15+
16+
if err := viper.ReadInConfig(); err != nil {
17+
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
18+
log.Panicln("Cannot read configuration file:", err)
19+
} else {
20+
log.Panicln("Read configuration file but an error occurred anyway:", err)
21+
}
22+
}
23+
24+
viper.WatchConfig()
25+
}

pkg/server/distributor.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (C) 2021-2021 The Whirlsplash Collective
2+
// SPDX-License-Identifier: GPL-3.0-only
3+
4+
package server
5+
6+
import (
7+
"encoding/binary"
8+
"github.com/Whirlsplash/munch/pkg/server/utils"
9+
"github.com/spf13/viper"
10+
"log"
11+
"net"
12+
"strconv"
13+
)
14+
15+
func doDistributor() string {
16+
tcpAddr, _ := net.ResolveTCPAddr(
17+
"tcp",
18+
viper.GetString("worlds.server.ip")+":"+viper.GetString("worlds.server.port"),
19+
)
20+
conn, _ := net.DialTCP("tcp", nil, tcpAddr)
21+
22+
// PROPREQ
23+
conn.Write([]byte("\x03\xff\x0a"))
24+
reply := make([]byte, 1024)
25+
conn.Read(reply)
26+
log.Println("AutoServer: PROPREQ")
27+
28+
// SESSINIT
29+
conn.Write(utils.EncodeSessionInitialization(
30+
viper.GetString("worlds.account.username"),
31+
viper.GetString("worlds.account.password"),
32+
utils.AutoServer,
33+
))
34+
reply = make([]byte, 1024)
35+
conn.Read(reply)
36+
log.Println("AutoServer: SESSINIT")
37+
38+
// PROPUPD
39+
conn.Write(utils.EncodePropertyUpdate(viper.GetString("worlds.account.avatar")))
40+
reply = make([]byte, 1024)
41+
conn.Read(reply)
42+
log.Println("AutoServer: PROPUPD")
43+
44+
// ROOMIDRQ
45+
conn.Write([]byte(
46+
"\x26\x01\x14\x22\x47\x72\x6f\x75\x6e\x64\x5a\x65\x72\x6f\x23\x73" +
47+
"\x74\x61\x69\x72\x63\x61\x73\x65\x31\x3c\x64\x69\x6d\x65\x6e\x73" +
48+
"\x69\x6f\x6e\x2d\x31\x3e",
49+
))
50+
reply = make([]byte, 1024)
51+
conn.Read(reply)
52+
port := strconv.Itoa(int(binary.BigEndian.Uint16(reply[44:46])))
53+
log.Println("AutoServer: ROOMIDRQ")
54+
55+
conn.Close()
56+
57+
return port
58+
}

pkg/server/hub.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (C) 2021-2021 The Whirlsplash Collective
2+
// SPDX-License-Identifier: GPL-3.0-only
3+
4+
package server
5+
6+
import (
7+
"github.com/Whirlsplash/munch/pkg/server/utils"
8+
"github.com/spf13/viper"
9+
"log"
10+
"net"
11+
)
12+
13+
func doHub(port string) {
14+
tcpAddr, _ := net.ResolveTCPAddr(
15+
"tcp",
16+
(viper.GetString("worlds.server.ip") + ":" + port),
17+
)
18+
conn, _ := net.DialTCP("tcp", nil, tcpAddr)
19+
20+
// PROPREQ
21+
conn.Write([]byte("\x03\xff\x0a"))
22+
reply := make([]byte, 1024)
23+
conn.Read(reply)
24+
log.Println("RoomServer: PROPREQ")
25+
26+
// SESSINIT
27+
conn.Write(utils.EncodeSessionInitialization(
28+
viper.GetString("worlds.account.username"),
29+
viper.GetString("worlds.account.password"),
30+
utils.RoomServer,
31+
))
32+
reply = make([]byte, 1024)
33+
conn.Read(reply)
34+
log.Println("RoomServer: SESSINIT")
35+
36+
// SUB-DIST
37+
conn.Write([]byte(
38+
"\x0d\x01\x16\x00\x02\x00\x00\x00\xfa\x00\x00\x0a\xf8" +
39+
"\x0d\x01\x16\x00\x01\x00\x00\x02\xee\x00\x00\x0c\xf9" +
40+
"\x0d\x01\x16\x00\x07\x00\x00\x36\xaf\x00\x00\x09\xbd" +
41+
"\x0d\x01\x16\x00\x06\xf1\x77\x00\x00\x01\x90\x0d\xcf\x0d\x01\x16" +
42+
"\x98\xee\x00\x00\x00\xc8\x00\x28\x0e\x67\x0d\x01\x16\x00\x04\x00" +
43+
"\x00\x00\xfa\x00\x00\x0c\xb7\x0d\x01\x16\x00\x03\x00\x00\x02\xee" +
44+
"\x00\x00\x0c\x30\x0d\x01\x16\x00\x09\x01\xf4\x03\xe8\x01\xf4\x09" +
45+
"\xd5\x0d\x01\x16\x00\x08\x00\x00\x00\x00\x00\x00\xff\x9c\x0f\x01" +
46+
"\x12\x00\x08\x00\x01\x04\xcc\x09\xa1\x00\x00\x01\x25\x07\x01\x18" +
47+
"\x00\x01\x0c\xf9\x07\x01\x18\x00\x03\x0c\x30\x07\x01\x18\x00\x09" +
48+
"\x09\xd5\x07\x01\x18\x00\x07\x09\xbd\x07\x01\x18\x00\x06\x0d\xcf" +
49+
"\x07\x01\x18\x98\xee\x0e\x67\x07\x01\x18\x00\x04\x0c\xb7\x07\x01" +
50+
"\x18\x00\x02\x0a\xf8",
51+
))
52+
reply = make([]byte, 1024)
53+
conn.Read(reply)
54+
log.Println("RoomServer: SUBSCRIB")
55+
56+
// Listen for whispers
57+
for {
58+
reply = make([]byte, 1024)
59+
conn.Read(reply)
60+
if reply[2] == 17 {
61+
decodedText := utils.DecodeText(reply)
62+
log.Printf(
63+
"RoomServer: WHISPER: %s: %s\n",
64+
decodedText.Author, decodedText.Message,
65+
)
66+
}
67+
}
68+
69+
conn.Close()
70+
}

pkg/server/server.go

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (C) 2021-2021 The Whirlsplash Collective
2+
// SPDX-License-Identifier: GPL-3.0-only
3+
4+
package server
5+
6+
func Do() {
7+
// The Distributor isn't actually needed for Munch to work, the only reason
8+
// that we use it is because it gives us the correct Hub port to connect to.
9+
doHub(doDistributor())
10+
}

pkg/server/utils/decode.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (C) 2021-2021 The Whirlsplash Collective
2+
// SPDX-License-Identifier: GPL-3.0-only
3+
4+
package utils
5+
6+
import "strconv"
7+
8+
type Text struct {
9+
Author string
10+
Message string
11+
}
12+
13+
// Decode a TEXT command from hexadecimal to a Text struct
14+
func DecodeText(data []byte) Text {
15+
authorLength, _ := strconv.ParseInt(strconv.Itoa(int(data[4])), 16, 32)
16+
authorLast := authorLength + 5
17+
messageLength, _ := strconv.ParseInt(strconv.Itoa(int(data[authorLast])), 16, 32)
18+
messageStart := authorLast + 1
19+
20+
return Text{
21+
Author: string(data[5:(authorLast)]),
22+
Message: string(data[messageStart : messageStart+(messageLength)]),
23+
}
24+
}

pkg/server/utils/encode.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (C) 2021-2021 The Whirlsplash Collective
2+
// SPDX-License-Identifier: GPL-3.0-only
3+
4+
package utils
5+
6+
const (
7+
AutoServer = iota
8+
RoomServer
9+
)
10+
11+
func EncodeSessionInitialization(username string, password string, serverType int) []byte {
12+
data := ""
13+
14+
// Data length
15+
if serverType == AutoServer {
16+
data += "\x2b"
17+
} else {
18+
data += "\x2c"
19+
}
20+
21+
// Command header and other stuff we don't need to worry about
22+
data += "\x01\x06\x03\x02\x32\x34\x09\x0a\x32\x30\x32\x30\x30\x33\x31" +
23+
"\x32\x30\x30"
24+
25+
if serverType == RoomServer {
26+
data += "\x07\x02\x32\x34"
27+
}
28+
29+
data +=
30+
// Username
31+
"\x02" + string(rune(len(username))) + ISO88591ToString(username) +
32+
33+
// Password
34+
"\x06" + string(rune(len(password))) + ISO88591ToString(password)
35+
36+
if serverType == AutoServer {
37+
data += "\x0c\x01\x31"
38+
}
39+
40+
return []byte(data)
41+
}
42+
43+
func EncodeBuddyListUpdate(buddy string) []byte {
44+
// Command header
45+
data := "\x01\x1d"
46+
47+
// Buddy UTF-8 length and UTF-8
48+
data += string(rune(len(buddy))) + ISO88591ToString(buddy)
49+
50+
// Buddy "add"
51+
data += "\x01"
52+
53+
// Data length
54+
data = (string(rune(len(data) + 1))) + data
55+
56+
return []byte(data)
57+
}
58+
59+
func EncodePropertyUpdate(avatar string) []byte {
60+
// Command header and extra stuff we don't need to worry about
61+
data := "\x01\x0f\x00\x05\x40\x01"
62+
63+
// Avatar UTF-8 length and UTF-8
64+
data += string(rune(len("avatar:"+avatar))) + ISO88591ToString("avatar:"+avatar)
65+
66+
// Data length
67+
data = (string(rune(len(data) + 1))) + data
68+
69+
return []byte(data)
70+
}

0 commit comments

Comments
 (0)