commands.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  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, nil); 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, nil); 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. } else {
  348. // Category
  349. for _, guild := range bot.State.Guilds {
  350. for _, ch := range guild.Channels {
  351. if ch.ParentID == target {
  352. channels = append(channels, ch.ID)
  353. if config.Debug {
  354. log.Println(lg("Command", "History", color.YellowString, "Added %s (#%s in %s) to history queue",
  355. ch.ID, ch.Name, ch.GuildID))
  356. }
  357. }
  358. }
  359. }
  360. }
  361. }
  362. } else if strings.Contains(strings.ToLower(target), "all") {
  363. channels = getAllRegisteredChannels()
  364. all = true
  365. }
  366. }
  367. }
  368. }
  369. //#endregion
  370. // Local
  371. if len(channels) == 0 {
  372. channels = append(channels, ctx.Msg.ChannelID)
  373. }
  374. // Foreach Channel
  375. for _, channel := range channels {
  376. //#region Process Channels
  377. if shouldProcess && config.Debug {
  378. nameGuild := channel
  379. if chinfo, err := bot.State.Channel(channel); err == nil {
  380. nameGuild = getGuildName(chinfo.GuildID)
  381. }
  382. nameCategory := getChannelCategoryName(channel)
  383. nameChannel := getChannelName(channel, nil)
  384. nameDisplay := fmt.Sprintf("%s / %s", nameGuild, nameChannel)
  385. if nameCategory != "unknown" {
  386. nameDisplay = fmt.Sprintf("%s / %s / %s", nameGuild, nameCategory, nameChannel)
  387. }
  388. log.Println(lg("Command", "History", color.HiMagentaString,
  389. "Queueing history job for \"%s\"\t\t(%s) ...", nameDisplay, channel))
  390. }
  391. if !isBotAdmin(ctx.Msg) {
  392. log.Println(lg("Command", "History", color.CyanString,
  393. "%s tried to handle history for %s but lacked proper permission.",
  394. getUserIdentifier(*ctx.Msg.Author), channel))
  395. if !hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  396. log.Println(lg("Command", "History", color.HiRedString, fmtBotSendPerm, channel))
  397. } else {
  398. if _, err := replyEmbed(ctx.Msg, "Command — History", cmderrLackingBotAdminPerms); err != nil {
  399. log.Println(lg("Command", "History", color.HiRedString, cmderrSendFailure,
  400. getUserIdentifier(*ctx.Msg.Author), err))
  401. }
  402. }
  403. } else { // IS BOT ADMIN
  404. if shouldProcess { // PROCESS TREE; MARKER: history queue via cmd
  405. if shouldAbort { // ABORT
  406. if job, exists := historyJobs.Get(channel); exists &&
  407. (job.Status == historyStatusRunning || job.Status == historyStatusWaiting) {
  408. // DOWNLOADING, ABORTING
  409. job.Status = historyStatusAbortRequested
  410. if job.Status == historyStatusWaiting {
  411. job.Status = historyStatusAbortCompleted
  412. }
  413. historyJobs.Set(channel, job)
  414. log.Println(lg("Command", "History", color.CyanString,
  415. "%s cancelled history cataloging for \"%s\"",
  416. getUserIdentifier(*ctx.Msg.Author), channel))
  417. } else { // NOT DOWNLOADING, ABORTING
  418. log.Println(lg("Command", "History", color.CyanString,
  419. "%s tried to cancel history for \"%s\" but it's not running",
  420. getUserIdentifier(*ctx.Msg.Author), channel))
  421. }
  422. } else { // RUN
  423. if job, exists := historyJobs.Get(channel); !exists ||
  424. (job.Status != historyStatusRunning && job.Status != historyStatusAbortRequested) {
  425. job.Status = historyStatusWaiting
  426. job.OriginChannel = ctx.Msg.ChannelID
  427. job.OriginUser = getUserIdentifier(*ctx.Msg.Author)
  428. job.TargetCommandingMessage = ctx.Msg
  429. job.TargetChannelID = channel
  430. job.TargetBefore = beforeID
  431. job.TargetSince = sinceID
  432. job.Updated = time.Now()
  433. job.Added = time.Now()
  434. historyJobs.Set(channel, job)
  435. } else { // ALREADY RUNNING
  436. log.Println(lg("Command", "History", color.CyanString,
  437. "%s tried using history command but history is already running for %s...",
  438. getUserIdentifier(*ctx.Msg.Author), channel))
  439. }
  440. }
  441. }
  442. if shouldWipeDB {
  443. if all {
  444. myDB.Close()
  445. time.Sleep(1 * time.Second)
  446. if _, err := os.Stat(pathDatabaseBase); err == nil {
  447. err = os.RemoveAll(pathDatabaseBase)
  448. if err != nil {
  449. log.Println(lg("Command", "History", color.HiRedString,
  450. "Encountered error deleting database folder:\t%s", err))
  451. } else {
  452. log.Println(lg("Command", "History", color.HiGreenString,
  453. "Deleted database."))
  454. }
  455. time.Sleep(1 * time.Second)
  456. mainWg.Add(1)
  457. go openDatabase()
  458. break
  459. } else {
  460. log.Println(lg("Command", "History", color.HiRedString,
  461. "Database folder inaccessible:\t%s", err))
  462. }
  463. } else {
  464. dbDeleteByChannelID(channel)
  465. }
  466. }
  467. if shouldWipeCache {
  468. if all {
  469. if _, err := os.Stat(pathCacheHistory); err == nil {
  470. err = os.RemoveAll(pathCacheHistory)
  471. if err != nil {
  472. log.Println(lg("Command", "History", color.HiRedString,
  473. "Encountered error deleting database folder:\t%s", err))
  474. } else {
  475. log.Println(lg("Command", "History", color.HiGreenString,
  476. "Deleted database."))
  477. break
  478. }
  479. } else {
  480. log.Println(lg("Command", "History", color.HiRedString,
  481. "Cache folder inaccessible:\t%s", err))
  482. }
  483. } else {
  484. fp := pathCacheHistory + string(os.PathSeparator) + channel + ".json"
  485. if _, err := os.Stat(fp); err == nil {
  486. err = os.RemoveAll(fp)
  487. if err != nil {
  488. log.Println(lg("Debug", "History", color.HiRedString,
  489. "Encountered error deleting cache file for %s:\t%s", channel, err))
  490. } else {
  491. log.Println(lg("Debug", "History", color.HiGreenString,
  492. "Deleted cache file for %s.", channel))
  493. }
  494. } else {
  495. log.Println(lg("Command", "History", color.HiRedString,
  496. "Cache folder inaccessible:\t%s", err))
  497. }
  498. }
  499. }
  500. }
  501. //#endregion
  502. }
  503. if shouldWipeDB {
  504. cachedDownloadID = dbDownloadCount()
  505. }
  506. }
  507. }).Cat("Admin").Alias("catalog", "cache").Desc("Catalogs history for this channel")
  508. go router.On("exit", func(ctx *exrouter.Context) {
  509. if isCommandableChannel(ctx.Msg) {
  510. if isBotAdmin(ctx.Msg) {
  511. if !hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  512. log.Println(lg("Command", "Exit", color.HiRedString, fmtBotSendPerm, ctx.Msg.ChannelID))
  513. } else {
  514. if _, err := replyEmbed(ctx.Msg, "Command — Exit", "Exiting program..."); err != nil {
  515. log.Println(lg("Command", "Exit", color.HiRedString,
  516. cmderrSendFailure, getUserIdentifier(*ctx.Msg.Author), err))
  517. }
  518. }
  519. log.Println(lg("Command", "Exit", color.HiCyanString,
  520. "%s (bot admin) requested exit, goodbye...",
  521. getUserIdentifier(*ctx.Msg.Author)))
  522. properExit()
  523. } else {
  524. if !hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  525. log.Println(lg("Command", "Exit", color.HiRedString, fmtBotSendPerm, ctx.Msg.ChannelID))
  526. } else {
  527. if _, err := replyEmbed(ctx.Msg, "Command — Exit", cmderrLackingBotAdminPerms); err != nil {
  528. log.Println(lg("Command", "Exit", color.HiRedString,
  529. cmderrSendFailure, getUserIdentifier(*ctx.Msg.Author), err))
  530. }
  531. }
  532. log.Println(lg("Command", "Exit", color.HiCyanString,
  533. "%s tried to exit but lacked bot admin perms.", getUserIdentifier(*ctx.Msg.Author)))
  534. }
  535. }
  536. }).Cat("Admin").Alias("reload", "kill").Desc("Kills the bot")
  537. go router.On("emojis", func(ctx *exrouter.Context) {
  538. if isCommandableChannel(ctx.Msg) {
  539. if isBotAdmin(ctx.Msg) {
  540. if hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  541. // Determine which guild(s)
  542. guilds := []string{ctx.Msg.GuildID} // default to origin
  543. if args := ctx.Args.After(1); args != "" { // specifics
  544. guilds = nil
  545. _guilds := strings.Split(args, ",")
  546. if len(_guilds) > 0 {
  547. for _, guild := range _guilds {
  548. guild = strings.TrimSpace(guild)
  549. guilds = append(guilds, guild)
  550. }
  551. }
  552. }
  553. for _, guild := range guilds {
  554. i := 0
  555. s := 0
  556. guildName := guild
  557. guildNameO := guild
  558. if guildInfo, err := bot.Guild(guild); err == nil {
  559. guildName = sanitize.Name(guildInfo.Name)
  560. guildNameO = guildInfo.Name
  561. }
  562. destination := "emojis" + string(os.PathSeparator) + guildName + string(os.PathSeparator)
  563. if err = os.MkdirAll(destination, 0755); err != nil {
  564. log.Println(lg("Command", "Emojis", color.HiRedString, "Error while creating destination folder \"%s\": %s", destination, err))
  565. } else {
  566. emojis, err := bot.GuildEmojis(guild)
  567. if err != nil {
  568. log.Println(lg("Command", "Emojis", color.HiRedString, "Failed to get server emojis:\t%s", err))
  569. } else {
  570. for _, emoji := range emojis {
  571. var message discordgo.Message
  572. message.ChannelID = ctx.Msg.ChannelID
  573. url := "https://cdn.discordapp.com/emojis/" + emoji.ID
  574. status, _ := downloadRequestStruct{
  575. InputURL: url,
  576. Filename: emoji.ID,
  577. Path: destination,
  578. Message: &message,
  579. FileTime: time.Now(),
  580. HistoryCmd: false,
  581. EmojiCmd: true,
  582. StartTime: time.Now(),
  583. }.handleDownload()
  584. if status.Status == downloadSuccess {
  585. i++
  586. } else {
  587. s++
  588. log.Println(lg("Command", "Emojis", color.HiRedString,
  589. "Failed to download emoji \"%s\": \t[%d - %s] %v",
  590. url, status.Status, getDownloadStatusString(status.Status), status.Error))
  591. }
  592. }
  593. destinationOut := destination
  594. abs, err := filepath.Abs(destination)
  595. if err == nil {
  596. destinationOut = abs
  597. }
  598. _, err = replyEmbed(ctx.Msg, "Command — Emojis",
  599. fmt.Sprintf("`%d` emojis downloaded, `%d` skipped or failed\n• Destination: `%s`\n• Server: `%s`",
  600. i, s, destinationOut, guildNameO,
  601. ),
  602. )
  603. if err != nil {
  604. log.Println(lg("Command", "Emojis", color.HiRedString,
  605. "Failed to send status message for emoji downloads:\t%s", err))
  606. }
  607. }
  608. }
  609. }
  610. }
  611. } else {
  612. if !hasPerms(ctx.Msg.ChannelID, discordgo.PermissionSendMessages) {
  613. log.Println(lg("Command", "Emojis", color.HiRedString, fmtBotSendPerm, ctx.Msg.ChannelID))
  614. } else {
  615. if _, err := replyEmbed(ctx.Msg, "Command — Emojis", cmderrLackingBotAdminPerms); err != nil {
  616. log.Println(lg("Command", "Emojis", color.HiRedString, cmderrSendFailure, getUserIdentifier(*ctx.Msg.Author), err))
  617. }
  618. }
  619. log.Println(lg("Command", "Emojis", color.HiCyanString,
  620. "%s tried to download emojis but lacked bot admin perms.", getUserIdentifier(*ctx.Msg.Author)))
  621. }
  622. }
  623. }).Cat("Admin").Desc("Saves all server emojis to download destination")
  624. //#endregion
  625. // Handler for Command Router
  626. go bot.AddHandler(func(_ *discordgo.Session, m *discordgo.MessageCreate) {
  627. //NOTE: This setup makes it case-insensitive but message content will be lowercase, currently case sensitivity is not necessary.
  628. router.FindAndExecute(bot, strings.ToLower(config.CommandPrefix), bot.State.User.ID, messageToLower(m.Message))
  629. })
  630. return router
  631. }