134 lines
3.3 KiB
Go
134 lines
3.3 KiB
Go
package commands
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"math/rand"
|
|
"net/http"
|
|
"time"
|
|
|
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
|
)
|
|
|
|
type XkcdCommand struct{}
|
|
|
|
type XkcdComic struct {
|
|
Num int `json:"num"`
|
|
Title string `json:"title"`
|
|
SafeTitle string `json:"safe_title"`
|
|
Img string `json:"img"`
|
|
Alt string `json:"alt"`
|
|
Transcript string `json:"transcript"`
|
|
Link string `json:"link"`
|
|
News string `json:"news"`
|
|
Year string `json:"year"`
|
|
Month string `json:"month"`
|
|
Day string `json:"day"`
|
|
}
|
|
|
|
func (x XkcdCommand) Name() string {
|
|
return "xkcd"
|
|
}
|
|
|
|
func (x XkcdCommand) Help() string {
|
|
return "Get a random XKCD comic. Usage: /xkcd"
|
|
}
|
|
|
|
func (x XkcdCommand) Execute(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
|
|
// Send "typing" action
|
|
typingAction := tgbotapi.NewChatAction(update.Message.Chat.ID, tgbotapi.ChatTyping)
|
|
bot.Send(typingAction)
|
|
|
|
comic, err := getRandomXkcdComic()
|
|
if err != nil {
|
|
log.Printf("Error getting XKCD comic: %v", err)
|
|
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "❌ Error fetching XKCD comic. Please try again later.")
|
|
bot.Send(msg)
|
|
return
|
|
}
|
|
|
|
// Create caption
|
|
caption := createXkcdCaption(comic)
|
|
|
|
// Send photo with caption
|
|
photo := tgbotapi.NewPhoto(update.Message.Chat.ID, tgbotapi.FileURL(comic.Img))
|
|
photo.Caption = caption
|
|
bot.Send(photo)
|
|
}
|
|
|
|
func getRandomXkcdComic() (*XkcdComic, error) {
|
|
// First get the latest comic to know the range
|
|
resp, err := http.Get("https://xkcd.com/info.0.json")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get latest comic info: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
return nil, fmt.Errorf("XKCD API returned status: %d", resp.StatusCode)
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %v", err)
|
|
}
|
|
|
|
var latest XkcdComic
|
|
if err := json.Unmarshal(body, &latest); err != nil {
|
|
return nil, fmt.Errorf("failed to parse latest comic: %v", err)
|
|
}
|
|
|
|
// Generate random comic number (avoiding 404 which doesn't exist)
|
|
rand.Seed(time.Now().UnixNano())
|
|
var randomNum int
|
|
for {
|
|
randomNum = rand.Intn(latest.Num) + 1
|
|
if randomNum != 404 { // Comic 404 doesn't exist
|
|
break
|
|
}
|
|
}
|
|
|
|
// Get the random comic
|
|
url := fmt.Sprintf("https://xkcd.com/%d/info.0.json", randomNum)
|
|
resp, err = http.Get(url)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get comic %d: %v", randomNum, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
return nil, fmt.Errorf("comic %d returned status: %d", randomNum, resp.StatusCode)
|
|
}
|
|
|
|
body, err = io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read comic response: %v", err)
|
|
}
|
|
|
|
var comic XkcdComic
|
|
if err := json.Unmarshal(body, &comic); err != nil {
|
|
return nil, fmt.Errorf("failed to parse comic: %v", err)
|
|
}
|
|
|
|
return &comic, nil
|
|
}
|
|
|
|
func createXkcdCaption(comic *XkcdComic) string {
|
|
url := fmt.Sprintf("https://xkcd.com/%d/", comic.Num)
|
|
|
|
// Try to include alt text if caption isn't too long
|
|
fullCaption := fmt.Sprintf("%s\n\n%s\n\n%s", comic.Title, comic.Alt, url)
|
|
if len(fullCaption) <= 1000 { // Keep it reasonable for Telegram
|
|
return fullCaption
|
|
}
|
|
|
|
// Fallback to just title and URL if too long
|
|
return fmt.Sprintf("%s\n\n%s", comic.Title, url)
|
|
}
|
|
|
|
func init() {
|
|
Register(XkcdCommand{})
|
|
}
|