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{}) }