Compare commits
	
		
			No commits in common. "main" and "rewrite-go" have entirely different histories.
		
	
	
		
			main
			...
			rewrite-go
		
	
		
					 50 changed files with 3049 additions and 2234 deletions
				
			
		
							
								
								
									
										31
									
								
								.eslintrc.js
									
										
									
									
									
								
							
							
						
						
									
										31
									
								
								.eslintrc.js
									
										
									
									
									
								
							| 
						 | 
					@ -1,31 +0,0 @@
 | 
				
			||||||
const OFF = 0;
 | 
					 | 
				
			||||||
// const WARN = 1;
 | 
					 | 
				
			||||||
const ERROR = 2;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
  extends: ["eslint:recommended"],
 | 
					 | 
				
			||||||
  parserOptions: {
 | 
					 | 
				
			||||||
    ecmaVersion: 2020,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  env: {
 | 
					 | 
				
			||||||
    es6: true,
 | 
					 | 
				
			||||||
    node: true,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  rules: {
 | 
					 | 
				
			||||||
    indent: OFF,
 | 
					 | 
				
			||||||
    semi: ERROR,
 | 
					 | 
				
			||||||
    quotes: [ERROR, "double", {avoidEscape: true, allowTemplateLiterals: true}],
 | 
					 | 
				
			||||||
    "no-empty": ERROR,
 | 
					 | 
				
			||||||
    "array-callback-return": ERROR,
 | 
					 | 
				
			||||||
    "consistent-return": ERROR,
 | 
					 | 
				
			||||||
    eqeqeq: OFF,
 | 
					 | 
				
			||||||
    "prefer-const": ERROR,
 | 
					 | 
				
			||||||
    "no-unused-vars": [ERROR, {args: "none", varsIgnorePattern: "^_"}],
 | 
					 | 
				
			||||||
    "no-console": OFF,
 | 
					 | 
				
			||||||
    "no-debugger": OFF,
 | 
					 | 
				
			||||||
    "require-atomic-updates": OFF,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  globals: {
 | 
					 | 
				
			||||||
    comcord: true,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
							
								
								
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -1 +1,2 @@
 | 
				
			||||||
node_modules/
 | 
					comcord
 | 
				
			||||||
 | 
					comcord.exe
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +0,0 @@
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    "semi": true,
 | 
					 | 
				
			||||||
    "bracketSpacing": false,
 | 
					 | 
				
			||||||
    "endOfLine": "lf"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										21
									
								
								LICENSE
									
										
									
									
									
								
							
							
						
						
									
										21
									
								
								LICENSE
									
										
									
									
									
								
							| 
						 | 
					@ -1,21 +0,0 @@
 | 
				
			||||||
MIT License
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Copyright (c) 2022 Cynthia Foxwell
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					 | 
				
			||||||
in the Software without restriction, including without limitation the rights
 | 
					 | 
				
			||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
					 | 
				
			||||||
copies of the Software, and to permit persons to whom the Software is
 | 
					 | 
				
			||||||
furnished to do so, subject to the following conditions:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The above copyright notice and this permission notice shall be included in all
 | 
					 | 
				
			||||||
copies or substantial portions of the Software.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
					 | 
				
			||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
					 | 
				
			||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
					 | 
				
			||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
					 | 
				
			||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
					 | 
				
			||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
					 | 
				
			||||||
SOFTWARE.
 | 
					 | 
				
			||||||
							
								
								
									
										70
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										70
									
								
								README.md
									
										
									
									
									
								
							| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
# comcord
 | 
					# comcord (`rewrite-go`)
 | 
				
			||||||
A CLI-based client for Discord inspired by [SDF](https://sdf.org)'s [commode](https://sdf.org/?tutorials/comnotirc).
 | 
					A CLI-based client for Discord inspired by [SDF](https://sdf.org)'s [commode](https://sdf.org/?tutorials/comnotirc).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Why?
 | 
					## Why?
 | 
				
			||||||
| 
						 | 
					@ -6,30 +6,13 @@ A CLI-based client for Discord inspired by [SDF](https://sdf.org)'s [commode](ht
 | 
				
			||||||
2. I've been spending more time in commode on SDF and have been accustomed to the experience.
 | 
					2. I've been spending more time in commode on SDF and have been accustomed to the experience.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Usage
 | 
					## Usage
 | 
				
			||||||
1. `pnpm i`
 | 
					TODO
 | 
				
			||||||
2. `node src/index.js <token>`
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Your token will be then stored in `.comcordrc` after the first launch.
 | 
					## Rewrite Design Decisions
 | 
				
			||||||
 | 
					Go is more portable than Node.js
 | 
				
			||||||
### User Accounts
 | 
					 | 
				
			||||||
User accounts are _partially_ supported via `allowUserAccounts=true` in your `.comcordrc`.
 | 
					 | 
				
			||||||
This is use at your own risk, despite spoofing the official client. I am not responsible for any banned accounts.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### Guild members not populating
 | 
					 | 
				
			||||||
This is due to most libraries not implementing Lazy Guilds, as bots do not need lazy guilds to function.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
If you are willing to implement Lazy Guilds based off of [unofficial documentation](https://luna.gitlab.io/discord-unofficial-docs/lazy_guilds.html)
 | 
					 | 
				
			||||||
and my already existing horrible hacks to make user accounts work in the first place, feel free to send a PR (on GitLab, GitHub repo is a read only mirror).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Bot Accounts (prefered)
 | 
					 | 
				
			||||||
You **MUST** grant your bot all Privileged Gateway Intents.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Design Decisions
 | 
					 | 
				
			||||||
- Node.js was chosen currently due to familiarity.
 | 
					 | 
				
			||||||
- Dysnomia was chosen due to familiarity and the nature of everything not being abstracted out to 200 different classes unlike discord.js.
 | 
					 | 
				
			||||||
- "Jank" by design. While I don't expect anyone to actually use comcord on serial terminals or teletypes other than for meme factor, the option is still there.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## TODO
 | 
					## TODO
 | 
				
			||||||
 | 
					- [x] Send mode
 | 
				
			||||||
- [x] Commands
 | 
					- [x] Commands
 | 
				
			||||||
  - [x] Quit (q)
 | 
					  - [x] Quit (q)
 | 
				
			||||||
  - [x] Switch guilds (G)
 | 
					  - [x] Switch guilds (G)
 | 
				
			||||||
| 
						 | 
					@ -38,41 +21,42 @@ You **MUST** grant your bot all Privileged Gateway Intents.
 | 
				
			||||||
  - [x] Emote (e)
 | 
					  - [x] Emote (e)
 | 
				
			||||||
    - Just sends message surrounded in `*`'s
 | 
					    - Just sends message surrounded in `*`'s
 | 
				
			||||||
  - [ ] Finger (f)
 | 
					  - [ ] Finger (f)
 | 
				
			||||||
    - [ ] Shows presence data if available
 | 
					    - Shows presence data if available
 | 
				
			||||||
    - [ ] Creation date, join date, ID, etc
 | 
					    - Creation date, join date, ID, etc
 | 
				
			||||||
  - [x] Room history (r)
 | 
					  - [x] Room history (r)
 | 
				
			||||||
  - [x] Extended room history (R)
 | 
					  - [x] Extended room history (R)
 | 
				
			||||||
 | 
					  - [x] Peek (p)
 | 
				
			||||||
 | 
					  - [x] Cross-guild peek (P)
 | 
				
			||||||
  - [x] List channels (l)
 | 
					  - [x] List channels (l)
 | 
				
			||||||
  - [x] List guilds (L)
 | 
					  - [x] List guilds (L)
 | 
				
			||||||
  - [x] Clear (c)
 | 
					  - [x] Clear (c)
 | 
				
			||||||
  - [ ] Surf channels forwards (>)
 | 
					  - [ ] Surf channels forwards (>)
 | 
				
			||||||
  - [ ] Surf channels backwards (<)
 | 
					  - [ ] Surf channels backwards (<)
 | 
				
			||||||
  - [x] AFK toggle (A)
 | 
					  - [ ] AFK toggle (A)
 | 
				
			||||||
  - [x] Send DM (s)
 | 
					  - [ ] Send DM (s)
 | 
				
			||||||
  - [x] Answer DM (a)
 | 
					  - [ ] Answer DM (a)
 | 
				
			||||||
  - [x] Peek (p)
 | 
					  - [x] Current time (+)
 | 
				
			||||||
 | 
					  - [ ] DM history (TBD)
 | 
				
			||||||
 | 
					  - [ ] Reply to message (TBD)
 | 
				
			||||||
 | 
					  - [ ] Toggle color (z)
 | 
				
			||||||
- [x] Message Receiving
 | 
					- [x] Message Receiving
 | 
				
			||||||
  - [x] Markdown styling
 | 
					  - Markdown styling
 | 
				
			||||||
    - [x] Common markdown (bold, italic, etc)
 | 
					    - [x] Emotes
 | 
				
			||||||
    - [x] Figure out how spoilers would work
 | 
					 | 
				
			||||||
    - [x] Emotes?????
 | 
					 | 
				
			||||||
    - [x] Timestamp parsing
 | 
					    - [x] Timestamp parsing
 | 
				
			||||||
    - [x] Mentions parsing
 | 
					    - [x] Mentions parsing
 | 
				
			||||||
  - [ ] Embeds in the style of commode's posted links
 | 
					  - [ ] Embeds
 | 
				
			||||||
 | 
					    - [ ] Plain links with title = commode's posted links
 | 
				
			||||||
  - [x] Messages wrapped in `*`'s or `_`'s parsed as emotes
 | 
					  - [x] Messages wrapped in `*`'s or `_`'s parsed as emotes
 | 
				
			||||||
  - [x] Inline DMs to replicate commode's private messages
 | 
					  - [x] Inline DMs to replicate commode's private messages
 | 
				
			||||||
  - [x] Replies
 | 
					  - [x] Replies
 | 
				
			||||||
 | 
					  - [ ] Group DMs
 | 
				
			||||||
 | 
					    - [ ] Only works with user accounts, might not even be worth doing
 | 
				
			||||||
- [x] Message sending
 | 
					- [x] Message sending
 | 
				
			||||||
  - [x] Puts incoming messages into queue whilst in send mode
 | 
					  - [x] Puts incoming messages into queue whilst in send mode
 | 
				
			||||||
  - [ ] Mentions
 | 
					  - [x] Send typing
 | 
				
			||||||
  - [ ] Replies
 | 
					  - [ ] Mentioning
 | 
				
			||||||
- [x] Configuration
 | 
					- [x] Configuration
 | 
				
			||||||
 | 
					  - [x] Write token from argv into rc file if rc file doesn't exist
 | 
				
			||||||
  - [x] Default guild/channel
 | 
					  - [x] Default guild/channel
 | 
				
			||||||
    - No way to set in client (yet?), `defaultChannel=` and `defaultGuild=` in your `.comcordrc`.
 | 
					- [ ] Threads/Forums
 | 
				
			||||||
- [ ] Threads
 | 
					- [ ] External rich presence when using bot accounts
 | 
				
			||||||
- [x] Not have the token just be in argv
 | 
					 | 
				
			||||||
- [x] Not have everything in one file
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Repository
 | 
					 | 
				
			||||||
If you're viewing this on GitHub or GitLab, you are viewing a read only mirror.
 | 
					 | 
				
			||||||
The main repository is located on [Gitdab](https://gitdab.com/Cynosphere/comcord) and is push mirrored to the other two.
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								commands/clear.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								commands/clear.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					package commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ClearCommand() {
 | 
				
			||||||
 | 
					  fmt.Print("\n\r\033[H\033[2J")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										45
									
								
								commands/emote.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								commands/emote.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,45 @@
 | 
				
			||||||
 | 
					package commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/lib"
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/state"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/discord"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func EmoteCommand() {
 | 
				
			||||||
 | 
					  channelId := state.GetCurrentChannel()
 | 
				
			||||||
 | 
					  if channelId == "" {
 | 
				
			||||||
 | 
					    fmt.Print("<not in a channel>\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  prompt := ":emote> "
 | 
				
			||||||
 | 
					  lib.MakePrompt(prompt, true, func(input string, interrupt bool) {
 | 
				
			||||||
 | 
					    if input == "" {
 | 
				
			||||||
 | 
					      if interrupt {
 | 
				
			||||||
 | 
					        fmt.Print("^C<no message sent>\n\r")
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        fmt.Print(prompt, "<no message sent>\n\r")
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      fmt.Print(prompt, input, "\n\r")
 | 
				
			||||||
 | 
					      client := state.GetClient()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      snowflake, err := discord.ParseSnowflake(channelId)
 | 
				
			||||||
 | 
					      if err != nil {
 | 
				
			||||||
 | 
					        fmt.Print("<failed to parse channel id: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      _, err = client.SendMessage(discord.ChannelID(snowflake), "*" + input + "*")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if err != nil {
 | 
				
			||||||
 | 
					        fmt.Print("<failed to send message: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // TODO: update afk state
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										658
									
								
								commands/guild.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										658
									
								
								commands/guild.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,658 @@
 | 
				
			||||||
 | 
					package commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"math"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"sort"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"unicode/utf8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/lib"
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/state"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/discord"
 | 
				
			||||||
 | 
						tsize "github.com/kopoli/go-terminal-size"
 | 
				
			||||||
 | 
						"github.com/mgutz/ansi"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var REGEX_EMOTE = regexp.MustCompile(`<(?:\x{200b}|&)?a?:(\w+):(\d+)>`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type GuildListing struct {
 | 
				
			||||||
 | 
					  Name string
 | 
				
			||||||
 | 
					  Members int
 | 
				
			||||||
 | 
					  Online int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ListGuildsCommand() {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  longest := 0
 | 
				
			||||||
 | 
					  guilds := make([]GuildListing, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  clientGuilds, err := client.Guilds()
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to get guilds: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for _, guild := range clientGuilds {
 | 
				
			||||||
 | 
					    length := utf8.RuneCountInString(guild.Name)
 | 
				
			||||||
 | 
					    if length > longest {
 | 
				
			||||||
 | 
					      longest = length
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    withCount, err := client.GuildWithCount(guild.ID)
 | 
				
			||||||
 | 
					    if err == nil {
 | 
				
			||||||
 | 
					      guilds = append(guilds, GuildListing{
 | 
				
			||||||
 | 
					        Name: guild.Name,
 | 
				
			||||||
 | 
					        Members: int(withCount.ApproximateMembers),
 | 
				
			||||||
 | 
					        Online: int(withCount.ApproximatePresences),
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      guilds = append(guilds, GuildListing{
 | 
				
			||||||
 | 
					        Name: guild.Name,
 | 
				
			||||||
 | 
					        Members: int(guild.ApproximateMembers),
 | 
				
			||||||
 | 
					        Online: int(guild.ApproximatePresences),
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fmt.Print("\n\r")
 | 
				
			||||||
 | 
					  fmt.Printf("  %*s  online  total\n\r", longest, "guild-name")
 | 
				
			||||||
 | 
					  fmt.Print(strings.Repeat("-", 80) + "\n\r")
 | 
				
			||||||
 | 
					  for _, guild := range guilds {
 | 
				
			||||||
 | 
					    fmt.Printf("  %*s  %6d  %5d\n\r", longest, guild.Name, guild.Online, guild.Members)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  fmt.Print(strings.Repeat("-", 80) + "\n\r")
 | 
				
			||||||
 | 
					  fmt.Print("\n\r")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetSortedChannels(guildId string, withCategories bool, withPrivate bool) []discord.Channel {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					  channels := make([]discord.Channel, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  guildSnowflake, err := discord.ParseSnowflake(guildId)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return channels
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parsedGuildId := discord.GuildID(guildSnowflake)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  guild, err := client.GuildStore.Guild(parsedGuildId)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return channels
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  guildChannels, err := client.ChannelStore.Channels(guild.ID)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return channels
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  self, err := client.MeStore.Me()
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return channels
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  selfMember, err := client.MemberStore.Member(guild.ID, self.ID)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return channels
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if withCategories {
 | 
				
			||||||
 | 
					    categories := make(map[string][]discord.Channel)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for _, channel := range guildChannels {
 | 
				
			||||||
 | 
					      if channel.Type != discord.GuildText && channel.Type != discord.GuildAnnouncement {
 | 
				
			||||||
 | 
					        continue
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      private := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if channel.ParentID.IsValid() {
 | 
				
			||||||
 | 
					        category, err := client.ChannelStore.Channel(channel.ParentID)
 | 
				
			||||||
 | 
					        if err == nil {
 | 
				
			||||||
 | 
					          perms := lib.ChannelPermissionsOf(*guild, *category, *selfMember)
 | 
				
			||||||
 | 
					          private = !perms.Has(discord.PermissionViewChannel)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if private {
 | 
				
			||||||
 | 
					        perms := lib.ChannelPermissionsOf(*guild, channel, *selfMember)
 | 
				
			||||||
 | 
					        private = !perms.Has(discord.PermissionViewChannel)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if private && !withPrivate {
 | 
				
			||||||
 | 
					        continue
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      categoryID := "0"
 | 
				
			||||||
 | 
					      if channel.ParentID.IsValid() {
 | 
				
			||||||
 | 
					        categoryID = channel.ParentID.String()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      _, has := categories[categoryID]
 | 
				
			||||||
 | 
					      if !has {
 | 
				
			||||||
 | 
					        categories[categoryID] = make([]discord.Channel, 0)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      categories[categoryID] = append(categories[categoryID], channel)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for id, category := range categories {
 | 
				
			||||||
 | 
					      // sort channels by position
 | 
				
			||||||
 | 
					      sort.Slice(category, func(i, j int) bool {
 | 
				
			||||||
 | 
					        return category[i].Position < category[j].Position
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      categoryChannels := make([]discord.Channel, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // append category channel to top
 | 
				
			||||||
 | 
					      if id != "0" {
 | 
				
			||||||
 | 
					        parsedCategoryId, err := discord.ParseSnowflake(id)
 | 
				
			||||||
 | 
					        if err != nil {
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for _, channel := range guildChannels {
 | 
				
			||||||
 | 
					          if channel.ID == discord.ChannelID(parsedCategoryId) {
 | 
				
			||||||
 | 
					            categoryChannels = append(categoryChannels, channel)
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // append channels
 | 
				
			||||||
 | 
					      for _, channel := range category {
 | 
				
			||||||
 | 
					        categoryChannels = append(categoryChannels, channel)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      categories[id] = categoryChannels
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    keys := make([]string, 0)
 | 
				
			||||||
 | 
					    for id := range categories {
 | 
				
			||||||
 | 
					      if id == "0" {
 | 
				
			||||||
 | 
					        continue
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      keys = append(keys, id)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    sort.Slice(keys, func(i, j int) bool {
 | 
				
			||||||
 | 
					      pa, _ := discord.ParseSnowflake(keys[i])
 | 
				
			||||||
 | 
					      pb, _ := discord.ParseSnowflake(keys[j])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      ca, _ := client.ChannelStore.Channel(discord.ChannelID(pa))
 | 
				
			||||||
 | 
					      cb, _ := client.ChannelStore.Channel(discord.ChannelID(pb))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return ca.Position < cb.Position
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    sortedCategories := make(map[string][]discord.Channel)
 | 
				
			||||||
 | 
					    sortedCategories["0"] = categories["0"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for _, id := range keys {
 | 
				
			||||||
 | 
					      sortedCategories[id] = categories[id]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for _, categoryChannels := range sortedCategories {
 | 
				
			||||||
 | 
					      for _, channel := range categoryChannels {
 | 
				
			||||||
 | 
					        channels = append(channels, channel)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    for _, channel := range guildChannels {
 | 
				
			||||||
 | 
					      if channel.Type != discord.GuildText && channel.Type != discord.GuildAnnouncement {
 | 
				
			||||||
 | 
					        continue
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      private := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if channel.ParentID.IsValid() {
 | 
				
			||||||
 | 
					        category, err := client.ChannelStore.Channel(channel.ParentID)
 | 
				
			||||||
 | 
					        if err == nil {
 | 
				
			||||||
 | 
					          perms := lib.ChannelPermissionsOf(*guild, *category, *selfMember)
 | 
				
			||||||
 | 
					          private = !perms.Has(discord.PermissionViewChannel)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if private {
 | 
				
			||||||
 | 
					        perms := lib.ChannelPermissionsOf(*guild, channel, *selfMember)
 | 
				
			||||||
 | 
					        private = !perms.Has(discord.PermissionViewChannel)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if private && !withPrivate {
 | 
				
			||||||
 | 
					        continue
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      channels = append(channels, channel)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sort.Slice(channels, func(i, j int) bool {
 | 
				
			||||||
 | 
					      return channels[i].Position < channels[j].Position
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return channels
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ListChannelsCommand() {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					  self, err := client.MeStore.Me()
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to get self: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  currentGuild := state.GetCurrentGuild()
 | 
				
			||||||
 | 
					  if currentGuild == "" {
 | 
				
			||||||
 | 
					    fmt.Print("<not in a guild>\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  guildSnowflake, err := discord.ParseSnowflake(currentGuild)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to parse current guild id: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parsedGuildId := discord.GuildID(guildSnowflake)
 | 
				
			||||||
 | 
					  guild, err := client.GuildStore.Guild(parsedGuildId)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to get current guild: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  selfMember, err := client.MemberStore.Member(parsedGuildId, self.ID)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to get self member: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  longest := 0
 | 
				
			||||||
 | 
					  channels := GetSortedChannels(currentGuild, true, false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for _, channel := range channels {
 | 
				
			||||||
 | 
					    private := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if channel.ParentID.IsValid() {
 | 
				
			||||||
 | 
					      category, err := client.ChannelStore.Channel(channel.ParentID)
 | 
				
			||||||
 | 
					      if err == nil {
 | 
				
			||||||
 | 
					        perms := lib.ChannelPermissionsOf(*guild, *category, *selfMember)
 | 
				
			||||||
 | 
					        private = !perms.Has(discord.PermissionViewChannel)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if private {
 | 
				
			||||||
 | 
					      perms := lib.ChannelPermissionsOf(*guild, channel, *selfMember)
 | 
				
			||||||
 | 
					      private = !perms.Has(discord.PermissionViewChannel)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    category := channel.Type == discord.GuildCategory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    catLen := 0
 | 
				
			||||||
 | 
					    if category {
 | 
				
			||||||
 | 
					      catLen = 6
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    privLen := 0
 | 
				
			||||||
 | 
					    if private {
 | 
				
			||||||
 | 
					      privLen = 1
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    length := utf8.RuneCountInString(channel.Name) + privLen + catLen
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if length > longest {
 | 
				
			||||||
 | 
					      longest = int(math.Min(25, float64(length)))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fmt.Print("\n\r")
 | 
				
			||||||
 | 
					  fmt.Printf("  %*s    created  topic\n\r", longest, "channel-name")
 | 
				
			||||||
 | 
					  fmt.Print(strings.Repeat("-", 80) + "\n\r")
 | 
				
			||||||
 | 
					  for _, channel := range channels {
 | 
				
			||||||
 | 
					    private := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if channel.ParentID.IsValid() {
 | 
				
			||||||
 | 
					      category, err := client.ChannelStore.Channel(channel.ParentID)
 | 
				
			||||||
 | 
					      if err == nil {
 | 
				
			||||||
 | 
					        perms := lib.ChannelPermissionsOf(*guild, *category, *selfMember)
 | 
				
			||||||
 | 
					        private = !perms.Has(discord.PermissionViewChannel)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if private {
 | 
				
			||||||
 | 
					      perms := lib.ChannelPermissionsOf(*guild, channel, *selfMember)
 | 
				
			||||||
 | 
					      private = !perms.Has(discord.PermissionViewChannel)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    category := channel.Type == discord.GuildCategory
 | 
				
			||||||
 | 
					    topic := REGEX_EMOTE.ReplaceAllString(channel.Topic, ":$1:")
 | 
				
			||||||
 | 
					    topic = strings.ReplaceAll(topic, "\n", " ")
 | 
				
			||||||
 | 
					    name := channel.Name
 | 
				
			||||||
 | 
					    if category {
 | 
				
			||||||
 | 
					      name = "-- " + name + " --"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if private {
 | 
				
			||||||
 | 
					      name = "*" + name
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nameLength := utf8.RuneCountInString(name)
 | 
				
			||||||
 | 
					    if nameLength > 25 {
 | 
				
			||||||
 | 
					      name = name[:24] + "\u2026"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    topicLength := utf8.RuneCountInString(topic)
 | 
				
			||||||
 | 
					    longestTopic := 80 - (longest + 5) - 11
 | 
				
			||||||
 | 
					    if topicLength > longestTopic {
 | 
				
			||||||
 | 
					      topic = topic[:(longestTopic - 1)] + "\u2026"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    created := "??-???-??"
 | 
				
			||||||
 | 
					    timestamp := channel.CreatedAt()
 | 
				
			||||||
 | 
					    created = timestamp.UTC().Format("02-Jan-06")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fmt.Printf("  %*s  %s  %s\n\r", longest, name, created, topic)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  fmt.Print(strings.Repeat("-", 80) + "\n\r")
 | 
				
			||||||
 | 
					  fmt.Print("\n\r")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ListedMember struct {
 | 
				
			||||||
 | 
					  Name string
 | 
				
			||||||
 | 
					  Bot bool
 | 
				
			||||||
 | 
					  Status discord.Status
 | 
				
			||||||
 | 
					  Position int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ListUsersCommand() {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  currentGuild := state.GetCurrentGuild()
 | 
				
			||||||
 | 
					  currentChannel := state.GetCurrentChannel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if currentGuild == "" {
 | 
				
			||||||
 | 
					    fmt.Print("<not in a guild>\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if currentChannel == "" {
 | 
				
			||||||
 | 
					    fmt.Print("<not in a channel>\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parsedGuildId, err := discord.ParseSnowflake(currentGuild)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to parse guild id: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  parsedChannelId, err := discord.ParseSnowflake(currentChannel)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to parse channel id: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  guild, err := client.GuildStore.Guild(discord.GuildID(parsedGuildId))
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to get guild: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  channel, err := client.ChannelStore.Channel(discord.ChannelID(parsedChannelId))
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to get channel: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  longest := 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  sortedMembers := make([]ListedMember, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  presences, err := client.Presences(guild.ID)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to get presences: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for _, presence := range presences {
 | 
				
			||||||
 | 
					    if presence.Status == discord.OfflineStatus {
 | 
				
			||||||
 | 
					      continue
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    member, err := client.MemberStore.Member(guild.ID, presence.User.ID)
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					      continue
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if channel.ParentID.IsValid() {
 | 
				
			||||||
 | 
					      category, err := client.ChannelStore.Channel(channel.ParentID)
 | 
				
			||||||
 | 
					      if err == nil {
 | 
				
			||||||
 | 
					        perms := lib.ChannelPermissionsOf(*guild, *category, *member)
 | 
				
			||||||
 | 
					        private = !perms.Has(discord.PermissionViewChannel)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if private {
 | 
				
			||||||
 | 
					      perms := lib.ChannelPermissionsOf(*guild, *channel, *member)
 | 
				
			||||||
 | 
					      private = !perms.Has(discord.PermissionViewChannel)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    length := utf8.RuneCountInString(member.User.Username) + 3
 | 
				
			||||||
 | 
					    if length > longest {
 | 
				
			||||||
 | 
					      longest = length
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    position := 0
 | 
				
			||||||
 | 
					    for _, id := range member.RoleIDs {
 | 
				
			||||||
 | 
					      role, err := client.RoleStore.Role(guild.ID, id)
 | 
				
			||||||
 | 
					      if err != nil {
 | 
				
			||||||
 | 
					        continue
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if role.Hoist && role.Position > position {
 | 
				
			||||||
 | 
					        position = role.Position
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sortedMembers = append(sortedMembers, ListedMember{
 | 
				
			||||||
 | 
					      Name: member.User.Username,
 | 
				
			||||||
 | 
					      Bot: member.User.Bot,
 | 
				
			||||||
 | 
					      Status: presence.Status,
 | 
				
			||||||
 | 
					      Position: position,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fmt.Print("\n\r")
 | 
				
			||||||
 | 
					  fmt.Printf("[you are in '%s' in '#%s' among %d]\n\r", guild.Name, channel.Name, len(sortedMembers))
 | 
				
			||||||
 | 
					  fmt.Print("\n\r")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  membersByPosition := make(map[int][]ListedMember)
 | 
				
			||||||
 | 
					  for _, member := range sortedMembers {
 | 
				
			||||||
 | 
					    _, has := membersByPosition[member.Position]
 | 
				
			||||||
 | 
					    if !has {
 | 
				
			||||||
 | 
					      membersByPosition[member.Position] = make([]ListedMember, 0)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    membersByPosition[member.Position] = append(membersByPosition[member.Position], member)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  for _, members := range membersByPosition {
 | 
				
			||||||
 | 
					    sort.Slice(members, func(i, j int) bool {
 | 
				
			||||||
 | 
					      return members[i].Name < members[j].Name
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  positions := make([]int, 0, len(membersByPosition))
 | 
				
			||||||
 | 
					  for k := range membersByPosition {
 | 
				
			||||||
 | 
					    positions = append(positions, k)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  sort.Slice(positions, func(i, j int) bool {
 | 
				
			||||||
 | 
					    return positions[i] > positions[j]
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  size, err := tsize.GetSize()
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  columns := int(math.Floor(float64(size.Width) / float64(longest)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  index := 0
 | 
				
			||||||
 | 
					  for _, position := range positions {
 | 
				
			||||||
 | 
					    members := membersByPosition[position]
 | 
				
			||||||
 | 
					    if len(members) > 150 {
 | 
				
			||||||
 | 
					      str := "[hiding " + strconv.Itoa(len(members)) + " members]"
 | 
				
			||||||
 | 
					      length := utf8.RuneCountInString(str)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      index++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      pad := 0
 | 
				
			||||||
 | 
					      if index % columns != 0 {
 | 
				
			||||||
 | 
					        pad = longest - length
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if pad < 0 {
 | 
				
			||||||
 | 
					        pad = 0
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      fmt.Printf(str + strings.Repeat(" ", pad))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if index % columns == 0 {
 | 
				
			||||||
 | 
					        fmt.Print("\n\r")
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      continue
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for _, member := range members {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      statusColor := "reset"
 | 
				
			||||||
 | 
					      if member.Status == discord.OnlineStatus {
 | 
				
			||||||
 | 
					        statusColor = "green+b"
 | 
				
			||||||
 | 
					      } else if member.Status == discord.IdleStatus {
 | 
				
			||||||
 | 
					        statusColor = "yellow+b"
 | 
				
			||||||
 | 
					      } else if member.Status == discord.DoNotDisturbStatus {
 | 
				
			||||||
 | 
					        statusColor = "red+b"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      nameColor := "reset"
 | 
				
			||||||
 | 
					      if member.Bot {
 | 
				
			||||||
 | 
					        nameColor = "yellow"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      nameAndStatus := ansi.Color(" \u2022 ", statusColor) + ansi.Color(member.Name, nameColor)
 | 
				
			||||||
 | 
					      nameLength := utf8.RuneCountInString(member.Name) + 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      index++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      pad := 0
 | 
				
			||||||
 | 
					      if index % columns != 0 {
 | 
				
			||||||
 | 
					        pad = longest - nameLength
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if pad < 0 {
 | 
				
			||||||
 | 
					        pad = 0
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      fmt.Printf(nameAndStatus + strings.Repeat(" ", pad))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if index % columns == 0 {
 | 
				
			||||||
 | 
					        fmt.Print("\n\r")
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if index % columns != 0 {
 | 
				
			||||||
 | 
					    fmt.Print("\n\r")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  fmt.Print("\n\r")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if channel.Topic != "" {
 | 
				
			||||||
 | 
					    fmt.Print("--Topic" + strings.Repeat("-", 73) + "\n\r")
 | 
				
			||||||
 | 
					    for _, line := range strings.Split(channel.Topic, "\n") {
 | 
				
			||||||
 | 
					      fmt.Print(line + "\n\r")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fmt.Print(strings.Repeat("-", 80) + "\n\r")
 | 
				
			||||||
 | 
					    fmt.Print("\n\r")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SwitchGuild(input string) {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if input == "" {
 | 
				
			||||||
 | 
					    ListChannelsCommand()
 | 
				
			||||||
 | 
					    ListUsersCommand()
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    target := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    guilds, err := client.GuildStore.Guilds()
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					      fmt.Print("<failed to get guilds: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for _, guild := range guilds {
 | 
				
			||||||
 | 
					      if strings.Index(strings.ToLower(guild.Name), strings.ToLower(input)) > -1 {
 | 
				
			||||||
 | 
					        target = guild.ID.String()
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if target == "" {
 | 
				
			||||||
 | 
					      fmt.Print("<guild not found>\n\r")
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      state.SetCurrentGuild(target)
 | 
				
			||||||
 | 
					      last := state.GetLastChannel(target)
 | 
				
			||||||
 | 
					      if last == "" {
 | 
				
			||||||
 | 
					        channels := GetSortedChannels(target, false, false)
 | 
				
			||||||
 | 
					        if len(channels) > 0 {
 | 
				
			||||||
 | 
					          topChannel := channels[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          state.SetCurrentChannel(topChannel.ID.String())
 | 
				
			||||||
 | 
					          state.SetLastChannel(target, topChannel.ID.String())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        state.SetCurrentChannel(last)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      ListChannelsCommand()
 | 
				
			||||||
 | 
					      ListUsersCommand()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      lib.UpdatePresence()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SwitchGuildsCommand() {
 | 
				
			||||||
 | 
					  lib.MakePrompt(":guild> ", false, func(input string, interrupt bool) {
 | 
				
			||||||
 | 
					    fmt.Print("\r")
 | 
				
			||||||
 | 
					    SwitchGuild(input)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SwitchChannelsCommand() {
 | 
				
			||||||
 | 
					  currentGuild := state.GetCurrentGuild()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if currentGuild == "" {
 | 
				
			||||||
 | 
					    fmt.Print("<not in a guild>\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lib.MakePrompt(":channel> ", false, func(input string, interrupt bool) {
 | 
				
			||||||
 | 
					    fmt.Print("\r")
 | 
				
			||||||
 | 
					    if input == "" {
 | 
				
			||||||
 | 
					      ListUsersCommand()
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      target := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      channels := GetSortedChannels(currentGuild, false, false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      for _, channel := range channels {
 | 
				
			||||||
 | 
					        if strings.Index(strings.ToLower(channel.Name), strings.ToLower(input)) > -1 {
 | 
				
			||||||
 | 
					          target = channel.ID.String()
 | 
				
			||||||
 | 
					          break
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if target == "" {
 | 
				
			||||||
 | 
					        fmt.Print("<channel not found>\n\r")
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        state.SetCurrentChannel(target)
 | 
				
			||||||
 | 
					        state.SetLastChannel(currentGuild, target)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ListUsersCommand()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        lib.UpdatePresence()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										83
									
								
								commands/help.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								commands/help.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,83 @@
 | 
				
			||||||
 | 
					package commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"sort"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"unicode"
 | 
				
			||||||
 | 
						"unicode/utf8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/state"
 | 
				
			||||||
 | 
						"github.com/mgutz/ansi"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const format string = "  %s - %s%s"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func lessLower(sa, sb string) bool {
 | 
				
			||||||
 | 
					  for {
 | 
				
			||||||
 | 
					    rb, nb := utf8.DecodeRuneInString(sb)
 | 
				
			||||||
 | 
					    if nb == 0 {
 | 
				
			||||||
 | 
					      // The number of runes in sa is greater than or
 | 
				
			||||||
 | 
					      // equal to the number of runes in sb. It follows
 | 
				
			||||||
 | 
					      // that sa is not less than sb.
 | 
				
			||||||
 | 
					      return false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ra, na := utf8.DecodeRuneInString(sa)
 | 
				
			||||||
 | 
					    if na == 0 {
 | 
				
			||||||
 | 
					      // The number of runes in sa is less than the
 | 
				
			||||||
 | 
					      // number of runes in sb. It follows that sa
 | 
				
			||||||
 | 
					      // is less than sb.
 | 
				
			||||||
 | 
					      return true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rbl := unicode.ToLower(rb)
 | 
				
			||||||
 | 
					    ral := unicode.ToLower(ra)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ral != rbl {
 | 
				
			||||||
 | 
					      return ral < rbl
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return ra > rb
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func HelpCommand() {
 | 
				
			||||||
 | 
					  noColor := state.HasNoColor()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fmt.Println("\r\nCOMcord (c)left 2023\n\r")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commands := GetAllCommands()
 | 
				
			||||||
 | 
					  keys := make([]string, 0, len(commands))
 | 
				
			||||||
 | 
					  for key := range commands {
 | 
				
			||||||
 | 
					    keys = append(keys, key)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  sort.Slice(keys, func(i, j int) bool {
 | 
				
			||||||
 | 
					    return lessLower(keys[i], keys[j])
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  index := 0
 | 
				
			||||||
 | 
					  for _, key := range keys {
 | 
				
			||||||
 | 
					    cmd := commands[key]
 | 
				
			||||||
 | 
					    str := fmt.Sprintf(format, key, cmd.Description, "")
 | 
				
			||||||
 | 
					    length := len(str)
 | 
				
			||||||
 | 
					    padding := strings.Repeat(" ", 25 - length)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if noColor {
 | 
				
			||||||
 | 
					      fmt.Printf(format, key, cmd.Description, padding)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      coloredKey := ansi.Color(key, "yellow+b")
 | 
				
			||||||
 | 
					      fmt.Printf(format, coloredKey, cmd.Description, padding)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    index++
 | 
				
			||||||
 | 
					    if index % 3 == 0 {
 | 
				
			||||||
 | 
					      fmt.Print("\n\r")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if index % 3 != 0 {
 | 
				
			||||||
 | 
					    fmt.Print("\n\r")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fmt.Println("\r\nTo begin TALK MODE, press [SPACE]\n\r")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										157
									
								
								commands/history.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								commands/history.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,157 @@
 | 
				
			||||||
 | 
					package commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/lib"
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/state"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/discord"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetHistory(limit int, channel string) {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parsedChannelId, err := discord.ParseSnowflake(channel)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to parse channel id: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  messages, err := client.Messages(discord.ChannelID(parsedChannelId), 100)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to get messages: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for i, j := 0, len(messages) - 1; i < j; i, j = i + 1, j - 1 {
 | 
				
			||||||
 | 
					    messages[i], messages[j] = messages[j], messages[i]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state.SetInPrompt(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fmt.Print("--Beginning-Review", strings.Repeat("-", 62), "\n\r")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lines := make([]string, 0)
 | 
				
			||||||
 | 
					  for _, msg := range messages {
 | 
				
			||||||
 | 
					    msgLines := lib.ProcessMessage(msg, lib.MessageOptions{NoColor: true, InHistory: true})
 | 
				
			||||||
 | 
					    for _, line := range msgLines {
 | 
				
			||||||
 | 
					      lines = append(lines, line)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  length := len(lines)
 | 
				
			||||||
 | 
					  startIndex := length - limit
 | 
				
			||||||
 | 
					  if startIndex < 0 {
 | 
				
			||||||
 | 
					    startIndex = 0
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  for i := startIndex; i < length; i++ {
 | 
				
			||||||
 | 
					    fmt.Print(lines[i])
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fmt.Print("--Review-Complete", strings.Repeat("-", 63), "\n\r")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state.SetInPrompt(false)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func HistoryCommand() {
 | 
				
			||||||
 | 
					  currentChannel := state.GetCurrentChannel()
 | 
				
			||||||
 | 
					  if currentChannel == "" {
 | 
				
			||||||
 | 
					    fmt.Print("<not in a channel>\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  GetHistory(20, currentChannel)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ExtendedHistoryCommand() {
 | 
				
			||||||
 | 
					  currentChannel := state.GetCurrentChannel()
 | 
				
			||||||
 | 
					  if currentChannel == "" {
 | 
				
			||||||
 | 
					    fmt.Print("<not in a channel>\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lib.MakePrompt(":lines> ", false, func(input string, interrupt bool) {
 | 
				
			||||||
 | 
					    fmt.Print("\r")
 | 
				
			||||||
 | 
					    limit, err := strconv.Atoi(input)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					      fmt.Print("<not a number>\n\r")
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      GetHistory(limit, currentChannel)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func PeekHistory(guild, channel string) {
 | 
				
			||||||
 | 
					  target := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  channels := GetSortedChannels(guild, false, false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for _, c := range channels {
 | 
				
			||||||
 | 
					    if strings.Index(strings.ToLower(c.Name), strings.ToLower(channel)) > -1 {
 | 
				
			||||||
 | 
					      target = c.ID.String()
 | 
				
			||||||
 | 
					      break
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if target == "" {
 | 
				
			||||||
 | 
					    fmt.Print("<channel not found>\n\r")
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    GetHistory(20, target)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func PeekCommand() {
 | 
				
			||||||
 | 
					  currentGuild := state.GetCurrentGuild()
 | 
				
			||||||
 | 
					  if currentGuild == "" {
 | 
				
			||||||
 | 
					    fmt.Print("<not in a guild>\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lib.MakePrompt(":peek> ", false, func(input string, interrupt bool) {
 | 
				
			||||||
 | 
					    fmt.Print("\r")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if input != "" {
 | 
				
			||||||
 | 
					      PeekHistory(currentGuild, input)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func CrossPeekCommand() {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lib.MakePrompt(":guild> ", false, func(input string, interrupt bool) {
 | 
				
			||||||
 | 
					    fmt.Print("\r")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if input != "" {
 | 
				
			||||||
 | 
					      targetGuild := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      guilds, err := client.GuildStore.Guilds()
 | 
				
			||||||
 | 
					      if err != nil {
 | 
				
			||||||
 | 
					        fmt.Print("<failed to get guilds: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      for _, guild := range guilds {
 | 
				
			||||||
 | 
					        if strings.Index(strings.ToLower(guild.Name), strings.ToLower(input)) > -1 {
 | 
				
			||||||
 | 
					          targetGuild = guild.ID.String()
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if targetGuild == "" {
 | 
				
			||||||
 | 
					        fmt.Print("<guild not found>\n\r")
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        lib.MakePrompt(":peek> ", false, func(input string, interrupt bool) {
 | 
				
			||||||
 | 
					          fmt.Print("\r")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if input != "" {
 | 
				
			||||||
 | 
					            PeekHistory(targetGuild, input)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										91
									
								
								commands/main.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								commands/main.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,91 @@
 | 
				
			||||||
 | 
					package commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var commandMap map[string]Command
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Command struct {
 | 
				
			||||||
 | 
					  Run func()
 | 
				
			||||||
 | 
					  Description string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Setup() {
 | 
				
			||||||
 | 
					  commandMap = make(map[string]Command)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commandMap["q"] = Command{
 | 
				
			||||||
 | 
					    Run: QuitCommand,
 | 
				
			||||||
 | 
					    Description: "quit comcord",
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commandMap["h"] = Command{
 | 
				
			||||||
 | 
					    Run: HelpCommand,
 | 
				
			||||||
 | 
					    Description: "command help",
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commandMap["c"] = Command{
 | 
				
			||||||
 | 
					    Run: ClearCommand,
 | 
				
			||||||
 | 
					    Description: "clear",
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commandMap["e"] = Command{
 | 
				
			||||||
 | 
					    Run: EmoteCommand,
 | 
				
			||||||
 | 
					    Description: "emote",
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commandMap["L"] = Command{
 | 
				
			||||||
 | 
					    Run: ListGuildsCommand,
 | 
				
			||||||
 | 
					    Description: "list guilds",
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commandMap["l"] = Command{
 | 
				
			||||||
 | 
					    Run: ListChannelsCommand,
 | 
				
			||||||
 | 
					    Description: "list channels",
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commandMap["G"] = Command{
 | 
				
			||||||
 | 
					    Run: SwitchGuildsCommand,
 | 
				
			||||||
 | 
					    Description: "goto guild",
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commandMap["g"] = Command{
 | 
				
			||||||
 | 
					    Run: SwitchChannelsCommand,
 | 
				
			||||||
 | 
					    Description: "goto channel",
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commandMap["w"] = Command{
 | 
				
			||||||
 | 
					    Run: ListUsersCommand,
 | 
				
			||||||
 | 
					    Description: "who is in channel",
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commandMap["r"] = Command{
 | 
				
			||||||
 | 
					    Run: HistoryCommand,
 | 
				
			||||||
 | 
					    Description: "channel history",
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commandMap["R"] = Command{
 | 
				
			||||||
 | 
					    Run: ExtendedHistoryCommand,
 | 
				
			||||||
 | 
					    Description: "extended history",
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commandMap["p"] = Command{
 | 
				
			||||||
 | 
					    Run: PeekCommand,
 | 
				
			||||||
 | 
					    Description: "peek at channel",
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commandMap["P"] = Command{
 | 
				
			||||||
 | 
					    Run: CrossPeekCommand,
 | 
				
			||||||
 | 
					    Description: "cross-guild peek",
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commandMap["+"] = Command{
 | 
				
			||||||
 | 
					    Run: TimeCommand,
 | 
				
			||||||
 | 
					    Description: "current time",
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetCommand(key string) (Command, bool) {
 | 
				
			||||||
 | 
					  command, has := commandMap[key]
 | 
				
			||||||
 | 
					  return command, has
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetAllCommands() map[string]Command {
 | 
				
			||||||
 | 
					  return commandMap
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										16
									
								
								commands/quit.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								commands/quit.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					package commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/state"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func QuitCommand() {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fmt.Print("Unlinking TTY...\n\r")
 | 
				
			||||||
 | 
					  client.Close()
 | 
				
			||||||
 | 
					  os.Exit(0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										129
									
								
								commands/send.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								commands/send.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,129 @@
 | 
				
			||||||
 | 
					package commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"unicode/utf8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/lib"
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/state"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/discord"
 | 
				
			||||||
 | 
						"github.com/mgutz/ansi"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var REGEX_MENTION = regexp.MustCompile("@([a-z0-9._]{1,32})")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SendMode() {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  currentGuild := state.GetCurrentGuild()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  channelId := state.GetCurrentChannel()
 | 
				
			||||||
 | 
					  if channelId == "" {
 | 
				
			||||||
 | 
					    fmt.Print("<not in a channel>\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  parsedChannelId, err := discord.ParseSnowflake(channelId)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to parse channel id: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  channel, err := client.ChannelStore.Channel(discord.ChannelID(parsedChannelId))
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<error getting channel: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  guild, err := client.GuildStore.Guild(channel.GuildID)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to get current guild: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  self, err := client.MeStore.Me()
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to get self: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  selfMember, err := client.MemberStore.Member(guild.ID, self.ID)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("<failed to get self as member: ", err.Error(), ">\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cannotSend := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if channel.ParentID.IsValid() {
 | 
				
			||||||
 | 
					    category, err := client.ChannelStore.Channel(channel.ParentID)
 | 
				
			||||||
 | 
					    if err == nil {
 | 
				
			||||||
 | 
					      perms := lib.ChannelPermissionsOf(*guild, *category, *selfMember)
 | 
				
			||||||
 | 
					      cannotSend = !perms.Has(discord.PermissionSendMessages)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if cannotSend {
 | 
				
			||||||
 | 
					    perms := lib.ChannelPermissionsOf(*guild, *channel, *selfMember)
 | 
				
			||||||
 | 
					    cannotSend = !perms.Has(discord.PermissionSendMessages)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if cannotSend {
 | 
				
			||||||
 | 
					    fmt.Print("<you do not have permission to send messages here>\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  length := utf8.RuneCountInString(self.Username) + 2
 | 
				
			||||||
 | 
					  curLength := state.GetNameLength()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  prompt := fmt.Sprintf("[%s]%s", self.Username, strings.Repeat(" ", (curLength - length) + 1))
 | 
				
			||||||
 | 
					  if !state.HasNoColor() {
 | 
				
			||||||
 | 
					    prompt = ansi.Color(prompt, "cyan+b")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  client.Typing(channel.ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lib.MakePrompt(prompt, true, func(input string, interrupt bool) {
 | 
				
			||||||
 | 
					    if input == "" {
 | 
				
			||||||
 | 
					      if interrupt {
 | 
				
			||||||
 | 
					        fmt.Print("^C<no message sent>\n\r")
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        fmt.Print(prompt, "<no message sent>\n\r")
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      fmt.Print(prompt, input, "\n\r")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      input := REGEX_MENTION.ReplaceAllStringFunc(input, func(match string) string {
 | 
				
			||||||
 | 
					        matches := REGEX_MENTION.FindStringSubmatch(match)
 | 
				
			||||||
 | 
					        username := matches[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parsedGuildId, err := discord.ParseSnowflake(currentGuild)
 | 
				
			||||||
 | 
					        if err != nil {
 | 
				
			||||||
 | 
					          return match
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        members, err := client.MemberStore.Members(discord.GuildID(parsedGuildId))
 | 
				
			||||||
 | 
					        if err != nil {
 | 
				
			||||||
 | 
					          return match
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for _, member := range members {
 | 
				
			||||||
 | 
					          if member.User.Username == username {
 | 
				
			||||||
 | 
					            return member.User.ID.Mention()
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return match
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      _, err := client.SendMessage(channel.ID, input)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if err != nil {
 | 
				
			||||||
 | 
					        fmt.Print("<failed to send message: ", err, ">\n\r")
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // TODO: update afk state
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										12
									
								
								commands/time.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								commands/time.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					package commands
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TimeCommand() {
 | 
				
			||||||
 | 
					  now := time.Now().UTC()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fmt.Printf("%s\n\r", now.Format("[Mon 02-Jan-06 15:04:05]"))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										42
									
								
								events/clock.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								events/clock.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					package events
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
						"unicode/utf8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/state"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var sentTime bool = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SetupClock() {
 | 
				
			||||||
 | 
					  clock := time.NewTicker(500 * time.Millisecond)
 | 
				
			||||||
 | 
					  go func() {
 | 
				
			||||||
 | 
					    for {
 | 
				
			||||||
 | 
					      select {
 | 
				
			||||||
 | 
					        case <- clock.C: {
 | 
				
			||||||
 | 
					          now := time.Now().UTC()
 | 
				
			||||||
 | 
					          if now.Minute() % 15 == 0 && now.Second() < 2 && !sentTime {
 | 
				
			||||||
 | 
					            if state.IsInPrompt() {
 | 
				
			||||||
 | 
					              // TODO
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              fmt.Printf("%s\n\r", now.Format("[Mon 02-Jan-06 15:04:05]"))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            client := state.GetClient()
 | 
				
			||||||
 | 
					            self, err := client.MeStore.Me()
 | 
				
			||||||
 | 
					            if err != nil {
 | 
				
			||||||
 | 
					              return
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            state.SetNameLength(utf8.RuneCountInString(self.Username) + 2)
 | 
				
			||||||
 | 
					            sentTime = true
 | 
				
			||||||
 | 
					          } else if now.Second() > 2 && sentTime {
 | 
				
			||||||
 | 
					            sentTime = false
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								events/main.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								events/main.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					package events
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/diamondburned/ningen/v3"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Setup(session *ningen.State) {
 | 
				
			||||||
 | 
					  session.PreHandler.AddHandler(Ready)
 | 
				
			||||||
 | 
					  session.PreHandler.AddHandler(MessageCreate)
 | 
				
			||||||
 | 
					  session.PreHandler.AddHandler(MessageUpdate)
 | 
				
			||||||
 | 
					  session.PreHandler.AddHandler(ReactionAdd)
 | 
				
			||||||
 | 
					  SetupClock()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										88
									
								
								events/messages.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								events/messages.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,88 @@
 | 
				
			||||||
 | 
					package events
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/lib"
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/state"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/gateway"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/discord"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func MessageCreate(msg *gateway.MessageCreateEvent) {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					  self, err := client.MeStore.Me()
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if msg.Author.ID == self.ID {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  channel, err := client.ChannelStore.Channel(msg.ChannelID)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  isDM := channel.Type == discord.DirectMessage || channel.Type == discord.GroupDM
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if state.IsInPrompt() {
 | 
				
			||||||
 | 
					    state.AddMessageToQueue(msg.Message)
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    lines := lib.ProcessMessage(msg.Message, lib.MessageOptions{NoColor: state.HasNoColor()})
 | 
				
			||||||
 | 
					    for _, line := range lines {
 | 
				
			||||||
 | 
					      fmt.Print(line)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if isDM {
 | 
				
			||||||
 | 
					    state.SetLastDM(msg.ChannelID.String())
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func MessageUpdate(msg *gateway.MessageUpdateEvent) {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					  self, err := client.MeStore.Me()
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if msg.Author.ID == self.ID {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /*old, err := client.MessageStore.Message(msg.ChannelID, msg.ID)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if msg.Content == old.Content {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // dont process embed updates as messages
 | 
				
			||||||
 | 
					  if !msg.EditedTimestamp.IsValid() {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  channel, err := client.ChannelStore.Channel(msg.ChannelID)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  isDM := channel.Type == discord.DirectMessage || channel.Type == discord.GroupDM
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if state.IsInPrompt() {
 | 
				
			||||||
 | 
					    state.AddMessageToQueue(msg.Message)
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    lines := lib.ProcessMessage(msg.Message, lib.MessageOptions{NoColor: state.HasNoColor()})
 | 
				
			||||||
 | 
					    for _, line := range lines {
 | 
				
			||||||
 | 
					      fmt.Print(line)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if isDM {
 | 
				
			||||||
 | 
					    state.SetLastDM(msg.ChannelID.String())
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										56
									
								
								events/reactions.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								events/reactions.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,56 @@
 | 
				
			||||||
 | 
					package events
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/lib"
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/state"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/discord"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/gateway"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ReactionAdd(event *gateway.MessageReactionAddEvent) {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					  currentChannel := state.GetCurrentChannel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if event.ChannelID.String() != currentChannel {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  emote := event.Emoji.Name
 | 
				
			||||||
 | 
					  if event.Emoji.IsCustom() {
 | 
				
			||||||
 | 
					    emote = ":" + emote + ":"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  now := time.Now()
 | 
				
			||||||
 | 
					  nowSnowflake := discord.NewSnowflake(now)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  message, err := client.MessageStore.Message(event.ChannelID, event.MessageID)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    message, err = client.Message(event.ChannelID, event.MessageID)
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  msg := discord.Message{
 | 
				
			||||||
 | 
					    Content: fmt.Sprintf("*reacted with %s*", emote),
 | 
				
			||||||
 | 
					    Author: event.Member.User,
 | 
				
			||||||
 | 
					    ChannelID: event.ChannelID,
 | 
				
			||||||
 | 
					    GuildID: event.GuildID,
 | 
				
			||||||
 | 
					    ID: discord.MessageID(nowSnowflake),
 | 
				
			||||||
 | 
					    ReferencedMessage: message,
 | 
				
			||||||
 | 
					    Type: discord.InlinedReplyMessage,
 | 
				
			||||||
 | 
					    Timestamp: discord.Timestamp(now),
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if state.IsInPrompt() {
 | 
				
			||||||
 | 
					    state.AddMessageToQueue(msg)
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    lines := lib.ProcessMessage(msg, lib.MessageOptions{NoColor: state.HasNoColor()})
 | 
				
			||||||
 | 
					    for _, line := range lines {
 | 
				
			||||||
 | 
					      fmt.Print(line)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										52
									
								
								events/ready.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								events/ready.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,52 @@
 | 
				
			||||||
 | 
					package events
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"unicode/utf8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/commands"
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/state"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/discord"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/gateway"
 | 
				
			||||||
 | 
						"github.com/mgutz/ansi"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Ready(event *gateway.ReadyEvent) {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					  self, err := client.Me()
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("\r% Failed to get self: ", err.Error(), "\n\r")
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fmt.Printf("\rLogged in as: %s\n\r", ansi.Color(fmt.Sprintf("%s (%s)", self.Username, self.ID), "yellow"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state.SetNameLength(utf8.RuneCountInString(self.Username) + 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commands.ListGuildsCommand()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  defaultGuild := state.GetConfigValue("defaultGuild")
 | 
				
			||||||
 | 
					  defaultChannel := state.GetConfigValue("defaultChannel")
 | 
				
			||||||
 | 
					  if defaultGuild != "" {
 | 
				
			||||||
 | 
					    parsedGuildId, err := discord.ParseSnowflake(defaultGuild)
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					      fmt.Print("\r% Failed to parse guild ID: ", err.Error(), "\n\r")
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    guild, err := client.Guild(discord.GuildID(parsedGuildId))
 | 
				
			||||||
 | 
					    if err == nil {
 | 
				
			||||||
 | 
					      if defaultChannel != "" {
 | 
				
			||||||
 | 
					        state.SetCurrentChannel(defaultChannel)
 | 
				
			||||||
 | 
					        state.SetLastChannel(defaultGuild, defaultChannel)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      commands.SwitchGuild(guild.Name)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      fmt.Println("\r% This account is not in the defined default guild.")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    if defaultChannel != "" {
 | 
				
			||||||
 | 
					      fmt.Println("\r% Default channel defined without defining default guild.")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								go.mod
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								go.mod
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,27 @@
 | 
				
			||||||
 | 
					module github.com/Cynosphere/comcord
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go 1.20
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						atomicgo.dev/keyboard v0.2.9 // indirect
 | 
				
			||||||
 | 
						github.com/containerd/console v1.0.3 // indirect
 | 
				
			||||||
 | 
						github.com/diamondburned/arikawa/v3 v3.3.1 // indirect
 | 
				
			||||||
 | 
						github.com/diamondburned/ningen/v3 v3.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/ergochat/readline v0.0.5 // indirect
 | 
				
			||||||
 | 
						github.com/gorilla/schema v1.2.0 // indirect
 | 
				
			||||||
 | 
						github.com/gorilla/websocket v1.4.2 // indirect
 | 
				
			||||||
 | 
						github.com/kopoli/go-terminal-size v0.0.0-20170219200355-5c97524c8b54 // indirect
 | 
				
			||||||
 | 
						github.com/mattn/go-colorable v0.1.13 // indirect
 | 
				
			||||||
 | 
						github.com/mattn/go-isatty v0.0.17 // indirect
 | 
				
			||||||
 | 
						github.com/mattn/go-runewidth v0.0.13 // indirect
 | 
				
			||||||
 | 
						github.com/mergestat/timediff v0.0.3 // indirect
 | 
				
			||||||
 | 
						github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
 | 
				
			||||||
 | 
						github.com/pkg/errors v0.9.1 // indirect
 | 
				
			||||||
 | 
						github.com/rivo/uniseg v0.2.0 // indirect
 | 
				
			||||||
 | 
						github.com/twmb/murmur3 v1.1.3 // indirect
 | 
				
			||||||
 | 
						golang.org/x/crypto v0.1.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/sys v0.10.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/term v0.10.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/text v0.9.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										350
									
								
								go.sum
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										350
									
								
								go.sum
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,350 @@
 | 
				
			||||||
 | 
					atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8=
 | 
				
			||||||
 | 
					atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=
 | 
				
			||||||
 | 
					cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 | 
				
			||||||
 | 
					cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 | 
				
			||||||
 | 
					cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
 | 
				
			||||||
 | 
					cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 | 
				
			||||||
 | 
					cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 | 
				
			||||||
 | 
					cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
 | 
				
			||||||
 | 
					cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
 | 
				
			||||||
 | 
					cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
 | 
				
			||||||
 | 
					cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
 | 
				
			||||||
 | 
					cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 | 
				
			||||||
 | 
					cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 | 
				
			||||||
 | 
					cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 | 
				
			||||||
 | 
					cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
 | 
				
			||||||
 | 
					cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
 | 
				
			||||||
 | 
					cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 | 
				
			||||||
 | 
					cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 | 
				
			||||||
 | 
					dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 | 
				
			||||||
 | 
					github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
				
			||||||
 | 
					github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 | 
				
			||||||
 | 
					github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
 | 
				
			||||||
 | 
					github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
 | 
				
			||||||
 | 
					github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
 | 
				
			||||||
 | 
					github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k=
 | 
				
			||||||
 | 
					github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI=
 | 
				
			||||||
 | 
					github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c=
 | 
				
			||||||
 | 
					github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
 | 
				
			||||||
 | 
					github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
 | 
				
			||||||
 | 
					github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
 | 
				
			||||||
 | 
					github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
 | 
				
			||||||
 | 
					github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 | 
				
			||||||
 | 
					github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 | 
				
			||||||
 | 
					github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
 | 
				
			||||||
 | 
					github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 | 
				
			||||||
 | 
					github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
 | 
				
			||||||
 | 
					github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
 | 
				
			||||||
 | 
					github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 | 
				
			||||||
 | 
					github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
 | 
				
			||||||
 | 
					github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 | 
				
			||||||
 | 
					github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
 | 
				
			||||||
 | 
					github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 | 
					github.com/diamondburned/arikawa v1.3.2 h1:ftWgP95IJGXNvCvtO5x0QBYsnFSnIBY0SvDdGoC3ILA=
 | 
				
			||||||
 | 
					github.com/diamondburned/arikawa v1.3.2/go.mod h1:nIhVIatzTQhPUa7NB8w4koG1RF9gYbpAr8Fj8sKq660=
 | 
				
			||||||
 | 
					github.com/diamondburned/arikawa/v3 v3.1.1-0.20221103093025-87c479a2dcd4/go.mod h1:5jBSNnp82Z/EhsKa6Wk9FsOqSxfVkNZDTDBPOj47LpY=
 | 
				
			||||||
 | 
					github.com/diamondburned/arikawa/v3 v3.3.1 h1:puqs7mog383RJnUfRSiug5K+z/pC0Cbuq4yGJMQrQWg=
 | 
				
			||||||
 | 
					github.com/diamondburned/arikawa/v3 v3.3.1/go.mod h1:+ifmDonP/JdBiUOzZmVReEjPTHDUSkyqqRRmjSf9NE8=
 | 
				
			||||||
 | 
					github.com/diamondburned/ningen v1.0.0 h1:fr+7oDWA0Db73CuVeLY8SScWdW6ft/aWwkULXD0flKw=
 | 
				
			||||||
 | 
					github.com/diamondburned/ningen v1.0.0/go.mod h1:TcvJV0bK4bp7t+7m29/Tz9dCqgA0sJBKM/Igt0WkvT4=
 | 
				
			||||||
 | 
					github.com/diamondburned/ningen/v3 v3.0.0 h1:S7DF+AwOt/zuFsBMAu00mtE8MfuYqaTtDii6iJPX758=
 | 
				
			||||||
 | 
					github.com/diamondburned/ningen/v3 v3.0.0/go.mod h1:wMe9WZQiFgkH5Slr5xK8XBBqMJxWTfRDZU82wPney4Y=
 | 
				
			||||||
 | 
					github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 | 
				
			||||||
 | 
					github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 | 
				
			||||||
 | 
					github.com/ergochat/readline v0.0.5 h1:PlmCLW9HUTnVfhFburg65pQUDKb0LB43G8hS+ygEkp8=
 | 
				
			||||||
 | 
					github.com/ergochat/readline v0.0.5/go.mod h1:8RNv74chpO0eTm6rdD1H6WZGihL5rJ+RfSlhv4fIfjg=
 | 
				
			||||||
 | 
					github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
 | 
				
			||||||
 | 
					github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
 | 
				
			||||||
 | 
					github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 | 
				
			||||||
 | 
					github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 | 
				
			||||||
 | 
					github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 | 
				
			||||||
 | 
					github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
				
			||||||
 | 
					github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
				
			||||||
 | 
					github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
				
			||||||
 | 
					github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 | 
				
			||||||
 | 
					github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 | 
				
			||||||
 | 
					github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
 | 
				
			||||||
 | 
					github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 | 
				
			||||||
 | 
					github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
				
			||||||
 | 
					github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
				
			||||||
 | 
					github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
				
			||||||
 | 
					github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
 | 
				
			||||||
 | 
					github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 | 
				
			||||||
 | 
					github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 | 
				
			||||||
 | 
					github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 | 
				
			||||||
 | 
					github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 | 
				
			||||||
 | 
					github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 | 
				
			||||||
 | 
					github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
				
			||||||
 | 
					github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 | 
				
			||||||
 | 
					github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 | 
				
			||||||
 | 
					github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 | 
				
			||||||
 | 
					github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
 | 
				
			||||||
 | 
					github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 | 
				
			||||||
 | 
					github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 | 
				
			||||||
 | 
					github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 | 
				
			||||||
 | 
					github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
 | 
				
			||||||
 | 
					github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
 | 
				
			||||||
 | 
					github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
 | 
				
			||||||
 | 
					github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
 | 
				
			||||||
 | 
					github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
 | 
				
			||||||
 | 
					github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 | 
				
			||||||
 | 
					github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 | 
				
			||||||
 | 
					github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 | 
				
			||||||
 | 
					github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 | 
				
			||||||
 | 
					github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 | 
				
			||||||
 | 
					github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 | 
				
			||||||
 | 
					github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 | 
				
			||||||
 | 
					github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 | 
				
			||||||
 | 
					github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 | 
				
			||||||
 | 
					github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
 | 
				
			||||||
 | 
					github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
 | 
				
			||||||
 | 
					github.com/kopoli/go-terminal-size v0.0.0-20170219200355-5c97524c8b54 h1:0SMHxjkLKNawqUjjnMlCtEdj6uWZjv0+qDZ3F6GOADI=
 | 
				
			||||||
 | 
					github.com/kopoli/go-terminal-size v0.0.0-20170219200355-5c97524c8b54/go.mod h1:bm7MVZZvHQBfqHG5X59jrRE/3ak6HvK+/Zb6aZhLR2s=
 | 
				
			||||||
 | 
					github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
				
			||||||
 | 
					github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
				
			||||||
 | 
					github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
				
			||||||
 | 
					github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 | 
				
			||||||
 | 
					github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
 | 
				
			||||||
 | 
					github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 | 
				
			||||||
 | 
					github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
 | 
				
			||||||
 | 
					github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 | 
				
			||||||
 | 
					github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 | 
				
			||||||
 | 
					github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
 | 
				
			||||||
 | 
					github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 | 
				
			||||||
 | 
					github.com/mergestat/timediff v0.0.3 h1:ucCNh4/ZrTPjFZ081PccNbhx9spymCJkFxSzgVuPU+Y=
 | 
				
			||||||
 | 
					github.com/mergestat/timediff v0.0.3/go.mod h1:yvMUaRu2oetc+9IbPLYBJviz6sA7xz8OXMDfhBl7YSI=
 | 
				
			||||||
 | 
					github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
 | 
				
			||||||
 | 
					github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
 | 
				
			||||||
 | 
					github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw=
 | 
				
			||||||
 | 
					github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI=
 | 
				
			||||||
 | 
					github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 | 
				
			||||||
 | 
					github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
				
			||||||
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
 | 
					github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 | 
				
			||||||
 | 
					github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
 | 
				
			||||||
 | 
					github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
 | 
				
			||||||
 | 
					github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
 | 
				
			||||||
 | 
					github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=
 | 
				
			||||||
 | 
					github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
 | 
				
			||||||
 | 
					github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
 | 
				
			||||||
 | 
					github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
 | 
				
			||||||
 | 
					github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 | 
				
			||||||
 | 
					github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 | 
				
			||||||
 | 
					github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 | 
				
			||||||
 | 
					github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
 | 
				
			||||||
 | 
					github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 | 
				
			||||||
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
				
			||||||
 | 
					github.com/twmb/murmur3 v1.1.3 h1:D83U0XYKcHRYwYIpBKf3Pks91Z0Byda/9SJ8B6EMRcA=
 | 
				
			||||||
 | 
					github.com/twmb/murmur3 v1.1.3/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
 | 
				
			||||||
 | 
					github.com/wader/readline v0.0.0-20230307172220-bcb7158e7448 h1:AzpBtmgdXa3uznrb3esNeEoaLqtNEwckRmaUH0qWD6w=
 | 
				
			||||||
 | 
					github.com/wader/readline v0.0.0-20230307172220-bcb7158e7448/go.mod h1:Zgz8IJWvJoe7NK23CCPpC109XMCqJCpUhpHcnnA4XaM=
 | 
				
			||||||
 | 
					github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
 | 
				
			||||||
 | 
					github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
				
			||||||
 | 
					github.com/yuin/goldmark v1.3.2/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 | 
				
			||||||
 | 
					github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
				
			||||||
 | 
					go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 | 
				
			||||||
 | 
					go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 | 
				
			||||||
 | 
					go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 | 
				
			||||||
 | 
					go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 | 
				
			||||||
 | 
					go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
 | 
				
			||||||
 | 
					golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 | 
				
			||||||
 | 
					golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 | 
				
			||||||
 | 
					golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
 | 
				
			||||||
 | 
					golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
 | 
				
			||||||
 | 
					golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
 | 
				
			||||||
 | 
					golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 | 
				
			||||||
 | 
					golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 | 
				
			||||||
 | 
					golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 | 
				
			||||||
 | 
					golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 | 
				
			||||||
 | 
					golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 | 
				
			||||||
 | 
					golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 | 
				
			||||||
 | 
					golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 | 
				
			||||||
 | 
					golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 | 
				
			||||||
 | 
					golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 | 
				
			||||||
 | 
					golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 | 
				
			||||||
 | 
					golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 | 
				
			||||||
 | 
					golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 | 
				
			||||||
 | 
					golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
 | 
				
			||||||
 | 
					golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 | 
				
			||||||
 | 
					golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 | 
				
			||||||
 | 
					golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 | 
				
			||||||
 | 
					golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 | 
				
			||||||
 | 
					golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 | 
				
			||||||
 | 
					golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 | 
				
			||||||
 | 
					golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
				
			||||||
 | 
					golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
				
			||||||
 | 
					golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 | 
				
			||||||
 | 
					golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 | 
				
			||||||
 | 
					golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 | 
				
			||||||
 | 
					golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 | 
				
			||||||
 | 
					golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 | 
				
			||||||
 | 
					golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20211001092434-39dca1131b70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
				
			||||||
 | 
					golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
				
			||||||
 | 
					golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
				
			||||||
 | 
					golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
				
			||||||
 | 
					golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
				
			||||||
 | 
					golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
 | 
				
			||||||
 | 
					golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
 | 
				
			||||||
 | 
					golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
				
			||||||
 | 
					golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
 | 
				
			||||||
 | 
					golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 | 
				
			||||||
 | 
					golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
				
			||||||
 | 
					golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
				
			||||||
 | 
					golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
				
			||||||
 | 
					golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
 | 
				
			||||||
 | 
					golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
				
			||||||
 | 
					golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
				
			||||||
 | 
					golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
				
			||||||
 | 
					golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
				
			||||||
 | 
					google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 | 
				
			||||||
 | 
					google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 | 
				
			||||||
 | 
					google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 | 
				
			||||||
 | 
					google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 | 
				
			||||||
 | 
					google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 | 
				
			||||||
 | 
					google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 | 
				
			||||||
 | 
					google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
 | 
				
			||||||
 | 
					google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
 | 
				
			||||||
 | 
					google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 | 
				
			||||||
 | 
					google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 | 
				
			||||||
 | 
					google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 | 
				
			||||||
 | 
					google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 | 
				
			||||||
 | 
					google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
 | 
				
			||||||
 | 
					google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 | 
				
			||||||
 | 
					google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 | 
				
			||||||
 | 
					google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 | 
				
			||||||
 | 
					google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 | 
				
			||||||
 | 
					google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 | 
				
			||||||
 | 
					google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 | 
				
			||||||
 | 
					google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 | 
				
			||||||
 | 
					google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
					gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
 | 
					honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
				
			||||||
 | 
					honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
				
			||||||
 | 
					honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
				
			||||||
 | 
					honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
				
			||||||
 | 
					honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 | 
				
			||||||
 | 
					rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 | 
				
			||||||
 | 
					rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 | 
				
			||||||
 | 
					rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 | 
				
			||||||
							
								
								
									
										547
									
								
								lib/messages.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										547
									
								
								lib/messages.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,547 @@
 | 
				
			||||||
 | 
					package lib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"math"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
						"unicode/utf8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/state"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/discord"
 | 
				
			||||||
 | 
						"github.com/mergestat/timediff"
 | 
				
			||||||
 | 
						"github.com/mgutz/ansi"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var REGEX_CODEBLOCK = regexp.MustCompile(`(?i)\x60\x60\x60(?:([a-z0-9_+\-\.]+?)\n)?\n*([^\n](?:.|\n)*?)\n*\x60\x60\x60`)
 | 
				
			||||||
 | 
					var REGEX_MENTION = regexp.MustCompile(`<@!?(\d+)>`)
 | 
				
			||||||
 | 
					var REGEX_ROLE_MENTION = regexp.MustCompile(`<@&(\d+)>`)
 | 
				
			||||||
 | 
					var REGEX_CHANNEL = regexp.MustCompile(`<#(\d+)>`)
 | 
				
			||||||
 | 
					var REGEX_EMOTE = regexp.MustCompile(`<(?:\x{200b}|&)?a?:(\w+):(\d+)>`)
 | 
				
			||||||
 | 
					var REGEX_COMMAND = regexp.MustCompile(`</([^\s]+?):(\d+)>`)
 | 
				
			||||||
 | 
					var REGEX_BLOCKQUOTE = regexp.MustCompile(`^ *>>?>? +`)
 | 
				
			||||||
 | 
					var REGEX_GREENTEXT = regexp.MustCompile(`^(>.+?)(?:\n|$)`)
 | 
				
			||||||
 | 
					var REGEX_SPOILER = regexp.MustCompile(`\|\|(.+?)\|\|`)
 | 
				
			||||||
 | 
					var REGEX_BOLD = regexp.MustCompile(`\*\*(.+?)\*\*`)
 | 
				
			||||||
 | 
					var REGEX_UNDERLINE = regexp.MustCompile(`__(.+?)__`)
 | 
				
			||||||
 | 
					var REGEX_ITALIC_1 = regexp.MustCompile(`\*(.+?)\*`)
 | 
				
			||||||
 | 
					var REGEX_ITALIC_2 = regexp.MustCompile(`_(.+?)_`)
 | 
				
			||||||
 | 
					var REGEX_STRIKE = regexp.MustCompile(`~~(.+?)~~`)
 | 
				
			||||||
 | 
					var REGEX_3Y3 = regexp.MustCompile(`[\x{e0020}-\x{e007e}]{1,}`)
 | 
				
			||||||
 | 
					var REGEX_TIMESTAMP = regexp.MustCompile(`<t:(-?\d{1,17})(?::(t|T|d|D|f|F|R))?>`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MessageOptions struct {
 | 
				
			||||||
 | 
					  Content string
 | 
				
			||||||
 | 
					  Name string
 | 
				
			||||||
 | 
					  Channel discord.ChannelID
 | 
				
			||||||
 | 
					  Bot bool
 | 
				
			||||||
 | 
					  Webhook bool
 | 
				
			||||||
 | 
					  Attachments []discord.Attachment
 | 
				
			||||||
 | 
					  Stickers []discord.StickerItem
 | 
				
			||||||
 | 
					  Reply *discord.Message
 | 
				
			||||||
 | 
					  Timestamp time.Time
 | 
				
			||||||
 | 
					  IsMention bool
 | 
				
			||||||
 | 
					  IsDM bool
 | 
				
			||||||
 | 
					  IsJoin bool
 | 
				
			||||||
 | 
					  IsPin bool
 | 
				
			||||||
 | 
					  IsDump bool
 | 
				
			||||||
 | 
					  NoColor bool
 | 
				
			||||||
 | 
					  InHistory bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Parse3y3(content string) string {
 | 
				
			||||||
 | 
					  out := []rune{}
 | 
				
			||||||
 | 
					  for i, w := 0, 0; i < len(content); i += w {
 | 
				
			||||||
 | 
					    runeValue, width := utf8.DecodeRuneInString(content[i:])
 | 
				
			||||||
 | 
					    w = width
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    out = append(out, rune(int(runeValue) - 0xe0000))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return string(out)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ReplaceStyledMarkdown(content string) string {
 | 
				
			||||||
 | 
					  content = REGEX_BLOCKQUOTE.ReplaceAllString(content, ansi.Color("\u258e", "black+h"))
 | 
				
			||||||
 | 
					  content = REGEX_GREENTEXT.ReplaceAllStringFunc(content, func(match string) string {
 | 
				
			||||||
 | 
					    return ansi.Color(match, "green")
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if state.GetConfigValue("enable3y3") == "true" {
 | 
				
			||||||
 | 
					    parsed := REGEX_3Y3.FindString(content)
 | 
				
			||||||
 | 
					    parsed = Parse3y3(parsed)
 | 
				
			||||||
 | 
					    parsed = "\033[3m" + parsed + "\033[23m"
 | 
				
			||||||
 | 
					    content = REGEX_3Y3.ReplaceAllString(content, ansi.Color(parsed, "magenta"))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  content = REGEX_SPOILER.ReplaceAllString(content, "\033[30m\033[40m$1\033[39m\033[49m")
 | 
				
			||||||
 | 
					  content = REGEX_STRIKE.ReplaceAllString(content, "\033[9m$1\033[29m")
 | 
				
			||||||
 | 
					  content = REGEX_BOLD.ReplaceAllString(content, "\033[1m$1\033[22m")
 | 
				
			||||||
 | 
					  content = REGEX_UNDERLINE.ReplaceAllString(content, "\033[4m$1\033[24m")
 | 
				
			||||||
 | 
					  content = REGEX_ITALIC_1.ReplaceAllString(content, "\033[3m$1\033[23m")
 | 
				
			||||||
 | 
					  content = REGEX_ITALIC_2.ReplaceAllString(content, "\033[3m$1\033[23m")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return content
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func replaceAllWithCallback(re regexp.Regexp, content string, callback func(matches []string) string) string {
 | 
				
			||||||
 | 
					  return re.ReplaceAllStringFunc(content, func(match string) string {
 | 
				
			||||||
 | 
					    matches := re.FindStringSubmatch(match)
 | 
				
			||||||
 | 
					    return callback(matches)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ReplaceMarkdown(content string, noColor bool) string {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  content = replaceAllWithCallback(*REGEX_MENTION, content, func(matches []string) string {
 | 
				
			||||||
 | 
					    id := matches[1]
 | 
				
			||||||
 | 
					    parsedId, err := discord.ParseSnowflake(id)
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					      return "@Unknown User"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    currentGuild := state.GetCurrentGuild()
 | 
				
			||||||
 | 
					    if currentGuild == "" {
 | 
				
			||||||
 | 
					      user, err := client.User(discord.UserID(parsedId))
 | 
				
			||||||
 | 
					      if err != nil {
 | 
				
			||||||
 | 
					        return "@Unknown User"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return "@" + user.Username
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      parsedGuildId, err := discord.ParseSnowflake(currentGuild)
 | 
				
			||||||
 | 
					      if err != nil {
 | 
				
			||||||
 | 
					        return "@Unknown User"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      member, err := client.MemberStore.Member(discord.GuildID(parsedGuildId), discord.UserID(parsedId))
 | 
				
			||||||
 | 
					      if err != nil {
 | 
				
			||||||
 | 
					        return "@Unknown User"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return "@" + member.User.Username
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  content = replaceAllWithCallback(*REGEX_ROLE_MENTION, content, func(matches []string) string {
 | 
				
			||||||
 | 
					    id := matches[1]
 | 
				
			||||||
 | 
					    parsedId, err := discord.ParseSnowflake(id)
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					      return "[@Unknown Role]"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    currentGuild := state.GetCurrentGuild()
 | 
				
			||||||
 | 
					    if currentGuild == "" {
 | 
				
			||||||
 | 
					      return "[@Unknown Role]"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    parsedGuildId, err := discord.ParseSnowflake(currentGuild)
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					      return "[@Unknown Role]"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    role, err := client.RoleStore.Role(discord.GuildID(parsedGuildId), discord.RoleID(parsedId))
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					      return "[@Unknown Role]"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return fmt.Sprintf("[@%s]", role.Name)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  content = replaceAllWithCallback(*REGEX_CHANNEL, content, func(matches []string) string {
 | 
				
			||||||
 | 
					    id := matches[1]
 | 
				
			||||||
 | 
					    parsedId, err := discord.ParseSnowflake(id)
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					      return "#Unknown"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    channel, err := client.ChannelStore.Channel(discord.ChannelID(parsedId))
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					      return "#Unknown"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return "#" + channel.Name
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  content = REGEX_EMOTE.ReplaceAllString(content, ":$1:")
 | 
				
			||||||
 | 
					  content = REGEX_COMMAND.ReplaceAllString(content, "/$1")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  content = replaceAllWithCallback(*REGEX_TIMESTAMP, content, func (matches []string) string {
 | 
				
			||||||
 | 
					    timestamp, err := strconv.Atoi(matches[1])
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					      return "Invalid Date"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    timeObj := time.Unix(int64(timestamp), 0).UTC()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    format := matches[2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch format {
 | 
				
			||||||
 | 
					      case "t":
 | 
				
			||||||
 | 
					        return timeObj.Format("15:04")
 | 
				
			||||||
 | 
					      case "T":
 | 
				
			||||||
 | 
					        return timeObj.Format("15:04:05")
 | 
				
			||||||
 | 
					      case "d":
 | 
				
			||||||
 | 
					        return timeObj.Format("2006/01/02")
 | 
				
			||||||
 | 
					      case "D":
 | 
				
			||||||
 | 
					        return timeObj.Format("2 January 2006")
 | 
				
			||||||
 | 
					      case "f":
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        return timeObj.Format("2 January 2006 15:04")
 | 
				
			||||||
 | 
					      case "F":
 | 
				
			||||||
 | 
					        return timeObj.Format("Monday, 2 January 2006 15:04")
 | 
				
			||||||
 | 
					      case "R":
 | 
				
			||||||
 | 
					        return timediff.TimeDiff(timeObj)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return "Invalid Date"
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if !noColor {
 | 
				
			||||||
 | 
					    content = ReplaceStyledMarkdown(content)
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    if state.GetConfigValue("enable3y3") == "true" {
 | 
				
			||||||
 | 
					      parsed := REGEX_3Y3.FindString(content)
 | 
				
			||||||
 | 
					      parsed = Parse3y3(parsed)
 | 
				
			||||||
 | 
					      content = REGEX_3Y3.ReplaceAllString(content, "<3y3:" + parsed + ">")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return content
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func FormatMessage(options MessageOptions) []string {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lines := make([]string, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  timestamp := options.Timestamp.UTC().Format("[15:04:05]")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  nameLength := utf8.RuneCountInString(options.Name) + 2
 | 
				
			||||||
 | 
					  stateNameLength := state.GetNameLength()
 | 
				
			||||||
 | 
					  if nameLength > stateNameLength {
 | 
				
			||||||
 | 
					    state.SetNameLength(nameLength)
 | 
				
			||||||
 | 
					    stateNameLength = nameLength
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if options.Reply != nil {
 | 
				
			||||||
 | 
					    nameColor := "cyan+b"
 | 
				
			||||||
 | 
					    if options.Reply.Author.Bot {
 | 
				
			||||||
 | 
					      nameColor = "yellow+b"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    headerLength := 6 + utf8.RuneCountInString(options.Reply.Author.Username)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    content := options.Reply.Content
 | 
				
			||||||
 | 
					    replyContent := strings.ReplaceAll(content, "\n", " ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    replyContent = ReplaceMarkdown(replyContent, options.NoColor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    attachmentCount := len(options.Reply.Attachments)
 | 
				
			||||||
 | 
					    if attachmentCount > 0 {
 | 
				
			||||||
 | 
					      attachmentPlural := ""
 | 
				
			||||||
 | 
					      if attachmentCount > 1 {
 | 
				
			||||||
 | 
					        attachmentPlural = "s"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      replyContent = strings.TrimSpace(replyContent + fmt.Sprintf(" <%d attachment%s>", attachmentCount, attachmentPlural))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    stickerCount := len(options.Reply.Stickers)
 | 
				
			||||||
 | 
					    if stickerCount > 0 {
 | 
				
			||||||
 | 
					      stickerPlural := ""
 | 
				
			||||||
 | 
					      if stickerCount > 0 {
 | 
				
			||||||
 | 
					        stickerPlural = "s"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      replyContent = strings.TrimSpace(replyContent + fmt.Sprintf(" <%d sticker%s>", stickerCount, stickerPlural))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    length := headerLength + utf8.RuneCountInString(replyContent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    replySymbol := " \u00bb "
 | 
				
			||||||
 | 
					    if !options.NoColor {
 | 
				
			||||||
 | 
					      replySymbol = ansi.Color(replySymbol, "white+b")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name := fmt.Sprintf("[%s] ", options.Reply.Author.Username)
 | 
				
			||||||
 | 
					    if !options.NoColor {
 | 
				
			||||||
 | 
					      name = ansi.Color(name, nameColor)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    moreContent := "\u2026"
 | 
				
			||||||
 | 
					    if !options.NoColor {
 | 
				
			||||||
 | 
					      moreContent = ansi.Color(moreContent, "reset")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if length > 79 {
 | 
				
			||||||
 | 
					      replyContent = replyContent[:79 - headerLength] + moreContent
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    lines = append(lines, replySymbol, name, replyContent, "\n\r")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if options.IsDump {
 | 
				
			||||||
 | 
					    if options.InHistory {
 | 
				
			||||||
 | 
					      headerLength := 80 - (utf8.RuneCountInString(options.Name) + 5)
 | 
				
			||||||
 | 
					      dumpHeader := fmt.Sprintf("--- %s %s\n\r", options.Name, strings.Repeat("-", headerLength))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      contentLines := strings.Split(options.Content, "\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      lines = append(lines, dumpHeader)
 | 
				
			||||||
 | 
					      for _, line := range contentLines {
 | 
				
			||||||
 | 
					        lines = append(lines, line + "\n\r")
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      lines = append(lines, dumpHeader)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      wordCount := len(strings.Split(options.Content, " "))
 | 
				
			||||||
 | 
					      lineCount := len(strings.Split(options.Content, "\n"))
 | 
				
			||||||
 | 
					      wordsPlural := ""
 | 
				
			||||||
 | 
					      linesPlural := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if wordCount > 1 {
 | 
				
			||||||
 | 
					        wordsPlural = "s"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if lineCount > 1 {
 | 
				
			||||||
 | 
					        linesPlural = "s"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      str := fmt.Sprintf("<%s DUMPs in %d characters of %d word%s in %d line%s>", options.Name, len(options.Content), wordCount, wordsPlural, lineCount, linesPlural)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if !options.NoColor {
 | 
				
			||||||
 | 
					        str = ansi.Color(str, "yellow+b")
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      lines = append(lines, str + "\n\r")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    content := options.Content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if options.IsDM {
 | 
				
			||||||
 | 
					      name := fmt.Sprintf("*%s*", options.Name)
 | 
				
			||||||
 | 
					      if !options.NoColor {
 | 
				
			||||||
 | 
					        name = ansi.Color(name, "red+b")
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      content = ReplaceMarkdown(content, options.NoColor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      lines = append(lines, fmt.Sprintf("%s %s\x07\n\r", name, content))
 | 
				
			||||||
 | 
					    } else if utf8.RuneCountInString(content) > 1 &&
 | 
				
			||||||
 | 
					    (strings.HasPrefix(content, "*") && strings.HasSuffix(content, "*") && !strings.HasPrefix(content, "**") && !strings.HasSuffix(content, "**")) ||
 | 
				
			||||||
 | 
					    (strings.HasPrefix(content, "_") && strings.HasSuffix(content, "_") && !strings.HasPrefix(content, "__") && !strings.HasSuffix(content, "__")) {
 | 
				
			||||||
 | 
					      str := fmt.Sprintf("<%s %s>", options.Name, content[1:len(content)-1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if !options.NoColor {
 | 
				
			||||||
 | 
					        str = ansi.Color(str, "green+b")
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      lines = append(lines, str + "\n\r")
 | 
				
			||||||
 | 
					    } else if options.IsJoin {
 | 
				
			||||||
 | 
					      channel, err := client.ChannelStore.Channel(options.Channel)
 | 
				
			||||||
 | 
					      if err != nil {
 | 
				
			||||||
 | 
					        return lines
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      guild, err := client.GuildStore.Guild(channel.GuildID)
 | 
				
			||||||
 | 
					      if err != nil {
 | 
				
			||||||
 | 
					        return lines
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      str := fmt.Sprintf("%s %s has joined %s", timestamp, options.Name, guild.Name)
 | 
				
			||||||
 | 
					      if !options.NoColor {
 | 
				
			||||||
 | 
					        str = ansi.Color(str, "yellow+b")
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      lines = append(lines, str + "\n\r")
 | 
				
			||||||
 | 
					    } else if options.IsPin {
 | 
				
			||||||
 | 
					      str := fmt.Sprintf("%s %s pinned a message to this channel", timestamp, options.Name)
 | 
				
			||||||
 | 
					      if !options.NoColor {
 | 
				
			||||||
 | 
					        str = ansi.Color(str, "yellow+b")
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      lines = append(lines, str + "\n\r")
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      nameColor := "cyan+b"
 | 
				
			||||||
 | 
					      if options.IsMention {
 | 
				
			||||||
 | 
					        nameColor = "red+b"
 | 
				
			||||||
 | 
					      } else if options.Webhook {
 | 
				
			||||||
 | 
					        nameColor = "magenta+b"
 | 
				
			||||||
 | 
					      } else if options.Bot {
 | 
				
			||||||
 | 
					        nameColor = "yellow+b"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      content = ReplaceMarkdown(content, options.NoColor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      name := fmt.Sprintf("[%s]", options.Name)
 | 
				
			||||||
 | 
					      if !options.NoColor {
 | 
				
			||||||
 | 
					        name = ansi.Color(name, nameColor)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      padding := strings.Repeat(" ", int(math.Abs(float64(stateNameLength) - float64(nameLength))) + 1)
 | 
				
			||||||
 | 
					      str := name + padding + content
 | 
				
			||||||
 | 
					      if options.IsMention {
 | 
				
			||||||
 | 
					        str = str + "\x07"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      lines = append(lines, str + "\n\r")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if len(options.Attachments) > 0 {
 | 
				
			||||||
 | 
					    for _, attachment := range options.Attachments {
 | 
				
			||||||
 | 
					      str := fmt.Sprintf("<attachment: %s >", attachment.URL)
 | 
				
			||||||
 | 
					      if !options.NoColor {
 | 
				
			||||||
 | 
					        str = ansi.Color(str, "yellow+b")
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      lines = append(lines, str + "\n\r")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if len(options.Stickers) > 0 {
 | 
				
			||||||
 | 
					    for _, sticker := range options.Stickers {
 | 
				
			||||||
 | 
					      str := fmt.Sprintf("<sticker: \"%s\" https://cdn.discordapp.com/stickers/%s.png >", sticker.Name, sticker.ID)
 | 
				
			||||||
 | 
					      if !options.NoColor {
 | 
				
			||||||
 | 
					        str = ansi.Color(str, "yellow+b")
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      lines = append(lines, str + "\n\r")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // TODO: links
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // TODO: embeds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // TODO: lines output for history
 | 
				
			||||||
 | 
					  return lines
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ProcessMessage(msg discord.Message, options MessageOptions) []string {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					  lines := make([]string, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  channel, err := client.ChannelStore.Channel(msg.ChannelID)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return lines
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  guild, err := client.GuildStore.Guild(channel.GuildID)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return lines
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  self, err := client.MeStore.Me()
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return lines
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  selfMember, err := client.MemberStore.Member(guild.ID, self.ID)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return lines
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  hasMentionedRole := false
 | 
				
			||||||
 | 
					  for _, role := range msg.MentionRoleIDs {
 | 
				
			||||||
 | 
					    for _, selfRole := range selfMember.RoleIDs {
 | 
				
			||||||
 | 
					      if role == selfRole {
 | 
				
			||||||
 | 
					        hasMentionedRole = true
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  isDirectlyMentioned := false
 | 
				
			||||||
 | 
					  for _, user := range msg.Mentions {
 | 
				
			||||||
 | 
					    if user.ID == self.ID {
 | 
				
			||||||
 | 
					      isDirectlyMentioned = true
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  isPing := msg.MentionEveryone || hasMentionedRole || isDirectlyMentioned
 | 
				
			||||||
 | 
					  isDM := channel.Type == discord.DirectMessage || channel.Type == discord.GroupDM
 | 
				
			||||||
 | 
					  isEdit := msg.EditedTimestamp.IsValid()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  currentChannel := state.GetCurrentChannel()
 | 
				
			||||||
 | 
					  isCurrentChannel := currentChannel == msg.ChannelID.String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if !isCurrentChannel && !isDM && !isPing && !options.InHistory {
 | 
				
			||||||
 | 
					    return lines
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if isPing && !isCurrentChannel && !isDM && !options.InHistory {
 | 
				
			||||||
 | 
					    str := fmt.Sprintf("**mentioned by %s in #%s in %s**", msg.Author.Username, channel.Name, guild.Name)
 | 
				
			||||||
 | 
					    if !options.NoColor {
 | 
				
			||||||
 | 
					      str = ansi.Color(str, "red+b")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    str = str + "\x07\n\r"
 | 
				
			||||||
 | 
					    lines = append(lines, str)
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    content := msg.Content
 | 
				
			||||||
 | 
					    if isEdit {
 | 
				
			||||||
 | 
					      content = content + " (edited)"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    isDump := REGEX_CODEBLOCK.MatchString(content)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if strings.Index(content, "\n") > -1 && !isDump {
 | 
				
			||||||
 | 
					      for i, line := range strings.Split(content, "\n") {
 | 
				
			||||||
 | 
					        options.Content = line
 | 
				
			||||||
 | 
					        options.Name = msg.Author.Username
 | 
				
			||||||
 | 
					        options.Channel = msg.ChannelID
 | 
				
			||||||
 | 
					        options.Bot = msg.Author.Bot
 | 
				
			||||||
 | 
					        options.Webhook = msg.WebhookID.IsValid()
 | 
				
			||||||
 | 
					        options.Attachments = msg.Attachments
 | 
				
			||||||
 | 
					        options.Stickers = msg.Stickers
 | 
				
			||||||
 | 
					        if i == 0 {
 | 
				
			||||||
 | 
					          options.Reply = msg.ReferencedMessage
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          options.Reply = nil
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        options.Timestamp = time.Time(msg.Timestamp)
 | 
				
			||||||
 | 
					        options.IsMention = isPing
 | 
				
			||||||
 | 
					        options.IsDM = isDM
 | 
				
			||||||
 | 
					        options.IsJoin = msg.Type == discord.GuildMemberJoinMessage
 | 
				
			||||||
 | 
					        options.IsPin = msg.Type == discord.ChannelPinnedMessage
 | 
				
			||||||
 | 
					        options.IsDump = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        msgLines := FormatMessage(options)
 | 
				
			||||||
 | 
					        for _, line := range msgLines {
 | 
				
			||||||
 | 
					          lines = append(lines, line)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      options.Content = content
 | 
				
			||||||
 | 
					      options.Name = msg.Author.Username
 | 
				
			||||||
 | 
					      options.Channel = msg.ChannelID
 | 
				
			||||||
 | 
					      options.Bot = msg.Author.Bot
 | 
				
			||||||
 | 
					      options.Webhook = msg.WebhookID.IsValid()
 | 
				
			||||||
 | 
					      options.Attachments = msg.Attachments
 | 
				
			||||||
 | 
					      options.Stickers = msg.Stickers
 | 
				
			||||||
 | 
					      options.Reply = msg.ReferencedMessage
 | 
				
			||||||
 | 
					      options.Timestamp = time.Time(msg.Timestamp)
 | 
				
			||||||
 | 
					      options.IsMention = isPing
 | 
				
			||||||
 | 
					      options.IsDM = isDM
 | 
				
			||||||
 | 
					      options.IsJoin = msg.Type == discord.GuildMemberJoinMessage
 | 
				
			||||||
 | 
					      options.IsPin = msg.Type == discord.ChannelPinnedMessage
 | 
				
			||||||
 | 
					      options.IsDump = isDump
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      lines = FormatMessage(options)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return lines
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ProcessQueue() {
 | 
				
			||||||
 | 
					  queue := state.GetMessageQueue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for _, msg := range queue {
 | 
				
			||||||
 | 
					    lines := ProcessMessage(msg, MessageOptions{NoColor: state.HasNoColor()})
 | 
				
			||||||
 | 
					    for _, line := range lines {
 | 
				
			||||||
 | 
					      fmt.Print(line)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state.EmptyMessageQueue()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										121
									
								
								lib/presence.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								lib/presence.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,121 @@
 | 
				
			||||||
 | 
					package lib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/state"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/discord"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/gateway"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func UpdatePresence() {
 | 
				
			||||||
 | 
					  client := state.GetClient()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  self, err := client.MeStore.Me()
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afk := state.IsAFK()
 | 
				
			||||||
 | 
					  presence := gateway.UpdatePresenceCommand{
 | 
				
			||||||
 | 
					    Since: 0,
 | 
				
			||||||
 | 
					    Activities: make([]discord.Activity, 0),
 | 
				
			||||||
 | 
					    AFK: false,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  currentGuild := state.GetCurrentGuild()
 | 
				
			||||||
 | 
					  currentChannel := state.GetCurrentChannel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  parsedGuildId, err := discord.ParseSnowflake(currentGuild)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  parsedChannelId, err := discord.ParseSnowflake(currentChannel)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var activity discord.Activity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  startTime := state.GetStartTime()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if self.Bot {
 | 
				
			||||||
 | 
					    activity = discord.Activity{
 | 
				
			||||||
 | 
					      Type: discord.GameActivity,
 | 
				
			||||||
 | 
					      Name: "comcord",
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if currentGuild != "" && currentChannel != "" {
 | 
				
			||||||
 | 
					      guild, guildErr := client.GuildStore.Guild(discord.GuildID(parsedGuildId))
 | 
				
			||||||
 | 
					      channel, channelErr := client.ChannelStore.Channel(discord.ChannelID(parsedChannelId))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if guildErr == nil && channelErr == nil {
 | 
				
			||||||
 | 
					        activity.Type = discord.CustomActivity
 | 
				
			||||||
 | 
					        activity.Name = "comcord"
 | 
				
			||||||
 | 
					        activity.State = fmt.Sprintf("#%s - %s | comcord", channel.Name, guild.Name)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if afk {
 | 
				
			||||||
 | 
					      activity.State = activity.State + " [AFK]"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    parsedAppId, err := discord.ParseSnowflake("1026163285877325874")
 | 
				
			||||||
 | 
					    if err != nil {
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    activity = discord.Activity{
 | 
				
			||||||
 | 
					      Type: 0,
 | 
				
			||||||
 | 
					      AppID: discord.AppID(parsedAppId),
 | 
				
			||||||
 | 
					      Name: "comcord",
 | 
				
			||||||
 | 
					      Timestamps: &discord.ActivityTimestamps{
 | 
				
			||||||
 | 
					        Start: discord.UnixMsTimestamp(startTime.Unix()),
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      /*Buttons: make([]string, 0),
 | 
				
			||||||
 | 
					      Metadata: ActivityMetadata{
 | 
				
			||||||
 | 
					        ButtonURLs: make([]string, 0),
 | 
				
			||||||
 | 
					      },*/
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //activity.Buttons = append(activity.Buttons, "comcord Repo")
 | 
				
			||||||
 | 
					    //activity.Metadata.ButtonURLs = append(activity.Metadata.ButtonURLs, "https://gitdab.com/Cynosphere/comcord")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if currentGuild != "" && currentChannel != "" {
 | 
				
			||||||
 | 
					      guild, guildErr := client.GuildStore.Guild(discord.GuildID(parsedGuildId))
 | 
				
			||||||
 | 
					      channel, channelErr := client.ChannelStore.Channel(discord.ChannelID(parsedChannelId))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if guildErr == nil && channelErr == nil {
 | 
				
			||||||
 | 
					        activity.Details = fmt.Sprintf("#%s - %s", channel.Name, guild.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        activity.Assets = &discord.ActivityAssets{}
 | 
				
			||||||
 | 
					        activity.Assets.LargeText = guild.Name
 | 
				
			||||||
 | 
					        if guild.Icon != "" {
 | 
				
			||||||
 | 
					          activity.Assets.LargeImage = fmt.Sprintf("mp:icons/%s/%s.png?size=1024", guild.ID, guild.Icon)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if afk {
 | 
				
			||||||
 | 
					      activity.State = "AFK"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  activity.CreatedAt = discord.UnixTimestamp(startTime.Unix())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  presence.Activities = append(presence.Activities, activity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  defaultStatus := state.GetConfigValue("defaultStatus")
 | 
				
			||||||
 | 
					  if defaultStatus != "" {
 | 
				
			||||||
 | 
					    presence.Status = discord.Status(defaultStatus)
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    if afk {
 | 
				
			||||||
 | 
					      presence.Status = discord.IdleStatus
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      presence.Status = discord.OnlineStatus
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  client.Gateway().Send(context.Background(), &presence)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										32
									
								
								lib/prompt.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								lib/prompt.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,32 @@
 | 
				
			||||||
 | 
					package lib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/state"
 | 
				
			||||||
 | 
						"github.com/ergochat/readline"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func MakePrompt(prompt string, uniqueLine bool, callback func(input string, interrupt bool)) {
 | 
				
			||||||
 | 
					  state.SetInPrompt(true)
 | 
				
			||||||
 | 
					  state.SetPromptText(prompt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  rl, _ := readline.NewFromConfig(&readline.Config{
 | 
				
			||||||
 | 
					    Prompt: prompt,
 | 
				
			||||||
 | 
					    UniqueEditLine: uniqueLine,
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  defer rl.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  input, err := rl.Readline()
 | 
				
			||||||
 | 
					  input = strings.TrimSpace(input)
 | 
				
			||||||
 | 
					  rl.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  interrupt := err == readline.ErrInterrupt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  callback(input, interrupt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state.SetInPrompt(false)
 | 
				
			||||||
 | 
					  state.SetPromptText("")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ProcessQueue()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										79
									
								
								lib/util.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								lib/util.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,79 @@
 | 
				
			||||||
 | 
					package lib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "github.com/diamondburned/arikawa/v3/discord"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GuildPermissionsOf(guild discord.Guild, member discord.Member) discord.Permissions {
 | 
				
			||||||
 | 
					  if guild.OwnerID == member.User.ID {
 | 
				
			||||||
 | 
					    return discord.PermissionAll
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var perm discord.Permissions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for _, role := range guild.Roles {
 | 
				
			||||||
 | 
					    if role.ID == discord.RoleID(guild.ID) {
 | 
				
			||||||
 | 
					      perm |= role.Permissions
 | 
				
			||||||
 | 
					      break
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if perm.Has(discord.PermissionAdministrator) {
 | 
				
			||||||
 | 
					    return discord.PermissionAll
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for _, role := range guild.Roles {
 | 
				
			||||||
 | 
					    for _, id := range member.RoleIDs {
 | 
				
			||||||
 | 
					      if id == role.ID {
 | 
				
			||||||
 | 
					        perm |= role.Permissions
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if perm.Has(discord.PermissionAdministrator) {
 | 
				
			||||||
 | 
					    return discord.PermissionAll
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return perm
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ChannelPermissionsOf(guild discord.Guild, channel discord.Channel, member discord.Member) discord.Permissions {
 | 
				
			||||||
 | 
					  perm := GuildPermissionsOf(guild, member)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if perm.Has(discord.PermissionAdministrator) {
 | 
				
			||||||
 | 
					    return discord.PermissionAll
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for _, overwrite := range channel.Overwrites {
 | 
				
			||||||
 | 
					    if discord.GuildID(overwrite.ID) == guild.ID {
 | 
				
			||||||
 | 
					      perm &= ^overwrite.Deny
 | 
				
			||||||
 | 
					      perm |= overwrite.Allow
 | 
				
			||||||
 | 
					      break
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var deny, allow discord.Permissions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for _, overwrite := range channel.Overwrites {
 | 
				
			||||||
 | 
					    for _, id := range member.RoleIDs {
 | 
				
			||||||
 | 
					      if id == discord.RoleID(overwrite.ID) && overwrite.Type == discord.OverwriteRole {
 | 
				
			||||||
 | 
					        deny |= overwrite.Deny
 | 
				
			||||||
 | 
					        allow |= overwrite.Allow
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  perm &= ^deny
 | 
				
			||||||
 | 
					  perm |= allow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for _, overwrite := range channel.Overwrites {
 | 
				
			||||||
 | 
					    if discord.UserID(overwrite.ID) == member.User.ID && overwrite.Type == discord.OverwriteMember {
 | 
				
			||||||
 | 
					      perm &= ^overwrite.Deny
 | 
				
			||||||
 | 
					      perm |= overwrite.Allow
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if perm.Has(discord.PermissionAdministrator) {
 | 
				
			||||||
 | 
					    return discord.PermissionAll
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return perm
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										185
									
								
								main.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								main.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,185 @@
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"runtime"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"atomicgo.dev/keyboard"
 | 
				
			||||||
 | 
						"atomicgo.dev/keyboard/keys"
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/commands"
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/events"
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/rcfile"
 | 
				
			||||||
 | 
						"github.com/Cynosphere/comcord/state"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/discord"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/gateway"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/session"
 | 
				
			||||||
 | 
						arikawa_state "github.com/diamondburned/arikawa/v3/state"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/state/store/defaultstore"
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/utils/handler"
 | 
				
			||||||
 | 
						"github.com/diamondburned/ningen/v3"
 | 
				
			||||||
 | 
						"golang.org/x/term"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
					  oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    panic(err)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  defer term.Restore(int(os.Stdin.Fd()), oldState)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var config map[string]string = make(map[string]string)
 | 
				
			||||||
 | 
					  var token string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  homeDir, homeErr := os.UserHomeDir()
 | 
				
			||||||
 | 
					  if homeErr != nil {
 | 
				
			||||||
 | 
					    panic(homeErr)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  RCPATH := rcfile.GetPath()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  _, rcErr := os.Stat(RCPATH)
 | 
				
			||||||
 | 
					  if !os.IsNotExist(rcErr) {
 | 
				
			||||||
 | 
					    fmt.Printf("%% Reading %s ...\n", strings.Replace(RCPATH, homeDir, "~", 1))
 | 
				
			||||||
 | 
					    config = rcfile.Load()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if len(os.Args) > 1 {
 | 
				
			||||||
 | 
					    token = os.Args[1]
 | 
				
			||||||
 | 
					    if os.IsNotExist(rcErr) {
 | 
				
			||||||
 | 
					      fmt.Println("% Writing token to ~/.comcordrc")
 | 
				
			||||||
 | 
					      config["token"] = token
 | 
				
			||||||
 | 
					      rcfile.Save(config)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    configToken, tokenInConfig := config["token"]
 | 
				
			||||||
 | 
					    if tokenInConfig {
 | 
				
			||||||
 | 
					      token = configToken
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      fmt.Println("No token provided.")
 | 
				
			||||||
 | 
					      os.Exit(1)
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fmt.Println("\rCOMcord (c)left 2023")
 | 
				
			||||||
 | 
					  fmt.Println("\rType 'h' for Commands")
 | 
				
			||||||
 | 
					  fmt.Print("\r")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commands.Setup()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  allowUserAccounts := config["allowUserAccounts"] == "true"
 | 
				
			||||||
 | 
					  tokenPrefix := "Bot "
 | 
				
			||||||
 | 
					  if allowUserAccounts {
 | 
				
			||||||
 | 
					    tokenPrefix = ""
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fullToken := tokenPrefix + token
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  props := gateway.IdentifyProperties{
 | 
				
			||||||
 | 
					    OS: runtime.GOOS,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  statusType := config["statusType"]
 | 
				
			||||||
 | 
					  if statusType == "mobile" {
 | 
				
			||||||
 | 
					    props.Browser = "Discord Android"
 | 
				
			||||||
 | 
					  } else if statusType == "embedded" {
 | 
				
			||||||
 | 
					    props.Browser = "Discord Embedded"
 | 
				
			||||||
 | 
					  } else if statusType == "desktop" {
 | 
				
			||||||
 | 
					    props.Browser = "Discord Client"
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    props.Browser = "comcord"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ident := gateway.IdentifyCommand{
 | 
				
			||||||
 | 
					    Token: fullToken,
 | 
				
			||||||
 | 
					    Properties: props,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Compress: true,
 | 
				
			||||||
 | 
					    LargeThreshold: 50,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  status := "online"
 | 
				
			||||||
 | 
					  defaultStatus := config["defaultStatus"]
 | 
				
			||||||
 | 
					  if defaultStatus != "" {
 | 
				
			||||||
 | 
					    status = defaultStatus
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  startTime := state.GetStartTime()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  activity := discord.Activity{
 | 
				
			||||||
 | 
					    Name: "comcord",
 | 
				
			||||||
 | 
					    Type: discord.GameActivity,
 | 
				
			||||||
 | 
					    CreatedAt: discord.UnixTimestamp(startTime.Unix()),
 | 
				
			||||||
 | 
					    Timestamps: &discord.ActivityTimestamps{
 | 
				
			||||||
 | 
					      Start: discord.UnixMsTimestamp(startTime.Unix()),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  presence := gateway.UpdatePresenceCommand{
 | 
				
			||||||
 | 
					    Since: 0,
 | 
				
			||||||
 | 
					    Activities: make([]discord.Activity, 0),
 | 
				
			||||||
 | 
					    Status: discord.Status(status),
 | 
				
			||||||
 | 
					    AFK: false,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  presence.Activities = append(presence.Activities, activity)
 | 
				
			||||||
 | 
					  ident.Presence = &presence
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  gwURL, err := gateway.URL(context.Background())
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("% Failed to get gateway URL: ", err, "\n\r")
 | 
				
			||||||
 | 
					    os.Exit(1)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  gw := gateway.NewCustomWithIdentifier(gateway.AddGatewayParams(gwURL), gateway.NewIdentifier(ident), nil)
 | 
				
			||||||
 | 
					  ses := session.NewWithGateway(gw, handler.New())
 | 
				
			||||||
 | 
					  st := arikawa_state.NewFromSession(ses, defaultstore.New())
 | 
				
			||||||
 | 
					  client := ningen.FromState(st)
 | 
				
			||||||
 | 
					  client.PreHandler = handler.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  client.AddIntents(gateway.IntentGuilds)
 | 
				
			||||||
 | 
					  client.AddIntents(gateway.IntentGuildPresences)
 | 
				
			||||||
 | 
					  client.AddIntents(gateway.IntentGuildMembers)
 | 
				
			||||||
 | 
					  client.AddIntents(gateway.IntentGuildMessages)
 | 
				
			||||||
 | 
					  client.AddIntents(gateway.IntentGuildMessageReactions)
 | 
				
			||||||
 | 
					  client.AddIntents(gateway.IntentDirectMessages)
 | 
				
			||||||
 | 
					  client.AddIntents(gateway.IntentDirectMessageReactions)
 | 
				
			||||||
 | 
					  client.AddIntents(gateway.IntentMessageContent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state.Setup(config, client)
 | 
				
			||||||
 | 
					  events.Setup(client)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  err = client.Open(context.Background())
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    fmt.Print("% Failed to connect to Discord: ", err, "\n\r")
 | 
				
			||||||
 | 
					    os.Exit(1)
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  defer client.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  keyboard.Listen(func(key keys.Key) (stop bool, err error) {
 | 
				
			||||||
 | 
					    if !state.IsInPrompt() {
 | 
				
			||||||
 | 
					      if key.Code == keys.CtrlC {
 | 
				
			||||||
 | 
					        term.Restore(int(os.Stdin.Fd()), oldState)
 | 
				
			||||||
 | 
					        commands.QuitCommand()
 | 
				
			||||||
 | 
					        return true, nil
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        command, has := commands.GetCommand(key.String())
 | 
				
			||||||
 | 
					        if has {
 | 
				
			||||||
 | 
					          if key.String() == "q" {
 | 
				
			||||||
 | 
					            term.Restore(int(os.Stdin.Fd()), oldState)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          command.Run()
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          commands.SendMode()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false, nil
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*sc := make(chan os.Signal, 1)
 | 
				
			||||||
 | 
						signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
 | 
				
			||||||
 | 
						<-sc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  client.Close()*/
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										14
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										14
									
								
								package.json
									
										
									
									
									
								
							| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  "name": "comcord",
 | 
					 | 
				
			||||||
  "version": "1.0.0",
 | 
					 | 
				
			||||||
  "description": "",
 | 
					 | 
				
			||||||
  "main": "index.js",
 | 
					 | 
				
			||||||
  "keywords": [],
 | 
					 | 
				
			||||||
  "author": "Cynosphere",
 | 
					 | 
				
			||||||
  "license": "MIT",
 | 
					 | 
				
			||||||
  "dependencies": {
 | 
					 | 
				
			||||||
    "@projectdysnomia/dysnomia": "^0.1.2",
 | 
					 | 
				
			||||||
    "chalk": "4.1.2",
 | 
					 | 
				
			||||||
    "discord-rpc": "^4.0.1"
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										191
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										191
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -1,191 +0,0 @@
 | 
				
			||||||
lockfileVersion: '6.0'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dependencies:
 | 
					 | 
				
			||||||
  '@projectdysnomia/dysnomia':
 | 
					 | 
				
			||||||
    specifier: ^0.1.2
 | 
					 | 
				
			||||||
    version: 0.1.2
 | 
					 | 
				
			||||||
  chalk:
 | 
					 | 
				
			||||||
    specifier: 4.1.2
 | 
					 | 
				
			||||||
    version: 4.1.2
 | 
					 | 
				
			||||||
  discord-rpc:
 | 
					 | 
				
			||||||
    specifier: ^4.0.1
 | 
					 | 
				
			||||||
    version: 4.0.1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
packages:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /@projectdysnomia/dysnomia@0.1.2:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-F64G64JwFWn/QUFqkhsyBvXJ0Du3E6Y0yu8tSrcukAnSeW8qV+reKqeQnMmHcQWYopwuYM8Q6OF/VX6VKggtOA==}
 | 
					 | 
				
			||||||
    engines: {node: '>=10.4.0'}
 | 
					 | 
				
			||||||
    peerDependencies:
 | 
					 | 
				
			||||||
      '@discordjs/opus': ^0.9.0
 | 
					 | 
				
			||||||
      erlpack: github:discord/erlpack || github:abalabahaha/erlpack
 | 
					 | 
				
			||||||
      eventemitter3: ^5.0.0
 | 
					 | 
				
			||||||
      pako: ^2.1.0
 | 
					 | 
				
			||||||
      sodium-native: ^4.0.1
 | 
					 | 
				
			||||||
      zlib-sync: ^0.1.8
 | 
					 | 
				
			||||||
    peerDependenciesMeta:
 | 
					 | 
				
			||||||
      '@discordjs/opus':
 | 
					 | 
				
			||||||
        optional: true
 | 
					 | 
				
			||||||
      erlpack:
 | 
					 | 
				
			||||||
        optional: true
 | 
					 | 
				
			||||||
      eventemitter3:
 | 
					 | 
				
			||||||
        optional: true
 | 
					 | 
				
			||||||
      pako:
 | 
					 | 
				
			||||||
        optional: true
 | 
					 | 
				
			||||||
      sodium-native:
 | 
					 | 
				
			||||||
        optional: true
 | 
					 | 
				
			||||||
      zlib-sync:
 | 
					 | 
				
			||||||
        optional: true
 | 
					 | 
				
			||||||
    dependencies:
 | 
					 | 
				
			||||||
      ws: 8.13.0
 | 
					 | 
				
			||||||
    optionalDependencies:
 | 
					 | 
				
			||||||
      opusscript: 0.0.8
 | 
					 | 
				
			||||||
      tweetnacl: 1.0.3
 | 
					 | 
				
			||||||
    transitivePeerDependencies:
 | 
					 | 
				
			||||||
      - bufferutil
 | 
					 | 
				
			||||||
      - utf-8-validate
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /ansi-styles@4.3.0:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
 | 
					 | 
				
			||||||
    engines: {node: '>=8'}
 | 
					 | 
				
			||||||
    dependencies:
 | 
					 | 
				
			||||||
      color-convert: 2.0.1
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /bindings@1.5.0:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
 | 
					 | 
				
			||||||
    dependencies:
 | 
					 | 
				
			||||||
      file-uri-to-path: 1.0.0
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /chalk@4.1.2:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
 | 
					 | 
				
			||||||
    engines: {node: '>=10'}
 | 
					 | 
				
			||||||
    dependencies:
 | 
					 | 
				
			||||||
      ansi-styles: 4.3.0
 | 
					 | 
				
			||||||
      supports-color: 7.2.0
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /color-convert@2.0.1:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
 | 
					 | 
				
			||||||
    engines: {node: '>=7.0.0'}
 | 
					 | 
				
			||||||
    dependencies:
 | 
					 | 
				
			||||||
      color-name: 1.1.4
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /color-name@1.1.4:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /discord-rpc@4.0.1:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-HOvHpbq5STRZJjQIBzwoKnQ0jHplbEWFWlPDwXXKm/bILh4nzjcg7mNqll0UY7RsjFoaXA7e/oYb/4lvpda2zA==}
 | 
					 | 
				
			||||||
    dependencies:
 | 
					 | 
				
			||||||
      node-fetch: 2.6.7
 | 
					 | 
				
			||||||
      ws: 7.5.9
 | 
					 | 
				
			||||||
    optionalDependencies:
 | 
					 | 
				
			||||||
      register-scheme: github.com/devsnek/node-register-scheme/e7cc9a63a1f512565da44cb57316d9fb10750e17
 | 
					 | 
				
			||||||
    transitivePeerDependencies:
 | 
					 | 
				
			||||||
      - bufferutil
 | 
					 | 
				
			||||||
      - encoding
 | 
					 | 
				
			||||||
      - utf-8-validate
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /file-uri-to-path@1.0.0:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /has-flag@4.0.0:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
 | 
					 | 
				
			||||||
    engines: {node: '>=8'}
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /node-addon-api@1.7.2:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==}
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /node-fetch@2.6.7:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
 | 
					 | 
				
			||||||
    engines: {node: 4.x || >=6.0.0}
 | 
					 | 
				
			||||||
    peerDependencies:
 | 
					 | 
				
			||||||
      encoding: ^0.1.0
 | 
					 | 
				
			||||||
    peerDependenciesMeta:
 | 
					 | 
				
			||||||
      encoding:
 | 
					 | 
				
			||||||
        optional: true
 | 
					 | 
				
			||||||
    dependencies:
 | 
					 | 
				
			||||||
      whatwg-url: 5.0.0
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /opusscript@0.0.8:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ==}
 | 
					 | 
				
			||||||
    requiresBuild: true
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /supports-color@7.2.0:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
 | 
					 | 
				
			||||||
    engines: {node: '>=8'}
 | 
					 | 
				
			||||||
    dependencies:
 | 
					 | 
				
			||||||
      has-flag: 4.0.0
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /tr46@0.0.3:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /tweetnacl@1.0.3:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==}
 | 
					 | 
				
			||||||
    requiresBuild: true
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /webidl-conversions@3.0.1:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /whatwg-url@5.0.0:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
 | 
					 | 
				
			||||||
    dependencies:
 | 
					 | 
				
			||||||
      tr46: 0.0.3
 | 
					 | 
				
			||||||
      webidl-conversions: 3.0.1
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /ws@7.5.9:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==}
 | 
					 | 
				
			||||||
    engines: {node: '>=8.3.0'}
 | 
					 | 
				
			||||||
    peerDependencies:
 | 
					 | 
				
			||||||
      bufferutil: ^4.0.1
 | 
					 | 
				
			||||||
      utf-8-validate: ^5.0.2
 | 
					 | 
				
			||||||
    peerDependenciesMeta:
 | 
					 | 
				
			||||||
      bufferutil:
 | 
					 | 
				
			||||||
        optional: true
 | 
					 | 
				
			||||||
      utf-8-validate:
 | 
					 | 
				
			||||||
        optional: true
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /ws@8.13.0:
 | 
					 | 
				
			||||||
    resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==}
 | 
					 | 
				
			||||||
    engines: {node: '>=10.0.0'}
 | 
					 | 
				
			||||||
    peerDependencies:
 | 
					 | 
				
			||||||
      bufferutil: ^4.0.1
 | 
					 | 
				
			||||||
      utf-8-validate: '>=5.0.2'
 | 
					 | 
				
			||||||
    peerDependenciesMeta:
 | 
					 | 
				
			||||||
      bufferutil:
 | 
					 | 
				
			||||||
        optional: true
 | 
					 | 
				
			||||||
      utf-8-validate:
 | 
					 | 
				
			||||||
        optional: true
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  github.com/devsnek/node-register-scheme/e7cc9a63a1f512565da44cb57316d9fb10750e17:
 | 
					 | 
				
			||||||
    resolution: {tarball: https://codeload.github.com/devsnek/node-register-scheme/tar.gz/e7cc9a63a1f512565da44cb57316d9fb10750e17}
 | 
					 | 
				
			||||||
    name: register-scheme
 | 
					 | 
				
			||||||
    version: 0.0.2
 | 
					 | 
				
			||||||
    requiresBuild: true
 | 
					 | 
				
			||||||
    dependencies:
 | 
					 | 
				
			||||||
      bindings: 1.5.0
 | 
					 | 
				
			||||||
      node-addon-api: 1.7.2
 | 
					 | 
				
			||||||
    dev: false
 | 
					 | 
				
			||||||
    optional: true
 | 
					 | 
				
			||||||
							
								
								
									
										47
									
								
								rcfile/main.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								rcfile/main.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,47 @@
 | 
				
			||||||
 | 
					package rcfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
					  "os"
 | 
				
			||||||
 | 
					  "path/filepath"
 | 
				
			||||||
 | 
					  "strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetPath() string {
 | 
				
			||||||
 | 
					  homeDir, err := os.UserHomeDir()
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    panic(err)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return filepath.Join(homeDir, ".comcordrc")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Load() map[string]string {
 | 
				
			||||||
 | 
					  config := make(map[string]string)
 | 
				
			||||||
 | 
					  file, err := os.ReadFile(GetPath())
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    panic(err)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lines := strings.Split(string(file), "\n")
 | 
				
			||||||
 | 
					  for _, line := range lines {
 | 
				
			||||||
 | 
					    kvs := strings.Split(line, "=")
 | 
				
			||||||
 | 
					    if len(kvs) == 2 {
 | 
				
			||||||
 | 
					      config[kvs[0]] = kvs[1]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return config
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Save(config map[string]string) {
 | 
				
			||||||
 | 
					  out := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for key, value := range config {
 | 
				
			||||||
 | 
					    out = out + key + "=" + value + "\n"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  err := os.WriteFile(GetPath(), []byte(out), 0644)
 | 
				
			||||||
 | 
					  if err != nil {
 | 
				
			||||||
 | 
					    panic(err)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,18 +0,0 @@
 | 
				
			||||||
const {addCommand} = require("../lib/command");
 | 
					 | 
				
			||||||
const {updatePresence} = require("../lib/presence");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
addCommand("A", "toggles AFK mode", function () {
 | 
					 | 
				
			||||||
  if (comcord.state.afk == true) {
 | 
					 | 
				
			||||||
    comcord.state.afk = false;
 | 
					 | 
				
			||||||
    comcord.client.editStatus("online");
 | 
					 | 
				
			||||||
    comcord.client.editAFK(false);
 | 
					 | 
				
			||||||
    console.log("<you have returned>");
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    comcord.state.afk = true;
 | 
					 | 
				
			||||||
    comcord.client.editStatus("idle");
 | 
					 | 
				
			||||||
    comcord.client.editAFK(true);
 | 
					 | 
				
			||||||
    console.log("<you go AFK>");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  updatePresence();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,5 +0,0 @@
 | 
				
			||||||
const {addCommand} = require("../lib/command");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
addCommand("c", "clear", function () {
 | 
					 | 
				
			||||||
  console.clear();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,25 +0,0 @@
 | 
				
			||||||
const {addCommand} = require("../lib/command");
 | 
					 | 
				
			||||||
const {startPrompt} = require("../lib/prompt");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
addCommand("e", "emote", function () {
 | 
					 | 
				
			||||||
  if (!comcord.state.currentChannel) {
 | 
					 | 
				
			||||||
    console.log("<not in a channel>");
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  startPrompt(":emote> ", async function (input) {
 | 
					 | 
				
			||||||
    if (input == "") {
 | 
					 | 
				
			||||||
      console.log("<no message sent>");
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        process.stdout.write("\n");
 | 
					 | 
				
			||||||
        await comcord.client.createMessage(comcord.state.currentChannel, {
 | 
					 | 
				
			||||||
          content: `*${input}*`,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        console.log(`<${comcord.client.user.username} ${input}>`);
 | 
					 | 
				
			||||||
      } catch (err) {
 | 
					 | 
				
			||||||
        console.log(`<failed to send message: ${err.message}>`);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,29 +0,0 @@
 | 
				
			||||||
const chalk = require("chalk");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const {addCommand} = require("../lib/command");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
addCommand("h", "command help", function () {
 | 
					 | 
				
			||||||
  console.log("\nCOMcord (c)left 2022\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const keys = Object.keys(comcord.commands);
 | 
					 | 
				
			||||||
  keys.sort((a, b) => a.localeCompare(b));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let index = 0;
 | 
					 | 
				
			||||||
  for (const key of keys) {
 | 
					 | 
				
			||||||
    const desc = comcord.commands[key].name;
 | 
					 | 
				
			||||||
    const length = `  ${key} - ${desc}`.length;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    process.stdout.write(
 | 
					 | 
				
			||||||
      "  " +
 | 
					 | 
				
			||||||
        chalk.bold.yellow(key) +
 | 
					 | 
				
			||||||
        chalk.reset(" - " + desc) +
 | 
					 | 
				
			||||||
        " ".repeat(Math.abs(25 - length))
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    index++;
 | 
					 | 
				
			||||||
    if (index % 3 == 0) process.stdout.write("\n");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (index % 3 != 0) process.stdout.write("\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  console.log("\nTo begin TALK MODE, press [SPACE]\n");
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,126 +0,0 @@
 | 
				
			||||||
const {addCommand} = require("../lib/command");
 | 
					 | 
				
			||||||
const {startPrompt} = require("../lib/prompt");
 | 
					 | 
				
			||||||
const {processMessage} = require("../lib/messages");
 | 
					 | 
				
			||||||
const {listChannels} = require("./listChannels");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function getHistory(limit = 20, channel = null) {
 | 
					 | 
				
			||||||
  if (!channel && !comcord.state.currentChannel) {
 | 
					 | 
				
			||||||
    console.log("<not in a channel>");
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const messages = await comcord.client.getMessages(
 | 
					 | 
				
			||||||
    channel ?? comcord.state.currentChannel
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  messages.reverse();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  console.log("--Beginning-Review".padEnd(72, "-"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const lines = [];
 | 
					 | 
				
			||||||
  for (const msg of messages) {
 | 
					 | 
				
			||||||
    const processedLines = processMessage(msg, {noColor: true, history: true});
 | 
					 | 
				
			||||||
    if (processedLines) lines.push(...processedLines);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  console.log(lines.slice(-limit).join("\n"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  console.log("--Review-Complete".padEnd(73, "-"));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function getExtendedHistory(input) {
 | 
					 | 
				
			||||||
  input = parseInt(input);
 | 
					 | 
				
			||||||
  if (isNaN(input)) {
 | 
					 | 
				
			||||||
    console.log("<not a number>");
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    await getHistory(input);
 | 
					 | 
				
			||||||
  } catch (err) {
 | 
					 | 
				
			||||||
    console.log(`<failed to get history: ${err.message}>`);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
addCommand("r", "channel history", getHistory);
 | 
					 | 
				
			||||||
addCommand("R", "extended history", function () {
 | 
					 | 
				
			||||||
  startPrompt(":lines> ", async function (input) {
 | 
					 | 
				
			||||||
    process.stdout.write("\n");
 | 
					 | 
				
			||||||
    await getExtendedHistory(input);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
addCommand("p", "peek at channel", function () {
 | 
					 | 
				
			||||||
  if (!comcord.state.currentGuild) {
 | 
					 | 
				
			||||||
    console.log("<not in a guild>");
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  startPrompt(":peek> ", async function (input) {
 | 
					 | 
				
			||||||
    console.log("");
 | 
					 | 
				
			||||||
    if (input == "") {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    let target;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const guild = comcord.client.guilds.get(comcord.state.currentGuild);
 | 
					 | 
				
			||||||
    const channels = [...guild.channels.values()].filter((c) => c.type == 0);
 | 
					 | 
				
			||||||
    channels.sort((a, b) => a.position - b.position);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (const channel of channels) {
 | 
					 | 
				
			||||||
      if (channel.name.toLowerCase().indexOf(input.toLowerCase()) > -1) {
 | 
					 | 
				
			||||||
        target = channel.id;
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (target == null) {
 | 
					 | 
				
			||||||
      console.log("<channel not found>");
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      await getHistory(20, target);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
addCommand("P", "cross-guild peek", function () {
 | 
					 | 
				
			||||||
  startPrompt(":guild> ", async function (input) {
 | 
					 | 
				
			||||||
    console.log("");
 | 
					 | 
				
			||||||
    if (input == "") {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    let targetGuild;
 | 
					 | 
				
			||||||
    for (const guild of comcord.client.guilds.values()) {
 | 
					 | 
				
			||||||
      if (guild.name.toLowerCase().indexOf(input.toLowerCase()) > -1) {
 | 
					 | 
				
			||||||
        targetGuild = guild.id;
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (targetGuild == null) {
 | 
					 | 
				
			||||||
      console.log("<guild not found>");
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      startPrompt(":peek> ", async function (input) {
 | 
					 | 
				
			||||||
        console.log("");
 | 
					 | 
				
			||||||
        if (input == "") {
 | 
					 | 
				
			||||||
          return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        let target;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const guild = comcord.client.guilds.get(targetGuild);
 | 
					 | 
				
			||||||
        const channels = [...guild.channels.values()].filter(
 | 
					 | 
				
			||||||
          (c) => c.type == 0
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        channels.sort((a, b) => a.position - b.position);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const channel of channels) {
 | 
					 | 
				
			||||||
          if (channel.name.toLowerCase().indexOf(input.toLowerCase()) > -1) {
 | 
					 | 
				
			||||||
            target = channel.id;
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (target == null) {
 | 
					 | 
				
			||||||
          console.log("<channel not found>");
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          await getHistory(20, target);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,55 +0,0 @@
 | 
				
			||||||
const {addCommand} = require("../lib/command");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function listChannels() {
 | 
					 | 
				
			||||||
  if (!comcord.state.currentGuild) {
 | 
					 | 
				
			||||||
    console.log("<not in a guild>");
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let longest = 0;
 | 
					 | 
				
			||||||
  const guild = comcord.client.guilds.get(comcord.state.currentGuild);
 | 
					 | 
				
			||||||
  const channels = Array.from(guild.channels.values()).filter(
 | 
					 | 
				
			||||||
    (c) => c.type == 0 || c.type == 5
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  channels.sort((a, b) => a.position - b.position);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (const channel of channels) {
 | 
					 | 
				
			||||||
    const perms = channel.permissionsOf(comcord.client.user.id);
 | 
					 | 
				
			||||||
    const private = !perms.has("readMessageHistory");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (channel.name.length + (private ? 1 : 0) > longest)
 | 
					 | 
				
			||||||
      longest = Math.min(25, channel.name.length + (private ? 1 : 0));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  console.log("");
 | 
					 | 
				
			||||||
  console.log("  " + "channel-name".padStart(longest, " ") + "  " + "topic");
 | 
					 | 
				
			||||||
  console.log("-".repeat(80));
 | 
					 | 
				
			||||||
  for (const channel of channels) {
 | 
					 | 
				
			||||||
    const topic =
 | 
					 | 
				
			||||||
      channel.topic != null ? channel.topic.replace(/\n/g, " ") : "";
 | 
					 | 
				
			||||||
    const perms = channel.permissionsOf(comcord.client.user.id);
 | 
					 | 
				
			||||||
    const private = !perms.has("viewChannel");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const name = (private ? "*" : "") + channel.name;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    console.log(
 | 
					 | 
				
			||||||
      "  " +
 | 
					 | 
				
			||||||
        (name.length > 24 ? name.substring(0, 24) + "\u2026" : name).padStart(
 | 
					 | 
				
			||||||
          longest,
 | 
					 | 
				
			||||||
          " "
 | 
					 | 
				
			||||||
        ) +
 | 
					 | 
				
			||||||
        "  " +
 | 
					 | 
				
			||||||
        (topic.length > 80 - (longest + 5)
 | 
					 | 
				
			||||||
          ? topic.substring(0, 79 - (longest + 5)) + "\u2026"
 | 
					 | 
				
			||||||
          : topic)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  console.log("-".repeat(80));
 | 
					 | 
				
			||||||
  console.log("");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
addCommand("l", "list channels", listChannels);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
  listChannels,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,37 +0,0 @@
 | 
				
			||||||
const {addCommand} = require("../lib/command");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function listGuilds() {
 | 
					 | 
				
			||||||
  let longest = 0;
 | 
					 | 
				
			||||||
  const guilds = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (const guild of comcord.client.guilds.values()) {
 | 
					 | 
				
			||||||
    if (guild.name.length > longest) longest = guild.name.length;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const online = Array.from(guild.members.values()).filter(
 | 
					 | 
				
			||||||
      (m) => m.status
 | 
					 | 
				
			||||||
    ).length;
 | 
					 | 
				
			||||||
    guilds.push({name: guild.name, members: guild.memberCount, online});
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  console.log("");
 | 
					 | 
				
			||||||
  console.log("  " + "guild-name".padStart(longest, " ") + "  online  total");
 | 
					 | 
				
			||||||
  console.log("-".repeat(80));
 | 
					 | 
				
			||||||
  for (const guild of guilds) {
 | 
					 | 
				
			||||||
    console.log(
 | 
					 | 
				
			||||||
      "  " +
 | 
					 | 
				
			||||||
        guild.name.padStart(longest, " ") +
 | 
					 | 
				
			||||||
        "  " +
 | 
					 | 
				
			||||||
        guild.online.toString().padStart(6, " ") +
 | 
					 | 
				
			||||||
        "  " +
 | 
					 | 
				
			||||||
        guild.members.toString().padStart(5, " ")
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  console.log("-".repeat(80));
 | 
					 | 
				
			||||||
  console.log("");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
addCommand("L", "list guilds", listGuilds);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
  listGuilds,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,88 +0,0 @@
 | 
				
			||||||
const chalk = require("chalk");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const {addCommand} = require("../lib/command");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getStatus(status) {
 | 
					 | 
				
			||||||
  let color;
 | 
					 | 
				
			||||||
  switch (status) {
 | 
					 | 
				
			||||||
    case "online":
 | 
					 | 
				
			||||||
      color = chalk.bold.green;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case "idle":
 | 
					 | 
				
			||||||
      color = chalk.bold.yellow;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case "dnd":
 | 
					 | 
				
			||||||
      color = chalk.bold.red;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    default:
 | 
					 | 
				
			||||||
      color = chalk.bold;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return color(" \u2022 ");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function listUsers() {
 | 
					 | 
				
			||||||
  if (!comcord.state.currentGuild) {
 | 
					 | 
				
			||||||
    console.log("<not in a guild>");
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!comcord.state.currentChannel) {
 | 
					 | 
				
			||||||
    console.log("<not in a channel>");
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const guild = comcord.client.guilds.get(comcord.state.currentGuild);
 | 
					 | 
				
			||||||
  const channel = guild.channels.get(comcord.state.currentChannel);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  console.log(
 | 
					 | 
				
			||||||
    `\n[you are in '${guild.name}' in '${channel.name}' among ${guild.memberCount}]\n`
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const online = Array.from(guild.members.values()).filter((m) => m.status);
 | 
					 | 
				
			||||||
  online.sort((a, b) => a.username.localeCompare(b.username));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let longest = 0;
 | 
					 | 
				
			||||||
  for (const member of online) {
 | 
					 | 
				
			||||||
    // FIXME: remove discrim stuff after username migration finished
 | 
					 | 
				
			||||||
    const name = member.username;
 | 
					 | 
				
			||||||
    if (name.length + 3 > longest) longest = name.length + 3;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const columns = Math.floor(process.stdout.columns / longest);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let index = 0;
 | 
					 | 
				
			||||||
  for (const member of online) {
 | 
					 | 
				
			||||||
    const name = member.username;
 | 
					 | 
				
			||||||
    const status = getStatus(member.status);
 | 
					 | 
				
			||||||
    const nameAndStatus =
 | 
					 | 
				
			||||||
      (member.user.bot ? chalk.yellow(name) : chalk.reset(name)) + status;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    index++;
 | 
					 | 
				
			||||||
    process.stdout.write(
 | 
					 | 
				
			||||||
      nameAndStatus +
 | 
					 | 
				
			||||||
        " ".repeat(
 | 
					 | 
				
			||||||
          index % columns == 0 ? 0 : Math.abs(longest - (name.length + 3))
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (index % columns == 0) process.stdout.write("\n");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (index % columns != 0) process.stdout.write("\n");
 | 
					 | 
				
			||||||
  console.log("");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (channel.topic != null) {
 | 
					 | 
				
			||||||
    console.log("--Topic".padEnd(80, "-"));
 | 
					 | 
				
			||||||
    console.log(channel.topic);
 | 
					 | 
				
			||||||
    console.log("-".repeat(80));
 | 
					 | 
				
			||||||
    console.log("");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if (!comcord.commands.w) {
 | 
					 | 
				
			||||||
  addCommand("w", "who is in guild", listUsers);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
  listUsers,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,60 +0,0 @@
 | 
				
			||||||
const chalk = require("chalk");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const {addCommand} = require("../lib/command");
 | 
					 | 
				
			||||||
const {startPrompt} = require("../lib/prompt");
 | 
					 | 
				
			||||||
const {listUsers} = require("./listUsers");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function startDM(channel) {
 | 
					 | 
				
			||||||
  startPrompt(":msg> ", async function (input) {
 | 
					 | 
				
			||||||
    if (input == "") {
 | 
					 | 
				
			||||||
      console.log(
 | 
					 | 
				
			||||||
        `\n<message not sent to ${channel.recipent?.username ?? "group DM"}>`
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        await channel.createMessage({content: input});
 | 
					 | 
				
			||||||
        console.log(
 | 
					 | 
				
			||||||
          chalk.bold.green(
 | 
					 | 
				
			||||||
            `\n<message sent to ${channel.recipient?.username ?? "group DM"}>`
 | 
					 | 
				
			||||||
          )
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } catch (err) {
 | 
					 | 
				
			||||||
        console.log(`\n<failed to send message: ${err.message}>`);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
addCommand("s", "send private", function () {
 | 
					 | 
				
			||||||
  console.log("Provide a RECIPIENT");
 | 
					 | 
				
			||||||
  startPrompt(":to> ", function (who) {
 | 
					 | 
				
			||||||
    let target;
 | 
					 | 
				
			||||||
    for (const user of comcord.client.users.values()) {
 | 
					 | 
				
			||||||
      if (user.username == who) {
 | 
					 | 
				
			||||||
        target = user;
 | 
					 | 
				
			||||||
        break;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (target) {
 | 
					 | 
				
			||||||
      console.log("");
 | 
					 | 
				
			||||||
      startDM(target);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      listUsers();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
addCommand("a", "answer a send", function () {
 | 
					 | 
				
			||||||
  if (comcord.state.lastDM) {
 | 
					 | 
				
			||||||
    console.log(
 | 
					 | 
				
			||||||
      chalk.bold.green(
 | 
					 | 
				
			||||||
        `<answering ${comcord.state.lastDM.recipient?.username ?? "group DM"}>`
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    startDM(comcord.state.lastDM);
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    // FIXME: figure out the actual message in com
 | 
					 | 
				
			||||||
    console.log("<no one to answer>");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,7 +0,0 @@
 | 
				
			||||||
const {addCommand} = require("../lib/command");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
addCommand("q", "quit comcord", function () {
 | 
					 | 
				
			||||||
  comcord.state.quitting = true;
 | 
					 | 
				
			||||||
  comcord.client.disconnect({reconnect: false});
 | 
					 | 
				
			||||||
  process.exit(0);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,44 +0,0 @@
 | 
				
			||||||
const chalk = require("chalk");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const {startPrompt} = require("../lib/prompt");
 | 
					 | 
				
			||||||
const {updatePresence} = require("../lib/presence");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function sendMode() {
 | 
					 | 
				
			||||||
  if (!comcord.state.currentChannel) {
 | 
					 | 
				
			||||||
    console.log("<not in a channel>");
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  startPrompt(
 | 
					 | 
				
			||||||
    chalk.bold.cyan(`[${comcord.client.user.username}]`) +
 | 
					 | 
				
			||||||
      " ".repeat(
 | 
					 | 
				
			||||||
        comcord.state.nameLength - (comcord.client.user.username.length + 2)
 | 
					 | 
				
			||||||
      ) +
 | 
					 | 
				
			||||||
      chalk.reset(" "),
 | 
					 | 
				
			||||||
    async function (input) {
 | 
					 | 
				
			||||||
      if (input == "") {
 | 
					 | 
				
			||||||
        console.log("<no message sent>");
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
          process.stdout.write("\n");
 | 
					 | 
				
			||||||
          await comcord.client.createMessage(comcord.state.currentChannel, {
 | 
					 | 
				
			||||||
            content: input,
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (comcord.state.afk == true) {
 | 
					 | 
				
			||||||
            comcord.state.afk = false;
 | 
					 | 
				
			||||||
            comcord.client.editStatus("online");
 | 
					 | 
				
			||||||
            comcord.client.editAFK(false);
 | 
					 | 
				
			||||||
            console.log("<you have returned>");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            updatePresence();
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        } catch (err) {
 | 
					 | 
				
			||||||
          console.log("<failed to send message: " + err.message + ">");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {sendMode};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,47 +0,0 @@
 | 
				
			||||||
const {addCommand} = require("../lib/command");
 | 
					 | 
				
			||||||
const {startPrompt} = require("../lib/prompt");
 | 
					 | 
				
			||||||
const {updatePresence} = require("../lib/presence");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const {listUsers} = require("./listUsers");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function switchChannel(input) {
 | 
					 | 
				
			||||||
  if (input == "") {
 | 
					 | 
				
			||||||
    listUsers();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  let target;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const guild = comcord.client.guilds.get(comcord.state.currentGuild);
 | 
					 | 
				
			||||||
  const channels = [...guild.channels.values()].filter((c) => c.type == 0);
 | 
					 | 
				
			||||||
  channels.sort((a, b) => a.position - b.position);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (const channel of channels) {
 | 
					 | 
				
			||||||
    if (channel.name.toLowerCase().indexOf(input.toLowerCase()) > -1) {
 | 
					 | 
				
			||||||
      target = channel.id;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (target == null) {
 | 
					 | 
				
			||||||
    console.log("<channel not found>");
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    comcord.state.currentChannel = target;
 | 
					 | 
				
			||||||
    comcord.state.lastChannel.set(comcord.state.currentGuild, target);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    listUsers();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const channel = guild.channels.get(comcord.state.currentChannel);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    process.title = `${guild.name} - ${channel.name} - comcord`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    updatePresence();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
addCommand("g", "goto channel", function () {
 | 
					 | 
				
			||||||
  if (!comcord.state.currentGuild) {
 | 
					 | 
				
			||||||
    console.log("<not in a guild>");
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  startPrompt(":channel> ", switchChannel);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,60 +0,0 @@
 | 
				
			||||||
const {addCommand} = require("../lib/command");
 | 
					 | 
				
			||||||
const {startPrompt} = require("../lib/prompt");
 | 
					 | 
				
			||||||
const {updatePresence} = require("../lib/presence");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const {listChannels} = require("./listChannels");
 | 
					 | 
				
			||||||
const {listUsers} = require("./listUsers");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function findTopChannel(guildId) {
 | 
					 | 
				
			||||||
  const guild = comcord.client.guilds.get(guildId);
 | 
					 | 
				
			||||||
  const channels = [...guild.channels.values()].filter((c) => c.type == 0);
 | 
					 | 
				
			||||||
  channels.sort((a, b) => a.position - b.position);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return channels[0];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function switchGuild(input) {
 | 
					 | 
				
			||||||
  if (input == "") {
 | 
					 | 
				
			||||||
    listChannels();
 | 
					 | 
				
			||||||
    listUsers();
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let target;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (const guild of comcord.client.guilds.values()) {
 | 
					 | 
				
			||||||
    if (guild.name.toLowerCase().indexOf(input.toLowerCase()) > -1) {
 | 
					 | 
				
			||||||
      target = guild.id;
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (target == null) {
 | 
					 | 
				
			||||||
    console.log("<guild not found>");
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    comcord.state.currentGuild = target;
 | 
					 | 
				
			||||||
    if (!comcord.state.lastChannel.has(target)) {
 | 
					 | 
				
			||||||
      const topChannel = findTopChannel(target);
 | 
					 | 
				
			||||||
      comcord.state.currentChannel = topChannel.id;
 | 
					 | 
				
			||||||
      comcord.state.lastChannel.set(target, topChannel.id);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      comcord.state.currentChannel = comcord.state.lastChannel.get(target);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    listChannels();
 | 
					 | 
				
			||||||
    listUsers();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const guild = comcord.client.guilds.get(comcord.state.currentGuild);
 | 
					 | 
				
			||||||
    const channel = guild.channels.get(comcord.state.currentChannel);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    process.title = `${guild.name} - ${channel.name} - comcord`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    updatePresence();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
addCommand("G", "goto guild", function () {
 | 
					 | 
				
			||||||
  startPrompt(":guild> ", switchGuild);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {switchGuild};
 | 
					 | 
				
			||||||
							
								
								
									
										470
									
								
								src/index.js
									
										
									
									
									
								
							
							
						
						
									
										470
									
								
								src/index.js
									
										
									
									
									
								
							| 
						 | 
					@ -1,470 +0,0 @@
 | 
				
			||||||
const {Client, Constants, Channel} = require("@projectdysnomia/dysnomia");
 | 
					 | 
				
			||||||
const DiscordRPC = require("discord-rpc");
 | 
					 | 
				
			||||||
const chalk = require("chalk");
 | 
					 | 
				
			||||||
const fs = require("fs");
 | 
					 | 
				
			||||||
const os = require("os");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const rcfile = require("./lib/rcfile");
 | 
					 | 
				
			||||||
const config = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if (fs.existsSync(rcfile.path)) {
 | 
					 | 
				
			||||||
  console.log(`% Reading ${rcfile.path.replace(os.homedir(), "~")} ...`);
 | 
					 | 
				
			||||||
  rcfile.readFile(config);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const CLIENT_ID = "1026163285877325874";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const token = process.argv[2];
 | 
					 | 
				
			||||||
if (!config.token && token) {
 | 
					 | 
				
			||||||
  console.log("% Writing token to .comcordrc");
 | 
					 | 
				
			||||||
  config.token = token;
 | 
					 | 
				
			||||||
  rcfile.writeFile(config);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if (!config.token && !token) {
 | 
					 | 
				
			||||||
  console.log("No token provided.");
 | 
					 | 
				
			||||||
  process.exit(1);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
process.title = "comcord";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
global.comcord = {
 | 
					 | 
				
			||||||
  config,
 | 
					 | 
				
			||||||
  state: {
 | 
					 | 
				
			||||||
    connected: true,
 | 
					 | 
				
			||||||
    rpcConnected: false,
 | 
					 | 
				
			||||||
    startTime: Date.now(),
 | 
					 | 
				
			||||||
    currentGuild: null,
 | 
					 | 
				
			||||||
    currentChannel: null,
 | 
					 | 
				
			||||||
    nameLength: 2,
 | 
					 | 
				
			||||||
    inPrompt: false,
 | 
					 | 
				
			||||||
    messageQueue: [],
 | 
					 | 
				
			||||||
    lastChannel: new Map(),
 | 
					 | 
				
			||||||
    afk: false,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  commands: {},
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
const client = new Client(
 | 
					 | 
				
			||||||
  (config.allowUserAccounts == "true" ? "" : "Bot ") + (token ?? config.token),
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    defaultImageFormat: "png",
 | 
					 | 
				
			||||||
    defaultImageSize: 1024,
 | 
					 | 
				
			||||||
    gateway: {
 | 
					 | 
				
			||||||
      intents: Object.values(Constants.Intents),
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    allowedMentions: {
 | 
					 | 
				
			||||||
      everyone: false,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
comcord.client = client;
 | 
					 | 
				
			||||||
const rpc = new DiscordRPC.Client({transport: "ipc"});
 | 
					 | 
				
			||||||
comcord.rpc = rpc;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const {finalizePrompt} = require("./lib/prompt");
 | 
					 | 
				
			||||||
const {processMessage, processQueue} = require("./lib/messages");
 | 
					 | 
				
			||||||
const {updatePresence} = require("./lib/presence");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
require("./commands/quit");
 | 
					 | 
				
			||||||
require("./commands/clear");
 | 
					 | 
				
			||||||
require("./commands/help");
 | 
					 | 
				
			||||||
const {sendMode} = require("./commands/send");
 | 
					 | 
				
			||||||
require("./commands/emote");
 | 
					 | 
				
			||||||
const {listGuilds} = require("./commands/listGuilds");
 | 
					 | 
				
			||||||
const {switchGuild} = require("./commands/switchGuild"); // loads listChannels and listUsers
 | 
					 | 
				
			||||||
require("./commands/switchChannel"); //loads listUsers
 | 
					 | 
				
			||||||
require("./commands/history"); // includes extended history
 | 
					 | 
				
			||||||
require("./commands/afk");
 | 
					 | 
				
			||||||
require("./commands/privateMessages");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
process.stdin.setRawMode(true);
 | 
					 | 
				
			||||||
process.stdin.resume();
 | 
					 | 
				
			||||||
process.stdin.setEncoding("utf8");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
client.once("ready", function () {
 | 
					 | 
				
			||||||
  console.log(
 | 
					 | 
				
			||||||
    "Logged in as: " +
 | 
					 | 
				
			||||||
      chalk.yellow(`${client.user?.username} (${client.user?.id})`)
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  comcord.state.nameLength = (client.user?.username?.length ?? 0) + 2;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  listGuilds();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (config.defaultGuild) {
 | 
					 | 
				
			||||||
    const guild = client.guilds.get(config.defaultGuild);
 | 
					 | 
				
			||||||
    if (guild != null) {
 | 
					 | 
				
			||||||
      if (config.defaultChannel) {
 | 
					 | 
				
			||||||
        comcord.state.currentChannel = config.defaultChannel;
 | 
					 | 
				
			||||||
        comcord.state.lastChannel.set(
 | 
					 | 
				
			||||||
          config.defaultGuild,
 | 
					 | 
				
			||||||
          config.defaultChannel
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      switchGuild(guild.name);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      console.log("% This account is not in the defined default guild.");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    if (config.defaultChannel) {
 | 
					 | 
				
			||||||
      console.log("% Default channel defined without defining default guild.");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (client.user.bot && !config.disableRPC) {
 | 
					 | 
				
			||||||
    rpc
 | 
					 | 
				
			||||||
      .login({
 | 
					 | 
				
			||||||
        clientId: CLIENT_ID,
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch(function () {});
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
client.on("error", function () {});
 | 
					 | 
				
			||||||
client.on("ready", function () {
 | 
					 | 
				
			||||||
  if (comcord.state.connected === false) {
 | 
					 | 
				
			||||||
    console.log("% Reconnected");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
client.on("disconnect", function () {
 | 
					 | 
				
			||||||
  if (!comcord.state.quitting) {
 | 
					 | 
				
			||||||
    comcord.state.connected = false;
 | 
					 | 
				
			||||||
    console.log("% Disconnected, retrying...");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
rpc.on("connected", function () {
 | 
					 | 
				
			||||||
  comcord.state.rpcConnected = true;
 | 
					 | 
				
			||||||
  updatePresence();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
let retryingRPC = false;
 | 
					 | 
				
			||||||
rpc.once("ready", function () {
 | 
					 | 
				
			||||||
  rpc.transport.on("error", function () {});
 | 
					 | 
				
			||||||
  rpc.transport.on("close", function () {
 | 
					 | 
				
			||||||
    comcord.state.rpcConnected = false;
 | 
					 | 
				
			||||||
    if (!retryingRPC) {
 | 
					 | 
				
			||||||
      retryingRPC = true;
 | 
					 | 
				
			||||||
      setTimeout(function () {
 | 
					 | 
				
			||||||
        rpc.transport
 | 
					 | 
				
			||||||
          .connect()
 | 
					 | 
				
			||||||
          .then(() => {
 | 
					 | 
				
			||||||
            retryingRPC = false;
 | 
					 | 
				
			||||||
          })
 | 
					 | 
				
			||||||
          .catch((err) => {
 | 
					 | 
				
			||||||
            retryingRPC = false;
 | 
					 | 
				
			||||||
            rpc.transport.emit("close");
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
      }, 5000);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
rpc.on("error", function () {});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
client.on("messageCreate", async function (msg) {
 | 
					 | 
				
			||||||
  if (
 | 
					 | 
				
			||||||
    (msg.mentions.find((user) => user.id == client.user.id) ||
 | 
					 | 
				
			||||||
      msg.mentionEveryone) &&
 | 
					 | 
				
			||||||
    msg.channel.id != comcord.state.currentChannel &&
 | 
					 | 
				
			||||||
    msg.channel.type !== Constants.ChannelTypes.DM &&
 | 
					 | 
				
			||||||
    msg.channel.type !== Constants.ChannelTypes.GROUP_DM
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    const data = {ping: true, channel: msg.channel, author: msg.author};
 | 
					 | 
				
			||||||
    if (comcord.state.inPrompt) {
 | 
					 | 
				
			||||||
      comcord.state.messageQueue.push(data);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      processMessage(data);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!msg.author) return;
 | 
					 | 
				
			||||||
  if (msg.author.id === client.user.id) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (
 | 
					 | 
				
			||||||
    !(msg.channel instanceof Channel) &&
 | 
					 | 
				
			||||||
    msg.author.id != client.user.id &&
 | 
					 | 
				
			||||||
    !msg.guildID
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    if (msg.channel.type === Constants.ChannelTypes.DM) {
 | 
					 | 
				
			||||||
      const newChannel = await client.getDMChannel(msg.author.id);
 | 
					 | 
				
			||||||
      if (msg.channel.id == newChannel.id) msg.channel = newChannel;
 | 
					 | 
				
			||||||
    } else if (msg.channel.type === Constants.ChannelTypes.GROUP_DM) {
 | 
					 | 
				
			||||||
      // TODO
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!(msg.channel instanceof Channel)) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (
 | 
					 | 
				
			||||||
    msg.channel.id == comcord.state.currentChannel ||
 | 
					 | 
				
			||||||
    msg.channel.type === Constants.ChannelTypes.DM ||
 | 
					 | 
				
			||||||
    msg.channel.type === Constants.ChannelTypes.GROUP_DM
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    if (comcord.state.inPrompt) {
 | 
					 | 
				
			||||||
      comcord.state.messageQueue.push(msg);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      processMessage(msg);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (
 | 
					 | 
				
			||||||
    msg.channel.type === Constants.ChannelTypes.DM ||
 | 
					 | 
				
			||||||
    msg.channel.type === Constants.ChannelTypes.GROUP_DM
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    comcord.state.lastDM = msg.channel;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
client.on("messageUpdate", async function (msg, old) {
 | 
					 | 
				
			||||||
  if (!msg.author) return;
 | 
					 | 
				
			||||||
  if (msg.author.id === client.user.id) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (
 | 
					 | 
				
			||||||
    !(msg.channel instanceof Channel) &&
 | 
					 | 
				
			||||||
    msg.author.id != client.user.id &&
 | 
					 | 
				
			||||||
    !msg.guildID
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    if (msg.channel.type === Constants.ChannelTypes.DM) {
 | 
					 | 
				
			||||||
      const newChannel = await client.getDMChannel(msg.author.id);
 | 
					 | 
				
			||||||
      if (msg.channel.id == newChannel.id) msg.channel = newChannel;
 | 
					 | 
				
			||||||
    } else if (msg.channel.type === Constants.ChannelTypes.GROUP_DM) {
 | 
					 | 
				
			||||||
      // TODO
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!(msg.channel instanceof Channel)) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (
 | 
					 | 
				
			||||||
    msg.channel.id == comcord.state.currentChannel ||
 | 
					 | 
				
			||||||
    msg.channel.type === Constants.ChannelTypes.DM ||
 | 
					 | 
				
			||||||
    msg.channel.type === Constants.ChannelTypes.GROUP_DM
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    if (old && msg.content == old.content) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (comcord.state.inPrompt) {
 | 
					 | 
				
			||||||
      comcord.state.messageQueue.push(msg);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      processMessage(msg);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (
 | 
					 | 
				
			||||||
    msg.channel.type === Constants.ChannelTypes.DM ||
 | 
					 | 
				
			||||||
    msg.channel.type === Constants.ChannelTypes.GROUP_DM
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    comcord.state.lastDM = msg.channel;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
client.on("messageReactionAdd", async function (msg, emoji, reactor) {
 | 
					 | 
				
			||||||
  if (msg.channel.id != comcord.state.currentChannel) return;
 | 
					 | 
				
			||||||
  const reply =
 | 
					 | 
				
			||||||
    msg.channel.messages.get(msg.id) ??
 | 
					 | 
				
			||||||
    (await msg.channel
 | 
					 | 
				
			||||||
      .getMessages({
 | 
					 | 
				
			||||||
        limit: 1,
 | 
					 | 
				
			||||||
        around: msg.id,
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .then((msgs) => msgs[0]));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const data = {
 | 
					 | 
				
			||||||
    channel: msg.channel,
 | 
					 | 
				
			||||||
    referencedMessage: reply,
 | 
					 | 
				
			||||||
    author: reactor?.user ?? client.users.get(reactor.id),
 | 
					 | 
				
			||||||
    timestamp: Date.now(),
 | 
					 | 
				
			||||||
    mentions: [],
 | 
					 | 
				
			||||||
    content: `*reacted with ${emoji.id ? `:${emoji.name}:` : emoji.name}*`,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (comcord.state.inPrompt) {
 | 
					 | 
				
			||||||
    comcord.state.messageQueue.push(data);
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    processMessage(data);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
process.stdin.on("data", async function (key) {
 | 
					 | 
				
			||||||
  if (comcord.state.inPrompt) {
 | 
					 | 
				
			||||||
    if (key === "\r") {
 | 
					 | 
				
			||||||
      await finalizePrompt();
 | 
					 | 
				
			||||||
      processQueue();
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      if (key === "\b" || key === "\u007f") {
 | 
					 | 
				
			||||||
        if (comcord.state.promptInput.length > 0) {
 | 
					 | 
				
			||||||
          process.stdout.moveCursor(-1);
 | 
					 | 
				
			||||||
          process.stdout.write(" ");
 | 
					 | 
				
			||||||
          process.stdout.moveCursor(-1);
 | 
					 | 
				
			||||||
          comcord.state.promptInput = comcord.state.promptInput.substring(
 | 
					 | 
				
			||||||
            0,
 | 
					 | 
				
			||||||
            comcord.state.promptInput.length - 1
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        key = key.replace("\u001b", "");
 | 
					 | 
				
			||||||
        process.stdout.write(key);
 | 
					 | 
				
			||||||
        comcord.state.promptInput += key;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    if (comcord.commands[key]) {
 | 
					 | 
				
			||||||
      comcord.commands[key].callback();
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      sendMode();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if (
 | 
					 | 
				
			||||||
  config.allowUserAccounts == "true" &&
 | 
					 | 
				
			||||||
  !(token ?? config.token).startsWith("Bot ")
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
  if (fetch == null) {
 | 
					 | 
				
			||||||
    console.log("Node v18+ needed for user account support.");
 | 
					 | 
				
			||||||
    process.exit(1);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  (async function () {
 | 
					 | 
				
			||||||
    comcord.clientSpoof = require("./lib/clientSpoof");
 | 
					 | 
				
			||||||
    const superProperties = await comcord.clientSpoof.getSuperProperties();
 | 
					 | 
				
			||||||
    comcord.clientSpoof.superProperties = superProperties;
 | 
					 | 
				
			||||||
    comcord.clientSpoof.superPropertiesBase64 = Buffer.from(
 | 
					 | 
				
			||||||
      JSON.stringify(superProperties)
 | 
					 | 
				
			||||||
    ).toString("base64");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // FIXME: is there a way we can string patch functions without having to
 | 
					 | 
				
			||||||
    //        dump locals into global
 | 
					 | 
				
			||||||
    global.MultipartData = require("@projectdysnomia/dysnomia/lib/util/MultipartData.js");
 | 
					 | 
				
			||||||
    global.SequentialBucket = require("@projectdysnomia/dysnomia/lib/util/SequentialBucket.js");
 | 
					 | 
				
			||||||
    global.DiscordHTTPError = require("@projectdysnomia/dysnomia/lib/errors/DiscordHTTPError.js");
 | 
					 | 
				
			||||||
    global.DiscordRESTError = require("@projectdysnomia/dysnomia/lib/errors/DiscordRESTError.js");
 | 
					 | 
				
			||||||
    global.Zlib = require("node:zlib");
 | 
					 | 
				
			||||||
    global.HTTPS = require("node:https");
 | 
					 | 
				
			||||||
    global.HTTP = require("node:http");
 | 
					 | 
				
			||||||
    global.GatewayOPCodes = Constants.GatewayOPCodes;
 | 
					 | 
				
			||||||
    global.GATEWAY_VERSION = Constants.GATEWAY_VERSION;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    client.getGateway = async function getGateway() {
 | 
					 | 
				
			||||||
      return {url: "wss://gateway.discord.gg"};
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    console.log("% Injecting headers into request handler");
 | 
					 | 
				
			||||||
    client.requestHandler.userAgent = superProperties.browser_user_agent;
 | 
					 | 
				
			||||||
    const requestFunction = client.requestHandler.request.toString();
 | 
					 | 
				
			||||||
    const newRequest = requestFunction
 | 
					 | 
				
			||||||
      .replace(
 | 
					 | 
				
			||||||
        "this.userAgent,",
 | 
					 | 
				
			||||||
        'this.userAgent,\n"X-Super-Properties":comcord.clientSpoof.superPropertiesBase64,'
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
      .replace("._token", '._token.replace("Bot ","")');
 | 
					 | 
				
			||||||
    if (requestFunction === newRequest)
 | 
					 | 
				
			||||||
      throw new Error("Failed to patch request");
 | 
					 | 
				
			||||||
    client.requestHandler.request = new Function(
 | 
					 | 
				
			||||||
      "method",
 | 
					 | 
				
			||||||
      "url",
 | 
					 | 
				
			||||||
      "auth",
 | 
					 | 
				
			||||||
      "body",
 | 
					 | 
				
			||||||
      "file",
 | 
					 | 
				
			||||||
      "_route",
 | 
					 | 
				
			||||||
      "short",
 | 
					 | 
				
			||||||
      `return (function ${newRequest}).apply(this,arguments)`
 | 
					 | 
				
			||||||
    ).bind(client.requestHandler);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    console.log("% Injecting shard spawning");
 | 
					 | 
				
			||||||
    client.shards._spawn = client.shards.spawn.bind(client.shards);
 | 
					 | 
				
			||||||
    client.shards.spawn = function (id) {
 | 
					 | 
				
			||||||
      const res = this._spawn.apply(this, [id]);
 | 
					 | 
				
			||||||
      const shard = this.get(id);
 | 
					 | 
				
			||||||
      if (shard) {
 | 
					 | 
				
			||||||
        const identifyFunction = shard.identify.toString();
 | 
					 | 
				
			||||||
        const newIdentify = identifyFunction
 | 
					 | 
				
			||||||
          .replace(
 | 
					 | 
				
			||||||
            /properties: {\n\s+.+?\n\s+.+?\n\s+.+?\n\s+}\n/,
 | 
					 | 
				
			||||||
            "properties: comcord.clientSpoof.superProperties\n"
 | 
					 | 
				
			||||||
          )
 | 
					 | 
				
			||||||
          .replace(/\s+intents: this.client.shards.options.intents,/, "");
 | 
					 | 
				
			||||||
        if (identifyFunction === newIdentify)
 | 
					 | 
				
			||||||
          throw new Error("Failed to patch identify");
 | 
					 | 
				
			||||||
        shard.identify = new Function(
 | 
					 | 
				
			||||||
          `(function ${newIdentify}).apply(this, arguments)`
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        shard._wsEvent = shard.wsEvent;
 | 
					 | 
				
			||||||
        shard.wsEvent = function (packet) {
 | 
					 | 
				
			||||||
          if (packet.t == "READY") {
 | 
					 | 
				
			||||||
            packet.d.application = {id: CLIENT_ID, flags: 565248};
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          const ret = this._wsEvent.apply(this, [packet]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (packet.t == "READY") {
 | 
					 | 
				
			||||||
            for (const guild of packet.d.guilds) {
 | 
					 | 
				
			||||||
              this._wsEvent.apply(this, [
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                  t: "GUILD_CREATE",
 | 
					 | 
				
			||||||
                  d: guild,
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
              ]);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          return ret;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return res;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    console.log("% Connecting to gateway now");
 | 
					 | 
				
			||||||
    await client.connect();
 | 
					 | 
				
			||||||
  })();
 | 
					 | 
				
			||||||
} else {
 | 
					 | 
				
			||||||
  client.connect();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
console.log("COMcord (c)left 2022");
 | 
					 | 
				
			||||||
console.log("Type 'h' for Commands");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const dateObj = new Date();
 | 
					 | 
				
			||||||
let sentTime = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
 | 
					 | 
				
			||||||
const months = [
 | 
					 | 
				
			||||||
  "Jan",
 | 
					 | 
				
			||||||
  "Feb",
 | 
					 | 
				
			||||||
  "Mar",
 | 
					 | 
				
			||||||
  "Apr",
 | 
					 | 
				
			||||||
  "May",
 | 
					 | 
				
			||||||
  "Jun",
 | 
					 | 
				
			||||||
  "Jul",
 | 
					 | 
				
			||||||
  "Aug",
 | 
					 | 
				
			||||||
  "Sep",
 | 
					 | 
				
			||||||
  "Oct",
 | 
					 | 
				
			||||||
  "Nov",
 | 
					 | 
				
			||||||
  "Dec",
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
setInterval(function () {
 | 
					 | 
				
			||||||
  dateObj.setTime(Date.now());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const hour = dateObj.getUTCHours(),
 | 
					 | 
				
			||||||
    minutes = dateObj.getUTCMinutes(),
 | 
					 | 
				
			||||||
    seconds = dateObj.getUTCSeconds(),
 | 
					 | 
				
			||||||
    day = dateObj.getUTCDate(),
 | 
					 | 
				
			||||||
    month = dateObj.getUTCMonth(),
 | 
					 | 
				
			||||||
    year = dateObj.getUTCFullYear(),
 | 
					 | 
				
			||||||
    weekDay = dateObj.getUTCDay();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const timeString = `[${weekdays[weekDay]} ${day
 | 
					 | 
				
			||||||
    .toString()
 | 
					 | 
				
			||||||
    .padStart(2, "0")}-${months[month]}-${year
 | 
					 | 
				
			||||||
    .toString()
 | 
					 | 
				
			||||||
    .substring(2, 4)} ${hour.toString().padStart(2, "0")}:${minutes
 | 
					 | 
				
			||||||
    .toString()
 | 
					 | 
				
			||||||
    .padStart(2, "0")}:${seconds.toString().padStart(2, "0")}]`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (minutes % 15 == 0 && seconds < 2 && !sentTime) {
 | 
					 | 
				
			||||||
    if (comcord.state.inPrompt == true) {
 | 
					 | 
				
			||||||
      comcord.state.messageQueue.push({time: true, content: timeString});
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      console.log(timeString);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    comcord.state.nameLength = (client.user?.username?.length ?? 0) + 2;
 | 
					 | 
				
			||||||
    sentTime = true;
 | 
					 | 
				
			||||||
  } else if (seconds > 2 && sentTime) {
 | 
					 | 
				
			||||||
    sentTime = false;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}, 500);
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,133 +0,0 @@
 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * This single file is **EXCLUDED** from the project license.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * (c) 2022 Cynthia Foxwell, all rights reserved.
 | 
					 | 
				
			||||||
 * Permission is hereby granted to redistribute this file ONLY with copies of comcord.
 | 
					 | 
				
			||||||
 * You may not reverse engineer, modify, copy, or redistribute this file for any other uses outside of comcord.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const os = require("os");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function fetchMainPage() {
 | 
					 | 
				
			||||||
  const res = await fetch("https://discord.com/channels/@me");
 | 
					 | 
				
			||||||
  return await res.text();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function fetchAsset(assetPath) {
 | 
					 | 
				
			||||||
  return await fetch("https://discord.com/" + assetPath).then((res) =>
 | 
					 | 
				
			||||||
    res.text()
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const MATCH_SCRIPT = '<script src="(.+?)" integrity=".+?">';
 | 
					 | 
				
			||||||
const REGEX_SCRIPT = new RegExp(MATCH_SCRIPT);
 | 
					 | 
				
			||||||
const REGEX_SCRIPT_GLOBAL = new RegExp(MATCH_SCRIPT, "g");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function extractScripts() {
 | 
					 | 
				
			||||||
  const mainPage = await fetchMainPage();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return mainPage
 | 
					 | 
				
			||||||
    .match(REGEX_SCRIPT_GLOBAL)
 | 
					 | 
				
			||||||
    .map((script) => script.match(REGEX_SCRIPT)[1]);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const REGEX_BUILD_NUMBER = /Build Number: (\d+), Version Hash:/;
 | 
					 | 
				
			||||||
const REGEX_BUILD_NUMBER_SWC = /Build Number: "\).concat\("(\d+)"/;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function getBuildNumber() {
 | 
					 | 
				
			||||||
  if (comcord.state.cachedBuildNumber) {
 | 
					 | 
				
			||||||
    return comcord.state.cachedBuildNumber;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const scripts = await extractScripts();
 | 
					 | 
				
			||||||
  const chunkWithBuildInfoAsset = scripts[3];
 | 
					 | 
				
			||||||
  const chunkWithBuildInfo = await fetchAsset(chunkWithBuildInfoAsset);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const buildNumber =
 | 
					 | 
				
			||||||
    chunkWithBuildInfo.match(REGEX_BUILD_NUMBER_SWC)?.[1] ??
 | 
					 | 
				
			||||||
    chunkWithBuildInfo.match(REGEX_BUILD_NUMBER)?.[1];
 | 
					 | 
				
			||||||
  comcord.state.cachedBuildNumber = buildNumber;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return buildNumber;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*async function getClientVersion() {
 | 
					 | 
				
			||||||
  if (comcord.state.cachedClientVersion) {
 | 
					 | 
				
			||||||
    return comcord.state.cachedClientVersion;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const data = await fetch(
 | 
					 | 
				
			||||||
    "https://updates.discord.com/distributions/app/manifests/latest?channel=stable&platform=win&arch=x86"
 | 
					 | 
				
			||||||
  ).then((res) => res.json());
 | 
					 | 
				
			||||||
  const clientVersion = data.full.host_version.join(".");
 | 
					 | 
				
			||||||
  comcord.state.cachedClientVersion = clientVersion;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return clientVersion;
 | 
					 | 
				
			||||||
}*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function getBrowserInfo() {
 | 
					 | 
				
			||||||
  let targetOS;
 | 
					 | 
				
			||||||
  switch (process.platform) {
 | 
					 | 
				
			||||||
    case "win32":
 | 
					 | 
				
			||||||
    default:
 | 
					 | 
				
			||||||
      targetOS = "windows";
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case "darwin":
 | 
					 | 
				
			||||||
      targetOS = "mac os";
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case "linux":
 | 
					 | 
				
			||||||
      targetOS = "linux";
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const data = await fetch(
 | 
					 | 
				
			||||||
    `https://cdn.jsdelivr.net/gh/ray-lothian/UserAgent-Switcher/v2/firefox/data/popup/browsers/firefox-${encodeURIComponent(
 | 
					 | 
				
			||||||
      targetOS
 | 
					 | 
				
			||||||
    )}.json`
 | 
					 | 
				
			||||||
  ).then((res) => res.json());
 | 
					 | 
				
			||||||
  data.sort((a, b) => Number(b.browser.major) - Number(a.browser.major));
 | 
					 | 
				
			||||||
  const target = data[0];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return {ua: target.ua, version: target.browser.version};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function getSuperProperties() {
 | 
					 | 
				
			||||||
  const buildNumber = await getBuildNumber();
 | 
					 | 
				
			||||||
  // const clientVersion = await getClientVersion();
 | 
					 | 
				
			||||||
  const browserInfo = await getBrowserInfo();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let _os;
 | 
					 | 
				
			||||||
  switch (process.platform) {
 | 
					 | 
				
			||||||
    case "win32":
 | 
					 | 
				
			||||||
    default:
 | 
					 | 
				
			||||||
      _os = "Windows";
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case "darwin":
 | 
					 | 
				
			||||||
      _os = "Mac OS X";
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
    case "linux":
 | 
					 | 
				
			||||||
      _os = "Linux";
 | 
					 | 
				
			||||||
      break;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const props = {
 | 
					 | 
				
			||||||
    browser: "Firefox",
 | 
					 | 
				
			||||||
    browser_user_agent: browserInfo.ua,
 | 
					 | 
				
			||||||
    browser_version: browserInfo.version,
 | 
					 | 
				
			||||||
    client_build_number: buildNumber,
 | 
					 | 
				
			||||||
    client_event_source: null,
 | 
					 | 
				
			||||||
    device: "",
 | 
					 | 
				
			||||||
    os: _os,
 | 
					 | 
				
			||||||
    os_version: os.release(),
 | 
					 | 
				
			||||||
    //os_arch: os.arch(),
 | 
					 | 
				
			||||||
    referrer: "",
 | 
					 | 
				
			||||||
    referrer_current: "",
 | 
					 | 
				
			||||||
    referring_domain: "",
 | 
					 | 
				
			||||||
    referring_domain_current: "",
 | 
					 | 
				
			||||||
    release_channel: "stable",
 | 
					 | 
				
			||||||
    system_locale: "en-US",
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
  return props;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {getSuperProperties};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,17 +0,0 @@
 | 
				
			||||||
function addCommand(key, name, callback) {
 | 
					 | 
				
			||||||
  if (comcord.commands[key]) {
 | 
					 | 
				
			||||||
    console.error(
 | 
					 | 
				
			||||||
      `Registering duplicate key for "${key}": "${name}" wants to overwrite "${comcord.commands[key].name}"!`
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  comcord.commands[key] = {
 | 
					 | 
				
			||||||
    name,
 | 
					 | 
				
			||||||
    callback,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
  addCommand,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,566 +0,0 @@
 | 
				
			||||||
const {Constants} = require("@projectdysnomia/dysnomia");
 | 
					 | 
				
			||||||
const chalk = require("chalk");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const REGEX_CODEBLOCK = /```(?:([a-z0-9_+\-.]+?)\n)?\n*([^\n][^]*?)\n*```/i;
 | 
					 | 
				
			||||||
const REGEX_CODEBLOCK_GLOBAL =
 | 
					 | 
				
			||||||
  /```(?:[a-z0-9_+\-.]+?\n)?\n*([^\n][^]*?)\n*```/gi;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const REGEX_MENTION = /<@!?(\d+)>/g;
 | 
					 | 
				
			||||||
const REGEX_ROLE_MENTION = /<@&?(\d+)>/g;
 | 
					 | 
				
			||||||
const REGEX_CHANNEL = /<#(\d+)>/g;
 | 
					 | 
				
			||||||
const REGEX_EMOTE = /<(?:\u200b|&)?a?:(\w+):(\d+)>/g;
 | 
					 | 
				
			||||||
const REGEX_COMMAND = /<\/([^\s]+?):(\d+)>/g;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const REGEX_BLOCKQUOTE = /^ *>>?>? +/;
 | 
					 | 
				
			||||||
const REGEX_GREENTEXT = /^(>.+?)(?:\n|$)/;
 | 
					 | 
				
			||||||
const REGEX_SPOILER = /\|\|(.+?)\|\|/;
 | 
					 | 
				
			||||||
const REGEX_BOLD = /\*\*(.+?)\*\*/g;
 | 
					 | 
				
			||||||
const REGEX_UNDERLINE = /__(.+?)__/g;
 | 
					 | 
				
			||||||
const REGEX_ITALIC_1 = /\*(.+?)\*/g;
 | 
					 | 
				
			||||||
const REGEX_ITALIC_2 = /_(.+?)_/g;
 | 
					 | 
				
			||||||
const REGEX_STRIKE = /~~(.+?)~~/g;
 | 
					 | 
				
			||||||
const REGEX_3Y3 = /[\u{e0020}-\u{e007e}]{1,}/gu;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function readableTime(time) {
 | 
					 | 
				
			||||||
  const seconds = time / 1000;
 | 
					 | 
				
			||||||
  const minutes = seconds / 60;
 | 
					 | 
				
			||||||
  const hours = minutes / 60;
 | 
					 | 
				
			||||||
  const days = hours / 24;
 | 
					 | 
				
			||||||
  const weeks = days / 7;
 | 
					 | 
				
			||||||
  const months = days / 30;
 | 
					 | 
				
			||||||
  const years = days / 365.25;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (years >= 1) {
 | 
					 | 
				
			||||||
    return `${years.toFixed(0)} year${years > 1 ? "s" : ""}`;
 | 
					 | 
				
			||||||
  } else if (weeks > 5 && months < 13) {
 | 
					 | 
				
			||||||
    return `${months.toFixed(0)} month${months > 1 ? "s" : ""}`;
 | 
					 | 
				
			||||||
  } else if (days > 7 && weeks < 5) {
 | 
					 | 
				
			||||||
    return `${weeks.toFixed(0)} week${weeks > 1 ? "s" : ""}`;
 | 
					 | 
				
			||||||
  } else if (hours > 24 && days < 7) {
 | 
					 | 
				
			||||||
    return `${days.toFixed(0)} day${days > 1 ? "s" : ""}`;
 | 
					 | 
				
			||||||
  } else if (minutes > 60 && hours < 24) {
 | 
					 | 
				
			||||||
    return `${hours.toFixed(0)} hour${hours > 1 ? "s" : ""}`;
 | 
					 | 
				
			||||||
  } else if (seconds > 60 && minutes < 60) {
 | 
					 | 
				
			||||||
    return `${minutes.toFixed(0)} minute${minutes > 1 ? "s" : ""}`;
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    return `${seconds.toFixed(0)} second${seconds > 1 ? "s" : ""}`;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const MONTH_NAMES = [
 | 
					 | 
				
			||||||
  "January",
 | 
					 | 
				
			||||||
  "Feburary",
 | 
					 | 
				
			||||||
  "March",
 | 
					 | 
				
			||||||
  "April",
 | 
					 | 
				
			||||||
  "May",
 | 
					 | 
				
			||||||
  "June",
 | 
					 | 
				
			||||||
  "July",
 | 
					 | 
				
			||||||
  "August",
 | 
					 | 
				
			||||||
  "September",
 | 
					 | 
				
			||||||
  "October",
 | 
					 | 
				
			||||||
  "November",
 | 
					 | 
				
			||||||
  "December",
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
const DAY_NAMES = [
 | 
					 | 
				
			||||||
  "Sunday",
 | 
					 | 
				
			||||||
  "Monday",
 | 
					 | 
				
			||||||
  "Tuesday",
 | 
					 | 
				
			||||||
  "Wednesday",
 | 
					 | 
				
			||||||
  "Thursday",
 | 
					 | 
				
			||||||
  "Friday",
 | 
					 | 
				
			||||||
  "Saturday",
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
const TIME_FORMATS = {
 | 
					 | 
				
			||||||
  t: function (time) {
 | 
					 | 
				
			||||||
    const timeObj = new Date(time);
 | 
					 | 
				
			||||||
    return timeObj.getUTCHours() + 1 + ":" + timeObj.getUTCMinutes();
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  T: function (time) {
 | 
					 | 
				
			||||||
    const timeObj = new Date(time);
 | 
					 | 
				
			||||||
    return TIME_FORMATS.t(time) + ":" + timeObj.getUTCSeconds();
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  d: function (time) {
 | 
					 | 
				
			||||||
    const timeObj = new Date(time);
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      timeObj.getUTCFullYear() +
 | 
					 | 
				
			||||||
      "/" +
 | 
					 | 
				
			||||||
      (timeObj.getUTCMonth() + 1).toString().padStart(2, "0") +
 | 
					 | 
				
			||||||
      "/" +
 | 
					 | 
				
			||||||
      timeObj.getUTCDate().toString().padStart(2, "0")
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  D: function (time) {
 | 
					 | 
				
			||||||
    const timeObj = new Date(time);
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      timeObj.getUTCDate() +
 | 
					 | 
				
			||||||
      " " +
 | 
					 | 
				
			||||||
      MONTH_NAMES[timeObj.getUTCMonth()] +
 | 
					 | 
				
			||||||
      " " +
 | 
					 | 
				
			||||||
      timeObj.getUTCFullYear()
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  f: function (time) {
 | 
					 | 
				
			||||||
    return TIME_FORMATS.D(time) + " " + TIME_FORMATS.t(time);
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  F: function (time) {
 | 
					 | 
				
			||||||
    const timeObj = new Date(time);
 | 
					 | 
				
			||||||
    return DAY_NAMES[timeObj.getUTCDay()] + ", " + TIME_FORMATS.f(time);
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  R: function (time) {
 | 
					 | 
				
			||||||
    const now = Date.now();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (time > now) {
 | 
					 | 
				
			||||||
      const delta = time - now;
 | 
					 | 
				
			||||||
      return "in " + readableTime(delta);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      const delta = now - time;
 | 
					 | 
				
			||||||
      return readableTime(delta) + " ago";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
const REGEX_TIMESTAMP = new RegExp(
 | 
					 | 
				
			||||||
  `<t:(-?\\d{1,17})(?::(${Object.keys(TIME_FORMATS).join("|")}))?>`,
 | 
					 | 
				
			||||||
  "g"
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function replaceMentions(_, id) {
 | 
					 | 
				
			||||||
  const user = comcord.client.users.get(id);
 | 
					 | 
				
			||||||
  if (user) {
 | 
					 | 
				
			||||||
    return `@${user.username}`;
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    return "@Unknown User";
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
function replaceRoles(_, id) {
 | 
					 | 
				
			||||||
  const role = comcord.client.guilds
 | 
					 | 
				
			||||||
    .get(comcord.state.currentGuild)
 | 
					 | 
				
			||||||
    .roles.get(id);
 | 
					 | 
				
			||||||
  if (role) {
 | 
					 | 
				
			||||||
    return `[@${role.name}]`;
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    return "[@Unknown Role]";
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
function replaceChannels(_, id) {
 | 
					 | 
				
			||||||
  const guildForChannel = comcord.client.channelGuildMap[id];
 | 
					 | 
				
			||||||
  if (guildForChannel) {
 | 
					 | 
				
			||||||
    const channel = comcord.client.guilds.get(guildForChannel).channels.get(id);
 | 
					 | 
				
			||||||
    if (channel) {
 | 
					 | 
				
			||||||
      return `#${channel.name}`;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      return "#unknown-channel";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    return "#unknown-channel";
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
function replaceEmotes(_, name, id) {
 | 
					 | 
				
			||||||
  return `:${name}:`;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
function replaceCommands(_, name, id) {
 | 
					 | 
				
			||||||
  return `/${name}`;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
function replaceTimestamps(_, time, format = "f") {
 | 
					 | 
				
			||||||
  return TIME_FORMATS[format](time * 1000);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function replaceStyledMarkdown(content) {
 | 
					 | 
				
			||||||
  content = content.replace(REGEX_BLOCKQUOTE, chalk.blackBright("\u258e"));
 | 
					 | 
				
			||||||
  content = content.replace(REGEX_GREENTEXT, (orig) => chalk.green(orig));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (comcord.config.enable3y3) {
 | 
					 | 
				
			||||||
    content = content.replace(REGEX_3Y3, (text) =>
 | 
					 | 
				
			||||||
      chalk.italic.magenta(
 | 
					 | 
				
			||||||
        [...text]
 | 
					 | 
				
			||||||
          .map((char) => String.fromCodePoint(char.codePointAt(0) - 0xe0000))
 | 
					 | 
				
			||||||
          .join("")
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  content = content.replace(REGEX_SPOILER, (_, text) =>
 | 
					 | 
				
			||||||
    chalk.bgBlack.black(text)
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  content = content.replace(REGEX_STRIKE, (_, text) =>
 | 
					 | 
				
			||||||
    chalk.strikethrough(text)
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  content = content.replace(REGEX_BOLD, (_, text) => chalk.bold(text));
 | 
					 | 
				
			||||||
  content = content.replace(REGEX_UNDERLINE, (_, text) =>
 | 
					 | 
				
			||||||
    chalk.underline(text)
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  content = content
 | 
					 | 
				
			||||||
    .replace(REGEX_ITALIC_1, (_, text) => chalk.italic(text))
 | 
					 | 
				
			||||||
    .replace(REGEX_ITALIC_2, (_, text) => chalk.italic(text));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return content;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function formatMessage({
 | 
					 | 
				
			||||||
  channel,
 | 
					 | 
				
			||||||
  name,
 | 
					 | 
				
			||||||
  content,
 | 
					 | 
				
			||||||
  bot,
 | 
					 | 
				
			||||||
  attachments,
 | 
					 | 
				
			||||||
  stickers,
 | 
					 | 
				
			||||||
  reply,
 | 
					 | 
				
			||||||
  timestamp,
 | 
					 | 
				
			||||||
  mention = false,
 | 
					 | 
				
			||||||
  noColor = false,
 | 
					 | 
				
			||||||
  dump = false,
 | 
					 | 
				
			||||||
  history = false,
 | 
					 | 
				
			||||||
  dm = false,
 | 
					 | 
				
			||||||
  join = false,
 | 
					 | 
				
			||||||
  pin = false,
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
  const dateObj = new Date(timestamp);
 | 
					 | 
				
			||||||
  const hour = dateObj.getUTCHours().toString().padStart(2, "0"),
 | 
					 | 
				
			||||||
    minutes = dateObj.getUTCMinutes().toString().padStart(2, "0"),
 | 
					 | 
				
			||||||
    seconds = dateObj.getUTCSeconds().toString().padStart(2, "0");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let console = global.console;
 | 
					 | 
				
			||||||
  const lines = [];
 | 
					 | 
				
			||||||
  if (history) {
 | 
					 | 
				
			||||||
    console = {
 | 
					 | 
				
			||||||
      log: function (...args) {
 | 
					 | 
				
			||||||
        lines.push(...args.join(" ").split("\n"));
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (name.length + 2 > comcord.state.nameLength)
 | 
					 | 
				
			||||||
    comcord.state.nameLength = name.length + 2;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (reply) {
 | 
					 | 
				
			||||||
    const nameColor = reply.author.bot ? chalk.bold.yellow : chalk.bold.cyan;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const headerLength = 5 + reply.author.username.length;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let replyContent = reply.content.replace(/\n/g, " ");
 | 
					 | 
				
			||||||
    replyContent = replyContent
 | 
					 | 
				
			||||||
      .replace(REGEX_MENTION, replaceMentions)
 | 
					 | 
				
			||||||
      .replace(REGEX_ROLE_MENTION, replaceRoles)
 | 
					 | 
				
			||||||
      .replace(REGEX_CHANNEL, replaceChannels)
 | 
					 | 
				
			||||||
      .replace(REGEX_EMOTE, replaceEmotes)
 | 
					 | 
				
			||||||
      .replace(REGEX_COMMAND, replaceCommands)
 | 
					 | 
				
			||||||
      .replace(REGEX_TIMESTAMP, replaceTimestamps);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!noColor) {
 | 
					 | 
				
			||||||
      replyContent = replaceStyledMarkdown(replyContent);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      if (comcord.config.enable3y3) {
 | 
					 | 
				
			||||||
        replyContent = replyContent.replace(
 | 
					 | 
				
			||||||
          REGEX_3Y3,
 | 
					 | 
				
			||||||
          (text) =>
 | 
					 | 
				
			||||||
            `<3y3:${[...text]
 | 
					 | 
				
			||||||
              .map((char) =>
 | 
					 | 
				
			||||||
                String.fromCodePoint(char.codePointAt(0) - 0xe0000)
 | 
					 | 
				
			||||||
              )
 | 
					 | 
				
			||||||
              .join("")}>`
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (reply.attachments.size > 0) {
 | 
					 | 
				
			||||||
      replyContent += ` <${reply.attachments.size} attachment${
 | 
					 | 
				
			||||||
        reply.attachments.size > 1 ? "s" : ""
 | 
					 | 
				
			||||||
      }>`;
 | 
					 | 
				
			||||||
      replyContent = replyContent.trim();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const length = headerLength + replyContent.length;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (noColor) {
 | 
					 | 
				
			||||||
      console.log(
 | 
					 | 
				
			||||||
        ` \u250d [${reply.author.username}] ${
 | 
					 | 
				
			||||||
          length > 79
 | 
					 | 
				
			||||||
            ? replyContent.substring(0, 79 - headerLength) + "\u2026"
 | 
					 | 
				
			||||||
            : replyContent
 | 
					 | 
				
			||||||
        }`
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      console.log(
 | 
					 | 
				
			||||||
        chalk.bold.white(" \u250d ") +
 | 
					 | 
				
			||||||
          nameColor(`[${reply.author.username}] `) +
 | 
					 | 
				
			||||||
          `${
 | 
					 | 
				
			||||||
            length > 79
 | 
					 | 
				
			||||||
              ? replyContent.substring(0, 79 - headerLength) +
 | 
					 | 
				
			||||||
                chalk.reset("\u2026")
 | 
					 | 
				
			||||||
              : replyContent
 | 
					 | 
				
			||||||
          }`
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (dump) {
 | 
					 | 
				
			||||||
    if (history) {
 | 
					 | 
				
			||||||
      const headerLength = 80 - (name.length + 5);
 | 
					 | 
				
			||||||
      console.log(`--- ${name} ${"-".repeat(headerLength)}`);
 | 
					 | 
				
			||||||
      console.log(content);
 | 
					 | 
				
			||||||
      console.log(`--- ${name} ${"-".repeat(headerLength)}`);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      const wordCount = content.split(" ").length;
 | 
					 | 
				
			||||||
      const lineCount = content.split("\n").length;
 | 
					 | 
				
			||||||
      if (noColor) {
 | 
					 | 
				
			||||||
        console.log(
 | 
					 | 
				
			||||||
          `<${name} DUMPs in ${content.length} characters of ${wordCount} word${
 | 
					 | 
				
			||||||
            wordCount > 1 ? "s" : ""
 | 
					 | 
				
			||||||
          } in ${lineCount} line${lineCount > 1 ? "s" : ""}>`
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        console.log(
 | 
					 | 
				
			||||||
          chalk.bold.yellow(
 | 
					 | 
				
			||||||
            `<${name} DUMPs in ${
 | 
					 | 
				
			||||||
              content.length
 | 
					 | 
				
			||||||
            } characters of ${wordCount} word${
 | 
					 | 
				
			||||||
              wordCount > 1 ? "s" : ""
 | 
					 | 
				
			||||||
            } in ${lineCount} line${lineCount > 1 ? "s" : ""}>`
 | 
					 | 
				
			||||||
          )
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    content = content
 | 
					 | 
				
			||||||
      .replace(REGEX_MENTION, replaceMentions)
 | 
					 | 
				
			||||||
      .replace(REGEX_ROLE_MENTION, replaceRoles)
 | 
					 | 
				
			||||||
      .replace(REGEX_CHANNEL, replaceChannels)
 | 
					 | 
				
			||||||
      .replace(REGEX_EMOTE, replaceEmotes)
 | 
					 | 
				
			||||||
      .replace(REGEX_COMMAND, replaceCommands)
 | 
					 | 
				
			||||||
      .replace(REGEX_TIMESTAMP, replaceTimestamps);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (dm) {
 | 
					 | 
				
			||||||
      if (noColor) {
 | 
					 | 
				
			||||||
        if (comcord.config.enable3y3) {
 | 
					 | 
				
			||||||
          content = content.replace(
 | 
					 | 
				
			||||||
            REGEX_3Y3,
 | 
					 | 
				
			||||||
            (text) =>
 | 
					 | 
				
			||||||
              `<3y3:${[...text]
 | 
					 | 
				
			||||||
                .map((char) =>
 | 
					 | 
				
			||||||
                  String.fromCodePoint(char.codePointAt(0) - 0xe0000)
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                .join("")}>`
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        console.log(`*${name}* ${content}\x07`);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        content = replaceStyledMarkdown(content);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        console.log(`${chalk.bold.red(`*${name}*`)} ${content}\x07`);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else if (
 | 
					 | 
				
			||||||
      content.length > 1 &&
 | 
					 | 
				
			||||||
      ((content.startsWith("*") &&
 | 
					 | 
				
			||||||
        content.endsWith("*") &&
 | 
					 | 
				
			||||||
        !content.startsWith("**") &&
 | 
					 | 
				
			||||||
        !content.endsWith("**")) ||
 | 
					 | 
				
			||||||
        (content.startsWith("_") &&
 | 
					 | 
				
			||||||
          content.endsWith("_") &&
 | 
					 | 
				
			||||||
          !content.startsWith("__") &&
 | 
					 | 
				
			||||||
          !content.endsWith("__")))
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
      if (comcord.config.enable3y3) {
 | 
					 | 
				
			||||||
        content = content.replace(
 | 
					 | 
				
			||||||
          REGEX_3Y3,
 | 
					 | 
				
			||||||
          (text) =>
 | 
					 | 
				
			||||||
            `<3y3:${[...text]
 | 
					 | 
				
			||||||
              .map((char) =>
 | 
					 | 
				
			||||||
                String.fromCodePoint(char.codePointAt(0) - 0xe0000)
 | 
					 | 
				
			||||||
              )
 | 
					 | 
				
			||||||
              .join("")}>`
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      const str = `<${name} ${content.substring(1, content.length - 1)}>`;
 | 
					 | 
				
			||||||
      if (noColor) {
 | 
					 | 
				
			||||||
        console.log(str);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        console.log(chalk.bold.green(str));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else if (join) {
 | 
					 | 
				
			||||||
      const str = `[${hour}:${minutes}:${seconds}] ${name} has joined ${channel.guild.name}`;
 | 
					 | 
				
			||||||
      if (noColor) {
 | 
					 | 
				
			||||||
        console.log(str);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        console.log(chalk.bold.yellow(str));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else if (pin) {
 | 
					 | 
				
			||||||
      const str = `[${hour}:${minutes}:${seconds}] ${name} pinned a message to this channel`;
 | 
					 | 
				
			||||||
      if (noColor) {
 | 
					 | 
				
			||||||
        console.log(str);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        console.log(chalk.bold.yellow(str));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      if (noColor) {
 | 
					 | 
				
			||||||
        if (comcord.config.enable3y3) {
 | 
					 | 
				
			||||||
          content = content.replace(
 | 
					 | 
				
			||||||
            REGEX_3Y3,
 | 
					 | 
				
			||||||
            (text) =>
 | 
					 | 
				
			||||||
              `<3y3:${[...text]
 | 
					 | 
				
			||||||
                .map((char) =>
 | 
					 | 
				
			||||||
                  String.fromCodePoint(char.codePointAt(0) - 0xe0000)
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                .join("")}>`
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        console.log(
 | 
					 | 
				
			||||||
          `[${name}]${" ".repeat(
 | 
					 | 
				
			||||||
            Math.abs(comcord.state.nameLength - (name.length + 2))
 | 
					 | 
				
			||||||
          )} ${content}`
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        const nameColor = mention
 | 
					 | 
				
			||||||
          ? chalk.bold.red
 | 
					 | 
				
			||||||
          : bot
 | 
					 | 
				
			||||||
          ? chalk.bold.yellow
 | 
					 | 
				
			||||||
          : chalk.bold.cyan;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        content = replaceStyledMarkdown(content);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        console.log(
 | 
					 | 
				
			||||||
          `${nameColor(`[${name}]`)}${" ".repeat(
 | 
					 | 
				
			||||||
            Math.abs(comcord.state.nameLength - (name.length + 2))
 | 
					 | 
				
			||||||
          )} ${content}${mention ? "\x07" : ""}`
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (attachments) {
 | 
					 | 
				
			||||||
    for (const attachment of attachments.values()) {
 | 
					 | 
				
			||||||
      if (noColor) {
 | 
					 | 
				
			||||||
        console.log(`<attachment: ${attachment.url} >`);
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        console.log(chalk.bold.yellow(`<attachment: ${attachment.url} >`));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (stickers) {
 | 
					 | 
				
			||||||
    for (const sticker of stickers) {
 | 
					 | 
				
			||||||
      if (noColor) {
 | 
					 | 
				
			||||||
        console.log(
 | 
					 | 
				
			||||||
          `<sticker: "${sticker.name}" https://media.discordapp.net/stickers/${sticker.id}.png >`
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        console.log(
 | 
					 | 
				
			||||||
          chalk.bold.yellow(
 | 
					 | 
				
			||||||
            `<sticker: "${sticker.name}" https://media.discordapp.net/stickers/${sticker.id}.png >`
 | 
					 | 
				
			||||||
          )
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (history) {
 | 
					 | 
				
			||||||
    return lines;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return null;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function processMessage(msg, options = {}) {
 | 
					 | 
				
			||||||
  if (
 | 
					 | 
				
			||||||
    msg.channel?.type === Constants.ChannelTypes.DM ||
 | 
					 | 
				
			||||||
    msg.channel?.type === Constants.ChannelTypes.GROUP_DM
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    options.dm = true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (msg.type === Constants.MessageTypes.USER_JOIN) {
 | 
					 | 
				
			||||||
    options.join = true;
 | 
					 | 
				
			||||||
  } else if (msg.type === Constants.MessageTypes.CHANNEL_PINNED_MESSAGE) {
 | 
					 | 
				
			||||||
    options.pin = true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (msg.time) {
 | 
					 | 
				
			||||||
    console.log(msg.content);
 | 
					 | 
				
			||||||
    return null;
 | 
					 | 
				
			||||||
  } else if (msg.ping) {
 | 
					 | 
				
			||||||
    console.log(
 | 
					 | 
				
			||||||
      chalk.bold.red(
 | 
					 | 
				
			||||||
        `**mentioned by ${msg.author?.username ?? "<unknown>"} in #${
 | 
					 | 
				
			||||||
          msg.channel?.name ?? "<unknown>"
 | 
					 | 
				
			||||||
        } in ${msg.channel.guild?.name ?? "<unknown>"}**\x07`
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    return null;
 | 
					 | 
				
			||||||
  } else if (msg.content && msg.content.indexOf("\n") > -1) {
 | 
					 | 
				
			||||||
    if (msg.content.match(REGEX_CODEBLOCK)) {
 | 
					 | 
				
			||||||
      return formatMessage({
 | 
					 | 
				
			||||||
        channel: msg.channel,
 | 
					 | 
				
			||||||
        name: msg.author.username,
 | 
					 | 
				
			||||||
        bot: msg.author.bot,
 | 
					 | 
				
			||||||
        content: msg.content.replace(
 | 
					 | 
				
			||||||
          REGEX_CODEBLOCK_GLOBAL,
 | 
					 | 
				
			||||||
          (_, content) => content
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        attachments: msg.attachments,
 | 
					 | 
				
			||||||
        stickers: msg.stickerItems,
 | 
					 | 
				
			||||||
        reply: msg.referencedMessage,
 | 
					 | 
				
			||||||
        timestamp: msg.timestamp,
 | 
					 | 
				
			||||||
        mention:
 | 
					 | 
				
			||||||
          msg.mentionsEveryone ||
 | 
					 | 
				
			||||||
          msg.mentions.find((user) => user.id == comcord.client.user.id),
 | 
					 | 
				
			||||||
        dump: true,
 | 
					 | 
				
			||||||
        ...options,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      const lines = msg.content.split("\n");
 | 
					 | 
				
			||||||
      const outLines = [];
 | 
					 | 
				
			||||||
      for (const index in lines) {
 | 
					 | 
				
			||||||
        const line = lines[index];
 | 
					 | 
				
			||||||
        outLines.push(
 | 
					 | 
				
			||||||
          formatMessage({
 | 
					 | 
				
			||||||
            channel: msg.channel,
 | 
					 | 
				
			||||||
            name: msg.author.username,
 | 
					 | 
				
			||||||
            bot: msg.author.bot,
 | 
					 | 
				
			||||||
            content:
 | 
					 | 
				
			||||||
              line +
 | 
					 | 
				
			||||||
              (msg.editedTimestamp != null && index == lines.length - 1
 | 
					 | 
				
			||||||
                ? " (edited)"
 | 
					 | 
				
			||||||
                : ""),
 | 
					 | 
				
			||||||
            attachments: index == lines.length - 1 ? msg.attachments : [],
 | 
					 | 
				
			||||||
            stickers: index == lines.length - 1 ? msg.stickerItems : [],
 | 
					 | 
				
			||||||
            reply: index == 0 ? msg.referencedMessage : null,
 | 
					 | 
				
			||||||
            timestamp: msg.timestamp,
 | 
					 | 
				
			||||||
            mention:
 | 
					 | 
				
			||||||
              index == 0 &&
 | 
					 | 
				
			||||||
              (msg.mentionsEveryone ||
 | 
					 | 
				
			||||||
                msg.mentions.find((user) => user.id == comcord.client.user.id)),
 | 
					 | 
				
			||||||
            ...options,
 | 
					 | 
				
			||||||
          })
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return outLines;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    return formatMessage({
 | 
					 | 
				
			||||||
      channel: msg.channel,
 | 
					 | 
				
			||||||
      name: msg.author.username,
 | 
					 | 
				
			||||||
      bot: msg.author.bot,
 | 
					 | 
				
			||||||
      content: msg.content + (msg.editedTimestamp != null ? " (edited)" : ""),
 | 
					 | 
				
			||||||
      attachments: msg.attachments,
 | 
					 | 
				
			||||||
      stickers: msg.stickerItems,
 | 
					 | 
				
			||||||
      reply: msg.referencedMessage,
 | 
					 | 
				
			||||||
      timestamp: msg.timestamp,
 | 
					 | 
				
			||||||
      mention:
 | 
					 | 
				
			||||||
        msg.mentionsEveryone ||
 | 
					 | 
				
			||||||
        msg.mentions.find((user) => user.id == comcord.client.user.id),
 | 
					 | 
				
			||||||
      ...options,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function processQueue() {
 | 
					 | 
				
			||||||
  for (const msg of comcord.state.messageQueue) {
 | 
					 | 
				
			||||||
    processMessage(msg);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  comcord.state.messageQueue.splice(0, comcord.state.messageQueue.length);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
  processMessage,
 | 
					 | 
				
			||||||
  processQueue,
 | 
					 | 
				
			||||||
  formatMessage,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,89 +0,0 @@
 | 
				
			||||||
const CLIENT_ID = "1026163285877325874";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function updatePresence() {
 | 
					 | 
				
			||||||
  let guild, channel;
 | 
					 | 
				
			||||||
  if (comcord.state.currentGuild != null) {
 | 
					 | 
				
			||||||
    guild = comcord.client.guilds.get(comcord.state.currentGuild);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (comcord.state.currentChannel != null && guild != null) {
 | 
					 | 
				
			||||||
    channel = guild.channels.get(comcord.state.currentChannel);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (comcord.client.user.bot) {
 | 
					 | 
				
			||||||
    if (comcord.state.rpcConnected) {
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        const activity = {
 | 
					 | 
				
			||||||
          startTimestamp: comcord.state.startTime,
 | 
					 | 
				
			||||||
          smallImageKey: `https://cdn.discordapp.com/avatars/${comcord.client.user.id}/${comcord.client.user.avatar}.png?size=1024`,
 | 
					 | 
				
			||||||
          smallImageText: comcord.client.user.username,
 | 
					 | 
				
			||||||
          buttons: [
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
              label: "comcord Repo",
 | 
					 | 
				
			||||||
              url: "https://github.com/Cynosphere/comcord",
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
          ],
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (guild != null) {
 | 
					 | 
				
			||||||
          activity.largeImageKey = `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.png?size=1024`;
 | 
					 | 
				
			||||||
          activity.largeImageText = guild.name;
 | 
					 | 
				
			||||||
          if (channel != null) {
 | 
					 | 
				
			||||||
            activity.details = `#${channel.name} - ${guild.name}`;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (comcord.state.afk == true) {
 | 
					 | 
				
			||||||
          activity.state = "AFK";
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        comcord.rpc.setActivity(activity);
 | 
					 | 
				
			||||||
      } catch (err) {
 | 
					 | 
				
			||||||
        //
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    comcord.client.editStatus(
 | 
					 | 
				
			||||||
      comcord.state.afk ? "idle" : comcord.config.defaultStatus ?? "online",
 | 
					 | 
				
			||||||
      [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          name: "comcord",
 | 
					 | 
				
			||||||
          type: 0,
 | 
					 | 
				
			||||||
          application_id: CLIENT_ID,
 | 
					 | 
				
			||||||
          timestamps: {
 | 
					 | 
				
			||||||
            start: comcord.state.startTime,
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      ]
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    const activity = {
 | 
					 | 
				
			||||||
      application_id: CLIENT_ID,
 | 
					 | 
				
			||||||
      name: "comcord",
 | 
					 | 
				
			||||||
      timestamps: {
 | 
					 | 
				
			||||||
        start: comcord.state.startTime,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      assets: {},
 | 
					 | 
				
			||||||
      buttons: ["comcord Repo"],
 | 
					 | 
				
			||||||
      metadata: {
 | 
					 | 
				
			||||||
        button_urls: ["https://github.com/Cynosphere/comcord"],
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      type: 0,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (guild != null) {
 | 
					 | 
				
			||||||
      activity.assets.large_image = `mp:icons/${guild.id}/${guild.icon}.png?size=1024`;
 | 
					 | 
				
			||||||
      activity.assets.large_text = guild.name;
 | 
					 | 
				
			||||||
      if (channel != null) {
 | 
					 | 
				
			||||||
        activity.details = `#${channel.name} - ${guild.name}`;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (comcord.state.afk == true) {
 | 
					 | 
				
			||||||
      activity.state = "AFK";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    comcord.client.editStatus(
 | 
					 | 
				
			||||||
      comcord.state.afk ? "idle" : comcord.config.defaultStatus ?? "online",
 | 
					 | 
				
			||||||
      [activity]
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {updatePresence};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,22 +0,0 @@
 | 
				
			||||||
function startPrompt(display, callback) {
 | 
					 | 
				
			||||||
  comcord.state.inPrompt = true;
 | 
					 | 
				
			||||||
  comcord.state.promptText = display;
 | 
					 | 
				
			||||||
  comcord.state.promptInput = "";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  comcord.state.promptCallback = callback;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  process.stdout.write(display);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function finalizePrompt() {
 | 
					 | 
				
			||||||
  comcord.state.inPrompt = false;
 | 
					 | 
				
			||||||
  comcord.state.promptText = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const input = comcord.state.promptInput.trim();
 | 
					 | 
				
			||||||
  await comcord.state.promptCallback(input);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					 | 
				
			||||||
  startPrompt,
 | 
					 | 
				
			||||||
  finalizePrompt,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,30 +0,0 @@
 | 
				
			||||||
const fs = require("fs");
 | 
					 | 
				
			||||||
const path = require("path");
 | 
					 | 
				
			||||||
const os = require("os");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const RCPATH = path.resolve(os.homedir(), ".comcordrc");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function readFile(config) {
 | 
					 | 
				
			||||||
  const rc = fs.readFileSync(RCPATH, "utf8");
 | 
					 | 
				
			||||||
  const lines = rc.split("\n");
 | 
					 | 
				
			||||||
  for (const line of lines) {
 | 
					 | 
				
			||||||
    const [key, value] = line.split("=");
 | 
					 | 
				
			||||||
    config[key] = value;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function writeFile(config) {
 | 
					 | 
				
			||||||
  if (fs.existsSync(RCPATH)) {
 | 
					 | 
				
			||||||
    readFile(config);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  const newrc = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  for (const key in config) {
 | 
					 | 
				
			||||||
    const value = config[key];
 | 
					 | 
				
			||||||
    newrc.push(`${key}=${value}`);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  fs.writeFileSync(RCPATH, newrc.join("\n"));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {readFile, writeFile, path: RCPATH};
 | 
					 | 
				
			||||||
							
								
								
									
										181
									
								
								state/main.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								state/main.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,181 @@
 | 
				
			||||||
 | 
					package state
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/diamondburned/arikawa/v3/discord"
 | 
				
			||||||
 | 
						"github.com/diamondburned/ningen/v3"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ComcordState struct {
 | 
				
			||||||
 | 
					  Client *ningen.State
 | 
				
			||||||
 | 
					  Config map[string]string
 | 
				
			||||||
 | 
					  Readied bool
 | 
				
			||||||
 | 
					  Connected bool
 | 
				
			||||||
 | 
					  RPCConnected bool
 | 
				
			||||||
 | 
					  StartTime time.Time
 | 
				
			||||||
 | 
					  CurrentGuild string
 | 
				
			||||||
 | 
					  CurrentChannel string
 | 
				
			||||||
 | 
					  NameLength int
 | 
				
			||||||
 | 
					  InPrompt bool
 | 
				
			||||||
 | 
					  PromptText string
 | 
				
			||||||
 | 
					  AFK bool
 | 
				
			||||||
 | 
					  MessageQueue []discord.Message
 | 
				
			||||||
 | 
					  LastChannel map[string]string
 | 
				
			||||||
 | 
					  LastDM string
 | 
				
			||||||
 | 
					  NoColor bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var state ComcordState
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Setup(config map[string]string, client *ningen.State) {
 | 
				
			||||||
 | 
					  state = ComcordState{}
 | 
				
			||||||
 | 
					  state.Client = client
 | 
				
			||||||
 | 
					  state.Config = config
 | 
				
			||||||
 | 
					  state.Readied = false
 | 
				
			||||||
 | 
					  state.Connected = true
 | 
				
			||||||
 | 
					  state.RPCConnected = false
 | 
				
			||||||
 | 
					  state.StartTime = time.Now()
 | 
				
			||||||
 | 
					  state.CurrentGuild = ""
 | 
				
			||||||
 | 
					  state.CurrentChannel = ""
 | 
				
			||||||
 | 
					  state.NameLength = 2
 | 
				
			||||||
 | 
					  state.InPrompt = false
 | 
				
			||||||
 | 
					  state.PromptText = ""
 | 
				
			||||||
 | 
					  state.AFK = false
 | 
				
			||||||
 | 
					  state.MessageQueue = make([]discord.Message, 0)
 | 
				
			||||||
 | 
					  state.LastChannel = make(map[string]string)
 | 
				
			||||||
 | 
					  state.LastDM = ""
 | 
				
			||||||
 | 
					  state.NoColor = false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetClient() *ningen.State {
 | 
				
			||||||
 | 
					  return state.Client
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func HasReadied() bool {
 | 
				
			||||||
 | 
					  return state.Readied
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SetReadied(value bool) {
 | 
				
			||||||
 | 
					  state.Readied = value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func IsConnected() bool {
 | 
				
			||||||
 | 
					  return state.Connected
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SetConnected(value bool) {
 | 
				
			||||||
 | 
					  state.Connected = value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func IsRPCConnected() bool {
 | 
				
			||||||
 | 
					  return state.RPCConnected
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SetRPCConnected(value bool) {
 | 
				
			||||||
 | 
					  state.RPCConnected = value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetStartTime() time.Time {
 | 
				
			||||||
 | 
					  return state.StartTime
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetCurrentGuild() string {
 | 
				
			||||||
 | 
					  return state.CurrentGuild
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SetCurrentGuild(value string) {
 | 
				
			||||||
 | 
					  state.CurrentGuild = value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetCurrentChannel() string {
 | 
				
			||||||
 | 
					  return state.CurrentChannel
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SetCurrentChannel(value string) {
 | 
				
			||||||
 | 
					  state.CurrentChannel = value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetNameLength() int {
 | 
				
			||||||
 | 
					  return state.NameLength
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SetNameLength(value int) {
 | 
				
			||||||
 | 
					  state.NameLength = value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func IsInPrompt() bool {
 | 
				
			||||||
 | 
					  return state.InPrompt
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SetInPrompt(value bool) {
 | 
				
			||||||
 | 
					  state.InPrompt = value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetPromptText() string {
 | 
				
			||||||
 | 
					  return state.PromptText
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SetPromptText(value string) {
 | 
				
			||||||
 | 
					  state.PromptText = value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func IsAFK() bool {
 | 
				
			||||||
 | 
					  return state.AFK
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SetAFK(value bool) {
 | 
				
			||||||
 | 
					  state.AFK = value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetMessageQueue() []discord.Message {
 | 
				
			||||||
 | 
					  return state.MessageQueue
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func AddMessageToQueue(msg discord.Message) {
 | 
				
			||||||
 | 
					  state.MessageQueue = append(state.MessageQueue, msg)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func EmptyMessageQueue() {
 | 
				
			||||||
 | 
					  state.MessageQueue = make([]discord.Message, 0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SetLastChannel(guild string, channel string) {
 | 
				
			||||||
 | 
					  state.LastChannel[guild] = channel
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetLastChannel(guild string) string {
 | 
				
			||||||
 | 
					  channel, has := state.LastChannel[guild]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if has {
 | 
				
			||||||
 | 
					    return channel
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    return ""
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetLastDM() string {
 | 
				
			||||||
 | 
					  return state.LastDM
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SetLastDM(value string) {
 | 
				
			||||||
 | 
					  state.LastDM = value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetConfigValue(key string) string {
 | 
				
			||||||
 | 
					  value, has := state.Config[key]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if has {
 | 
				
			||||||
 | 
					    return value
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    return ""
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SetNoColor(value bool) {
 | 
				
			||||||
 | 
					  state.NoColor = value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func HasNoColor() bool {
 | 
				
			||||||
 | 
					  return state.NoColor
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue