119 lines
3.6 KiB
Go
119 lines
3.6 KiB
Go
package reward
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/wangjia/pangolin/server/internal/apierr"
|
|
"github.com/wangjia/pangolin/server/internal/auth"
|
|
)
|
|
|
|
const inviteLinkBase = "https://pangolin.yanmeiai.com/i/"
|
|
|
|
type Handler struct {
|
|
svc *Service
|
|
st *Store
|
|
tgEnabled bool
|
|
channel string
|
|
}
|
|
|
|
func NewHandler(s *Service, st *Store, tgEnabled bool, channel string) *Handler {
|
|
return &Handler{svc: s, st: st, tgEnabled: tgEnabled, channel: channel}
|
|
}
|
|
|
|
func (h *Handler) GetInvite(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
uid, ok := auth.UserIDFromContext(ctx)
|
|
if !ok {
|
|
apierr.WriteJSON(w, http.StatusUnauthorized, apierr.ErrUnauthorized)
|
|
return
|
|
}
|
|
code, err := h.svc.EnsureCode(ctx, uid)
|
|
if err != nil {
|
|
apierr.WriteJSON(w, http.StatusInternalServerError, apierr.ErrInternal)
|
|
return
|
|
}
|
|
invited, converted, earned, err := h.st.Summary(ctx, uid)
|
|
if err != nil {
|
|
apierr.WriteJSON(w, http.StatusInternalServerError, apierr.ErrInternal)
|
|
return
|
|
}
|
|
joined, _ := h.st.TelegramClaimed(ctx, uid)
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"invite_code": code,
|
|
"invite_link": inviteLinkBase + code,
|
|
"invited": invited,
|
|
"converted": converted,
|
|
"earned_days": earned,
|
|
"telegram": map[string]any{
|
|
"enabled": h.tgEnabled,
|
|
"joined": joined,
|
|
"channel": h.channel,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (h *Handler) TelegramStart(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
uid, ok := auth.UserIDFromContext(ctx)
|
|
if !ok {
|
|
apierr.WriteJSON(w, http.StatusUnauthorized, apierr.ErrUnauthorized)
|
|
return
|
|
}
|
|
if !h.tgEnabled {
|
|
apierr.WriteJSON(w, http.StatusNotFound, apierr.ErrNotFound)
|
|
return
|
|
}
|
|
tok, err := h.svc.IssueTelegramToken(ctx, uid)
|
|
if err != nil {
|
|
apierr.WriteJSON(w, http.StatusInternalServerError, apierr.ErrInternal)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"deep_link": h.svc.TelegramDeepLink(tok)})
|
|
}
|
|
|
|
// TelegramWebhook 处理 Telegram Bot API 回调:校验 secret 头 → 解析 `/start <token>`
|
|
// → ConsumeTelegramToken → ClaimTelegram → sendMessage 回执。Telegram 只要求 200 即不重投,
|
|
// 业务结果通过 sendMessage 异步告知用户。
|
|
func (h *Handler) TelegramWebhook(w http.ResponseWriter, r *http.Request) {
|
|
if !h.tgEnabled || r.Header.Get("X-Telegram-Bot-Api-Secret-Token") != h.svc.WebhookSecret() {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
var upd struct {
|
|
Message struct {
|
|
Text string `json:"text"`
|
|
From struct {
|
|
ID int64 `json:"id"`
|
|
} `json:"from"`
|
|
} `json:"message"`
|
|
}
|
|
_ = json.NewDecoder(http.MaxBytesReader(w, r.Body, 16<<10)).Decode(&upd)
|
|
w.WriteHeader(http.StatusOK) // Telegram 只要 200;业务异步在下方
|
|
text, fromID := upd.Message.Text, upd.Message.From.ID
|
|
if !strings.HasPrefix(text, "/start ") || fromID == 0 {
|
|
return
|
|
}
|
|
token := strings.TrimSpace(strings.TrimPrefix(text, "/start "))
|
|
ctx := r.Context()
|
|
uid, ok, _ := h.svc.ConsumeTelegramToken(ctx, token)
|
|
if !ok {
|
|
h.svc.SendTelegram(ctx, fromID, "链接已失效,请回 App 重新点「验证领取」。")
|
|
return
|
|
}
|
|
granted, err := h.svc.ClaimTelegram(ctx, uid, strconv.FormatInt(fromID, 10))
|
|
switch {
|
|
case err != nil:
|
|
h.svc.SendTelegram(ctx, fromID, "验证出错,请稍后重试。")
|
|
case granted:
|
|
h.svc.SendTelegram(ctx, fromID, "已到账 +3 天 Pro,感谢加入!")
|
|
default:
|
|
h.svc.SendTelegram(ctx, fromID, "请先加入频道 "+h.svc.Channel()+" 再点验证;若已加入且领取过则无需重复。")
|
|
}
|
|
}
|
|
|