├── LICENSE
├── README.md
├── constant.go
├── error.go
├── example
└── poe_test.go
├── go.mod
├── go.sum
├── models.go
├── poe.go
├── poe_graphql
├── AddHumanMessageMutation.graphql
├── AddMessageBreakMutation.graphql
├── AutoSubscriptionMutation.graphql
├── BioFragment.graphql
├── ChatAddedSubscription.graphql
├── ChatFragment.graphql
├── ChatListPaginationQuery.graphql
├── ChatPaginationQuery.graphql
├── ChatViewQuery.graphql
├── DeleteHumanMessagesMutation.graphql
├── DeleteMessageMutation.graphql
├── ExploreBotsListPaginationQuery.graphql
├── HandleFragment.graphql
├── LoginWithVerificationCodeMutation.graphql
├── MessageAddedSubscription.graphql
├── MessageDeletedSubscription.graphql
├── MessageFragment.graphql
├── MessageRemoveVoteMutation.graphql
├── MessageSetVoteMutation.graphql
├── PoeBotCreateMutation.graphql
├── PoeBotEditMutation.graphql
├── SendMessageMutation.graphql
├── SendVerificationCodeForLoginMutation.graphql
├── ShareMessagesMutation.graphql
├── SignupWithVerificationCodeMutation.graphql
├── StaleChatUpdateMutation.graphql
├── SubscriptionsMutation.graphql
├── SummarizePlainPostQuery.graphql
├── SummarizeQuotePostQuery.graphql
├── SummarizeSharePostQuery.graphql
├── UserSnippetFragment.graphql
├── ViewerInfoQuery.graphql
├── ViewerStateFragment.graphql
└── ViewerStateUpdatedSubscription.graphql
├── poe_test.go
└── utils.go
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 li wei
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Golang Poe API
2 | The golang version of https://github.com/ading2210/poe-api, used for the golang project to call poe api
3 |
4 |
5 | The latest commit that is currently compatible is https://github.com/ading2210/poe-api/tree/7470d07b989af596293f24d0bcfc6315161505e0
6 |
7 | # Notice
8 | As a single person, it can be challenging to keep up with the frequent API definition changes made by Poe in the ading2210/poe-api project. Therefore,
9 | welcome those with the necessary skills to contribute by submitting modifications directly via MR, rather than forking the code.
10 | This will help streamline the process and ensure that updates are efficiently implemented.
11 |
12 | # Instructions
13 |
14 | ## install
15 |
16 | ```bash
17 | go get github.com/lwydyby/poe-api
18 | ```
19 |
20 | ## use
21 |
22 | ```golang
23 |
24 | import (
25 | "fmt"
26 | "time"
27 |
28 | "github.com/lwydyby/poe-api"
29 | )
30 |
31 |
32 | func ExampleSendMessage() {
33 | c := poe_api.NewClient("", nil)
34 | res, err := c.SendMessage("ChatGPT", "一句话描述golang的channel", true, 30*time.Second)
35 | if err != nil {
36 | panic(err)
37 | }
38 | // 等待全部返回后 直接返回全文
39 | fmt.Println(poe_api.GetFinalResponse(res))
40 | res, err = c.SendMessage("ChatGPT", "channel是并发安全的吗", false, 30*time.Second)
41 | if err != nil {
42 | panic(err)
43 | }
44 | // 流式返回 每次返回新增的数据
45 | for m := range poe_api.GetTextStream(res) {
46 | fmt.Println(m)
47 | }
48 | // output:
49 | }
50 | ```
51 |
--------------------------------------------------------------------------------
/constant.go:
--------------------------------------------------------------------------------
1 | package poe_api
2 |
3 | import (
4 | "embed"
5 | "io/fs"
6 | "log"
7 | "os"
8 | "path/filepath"
9 | "strings"
10 |
11 | fhttp "github.com/bogdanfinn/fhttp"
12 | )
13 |
14 | const (
15 | gqlURL = "https://poe.com/api/gql_POST"
16 | gqlRecvURL = "https://poe.com/api/receive_POST"
17 | homeURL = "https://poe.com"
18 | settingsURL = "https://poe.com/api/settings"
19 | )
20 |
21 | //go:embed poe_graphql/*.graphql
22 | var graphql embed.FS
23 | var queries = make(map[string]string)
24 |
25 | var logger = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
26 |
27 | var userAgent = "This will be ignored! See the README for info on how to set custom headers."
28 | var headers = fhttp.Header{
29 | "User-Agent": []string{"Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"},
30 | "Accept": []string{"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"},
31 | "Accept-Encoding": []string{"gzip, deflate, br"},
32 | "Accept-Language": []string{"en-US,en;q=0.5"},
33 | "Te": []string{"trailers"},
34 | "Upgrade-Insecure-Requests": []string{"1"},
35 | }
36 | var clientIdentifier = "firefox_102"
37 |
38 | func init() {
39 | loadQueries()
40 | }
41 |
42 | func loadQueries() {
43 | queryFS, err := fs.Sub(graphql, "poe_graphql")
44 | if err != nil {
45 | panic(err)
46 | }
47 | // 遍历嵌入的查询文件
48 | err = fs.WalkDir(queryFS, ".", func(path string, d fs.DirEntry, err error) error {
49 | if err != nil {
50 | return err
51 | }
52 | if d.IsDir() || filepath.Ext(path) != ".graphql" {
53 | return nil
54 | }
55 |
56 | queryBytes, err := fs.ReadFile(queryFS, path)
57 | if err != nil {
58 | return err
59 | }
60 |
61 | // 将查询文件内容存储到 queries 映射中
62 | queries[strings.TrimSuffix(d.Name(), filepath.Ext(d.Name()))] = string(queryBytes)
63 | return nil
64 | })
65 | if err != nil {
66 | panic(err)
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/error.go:
--------------------------------------------------------------------------------
1 | package poe_api
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | type InvalidToken struct {
8 | token string
9 | }
10 |
11 | func (e *InvalidToken) Error() string {
12 | return fmt.Sprintf("Invalid token: %s", e.token)
13 | }
14 |
--------------------------------------------------------------------------------
/example/poe_test.go:
--------------------------------------------------------------------------------
1 | package example
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/lwydyby/poe-api"
8 | )
9 |
10 | func ExampleSendMessage() {
11 | c := poe_api.NewClient("", nil)
12 | res, err := c.SendMessage("ChatGPT", "一句话描述golang的channel", true, 30*time.Second)
13 | if err != nil {
14 | panic(err)
15 | }
16 | fmt.Println(poe_api.GetFinalResponse(res))
17 | res, err = c.SendMessage("ChatGPT", "channel是并发安全的吗", false, 30*time.Second)
18 | if err != nil {
19 | panic(err)
20 | }
21 | for m := range poe_api.GetTextStream(res) {
22 | fmt.Println(m)
23 | }
24 | // output:
25 | }
26 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/lwydyby/poe-api
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/bogdanfinn/fhttp v0.5.23
7 | github.com/bogdanfinn/tls-client v1.3.12
8 | github.com/google/uuid v1.3.0
9 | github.com/gorilla/websocket v1.5.0
10 | )
11 |
12 | require (
13 | github.com/andybalholm/brotli v1.0.4 // indirect
14 | github.com/bogdanfinn/utls v1.5.16 // indirect
15 | github.com/klauspost/compress v1.15.12 // indirect
16 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 // indirect
17 | github.com/zhangyunhao116/fastrand v0.3.0 // indirect
18 | github.com/zhangyunhao116/skipmap v0.10.1 // indirect
19 | golang.org/x/crypto v0.1.0 // indirect
20 | golang.org/x/net v0.5.0 // indirect
21 | golang.org/x/sys v0.4.0 // indirect
22 | golang.org/x/text v0.6.0 // indirect
23 | )
24 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
2 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
3 | github.com/bogdanfinn/fhttp v0.5.23 h1:4Xb5OjYArB8GpnUw4A4r5jmt8UW0/Cvey3R9nS2dC9U=
4 | github.com/bogdanfinn/fhttp v0.5.23/go.mod h1:brqi5woc5eSCVHdKYBV8aZLbO7HGqpwyDLeXW+fT18I=
5 | github.com/bogdanfinn/tls-client v1.3.12 h1:jpNj7owMY/oULUQyAhAv6tRFkliFGLyr8Qx1ZZY/gp8=
6 | github.com/bogdanfinn/tls-client v1.3.12/go.mod h1:Q46nwIm0wPCweDM3XZcupxEIsTOWo3HVYSSsDj02/Qo=
7 | github.com/bogdanfinn/utls v1.5.16 h1:NhhWkegEcYETBMj9nvgO4lwvc6NcLH+znrXzO3gnw4M=
8 | github.com/bogdanfinn/utls v1.5.16/go.mod h1:mHeRCi69cUiEyVBkKONB1cAbLjRcZnlJbGzttmiuK4o=
9 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
10 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
11 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
12 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
13 | github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM=
14 | github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
15 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 h1:YqAladjX7xpA6BM04leXMWAEjS0mTZ5kUU9KRBriQJc=
16 | github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5/go.mod h1:2JjD2zLQYH5HO74y5+aE3remJQvl6q4Sn6aWA2wD1Ng=
17 | github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
18 | github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc=
19 | github.com/zhangyunhao116/skipmap v0.10.1 h1:CMH4yGZQESBM1kUNozQqQ+Ra2pKqwF3HxaTADOaIfPs=
20 | github.com/zhangyunhao116/skipmap v0.10.1/go.mod h1:CClnLPHl3DI+hHgrcy0OZ/QJ45AWgA3ObVcQyJop12c=
21 | golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
22 | golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
23 | golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
24 | golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
25 | golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
26 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
27 | golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
28 | golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
29 |
--------------------------------------------------------------------------------
/models.go:
--------------------------------------------------------------------------------
1 | package poe_api
2 |
3 | type CreateBot struct {
4 | Handle string
5 | Prompt string
6 | DisplayName string
7 | BaseModel string
8 | Description string
9 | IntroMessage string
10 | ApiKey *string
11 | ApiBot bool
12 | ApiUrl *string
13 | PromptPublic *bool
14 | PfpUrl *string
15 | Linkification bool
16 | MarkdownRendering *bool
17 | SuggestedReplies bool
18 | Private bool
19 | Temperature string
20 | }
21 |
22 | type Point interface {
23 | string | bool
24 | }
25 |
26 | func GetPoint[T Point](s T) *T {
27 | return &s
28 | }
29 |
--------------------------------------------------------------------------------
/poe.go:
--------------------------------------------------------------------------------
1 | package poe_api
2 |
3 | import (
4 | "bytes"
5 | "crypto/md5"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "io"
10 | "log"
11 | "math"
12 | "math/rand"
13 | "net/http"
14 | "net/url"
15 | "regexp"
16 | "strconv"
17 | "strings"
18 | "sync"
19 | "sync/atomic"
20 | "time"
21 |
22 | fhttp "github.com/bogdanfinn/fhttp"
23 | tls_client "github.com/bogdanfinn/tls-client"
24 | "github.com/gorilla/websocket"
25 | "github.com/zhangyunhao116/skipmap"
26 | )
27 |
28 | type Client struct {
29 | token string
30 | deviceID string
31 | proxy *url.URL
32 | session tls_client.HttpClient
33 | activeMessages *skipmap.StringMap[float64]
34 | messageQueues *skipmap.StringMap[chan map[string]interface{}]
35 | headers fhttp.Header
36 | formKey string
37 | viewer map[string]interface{}
38 | userID string
39 | nextData map[string]interface{}
40 | channel map[string]interface{}
41 | bots map[string]interface{}
42 | botNames map[string]string
43 | gqlHeaders http.Header
44 | wsDomain string
45 | wsConn *websocket.Conn
46 | wsConnected bool
47 | requestCount atomic.Int64
48 | }
49 |
50 | func NewClient(token string, proxy *url.URL) *Client {
51 | // Initialize the client
52 | client := &Client{
53 | token: token,
54 | deviceID: "",
55 | proxy: proxy,
56 | headers: headers,
57 | activeMessages: skipmap.NewString[float64](),
58 | messageQueues: skipmap.NewString[chan map[string]interface{}](),
59 | }
60 | // Set up the session
61 | client.setupSession(token)
62 |
63 | // Set up the connection
64 | client.setupConnection()
65 | client.connectWs()
66 |
67 | return client
68 | }
69 |
70 | func (c *Client) GetBots() map[string]string {
71 | return c.botNames
72 | }
73 |
74 | func (c *Client) SendMessage(chatbot, message string, withChatBreak bool, timeout time.Duration) (<-chan map[string]interface{}, error) {
75 | // 支持通过name获取chatbot 而不需要拿到poe后端需要的name
76 | if name, ok := c.botNames[chatbot]; ok {
77 | chatbot = name
78 | }
79 | result := make(chan map[string]interface{})
80 | timer := 0 * time.Second
81 | // 防止并发 这里要先检查下是否有仍然未完成的消息
82 | for c.activeMessages.Len() != 0 {
83 | time.Sleep(10 * time.Millisecond)
84 | timer += 10 * time.Millisecond
85 | if timer > timeout {
86 | return nil, errors.New("timed out waiting for other messages to send")
87 | }
88 | }
89 | log.Printf("Sending message to %s: %s", chatbot, message)
90 |
91 | if !c.wsConnected {
92 | c.disconnectWs()
93 | c.setupConnection()
94 | c.connectWs()
95 | }
96 |
97 | chatID := c.getBotByCodename(chatbot)["chatId"].(float64)
98 | messageData := c.sendQuery("SendMessageMutation", map[string]interface{}{
99 | "bot": chatbot,
100 | "query": message,
101 | "chatId": chatID,
102 | "source": nil,
103 | "clientNonce": generateNonce(16),
104 | "sdid": c.deviceID,
105 | "withChatBreak": withChatBreak,
106 | }, 0)
107 |
108 | if messageData["data"].(map[string]interface{})["messageEdgeCreate"].(map[string]interface{})["message"] == nil {
109 | return nil, fmt.Errorf("daily limit reached for %s", chatbot)
110 | }
111 |
112 | humanMessage := messageData["data"].(map[string]interface{})["messageEdgeCreate"].(map[string]interface{})["message"].(map[string]interface{})
113 | humanMessageIDFloat64 := humanMessage["node"].(map[string]interface{})["messageId"].(float64)
114 | humanMessageID := fmt.Sprintf("%v", humanMessageIDFloat64)
115 | c.activeMessages.Store(humanMessageID, 0)
116 | c.messageQueues.Store(humanMessageID, make(chan map[string]interface{}, 1))
117 | var lastChan = make(chan string, 1)
118 | go c.dealMessage(humanMessageID, lastChan, result, timeout)
119 | go c.sendRecv(humanMessageID, chatbot, chatbot, lastChan)
120 | return result, nil
121 | }
122 |
123 | func (c *Client) SendChatBreak(chatbot string) (map[string]interface{}, error) {
124 | log.Printf("Sending chat break to %s", chatbot)
125 | result := c.sendQuery("AddMessageBreakMutation", map[string]interface{}{
126 | "chatId": c.getBotByCodename(chatbot)["chatId"],
127 | }, 0)
128 | return result["data"].(map[string]interface{})["messageBreakCreate"].(map[string]interface{})["message"].(map[string]interface{}), nil
129 | }
130 |
131 | func (c *Client) GetMessageHistory(chatbot string, count int, cursor interface{}) ([]map[string]interface{}, error) {
132 | log.Printf("Downloading %d messages from %s", count, chatbot)
133 |
134 | messages := []map[string]interface{}{}
135 |
136 | if cursor == nil {
137 | chatData := c.getBot(chatbot)
138 | if len(chatData["messagesConnection"].(map[string]interface{})["edges"].([]interface{})) == 0 {
139 | return []map[string]interface{}{}, nil
140 | }
141 |
142 | edges := chatData["messagesConnection"].(map[string]interface{})["edges"].([]map[string]interface{})
143 | messages = edges[int(math.Max(float64(len(edges)-count), 0)):]
144 | cursor = chatData["messagesConnection"].(map[string]interface{})["pageInfo"].(map[string]interface{})["startCursor"]
145 | count -= len(messages)
146 | }
147 |
148 | if count <= 0 {
149 | return messages, nil
150 | }
151 |
152 | if count > 50 {
153 | var err error
154 | messages, err = c.GetMessageHistory(chatbot, 50, cursor)
155 | if err != nil {
156 | return nil, err
157 | }
158 | for count > 0 {
159 | count -= 50
160 | newCursor := messages[0]["cursor"].(string)
161 | newMessages, err := c.GetMessageHistory(chatbot, min(50, count), newCursor)
162 | if err != nil {
163 | return nil, err
164 | }
165 | messages = append(newMessages, messages...)
166 | }
167 | return messages, nil
168 | }
169 |
170 | result := c.sendQuery("ChatListPaginationQuery", map[string]interface{}{
171 | "count": count,
172 | "cursor": cursor,
173 | "id": c.getBotByCodename(chatbot)["id"].(string),
174 | }, 0)
175 | queryMessages := result["data"].(map[string]interface{})["node"].(map[string]interface{})["messagesConnection"].(map[string]interface{})["edges"].([]map[string]interface{})
176 | messages = append(queryMessages, messages...)
177 | return messages, nil
178 | }
179 |
180 | func (c *Client) DeleteMessage(messageIDs []int) error {
181 | log.Printf("Deleting messages: %v", messageIDs)
182 | c.sendQuery("DeleteMessageMutation", map[string]interface{}{
183 | "messageIds": messageIDs,
184 | }, 0)
185 | return nil
186 | }
187 |
188 | func (c *Client) PurgeConversation(chatbot string, count int) error {
189 | log.Printf("Purging messages from %s", chatbot)
190 | lastMessages, err := c.GetMessageHistory(chatbot, 50, nil)
191 | if err != nil {
192 | return err
193 | }
194 | reverseSlice(lastMessages)
195 |
196 | for len(lastMessages) > 0 {
197 | var messageIDs []int
198 |
199 | for _, message := range lastMessages {
200 | if count == 0 {
201 | break
202 | }
203 | count--
204 | messageID := int(message["node"].(map[string]interface{})["messageId"].(float64))
205 | messageIDs = append(messageIDs, messageID)
206 | }
207 |
208 | err := c.DeleteMessage(messageIDs)
209 | if err != nil {
210 | return err
211 | }
212 |
213 | if count == 0 {
214 | return nil
215 | }
216 | lastMessages, err = c.GetMessageHistory(chatbot, 50, nil)
217 | if err != nil {
218 | return err
219 | }
220 | reverseSlice(lastMessages)
221 | }
222 |
223 | log.Printf("No more messages left to delete.")
224 | return nil
225 | }
226 |
227 | func (c *Client) CreateBot(req CreateBot) map[string]interface{} {
228 | if req.PromptPublic == nil {
229 | req.PromptPublic = GetPoint[bool](true)
230 | }
231 | if req.MarkdownRendering == nil {
232 | req.MarkdownRendering = GetPoint[bool](true)
233 | }
234 | result := c.sendQuery("PoeBotCreateMutation", map[string]interface{}{
235 | "baseBot": req.BaseModel,
236 | "displayName": req.DisplayName,
237 | "handle": req.Handle,
238 | "prompt": req.Prompt,
239 | "isPromptPublic": req.PromptPublic,
240 | "introduction": req.IntroMessage,
241 | "description": req.Description,
242 | "profilePictureUrl": req.PfpUrl,
243 | "apiUrl": req.ApiUrl,
244 | "apiKey": req.ApiKey,
245 | "isApiBot": req.ApiBot,
246 | "hasLinkification": req.Linkification,
247 | "hasMarkdownRendering": req.MarkdownRendering,
248 | "hasSuggestedReplies": req.SuggestedReplies,
249 | "isPrivateBot": req.Private,
250 | "temperature": req.Temperature,
251 | }, 0)
252 | data := getMap(getMap(result, "data"), "poeBotCreate")
253 | if data["status"] != "success" {
254 | panic(errors.New("Poe returned an error while trying to create a bot "))
255 | }
256 | c.getBots(false)
257 | return data
258 | }
259 |
260 | func (c *Client) EditBot(botID string, req CreateBot) map[string]interface{} {
261 | if req.PromptPublic == nil {
262 | req.PromptPublic = GetPoint[bool](true)
263 | }
264 | if req.MarkdownRendering == nil {
265 | req.MarkdownRendering = GetPoint[bool](true)
266 | }
267 | result := c.sendQuery("PoeBotEditMutation", map[string]interface{}{
268 | "botId": botID,
269 | "baseBot": req.BaseModel,
270 | "displayName": req.DisplayName,
271 | "handle": req.Handle,
272 | "prompt": req.Prompt,
273 | "isPromptPublic": req.PromptPublic,
274 | "introduction": req.IntroMessage,
275 | "description": req.Description,
276 | "profilePictureUrl": req.PfpUrl,
277 | "apiUrl": req.ApiUrl,
278 | "apiKey": req.ApiKey,
279 | "isApiBot": req.ApiBot,
280 | "hasLinkification": req.Linkification,
281 | "hasMarkdownRendering": req.MarkdownRendering,
282 | "hasSuggestedReplies": req.SuggestedReplies,
283 | "isPrivateBot": req.Private,
284 | "temperature": req.Temperature,
285 | }, 0)
286 | data := getMap(getMap(result, "data"), "poeBotEdit")
287 | if data["status"] != "success" {
288 | panic(errors.New("Poe returned an error while trying to create a bot "))
289 | }
290 | c.getBots(false)
291 | return data
292 | }
293 |
294 | func (c *Client) requestWithRetries(method string, url string, attempts int, data []byte, headers map[string][]string) (*fhttp.Response, error) {
295 | if attempts == 0 {
296 | attempts = 10
297 | }
298 | client := c.session
299 | var payload io.Reader
300 | if data != nil {
301 | payload = bytes.NewBuffer(data)
302 | }
303 | req, err := fhttp.NewRequest(method, url, payload)
304 | if err != nil {
305 | return nil, err
306 | }
307 | req.Header = c.headers.Clone()
308 | if headers != nil {
309 | for key, value := range headers {
310 | req.Header[key] = value
311 | }
312 | }
313 |
314 | for i := 0; i < attempts; i++ {
315 | resp, err := client.Do(req)
316 | if err != nil {
317 | return nil, err
318 | }
319 | if resp.StatusCode == http.StatusOK {
320 | return resp, nil
321 | }
322 | if resp.StatusCode == http.StatusTemporaryRedirect {
323 | body, _ := io.ReadAll(resp.Body)
324 | if strings.HasPrefix(resp.Header.Get("Location"), "/login") {
325 | return nil, &InvalidToken{token: c.token}
326 | }
327 | fmt.Println(body)
328 | }
329 | logger.Printf("Server returned a status code of %d while downloading %s. Retrying (%d/%d)...", resp.StatusCode, url, i+1, attempts)
330 | time.Sleep(time.Second)
331 | }
332 |
333 | return nil, fmt.Errorf("failed to download %s too many times", url)
334 | }
335 |
336 | func (c *Client) setupSession(token string) {
337 | // Set up the session with the provided token and proxy
338 | jar := tls_client.NewCookieJar()
339 | options := []tls_client.HttpClientOption{
340 | tls_client.WithTimeoutSeconds(30),
341 | tls_client.WithClientProfile(tls_client.Firefox_102),
342 | tls_client.WithNotFollowRedirects(),
343 | tls_client.WithCookieJar(jar), // create cookieJar instance and pass it as argument
344 | }
345 |
346 | client, err := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
347 | if err != nil {
348 | log.Println(err)
349 | return
350 | }
351 | c.session = client
352 |
353 | if c.proxy != nil {
354 | c.session.SetProxy(c.proxy.String())
355 | log.Printf("Proxy enabled: %s\n", c.proxy.String())
356 | }
357 |
358 | // Update session headers
359 | c.headers.Set("Referrer", "https://poe.com/")
360 | c.headers.Set("Origin", "https://poe.com")
361 | c.headers.Set("Host", "poe.com")
362 | c.headers.Set("Sec-Fetch-Dest", "empty")
363 | c.headers.Set("Sec-Fetch-Mode", "cors")
364 | c.headers.Set("Sec-Fetch-Site", "same-origin")
365 | c.headers.Set("Client-Identifier", clientIdentifier)
366 | for key, value := range headers {
367 | c.headers[key] = value
368 | }
369 | // Set cookie
370 | cookie := &fhttp.Cookie{
371 | Name: "p-b",
372 | Value: token,
373 | Domain: "poe.com",
374 | }
375 | url, err := url.Parse(homeURL)
376 | if err != nil {
377 | panic(err)
378 | }
379 | c.session.SetCookies(url, []*fhttp.Cookie{cookie})
380 | }
381 |
382 | func (c *Client) setupConnection() {
383 | c.wsDomain = fmt.Sprintf("tch%d", rand.Intn(1000000))
384 | c.nextData = c.getNextData(true)
385 | c.channel = c.getChannelData()
386 | c.bots = c.getBots(false)
387 | c.botNames = c.getBotNames()
388 |
389 | if c.deviceID == "" {
390 | c.deviceID = c.getDeviceID()
391 | }
392 |
393 | c.gqlHeaders = make(http.Header)
394 | c.gqlHeaders.Set("poe-formkey", c.formKey)
395 | c.gqlHeaders.Set("poe-tchannel", c.channel["channel"].(string))
396 |
397 | for k, v := range c.headers {
398 | c.gqlHeaders[k] = v
399 | }
400 |
401 | c.subscribe()
402 | }
403 |
404 | func (c *Client) getDeviceID() string {
405 | userID := c.viewer["poeUser"].(map[string]interface{})["id"].(string)
406 | deviceID := getSavedDeviceID(userID)
407 | return deviceID
408 | }
409 |
410 | func (c *Client) extractFormKey(html string) string {
411 | scriptRegex := regexp.MustCompile(``)
412 | scriptText := scriptRegex.FindStringSubmatch(html)[1]
413 | keyRegex := regexp.MustCompile(`var .="([0-9a-f]+)",`)
414 | keyText := keyRegex.FindStringSubmatch(scriptText)[1]
415 | cipherRegex := regexp.MustCompile(`.\[(\d+)\]=.\[(\d+)\]`)
416 | cipherPairs := cipherRegex.FindAllStringSubmatch(scriptText, -1)
417 |
418 | formKeyList := make([]string, len(cipherPairs))
419 | for _, pair := range cipherPairs {
420 | formKeyIndex, _ := strconv.Atoi(pair[1])
421 | keyIndex, _ := strconv.Atoi(pair[2])
422 | formKeyList[formKeyIndex] = string(keyText[keyIndex])
423 | }
424 | formKey := strings.Join(formKeyList, "")
425 |
426 | return formKey
427 | }
428 |
429 | func (c *Client) getNextData(overwriteVars bool) map[string]interface{} {
430 | resp, err := c.requestWithRetries(http.MethodGet, homeURL, 0, nil, nil)
431 | if err != nil {
432 | panic(err)
433 | }
434 |
435 | defer resp.Body.Close()
436 | body, err := io.ReadAll(resp.Body)
437 |
438 | jsonRegex := regexp.MustCompile(``)
439 | jsonText := jsonRegex.FindStringSubmatch(string(body))[1]
440 |
441 | var nextData map[string]interface{}
442 | err = json.Unmarshal([]byte(jsonText), &nextData)
443 |
444 | if overwriteVars {
445 | c.formKey = c.extractFormKey(string(body))
446 | if containKey("payload", nextData["props"].(map[string]interface{})["pageProps"].(map[string]interface{})) {
447 | c.viewer = nextData["props"].(map[string]interface{})["pageProps"].(map[string]interface{})["payload"].(map[string]interface{})["viewer"].(map[string]interface{})
448 | } else {
449 | c.viewer = nextData["props"].(map[string]interface{})["pageProps"].(map[string]interface{})["data"].(map[string]interface{})["viewer"].(map[string]interface{})
450 | }
451 | c.userID = c.viewer["poeUser"].(map[string]interface{})["id"].(string)
452 | c.nextData = nextData
453 | }
454 |
455 | return nextData
456 | }
457 |
458 | func (c *Client) getBot(displayName string) map[string]interface{} {
459 | url := fmt.Sprintf("https://poe.com/_next/data/%s/%s.json", c.nextData["buildId"].(string), displayName)
460 |
461 | resp, err := c.requestWithRetries(http.MethodGet, url, 0, nil, nil)
462 | if err != nil {
463 | // handle error
464 | }
465 |
466 | defer resp.Body.Close()
467 | body, err := io.ReadAll(resp.Body)
468 |
469 | var jsonData map[string]interface{}
470 | err = json.Unmarshal(body, &jsonData)
471 |
472 | var chatData map[string]interface{}
473 | if containKey("payload", jsonData["pageProps"].(map[string]interface{})) {
474 | chatData = jsonData["pageProps"].(map[string]interface{})["payload"].(map[string]interface{})["chatOfBotHandle"].(map[string]interface{})
475 | } else {
476 | chatData = jsonData["pageProps"].(map[string]interface{})["data"].(map[string]interface{})["chatOfBotHandle"].(map[string]interface{})
477 | }
478 | return chatData
479 | }
480 |
481 | func (c *Client) getBots(downloadNextData bool) map[string]interface{} {
482 | if _, ok := c.viewer["availableBotsConnection"]; !ok {
483 | panic("Invalid token or no bots are available.")
484 | }
485 | botList := c.viewer["availableBotsConnection"].(map[string]interface{})["edges"].([]interface{})
486 |
487 | var wg sync.WaitGroup
488 | bots := make(map[string]interface{})
489 | lock := &sync.Mutex{}
490 |
491 | getBotThread := func(bot map[string]interface{}) {
492 | defer wg.Done()
493 | lock.Lock()
494 | defer lock.Unlock()
495 | chatData := c.getBot(bot["node"].(map[string]interface{})["handle"].(string))
496 | bots[chatData["defaultBotObject"].(map[string]interface{})["nickname"].(string)] = chatData
497 | }
498 |
499 | wg.Add(len(botList))
500 | for _, bot := range botList {
501 | go getBotThread(bot.(map[string]interface{}))
502 | }
503 | wg.Wait()
504 |
505 | c.bots = bots
506 | c.botNames = c.getBotNames()
507 | return bots
508 | }
509 |
510 | func (c *Client) getBotByCodename(botCodename string) map[string]interface{} {
511 | if bot, ok := c.bots[botCodename]; ok {
512 | return bot.(map[string]interface{})
513 | }
514 | // TODO: Cache this so it isn't re-downloaded every time
515 | return c.getBot(botCodename)
516 | }
517 |
518 | func (c *Client) getBotNames() map[string]string {
519 | result := map[string]string{}
520 | for k, v := range c.bots {
521 | object := v.(map[string]interface{})["defaultBotObject"].(map[string]interface{})
522 | if name, ok := object["displayName"]; ok {
523 | result[name.(string)] = k
524 | }
525 | }
526 | return result
527 | }
528 |
529 | func (c *Client) exploreBots(endCursor *string, count int) map[string]interface{} {
530 | var url string
531 | var resp *fhttp.Response
532 | var err error
533 |
534 | if endCursor == nil {
535 | url = fmt.Sprintf("https://poe.com/_next/data/%s/explore_bots.json", c.nextData["buildId"].(string))
536 | resp, err = c.requestWithRetries(http.MethodGet, url, 0, nil, nil)
537 | } else {
538 | // Use GraphQL to get the next page
539 | queryData := map[string]interface{}{
540 | "count": count,
541 | "cursor": *endCursor,
542 | }
543 | result := c.sendQuery("ExploreBotsListPaginationQuery", queryData, 0)
544 | resultData := result["data"].(map[string]interface{})["exploreBotsConnection"].(map[string]interface{})
545 |
546 | bots := make([]map[string]interface{}, len(resultData["edges"].([]interface{})))
547 | for i, node := range resultData["edges"].([]interface{}) {
548 | bots[i] = node.(map[string]interface{})["node"].(map[string]interface{})
549 | }
550 |
551 | return map[string]interface{}{
552 | "bots": bots,
553 | "end_cursor": resultData["pageInfo"].(map[string]interface{})["endCursor"],
554 | }
555 | }
556 |
557 | // Handle error in HTTP response
558 | if err != nil {
559 | panic(err)
560 | }
561 |
562 | defer resp.Body.Close()
563 | body, err := io.ReadAll(resp.Body)
564 |
565 | var jsonData map[string]interface{}
566 | err = json.Unmarshal(body, &jsonData)
567 |
568 | nodes := jsonData["pageProps"].(map[string]interface{})["payload"].(map[string]interface{})["exploreBotsConnection"].(map[string]interface{})["edges"].([]interface{})
569 | bots := make([]map[string]interface{}, len(nodes))
570 | for i, node := range nodes {
571 | bots[i] = node.(map[string]interface{})["node"].(map[string]interface{})
572 | }
573 |
574 | return map[string]interface{}{
575 | "bots": bots[:count],
576 | "end_cursor": jsonData["pageProps"].(map[string]interface{})["payload"].(map[string]interface{})["exploreBotsConnection"].(map[string]interface{})["pageInfo"].(map[string]interface{})["endCursor"],
577 | }
578 | }
579 |
580 | func (c *Client) getRemainingMessages(chatbot string) int {
581 | chatData := c.getBotByCodename(chatbot)
582 | return int(chatData["defaultBotObject"].(map[string]interface{})["messageLimit"].(map[string]interface{})["numMessagesRemaining"].(float64))
583 | }
584 |
585 | func (c *Client) getChannelData() map[string]interface{} {
586 | log.Println("Downloading channel data...")
587 | resp, err := c.requestWithRetries(http.MethodGet, settingsURL, 0, nil, nil)
588 |
589 | if err != nil {
590 | panic(err)
591 | }
592 |
593 | defer resp.Body.Close()
594 | body, err := io.ReadAll(resp.Body)
595 |
596 | var jsonData map[string]interface{}
597 | err = json.Unmarshal(body, &jsonData)
598 |
599 | return jsonData["tchannelData"].(map[string]interface{})
600 | }
601 |
602 | func (c *Client) getWebsocketURL(channel map[string]interface{}) string {
603 | if channel == nil {
604 | channel = c.channel
605 | }
606 | minSeq := channel["minSeq"].(string)
607 | channelName := channel["channel"].(string)
608 | hash := channel["channelHash"].(string)
609 | baseHost := channel["baseHost"].(string)
610 | boxName := channel["boxName"].(string)
611 |
612 | return fmt.Sprintf("wss://%s.tch.%s/up/%s/updates?min_seq=%s&channel=%s&hash=%s", c.wsDomain, baseHost, boxName, minSeq, channelName, hash)
613 | }
614 |
615 | func (c *Client) sendQuery(queryName string, variables map[string]interface{}, attempts int) map[string]interface{} {
616 | if attempts == 0 {
617 | attempts = 10
618 | }
619 | for i := 0; i < attempts; i++ {
620 | jsonData := generatePayload(queryName, variables)
621 | payload, _ := json.Marshal(jsonData)
622 |
623 | baseString := string(payload) + c.gqlHeaders["Poe-Formkey"][0] + "WpuLMiXEKKE98j56k"
624 |
625 | headers := map[string][]string{
626 | "content-type": {"application/json"},
627 | "poe-tag-id": {fmt.Sprintf("%x", md5.Sum([]byte(baseString)))},
628 | }
629 |
630 | for k, v := range c.gqlHeaders {
631 | headers[k] = v
632 | }
633 | if queryName == "recv" {
634 | _, err := c.requestWithRetries(http.MethodPost, gqlRecvURL, attempts, payload, headers)
635 | if err != nil {
636 | panic(err)
637 | }
638 | return nil
639 | }
640 | resp, err := c.requestWithRetries(http.MethodPost, gqlURL, attempts, payload, headers)
641 |
642 | // Handle error in HTTP response
643 | if err != nil {
644 | panic(err)
645 | }
646 |
647 | defer resp.Body.Close()
648 | body, err := io.ReadAll(resp.Body)
649 |
650 | var data map[string]interface{}
651 | err = json.Unmarshal(body, &data)
652 |
653 | if data["data"] == nil {
654 | log.Printf("%s returned an error: %s | Retrying (%d/%d)\n", queryName, data["errors"].([]interface{})[0].(map[string]interface{})["message"].(string), i+1, attempts)
655 | time.Sleep(2 * time.Second)
656 | continue
657 | }
658 |
659 | return data
660 | }
661 |
662 | panic(fmt.Sprintf("%s failed too many times.", queryName))
663 | }
664 |
665 | func (c *Client) subscribe() map[string]interface{} {
666 | log.Println("Subscribing to mutations")
667 | result := c.sendQuery("SubscriptionsMutation", map[string]interface{}{
668 | "subscriptions": []map[string]interface{}{
669 | {
670 | "subscriptionName": "messageAdded",
671 | "query": queries["MessageAddedSubscription"],
672 | },
673 | {
674 | "subscriptionName": "viewerStateUpdated",
675 | "query": queries["ViewerStateUpdatedSubscription"],
676 | },
677 | },
678 | }, 0)
679 | return result
680 | }
681 |
682 | func (c *Client) wsRunThread() {
683 | dialer := websocket.DefaultDialer
684 |
685 | if c.proxy != nil {
686 | dialer.Proxy = http.ProxyURL(c.proxy)
687 | }
688 |
689 | conn, _, err := dialer.Dial(c.getWebsocketURL(nil), http.Header{"User-Agent": {userAgent}})
690 | if err != nil {
691 | log.Fatalf("Error connecting to websocket: %v", err)
692 | }
693 | c.wsConn = conn
694 | c.wsConnected = true
695 |
696 | for {
697 | _, message, err := c.wsConn.ReadMessage()
698 | if err != nil {
699 | c.onWsError(err)
700 | return
701 | }
702 | c.onMessage(message)
703 | }
704 | }
705 |
706 | func (c *Client) connectWs() {
707 | c.wsConnected = false
708 | go c.wsRunThread()
709 |
710 | for !c.wsConnected {
711 | time.Sleep(10 * time.Millisecond)
712 | }
713 | }
714 |
715 | func (c *Client) disconnectWs() {
716 | if c.wsConn != nil {
717 | c.wsConn.Close()
718 | }
719 | c.wsConnected = false
720 | }
721 |
722 | func (c *Client) onWsConnect() {
723 | c.wsConnected = true
724 | }
725 |
726 | func (c *Client) onWsClose(code int, text string) {
727 | c.wsConnected = false
728 | log.Printf("Websocket closed with status %d: %s", code, text)
729 | }
730 |
731 | func (c *Client) onWsError(err error) {
732 | c.disconnectWs()
733 | c.connectWs()
734 | }
735 |
736 | func (c *Client) onMessage(msg []byte) {
737 | var data map[string]interface{}
738 | err := json.Unmarshal(msg, &data)
739 | if err != nil {
740 | log.Printf("Error unmarshaling message: %v", err)
741 | c.disconnectWs()
742 | c.connectWs()
743 | return
744 | }
745 |
746 | messages, ok := data["messages"].([]interface{})
747 | if !ok {
748 | return
749 | }
750 |
751 | for _, messageStr := range messages {
752 | var messageData map[string]interface{}
753 | err := json.Unmarshal([]byte(messageStr.(string)), &messageData)
754 | if err != nil {
755 | log.Printf("Error unmarshaling message data: %v", err)
756 | c.disconnectWs()
757 | c.connectWs()
758 | return
759 | }
760 |
761 | if messageData["message_type"].(string) != "subscriptionUpdate" {
762 | continue
763 | }
764 |
765 | message := messageData["payload"].(map[string]interface{})["data"].(map[string]interface{})["messageAdded"].(map[string]interface{})
766 |
767 | copiedDict := make(map[string]float64)
768 | c.activeMessages.Range(func(key string, value float64) bool {
769 | copiedDict[key] = value
770 | return true
771 | })
772 |
773 | for key, value := range copiedDict {
774 | queue, ok := c.messageQueues.Load(key)
775 | if !ok {
776 | continue
777 | }
778 | if value == message["messageId"].(float64) {
779 | queue <- message
780 | return
781 | } else if key != "pending" && value == 0 && message["state"].(string) != "complete" {
782 | c.activeMessages.Store(key, message["messageId"].(float64))
783 | queue <- message
784 | return
785 | }
786 | }
787 | }
788 | }
789 |
790 | func (c *Client) dealMessage(humanMessageID string, textCh chan string, result chan map[string]interface{}, timeout time.Duration) {
791 | defer c.activeMessages.Delete(humanMessageID)
792 | defer c.messageQueues.Delete(humanMessageID)
793 | defer close(result)
794 | defer close(textCh)
795 | lastText := ""
796 | messageID := ""
797 | ch, ok := c.messageQueues.Load(humanMessageID)
798 | if !ok {
799 | return
800 | }
801 | for {
802 | select {
803 | case <-time.After(timeout):
804 | return
805 | case message := <-ch:
806 | if message["state"].(string) == "complete" {
807 | if lastText != "" && fmt.Sprintf("%v", message["messageId"].(float64)) == messageID {
808 | return
809 | } else {
810 | continue
811 | }
812 | }
813 |
814 | textNew := message["text"].(string)[len(lastText):]
815 | lastText = message["text"].(string)
816 | messageID = fmt.Sprintf("%v", message["messageId"].(float64))
817 | textCh <- lastText
818 | message["text_new"] = textNew
819 | result <- message
820 | }
821 | }
822 | }
823 |
824 | func (c *Client) sendRecv(humanMessageID, chatbot, chatID string, textCh chan string) {
825 | for text := range textCh {
826 | m := map[string]interface{}{
827 | "bot": chatbot,
828 | "time_to_first_typing_indicator": 300,
829 | "time_to_first_subscription_response": 600,
830 | "time_to_full_bot_response": 1100,
831 | "full_response_length": len(text) + 1,
832 | "full_response_word_count": len(strings.Split(text, " ")) + 1,
833 | "human_message_id": humanMessageID,
834 | "chat_id": chatID,
835 | "bot_response_status": "success",
836 | }
837 | id, ok := c.activeMessages.Load(humanMessageID)
838 | if !ok || id == 0 {
839 | m["bot_message_id"] = nil
840 | } else {
841 | m["bot_message_id"] = id
842 | }
843 | c.sendQuery("recv", m, 0)
844 | }
845 | }
846 |
--------------------------------------------------------------------------------
/poe_graphql/AddHumanMessageMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation AddHumanMessageMutation(
2 | $chatId: BigInt!
3 | $bot: String!
4 | $query: String!
5 | $source: MessageSource
6 | $withChatBreak: Boolean! = false
7 | ) {
8 | messageCreateWithStatus(
9 | chatId: $chatId
10 | bot: $bot
11 | query: $query
12 | source: $source
13 | withChatBreak: $withChatBreak
14 | ) {
15 | message {
16 | id
17 | __typename
18 | messageId
19 | text
20 | linkifiedText
21 | authorNickname
22 | state
23 | vote
24 | voteReason
25 | creationTime
26 | suggestedReplies
27 | chat {
28 | id
29 | shouldShowDisclaimer
30 | }
31 | }
32 | messageLimit{
33 | canSend
34 | numMessagesRemaining
35 | resetTime
36 | shouldShowReminder
37 | }
38 | chatBreak {
39 | id
40 | __typename
41 | messageId
42 | text
43 | linkifiedText
44 | authorNickname
45 | state
46 | vote
47 | voteReason
48 | creationTime
49 | suggestedReplies
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/poe_graphql/AddMessageBreakMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation AddMessageBreakMutation($chatId: BigInt!) {
2 | messageBreakCreate(chatId: $chatId) {
3 | message {
4 | id
5 | __typename
6 | messageId
7 | text
8 | linkifiedText
9 | authorNickname
10 | state
11 | vote
12 | voteReason
13 | creationTime
14 | suggestedReplies
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/poe_graphql/AutoSubscriptionMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation AutoSubscriptionMutation($subscriptions: [AutoSubscriptionQuery!]!) {
2 | autoSubscribe(subscriptions: $subscriptions) {
3 | viewer {
4 | id
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/poe_graphql/BioFragment.graphql:
--------------------------------------------------------------------------------
1 | fragment BioFragment on Viewer {
2 | id
3 | poeUser {
4 | id
5 | uid
6 | bio
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/poe_graphql/ChatAddedSubscription.graphql:
--------------------------------------------------------------------------------
1 | subscription ChatAddedSubscription {
2 | chatAdded {
3 | ...ChatFragment
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/poe_graphql/ChatFragment.graphql:
--------------------------------------------------------------------------------
1 | fragment ChatFragment on Chat {
2 | id
3 | chatId
4 | defaultBotNickname
5 | shouldShowDisclaimer
6 | }
7 |
--------------------------------------------------------------------------------
/poe_graphql/ChatListPaginationQuery.graphql:
--------------------------------------------------------------------------------
1 | query ChatListPaginationQuery(
2 | $count: Int = 5
3 | $cursor: String
4 | $id: ID!
5 | ) {
6 | node(id: $id) {
7 | __typename
8 | ...ChatPageMain_chat_1G22uz
9 | id
10 | }
11 | }
12 |
13 | fragment BotImage_bot on Bot {
14 | displayName
15 | ...botHelpers_useDeletion_bot
16 | ...BotImage_useProfileImage_bot
17 | }
18 |
19 | fragment BotImage_useProfileImage_bot on Bot {
20 | image {
21 | __typename
22 | ... on LocalBotImage {
23 | localName
24 | }
25 | ... on UrlBotImage {
26 | url
27 | }
28 | }
29 | ...botHelpers_useDeletion_bot
30 | }
31 |
32 | fragment ChatMessageDownvotedButton_message on Message {
33 | ...MessageFeedbackReasonModal_message
34 | ...MessageFeedbackOtherModal_message
35 | }
36 |
37 | fragment ChatMessageDropdownMenu_message on Message {
38 | id
39 | messageId
40 | vote
41 | text
42 | author
43 | ...chatHelpers_isBotMessage
44 | }
45 |
46 | fragment ChatMessageFeedbackButtons_message on Message {
47 | id
48 | messageId
49 | vote
50 | voteReason
51 | ...ChatMessageDownvotedButton_message
52 | }
53 |
54 | fragment ChatMessageInputView_chat on Chat {
55 | id
56 | chatId
57 | defaultBotObject {
58 | nickname
59 | messageLimit {
60 | dailyBalance
61 | shouldShowRemainingMessageCount
62 | }
63 | hasClearContext
64 | isDown
65 | ...botHelpers_useDeletion_bot
66 | id
67 | }
68 | shouldShowDisclaimer
69 | ...chatHelpers_useSendMessage_chat
70 | ...chatHelpers_useSendChatBreak_chat
71 | }
72 |
73 | fragment ChatMessageInputView_edges on MessageEdge {
74 | node {
75 | ...chatHelpers_isChatBreak
76 | ...chatHelpers_isHumanMessage
77 | state
78 | text
79 | id
80 | }
81 | }
82 |
83 | fragment ChatMessageOverflowButton_message on Message {
84 | text
85 | ...ChatMessageDropdownMenu_message
86 | ...chatHelpers_isBotMessage
87 | }
88 |
89 | fragment ChatMessageSuggestedReplies_SuggestedReplyButton_chat on Chat {
90 | ...chatHelpers_useSendMessage_chat
91 | }
92 |
93 | fragment ChatMessageSuggestedReplies_SuggestedReplyButton_message on Message {
94 | messageId
95 | }
96 |
97 | fragment ChatMessageSuggestedReplies_chat on Chat {
98 | ...ChatWelcomeView_chat
99 | ...ChatMessageSuggestedReplies_SuggestedReplyButton_chat
100 | defaultBotObject {
101 | hasWelcomeTopics
102 | id
103 | }
104 | }
105 |
106 | fragment ChatMessageSuggestedReplies_message on Message {
107 | suggestedReplies
108 | ...ChatMessageSuggestedReplies_SuggestedReplyButton_message
109 | }
110 |
111 | fragment ChatMessage_chat on Chat {
112 | defaultBotObject {
113 | hasWelcomeTopics
114 | hasSuggestedReplies
115 | disclaimerText
116 | messageLimit {
117 | ...ChatPageRateLimitedBanner_messageLimit
118 | }
119 | ...ChatPageDisclaimer_bot
120 | id
121 | }
122 | ...ChatMessageSuggestedReplies_chat
123 | ...ChatWelcomeView_chat
124 | }
125 |
126 | fragment ChatMessage_message on Message {
127 | id
128 | messageId
129 | text
130 | author
131 | linkifiedText
132 | state
133 | contentType
134 | ...ChatMessageSuggestedReplies_message
135 | ...ChatMessageFeedbackButtons_message
136 | ...ChatMessageOverflowButton_message
137 | ...chatHelpers_isHumanMessage
138 | ...chatHelpers_isBotMessage
139 | ...chatHelpers_isChatBreak
140 | ...chatHelpers_useTimeoutLevel
141 | ...MarkdownLinkInner_message
142 | ...IdAnnotation_node
143 | }
144 |
145 | fragment ChatMessagesView_chat on Chat {
146 | ...ChatMessage_chat
147 | ...ChatWelcomeView_chat
148 | ...IdAnnotation_node
149 | defaultBotObject {
150 | hasWelcomeTopics
151 | messageLimit {
152 | ...ChatPageRateLimitedBanner_messageLimit
153 | }
154 | id
155 | }
156 | }
157 |
158 | fragment ChatMessagesView_edges on MessageEdge {
159 | node {
160 | id
161 | messageId
162 | creationTime
163 | ...ChatMessage_message
164 | ...chatHelpers_isBotMessage
165 | ...chatHelpers_isHumanMessage
166 | ...chatHelpers_isChatBreak
167 | }
168 | }
169 |
170 | fragment ChatPageDeleteFooter_chat on Chat {
171 | ...MessageDeleteConfirmationModal_chat
172 | }
173 |
174 | fragment ChatPageDisclaimer_bot on Bot {
175 | disclaimerText
176 | }
177 |
178 | fragment ChatPageMainFooter_chat on Chat {
179 | defaultBotObject {
180 | ...ChatPageMainFooter_useAccessMessage_bot
181 | id
182 | }
183 | ...ChatMessageInputView_chat
184 | ...ChatPageShareFooter_chat
185 | ...ChatPageDeleteFooter_chat
186 | }
187 |
188 | fragment ChatPageMainFooter_edges on MessageEdge {
189 | ...ChatMessageInputView_edges
190 | }
191 |
192 | fragment ChatPageMainFooter_useAccessMessage_bot on Bot {
193 | ...botHelpers_useDeletion_bot
194 | ...botHelpers_useViewerCanAccessPrivateBot
195 | }
196 |
197 | fragment ChatPageMain_chat_1G22uz on Chat {
198 | id
199 | chatId
200 | ...ChatPageShareFooter_chat
201 | ...ChatPageDeleteFooter_chat
202 | ...ChatMessagesView_chat
203 | ...MarkdownLinkInner_chat
204 | ...chatHelpers_useUpdateStaleChat_chat
205 | ...ChatSubscriptionPaywallContextWrapper_chat
206 | ...ChatPageMainFooter_chat
207 | messagesConnection(last: $count, before: $cursor) {
208 | edges {
209 | ...ChatMessagesView_edges
210 | ...ChatPageMainFooter_edges
211 | ...MarkdownLinkInner_edges
212 | node {
213 | ...chatHelpers_useUpdateStaleChat_message
214 | id
215 | __typename
216 | }
217 | cursor
218 | id
219 | }
220 | pageInfo {
221 | hasPreviousPage
222 | startCursor
223 | }
224 | id
225 | }
226 | }
227 |
228 | fragment ChatPageRateLimitedBanner_messageLimit on MessageLimit {
229 | numMessagesRemaining
230 | }
231 |
232 | fragment ChatPageShareFooter_chat on Chat {
233 | chatId
234 | }
235 |
236 | fragment ChatSubscriptionPaywallContextWrapper_chat on Chat {
237 | defaultBotObject {
238 | messageLimit {
239 | numMessagesRemaining
240 | shouldShowRemainingMessageCount
241 | }
242 | ...SubscriptionPaywallModal_bot
243 | id
244 | }
245 | }
246 |
247 | fragment ChatWelcomeView_ChatWelcomeButton_chat on Chat {
248 | ...chatHelpers_useSendMessage_chat
249 | }
250 |
251 | fragment ChatWelcomeView_chat on Chat {
252 | ...ChatWelcomeView_ChatWelcomeButton_chat
253 | defaultBotObject {
254 | displayName
255 | id
256 | }
257 | }
258 |
259 | fragment IdAnnotation_node on Node {
260 | __isNode: __typename
261 | id
262 | }
263 |
264 | fragment MarkdownLinkInner_chat on Chat {
265 | id
266 | chatId
267 | defaultBotObject {
268 | nickname
269 | id
270 | }
271 | ...chatHelpers_useSendMessage_chat
272 | }
273 |
274 | fragment MarkdownLinkInner_edges on MessageEdge {
275 | node {
276 | state
277 | id
278 | }
279 | }
280 |
281 | fragment MarkdownLinkInner_message on Message {
282 | messageId
283 | }
284 |
285 | fragment MessageDeleteConfirmationModal_chat on Chat {
286 | id
287 | }
288 |
289 | fragment MessageFeedbackOtherModal_message on Message {
290 | id
291 | messageId
292 | }
293 |
294 | fragment MessageFeedbackReasonModal_message on Message {
295 | id
296 | messageId
297 | }
298 |
299 | fragment SubscriptionPaywallModal_bot on Bot {
300 | displayName
301 | messageLimit {
302 | dailyLimit
303 | numMessagesRemaining
304 | shouldShowRemainingMessageCount
305 | resetTime
306 | }
307 | ...BotImage_bot
308 | }
309 |
310 | fragment botHelpers_useDeletion_bot on Bot {
311 | deletionState
312 | }
313 |
314 | fragment botHelpers_useViewerCanAccessPrivateBot on Bot {
315 | isPrivateBot
316 | viewerIsCreator
317 | }
318 |
319 | fragment chatHelpers_isBotMessage on Message {
320 | ...chatHelpers_isHumanMessage
321 | ...chatHelpers_isChatBreak
322 | }
323 |
324 | fragment chatHelpers_isChatBreak on Message {
325 | author
326 | }
327 |
328 | fragment chatHelpers_isHumanMessage on Message {
329 | author
330 | }
331 |
332 | fragment chatHelpers_useSendChatBreak_chat on Chat {
333 | id
334 | chatId
335 | defaultBotObject {
336 | nickname
337 | introduction
338 | model
339 | id
340 | }
341 | shouldShowDisclaimer
342 | }
343 |
344 | fragment chatHelpers_useSendMessage_chat on Chat {
345 | id
346 | chatId
347 | defaultBotObject {
348 | id
349 | nickname
350 | }
351 | shouldShowDisclaimer
352 | }
353 |
354 | fragment chatHelpers_useTimeoutLevel on Message {
355 | id
356 | state
357 | text
358 | messageId
359 | chat {
360 | chatId
361 | defaultBotNickname
362 | id
363 | }
364 | }
365 |
366 | fragment chatHelpers_useUpdateStaleChat_chat on Chat {
367 | chatId
368 | defaultBotObject {
369 | contextClearWindowSecs
370 | id
371 | }
372 | ...chatHelpers_useSendChatBreak_chat
373 | }
374 |
375 | fragment chatHelpers_useUpdateStaleChat_message on Message {
376 | creationTime
377 | ...chatHelpers_isChatBreak
378 | }
379 |
--------------------------------------------------------------------------------
/poe_graphql/ChatPaginationQuery.graphql:
--------------------------------------------------------------------------------
1 | query ChatPaginationQuery($bot: String!, $before: String, $last: Int! = 10) {
2 | chatOfBot(bot: $bot) {
3 | id
4 | __typename
5 | messagesConnection(before: $before, last: $last) {
6 | pageInfo {
7 | hasPreviousPage
8 | }
9 | edges {
10 | node {
11 | id
12 | __typename
13 | messageId
14 | text
15 | linkifiedText
16 | authorNickname
17 | state
18 | vote
19 | voteReason
20 | creationTime
21 | suggestedReplies
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/poe_graphql/ChatViewQuery.graphql:
--------------------------------------------------------------------------------
1 | query ChatViewQuery($bot: String!) {
2 | chatOfBot(bot: $bot) {
3 | id
4 | chatId
5 | defaultBotNickname
6 | shouldShowDisclaimer
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/poe_graphql/DeleteHumanMessagesMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation DeleteHumanMessagesMutation($messageIds: [BigInt!]!) {
2 | messagesDelete(messageIds: $messageIds) {
3 | viewer {
4 | id
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/poe_graphql/DeleteMessageMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation deleteMessageMutation(
2 | $messageIds: [BigInt!]!
3 | ) {
4 | messagesDelete(messageIds: $messageIds) {
5 | edgeIds
6 | }
7 | }
--------------------------------------------------------------------------------
/poe_graphql/ExploreBotsListPaginationQuery.graphql:
--------------------------------------------------------------------------------
1 | query ExploreBotsListPaginationQuery($count: Int = 20, $cursor: String) {
2 | ...ExploreBotsMain_queryRoot_1G22uz
3 | }
4 |
5 | fragment BotImage_bot on Bot {
6 | displayName
7 | ...botHelpers_useDeletion_bot
8 | ...BotImage_useProfileImage_bot
9 | }
10 |
11 | fragment BotImage_useProfileImage_bot on Bot {
12 | image {
13 | __typename
14 | ... on LocalBotImage {
15 | localName
16 | }
17 | ... on UrlBotImage {
18 | url
19 | }
20 | }
21 | ...botHelpers_useDeletion_bot
22 | }
23 |
24 | fragment BotLink_bot on Bot {
25 | displayName
26 | }
27 |
28 | fragment ExploreBotListItem_bot on Bot {
29 | ...BotLink_bot
30 | ...BotImage_bot
31 | botId
32 | displayName
33 | followerCount
34 | description
35 | }
36 |
37 | fragment ExploreBotsMain_queryRoot_1G22uz on QueryRoot {
38 | exploreBotsConnection(first: $count, after: $cursor) {
39 | edges {
40 | ...ExploreBotsPagedList_edges
41 | cursor
42 | node {
43 | __typename
44 | id
45 | }
46 | id
47 | }
48 | pageInfo {
49 | endCursor
50 | hasNextPage
51 | }
52 | id
53 | }
54 | }
55 |
56 | fragment ExploreBotsPagedList_edges on BotEdge {
57 | node {
58 | id
59 | ...ExploreBotListItem_bot
60 | }
61 | }
62 |
63 | fragment botHelpers_useDeletion_bot on Bot {
64 | deletionState
65 | }
66 |
--------------------------------------------------------------------------------
/poe_graphql/HandleFragment.graphql:
--------------------------------------------------------------------------------
1 | fragment HandleFragment on Viewer {
2 | id
3 | poeUser {
4 | id
5 | uid
6 | handle
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/poe_graphql/LoginWithVerificationCodeMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation LoginWithVerificationCodeMutation(
2 | $verificationCode: String!
3 | $emailAddress: String
4 | $phoneNumber: String
5 | ) {
6 | loginWithVerificationCode(
7 | verificationCode: $verificationCode
8 | emailAddress: $emailAddress
9 | phoneNumber: $phoneNumber
10 | ) {
11 | status
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/poe_graphql/MessageAddedSubscription.graphql:
--------------------------------------------------------------------------------
1 | subscription messageAdded (
2 | $chatId: BigInt!
3 | ) {
4 | messageAdded(chatId: $chatId) {
5 | id
6 | messageId
7 | creationTime
8 | state
9 | ...ChatMessage_message
10 | ...chatHelpers_isBotMessage
11 | }
12 | }
13 |
14 | fragment ChatMessageDownvotedButton_message on Message {
15 | ...MessageFeedbackReasonModal_message
16 | ...MessageFeedbackOtherModal_message
17 | }
18 |
19 | fragment ChatMessageDropdownMenu_message on Message {
20 | id
21 | messageId
22 | vote
23 | text
24 | linkifiedText
25 | ...chatHelpers_isBotMessage
26 | }
27 |
28 | fragment ChatMessageFeedbackButtons_message on Message {
29 | id
30 | messageId
31 | vote
32 | voteReason
33 | ...ChatMessageDownvotedButton_message
34 | }
35 |
36 | fragment ChatMessageOverflowButton_message on Message {
37 | text
38 | ...ChatMessageDropdownMenu_message
39 | ...chatHelpers_isBotMessage
40 | }
41 |
42 | fragment ChatMessageSuggestedReplies_SuggestedReplyButton_message on Message {
43 | messageId
44 | }
45 |
46 | fragment ChatMessageSuggestedReplies_message on Message {
47 | suggestedReplies
48 | ...ChatMessageSuggestedReplies_SuggestedReplyButton_message
49 | }
50 |
51 | fragment ChatMessage_message on Message {
52 | id
53 | messageId
54 | text
55 | author
56 | linkifiedText
57 | state
58 | ...ChatMessageSuggestedReplies_message
59 | ...ChatMessageFeedbackButtons_message
60 | ...ChatMessageOverflowButton_message
61 | ...chatHelpers_isHumanMessage
62 | ...chatHelpers_isBotMessage
63 | ...chatHelpers_isChatBreak
64 | ...chatHelpers_useTimeoutLevel
65 | ...MarkdownLinkInner_message
66 | }
67 |
68 | fragment MarkdownLinkInner_message on Message {
69 | messageId
70 | }
71 |
72 | fragment MessageFeedbackOtherModal_message on Message {
73 | id
74 | messageId
75 | }
76 |
77 | fragment MessageFeedbackReasonModal_message on Message {
78 | id
79 | messageId
80 | }
81 |
82 | fragment chatHelpers_isBotMessage on Message {
83 | ...chatHelpers_isHumanMessage
84 | ...chatHelpers_isChatBreak
85 | }
86 |
87 | fragment chatHelpers_isChatBreak on Message {
88 | author
89 | }
90 |
91 | fragment chatHelpers_isHumanMessage on Message {
92 | author
93 | }
94 |
95 | fragment chatHelpers_useTimeoutLevel on Message {
96 | id
97 | state
98 | text
99 | messageId
100 | }
101 |
--------------------------------------------------------------------------------
/poe_graphql/MessageDeletedSubscription.graphql:
--------------------------------------------------------------------------------
1 | subscription MessageDeletedSubscription($chatId: BigInt!) {
2 | messageDeleted(chatId: $chatId) {
3 | id
4 | messageId
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/poe_graphql/MessageFragment.graphql:
--------------------------------------------------------------------------------
1 | fragment MessageFragment on Message {
2 | id
3 | __typename
4 | messageId
5 | text
6 | linkifiedText
7 | authorNickname
8 | state
9 | vote
10 | voteReason
11 | creationTime
12 | suggestedReplies
13 | }
14 |
--------------------------------------------------------------------------------
/poe_graphql/MessageRemoveVoteMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation MessageRemoveVoteMutation($messageId: BigInt!) {
2 | messageRemoveVote(messageId: $messageId) {
3 | message {
4 | ...MessageFragment
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/poe_graphql/MessageSetVoteMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation MessageSetVoteMutation($messageId: BigInt!, $voteType: VoteType!, $reason: String) {
2 | messageSetVote(messageId: $messageId, voteType: $voteType, reason: $reason) {
3 | message {
4 | ...MessageFragment
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/poe_graphql/PoeBotCreateMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation CreateBotMain_poeBotCreate_Mutation(
2 | $model: String!
3 | $displayName: String
4 | $handle: String!
5 | $prompt: String!
6 | $isPromptPublic: Boolean!
7 | $introduction: String!
8 | $description: String!
9 | $profilePictureUrl: String
10 | $apiUrl: String
11 | $apiKey: String
12 | $isApiBot: Boolean
13 | $hasLinkification: Boolean
14 | $hasMarkdownRendering: Boolean
15 | $hasSuggestedReplies: Boolean
16 | $isPrivateBot: Boolean
17 | $temperature: Float
18 | ) {
19 | poeBotCreate(model: $model, handle: $handle, displayName: $displayName, promptPlaintext: $prompt, isPromptPublic: $isPromptPublic, introduction: $introduction, description: $description, profilePicture: $profilePictureUrl, apiUrl: $apiUrl, apiKey: $apiKey, isApiBot: $isApiBot, hasLinkification: $hasLinkification, hasMarkdownRendering: $hasMarkdownRendering, hasSuggestedReplies: $hasSuggestedReplies, isPrivateBot: $isPrivateBot, temperature: $temperature) {
20 | status
21 | bot {
22 | id
23 | ...BotHeader_bot
24 | }
25 | }
26 | }
27 |
28 | fragment BotHeader_bot on Bot {
29 | displayName
30 | isLimitedAccess
31 | ...BotImage_bot
32 | ...BotLink_bot
33 | ...IdAnnotation_node
34 | ...botHelpers_useViewerCanAccessPrivateBot
35 | ...botHelpers_useDeletion_bot
36 | }
37 |
38 | fragment BotImage_bot on Bot {
39 | displayName
40 | ...botHelpers_useDeletion_bot
41 | ...BotImage_useProfileImage_bot
42 | }
43 |
44 | fragment BotImage_useProfileImage_bot on Bot {
45 | image {
46 | __typename
47 | ... on LocalBotImage {
48 | localName
49 | }
50 | ... on UrlBotImage {
51 | url
52 | }
53 | }
54 | ...botHelpers_useDeletion_bot
55 | }
56 |
57 | fragment BotLink_bot on Bot {
58 | handle
59 | }
60 |
61 | fragment IdAnnotation_node on Node {
62 | __isNode: __typename
63 | id
64 | }
65 |
66 | fragment botHelpers_useDeletion_bot on Bot {
67 | deletionState
68 | }
69 |
70 | fragment botHelpers_useViewerCanAccessPrivateBot on Bot {
71 | isPrivateBot
72 | viewerIsCreator
73 | isSystemBot
74 | }
--------------------------------------------------------------------------------
/poe_graphql/PoeBotEditMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation EditBotMain_poeBotEdit_Mutation(
2 | $botId: BigInt!
3 | $handle: String!
4 | $displayName: String
5 | $description: String!
6 | $introduction: String!
7 | $isPromptPublic: Boolean!
8 | $baseBot: String!
9 | $profilePictureUrl: String
10 | $prompt: String!
11 | $apiUrl: String
12 | $apiKey: String
13 | $hasLinkification: Boolean
14 | $hasMarkdownRendering: Boolean
15 | $hasSuggestedReplies: Boolean
16 | $isPrivateBot: Boolean
17 | $temperature: Float
18 | ) {
19 | poeBotEdit(botId: $botId, handle: $handle, displayName: $displayName, description: $description, introduction: $introduction, isPromptPublic: $isPromptPublic, model: $baseBot, promptPlaintext: $prompt, profilePicture: $profilePictureUrl, apiUrl: $apiUrl, apiKey: $apiKey, hasLinkification: $hasLinkification, hasMarkdownRendering: $hasMarkdownRendering, hasSuggestedReplies: $hasSuggestedReplies, isPrivateBot: $isPrivateBot, temperature: $temperature) {
20 | status
21 | bot {
22 | handle
23 | id
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/poe_graphql/SendMessageMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation chatHelpers_sendMessageMutation_Mutation(
2 | $chatId: BigInt!
3 | $bot: String!
4 | $query: String!
5 | $source: MessageSource
6 | $withChatBreak: Boolean!
7 | ) {
8 | messageEdgeCreate(chatId: $chatId, bot: $bot, query: $query, source: $source, withChatBreak: $withChatBreak) {
9 | chatBreak {
10 | cursor
11 | node {
12 | id
13 | messageId
14 | text
15 | author
16 | suggestedReplies
17 | creationTime
18 | state
19 | }
20 | id
21 | }
22 | message {
23 | cursor
24 | node {
25 | id
26 | messageId
27 | text
28 | author
29 | suggestedReplies
30 | creationTime
31 | state
32 | chat {
33 | shouldShowDisclaimer
34 | id
35 | }
36 | }
37 | id
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/poe_graphql/SendVerificationCodeForLoginMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation SendVerificationCodeForLoginMutation(
2 | $emailAddress: String
3 | $phoneNumber: String
4 | ) {
5 | sendVerificationCode(
6 | verificationReason: login
7 | emailAddress: $emailAddress
8 | phoneNumber: $phoneNumber
9 | ) {
10 | status
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/poe_graphql/ShareMessagesMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation ShareMessagesMutation(
2 | $chatId: BigInt!
3 | $messageIds: [BigInt!]!
4 | $comment: String
5 | ) {
6 | messagesShare(chatId: $chatId, messageIds: $messageIds, comment: $comment) {
7 | shareCode
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/poe_graphql/SignupWithVerificationCodeMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation SignupWithVerificationCodeMutation(
2 | $verificationCode: String!
3 | $emailAddress: String
4 | $phoneNumber: String
5 | ) {
6 | signupWithVerificationCode(
7 | verificationCode: $verificationCode
8 | emailAddress: $emailAddress
9 | phoneNumber: $phoneNumber
10 | ) {
11 | status
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/poe_graphql/StaleChatUpdateMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation StaleChatUpdateMutation($chatId: BigInt!) {
2 | staleChatUpdate(chatId: $chatId) {
3 | message {
4 | ...MessageFragment
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/poe_graphql/SubscriptionsMutation.graphql:
--------------------------------------------------------------------------------
1 | mutation subscriptionsMutation(
2 | $subscriptions: [AutoSubscriptionQuery!]!
3 | ) {
4 | autoSubscribe(subscriptions: $subscriptions) {
5 | viewer {
6 | id
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/poe_graphql/SummarizePlainPostQuery.graphql:
--------------------------------------------------------------------------------
1 | query SummarizePlainPostQuery($comment: String!) {
2 | summarizePlainPost(comment: $comment)
3 | }
4 |
--------------------------------------------------------------------------------
/poe_graphql/SummarizeQuotePostQuery.graphql:
--------------------------------------------------------------------------------
1 | query SummarizeQuotePostQuery($comment: String, $quotedPostId: BigInt!) {
2 | summarizeQuotePost(comment: $comment, quotedPostId: $quotedPostId)
3 | }
4 |
--------------------------------------------------------------------------------
/poe_graphql/SummarizeSharePostQuery.graphql:
--------------------------------------------------------------------------------
1 | query SummarizeSharePostQuery($comment: String!, $chatId: BigInt!, $messageIds: [BigInt!]!) {
2 | summarizeSharePost(comment: $comment, chatId: $chatId, messageIds: $messageIds)
3 | }
4 |
--------------------------------------------------------------------------------
/poe_graphql/UserSnippetFragment.graphql:
--------------------------------------------------------------------------------
1 | fragment UserSnippetFragment on PoeUser {
2 | id
3 | uid
4 | bio
5 | handle
6 | fullName
7 | viewerIsFollowing
8 | isPoeOnlyUser
9 | profilePhotoURLTiny: profilePhotoUrl(size: tiny)
10 | profilePhotoURLSmall: profilePhotoUrl(size: small)
11 | profilePhotoURLMedium: profilePhotoUrl(size: medium)
12 | profilePhotoURLLarge: profilePhotoUrl(size: large)
13 | isFollowable
14 | }
15 |
--------------------------------------------------------------------------------
/poe_graphql/ViewerInfoQuery.graphql:
--------------------------------------------------------------------------------
1 | query ViewerInfoQuery {
2 | viewer {
3 | id
4 | uid
5 | ...ViewerStateFragment
6 | ...BioFragment
7 | ...HandleFragment
8 | hasCompletedMultiplayerNux
9 | poeUser {
10 | id
11 | ...UserSnippetFragment
12 | }
13 | messageLimit{
14 | canSend
15 | numMessagesRemaining
16 | resetTime
17 | shouldShowReminder
18 | }
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/poe_graphql/ViewerStateFragment.graphql:
--------------------------------------------------------------------------------
1 | fragment ViewerStateFragment on Viewer {
2 | id
3 | __typename
4 | iosMinSupportedVersion: integerGate(gateName: "poe_ios_min_supported_version")
5 | iosMinEncouragedVersion: integerGate(
6 | gateName: "poe_ios_min_encouraged_version"
7 | )
8 | macosMinSupportedVersion: integerGate(
9 | gateName: "poe_macos_min_supported_version"
10 | )
11 | macosMinEncouragedVersion: integerGate(
12 | gateName: "poe_macos_min_encouraged_version"
13 | )
14 | showPoeDebugPanel: booleanGate(gateName: "poe_show_debug_panel")
15 | enableCommunityFeed: booleanGate(gateName: "enable_poe_shares_feed")
16 | linkifyText: booleanGate(gateName: "poe_linkify_response")
17 | enableSuggestedReplies: booleanGate(gateName: "poe_suggested_replies")
18 | removeInviteLimit: booleanGate(gateName: "poe_remove_invite_limit")
19 | enableInAppPurchases: booleanGate(gateName: "poe_enable_in_app_purchases")
20 | availableBots {
21 | nickname
22 | displayName
23 | profilePicture
24 | isDown
25 | disclaimer
26 | subtitle
27 | poweredBy
28 | }
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/poe_graphql/ViewerStateUpdatedSubscription.graphql:
--------------------------------------------------------------------------------
1 | subscription viewerStateUpdated {
2 | viewerStateUpdated {
3 | id
4 | ...ChatPageBotSwitcher_viewer
5 | }
6 | }
7 |
8 | fragment BotHeader_bot on Bot {
9 | displayName
10 | messageLimit {
11 | dailyLimit
12 | }
13 | ...BotImage_bot
14 | }
15 |
16 | fragment BotImage_bot on Bot {
17 | image {
18 | __typename
19 | ... on LocalBotImage {
20 | localName
21 | }
22 | ... on UrlBotImage {
23 | url
24 | }
25 | }
26 | displayName
27 | }
28 |
29 | fragment BotLink_bot on Bot {
30 | displayName
31 | }
32 |
33 | fragment ChatPageBotSwitcher_viewer on Viewer {
34 | availableBots {
35 | id
36 | messageLimit {
37 | dailyLimit
38 | }
39 | ...BotLink_bot
40 | ...BotHeader_bot
41 | }
42 | allowUserCreatedBots: booleanGate(gateName: "enable_user_created_bots")
43 | }
44 |
--------------------------------------------------------------------------------
/poe_test.go:
--------------------------------------------------------------------------------
1 | package poe_api
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func TestSendMessage(t *testing.T) {
10 | c := NewClient("", nil)
11 | res, err := c.SendMessage("ChatGPT", "", true, 30*time.Second)
12 | if err != nil {
13 | panic(err)
14 | }
15 | for r := range res {
16 | fmt.Println(r)
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/utils.go:
--------------------------------------------------------------------------------
1 | package poe_api
2 |
3 | import (
4 | "encoding/json"
5 | "math/rand"
6 | "os"
7 | "path/filepath"
8 |
9 | "github.com/google/uuid"
10 | )
11 |
12 | func GetFinalResponse(ch <-chan map[string]interface{}) string {
13 | var m map[string]interface{}
14 | for message := range ch {
15 | m = message
16 | if message["state"] != "complete" {
17 | continue
18 | }
19 | return message["text"].(string)
20 | }
21 | return m["text"].(string)
22 | }
23 |
24 | func GetTextStream(ch <-chan map[string]interface{}) <-chan string {
25 | stream := make(chan string, 1)
26 | go func() {
27 | for message := range ch {
28 | stream <- message["text_new"].(string)
29 | }
30 | close(stream)
31 | }()
32 | return stream
33 | }
34 |
35 | func generatePayload(queryName string, variables map[string]interface{}) interface{} {
36 | if queryName == "recv" {
37 | if rand.Float64() > 0.9 {
38 | return []map[string]interface{}{
39 | {
40 | "category": "poe/bot_response_speed",
41 | "data": variables,
42 | },
43 | {
44 | "category": "poe/statsd_event",
45 | "data": map[string]interface{}{
46 | "key": "poe.speed.web_vitals.INP",
47 | "value": rand.Intn(26) + 100,
48 | "category": "time",
49 | "path": "/[handle]",
50 | "extra_data": map[string]interface{}{},
51 | },
52 | },
53 | }
54 | } else {
55 | return []map[string]interface{}{
56 | {
57 | "category": "poe/bot_response_speed",
58 | "data": variables,
59 | },
60 | }
61 | }
62 | }
63 | return map[string]interface{}{
64 | "query": queries[queryName],
65 | "queryName": queryName,
66 | "variables": variables,
67 | }
68 | }
69 |
70 | func generateNonce(length int) string {
71 | if length == 0 {
72 | length = 16
73 | }
74 | const lettersAndDigits = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
75 | var nonce = make([]rune, length)
76 | for i := range nonce {
77 | nonce[i] = rune(lettersAndDigits[rand.Intn(len(lettersAndDigits))])
78 | }
79 | return string(nonce)
80 | }
81 |
82 | func getConfigPath() string {
83 | var configPath string
84 | if os.PathSeparator == '\\' {
85 | configPath = filepath.Join(os.Getenv("APPDATA"), "poe-api")
86 | } else {
87 | configPath = filepath.Join(os.Getenv("HOME"), ".config", "poe-api")
88 | }
89 | return configPath
90 | }
91 |
92 | func setSavedDeviceID(userID, deviceID string) {
93 | deviceIDPath := filepath.Join(getConfigPath(), "device_id.json")
94 | deviceIDs := make(map[string]string)
95 |
96 | if _, err := os.Stat(deviceIDPath); !os.IsNotExist(err) {
97 | deviceIDBytes, err := os.ReadFile(deviceIDPath)
98 | if err != nil {
99 | panic(err)
100 | }
101 | err = json.Unmarshal(deviceIDBytes, &deviceIDs)
102 | if err != nil {
103 | panic(err)
104 | }
105 | }
106 |
107 | deviceIDs[userID] = deviceID
108 | err := os.MkdirAll(filepath.Dir(deviceIDPath), os.ModePerm)
109 | if err != nil {
110 | panic(err)
111 | }
112 | deviceIDBytes, err := json.MarshalIndent(deviceIDs, "", " ")
113 | if err != nil {
114 | panic(err)
115 | }
116 | err = os.WriteFile(deviceIDPath, deviceIDBytes, 0644)
117 | if err != nil {
118 | panic(err)
119 | }
120 | }
121 |
122 | func getSavedDeviceID(userID string) string {
123 | deviceIDPath := filepath.Join(getConfigPath(), "device_id.json")
124 | deviceIDs := make(map[string]string)
125 |
126 | if _, err := os.Stat(deviceIDPath); !os.IsNotExist(err) {
127 | deviceIDBytes, err := os.ReadFile(deviceIDPath)
128 | if err != nil {
129 | panic(err)
130 | }
131 | err = json.Unmarshal(deviceIDBytes, &deviceIDs)
132 | if err != nil {
133 | panic(err)
134 | }
135 | }
136 |
137 | if deviceID, ok := deviceIDs[userID]; ok {
138 | return deviceID
139 | }
140 |
141 | deviceID := uuid.New().String()
142 | deviceIDs[userID] = deviceID
143 | err := os.MkdirAll(filepath.Dir(deviceIDPath), os.ModePerm)
144 | if err != nil {
145 | panic(err)
146 | }
147 | deviceIDBytes, err := json.MarshalIndent(deviceIDs, "", " ")
148 | if err != nil {
149 | panic(err)
150 | }
151 | err = os.WriteFile(deviceIDPath, deviceIDBytes, 0644)
152 | if err != nil {
153 | panic(err)
154 | }
155 |
156 | return deviceID
157 | }
158 |
159 | func min(a, b int) int {
160 | if a < b {
161 | return a
162 | }
163 | return b
164 | }
165 |
166 | func reverseSlice(s []map[string]interface{}) {
167 | for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
168 | s[i], s[j] = s[j], s[i]
169 | }
170 | }
171 |
172 | func containKey(key string, m map[string]interface{}) bool {
173 | _, ok := m[key]
174 | return ok
175 | }
176 |
177 | func getMap(m map[string]interface{}, key string) map[string]interface{} {
178 | return m[key].(map[string]interface{})
179 | }
180 |
--------------------------------------------------------------------------------