aboutsummaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2023-06-17 13:38:38 -0400
committerXe Iaso <me@xeiaso.net>2023-06-17 13:38:38 -0400
commit24f5fb57f3e36ff2f6150468c4cb10c4f5bb282c (patch)
tree80ae280ac54c1cdf36b43210f6e1dee062edf534 /web
parentacad94fc2d42f295e25d5a2d33d8be78290bc3a8 (diff)
downloadx-1.5.0.tar.xz
x-1.5.0.zip
cmd/marabot: move emoji copying to another binaryv1.5.0
Signed-off-by: Xe Iaso <me@xeiaso.net>
Diffstat (limited to 'web')
-rw-r--r--web/openai/chatgpt/chatgpt.go30
-rw-r--r--web/revolt/client.go167
-rw-r--r--web/revolt/emoji.go70
-rw-r--r--web/revolt/http.go49
-rw-r--r--web/revolt/websocket.go4
5 files changed, 290 insertions, 30 deletions
diff --git a/web/openai/chatgpt/chatgpt.go b/web/openai/chatgpt/chatgpt.go
index d0b70dc..aab4ce7 100644
--- a/web/openai/chatgpt/chatgpt.go
+++ b/web/openai/chatgpt/chatgpt.go
@@ -13,13 +13,35 @@ import (
)
type Request struct {
- Model string `json:"model"`
- Messages []Message `json:"messages"`
+ Model string `json:"model"`
+ Messages []Message `json:"messages"`
+ Functions []Function `json:"functions,omitempty"`
+ FunctionCall string `json:"function_call"`
+}
+
+type Function struct {
+ Name string `json:"name"`
+ Description string `json:"description,omitempty"`
+ Parameters []Param `json:"parameters"`
+}
+
+type Param struct {
+ Type string `json:"type"`
+ Description string `json:"description,omitempty"`
+ Enum []string `json:"enum,omitempty"`
+ Properties map[string]Param `json:"properties,omitempty"`
+ Required []string `json:"required,omitempty"`
}
type Message struct {
- Role string `json:"role"`
- Content string `json:"content"`
+ Role string `json:"role"`
+ Content string `json:"content"`
+ FunctionCall *Funcall `json:"function_call"`
+}
+
+type Funcall struct {
+ Name string `json:"name"`
+ Arguments json.RawMessage `json:"arguments"`
}
func (m Message) ProxyFormat() string {
diff --git a/web/revolt/client.go b/web/revolt/client.go
index 60cc001..f83f8ea 100644
--- a/web/revolt/client.go
+++ b/web/revolt/client.go
@@ -1,51 +1,129 @@
package revolt
import (
+ "bytes"
"context"
"encoding/json"
"fmt"
+ "io"
+ "mime/multipart"
"net/http"
+ "net/textproto"
"time"
"github.com/sacOO7/gowebsocket"
)
+type RevoltSettings struct {
+ Revolt string `json:"revolt"`
+ Features RevoltFeatures `json:"features"`
+ Ws string `json:"ws"`
+ App string `json:"app"`
+ Vapid string `json:"vapid"`
+ Build RevoltBuild `json:"build"`
+}
+
+type RevoltCaptcha struct {
+ Enabled bool `json:"enabled"`
+ Key string `json:"key"`
+}
+
+type RevoltAutumn struct {
+ Enabled bool `json:"enabled"`
+ URL string `json:"url"`
+}
+
+type RevoltJanuary struct {
+ Enabled bool `json:"enabled"`
+ URL string `json:"url"`
+}
+
+type RevoltVoso struct {
+ Enabled bool `json:"enabled"`
+ URL string `json:"url"`
+ Ws string `json:"ws"`
+}
+
+type RevoltFeatures struct {
+ Captcha RevoltCaptcha `json:"captcha"`
+ Email bool `json:"email"`
+ InviteOnly bool `json:"invite_only"`
+ Autumn RevoltAutumn `json:"autumn"`
+ January RevoltJanuary `json:"january"`
+ Voso RevoltVoso `json:"voso"`
+}
+
+type RevoltBuild struct {
+ CommitSha string `json:"commit_sha"`
+ CommitTimestamp string `json:"commit_timestamp"`
+ Semver string `json:"semver"`
+ OriginURL string `json:"origin_url"`
+ Timestamp string `json:"timestamp"`
+}
+
// New creates a new client with the default Revolt server details.
//
// Use NewWithEndpoint to create a client with a custom endpoint.
-func New(token string) *Client {
- return &Client{
- HTTP: &http.Client{},
- Token: token,
- BaseURL: "https://api.revolt.chat",
- WSURL: "wss://ws.revolt.chat",
- Ticker: time.NewTicker(3 * time.Second),
+func New(token string) (*Client, error) {
+ var settings RevoltSettings
+
+ resp, err := http.Get("https://api.revolt.chat/")
+ if err != nil {
+ return nil, fmt.Errorf("failed to get Revolt settings: %w", err)
+ }
+ defer resp.Body.Close()
+
+ if err := json.NewDecoder(resp.Body).Decode(&settings); err != nil {
+ return nil, fmt.Errorf("failed to decode Revolt settings: %w", err)
}
+
+ return &Client{
+ HTTP: &http.Client{},
+ Token: token,
+ BaseURL: "https://api.revolt.chat",
+ WSURL: "wss://ws.revolt.chat",
+ Ticker: time.NewTicker(3 * time.Second),
+ Settings: settings,
+ }, nil
}
// NewWithEndpoint creates a new client with a custom Revolt endpoint.
//
// You can use this to test the library against an arbirtary Revolt server.
-func NewWithEndpoint(token, baseURL, wsURL string) *Client {
- return &Client{
- HTTP: &http.Client{},
- Token: token,
- BaseURL: baseURL,
- WSURL: wsURL,
- Ticker: time.NewTicker(3 * time.Second),
+func NewWithEndpoint(token, baseURL, wsURL string) (*Client, error) {
+ var settings RevoltSettings
+
+ resp, err := http.Get(baseURL)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get Revolt settings: %w", err)
}
+ defer resp.Body.Close()
+
+ if err := json.NewDecoder(resp.Body).Decode(&settings); err != nil {
+ return nil, fmt.Errorf("failed to decode Revolt settings: %w", err)
+ }
+
+ return &Client{
+ HTTP: &http.Client{},
+ Token: token,
+ BaseURL: baseURL,
+ WSURL: wsURL,
+ Ticker: time.NewTicker(3 * time.Second),
+ Settings: settings,
+ }, nil
}
// Client struct.
type Client struct {
- SelfBot *SelfBot
- Token string
- Socket gowebsocket.Socket
- HTTP *http.Client
- Cache *Cache
- BaseURL string
- WSURL string
- Ticker *time.Ticker
+ SelfBot *SelfBot
+ Token string
+ Socket gowebsocket.Socket
+ HTTP *http.Client
+ Cache *Cache
+ BaseURL string
+ WSURL string
+ Ticker *time.Ticker
+ Settings RevoltSettings
}
// Self bot struct.
@@ -288,3 +366,48 @@ func (c *Client) FetchBot(ctx context.Context, id string) (*Bot, error) {
err = json.Unmarshal(resp, bot)
return bot.Bot, err
}
+
+// Upload a file to Revolt using a multi-part form.
+func (c *Client) Upload(ctx context.Context, tag, fname string, data []byte) (string, error) {
+ type response struct {
+ ID string `json:"id"`
+ }
+
+ // Create a new multi-part form.
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+
+ header := textproto.MIMEHeader{}
+ header.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, fname))
+ header.Set("Content-Type", http.DetectContentType(data))
+
+ // Add the file to the form.
+ part, err := writer.CreatePart(header)
+ if err != nil {
+ return "", err
+ }
+ _, err = io.Copy(part, bytes.NewReader(data))
+ if err != nil {
+ return "", err
+ }
+
+ // Close the form and set the content type.
+ err = writer.Close()
+ if err != nil {
+ return "", err
+ }
+
+ // Send the request to the server.
+ resp, err := c.RequestWithPathAndContentType(ctx, "POST", c.Settings.Features.Autumn.URL+"/"+tag, writer.FormDataContentType(), body.Bytes())
+ if err != nil {
+ return "", err
+ }
+
+ // Parse the response and return the ID of the uploaded file.
+ var res response
+ err = json.Unmarshal(resp, &res)
+ if err != nil {
+ return "", err
+ }
+ return res.ID, nil
+}
diff --git a/web/revolt/emoji.go b/web/revolt/emoji.go
index 57f37ac..57e5324 100644
--- a/web/revolt/emoji.go
+++ b/web/revolt/emoji.go
@@ -1,6 +1,76 @@
package revolt
+import (
+ "context"
+ "encoding/json"
+)
+
// Emoji struct.
type Emoji struct {
+ ID string `json:"_id"`
+ Name string `json:"name"`
+ Animated bool `json:"animated"`
+ NSFW bool `json:"nsfw"`
+ CreatorID string `json:"creator_id"`
+ URL string `json:"url"`
+}
+
+// Emoji grabs a single emoji by URL.
+func (c *Client) Emoji(ctx context.Context, id string) (*Emoji, error) {
+ emoji := &Emoji{}
+
+ resp, err := c.Request(ctx, "GET", "/custom/emoji/"+id, []byte{})
+
+ if err != nil {
+ return nil, err
+ }
+
+ err = json.Unmarshal(resp, emoji)
+ if err != nil {
+ return nil, err
+ }
+
+ emoji.URL = c.Settings.Features.Autumn.URL + "/emojis/" + emoji.ID
+
+ return emoji, err
+}
+
+type CreateEmoji struct {
+ Name string `json:"name"`
+ NSFW bool `json:"nsfw"`
+ Parent Parent `json:"parent"`
+}
+
+type Parent struct {
+ Type string `json:"type"`
+ ID string `json:"id"`
+}
+
+// CreateEmoji creates a new emoji.
+func (c *Client) CreateEmoji(ctx context.Context, uploadID string, emoji CreateEmoji) (*Emoji, error) {
+ data, err := json.Marshal(emoji)
+ if err != nil {
+ return nil, err
+ }
+
+ var emojiData Emoji
+
+ resp, err := c.Request(ctx, "PUT", "/custom/emoji/"+uploadID, data)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := json.Unmarshal(resp, &emojiData); err != nil {
+ return nil, err
+ }
+
+ emojiData.URL = c.Settings.Features.Autumn.URL + "/emojis/" + emojiData.ID
+
+ return &emojiData, nil
+}
+// DeleteEmoji deletes an emoji.
+func (c *Client) DeleteEmoji(ctx context.Context, id string) error {
+ _, err := c.Request(ctx, "DELETE", "/custom/emoji/"+id, []byte{})
+ return err
}
diff --git a/web/revolt/http.go b/web/revolt/http.go
index b7293d2..377eb62 100644
--- a/web/revolt/http.go
+++ b/web/revolt/http.go
@@ -9,19 +9,18 @@ import (
"within.website/x/web"
)
-// Send http request
-func (c Client) Request(ctx context.Context, method, path string, data []byte) ([]byte, error) {
+func (c *Client) RequestWithPathAndContentType(ctx context.Context, method, path, contentType string, data []byte) ([]byte, error) {
reqBody := bytes.NewBuffer(data)
<-c.Ticker.C
// Prepare request
- req, err := http.NewRequestWithContext(ctx, method, c.BaseURL+path, reqBody)
+ req, err := http.NewRequestWithContext(ctx, method, path, reqBody)
if err != nil {
return []byte{}, err
}
- req.Header.Set("content-type", "application/json")
+ req.Header.Set("content-type", contentType)
// Set auth headers
if c.SelfBot == nil {
@@ -38,15 +37,57 @@ func (c Client) Request(ctx context.Context, method, path string, data []byte) (
}
defer resp.Body.Close()
+
+ if !(resp.StatusCode >= 200 && resp.StatusCode < 300) {
+ return []byte{}, web.NewError(200, resp)
+ }
+
body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return []byte{}, err
+ }
+
+ return body, nil
+}
+
+// Send http request
+func (c Client) Request(ctx context.Context, method, path string, data []byte) ([]byte, error) {
+ reqBody := bytes.NewBuffer(data)
+
+ <-c.Ticker.C
+
+ // Prepare request
+ req, err := http.NewRequestWithContext(ctx, method, c.BaseURL+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()
+
if !(resp.StatusCode >= 200 && resp.StatusCode < 300) {
return []byte{}, web.NewError(200, resp)
}
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return []byte{}, err
+ }
+
return body, nil
}
diff --git a/web/revolt/websocket.go b/web/revolt/websocket.go
index 84fbbcd..2e2ad74 100644
--- a/web/revolt/websocket.go
+++ b/web/revolt/websocket.go
@@ -17,6 +17,10 @@ func (c *Client) Connect(ctx context.Context, handler Handler) {
defer t.Stop()
go func(ctx context.Context) {
+ if err := c.doWebsocket(ctx, c.Token, c.WSURL, handler); err != nil {
+ ln.Error(ctx, err, ln.Info("websocket error, retrying"))
+ }
+
for {
select {
case <-ctx.Done():