├── go.mod ├── go.sum ├── vercel.json ├── README.md └── api └── chat.go /go.mod: -------------------------------------------------------------------------------- 1 | module getmerlin2api 2 | 3 | go 1.22.2 4 | 5 | require github.com/google/uuid v1.6.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 2 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 3 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "api/*.go", 6 | "use": "@vercel/go" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "/api/chat.go" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 开始使用 2 | 3 | 1. 点击右侧按钮开始部署: 4 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3a%2f%2fgithub.com%2fSMNETSTUDIO%2fGetMerlin2Api&env=UUID&project-name=getmerlin2api&repository-name=getmerlin2api) 5 | 2. 在环境变量页填入UUID & AUTH_TOKEN 6 | 3. 部署完毕后,即可开始使用 7 | -------------------------------------------------------------------------------- /api/chat.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "os" 10 | "strings" 11 | "time" 12 | 13 | "github.com/google/uuid" 14 | ) 15 | 16 | type OpenAIRequest struct { 17 | Messages []Message `json:"messages"` 18 | Stream bool `json:"stream"` 19 | Model string `json:"model"` 20 | } 21 | 22 | type Message struct { 23 | Role string `json:"role"` 24 | Content string `json:"content"` 25 | } 26 | 27 | type MerlinRequest struct { 28 | Attachments []interface{} `json:"attachments"` 29 | ChatId string `json:"chatId"` 30 | Language string `json:"language"` 31 | Message struct { 32 | Content string `json:"content"` 33 | Context string `json:"context"` 34 | ChildId string `json:"childId"` 35 | Id string `json:"id"` 36 | ParentId string `json:"parentId"` 37 | } `json:"message"` 38 | Metadata struct { 39 | LargeContext bool `json:"largeContext"` 40 | MerlinMagic bool `json:"merlinMagic"` 41 | ProFinderMode bool `json:"proFinderMode"` 42 | WebAccess bool `json:"webAccess"` 43 | } `json:"metadata"` 44 | Mode string `json:"mode"` 45 | Model string `json:"model"` 46 | } 47 | 48 | type MerlinResponse struct { 49 | Data struct { 50 | Content string `json:"content"` 51 | } `json:"data"` 52 | } 53 | 54 | type OpenAIResponse struct { 55 | Id string `json:"id"` 56 | Object string `json:"object"` 57 | Created int64 `json:"created"` 58 | Model string `json:"model"` 59 | Choices []struct { 60 | Delta struct { 61 | Content string `json:"content"` 62 | } `json:"delta"` 63 | Index int `json:"index"` 64 | FinishReason string `json:"finish_reason"` 65 | } `json:"choices"` 66 | } 67 | 68 | type TokenResponse struct { 69 | IdToken string `json:"idToken"` 70 | } 71 | 72 | func getEnvOrDefault(key, defaultValue string) string { 73 | if value := os.Getenv(key); value != "" { 74 | return value 75 | } 76 | return defaultValue 77 | } 78 | 79 | func getToken() (string, error) { 80 | tokenReq := struct { 81 | UUID string `json:"uuid"` 82 | }{ 83 | UUID: getEnvOrDefault("UUID", ""), 84 | } 85 | 86 | tokenReqBody, _ := json.Marshal(tokenReq) 87 | resp, err := http.Post( 88 | "https://getmerlin-main-server.vercel.app/generate", 89 | "application/json", 90 | strings.NewReader(string(tokenReqBody)), 91 | ) 92 | if err != nil { 93 | return "", err 94 | } 95 | defer resp.Body.Close() 96 | 97 | var tokenResp TokenResponse 98 | if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil { 99 | return "", err 100 | } 101 | 102 | return tokenResp.IdToken, nil 103 | } 104 | 105 | func Handler(w http.ResponseWriter, r *http.Request) { 106 | authToken := r.Header.Get("Authorization") 107 | envToken := getEnvOrDefault("AUTH_TOKEN", "") 108 | 109 | if envToken != "" && authToken != "Bearer "+envToken { 110 | http.Error(w, "Unauthorized", http.StatusUnauthorized) 111 | return 112 | } 113 | 114 | if r.URL.Path != "/v1/chat/completions" { 115 | w.Header().Set("Content-Type", "application/json") 116 | w.WriteHeader(http.StatusOK) 117 | fmt.Fprintf(w, `{"status":"GetMerlin2Api Service Running...","message":"MoLoveSze..."}`) 118 | return 119 | } 120 | 121 | if r.Method != http.MethodPost { 122 | http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) 123 | return 124 | } 125 | 126 | var openAIReq OpenAIRequest 127 | if err := json.NewDecoder(r.Body).Decode(&openAIReq); err != nil { 128 | http.Error(w, err.Error(), http.StatusBadRequest) 129 | return 130 | } 131 | var contextMessages []string 132 | for i := 0; i < len(openAIReq.Messages)-1; i++ { 133 | msg := openAIReq.Messages[i] 134 | contextMessages = append(contextMessages, fmt.Sprintf("%s: %s", msg.Role, msg.Content)) 135 | } 136 | context := strings.Join(contextMessages, "\n") 137 | merlinReq := MerlinRequest{ 138 | Attachments: make([]interface{}, 0), 139 | ChatId: generateV1UUID(), 140 | Language: "AUTO", 141 | Message: struct { 142 | Content string `json:"content"` 143 | Context string `json:"context"` 144 | ChildId string `json:"childId"` 145 | Id string `json:"id"` 146 | ParentId string `json:"parentId"` 147 | }{ 148 | Content: openAIReq.Messages[len(openAIReq.Messages)-1].Content, 149 | Context: context, 150 | ChildId: generateUUID(), 151 | Id: generateUUID(), 152 | ParentId: "root", 153 | }, 154 | Mode: "UNIFIED_CHAT", 155 | Model: openAIReq.Model, 156 | Metadata: struct { 157 | LargeContext bool `json:"largeContext"` 158 | MerlinMagic bool `json:"merlinMagic"` 159 | ProFinderMode bool `json:"proFinderMode"` 160 | WebAccess bool `json:"webAccess"` 161 | }{ 162 | LargeContext: false, 163 | MerlinMagic: false, 164 | ProFinderMode: false, 165 | WebAccess: false, 166 | }, 167 | } 168 | token, err := getToken() 169 | if err != nil { 170 | http.Error(w, "Failed to get token: "+err.Error(), http.StatusInternalServerError) 171 | return 172 | } 173 | client := &http.Client{} 174 | merlinReqBody, _ := json.Marshal(merlinReq) 175 | 176 | req, _ := http.NewRequest("POST", "https://arcane.getmerlin.in/v1/thread/unified", strings.NewReader(string(merlinReqBody))) 177 | req.Header.Set("Content-Type", "application/json") 178 | req.Header.Set("Accept", "text/event-stream, text/event-stream") 179 | req.Header.Set("Authorization", "Bearer "+token) 180 | req.Header.Set("x-merlin-version", "web-merlin") 181 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36") 182 | req.Header.Set("sec-ch-ua", `"Not(A:Brand";v="99", "Microsoft Edge";v="133", "Chromium";v="133"`) 183 | req.Header.Set("sec-ch-ua-mobile", "?0") 184 | req.Header.Set("sec-ch-ua-platform", "Windows") 185 | req.Header.Set("Sec-Fetch-Site", "same-site") 186 | req.Header.Set("Sec-Fetch-Mode", "cors") 187 | req.Header.Set("Sec-Fetch-Dest", "empty") 188 | req.Header.Set("host", "arcane.getmerlin.in") 189 | if openAIReq.Stream { 190 | w.Header().Set("Content-Type", "text/event-stream") 191 | w.Header().Set("Cache-Control", "no-cache") 192 | w.Header().Set("Connection", "keep-alive") 193 | w.Header().Set("X-Accel-Buffering", "no") 194 | w.Header().Set("Transfer-Encoding", "chunked") 195 | } else { 196 | w.Header().Set("Content-Type", "application/json") 197 | } 198 | 199 | resp, err := client.Do(req) 200 | if err != nil { 201 | http.Error(w, err.Error(), http.StatusInternalServerError) 202 | return 203 | } 204 | defer resp.Body.Close() 205 | 206 | if !openAIReq.Stream { 207 | var fullContent string 208 | reader := bufio.NewReader(resp.Body) 209 | for { 210 | line, err := reader.ReadString('\n') 211 | if err != nil { 212 | if err == io.EOF { 213 | break 214 | } 215 | continue 216 | } 217 | 218 | line = strings.TrimSpace(line) 219 | 220 | if strings.HasPrefix(line, "event: message") { 221 | dataLine, err := reader.ReadString('\n') 222 | if err != nil { 223 | continue 224 | } 225 | dataLine = strings.TrimSpace(dataLine) 226 | 227 | if strings.HasPrefix(dataLine, "data: ") { 228 | dataStr := strings.TrimPrefix(dataLine, "data: ") 229 | var merlinResp MerlinResponse 230 | if err := json.Unmarshal([]byte(dataStr), &merlinResp); err != nil { 231 | continue 232 | } 233 | if merlinResp.Data.Content != " " { 234 | fullContent += merlinResp.Data.Content 235 | } 236 | } 237 | } 238 | } 239 | 240 | response := map[string]interface{}{ 241 | "id": generateUUID(), 242 | "object": "chat.completion", 243 | "created": getCurrentTimestamp(), 244 | "model": openAIReq.Model, 245 | "choices": []map[string]interface{}{ 246 | { 247 | "message": map[string]interface{}{ 248 | "role": "assistant", 249 | "content": fullContent, 250 | }, 251 | "finish_reason": "stop", 252 | "index": 0, 253 | }, 254 | }, 255 | } 256 | json.NewEncoder(w).Encode(response) 257 | return 258 | } 259 | 260 | reader := bufio.NewReader(resp.Body) 261 | for { 262 | line, err := reader.ReadString('\n') 263 | if err != nil { 264 | if err == io.EOF { 265 | break 266 | } 267 | continue 268 | } 269 | 270 | if strings.HasPrefix(line, "event: message") { 271 | dataLine, _ := reader.ReadString('\n') 272 | var merlinResp MerlinResponse 273 | json.Unmarshal([]byte(strings.TrimPrefix(dataLine, "data: ")), &merlinResp) 274 | 275 | if merlinResp.Data.Content != "" { 276 | openAIResp := OpenAIResponse{ 277 | Id: generateUUID(), 278 | Object: "chat.completion.chunk", 279 | Created: getCurrentTimestamp(), 280 | Model: openAIReq.Model, 281 | Choices: []struct { 282 | Delta struct { 283 | Content string `json:"content"` 284 | } `json:"delta"` 285 | Index int `json:"index"` 286 | FinishReason string `json:"finish_reason"` 287 | }{{ 288 | Delta: struct { 289 | Content string `json:"content"` 290 | }{ 291 | Content: merlinResp.Data.Content, 292 | }, 293 | Index: 0, 294 | FinishReason: "", 295 | }}, 296 | } 297 | 298 | respData, _ := json.Marshal(openAIResp) 299 | fmt.Fprintf(w, "data: %s\n\n", string(respData)) 300 | } 301 | } 302 | } 303 | 304 | finalResp := OpenAIResponse{ 305 | Choices: []struct { 306 | Delta struct { 307 | Content string `json:"content"` 308 | } `json:"delta"` 309 | Index int `json:"index"` 310 | FinishReason string `json:"finish_reason"` 311 | }{{ 312 | Delta: struct { 313 | Content string `json:"content"` 314 | }{Content: ""}, 315 | Index: 0, 316 | FinishReason: "stop", 317 | }}, 318 | } 319 | respData, _ := json.Marshal(finalResp) 320 | fmt.Fprintf(w, "data: %s\n\n", string(respData)) 321 | fmt.Fprintf(w, "data: [DONE]\n\n") 322 | } 323 | 324 | func generateUUID() string { 325 | return uuid.New().String() 326 | } 327 | 328 | func generateV1UUID() string { 329 | uuidObj := uuid.Must(uuid.NewUUID()) 330 | return uuidObj.String() 331 | } 332 | 333 | func getCurrentTimestamp() int64 { 334 | return time.Now().Unix() 335 | } 336 | --------------------------------------------------------------------------------