-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild_pack.py
More file actions
117 lines (98 loc) · 4.46 KB
/
Copy pathbuild_pack.py
File metadata and controls
117 lines (98 loc) · 4.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#!/usr/bin/env python3
"""Build the server emote resource pack from a Discord guild's custom emojis.
Reads every custom emoji in your guild, downloads each as a static PNG, and
builds a Minecraft resource pack whose font maps a private-use codepoint
(U+E000+) to each emote. The server pushes this pack to clients, so even
vanilla players (no mod) see the emotes drawn in chat. Pairs with the
WaveMotes client mod, which bakes the same glyphs in so they render even if
the pack never reaches a player.
Config lives in config.json (copy config.example.json):
{
"guild_id": "your Discord guild id",
"bot_token": "a bot token that is in that guild",
"output_dir": "out"
}
Outputs in <output_dir>/:
MineWaveEmotes.zip the resource pack (set as the server resource-pack)
emote_map.json name -> {char, codepoint, id, ...}, used by inject_emoticons.py
SHA1 the pack's sha1 (server.properties resource-pack-sha1)
Usage: python build_pack.py
"""
import json
import os
import sys
import urllib.request
import urllib.error
import hashlib
import shutil
import zipfile
HERE = os.path.dirname(os.path.abspath(__file__))
def load_config():
path = os.path.join(HERE, 'config.json')
if not os.path.exists(path):
sys.exit('config.json not found. Copy config.example.json and fill it in.')
cfg = json.load(open(path, encoding='utf-8'))
if not cfg.get('guild_id') or not cfg.get('bot_token'):
sys.exit('config.json needs both "guild_id" and "bot_token".')
return cfg
def http(url, headers=None):
h = {'User-Agent': 'DiscordBot (https://w4ve.xyz, 1.0)'}
if headers:
h.update(headers)
req = urllib.request.Request(url, headers=h)
with urllib.request.urlopen(req, timeout=30) as r:
return r.read()
def main():
cfg = load_config()
guild = str(cfg['guild_id'])
token = cfg['bot_token']
work = os.path.join(HERE, cfg.get('output_dir', 'out'))
pack = os.path.join(work, 'pack')
texdir = os.path.join(pack, 'assets/minecraft/textures/font/emote')
fontdir = os.path.join(pack, 'assets/minecraft/font')
data = json.loads(http('https://discord.com/api/v10/guilds/%s/emojis' % guild,
{'Authorization': 'Bot ' + token}))
print('emojis from guild:', len(data))
shutil.rmtree(work, ignore_errors=True)
os.makedirs(texdir)
os.makedirs(fontdir)
providers, mapping, fails = [], {}, []
cp = 0xE000
for e in sorted(data, key=lambda x: x['name'].lower()):
name, eid, anim = e['name'], e['id'], bool(e.get('animated'))
# .png returns a static (first-frame) PNG even for animated emojis; size=64 pre-scales
url = 'https://cdn.discordapp.com/emojis/%s.png?size=64' % eid
try:
raw = http(url)
except urllib.error.HTTPError as ex:
fails.append((name, eid, ex.code))
continue
if raw[:8] != b'\x89PNG\r\n\x1a\n':
fails.append((name, eid, 'not-png'))
continue
safe = ''.join(c if c.isalnum() or c in '_-' else '_' for c in name)
open(os.path.join(texdir, safe + '.png'), 'wb').write(raw)
ch = chr(cp)
providers.append({"type": "bitmap", "file": "minecraft:font/emote/%s.png" % safe,
"ascent": 7, "height": 8, "chars": [ch]})
mapping[name] = {"char": ch, "codepoint": "U+%04X" % cp, "id": eid,
"animated": anim, "file": safe + '.png'}
cp += 1
json.dump({"providers": providers}, open(os.path.join(fontdir, 'default.json'), 'w'),
ensure_ascii=False, indent=2)
json.dump({"pack": {"pack_format": 34, "description": cfg.get('pack_description', "MineWave Emotes")}},
open(os.path.join(pack, 'pack.mcmeta'), 'w'), ensure_ascii=False, indent=2)
json.dump(mapping, open(os.path.join(work, 'emote_map.json'), 'w'), ensure_ascii=False, indent=2)
zip_path = os.path.join(work, 'MineWaveEmotes.zip')
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as z:
for root, _, files in os.walk(pack):
for f in files:
full = os.path.join(root, f)
z.write(full, os.path.relpath(full, pack))
sha1 = hashlib.sha1(open(zip_path, 'rb').read()).hexdigest()
print('ZIP %.1f KB' % (os.path.getsize(zip_path) / 1024))
print('SHA1:', sha1)
print('packed:', len(mapping), 'fails:', fails)
open(os.path.join(work, 'SHA1'), 'w').write(sha1)
if __name__ == '__main__':
main()