From a155f3cd8556d30185bc4e7a0a87dd7861c0afdb Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Mon, 12 Jun 2023 17:47:10 -0400 Subject: import revolt package Signed-off-by: Xe Iaso --- web/revolt/LICENSE | 7 + web/revolt/README.md | 155 ++++++++++++++++++ web/revolt/bot.go | 56 +++++++ web/revolt/cache.go | 123 ++++++++++++++ web/revolt/channel.go | 201 +++++++++++++++++++++++ web/revolt/client.go | 403 ++++++++++++++++++++++++++++++++++++++++++++++ web/revolt/emoji.go | 6 + web/revolt/events.go | 5 + web/revolt/go.sum | 10 ++ web/revolt/group.go | 71 ++++++++ web/revolt/http.go | 48 ++++++ web/revolt/interaction.go | 6 + web/revolt/message.go | 150 +++++++++++++++++ web/revolt/other.go | 324 +++++++++++++++++++++++++++++++++++++ web/revolt/permissions.go | 100 ++++++++++++ web/revolt/server.go | 368 ++++++++++++++++++++++++++++++++++++++++++ web/revolt/user.go | 133 +++++++++++++++ web/revolt/utils.go | 15 ++ web/revolt/websocket.go | 364 +++++++++++++++++++++++++++++++++++++++++ 19 files changed, 2545 insertions(+) create mode 100644 web/revolt/LICENSE create mode 100644 web/revolt/README.md create mode 100644 web/revolt/bot.go create mode 100644 web/revolt/cache.go create mode 100644 web/revolt/channel.go create mode 100644 web/revolt/client.go create mode 100644 web/revolt/emoji.go create mode 100644 web/revolt/events.go create mode 100644 web/revolt/go.sum create mode 100644 web/revolt/group.go create mode 100644 web/revolt/http.go create mode 100644 web/revolt/interaction.go create mode 100644 web/revolt/message.go create mode 100644 web/revolt/other.go create mode 100644 web/revolt/permissions.go create mode 100644 web/revolt/server.go create mode 100644 web/revolt/user.go create mode 100644 web/revolt/utils.go create mode 100644 web/revolt/websocket.go (limited to 'web') diff --git a/web/revolt/LICENSE b/web/revolt/LICENSE new file mode 100644 index 0000000..6972546 --- /dev/null +++ b/web/revolt/LICENSE @@ -0,0 +1,7 @@ +Copyright 2021 5elenay, itsTheMeow, & Ben Forster + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/web/revolt/README.md b/web/revolt/README.md new file mode 100644 index 0000000..d532534 --- /dev/null +++ b/web/revolt/README.md @@ -0,0 +1,155 @@ +# revolt.go + +Revolt.go is a Go package for writing bots and self-bots in Revolt +easily. This project is a mantained and re-worked version +[ben-forster's fork](https://github.com/ben-forster/revolt) of +5elenay's library [revoltgo](https://github.com/5elenay/revoltgo). + +## Features + +- Multiple event listen +- Easy to use +- Supports self bots +- Simple cache system + +## API Reference + +Click [here](https://pkg.go.dev/within.website/x/web/revolt) for api reference. + +## Notice + +Please note that you will need the Go 1.20 to use revolt. + +This package is still under development and while you can create a +working bot, the library is not finished. Create an issue if you would +like to contribute. + +## Ping Pong Example (Bot) + +```go +package main + +import ( + "os" + "os/signal" + "syscall" + + "within.website/x/web/revolt" +) + +func main() { + // Init a new client. + client := revolt.Client{ + Token: "bot token", + } + + // Listen a on message event. + client.OnMessage(func(m *revolt.Message) { + if m.Content == "!ping" { + sendMsg := &revolt.SendMessage{} + sendMsg.SetContent("🏓 Pong!") + + m.Reply(true, sendMsg) + } + }) + + // Start the client. + client.Start() + + // Wait for close. + sc := make(chan os.Signal, 1) + + signal.Notify( + sc, + syscall.SIGINT, + syscall.SIGTERM, + os.Interrupt, + ) + <-sc + + // Destroy client. + client.Destroy() +} + +``` + +## Ping Pong Example (Self-Bot) + +```go +package main + +import ( + "os" + "os/signal" + "syscall" + + "within.website/x/web/revolt" +) + +func main() { + // Init a new client. + client := revolt.Client{ + SelfBot: &revolt.SelfBot{ + Id: "session id", + SessionToken: "session token", + UserId: "user id", + }, + } + + // Listen a on message event. + client.OnMessage(func(m *revolt.Message) { + if m.Content == "!ping" { + sendMsg := &revolt.SendMessage{} + sendMsg.SetContent("🏓 Pong!") + + m.Reply(true, sendMsg) + } + }) + + // Start the client. + client.Start() + + // Wait for close. + sc := make(chan os.Signal, 1) + + signal.Notify( + sc, + syscall.SIGINT, + syscall.SIGTERM, + os.Interrupt, + ) + <-sc + + // Destroy client. + client.Destroy() +} + +``` + +## To-Do + +- [x] OnReady +- [x] OnMessage +- [x] OnMessageUpdate +- [ ] OnMessageAppend +- [x] OnMessageDelete +- [x] OnChannelCreate +- [x] OnChannelUpdate +- [x] OnChannelDelete +- [ ] OnChannelGroupJoin +- [ ] OnChannelGroupLeave +- [x] OnChannelStartTyping +- [x] OnChannelStopTyping +- [ ] OnChannelAck +- [x] OnServerCreate +- [x] OnServerUpdate +- [x] OnServerDelete +- [x] OnServerMemberUpdate +- [x] OnServerMemberJoin +- [x] OnServerMemberLeave +- [ ] OnServerRoleUpdate +- [ ] OnServerRoleDelete +- [ ] OnUserUpdate +- [ ] OnUserRelationship +- [ ] OnEmojiCreate +- [ ] OnEmojiDelete diff --git a/web/revolt/bot.go b/web/revolt/bot.go new file mode 100644 index 0000000..de29081 --- /dev/null +++ b/web/revolt/bot.go @@ -0,0 +1,56 @@ +package revolt + +import ( + "encoding/json" + "time" + + "github.com/oklog/ulid/v2" +) + +// Bot struct. +type Bot struct { + Client *Client + CreatedAt time.Time + + Id string `json:"_id"` + OwnerId string `json:"owner"` + Token string `json:"token"` + IsPublic bool `json:"public"` + InteractionsUrl string `json:"interactionsURL"` +} + +// Fetched bots struct. +type FetchedBots struct { + Bots []*Bot `json:"bots"` + Users []*User `json:"users"` +} + +// Calculate creation date and edit the struct. +func (b *Bot) CalculateCreationDate() error { + ulid, err := ulid.Parse(b.Id) + + if err != nil { + return err + } + + b.CreatedAt = time.UnixMilli(int64(ulid.Time())) + return nil +} + +// Edit the bot. +func (b *Bot) Edit(eb *EditBot) error { + data, err := json.Marshal(eb) + + if err != nil { + return err + } + + _, err = b.Client.Request("PATCH", "/bots/"+b.Id, data) + return err +} + +// Delete the bot. +func (b *Bot) Delete() error { + _, err := b.Client.Request("DELETE", "/bots/"+b.Id, []byte{}) + return err +} diff --git a/web/revolt/cache.go b/web/revolt/cache.go new file mode 100644 index 0000000..c04c80d --- /dev/null +++ b/web/revolt/cache.go @@ -0,0 +1,123 @@ +package revolt + +import "fmt" + +// Client cache struct. +type Cache struct { + Users []*User `json:"users"` + Servers []*Server `json:"servers"` + Channels []*Channel `json:"channels"` + Members []*Member `json:"members"` +} + +// Get a channel from cache by Id. +// Will return an empty channel struct if not found. +func (c *Cache) GetChannel(id string) *Channel { + for _, i := range c.Channels { + if i.Id == id { + return i + } + } + + return &Channel{} +} + +// Get a server from cache by Id. +// Will return an empty server struct if not found. +func (c *Cache) GetServer(id string) *Server { + for _, i := range c.Servers { + if i.Id == id { + return i + } + } + + return &Server{} +} + +// Get an user from cache by Id. +// Will return an empty user struct if not found. +func (c *Cache) GetUser(id string) *User { + for _, i := range c.Users { + if i.Id == id { + return i + } + } + + return &User{} +} + +// Get a member from cache by Id. +// Will return an empty member struct if not found. +func (c *Cache) GetMember(id string) *Member { + for _, i := range c.Members { + if i.Informations.UserId == id { + return i + } + } + + return &Member{} +} + +// Remove a channel from cache by Id. +// Will not delete the channel, just deletes the channel from cache. +// Will change the entire channel cache order! +func (c *Cache) RemoveChannel(id string) error { + for i, v := range c.Channels { + if v.Id == id { + c.Channels[i] = c.Channels[len(c.Channels)-1] + c.Channels = c.Channels[:len(c.Channels)-1] + + return nil + } + } + + return fmt.Errorf("channel not found") +} + +// Remove a server from cache by Id. +// Will not delete the server, just deletes the server from cache. +// Will change the entire server cache order! +func (c *Cache) RemoveServer(id string) error { + for i, v := range c.Servers { + if v.Id == id { + c.Servers[i] = c.Servers[len(c.Servers)-1] + c.Servers = c.Servers[:len(c.Servers)-1] + + return nil + } + } + + return fmt.Errorf("server not found") +} + +// Remove an user from cache by Id. +// Will not delete the user, just deletes the user from cache. +// Will change the entire user cache order! +func (c *Cache) RemoveUser(id string) error { + for i, v := range c.Users { + if v.Id == id { + c.Users[i] = c.Users[len(c.Users)-1] + c.Users = c.Users[:len(c.Users)-1] + + return nil + } + } + + return fmt.Errorf("user not found") +} + +// Remove a member from cache by Id. +// Will not delete the member, just deletes the member from cache. +// Will change the entire member cache order! +func (c *Cache) RemoveMember(id string) error { + for i, v := range c.Members { + if v.Informations.UserId == id { + c.Members[i] = c.Members[len(c.Members)-1] + c.Members = c.Members[:len(c.Members)-1] + + return nil + } + } + + return fmt.Errorf("member not found") +} diff --git a/web/revolt/channel.go b/web/revolt/channel.go new file mode 100644 index 0000000..99211ba --- /dev/null +++ b/web/revolt/channel.go @@ -0,0 +1,201 @@ +package revolt + +import ( + "encoding/json" + "fmt" + "reflect" + "time" + + "github.com/oklog/ulid/v2" +) + +// Channel struct. +type Channel struct { + Client *Client + CreatedAt time.Time + + Id string `json:"_id"` + Nonce string `json:"nonce"` + OwnerId string `json:"owner"` + Name string `json:"name"` + Active bool `json:"active"` + Recipients []string `json:"recipients"` + LastMessage interface{} `json:"last_message"` + Description string `json:"description"` + Icon *Attachment `json:"icon"` + DefaultPermissions uint `json:"default_permissions"` + RolePermissions interface{} `json:"role_permissions"` + Permissions uint `json:"permissions"` +} + +// Fetched messages struct. +type FetchedMessages struct { + Messages []*Message `json:"messages"` + Users []*User `json:"users"` +} + +// Calculate creation date and edit the struct. +func (c *Channel) CalculateCreationDate() error { + ulid, err := ulid.Parse(c.Id) + + if err != nil { + return err + } + + c.CreatedAt = time.UnixMilli(int64(ulid.Time())) + return nil +} + +// Send a message to the channel. +func (c Channel) SendMessage(message *SendMessage) (*Message, error) { + if message.Nonce == "" { + message.CreateNonce() + } + + respMessage := &Message{} + respMessage.Client = c.Client + msgData, err := json.Marshal(message) + + if err != nil { + return respMessage, err + } + + resp, err := c.Client.Request("POST", "/channels/"+c.Id+"/messages", msgData) + + if err != nil { + return respMessage, err + } + + err = json.Unmarshal(resp, respMessage) + + if err != nil { + return respMessage, err + } + + if message.DeleteAfter != 0 { + go func() { + time.Sleep(time.Second * time.Duration(message.DeleteAfter)) + respMessage.Delete() + }() + } + + return respMessage, nil +} + +// Fetch messages from channel. +// Check: https://developers.revolt.chat/api/#tag/Messaging/paths/~1channels~1:channel~1messages/get for map parameters. +func (c Channel) FetchMessages(options map[string]interface{}) (*FetchedMessages, error) { + // Format url + url := "/channels/" + c.Id + "/messages?" + + for key, value := range options { + if !reflect.ValueOf(value).IsZero() { + url += fmt.Sprintf("%s=%v&", key, value) + } + } + + url = url[:len(url)-1] + + fetchedMsgs := &FetchedMessages{} + + // Send request + resp, err := c.Client.Request("GET", url, []byte{}) + + if err != nil { + return fetchedMsgs, err + } + + err = json.Unmarshal(resp, &fetchedMsgs) + + if err != nil { + err = json.Unmarshal([]byte(fmt.Sprintf("{\"messages\": %s}", resp)), &fetchedMsgs) + + if err != nil { + return fetchedMsgs, err + } + } + + // Add client to users & messages + for _, msg := range fetchedMsgs.Messages { + msg.Client = c.Client + } + + if fetchedMsgs.Users != nil { + for _, msg := range fetchedMsgs.Users { + msg.Client = c.Client + } + } + + return fetchedMsgs, nil +} + +// Fetch a message from channel by Id. +func (c Channel) FetchMessage(id string) (*Message, error) { + msg := &Message{} + msg.Client = c.Client + + resp, err := c.Client.Request("GET", "/channels/"+c.Id+"/messages/"+id, []byte{}) + + if err != nil { + return msg, err + } + + err = json.Unmarshal(resp, msg) + return msg, err +} + +// Edit channel. +func (c Channel) Edit(ec *EditChannel) error { + data, err := json.Marshal(ec) + + if err != nil { + return err + } + + _, err = c.Client.Request("PATCH", "/channels/"+c.Id, data) + return err +} + +// Delete channel. +func (c Channel) Delete() error { + _, err := c.Client.Request("DELETE", "/channels/"+c.Id, []byte{}) + return err +} + +// Create a new invite. +// Returns a string (invite code) and error (nil if not exists). +func (c Channel) CreateInvite() (string, error) { + data, err := c.Client.Request("POST", "/channels/"+c.Id+"/invites", []byte{}) + + if err != nil { + return "", err + } + + dataStruct := &struct { + InviteCode string `json:"code"` + }{} + + err = json.Unmarshal(data, dataStruct) + return dataStruct.InviteCode, err +} + +// Set channel permissions for a role. +// Leave role field empty if you want to edit default permissions +func (c Channel) SetPermissions(role_id string, permissions uint) error { + if role_id == "" { + role_id = "default" + } + + _, err := c.Client.Request("PUT", "/channels/"+c.Id+"/permissions/"+role_id, []byte(fmt.Sprintf("{\"permissions\":%d}", permissions))) + return err +} + +// Send a typing start event to the channel. +func (c *Channel) BeginTyping() { + c.Client.Socket.SendText(fmt.Sprintf("{\"type\":\"BeginTyping\",\"channel\":\"%s\"}", c.Id)) +} + +// End the typing event in the channel. +func (c *Channel) EndTyping() { + c.Client.Socket.SendText(fmt.Sprintf("{\"type\":\"EndTyping\",\"channel\":\"%s\"}", c.Id)) +} diff --git a/web/revolt/client.go b/web/revolt/client.go new file mode 100644 index 0000000..b58479f --- /dev/null +++ b/web/revolt/client.go @@ -0,0 +1,403 @@ +package revolt + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/sacOO7/gowebsocket" +) + +const ( + WS_URL = "wss://ws.revolt.chat" + API_URL = "https://api.revolt.chat" +) + +// Client struct. +type Client struct { + SelfBot *SelfBot + Token string + Socket gowebsocket.Socket + HTTP *http.Client + Cache *Cache + + // Event Functions + OnUnknownEventFunctions []func(message string) + OnReadyFunctions []func() + OnMessageFunctions []func(message *Message) + OnMessageUpdateFunctions []func(channel_id, message_id string, payload map[string]interface{}) + OnMessageDeleteFunctions []func(channel_id, message_id string) + OnChannelCreateFunctions []func(channel *Channel) + OnChannelUpdateFunctions []func(channel_id, clear string, payload map[string]interface{}) + OnChannelDeleteFunctions []func(channel_id string) + OnGroupCreateFunctions []func(group *Group) + OnGroupMemberAddedFunctions []func(group_id, user_id string) + OnGroupMemberRemovedFunctions []func(group_id, user_id string) + OnChannelStartTypingFunctions []func(channel_id, user_id string) + OnChannelStopTypingFunctions []func(channel_id, user_id string) + OnServerCreateFunctions []func(server_id *Server) + OnServerUpdateFunctions []func(server_id, clear string, payload map[string]interface{}) + OnServerDeleteFunctions []func(server_id string) + OnServerMemberUpdateFunctions []func(server_id, clear string, payload map[string]interface{}) + OnServerMemberJoinFunctions []func(server_id, user_id string) + OnServerMemberLeaveFunctions []func(server_id, user_id string) +} + +// Self bot struct. +type SelfBot struct { + Email string `json:"-"` + Password string `json:"-"` + Id string `json:"id"` + UserId string `json:"user_id"` + SessionToken string `json:"token"` +} + +// On ready event will run when websocket connection is started and bot is ready to work. +func (c *Client) OnReady(fn func()) { + c.OnReadyFunctions = append(c.OnReadyFunctions, fn) +} + +// On message event will run when someone sends a message. +func (c *Client) OnMessage(fn func(message *Message)) { + c.OnMessageFunctions = append(c.OnMessageFunctions, fn) +} + +// On message update event will run when someone updates a message. +func (c *Client) OnMessageUpdate(fn func(channel_id, message_id string, payload map[string]interface{})) { + c.OnMessageUpdateFunctions = append(c.OnMessageUpdateFunctions, fn) +} + +// On message delete event will run when someone deletes a message. +func (c *Client) OnMessageDelete(fn func(channel_id, message_id string)) { + c.OnMessageDeleteFunctions = append(c.OnMessageDeleteFunctions, fn) +} + +// On channel create event will run when someone creates a channel. +func (c *Client) OnChannelCreate(fn func(channel *Channel)) { + c.OnChannelCreateFunctions = append(c.OnChannelCreateFunctions, fn) +} + +// On channel update event will run when someone updates a channel. +func (c *Client) OnChannelUpdate(fn func(channel_id, clear string, payload map[string]interface{})) { + c.OnChannelUpdateFunctions = append(c.OnChannelUpdateFunctions, fn) +} + +// On channel delete event will run when someone deletes a channel. +func (c *Client) OnChannelDelete(fn func(channel_id string)) { + c.OnChannelDeleteFunctions = append(c.OnChannelDeleteFunctions, fn) +} + +// On group channel create event will run when someones creates a group channel. +func (c *Client) OnGroupCreate(fn func(group *Group)) { + c.OnGroupCreateFunctions = append(c.OnGroupCreateFunctions, fn) +} + +// On group member added will run when someone is added to a group channel. +func (c *Client) OnGroupMemberAdded(fn func(group_id string, user_id string)) { + c.OnGroupMemberAddedFunctions = append(c.OnGroupMemberAddedFunctions, fn) +} + +// On group member removed will run when someone is removed from a group channel. +func (c *Client) OnGroupMemberRemoved(fn func(group_id string, user_id string)) { + c.OnGroupMemberRemovedFunctions = append(c.OnGroupMemberRemovedFunctions, fn) +} + +// On unknown event will run when client gets a unknown event. +func (c *Client) OnUnknownEvent(fn func(message string)) { + c.OnUnknownEventFunctions = append(c.OnUnknownEventFunctions, fn) +} + +// On channel start typing will run when someone starts to type a message. +func (c *Client) OnChannelStartTyping(fn func(channel_id, user_id string)) { + c.OnChannelStartTypingFunctions = append(c.OnChannelStartTypingFunctions, fn) +} + +// On channel stop typing will run when someone stops the typing status. +func (c *Client) OnChannelStopTyping(fn func(channel_id, user_id string)) { + c.OnChannelStopTypingFunctions = append(c.OnChannelStopTypingFunctions, fn) +} + +// On server create event will run when someone creates a server. +func (c *Client) OnServerCreate(fn func(server *Server)) { + c.OnServerCreateFunctions = append(c.OnServerCreateFunctions, fn) +} + +// On server update will run when someone updates a server. +func (c *Client) OnServerUpdate(fn func(server_id, clear string, payload map[string]interface{})) { + c.OnServerUpdateFunctions = append(c.OnServerUpdateFunctions, fn) +} + +// On server delete will run when someone deletes a server. +func (c *Client) OnServerDelete(fn func(server_id string)) { + c.OnServerDeleteFunctions = append(c.OnServerDeleteFunctions, fn) +} + +// On server member update will run when a server member updates. +func (c *Client) OnServerMemberUpdate(fn func(server_id, clear string, payload map[string]interface{})) { + c.OnServerMemberUpdateFunctions = append(c.OnServerMemberUpdateFunctions, fn) +} + +// On server member join will run when someone joins to the server. +func (c *Client) OnServerMemberJoin(fn func(server_id string, user_id string)) { + c.OnServerMemberJoinFunctions = append(c.OnServerMemberJoinFunctions, fn) +} + +// On server member leave will run when someone left from server. +func (c *Client) OnServerMemberLeave(fn func(server_id string, user_id string)) { + c.OnServerMemberLeaveFunctions = append(c.OnServerMemberLeaveFunctions, fn) +} + +// Fetch a channel by Id. +func (c *Client) FetchChannel(id string) (*Channel, error) { + channel := &Channel{} + channel.Client = c + + data, err := c.Request("GET", "/channels/"+id, []byte{}) + + if err != nil { + return channel, err + } + + err = json.Unmarshal(data, channel) + return channel, err +} + +// Fetch an user by Id. +func (c *Client) FetchUser(id string) (*User, error) { + user := &User{} + user.Client = c + + data, err := c.Request("GET", "/users/"+id, []byte{}) + + if err != nil { + return user, err + } + + err = json.Unmarshal(data, user) + return user, err +} + +// Fetch a server by Id. +func (c *Client) FetchServer(id string) (*Server, error) { + server := &Server{} + server.Client = c + + data, err := c.Request("GET", "/servers/"+id, []byte{}) + + if err != nil { + return server, err + } + + err = json.Unmarshal(data, server) + return server, err +} + +// Create a server. +func (c *Client) CreateServer(name, description string) (*Server, error) { + server := &Server{} + server.Client = c + + data, err := c.Request("POST", "/servers/create", []byte("{\"name\":\""+name+"\",\"description\":\""+description+"\",\"nonce\":\""+genULID()+"\"}")) + + if err != nil { + return server, err + } + + err = json.Unmarshal(data, server) + return server, err +} + +// Auth client user. +func (c *Client) Auth(friendlyName string) error { + if c.SelfBot == nil { + return fmt.Errorf("can't auth user (not a self-bot.)") + } + + resp, err := c.Request("POST", "/auth/session/login", []byte("{\"email\":\""+c.SelfBot.Email+"\",\"password\":\""+c.SelfBot.Password+"\",\"friendly_name\":\""+friendlyName+"\"}")) + + if err != nil { + return err + } + + err = json.Unmarshal(resp, c.SelfBot) + return err +} + +// Fetch all of the DMs. +func (c *Client) FetchDirectMessages() ([]*Channel, error) { + var dmChannels []*Channel + + resp, err := c.Request("GET", "/users/dms", []byte{}) + + if err != nil { + return dmChannels, err + } + + err = json.Unmarshal(resp, &dmChannels) + + if err != nil { + return dmChannels, err + } + + // Prepare channels. + for _, i := range dmChannels { + i.Client = c + } + + return dmChannels, nil +} + +// Edit client user. +func (c Client) Edit(eu *EditUser) error { + data, err := json.Marshal(eu) + + if err != nil { + return err + } + + _, err = c.Request("PATCH", "/users/@me", data) + return err +} + +// Create a new group. +// Users parameter is a list of users will be added. +func (c *Client) CreateGroup(name, description string, users []string) (*Channel, error) { + groupChannel := &Channel{} + groupChannel.Client = c + + dataStruct := &struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Users []string `json:"users"` + Nonce string `json:"nonce"` + }{ + Nonce: genULID(), + Name: name, + Description: description, + Users: users, + } + + data, err := json.Marshal(dataStruct) + + if err != nil { + return groupChannel, err + } + + resp, err := c.Request("POST", "/channels/create", data) + + if err != nil { + return groupChannel, err + } + + err = json.Unmarshal(resp, groupChannel) + return groupChannel, err +} + +// Fetch relationships. +func (c Client) FetchRelationships() ([]*UserRelations, error) { + relationshipDatas := []*UserRelations{} + + resp, err := c.Request("GET", "/users/relationships", []byte{}) + + if err != nil { + return relationshipDatas, err + } + + err = json.Unmarshal(resp, &relationshipDatas) + return relationshipDatas, err +} + +// Send friend request. / Accept friend request. +// User relations struct only will have status. id is not defined for this function. +func (c Client) AddFriend(username string) (*UserRelations, error) { + relationshipData := &UserRelations{} + + resp, err := c.Request("PUT", "/users/"+username+"/friend", []byte{}) + + if err != nil { + return relationshipData, err + } + + err = json.Unmarshal(resp, relationshipData) + return relationshipData, err +} + +// Deny friend request. / Remove friend. +// User relations struct only will have status. id is not defined for this function. +func (c Client) RemoveFriend(username string) (*UserRelations, error) { + relationshipData := &UserRelations{} + + resp, err := c.Request("DELETE", "/users/"+username+"/friend", []byte{}) + + if err != nil { + return relationshipData, err + } + + err = json.Unmarshal(resp, relationshipData) + return relationshipData, err +} + +// Create a new bot. +func (c *Client) CreateBot(name string) (*Bot, error) { + botData := &Bot{} + botData.Client = c + + resp, err := c.Request("POST", "/bots/create", []byte("{\"name\":\""+name+"\"}")) + + if err != nil { + return botData, err + } + + err = json.Unmarshal(resp, botData) + return botData, err + +} + +// Fetch client bots. +func (c *Client) FetchBots() (*FetchedBots, error) { + bots := &FetchedBots{} + + resp, err := c.Request("GET", "/bots/@me", []byte{}) + + if err != nil { + return bots, err + } + + err = json.Unmarshal(resp, bots) + + if err != nil { + return bots, err + } + + // Add client for bots. + for _, i := range bots.Bots { + i.Client = c + } + + // Add client for users. + for _, i := range bots.Users { + i.Client = c + } + + return bots, nil +} + +// Fetch a bot. +func (c *Client) FetchBot(id string) (*Bot, error) { + bot := &struct { + Bot *Bot `json:"bot"` + }{ + Bot: &Bot{ + Client: c, + }, + } + + resp, err := c.Request("GET", "/bots/"+id, []byte{}) + + if err != nil { + return bot.Bot, err + } + + err = json.Unmarshal(resp, bot) + return bot.Bot, err +} diff --git a/web/revolt/emoji.go b/web/revolt/emoji.go new file mode 100644 index 0000000..57f37ac --- /dev/null +++ b/web/revolt/emoji.go @@ -0,0 +1,6 @@ +package revolt + +// Emoji struct. +type Emoji struct { + +} diff --git a/web/revolt/events.go b/web/revolt/events.go new file mode 100644 index 0000000..6e9c608 --- /dev/null +++ b/web/revolt/events.go @@ -0,0 +1,5 @@ +package revolt + +type Events struct { + +} diff --git a/web/revolt/go.sum b/web/revolt/go.sum new file mode 100644 index 0000000..c1fa47c --- /dev/null +++ b/web/revolt/go.sum @@ -0,0 +1,10 @@ +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= +github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/sacOO7/go-logger v0.0.0-20180719173527-9ac9add5a50d h1:5T+fbRuQbpi+WZtB2yfuu59r00F6T2HV/zGYrwX8nvE= +github.com/sacOO7/go-logger v0.0.0-20180719173527-9ac9add5a50d/go.mod h1:L5EJe2k8GwpBoGXDRLAEs58R239jpZuE7NNEtW+T7oo= +github.com/sacOO7/gowebsocket v0.0.0-20221109081133-70ac927be105 h1:WgzGzpeh4gpYaVzpdMlThUp5HK2w+tmX8FiGxyVMLys= +github.com/sacOO7/gowebsocket v0.0.0-20221109081133-70ac927be105/go.mod h1:h00QywbM5Le22ESUiI8Yz2/9TVGD8eAz/cAk55Kcz/E= diff --git a/web/revolt/group.go b/web/revolt/group.go new file mode 100644 index 0000000..6d3179d --- /dev/null +++ b/web/revolt/group.go @@ -0,0 +1,71 @@ +package revolt + +import ( + "encoding/json" + "time" + + "github.com/oklog/ulid/v2" +) + +// Group channel struct. +type Group struct { + Client *Client + CreatedAt time.Time + + Id string `json:"_id"` + Nonce string `json:"nonce"` + OwnerId string `json:"owner"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Users []string `json:"users"` +} + +// Fetched group members struct. +type FetchedGroupMembers struct { + Messages []*Message `json:"messages"` + Users []*User `json:"users"` +} + +// System messages struct. +type GroupSystemMessages struct { + UserJoined string `json:"user_joined,omitempty"` + UserLeft string `json:"user_left,omitempty"` +} + +// Calculate creation date and edit the struct. +func (c *Group) CalculateCreationDate() error { + ulid, err := ulid.Parse(c.Id) + + if err != nil { + return err + } + + c.CreatedAt = time.UnixMilli(int64(ulid.Time())) + return nil +} + +// Fetch all of the members from group. +func (c Channel) FetchMembers() ([]*User, error) { + var groupMembers []*User + + resp, err := c.Client.Request("GET", "/channels/"+c.Id+"/members", []byte{}) + + if err != nil { + return groupMembers, err + } + + err = json.Unmarshal(resp, &groupMembers) + return groupMembers, err +} + +// Add a new group recipient. +func (c Channel) AddGroupRecipient(user_id string) error { + _, err := c.Client.Request("PUT", "/channels/"+c.Id+"/recipients/"+user_id, []byte{}) + return err +} + +// Delete a group recipient. +func (c Channel) DeleteGroupRecipient(user_id string) error { + _, err := c.Client.Request("DELETE", "/channels/"+c.Id+"/recipients/"+user_id, []byte{}) + return err +} \ No newline at end of file diff --git a/web/revolt/http.go b/web/revolt/http.go new file mode 100644 index 0000000..af60a94 --- /dev/null +++ b/web/revolt/http.go @@ -0,0 +1,48 @@ +package revolt + +import ( + "bytes" + "fmt" + "io" + "net/http" +) + +// Send http request +func (c Client) Request(method, path string, data []byte) ([]byte, error) { + reqBody := bytes.NewBuffer(data) + + // Prepare request + req, err := http.NewRequest(method, API_URL+path, reqBody) + if err != nil { + return []byte{}, err + } + + req.Header.Set("content-type", "application/json") + + // Set auth headers + if c.SelfBot == nil { + req.Header.Set("x-bot-token", c.Token) + } else if c.SelfBot.SessionToken != "" { + req.Header.Set("x-session-token", c.SelfBot.SessionToken) + } + + // Send request + resp, err := c.HTTP.Do(req) + + if err != nil { + return []byte{}, err + } + + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + + if err != nil { + return []byte{}, err + } + + if !(resp.StatusCode >= 200 && resp.StatusCode < 300) { + return []byte{}, fmt.Errorf("%s: %s", resp.Status, body) + } + + return body, nil +} diff --git a/web/revolt/interaction.go b/web/revolt/interaction.go new file mode 100644 index 0000000..12e4f21 --- /dev/null +++ b/web/revolt/interaction.go @@ -0,0 +1,6 @@ +package revolt + +// Interaction struct. +type Interaction struct { + +} diff --git a/web/revolt/message.go b/web/revolt/message.go new file mode 100644 index 0000000..6b0b7e9 --- /dev/null +++ b/web/revolt/message.go @@ -0,0 +1,150 @@ +package revolt + +import ( + "encoding/json" + "time" + + "github.com/oklog/ulid/v2" +) + +// Message struct +type Message struct { + Client *Client + CreatedAt time.Time + + Id string `json:"_id"` + Nonce string `json:"nonce"` + ChannelId string `json:"channel"` + AuthorId string `json:"author"` + Content interface{} `json:"content"` + Edited interface{} `json:"edited"` + Embeds []*MessageEmbed `json:"embeds"` + Attachments []*Attachment `json:"attachments"` + Mentions []string `json:"mentions"` + Replies []string `json:"replies"` +} + +// Attachment struct. +type Attachment struct { + Id string `json:"_id"` + Tag string `json:"tag"` + Size int `json:"size"` + FileName string `json:"filename"` + Metadata *AttachmentMetadata + ContentType string `json:"content_type"` +} + +// Attachment metadata struct. +type AttachmentMetadata struct { + Type string `json:"type"` + Width int `json:"width"` + Height int `json:"height"` +} + +// Message edited struct. +type MessageEdited struct { + Date int `json:"$date"` +} + +// Message embed struct. +type MessageEmbed struct { + Type string `json:"type"` + Url string `json:"url"` + Special *MessageSpecialEmbed + Title string `json:"title"` + Description string `json:"description"` + Image *MessageEmbeddedImage `json:"image"` + Video *MessageEmbeddedVideo `json:"video"` + IconUrl string `json:"icon_url"` + Color string `json:"color"` +} + +// Message special embed struct. +type MessageSpecialEmbed struct { + Type string `json:"type"` + Id string `json:"id"` + ContentType string `json:"content_type"` +} + +// Message embedded image struct +type MessageEmbeddedImage struct { + Size string `json:"size"` + Url string `json:"url"` + Width int `json:"width"` + Height int `json:"height"` +} + +// Message embedded video struct +type MessageEmbeddedVideo struct { + Url string `json:"url"` + Width int `json:"width"` + Height int `json:"height"` +} + +// Calculate creation date and edit the struct. +func (c *Message) CalculateCreationDate() error { + ulid, err := ulid.Parse(c.Id) + + if err != nil { + return err + } + + c.CreatedAt = time.UnixMilli(int64(ulid.Time())) + return nil +} + +// Edit message content. +func (m *Message) Edit(content string) error { + _, err := m.Client.Request("PATCH", "/channels/"+m.ChannelId+"/messages/"+m.Id, []byte("{\"content\": \""+content+"\"}")) + + if err != nil { + return err + } + + m.Content = content + return nil +} + +// Delete the message. +func (m Message) Delete() error { + _, err := m.Client.Request("DELETE", "/channels/"+m.ChannelId+"/messages/"+m.Id, []byte{}) + return err +} + +// Reply to the message. +func (m Message) Reply(mention bool, sm *SendMessage) (*Message, error) { + if sm.Nonce == "" { + sm.CreateNonce() + } + + sm.AddReply(m.Id, mention) + + respMessage := &Message{} + respMessage.Client = m.Client + msgData, err := json.Marshal(sm) + + if err != nil { + return respMessage, err + } + + resp, err := m.Client.Request("POST", "/channels/"+m.ChannelId+"/messages", msgData) + + if err != nil { + return respMessage, err + } + + err = json.Unmarshal(resp, respMessage) + + if err != nil { + return respMessage, err + } + + if sm.DeleteAfter != 0 { + go func() { + time.Sleep(time.Second * time.Duration(sm.DeleteAfter)) + respMessage.Delete() + }() + } + + return respMessage, nil +} diff --git a/web/revolt/other.go b/web/revolt/other.go new file mode 100644 index 0000000..cde9b1b --- /dev/null +++ b/web/revolt/other.go @@ -0,0 +1,324 @@ +package revolt + +import ( + "fmt" + "os" +) + +// Similar to message, but created for send message function. +type SendMessage struct { + Content string `json:"content"` + Nonce string `json:"nonce,omitempty"` + Attachments []string `json:"attachments,omitempty"` + Replies []Replies `json:"replies,omitempty"` + Embeds []SendableEmbed `json:"embeds,omitempty"` + DeleteAfter uint `json:"-"` +} + +type SendableEmbed struct { + Type string `json:"type"` + IconUrl string `json:"icon_url,omitempty"` + Url string `json:"url,omitempty"` + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + Media string `json:"media,omitempty"` + Colour string `json:"colour,omitempty"` +} + +type Replies struct { + Id string `json:"id"` + Mention bool `json:"mention"` +} + +// Set content. +func (sms *SendMessage) SetContent(content string) *SendMessage { + sms.Content = content + return sms +} + +// Set and format content. +func (sms *SendMessage) SetContentf(format string, values ...interface{}) *SendMessage { + sms.Content = fmt.Sprintf(format, values...) + return sms +} + +// Set delete after option. +func (sms *SendMessage) SetDeleteAfter(second uint) *SendMessage { + sms.DeleteAfter = second + return sms +} + +// Add a new attachment. +func (sms *SendMessage) AddAttachment(attachment string) *SendMessage { + sms.Attachments = append(sms.Attachments, attachment) + return sms +} + +// Add a new reply. +func (sms *SendMessage) AddReply(id string, mention bool) *SendMessage { + sms.Replies = append(sms.Replies, Replies{ + Id: id, + Mention: mention, + }) + + return sms +} + +// Create a unique nonce. +func (sms *SendMessage) CreateNonce() *SendMessage { + sms.Nonce = genULID() + return sms +} + +// Edit channel struct. +// Please see: https://developers.revolt.chat/api/#tag/Channel-Information/paths/~1channels~1:channel/patch for more information. +type EditChannel struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Icon string `json:"icon,omitempty"` + Remove string `json:"remove,omitempty"` +} + +// Set name for struct. +func (ec *EditChannel) SetName(name string) *EditChannel { + ec.Name = name + return ec +} + +// Set description for struct. +func (ec *EditChannel) SetDescription(desc string) *EditChannel { + ec.Description = desc + return ec +} + +// Set icon for struct. +func (ec *EditChannel) SetIcon(autumn_id string) *EditChannel { + ec.Icon = autumn_id + return ec +} + +// Set remove item. +func (ec *EditChannel) RemoveItem(item string) *EditChannel { + ec.Remove = item + return ec +} + +// Edit server struct. +// Please see https://developers.revolt.chat/api/#tag/Server-Information/paths/~1servers~1:server/patch for more detail. +type EditServer struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Icon string `json:"icon,omitempty"` + Banner string `json:"banner,omitempty"` + Categories []*ServerCategory `json:"categories,omitempty"` + SystemMessages *ServerSystemMessages `json:"system_messages,omitempty"` + Remove string `json:"remove,omitempty"` +} + +// Set name for struct +func (es *EditServer) SetName(name string) *EditServer { + es.Name = name + return es +} + +// Set description for struct. +func (es *EditServer) SetDescription(desc string) *EditServer { + es.Description = desc + return es +} + +// Set icon for struct. +func (es *EditServer) SetIcon(autumn_id string) *EditServer { + es.Icon = autumn_id + return es +} + +// Set banner for struct. +func (es *EditServer) SetBanner(autumn_id string) *EditServer { + es.Banner = autumn_id + return es +} + +// Add a new category for struct. +func (es *EditServer) AddCategory(category *ServerCategory) *EditServer { + es.Categories = append(es.Categories, category) + return es +} + +// Set system messages for struct. +func (es *EditServer) SetSystemMessages(sm *ServerSystemMessages) *EditServer { + es.SystemMessages = sm + return es +} + +// Set remove item. +func (es *EditServer) RemoveItem(item string) *EditServer { + es.Remove = item + return es +} + +// Edit member struct. +// Please see https://developers.revolt.chat/api/#tag/Server-Members/paths/~1servers~1:server~1members~1:member/patch for more information. +type EditMember struct { + Nickname string `json:"nickname,omitempty"` + Avatar string `json:"avatar,omitempty"` + Roles []string `json:"roles,omitempty"` + Remove string `json:"remove,omitempty"` +} + +// Set nickname for struct. +func (em *EditMember) SetNickname(nick string) *EditMember { + em.Nickname = nick + return em +} + +// Set avatar for struct. +func (em *EditMember) SetAvatar(autumn_id string) *EditMember { + em.Avatar = autumn_id + return em +} + +// Add role for struct. +func (em *EditMember) AddRole(role_id string) *EditMember { + em.Roles = append(em.Roles, role_id) + return em +} + +// Set remove item. +func (em *EditMember) RemoveItem(item string) *EditMember { + em.Remove = item + return em +} + +// Edit role struct. +type EditRole struct { + Name string `json:"name,omitempty"` + Colour string `json:"colour,omitempty"` + Hoist bool `json:"hoist,omitempty"` + Rank int `json:"rank,omitempty"` + Remove string `json:"remove,omitempty"` +} + +// Set name for struct. +func (er *EditRole) SetName(name string) *EditRole { + er.Name = name + return er +} + +// Set valid HTML color for struct. +func (er *EditRole) SetColour(color string) *EditRole { + er.Colour = color + return er +} + +// Set hoist boolean value for struct. +func (er *EditRole) IsHoist(hoist bool) *EditRole { + er.Hoist = hoist + return er +} + +// Set role ranking for struct. +func (er *EditRole) SetRank(rank int) *EditRole { + er.Rank = rank + return er +} + +// Set role ranking for struct. +func (er *EditRole) RemoveColour() *EditRole { + er.Remove = "Colour" + return er +} + +// Edit client user struct. +// See https://developers.revolt.chat/api/#tag/User-Information/paths/~1users~1@me/patch for more information. +type EditUser struct { + Status struct { + Text string `json:"text,omitempty"` + Presence string `json:"presence,omitempty"` + } `json:"status,omitempty"` + Profile struct { + Content string `json:"content,omitempty"` + Background string `json:"background,omitempty"` + } `json:"profile,omitempty"` + Avatar string `json:"avatar,omitempty"` + Remove string `json:"remove,omitempty"` +} + +// Set status for struct. +func (eu *EditUser) SetStatus(text, presence string) *EditUser { + eu.Status = struct { + Text string "json:\"text,omitempty\"" + Presence string "json:\"presence,omitempty\"" + }{ + Text: text, + Presence: presence, + } + return eu +} + +// Set profile informations for struct. +func (eu *EditUser) SetProfile(content, background string) *EditUser { + eu.Profile = struct { + Content string "json:\"content,omitempty\"" + Background string "json:\"background,omitempty\"" + }{ + Content: content, + Background: background, + } + return eu +} + +// Set avatar for struct. +func (eu *EditUser) SetAvatar(autumn_id string) *EditUser { + eu.Avatar = autumn_id + return eu +} + +// Set remove item for struct. +func (eu *EditUser) SetRemove(item string) *EditUser { + eu.Remove = item + return eu +} + +// revolt binary struct. +type Binary struct { + Data []byte +} + +// Save data to the given path. +func (b Binary) Save(path string) error { + return os.WriteFile(path, b.Data, 0666) +} + +// Edit bot struct +// Please see https://developers.revolt.chat/api/#tag/Bots/paths/~1bots~1:bot/patch for more information. +type EditBot struct { + Name string `json:"name,omitempty"` + Public bool `json:"public,omitempty"` + InteractionsUrl string `json:"interactionsURL,omitempty"` + Remove string `json:"remove,omitempty"` +} + +// Set name for struct. +func (eb *EditBot) SetName(name string) *EditBot { + eb.Name = name + return eb +} + +// Set public value for struct. +func (eb *EditBot) SetPublicValue(is_public bool) *EditBot { + eb.Public = is_public + return eb +} + +// Set interaction url for struct. +func (eb *EditBot) SetInteractionsUrl(url string) *EditBot { + eb.InteractionsUrl = url + return eb +} + +// Remove interaction url for struct. +func (eb *EditBot) RemoveInteractionsUrl() *EditBot { + eb.Remove = "InteractionsURL" + return eb +} diff --git a/web/revolt/permissions.go b/web/revolt/permissions.go new file mode 100644 index 0000000..7bcc516 --- /dev/null +++ b/web/revolt/permissions.go @@ -0,0 +1,100 @@ +package revolt + +// Permissions struct +type Permissions struct { + Bitvise uint + Mode string // can ben CHANNEL, SERVER or USER + Permissions map[string]uint +} + +// Init all of the perms for channel. +func (p *Permissions) InitChannel() *Permissions { + p.Permissions = map[string]uint{ + "VIEW": 1 << 0, + "SEND_MESSAGE": 1 << 1, + "MANAGE_MESSAGES": 1 << 2, + "MANAGE_CHANNEL": 1 << 3, + "VOICE_CALL": 1 << 4, + "INVITE_OTHERS": 1 << 5, + "EMBED_LINKS": 1 << 6, + "UPLOAD_FILES": 1 << 7, + } + p.Mode = "CHANNEL" + return p +} + +// Init all of the perms for user. +func (p *Permissions) InitUser() *Permissions { + p.Permissions = map[string]uint{ + "ACCESS": 1 << 0, + "VIEW_PROFILE": 1 << 1, + "SEND_MESSAGE": 1 << 2, + "INVITE": 1 << 3, + } + p.Mode = "USER" + return p +} + +// Init all of the perms for server. +func (p *Permissions) InitServer() *Permissions { + p.Permissions = map[string]uint{ + "VIEW": 1 << 0, + "MANAGE_ROLES": 1 << 1, + "MANAGE_CHANNELS": 1 << 2, + "MANAGE_SERVER": 1 << 3, + "KICK_MEMBERS": 1 << 4, + "BAN_MEMBERS": 1 << 5, + "TIMEOUT_MEMBERS": 1 << 6, + // 6 bits of space + "CHANGE_NICKNAME": 1 << 12, + "CHANGE_NICKNAMES": 1 << 13, + "CHANGE_AVATAR": 1 << 14, + "REMOVE_AVATARS": 1 << 15, + } + p.Mode = "SERVER" + return p +} + +// Calculate if bitvise has permission +func (p Permissions) Has(perm string) bool { + if value, ok := p.Permissions[perm]; ok { + return p.Bitvise&value != 0 + } + + return false +} + +// Add new permission(s). +func (p *Permissions) Add(perms ...string) *Permissions { + for _, perm := range perms { + if value, ok := p.Permissions[perm]; ok { + p.Bitvise = p.Bitvise | value + } + } + + return p +} + +// Remove permission(s). +func (p *Permissions) Remove(perms ...string) *Permissions { + for _, perm := range perms { + if value, ok := p.Permissions[perm]; ok { + p.Bitvise = p.Bitvise - value + } + } + + return p +} + +// Calculate perms and return unsigned int. +func (p Permissions) Calculate(perms ...string) uint { + var total uint + + for _, perm := range perms { + if value, ok := p.Permissions[perm]; ok { + total = total | value + } + } + + return total +} diff --git a/web/revolt/server.go b/web/revolt/server.go new file mode 100644 index 0000000..1345015 --- /dev/null +++ b/web/revolt/server.go @@ -0,0 +1,368 @@ +package revolt + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/oklog/ulid/v2" +) + +// Server struct. +type Server struct { + Client *Client + CreatedAt time.Time + + Id string `json:"_id"` + Nonce string `json:"nonce"` + OwnerId string `json:"owner"` + Name string `json:"name"` + Description string `json:"description"` + ChannelIds []string `json:"channels"` + Categories []*ServerCategory `json:"categories"` + SystemMessages *ServerSystemMessages `json:"system_messages"` + Roles map[string]interface{} `json:"roles"` + DefaultPermissions uint `json:"default_permissions"` + Icon *Attachment `json:"icon"` + Banner *Attachment `json:"banner"` +} + +// Server categories struct. +type ServerCategory struct { + Id string `json:"id"` + Title string `json:"title"` + ChannelIds []string `json:"channels"` +} + +// System messages struct. +type ServerSystemMessages struct { + UserJoined string `json:"user_joined,omitempty"` + UserLeft string `json:"user_left,omitempty"` + UserKicked string `json:"user_kicked,omitempty"` + UserBanned string `json:"user_banned,omitempty"` + UserTimeout string `json:"user_timeout,omitempty"` +} + +// Server member struct. +type Member struct { + Informations struct { + ServerId string `json:"server"` + UserId string `json:"user"` + } `json:"_id"` + Nickname string `json:"nickname"` + Avatar *Attachment `json:"avatar"` + Roles []string `json:"roles"` +} + +// Fetched server members struct. +type FetchedMembers struct { + Members []*Member `json:"members"` + Users []*User `json:"users"` +} + +// Fetched bans struct. +type FetchedBans struct { + Users []*User `json:"users"` + Bans []struct { + Ids struct { + UserId string `json:"user"` + ServerUd string `json:"server"` + } `json:"_id"` + Reason string `json:"reason"` + } `json:"bans"` +} + +// Calculate creation date and edit the struct. +func (s *Server) CalculateCreationDate() error { + ulid, err := ulid.Parse(s.Id) + + if err != nil { + return err + } + + s.CreatedAt = time.UnixMilli(int64(ulid.Time())) + return nil +} + +// Edit server. +func (s Server) Edit(es *EditServer) error { + data, err := json.Marshal(es) + + if err != nil { + return err + } + + _, err = s.Client.Request("PATCH", "/servers/"+s.Id, data) + + if err != nil { + return err + } + + return nil +} + +// Delete / leave server. +// If the server not created by client, it will leave. +// Otherwise it will be deleted. +func (s Server) Delete() error { + _, err := s.Client.Request("DELETE", "/servers/"+s.Id, []byte{}) + + if err != nil { + return err + } + + return nil +} + +// Create a new text-channel. +func (s Server) CreateTextChannel(name, description string) (*Channel, error) { + channel := &Channel{} + channel.Client = s.Client + + data, err := s.Client.Request("POST", "/servers/"+s.Id+"/channels", []byte("{\"type\":\"Text\",\"name\":\""+name+"\",\"description\":\""+description+"\",\"nonce\":\""+genULID()+"\"}")) + + if err != nil { + return channel, err + } + + err = json.Unmarshal(data, channel) + + if err != nil { + return channel, err + } + + return channel, nil +} + +// Create a new voice-channel. +func (s Server) CreateVoiceChannel(name, description string) (*Channel, error) { + channel := &Channel{} + channel.Client = s.Client + + data, err := s.Client.Request("POST", "/servers/"+s.Id+"/channels", []byte("{\"type\":\"Voice\",\"name\":\""+name+"\",\"description\":\""+description+"\",\"nonce\":\""+genULID()+"\"}")) + + if err != nil { + return channel, err + } + + err = json.Unmarshal(data, channel) + + if err != nil { + return channel, err + } + + return channel, nil +} + +// Fetch a member from Server. +func (s Server) FetchMember(id string) (*Member, error) { + member := &Member{} + + data, err := s.Client.Request("GET", "/servers/"+s.Id+"/members/"+id, []byte{}) + + if err != nil { + return member, err + } + + err = json.Unmarshal(data, member) + + if err != nil { + return member, err + } + + return member, nil +} + +// Fetch all of the members from Server. +func (s Server) FetchMembers() (*FetchedMembers, error) { + members := &FetchedMembers{} + + data, err := s.Client.Request("GET", "/servers/"+s.Id+"/members", []byte{}) + + if err != nil { + return members, err + } + + err = json.Unmarshal(data, members) + + if err != nil { + return members, err + } + + // Add client to the user + for _, i := range members.Users { + i.Client = s.Client + } + + return members, nil +} + +// Edit a member. +func (s Server) EditMember(id string, em *EditMember) error { + data, err := json.Marshal(em) + + if err != nil { + return err + } + + _, err = s.Client.Request("PATCH", "/servers/"+s.Id+"/members/"+id, data) + + if err != nil { + return err + } + + return nil +} + +// Kick a member from server. +func (s Server) KickMember(id string) error { + _, err := s.Client.Request("DELETE", "/servers/"+s.Id+"/members/"+id, []byte{}) + + if err != nil { + return err + } + + return nil +} + +// Ban a member from server. +func (s Server) BanMember(id, reason string) error { + _, err := s.Client.Request("PUT", "/servers/"+s.Id+"/bans/"+id, []byte("{\"reason\":\""+reason+"\"}")) + + if err != nil { + return err + } + + return nil +} + +// Unban a member from server. +func (s Server) UnbanMember(id string) error { + _, err := s.Client.Request("DELETE", "/servers/"+s.Id+"/bans/"+id, []byte{}) + + if err != nil { + return err + } + + return nil +} + +// Fetch server bans. +func (s Server) FetchBans() (*FetchedBans, error) { + bans := &FetchedBans{} + + data, err := s.Client.Request("GET", "/servers/"+s.Id+"/bans", []byte{}) + + if err != nil { + return bans, err + } + + err = json.Unmarshal(data, bans) + + if err != nil { + return bans, err + } + + // Add client to the user + for _, i := range bans.Users { + i.Client = s.Client + } + + return bans, nil +} + +// Timeout a member from server. +func (s Server) TimeoutMember(id string) error { + // Placeholder for timeout. + + return nil +} + +// Set server permissions for a role. +// Leave role field empty if you want to edit default permissions +func (s Server) SetPermissions(role_id string, channel_permissions, server_permissions uint) error { + if role_id == "" { + role_id = "default" + } + + _, err := s.Client.Request("PUT", "/servers/"+s.Id+"/permissions/"+role_id, []byte(fmt.Sprintf("{\"permissions\":{\"server\":%d,\"channel\":%d}}", channel_permissions, server_permissions))) + + if err != nil { + return err + } + + return nil +} + +// Create a new role for server. +// Returns string (role id), uint (server perms), uint (channel perms) and error. +func (s Server) CreateRole(name string) (string, uint, uint, error) { + role := &struct { + Id string `json:"id"` + Permissions []uint `json:"permissions"` + }{} + + data, err := s.Client.Request("POST", "/servers/"+s.Id+"/roles", []byte("{\"name\":\""+name+"\"}")) + + if err != nil { + return role.Id, 0, 0, err + } + + err = json.Unmarshal(data, role) + + if err != nil { + return role.Id, 0, 0, err + } + + return role.Id, role.Permissions[0], role.Permissions[1], nil +} + +// Edit a server role. +func (s Server) EditRole(id string, er *EditRole) error { + data, err := json.Marshal(er) + + if err != nil { + return err + } + + _, err = s.Client.Request("PATCH", "/servers/"+s.Id+"/roles/"+id, data) + + if err != nil { + return err + } + + return nil +} + +// Delete a server role. +func (s Server) DeleteRole(id string) error { + _, err := s.Client.Request("DELETE", "/servers/"+s.Id+"/roles/"+id, []byte{}) + + if err != nil { + return err + } + + return nil +} + +// Fetch server invite. +func (s Server) FetchInvites(id string) error { + _, err := s.Client.Request("GET", "/servers/"+id+"/invites", []byte{}) + + if err != nil { + return err + } + + return nil +} + +// Mark a server as read. +func (s Server) MarkServerAsRead(id string) error { + _, err := s.Client.Request("PUT", "/servers/"+id+"/ack", []byte{}) + + if err != nil { + return err + } + + return nil +} diff --git a/web/revolt/user.go b/web/revolt/user.go new file mode 100644 index 0000000..ccfcc81 --- /dev/null +++ b/web/revolt/user.go @@ -0,0 +1,133 @@ +package revolt + +import ( + "encoding/json" + "time" + + "github.com/oklog/ulid/v2" +) + +// User struct. +type User struct { + Client *Client + CreatedAt time.Time + + Id string `json:"_id"` + Username string `json:"username"` + Avatar *Attachment `json:"avatar"` + Relations []*UserRelations `json:"relations"` + Badges int `json:"badges"` + Status *UserStatus `json:"status"` + Relationship string `json:"relationship"` + IsOnline bool `json:"online"` + Flags int `json:"flags"` + BotInformation *BotInformation `json:"bot"` +} + +// User relations struct. +type UserRelations struct { + Id string `json:"_id"` + Status string `json:"status"` +} + +// User status struct. +type UserStatus struct { + Text string `json:"text"` + Presence string `json:"presence"` +} + +// Bot information struct. +type BotInformation struct { + Owner string `json:"owner"` +} + +// Calculate creation date and edit the struct. +func (u *User) CalculateCreationDate() error { + ulid, err := ulid.Parse(u.Id) + + if err != nil { + return err + } + + u.CreatedAt = time.UnixMilli(int64(ulid.Time())) + return nil +} + +// Create a mention format. +func (u User) FormatMention() string { + return "<@" + u.Id + ">" +} + +// Open a DM with the user. +func (u User) OpenDirectMessage() (*Channel, error) { + dmChannel := &Channel{} + dmChannel.Client = u.Client + + resp, err := u.Client.Request("GET", "/users/"+u.Id+"/dm", []byte{}) + + if err != nil { + return dmChannel, err + } + + err = json.Unmarshal(resp, dmChannel) + return dmChannel, err +} + +// Fetch default user avatar. +func (u User) FetchDefaultAvatar() (*Binary, error) { + avatarData := &Binary{} + + resp, err := u.Client.Request("GET", "/users/"+u.Id+"/default_avatar", []byte{}) + + if err != nil { + return avatarData, err + } + + avatarData.Data = resp + return avatarData, nil +} + +// Fetch user relationship. +func (u User) FetchRelationship() (*UserRelations, error) { + relationshipData := &UserRelations{} + relationshipData.Id = u.Id + + resp, err := u.Client.Request("GET", "/users/"+u.Id+"/relationship", []byte{}) + + if err != nil { + return relationshipData, err + } + + err = json.Unmarshal(resp, relationshipData) + return relationshipData, err +} + +// Block user. +func (u User) Block() (*UserRelations, error) { + relationshipData := &UserRelations{} + relationshipData.Id = u.Id + + resp, err := u.Client.Request("PUT", "/users/"+u.Id+"/block", []byte{}) + + if err != nil { + return relationshipData, err + } + + err = json.Unmarshal(resp, relationshipData) + return relationshipData, err +} + +// Un-block user. +func (u User) Unblock() (*UserRelations, error) { + relationshipData := &UserRelations{} + relationshipData.Id = u.Id + + resp, err := u.Client.Request("DELETE", "/users/"+u.Id+"/block", []byte{}) + + if err != nil { + return relationshipData, err + } + + err = json.Unmarshal(resp, relationshipData) + return relationshipData, err +} diff --git a/web/revolt/utils.go b/web/revolt/utils.go new file mode 100644 index 0000000..f9c53d6 --- /dev/null +++ b/web/revolt/utils.go @@ -0,0 +1,15 @@ +package revolt + +import ( + "math/rand" + "time" + + "github.com/oklog/ulid/v2" +) + +func genULID() string { + t := time.Now() + entropy := ulid.Monotonic(rand.New(rand.NewSource(t.UnixNano())), 0) + + return ulid.MustNew(ulid.Timestamp(t), entropy).String() +} diff --git a/web/revolt/websocket.go b/web/revolt/websocket.go new file mode 100644 index 0000000..b690dfb --- /dev/null +++ b/web/revolt/websocket.go @@ -0,0 +1,364 @@ +package revolt + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/sacOO7/gowebsocket" +) + +func (c *Client) Start() { + // Create new socket + c.Socket = gowebsocket.New(WS_URL) + c.HTTP = &http.Client{} + + // Auth the user if self-bot. + // if c.SelfBot != nil { + // c.Auth() + // } + + // Send auth when connected + c.Socket.OnConnected = func(_ gowebsocket.Socket) { + c.handleWebsocketAuth() + } + + c.Socket.OnTextMessage = func(message string, _ gowebsocket.Socket) { + //fmt.Println(message) + + // Parse data + rawData := &struct { + Type string `json:"type"` + }{} + err := json.Unmarshal([]byte(message), rawData) + + if err != nil { + c.Destroy() + panic(err) + } + + if rawData.Type == "Authenticated" { + go c.ping() + } + + // Handle events. + c.handleEvents(rawData, message) + // fmt.Println(message) + } + + // Start connection. + c.Socket.Connect() +} + +// Handle on connected. +func (c *Client) handleWebsocketAuth() { + if c.SelfBot == nil { + c.Socket.SendText(fmt.Sprintf("{\"type\":\"Authenticate\",\"token\":\"%s\"}", c.Token)) + } else { + c.Socket.SendText(fmt.Sprintf("{\"type\":\"Authenticate\",\"result\":\"Success\",\"_id\":\"%s\",\"token\":\"%s\",\"user_id\":\"%s\",\"name\":\"revolt\"}", c.SelfBot.Id, c.SelfBot.SessionToken, c.SelfBot.UserId)) + } +} + +// Destroy the websocket. +func (c *Client) Destroy() { + c.Socket.Close() +} + +// Ping websocket. +func (c *Client) ping() { + for { + time.Sleep(30 * time.Second) + c.Socket.SendText("{\"type\":\"Ping\",\"data\":0}") + } +} + +// Handle events. +func (c *Client) handleEvents(rawData *struct { + Type string `json:"type"` +}, message string) { + if rawData.Type == "Ready" { + // Add cache. + c.handleCache(message) + + // onReady event + if c.OnReadyFunctions != nil { + for _, i := range c.OnReadyFunctions { + i() + } + } + } else if rawData.Type == "Message" && c.OnMessageFunctions != nil { + // Message create event. + msgData := &Message{} + msgData.Client = c + + err := json.Unmarshal([]byte(message), msgData) + + if err != nil { + fmt.Printf("Unexcepted Error: %s", err) + } + + for _, i := range c.OnMessageFunctions { + i(msgData) + } + } else if rawData.Type == "MessageUpdate" && c.OnMessageUpdateFunctions != nil { + // Message update event. + data := &struct { + ChannelId string `json:"channel"` + MessageId string `json:"id"` + Payload map[string]interface{} `json:"data"` + }{} + + err := json.Unmarshal([]byte(message), data) + + if err != nil { + fmt.Printf("Unexcepted Error: %s", err) + } + + for _, i := range c.OnMessageUpdateFunctions { + i(data.ChannelId, data.MessageId, data.Payload) + } + } else if rawData.Type == "MessageDelete" && c.OnMessageDeleteFunctions != nil { + // Message delete event. + data := &map[string]string{} + + err := json.Unmarshal([]byte(message), data) + + if err != nil { + fmt.Printf("Unexcepted Error: %s", err) + } + + for _, i := range c.OnMessageDeleteFunctions { + i((*data)["channel"], (*data)["id"]) + } + } else if rawData.Type == "ChannelCreate" && c.OnChannelCreateFunctions != nil { + // Channel create event. + channelData := &Channel{} + channelData.Client = c + + err := json.Unmarshal([]byte(message), channelData) + + if err != nil { + fmt.Printf("Unexcepted Error: %s", err) + } + + for _, i := range c.OnChannelCreateFunctions { + i(channelData) + } + } else if rawData.Type == "ChannelUpdate" && c.OnChannelUpdateFunctions != nil { + // Channel update event. + data := &struct { + ChannelId string `json:"id"` + Clear string `json:"clear"` + Payload map[string]interface{} `json:"data"` + }{} + + err := json.Unmarshal([]byte(message), data) + + if err != nil { + fmt.Printf("Unexcepted Error: %s", err) + } + + for _, i := range c.OnChannelUpdateFunctions { + i(data.ChannelId, data.Clear, data.Payload) + } + } else if rawData.Type == "ChannelDelete" && c.OnChannelDeleteFunctions != nil { + // Channel delete event. + data := &map[string]string{} + + err := json.Unmarshal([]byte(message), data) + + if err != nil { + fmt.Printf("Unexcepted Error: %s", err) + } + + for _, i := range c.OnChannelDeleteFunctions { + i((*data)["id"]) + } + } else if rawData.Type == "GroupCreate" && c.OnGroupCreateFunctions != nil { + // Group channel create event. + groupChannelData := &Group{} + groupChannelData.Client = c + + err := json.Unmarshal([]byte(message), groupChannelData) + + if err != nil { + fmt.Printf("Unexcepted Error: %s", err) + } + + for _, i := range c.OnGroupCreateFunctions { + i(groupChannelData) + } + } else if rawData.Type == "GroupMemberAdded" && c.OnGroupMemberAddedFunctions != nil { + // Group member added event. + data := &map[string]string{} + + err := json.Unmarshal([]byte(message), data) + + if err != nil { + fmt.Printf("Unexcepted Error: %s", err) + } + + for _, i := range c.OnGroupMemberAddedFunctions { + i((*data)["id"], (*data)["user"]) + } + } else if rawData.Type == "GroupMemberRemoved" && c.OnGroupMemberRemovedFunctions != nil { + // Group member removed event. + data := &map[string]string{} + + err := json.Unmarshal([]byte(message), data) + + if err != nil { + fmt.Printf("Unexcepted Error: %s", err) + } + + for _, i := range