Compare commits

...

2 Commits

Author SHA1 Message Date
e7a8cbc2a1 More commands 2025-06-25 15:53:42 +02:00
1eaef9f22f osrs command 2025-06-25 14:17:36 +02:00
8 changed files with 939 additions and 1 deletions

76
commands/fortune.go Normal file
View File

@ -0,0 +1,76 @@
package commands
import (
"encoding/json"
"fmt"
"io"
"log"
"math/rand"
"os"
"time"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type FortuneCommand struct{}
func (f FortuneCommand) Name() string {
return "fortune"
}
func (f FortuneCommand) Help() string {
return "Get your fortune. Usage: /fortune"
}
func (f FortuneCommand) Execute(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
fortune, err := getRandomFortune()
if err != nil {
log.Printf("Error getting fortune: %v", err)
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "❌ Error loading fortune database. Please try again later.")
bot.Send(msg)
return
}
msg := tgbotapi.NewMessage(update.Message.Chat.ID, fortune)
bot.Send(msg)
}
func getRandomFortune() (string, error) {
// Try current json directory first, then fallback to old_json
file, err := os.Open("json/fortune.json")
if err != nil {
// Try old_json directory
file, err = os.Open("old_json/fortune.json")
if err != nil {
return "", fmt.Errorf("failed to open fortune.json: %v", err)
}
}
defer file.Close()
// Read the file content
data, err := io.ReadAll(file)
if err != nil {
return "", fmt.Errorf("failed to read fortune.json: %v", err)
}
// Parse JSON array
var fortunes []string
if err := json.Unmarshal(data, &fortunes); err != nil {
return "", fmt.Errorf("failed to parse fortune.json: %v", err)
}
if len(fortunes) == 0 {
return "", fmt.Errorf("fortune database is empty")
}
// Seed random number generator
rand.Seed(time.Now().UnixNano())
// Pick a random fortune
randomIndex := rand.Intn(len(fortunes))
return fortunes[randomIndex], nil
}
func init() {
Register(FortuneCommand{})
}

View File

