Skip to content
This repository was archived by the owner on Mar 24, 2026. It is now read-only.
/ Crycord Public archive

Commit e360dd7

Browse files
committed
Initial commit
0 parents  commit e360dd7

File tree

17 files changed

+737
-0
lines changed

17 files changed

+737
-0
lines changed

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*.cr]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
indent_style = space
8+
indent_size = 2
9+
trim_trailing_whitespace = true

.github/FUNDING.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ko_fi: geopjr
2+
liberapay: GeopJr
3+
custom: ["https://www.paypal.me/GeopJr"]

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/docs/
2+
/lib/
3+
/bin/
4+
/.shards/
5+
*.dwarf

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2020 GeopJr
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<div align="center">
2+
<br />
3+
<p>
4+
<img src="https://i.imgur.com/eS2ugrZ.png"/>
5+
</p>
6+
<br />
7+
<p>
8+
<a href="https://ko-fi.com/GeopJr" title="Donate to this project using Ko-Fi"><img src="https://img.shields.io/badge/Buy%20me%20a-KoFi-white.svg" alt="Ko-Fi donate button" /></a>
9+
<a href="https://liberapay.com/GeopJr"><img src="https://img.shields.io/liberapay/patrons/GeopJr.svg?logo=liberapay" alt="liberapay" ></a>
10+
<a href="https://github.com/GeopJr/Crycord/blob/master/LICENSE"><img src="https://img.shields.io/badge/LICENSE-MIT-000000.svg" alt="MIT" /></a>
11+
</p>
12+
</div>
13+
14+
# crycord
15+
16+
Crycord is a modular Discord Client Mod written in Crystal.
17+
18+
Uses [asar-cr](https://github.com/GeopJr/asar-cr).
19+
20+
## Installation
21+
22+
You can download the *statically* linked build from the releases page!
23+
24+
## Building
25+
26+
1. `shards install`
27+
2. `crystal build --release`
28+
29+
*Note:* Static builds can only be built in AlpineLinux
30+
31+
## Usage
32+
33+
```
34+
$ ./crycord -h
35+
36+
<== [Crycord] ==>
37+
-v, --version Show version
38+
-h, --help Show help
39+
-gs, --groups Lists all available plugin groups
40+
-p, --plugins Lists all available plugins
41+
-c CSS_PATH, --css=CSS_PATH Sets CSS location
42+
-f CORE_ASAR_PATH, --force=CORE_ASAR_PATH
43+
Forces an asar path
44+
-g PLUGIN_GROUP, --group=PLUGIN_GROUP
45+
Selects the plugin group(s) to install. Split multiple groups with commas(,).
46+
```
47+
48+
```
49+
$ ./crycord -c ./Downloads/css.css
50+
51+
Flatpak Detected:
52+
Make sure it has access to your CSS file
53+
Usually ~/Downloads is accessible
54+
Extracting core.asar...
55+
Installing enable_css...
56+
Installing enable_https...
57+
Packing core.asar...
58+
Done!
59+
Restart Discord to see the results!
60+
```
61+
62+
## Benchmarks
63+
64+
Crycord:
65+
```
66+
$time ./crycord -c ~/Downloads/css.css
67+
...
68+
69+
real 0m2,942s
70+
user 0m2,932s
71+
sys 0m0,462s
72+
```
73+
74+
BeautifulDiscord:
75+
```
76+
$ time python3 -m beautifuldiscord --css ~/Downloads/css.css
77+
...
78+
79+
real 0m4,593s
80+
user 0m2,026s
81+
sys 0m2,381s
82+
```
83+
84+
## (Laggy) Gifs
85+
86+
![install](https://i.imgur.com/gf6Sa8i.gif)
87+
![restore](https://i.imgur.com/1ooO8me.gif)
88+
![hotreload](https://i.imgur.com/q6nAvhT.gif)
89+
90+
## WARNING
91+
92+
- Any Discord Client modification is against their T.O.S.
93+
- I am not responsible if your account gets terminated.
94+
- Using a client mod such as this (and all others), deactivates many electron security functions.
95+
- If a Discord Staff happens to stumble upon this, I don't use this tool on my account and it's made for educational purposes.
96+
97+
## Goals
98+
99+
As I also wrote the shard that manages the asar pack/extract (these 2 functions at least),
100+
my main goal is to achieve max speed and max compatibility. Using Crystal only tools like Path, File, Dir etc.
101+
is one way to reach it. However, since I don't have access to a Mac and Windows doesn't have proper support, some
102+
paths (Discord config) are made specifically for linux.
103+
104+
## How is it different than BeautifulDiscord?
105+
106+
First of all, it's written, well... in Crystal!
107+
108+
That alone makes it a lot faster!
109+
110+
Crycord also has a plugin(?) system!
111+
112+
Lastly, it can patch the flatpak version.
113+
114+
## TODO
115+
116+
- Use a cross-platform way to find Discord's pid
117+
- Clean the module collector
118+
- GitHub action using docker in an attempt to build static builds automatically
119+
120+
## Contributing
121+
122+
1. Fork it (<https://github.com/your-github-user/crycord/fork>)
123+
2. Create your feature branch (`git checkout -b my-new-feature`)
124+
3. Commit your changes (`git commit -am 'Add some feature'`)
125+
4. Push to the branch (`git push origin my-new-feature`)
126+
5. Create a new Pull Request
127+
128+
## Contributors
129+
130+
- [GeopJr](https://github.com/GeopJr) - creator and maintainer
131+
- [leovoel](https://github.com/leovoel) - CSS injector

shard.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: crycord
2+
version: 1.0.0
3+
4+
authors:
5+
- GeopJr <[email protected]>
6+
7+
targets:
8+
crycord:
9+
main: src/crycord.cr
10+
11+
dependencies:
12+
asar-cr:
13+
github: GeopJr/asar-cr
14+
15+
crystal: 0.34.0
16+
17+
license: MIT

src/crycord.cr

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
require "option_parser"
2+
require "asar-cr"
3+
require "./version.cr"
4+
require "./functions/*"
5+
require "./locators/*"
6+
require "./plugins/core/*"
7+
require "./plugins/extra/*"
8+
9+
module Crycord
10+
extend self
11+
@@plugins : Array(String)
12+
@@available_groups : Array(String)
13+
14+
@@plugins = Crycord::PLUGINS.keys.reject! { |x| Crycord::PLUGINS[x].disabled }
15+
@@available_groups = @@plugins.map { |x| x = Crycord::PLUGINS[x].category }.uniq
16+
css_path = ""
17+
asar_path = ""
18+
groups = ["core"]
19+
should_revert = false
20+
21+
def get_paths : String
22+
path = flatpak
23+
if path.nil?
24+
path = linux
25+
else
26+
puts "Flatpak Detected:"
27+
puts "Make sure it has access to your CSS file"
28+
puts "Usually ~/Downloads is accessible"
29+
end
30+
if path.nil?
31+
puts "ERROR: couldn't find core.asar, try manually setting it using the -f flag."
32+
exit
33+
end
34+
return path.to_s
35+
end
36+
37+
def group_list
38+
puts "Available plugin groups:"
39+
puts @@available_groups.join("\n")
40+
puts "Note: core is being installed by default"
41+
end
42+
43+
# CLI options
44+
OptionParser.parse do |parser|
45+
parser.banner = "<== [Crycord] ==>"
46+
47+
parser.on "-v", "--version", "Show version" do
48+
puts "Crycord"
49+
puts "Version: #{VERSION}"
50+
exit
51+
end
52+
parser.on "-h", "--help", "Show help" do
53+
puts parser
54+
exit
55+
end
56+
parser.on "-gs", "--groups", "Lists all available plugin groups" do
57+
group_list
58+
exit
59+
end
60+
parser.on "-r", "--revert", "Reverts back to original asar" do
61+
should_revert = true
62+
end
63+
parser.on "-p", "--plugins", "Lists all available plugins" do
64+
available_plugins = Crycord::PLUGINS.keys.map { |x| x = Crycord::PLUGINS[x].name + " | " + Crycord::PLUGINS[x].category + " | " + Crycord::PLUGINS[x].desc }.uniq
65+
puts "Available plugins:"
66+
puts "NAME | GROUP | DESCRIPTION"
67+
puts available_plugins.reject! { |x| Crycord::PLUGINS[x].disabled }.join("\n")
68+
puts "Note: Disabled plugins are omitted"
69+
exit
70+
end
71+
parser.on "-c CSS_PATH", "--css=CSS_PATH", "Sets CSS location" do |path|
72+
css = Path[path].expand(home: true)
73+
unless File.exists?(css)
74+
puts "ERROR: CSS file not found"
75+
exit
76+
end
77+
css_path = css.to_s
78+
end
79+
parser.on "-f CORE_ASAR_PATH", "--force=CORE_ASAR_PATH", "Forces an asar path" do |path|
80+
asar_path = Path[path].expand(home: true).to_s
81+
unless File.exists?(asar_path)
82+
puts "ERROR: core.asar not found"
83+
exit
84+
end
85+
end
86+
parser.on "-g PLUGIN_GROUP", "--group=PLUGIN_GROUP", "Selects the plugin group(s) to install. Split multiple groups with commas(,)." do |input|
87+
if input == "" || input.nil?
88+
group_list
89+
end
90+
groups.concat(input.downcase.gsub(" ", "").split(","))
91+
groups.uniq!
92+
groups.each do |item|
93+
unless @@available_groups.includes?(item)
94+
puts "ERROR: unknown group, use the -gs flag to list all groups."
95+
exit
96+
end
97+
end
98+
end
99+
100+
parser.missing_option do |option_flag|
101+
STDERR.puts "ERROR: #{option_flag} is missing something."
102+
STDERR.puts ""
103+
STDERR.puts parser
104+
exit(1)
105+
end
106+
parser.invalid_option do |option_flag|
107+
STDERR.puts "ERROR: #{option_flag} is not a valid option."
108+
STDERR.puts parser
109+
exit(1)
110+
end
111+
end
112+
113+
# Check revert
114+
if should_revert
115+
asar_path = get_paths if asar_path == ""
116+
res = revert(Path[asar_path])
117+
puts "Restore was #{res.nil? ? "un" : ""}successful"
118+
exit
119+
end
120+
121+
# Check options
122+
if css_path == ""
123+
puts "ERROR: -c option is missing"
124+
exit
125+
end
126+
127+
# Check discord and get paths
128+
asar_path = get_paths if asar_path == ""
129+
130+
# Extract asar
131+
puts "Extracting core.asar..."
132+
path = extract(Path[asar_path])
133+
if path.nil?
134+
puts "ERROR: couldn't extract core.asar"
135+
exit
136+
end
137+
138+
# See collector.cr
139+
modules = Crycord::Plugins.collected_modules
140+
selected_plugins = @@plugins.reject! { |x| !groups.includes?(Crycord::PLUGINS[x].category) }
141+
142+
selected_plugins.each do |plugin|
143+
plugin_module = modules.find { |i| i.to_s == "Crycord::Plugins::#{plugin.upcase}" }
144+
css = Crycord::PLUGINS[plugin].css ? css_path : nil
145+
puts "Installing #{plugin}..."
146+
plugin_module.try &.execute(path, css)
147+
end
148+
149+
puts "Packing core.asar..."
150+
pack(Path[path])
151+
puts "Done!"
152+
puts "Restart Discord to see the results!"
153+
end

src/functions/asar.cr

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
require "file_utils"
2+
3+
module Crycord
4+
# extract and pack require a significant amount of RAM
5+
6+
# Asar to path
7+
def extract(path : Path) : Path | Nil
8+
output = path.parent.join("core").to_s
9+
return if Dir.exists?(output)
10+
asar = Asar::Extract.new path.to_s
11+
asar.extract output
12+
Path[output]
13+
end
14+
15+
# Path to asar
16+
def pack(path : Path) : Path | Nil
17+
return unless Dir.exists?(path)
18+
core = path.parent.join("core.asar")
19+
backup(core) unless File.exists?(path.parent.join("core.asar.bak"))
20+
File.delete(core) if File.exists?(core)
21+
asar = Asar::Pack.new path.to_s
22+
asar.pack core.to_s
23+
delete_unpacked(path)
24+
core
25+
end
26+
27+
def delete_unpacked(path : Path) : Bool
28+
FileUtils.rm_rf(path.to_s)
29+
true
30+
end
31+
32+
def backup(path : Path) : Bool
33+
File.rename(path.to_s, path.parent.join("core.asar.bak").to_s)
34+
return true
35+
end
36+
37+
def revert(path : Path) : Bool | Nil
38+
parent = path.parent
39+
bak = parent.join("core.asar.bak")
40+
return unless File.exists?(path) && File.exists?(bak)
41+
File.delete(path.to_s)
42+
File.rename(bak.to_s, path.to_s)
43+
true
44+
end
45+
end

0 commit comments

Comments
 (0)