mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-17 19:47:08 +08:00
136 lines
3.6 KiB
Go
136 lines
3.6 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
|
|
"github.com/Wxw-Gu/WechatExplorer/services/wechat-connector/ilink"
|
|
"github.com/Wxw-Gu/WechatExplorer/services/wechat-connector/messaging"
|
|
)
|
|
|
|
// Server provides an HTTP API for sending messages.
|
|
type Server struct {
|
|
clients []*ilink.Client
|
|
addr string
|
|
}
|
|
|
|
// NewServer creates an API server.
|
|
func NewServer(clients []*ilink.Client, addr string) *Server {
|
|
if addr == "" {
|
|
addr = "127.0.0.1:18011"
|
|
}
|
|
return &Server{clients: clients, addr: addr}
|
|
}
|
|
|
|
// SendRequest is the JSON body for POST /api/send.
|
|
type SendRequest struct {
|
|
AccountID string `json:"account_id,omitempty"`
|
|
To string `json:"to"`
|
|
Text string `json:"text,omitempty"`
|
|
MediaURL string `json:"media_url,omitempty"` // image/video/file URL
|
|
}
|
|
|
|
// Run starts the HTTP server. Blocks until ctx is cancelled.
|
|
func (s *Server) Run(ctx context.Context) error {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/api/send", s.handleSend)
|
|
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
fmt.Fprintln(w, "ok")
|
|
})
|
|
|
|
srv := &http.Server{Addr: s.addr, Handler: mux}
|
|
|
|
go func() {
|
|
<-ctx.Done()
|
|
srv.Shutdown(context.Background())
|
|
}()
|
|
|
|
log.Printf("[api] listening on %s", s.addr)
|
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) handleSend(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "POST only", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
var req SendRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "invalid JSON: "+err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if req.To == "" {
|
|
http.Error(w, `"to" is required`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
if req.Text == "" && req.MediaURL == "" {
|
|
http.Error(w, `"text" or "media_url" is required`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if len(s.clients) == 0 {
|
|
http.Error(w, "no accounts configured", http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
|
|
client := s.clientForAccount(req.AccountID)
|
|
if client == nil {
|
|
http.Error(w, "requested account is not available", http.StatusNotFound)
|
|
return
|
|
}
|
|
ctx := r.Context()
|
|
|
|
// Send text if provided
|
|
if req.Text != "" {
|
|
if err := messaging.SendTextReply(ctx, client, req.To, req.Text, "", ""); err != nil {
|
|
log.Printf("[api] send text failed: %v", err)
|
|
http.Error(w, "send text failed: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
log.Printf("[api] sent text to %s: %q", req.To, req.Text)
|
|
|
|
// Extract and send any markdown images embedded in text
|
|
for _, imgURL := range messaging.ExtractImageURLs(req.Text) {
|
|
if err := messaging.SendMediaFromURL(ctx, client, req.To, imgURL, ""); err != nil {
|
|
log.Printf("[api] send extracted image failed: %v", err)
|
|
} else {
|
|
log.Printf("[api] sent extracted image to %s: %s", req.To, imgURL)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send media if provided
|
|
if req.MediaURL != "" {
|
|
if err := messaging.SendMediaFromURL(ctx, client, req.To, req.MediaURL, ""); err != nil {
|
|
log.Printf("[api] send media failed: %v", err)
|
|
http.Error(w, "send media failed: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
log.Printf("[api] sent media to %s: %s", req.To, req.MediaURL)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
|
}
|
|
|
|
func (s *Server) clientForAccount(accountID string) *ilink.Client {
|
|
if accountID == "" {
|
|
return s.clients[0]
|
|
}
|
|
for _, client := range s.clients {
|
|
if client.BotID() == accountID {
|
|
return client
|
|
}
|
|
}
|
|
return nil
|
|
}
|