commands.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "log"
  6. "os"
  7. "path/filepath"
  8. "runtime"
  9. "strings"
  10. "time"
  11. "github.com/Necroforger/dgrouter/exrouter"
  12. "github.com/bwmarrin/discordgo"
  13. "github.com/fatih/color"
  14. "github.com/hako/durafmt"
  15. "github.com/kennygrant/sanitize"
  16. )
  17. // TODO: Implement this for more?
  18. const (
  19. cmderrLackingBotAdminPerms = "You do not have permission to use this command. Your User ID must be set as a bot administrator in the settings file."
  20. cmderrSendFailure = "Failed to send command message (requested by %s)...\t%s"
  21. )
  22. func safeReply(ctx *exrouter.Context, content string) bool {
  23. if hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  24. if _, err := ctx.Reply(content); err != nil {
  25. log.Println(lg("Command", "", color.HiRedString, cmderrSendFailure, getUserIdentifier(*ctx.Msg.Author), err))
  26. return false
  27. } else {
  28. return true
  29. }
  30. } else {
  31. log.Println(lg("Command", "", color.HiRedString, fmtBotSendPerm, ctx.Msg.ChannelID))
  32. return false
  33. }
  34. }
  35. // TODO: function for handling perm error messages, etc etc to reduce clutter
  36. func handleCommands() *exrouter.Route {
  37. router := exrouter.New()
  38. //#region Utility Commands
  39. go router.On("ping", func(ctx *exrouter.Context) {
  40. if isCommandableChannel(ctx.Msg) {
  41. if !hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  42. log.Println(lg("Command", "Ping", color.HiRedString, fmtBotSendPerm, ctx.Msg.ChannelID))
  43. } else {
  44. beforePong := time.Now()
  45. pong, err := ctx.Reply("Pong!")
  46. if err != nil {
  47. log.Println(lg("Command", "Ping", color.HiRedString, "Error sending pong message:\t%s", err))
  48. } else {
  49. afterPong := time.Now()
  50. latency := bot.HeartbeatLatency().Milliseconds()
  51. roundtrip := afterPong.Sub(beforePong).Milliseconds()
  52. mention := ctx.Msg.Author.Mention()
  53. content := fmt.Sprintf("**Latency:** ``%dms`` — **Roundtrip:** ``%dms``",
  54. latency,
  55. roundtrip,
  56. )
  57. if pong != nil {
  58. if selfbot {
  59. bot.ChannelMessageEdit(pong.ChannelID, pong.ID, fmt.Sprintf("%s **Command — Ping**\n\n%s", mention, content))
  60. } else {
  61. bot.ChannelMessageEditComplex(&discordgo.MessageEdit{
  62. ID: pong.ID,
  63. Channel: pong.ChannelID,
  64. Content: &mention,
  65. Embed: buildEmbed(ctx.Msg.ChannelID, "Command — Ping", content),
  66. })
  67. }
  68. }
  69. // Log
  70. log.Println(lg("Command", "Ping", color.HiCyanString, "%s pinged bot - Latency: %dms, Roundtrip: %dms",
  71. getUserIdentifier(*ctx.Msg.Author),
  72. latency,
  73. roundtrip),
  74. )
  75. }
  76. }
  77. }
  78. }).Cat("Utility").Alias("test").Desc("Pings the bot")
  79. go router.On("help", func(ctx *exrouter.Context) {
  80. if isCommandableChannel(ctx.Msg) {
  81. if !hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  82. log.Println(lg("Command", "Help", color.HiRedString, fmtBotSendPerm, ctx.Msg.ChannelID))
  83. } else {
  84. content := ""
  85. for _, cmd := range router.Routes {
  86. if cmd.Category != "Admin" || isBotAdmin(ctx.Msg) {
  87. content += fmt.Sprintf("• \"%s\" : %s",
  88. cmd.Name,
  89. cmd.Description,
  90. )
  91. if len(cmd.Aliases) > 0 {
  92. content += fmt.Sprintf("\n— Aliases: \"%s\"", strings.Join(cmd.Aliases, "\", \""))
  93. }
  94. content += "\n\n"
  95. }
  96. }
  97. if _, err := replyEmbed(ctx.Msg, "Command — Help",
  98. fmt.Sprintf("Use commands as ``\"%s<command> <arguments?>\"``\n```%s```\n%s",
  99. config.CommandPrefix, content, projectRepoURL)); err != nil {
  100. log.Println(lg("Command", "Help", color.HiRedString, cmderrSendFailure, getUserIdentifier(*ctx.Msg.Author), err))
  101. }
  102. log.Println(lg("Command", "Help", color.HiCyanString, "%s asked for help", getUserIdentifier(*ctx.Msg.Author)))
  103. }
  104. }
  105. }).Cat("Utility").Alias("commands").Desc("Outputs this help menu")
  106. //#endregion
  107. //#region Info Commands
  108. go router.On("status", func(ctx *exrouter.Context) {
  109. if isCommandableChannel(ctx.Msg) {
  110. if !hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  111. log.Println(lg("Command", "Status", color.HiRedString, fmtBotSendPerm, ctx.Msg.ChannelID))
  112. } else {
  113. message := fmt.Sprintf("• **Uptime —** %s\n"+
  114. "• **Started at —** %s\n"+
  115. "• **Joined Servers —** %d\n"+
  116. "• **Bound Channels —** %d\n"+
  117. "• **Bound Cagetories —** %d\n"+
  118. "• **Bound Servers —** %d\n"+
  119. "• **Bound Users —** %d\n"+
  120. "• **Admin Channels —** %d\n"+
  121. "• **Heartbeat Latency —** %dms",
  122. durafmt.Parse(time.Since(startTime)).String(),
  123. startTime.Format("03:04:05pm on Monday, January 2, 2006 (MST)"),
  124. len(bot.State.Guilds),
  125. getBoundChannelsCount(),
  126. getBoundCategoriesCount(),
  127. getBoundServersCount(),
  128. getBoundUsersCount(),
  129. len(config.AdminChannels),
  130. bot.HeartbeatLatency().Milliseconds(),
  131. )
  132. if sourceConfig := getSource(ctx.Msg); sourceConfig != emptyConfig {
  133. configJson, _ := json.MarshalIndent(sourceConfig, "", "\t")
  134. message = message + fmt.Sprintf("\n• **Channel Settings...** ```%s```", string(configJson))
  135. }
  136. if _, err := replyEmbed(ctx.Msg, "Command — Status", message); err != nil {
  137. log.Println(lg("Command", "Status", color.HiRedString, cmderrSendFailure, getUserIdentifier(*ctx.Msg.Author), err))
  138. }
  139. log.Println(lg("Command", "Status", color.HiCyanString, "%s requested status report", getUserIdentifier(*ctx.Msg.Author)))
  140. }
  141. }
  142. }).Cat("Info").Desc("Displays info regarding the current status of the bot")
  143. go router.On("stats", func(ctx *exrouter.Context) {
  144. if isCommandableChannel(ctx.Msg) {
  145. if !hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  146. log.Println(lg("Command", "Stats", color.HiRedString, fmtBotSendPerm, ctx.Msg.ChannelID))
  147. } else {
  148. if sourceConfig := getSource(ctx.Msg); sourceConfig != emptyConfig {
  149. if *sourceConfig.AllowCommands {
  150. content := fmt.Sprintf("• **Total Downloads —** %s\n"+
  151. "• **Downloads in this Channel —** %s",
  152. formatNumber(int64(dbDownloadCount())),
  153. formatNumber(int64(dbDownloadCountByChannel(ctx.Msg.ChannelID))),
  154. )
  155. //TODO: Count in channel by users
  156. if _, err := replyEmbed(ctx.Msg, "Command — Stats", content); err != nil {
  157. log.Println(lg("Command", "Stats", color.HiRedString, cmderrSendFailure,
  158. getUserIdentifier(*ctx.Msg.Author), err))
  159. }
  160. log.Println(lg("Command", "Stats", color.HiCyanString, "%s requested stats",
  161. getUserIdentifier(*ctx.Msg.Author)))
  162. }
  163. }
  164. }
  165. }
  166. }).Cat("Info").Desc("Outputs statistics regarding this channel")
  167. go router.On("info", func(ctx *exrouter.Context) {
  168. if isCommandableChannel(ctx.Msg) {
  169. if !hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  170. log.Println(lg("Command", "Info", color.HiRedString, fmtBotSendPerm, ctx.Msg.ChannelID))
  171. } else {
  172. content := fmt.Sprintf("Here is some useful info...\n\n"+
  173. "• **Your User ID —** `%s`\n"+
  174. "• **Bots User ID —** `%s`\n"+
  175. "• **This Channel ID —** `%s`\n"+
  176. "• **This Server ID —** `%s`\n\n"+
  177. "• **Versions —** `%s, discordgo v%s (modified), Discord API v%s`"+
  178. "\n\nRemember to remove any spaces when copying to settings.",
  179. ctx.Msg.Author.ID, botUser.ID, ctx.Msg.ChannelID, ctx.Msg.GuildID, runtime.Version(), discordgo.VERSION, discordgo.APIVersion)
  180. if _, err := replyEmbed(ctx.Msg, "Command — Info", content); err != nil {
  181. log.Println(lg("Command", "Info", color.HiRedString, cmderrSendFailure, getUserIdentifier(*ctx.Msg.Author), err))
  182. }
  183. log.Println(lg("Command", "Info", color.HiCyanString, "%s requested info", getUserIdentifier(*ctx.Msg.Author)))
  184. }
  185. }
  186. }).Cat("Info").Alias("debug").Desc("Displays info regarding Discord IDs")
  187. //#endregion
  188. //#region Admin Commands
  189. go router.On("history", func(ctx *exrouter.Context) {
  190. if isCommandableChannel(ctx.Msg) {
  191. // Vars
  192. var all = false
  193. var channels []string
  194. var shouldAbort bool = false
  195. var shouldProcess bool = true
  196. var shouldWipeDB bool = false
  197. var shouldWipeCache bool = false
  198. var before string
  199. var beforeID string
  200. var since string
  201. var sinceID string
  202. //#region Parse Args
  203. for argKey, argValue := range ctx.Args {
  204. if argKey == 0 { // skip head
  205. continue
  206. }
  207. //SUBCOMMAND: cancel
  208. if strings.Contains(strings.ToLower(argValue), "cancel") ||
  209. strings.Contains(strings.ToLower(argValue), "stop") {
  210. shouldAbort = true
  211. } else if strings.Contains(strings.ToLower(argValue), "dbwipe") ||
  212. strings.Contains(strings.ToLower(argValue), "wipedb") { //SUBCOMMAND: dbwipe
  213. shouldProcess = false
  214. shouldWipeDB = true
  215. } else if strings.Contains(strings.ToLower(argValue), "cachewipe") ||
  216. strings.Contains(strings.ToLower(argValue), "wipecache") { //SUBCOMMAND: cachewipe
  217. shouldProcess = false
  218. shouldWipeCache = true
  219. } else if strings.Contains(strings.ToLower(argValue), "help") ||
  220. strings.Contains(strings.ToLower(argValue), "info") { //SUBCOMMAND: help
  221. shouldProcess = false
  222. if hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  223. //content := fmt.Sprintf("")
  224. _, err := replyEmbed(ctx.Msg, "Command — History Help", "TODO: this")
  225. if err != nil {
  226. log.Println(lg("Command", "History",
  227. color.HiRedString, cmderrSendFailure, getUserIdentifier(*ctx.Msg.Author), err))
  228. }
  229. } else {
  230. log.Println(lg("Command", "History", color.HiRedString, fmtBotSendPerm, ctx.Msg.ChannelID))
  231. }
  232. log.Println(lg("Command", "History", color.CyanString, "%s requested history help.", getUserIdentifier(*ctx.Msg.Author)))
  233. } else if strings.Contains(strings.ToLower(argValue), "list") ||
  234. strings.Contains(strings.ToLower(argValue), "status") ||
  235. strings.Contains(strings.ToLower(argValue), "output") { //SUBCOMMAND: list
  236. shouldProcess = false
  237. //MARKER: history jobs list
  238. // 1st
  239. output := fmt.Sprintf("**CURRENT HISTORY JOBS** ~ `%d total, %d running",
  240. historyJobCnt, historyJobCntRunning)
  241. outputC := fmt.Sprintf("CURRENT HISTORY JOBS ~ %d total, %d running",
  242. historyJobCnt, historyJobCntRunning)
  243. if historyJobCntCompleted > 0 {
  244. t := fmt.Sprintf(", %d completed", historyJobCntCompleted)
  245. output += t
  246. outputC += t
  247. }
  248. if historyJobCntWaiting > 0 {
  249. t := fmt.Sprintf(", %d waiting", historyJobCntWaiting)
  250. output += t
  251. outputC += t
  252. }
  253. if historyJobCntAborted > 0 {
  254. t := fmt.Sprintf(", %d cancelled", historyJobCntAborted)
  255. output += t
  256. outputC += t
  257. }
  258. if historyJobCntErrored > 0 {
  259. t := fmt.Sprintf(", %d failed", historyJobCntErrored)
  260. output += t
  261. outputC += t
  262. }
  263. safeReply(ctx, output+"`")
  264. log.Println(lg("Command", "History", color.HiCyanString, outputC))
  265. // Following
  266. output = ""
  267. for pair := historyJobs.Oldest(); pair != nil; pair = pair.Next() {
  268. channelID := pair.Key
  269. job := pair.Value
  270. jobSourceName, jobChannelName := channelDisplay(channelID)
  271. newline := fmt.Sprintf("• _%s_ (%s) `%s - %s`, `updated %s ago, added %s ago`\n",
  272. historyStatusLabel(job.Status), job.OriginUser, jobSourceName, jobChannelName,
  273. shortenTime(durafmt.ParseShort(time.Since(job.Updated)).String()),
  274. shortenTime(durafmt.ParseShort(time.Since(job.Added)).String()))
  275. redothismath: // bad way but dont care right now
  276. if len(output)+len(newline) > limitMsg {
  277. // send batch
  278. safeReply(ctx, output)
  279. output = ""
  280. goto redothismath
  281. }
  282. output += newline
  283. log.Println(lg("Command", "History", color.HiCyanString,
  284. fmt.Sprintf("%s (%s) %s - %s, updated %s ago, added %s ago",
  285. historyStatusLabel(job.Status), job.OriginUser, jobSourceName, jobChannelName,
  286. shortenTime(durafmt.ParseShort(time.Since(job.Updated)).String()),
  287. shortenTime(durafmt.ParseShort(time.Since(job.Added)).String())))) // no batching
  288. }
  289. // finish off
  290. if output != "" {
  291. safeReply(ctx, output)
  292. }
  293. // done
  294. log.Println(lg("Command", "History", color.HiRedString, "%s requested statuses of history jobs.",
  295. getUserIdentifier(*ctx.Msg.Author)))
  296. } else if strings.Contains(strings.ToLower(argValue), "--before=") { // before key
  297. before = strings.ReplaceAll(strings.ToLower(argValue), "--before=", "")
  298. if isDate(before) {
  299. beforeID = discordTimestampToSnowflake("2006-01-02", before)
  300. } else if isNumeric(before) {
  301. beforeID = before
  302. }
  303. if config.Debug {
  304. log.Println(lg("Command", "History", color.CyanString, "Date before range applied, snowflake %s, converts back to %s",
  305. beforeID, discordSnowflakeToTimestamp(beforeID, "2006-01-02T15:04:05.000Z07:00")))
  306. }
  307. } else if strings.Contains(strings.ToLower(argValue), "--since=") { // since key
  308. since = strings.ReplaceAll(strings.ToLower(argValue), "--since=", "")
  309. if isDate(since) {
  310. sinceID = discordTimestampToSnowflake("2006-01-02", since)
  311. } else if isNumeric(since) {
  312. sinceID = since
  313. }
  314. if config.Debug {
  315. log.Println(lg("Command", "History", color.CyanString, "Date since range applied, snowflake %s, converts back to %s",
  316. sinceID, discordSnowflakeToTimestamp(sinceID, "2006-01-02T15:04:05.000Z07:00")))
  317. }
  318. } else {
  319. // Actual Source ID(s)
  320. targets := strings.Split(ctx.Args.Get(argKey), ",")
  321. for _, target := range targets {
  322. if isNumeric(target) {
  323. // Test/Use if number is guild
  324. guild, err := bot.State.Guild(target)
  325. if err == nil {
  326. if config.Debug {
  327. log.Println(lg("Command", "History", color.YellowString,
  328. "Specified target %s is a guild: \"%s\", adding all channels...",
  329. target, guild.Name))
  330. }
  331. for _, ch := range guild.Channels {
  332. channels = append(channels, ch.ID)
  333. if config.Debug {
  334. log.Println(lg("Command", "History", color.YellowString,
  335. "Added %s (#%s in \"%s\") to history queue",
  336. ch.ID, ch.Name, guild.Name))
  337. }
  338. }
  339. } else { // Test/Use if number is channel
  340. ch, err := bot.State.Channel(target)
  341. if err == nil {
  342. channels = append(channels, target)
  343. if config.Debug {
  344. log.Println(lg("Command", "History", color.YellowString, "Added %s (#%s in %s) to history queue",
  345. ch.ID, ch.Name, ch.GuildID))
  346. }
  347. }
  348. }
  349. } else if strings.Contains(strings.ToLower(target), "all") {
  350. channels = getAllRegisteredChannels()
  351. all = true
  352. }
  353. }
  354. }
  355. }
  356. //#endregion
  357. // Local
  358. if len(channels) == 0 {
  359. channels = append(channels, ctx.Msg.ChannelID)
  360. }
  361. // Foreach Channel
  362. for _, channel := range channels {
  363. //#region Process Channels
  364. if shouldProcess && config.Debug {
  365. nameGuild := channel
  366. if chinfo, err := bot.State.Channel(channel); err == nil {
  367. nameGuild = getGuildName(chinfo.GuildID)
  368. }
  369. nameCategory := getChannelCategoryName(channel)
  370. nameChannel := getChannelName(channel)
  371. nameDisplay := fmt.Sprintf("%s / %s", nameGuild, nameChannel)
  372. if nameCategory != "unknown" {
  373. nameDisplay = fmt.Sprintf("%s / %s / %s", nameGuild, nameCategory, nameChannel)
  374. }
  375. log.Println(lg("Command", "History", color.HiMagentaString,
  376. "Queueing history job for \"%s\"\t\t(%s) ...", nameDisplay, channel))
  377. }
  378. if !isBotAdmin(ctx.Msg) {
  379. log.Println(lg("Command", "History", color.CyanString,
  380. "%s tried to handle history for %s but lacked proper permission.",
  381. getUserIdentifier(*ctx.Msg.Author), channel))
  382. if !hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  383. log.Println(lg("Command", "History", color.HiRedString, fmtBotSendPerm, channel))
  384. } else {
  385. if _, err := replyEmbed(ctx.Msg, "Command — History", cmderrLackingBotAdminPerms); err != nil {
  386. log.Println(lg("Command", "History", color.HiRedString, cmderrSendFailure,
  387. getUserIdentifier(*ctx.Msg.Author), err))
  388. }
  389. }
  390. } else { // IS BOT ADMIN
  391. if shouldProcess { // PROCESS TREE; MARKER: history queue via cmd
  392. if shouldAbort { // ABORT
  393. if job, exists := historyJobs.Get(channel); exists &&
  394. (job.Status == historyStatusRunning || job.Status == historyStatusWaiting) {
  395. // DOWNLOADING, ABORTING
  396. job.Status = historyStatusAbortRequested
  397. if job.Status == historyStatusWaiting {
  398. job.Status = historyStatusAbortCompleted
  399. }
  400. historyJobs.Set(channel, job)
  401. log.Println(lg("Command", "History", color.CyanString,
  402. "%s cancelled history cataloging for \"%s\"",
  403. getUserIdentifier(*ctx.Msg.Author), channel))
  404. } else { // NOT DOWNLOADING, ABORTING
  405. log.Println(lg("Command", "History", color.CyanString,
  406. "%s tried to cancel history for \"%s\" but it's not running",
  407. getUserIdentifier(*ctx.Msg.Author), channel))
  408. }
  409. } else { // RUN
  410. if job, exists := historyJobs.Get(channel); !exists ||
  411. (job.Status != historyStatusRunning && job.Status != historyStatusAbortRequested) {
  412. job.Status = historyStatusWaiting
  413. job.OriginChannel = ctx.Msg.ChannelID
  414. job.OriginUser = getUserIdentifier(*ctx.Msg.Author)
  415. job.TargetCommandingMessage = ctx.Msg
  416. job.TargetChannelID = channel
  417. job.TargetBefore = beforeID
  418. job.TargetSince = sinceID
  419. job.Updated = time.Now()
  420. job.Added = time.Now()
  421. historyJobs.Set(channel, job)
  422. } else { // ALREADY RUNNING
  423. log.Println(lg("Command", "History", color.CyanString,
  424. "%s tried using history command but history is already running for %s...",
  425. getUserIdentifier(*ctx.Msg.Author), channel))
  426. }
  427. }
  428. }
  429. if shouldWipeDB {
  430. if all {
  431. myDB.Close()
  432. time.Sleep(1 * time.Second)
  433. if _, err := os.Stat(databasePath); err == nil {
  434. err = os.RemoveAll(databasePath)
  435. if err != nil {
  436. log.Println(lg("Command", "History", color.HiRedString,
  437. "Encountered error deleting database folder:\t%s", err))
  438. } else {
  439. log.Println(lg("Command", "History", color.HiGreenString,
  440. "Deleted database."))
  441. }
  442. time.Sleep(1 * time.Second)
  443. mainWg.Add(1)
  444. go openDatabase()
  445. break
  446. } else {
  447. log.Println(lg("Command", "History", color.HiRedString,
  448. "Database folder inaccessible:\t%s", err))
  449. }
  450. } else {
  451. dbDeleteByChannelID(channel)
  452. }
  453. }
  454. if shouldWipeCache {
  455. if all {
  456. if _, err := os.Stat(historyCachePath); err == nil {
  457. err = os.RemoveAll(historyCachePath)
  458. if err != nil {
  459. log.Println(lg("Command", "History", color.HiRedString,
  460. "Encountered error deleting database folder:\t%s", err))
  461. } else {
  462. log.Println(lg("Command", "History", color.HiGreenString,
  463. "Deleted database."))
  464. break
  465. }
  466. } else {
  467. log.Println(lg("Command", "History", color.HiRedString,
  468. "Cache folder inaccessible:\t%s", err))
  469. }
  470. } else {
  471. deleteHistoryCache := func(dirpath string) {
  472. fp := dirpath + string(os.PathSeparator) + channel
  473. if _, err := os.Stat(fp); err == nil {
  474. err = os.RemoveAll(fp)
  475. if err != nil {
  476. log.Println(lg("Debug", "History", color.HiRedString,
  477. "Encountered error deleting cache file for %s:\t%s", channel, err))
  478. } else {
  479. log.Println(lg("Debug", "History", color.HiGreenString,
  480. "Deleted cache file for %s.", channel))
  481. }
  482. } else {
  483. log.Println(lg("Command", "History", color.HiRedString,
  484. "Cache folder inaccessible:\t%s", err))
  485. }
  486. }
  487. deleteHistoryCache(historyCacheBefore)
  488. deleteHistoryCache(historyCacheSince)
  489. }
  490. }
  491. }
  492. //#endregion
  493. }
  494. if shouldWipeDB {
  495. cachedDownloadID = dbDownloadCount()
  496. }
  497. }
  498. }).Cat("Admin").Alias("catalog", "cache").Desc("Catalogs history for this channel")
  499. go router.On("exit", func(ctx *exrouter.Context) {
  500. if isCommandableChannel(ctx.Msg) {
  501. if isBotAdmin(ctx.Msg) {
  502. if !hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  503. log.Println(lg("Command", "Exit", color.HiRedString, fmtBotSendPerm, ctx.Msg.ChannelID))
  504. } else {
  505. if _, err := replyEmbed(ctx.Msg, "Command — Exit", "Exiting program..."); err != nil {
  506. log.Println(lg("Command", "Exit", color.HiRedString,
  507. cmderrSendFailure, getUserIdentifier(*ctx.Msg.Author), err))
  508. }
  509. }
  510. log.Println(lg("Command", "Exit", color.HiCyanString,
  511. "%s (bot admin) requested exit, goodbye...",
  512. getUserIdentifier(*ctx.Msg.Author)))
  513. properExit()
  514. } else {
  515. if !hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  516. log.Println(lg("Command", "Exit", color.HiRedString, fmtBotSendPerm, ctx.Msg.ChannelID))
  517. } else {
  518. if _, err := replyEmbed(ctx.Msg, "Command — Exit", cmderrLackingBotAdminPerms); err != nil {
  519. log.Println(lg("Command", "Exit", color.HiRedString,
  520. cmderrSendFailure, getUserIdentifier(*ctx.Msg.Author), err))
  521. }
  522. }
  523. log.Println(lg("Command", "Exit", color.HiCyanString,
  524. "%s tried to exit but lacked bot admin perms.", getUserIdentifier(*ctx.Msg.Author)))
  525. }
  526. }
  527. }).Cat("Admin").Alias("reload", "kill").Desc("Kills the bot")
  528. go router.On("emojis", func(ctx *exrouter.Context) {
  529. if isCommandableChannel(ctx.Msg) {
  530. if isBotAdmin(ctx.Msg) {
  531. if hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  532. // Determine which guild(s)
  533. guilds := []string{ctx.Msg.GuildID} // default to origin
  534. if args := ctx.Args.After(1); args != "" { // specifics
  535. guilds = nil
  536. _guilds := strings.Split(args, ",")
  537. if len(_guilds) > 0 {
  538. for _, guild := range _guilds {
  539. guild = strings.TrimSpace(guild)
  540. guilds = append(guilds, guild)
  541. }
  542. }
  543. }
  544. for _, guild := range guilds {
  545. i := 0
  546. s := 0
  547. guildName := guild
  548. guildNameO := guild
  549. if guildInfo, err := bot.Guild(guild); err == nil {
  550. guildName = sanitize.Name(guildInfo.Name)
  551. guildNameO = guildInfo.Name
  552. }
  553. destination := "emojis" + string(os.PathSeparator) + guildName + string(os.PathSeparator)
  554. if err = os.MkdirAll(destination, 0755); err != nil {
  555. log.Println(lg("Command", "Emojis", color.HiRedString, "Error while creating destination folder \"%s\": %s", destination, err))
  556. } else {
  557. emojis, err := bot.GuildEmojis(guild)
  558. if err != nil {
  559. log.Println(lg("Command", "Emojis", color.HiRedString, "Failed to get server emojis:\t%s", err))
  560. } else {
  561. for _, emoji := range emojis {
  562. var message discordgo.Message
  563. message.ChannelID = ctx.Msg.ChannelID
  564. url := "https://cdn.discordapp.com/emojis/" + emoji.ID
  565. status, _ := downloadRequestStruct{
  566. InputURL: url,
  567. Filename: emoji.ID,
  568. Path: destination,
  569. Message: &message,
  570. FileTime: time.Now(),
  571. HistoryCmd: false,
  572. EmojiCmd: true,
  573. StartTime: time.Now(),
  574. }.handleDownload()
  575. if status.Status == downloadSuccess {
  576. i++
  577. } else {
  578. s++
  579. log.Println(lg("Command", "Emojis", color.HiRedString,
  580. "Failed to download emoji \"%s\": \t[%d - %s] %v",
  581. url, status.Status, getDownloadStatusString(status.Status), status.Error))
  582. }
  583. }
  584. destinationOut := destination
  585. abs, err := filepath.Abs(destination)
  586. if err == nil {
  587. destinationOut = abs
  588. }
  589. _, err = replyEmbed(ctx.Msg, "Command — Emojis",
  590. fmt.Sprintf("`%d` emojis downloaded, `%d` skipped or failed\n• Destination: `%s`\n• Server: `%s`",
  591. i, s, destinationOut, guildNameO,
  592. ),
  593. )
  594. if err != nil {
  595. log.Println(lg("Command", "Emojis", color.HiRedString,
  596. "Failed to send status message for emoji downloads:\t%s", err))
  597. }
  598. }
  599. }
  600. }
  601. }
  602. } else {
  603. if !hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  604. log.Println(lg("Command", "Emojis", color.HiRedString, fmtBotSendPerm, ctx.Msg.ChannelID))
  605. } else {
  606. if _, err := replyEmbed(ctx.Msg, "Command — Emojis", cmderrLackingBotAdminPerms); err != nil {
  607. log.Println(lg("Command", "Emojis", color.HiRedString, cmderrSendFailure, getUserIdentifier(*ctx.Msg.Author), err))
  608. }
  609. }
  610. log.Println(lg("Command", "Emojis", color.HiCyanString,
  611. "%s tried to download emojis but lacked bot admin perms.", getUserIdentifier(*ctx.Msg.Author)))
  612. }
  613. }
  614. }).Cat("Admin").Desc("Saves all server emojis to download destination")
  615. //#endregion
  616. // Handler for Command Router
  617. go bot.AddHandler(func(_ *discordgo.Session, m *discordgo.MessageCreate) {
  618. //NOTE: This setup makes it case-insensitive but message content will be lowercase, currently case sensitivity is not necessary.
  619. router.FindAndExecute(bot, strings.ToLower(config.CommandPrefix), bot.State.User.ID, messageToLower(m.Message))
  620. })
  621. return router
  622. }