@ -17,7 +17,7 @@ func (i InfoCommand) Help() string {
}
func (i InfoCommand) Execute(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "This bot does dumb stuff and chemistry. Now version 2.0, rewritten in Go.")
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "This bot does dumb stuff and chemistry. Now version 2.0, rewritten in Go.\n\nSauce: https://gitea.boner.be/nignogbot/nignoggobot\n\nComplaints and support: @fatboners")
_, err := bot.Send(msg)
if err != nil {
log.Println("Failed to send info message:", err)

66
commands/kekget.go Normal file
View File

@ -0,0 +1,66 @@
package commands
import (
"fmt"
"math/rand"
"strings"
"time"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type KekgetCommand struct{}
func (k KekgetCommand) Name() string {
return "kekget"
}
func (k KekgetCommand) Help() string {
return "Try to get a KEK or KKK, or even a multiKEK. Usage: /kekget"
}
func (k KekgetCommand) Execute(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
result := generateKekget()
msg := tgbotapi.NewMessage(update.Message.Chat.ID, result)
bot.Send(msg)
}
func generateKekget() string {
rand.Seed(time.Now().UnixNano())
// Generate random length between 1 and 20
length := rand.Intn(20) + 1
var text strings.Builder
for i := 0; i < length; i++ {
if rand.Float64() > 0.5 {
text.WriteString("K")
} else {
text.WriteString("E")
}
}
result := text.String()
// Check for special patterns
switch result {
case "KEK":
return fmt.Sprintf("%s\nYOU WIN TOPKEK!!!", result)
case "KKK":
return fmt.Sprintf("%s\nYOU WIN TOPKKK HEIL HITLER!!!", result)
case "KEKKEK":
return fmt.Sprintf("%s\nYOU WIN DOUBLE TOPKEKKEK!!!", result)
case "KEKKEKKEK":
return fmt.Sprintf("%s\nYOU WIN ULTIMATE TRIPLE TOPKEKKEKKEK!!!", result)
case "KEKKEKKEKKEK":
return fmt.Sprintf("%s\nQUADDRUPPLE TOPKEKKEKKEKKEK!!! YOU ARE GAY!!!", result)
case "KEKKEKKEKKEKKEK":
return fmt.Sprintf("%s\nQUINTUPLE TOPKEKKEKKEKKEKKEK!!! UNBELIEVABLE M8!!!", result)
default:
return fmt.Sprintf("%s\nLength: %d", result, len(result))
}
}
func init() {
Register(KekgetCommand{})
}

24
commands/lenny.go Normal file
View File

@ -0,0 +1,24 @@
package commands
import (
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type LennyCommand struct{}
func (l LennyCommand) Name() string {
return "lenny"
}
func (l LennyCommand) Help() string {
return "( ͡° ͜ʖ ͡°) Usage: /lenny"
}
func (l LennyCommand) Execute(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "( ͡° ͜ʖ ͡°)")
bot.Send(msg)
}
func init() {
Register(LennyCommand{})
}

549
commands/osrs.go Normal file
View File

@ -0,0 +1,549 @@
package commands
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strings"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type OSRSCommand struct{}
func (o OSRSCommand) Name() string {
return "osrs"
}
func (o OSRSCommand) Help() string {
return "Get Old School RuneScape player stats with interactive navigation. Usage: /osrs <username>"
}
// OSRS JSON API structures
type OSRSResponse struct {
Skills []OSRSSkill `json:"skills"`
Activities []OSRSActivity `json:"activities"`
}
type OSRSSkill struct {
ID int `json:"id"`
Name string `json:"name"`
Rank int `json:"rank"`
Level int `json:"level"`
XP int64 `json:"xp"`
}
type OSRSActivity struct {
ID int `json:"id"`
Name string `json:"name"`
Rank int `json:"rank"`
Score int64 `json:"score"`
}
type OSRSStats struct {
Username string
AccountType string
CombatLevel int
TotalLevel int
TotalXP int64
Skills map[string]OSRSSkill
Activities map[string]OSRSActivity
}
type StatsCategory int
const (
CategoryOverview StatsCategory = iota
CategoryCombat
CategorySkilling
CategoryActivities
)
func (o OSRSCommand) Execute(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
args := strings.TrimSpace(update.Message.CommandArguments())
if args == "" {
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "⚔️ *OSRS Stats Command*\n\nUsage: `/osrs <username>`\n\nExample: `/osrs Zezima`\n\nThis command shows Old School RuneScape player statistics with interactive navigation through different skill categories.")
msg.ParseMode = "Markdown"
bot.Send(msg)
return
}
// Send "typing" action
typingAction := tgbotapi.NewChatAction(update.Message.Chat.ID, tgbotapi.ChatTyping)
bot.Send(typingAction)
stats, err := fetchOSRSStats(args, "normal")
if err != nil {
handleOSRSError(bot, update.Message.Chat.ID, err, args)
return
}
sendOSRSStats(bot, update.Message.Chat.ID, stats, CategoryOverview)
}
func fetchOSRSStats(username, accountType string) (*OSRSStats, error) {
var url string
switch accountType {
case "ironman":
url = fmt.Sprintf("https://secure.runescape.com/m=hiscore_oldschool_ironman/index_lite.json?player=%s", username)
case "hardcore":
url = fmt.Sprintf("https://secure.runescape.com/m=hiscore_oldschool_hardcore_ironman/index_lite.json?player=%s", username)
case "ultimate":
url = fmt.Sprintf("https://secure.runescape.com/m=hiscore_oldschool_ultimate/index_lite.json?player=%s", username)
default: // normal
url = fmt.Sprintf("https://secure.runescape.com/m=hiscore_oldschool/index_lite.json?player=%s", username)
}
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("network error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode == 404 {
return nil, fmt.Errorf("player '%s' not found", username)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("OSRS API error: status %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %v", err)
}
var osrsResp OSRSResponse
if err := json.Unmarshal(body, &osrsResp); err != nil {
return nil, fmt.Errorf("failed to parse JSON response: %v", err)
}
return parseOSRSStats(&osrsResp, username, accountType)
}
func parseOSRSStats(resp *OSRSResponse, username, accountType string) (*OSRSStats, error) {
stats := &OSRSStats{
Username: username,
AccountType: accountType,
Skills: make(map[string]OSRSSkill),
Activities: make(map[string]OSRSActivity),
}
// Parse skills
for _, skill := range resp.Skills {
stats.Skills[skill.Name] = skill
if skill.Name == "Overall" {
stats.TotalLevel = skill.Level
stats.TotalXP = skill.XP
}
}
// Parse activities
for _, activity := range resp.Activities {
stats.Activities[activity.Name] = activity
}
// Calculate combat level
stats.CombatLevel = calculateCombatLevel(stats.Skills)
return stats, nil
}
func calculateCombatLevel(skills map[string]OSRSSkill) int {
att := float64(getSkillLevel(skills, "Attack"))
def := float64(getSkillLevel(skills, "Defence"))
str := float64(getSkillLevel(skills, "Strength"))
hp := float64(getSkillLevel(skills, "Hitpoints"))
pray := float64(getSkillLevel(skills, "Prayer"))
rang := float64(getSkillLevel(skills, "Ranged"))
mage := float64(getSkillLevel(skills, "Magic"))
base := 0.25 * (def + hp + (pray / 2))
melee := 0.325 * (att + str)
ranged := 0.325 * (rang + (rang / 2))
magic := 0.325 * (mage + (mage / 2))
var max float64
if melee >= ranged && melee >= magic {
max = melee
} else if ranged >= magic {
max = ranged
} else {
max = magic
}
return int(base + max)
}
func getSkillLevel(skills map[string]OSRSSkill, skillName string) int {
if skill, exists := skills[skillName]; exists {
return skill.Level
}
return 1 // Default level
}
func sendOSRSStats(bot *tgbotapi.BotAPI, chatID int64, stats *OSRSStats, category StatsCategory) {
caption := formatOSRSCaption(stats, category)
keyboard := createOSRSKeyboard(stats.Username, stats.AccountType, category)
// Use OSRS logo
imageURL := "https://oldschool.runescape.wiki/images/thumb/b/b2/Old_School_RuneScape_logo.png/300px-Old_School_RuneScape_logo.png"
photo := tgbotapi.NewPhoto(chatID, tgbotapi.FileURL(imageURL))
photo.Caption = caption
photo.ParseMode = "Markdown"
photo.ReplyMarkup = keyboard
bot.Send(photo)
}
func formatOSRSCaption(stats *OSRSStats, category StatsCategory) string {
switch category {
case CategoryOverview:
return formatOverview(stats)
case CategoryCombat:
return formatCombatStats(stats)
case CategorySkilling:
return formatSkillingStats(stats)
case CategoryActivities:
return formatActivities(stats)
default:
return formatOverview(stats)
}
}
func formatOverview(stats *OSRSStats) string {
b := &strings.Builder{}
fmt.Fprintf(b, "⚔️ *%s*", stats.Username)
if stats.AccountType != "normal" {
accountTypeEmoji := map[string]string{
"ironman": "⚫",
"hardcore": "🔴",
"ultimate": "⚪",
}
emoji := accountTypeEmoji[stats.AccountType]
fmt.Fprintf(b, " %s %s", emoji, strings.Title(stats.AccountType))
}
fmt.Fprintf(b, "\n📊 *Overview*\n\n")
fmt.Fprintf(b, "*Combat Level:* %d\n", stats.CombatLevel)
fmt.Fprintf(b, "*Total Level:* %s\n", formatNumber(stats.TotalLevel))
fmt.Fprintf(b, "*Total XP:* %s\n", formatNumber(int(stats.TotalXP)))
// Show overall rank if available
if overallSkill, exists := stats.Skills["Overall"]; exists && overallSkill.Rank > 0 {
fmt.Fprintf(b, "*Overall Rank:* %d\n", overallSkill.Rank)
}
fmt.Fprintf(b, "\n")
// Show top 5 skills by XP
var skillList []OSRSSkill
for name, skill := range stats.Skills {
if name != "Overall" && skill.Level > 1 {
skillList = append(skillList, skill)
}
}
// Sort by XP (bubble sort for simplicity)
for i := 0; i < len(skillList)-1; i++ {
for j := i + 1; j < len(skillList); j++ {
if skillList[j].XP > skillList[i].XP {
skillList[i], skillList[j] = skillList[j], skillList[i]
}
}
}
fmt.Fprintf(b, "*Top Skills:*\n")
maxSkills := 5
if len(skillList) < 5 {
maxSkills = len(skillList)
}
for i := 0; i < maxSkills; i++ {
skill := skillList[i]
fmt.Fprintf(b, "%d. *%s* - Level %d (%s XP)",
i+1, skill.Name, skill.Level, formatNumber(int(skill.XP)))
if skill.Rank > 0 {
fmt.Fprintf(b, " - Rank: %d", skill.Rank)
}
fmt.Fprintf(b, "\n")
}
fmt.Fprintf(b, "\n🔗 [OSRS Hiscores](https://secure.runescape.com/m=hiscore_oldschool/hiscorepersonal?user1=%s)", stats.Username)
return b.String()
}
func formatCombatStats(stats *OSRSStats) string {
b := &strings.Builder{}
fmt.Fprintf(b, "⚔️ *%s*", stats.Username)
if stats.AccountType != "normal" {
accountTypeEmoji := map[string]string{
"ironman": "⚫",
"hardcore": "🔴",
"ultimate": "⚪",
}
emoji := accountTypeEmoji[stats.AccountType]
fmt.Fprintf(b, " %s %s", emoji, strings.Title(stats.AccountType))
}
fmt.Fprintf(b, "\n⚔ *Combat Stats*\n\n")
fmt.Fprintf(b, "*Combat Level:* %d\n\n", stats.CombatLevel)
combatSkillNames := []string{"Attack", "Strength", "Defence", "Hitpoints", "Ranged", "Prayer", "Magic"}
// Collect combat skills that exist
var combatSkills []OSRSSkill
for _, skillName := range combatSkillNames {
if skill, exists := stats.Skills[skillName]; exists {
combatSkills = append(combatSkills, skill)
}
}
// Sort by XP descending
for i := 0; i < len(combatSkills)-1; i++ {
for j := i + 1; j < len(combatSkills); j++ {
if combatSkills[j].XP > combatSkills[i].XP {
combatSkills[i], combatSkills[j] = combatSkills[j], combatSkills[i]
}
}
}
// Display combat stats sorted by XP
for _, skill := range combatSkills {
fmt.Fprintf(b, "*%s:* Level %d\n", skill.Name, skill.Level)
fmt.Fprintf(b, "└ XP: %s", formatNumber(int(skill.XP)))
if skill.Rank > 0 {
fmt.Fprintf(b, " (Rank: %d)", skill.Rank)
}
fmt.Fprintf(b, "\n")
}
return b.String()
}
func formatSkillingStats(stats *OSRSStats) string {
b := &strings.Builder{}
fmt.Fprintf(b, "⚔️ *%s*", stats.Username)
if stats.AccountType != "normal" {
accountTypeEmoji := map[string]string{
"ironman": "⚫",
"hardcore": "🔴",
"ultimate": "⚪",
}
emoji := accountTypeEmoji[stats.AccountType]
fmt.Fprintf(b, " %s %s", emoji, strings.Title(stats.AccountType))
}
fmt.Fprintf(b, "\n🔨 *Skilling Stats*\n\n")
skillingSkillNames := []string{
"Woodcutting", "Fishing", "Mining", "Cooking", "Firemaking",
"Crafting", "Smithing", "Fletching", "Herblore", "Agility",
"Thieving", "Slayer", "Farming", "Runecraft", "Hunter", "Construction",
}
// Collect skilling skills that exist and have level > 1
var skillingSkills []OSRSSkill
for _, skillName := range skillingSkillNames {
if skill, exists := stats.Skills[skillName]; exists && skill.Level > 1 {
skillingSkills = append(skillingSkills, skill)
}
}
// Sort by XP descending
for i := 0; i < len(skillingSkills)-1; i++ {
for j := i + 1; j < len(skillingSkills); j++ {
if skillingSkills[j].XP > skillingSkills[i].XP {
skillingSkills[i], skillingSkills[j] = skillingSkills[j], skillingSkills[i]
}
}
}
// Display in the same format as combat stats
for _, skill := range skillingSkills {
fmt.Fprintf(b, "*%s:* Level %d\n", skill.Name, skill.Level)
fmt.Fprintf(b, "└ XP: %s", formatNumber(int(skill.XP)))
if skill.Rank > 0 {
fmt.Fprintf(b, " (Rank: %d)", skill.Rank)
}
fmt.Fprintf(b, "\n")
}
return b.String()
}
func formatActivities(stats *OSRSStats) string {
b := &strings.Builder{}
fmt.Fprintf(b, "⚔️ *%s*", stats.Username)
if stats.AccountType != "normal" {
accountTypeEmoji := map[string]string{
"ironman": "⚫",
"hardcore": "🔴",
"ultimate": "⚪",
}
emoji := accountTypeEmoji[stats.AccountType]
fmt.Fprintf(b, " %s %s", emoji, strings.Title(stats.AccountType))
}
fmt.Fprintf(b, "\n🏆 *Activities & Bosses*\n\n")
// Collect all activities with scores > 0
var activeActivities []OSRSActivity
for _, activity := range stats.Activities {
if activity.Score > 0 {
activeActivities = append(activeActivities, activity)
}
}
// Sort by score descending (highest to lowest)
for i := 0; i < len(activeActivities)-1; i++ {
for j := i + 1; j < len(activeActivities); j++ {
if activeActivities[j].Score > activeActivities[i].Score {
activeActivities[i], activeActivities[j] = activeActivities[j], activeActivities[i]
}
}
}
// Display all activities with non-zero scores
if len(activeActivities) > 0 {
for _, activity := range activeActivities {
fmt.Fprintf(b, "*%s:* %s", activity.Name, formatNumber(int(activity.Score)))
if activity.Rank > 0 {
fmt.Fprintf(b, " (Rank: %d)", activity.Rank)
}
fmt.Fprintf(b, "\n")
}
} else {
fmt.Fprintf(b, "No activities found.\nStart bossing to see stats here! 💪")
}
return b.String()
}
func formatNumber(n int) string {
if n >= 1000000 {
return fmt.Sprintf("%.2fM", float64(n)/1000000)
} else if n >= 1000 {
return fmt.Sprintf("%.1fK", float64(n)/1000)
}
return fmt.Sprintf("%d", n)
}
func createOSRSKeyboard(username, accountType string, currentCategory StatsCategory) tgbotapi.InlineKeyboardMarkup {
var buttons [][]tgbotapi.InlineKeyboardButton
// Category buttons
categoryRow1 := []tgbotapi.InlineKeyboardButton{
tgbotapi.NewInlineKeyboardButtonData(getOSRSButtonText("📊", currentCategory == CategoryOverview), fmt.Sprintf("osrs:%s:%s:overview", username, accountType)),
tgbotapi.NewInlineKeyboardButtonData(getOSRSButtonText("⚔️", currentCategory == CategoryCombat), fmt.Sprintf("osrs:%s:%s:combat", username, accountType)),
}
buttons = append(buttons, categoryRow1)
categoryRow2 := []tgbotapi.InlineKeyboardButton{
tgbotapi.NewInlineKeyboardButtonData(getOSRSButtonText("🔨", currentCategory == CategorySkilling), fmt.Sprintf("osrs:%s:%s:skilling", username, accountType)),
tgbotapi.NewInlineKeyboardButtonData(getOSRSButtonText("🏆", currentCategory == CategoryActivities), fmt.Sprintf("osrs:%s:%s:activities", username, accountType)),
}
buttons = append(buttons, categoryRow2)
// Account type buttons
if accountType == "normal" {
accountRow := []tgbotapi.InlineKeyboardButton{
tgbotapi.NewInlineKeyboardButtonData("⚫ Ironman", fmt.Sprintf("osrs:%s:ironman:overview", username)),
tgbotapi.NewInlineKeyboardButtonData("🔴 Hardcore", fmt.Sprintf("osrs:%s:hardcore:overview", username)),
}
buttons = append(buttons, accountRow)
ultimateRow := []tgbotapi.InlineKeyboardButton{
tgbotapi.NewInlineKeyboardButtonData("⚪ Ultimate", fmt.Sprintf("osrs:%s:ultimate:overview", username)),
}
buttons = append(buttons, ultimateRow)
} else {
// Return to normal account button
normalRow := []tgbotapi.InlineKeyboardButton{
tgbotapi.NewInlineKeyboardButtonData("🔙 Normal Account", fmt.Sprintf("osrs:%s:normal:overview", username)),
}
buttons = append(buttons, normalRow)
}
return tgbotapi.NewInlineKeyboardMarkup(buttons...)
}
func getOSRSButtonText(emoji string, isActive bool) string {
if isActive {
return emoji + " ●"
}
return emoji
}
func handleOSRSError(bot *tgbotapi.BotAPI, chatID int64, err error, username string) {
var message string
if strings.Contains(err.Error(), "not found") {
message = fmt.Sprintf("❌ Player '%s' not found.\n\n💡 *Tips:*\n• Check spelling\n• Username is case-sensitive\n• Player must have logged in recently\n• Try different account type (ironman, etc.)", username)
} else if strings.Contains(err.Error(), "network") {
message = "🌐 Network error. OSRS servers might be down. Try again later."
} else {
message = "⚠️ An error occurred while fetching player data. Please try again."
}
msg := tgbotapi.NewMessage(chatID, message)
msg.ParseMode = "Markdown"
bot.Send(msg)
}
// HandleCallback handles inline keyboard button presses for OSRS
func (o OSRSCommand) HandleCallback(update tgbotapi.Update, bot *tgbotapi.BotAPI, params []string) {
if len(params) < 3 {
log.Printf("Invalid OSRS callback params: %v", params)
return
}
username := params[0]
accountType := params[1]
categoryStr := params[2]
var category StatsCategory
switch categoryStr {
case "overview":
category = CategoryOverview
case "combat":
category = CategoryCombat
case "skilling":
category = CategorySkilling
case "activities":
category = CategoryActivities
default:
log.Printf("Invalid OSRS category in callback: %s", categoryStr)
return
}
// Fetch stats
stats, err := fetchOSRSStats(username, accountType)
if err != nil {
log.Printf("Error fetching OSRS stats for %s: %v", username, err)
callback := tgbotapi.NewCallback(update.CallbackQuery.ID, "Error loading player data")
bot.Request(callback)
return
}
// Edit the message caption and keyboard
caption := formatOSRSCaption(stats, category)
keyboard := createOSRSKeyboard(stats.Username, stats.AccountType, category)
editCaption := tgbotapi.NewEditMessageCaption(
update.CallbackQuery.Message.Chat.ID,
update.CallbackQuery.Message.MessageID,
caption,
)
editCaption.ParseMode = "Markdown"
editCaption.ReplyMarkup = &keyboard
if _, err := bot.Send(editCaption); err != nil {
log.Printf("Error editing OSRS message caption: %v", err)
}
}
func init() {
Register(OSRSCommand{})
}

81
commands/troll.go Normal file
View File

@ -0,0 +1,81 @@
package commands
import (
"encoding/json"
"fmt"
"io"
"log"
"math/rand"
"os"
"time"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
type TrollCommand struct{}
func (t TrollCommand) Name() string {
return "troll"
}
func (t TrollCommand) Help() string {
return "Get a random troll message from the database. Usage: /troll"
}
func (t TrollCommand) Execute(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
// Send "typing" action
typingAction := tgbotapi.NewChatAction(update.Message.Chat.ID, tgbotapi.ChatTyping)
bot.Send(typingAction)
trollMessage, err := getRandomTrollMessage()
if err != nil {
log.Printf("Error getting troll message: %v", err)
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "❌ Error loading troll database. Please try again later.")
bot.Send(msg)
return
}
// Limit message length for Telegram (max 4096 characters)
if len(trollMessage) > 4000 {
trollMessage = trollMessage[:3997] + "..."
}
msg := tgbotapi.NewMessage(update.Message.Chat.ID, trollMessage)
bot.Send(msg)
}
func getRandomTrollMessage() (string, error) {
// Open the troll database file
file, err := os.Open("json/trolldb.json")
if err != nil {
return "", fmt.Errorf("failed to open trolldb.json: %v", err)
}
defer file.Close()
// Read the file content
data, err := io.ReadAll(file)
if err != nil {
return "", fmt.Errorf("failed to read trolldb.json: %v", err)
}
// Parse JSON array
var trollMessages []string
if err := json.Unmarshal(data, &trollMessages); err != nil {
return "", fmt.Errorf("failed to parse trolldb.json: %v", err)
}
if len(trollMessages) == 0 {
return "", fmt.Errorf("troll database is empty")
}
// Seed random number generator
rand.Seed(time.Now().UnixNano())
// Pick a random message
randomIndex := rand.Intn(len(trollMessages))
return trollMessages[randomIndex], nil
}
func init() {
Register(TrollCommand{})
}

133
commands/xkcd.go Normal file
View File

@ -0,0 +1,133 @@
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{})
}

View File

@ -58,6 +58,15 @@ func handleCallbackQuery(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
medchemCmd.HandleCallback(update, bot, parts[1:])
}
}
case "osrs":
// Get the OSRS command and handle callback
if cmd, ok := commands.All()["osrs"]; ok {
if osrsCmd, ok := cmd.(interface {
HandleCallback(tgbotapi.Update, *tgbotapi.BotAPI, []string)
}); ok {
osrsCmd.HandleCallback(update, bot, parts[1:])
}
}
default:
log.Printf("Unknown callback command: %s", commandName)
}