discord: fetch distros at build time (#507728)

This commit is contained in:
Sefa Eyeoglu
2026-04-24 06:26:55 +00:00
committed by GitHub
5 changed files with 481 additions and 113 deletions
@@ -1,13 +1,13 @@
{
pname,
version,
src,
source,
meta,
stdenv,
binaryName,
desktopName,
self,
lib,
fetchurl,
undmg,
makeWrapper,
writeScript,
@@ -33,6 +33,9 @@ let
];
enabledDiscordModsCount = builtins.length (lib.filter (x: x) discordMods);
inherit (source) version;
src = fetchurl { inherit (source) url hash; };
disableBreakingUpdates =
runCommand "disable-breaking-updates.py"
{
@@ -106,6 +109,7 @@ stdenv.mkDerivation {
passthru = {
# make it possible to run disableBreakingUpdates standalone
inherit disableBreakingUpdates;
inherit source;
updateScript = ./update.py;
tests = {
@@ -1,6 +1,5 @@
{
callPackage,
fetchurl,
lib,
stdenv,
discord,
@@ -101,13 +100,7 @@ lib.genAttrs [ "discord" "discord-ptb" "discord-canary" "discord-development" ]
callPackage package (
args
// {
inherit pname;
inherit (source) version;
src = fetchurl {
inherit (source) url hash;
};
inherit pname source;
meta = meta // {
mainProgram = args.binaryName;
};
@@ -1,12 +1,12 @@
{
pname,
version,
src,
source,
meta,
binaryName,
desktopName,
self,
autoPatchelfHook,
fetchurl,
makeDesktopItem,
lib,
stdenv,
@@ -44,15 +44,18 @@
libxrender,
libxtst,
libxcb,
libxkbcommon,
libxshmfence,
libgbm,
nspr,
nss,
openssl_1_1,
pango,
systemdLibs,
libappindicator-gtk3,
libdbusmenu,
writeScript,
brotli,
writeShellScript,
pipewire,
python3,
runCommand,
@@ -85,6 +88,97 @@ let
];
enabledDiscordModsCount = builtins.length (lib.filter (x: x) discordMods);
# Starting with discord-development 0.0.235, the linux tarball ships only a
# small `updater_bootstrap` ELF that downloads the real app at first launch
#
# That binary always fetches the latest version from Discord's CDN with no way
# to pin, making the build impure and the nix version a lie
#
# Instead we fetch the app directly from the distributions API at build time:
# https://updates.discord.com/distributions/app/manifests/latest?channel=...
# The host + module distros are brotli-compressed tars on Discord's CDN at
# predictable URLs with SHA256 hashes in the manifest
isDistro = source.kind == "distro";
inherit (source) version;
src =
if isDistro then
fetchurl { inherit (source.distro) url hash; }
else
fetchurl { inherit (source) url hash; };
moduleSrcs = lib.optionalAttrs isDistro (
lib.mapAttrs (_: mod: fetchurl { inherit (mod) url hash; }) source.modules
);
moduleVersions = lib.optionalAttrs isDistro (lib.mapAttrs (_: mod: mod.version) source.modules);
libPath = lib.makeLibraryPath (
[
libcxx
systemdLibs
libpulseaudio
libdrm
libgbm
stdenv.cc.cc
alsa-lib
atk
at-spi2-atk
at-spi2-core
cairo
cups
dbus
expat
fontconfig
freetype
gdk-pixbuf
glib
gtk3
libglvnd
libnotify
libx11
libxcomposite
libunity
libuuid
libva
libxcursor
libxdamage
libxext
libxfixes
libxi
libxrandr
libxrender
libxtst
nspr
nss
libxcb
libxkbcommon
pango
pipewire
libxscrnsaver
libappindicator-gtk3
libdbusmenu
wayland
]
++ lib.optionals withTTS [ speechd-minimal ]
);
# Symlink native modules from the nix store into the user config dir
# where Discord's JS moduleUpdater expects them.
stageModules = writeShellScript "discord-stage-modules" ''
store_modules="$1"
modules_dir="''${XDG_CONFIG_HOME:-$HOME/.config}/${lib.toLower binaryName}/${version}/modules"
if [ ! -f "$modules_dir/installed.json" ]; then
mkdir -p "$modules_dir"
for m in ${lib.concatStringsSep " " (lib.attrNames moduleSrcs)}; do
ln -sfn "$store_modules/$m" "$modules_dir/$m"
done
echo '${builtins.toJSON (lib.mapAttrs (_: mod: { installedVersion = mod; }) moduleVersions)}' \
> "$modules_dir/installed.json"
fi
'';
disableBreakingUpdates =
runCommand "disable-breaking-updates.py"
{
@@ -123,7 +217,8 @@ stdenv.mkDerivation (finalAttrs: {
libxshmfence
wrapGAppsHook3
makeShellWrapper
];
]
++ lib.optionals isDistro [ brotli ];
dontWrapGApps = true;
@@ -132,67 +227,59 @@ stdenv.mkDerivation (finalAttrs: {
libgbm
nspr
nss
]
# The new distro layout ships prebuilt `.node` modules:
# discord_dispatch is linked against openssl 1.1, discord_voice against libpulseaudio
++ lib.optionals isDistro [
openssl_1_1
libpulseaudio
];
strictDeps = true;
libPath = lib.makeLibraryPath (
[
libcxx
systemdLibs
libpulseaudio
libdrm
libgbm
stdenv.cc.cc
alsa-lib
atk
at-spi2-atk
at-spi2-core
cairo
cups
dbus
expat
fontconfig
freetype
gdk-pixbuf
glib
gtk3
libglvnd
libnotify
libx11
libxcomposite
libunity
libuuid
libva
libxcursor
libxdamage
libxext
libxfixes
libxi
libxrandr
libxrender
libxtst
nspr
libxcb
pango
pipewire
libxscrnsaver
libappindicator-gtk3
libdbusmenu
wayland
]
++ lib.optionals withTTS [ speechd-minimal ]
);
dontUnpack = isDistro;
inherit libPath;
installPhase = ''
runHook preInstall
mkdir -p $out/{bin,opt/${binaryName},share/icons/hicolor/256x256/apps}
mv * $out/opt/${binaryName}
''
+ (
if isDistro then
''
# Distro layout (currently discord-ptb, discord-canary and discord-development):
#
# The host distro is a brotli-compressed tar with all files under a `files/`
# prefix (the channel binary, libffmpeg.so, resources/, etc). Module distros
# follow the same format with module contents under `files/`
#
# The module directory layout must match what Discord's node runtime
# expects: modules/<name>/ (the moduleUpdater extracts zips into
# path.join(moduleInstallPath, moduleName) see processUnzipQueue)
chmod +x $out/opt/${binaryName}/${binaryName}
patchelf --set-interpreter ${stdenv.cc.bintools.dynamicLinker} \
$out/opt/${binaryName}/${binaryName}
brotli -d < $src | tar xf - --strip-components=1 -C $out/opt/${binaryName}
chmod +x $out/opt/${binaryName}/${binaryName}
# Extract native modules
${lib.concatStringsSep "\n" (
lib.mapAttrsToList (name: src: ''
mkdir -p $out/opt/${binaryName}/modules/${name}
brotli -d < ${src} | tar xf - --strip-components=1 -C $out/opt/${binaryName}/modules/${name}
'') moduleSrcs
)}
''
else
''
# Tarball layout (stable): the tarball unpacks into a
# directory containing the channel binary directly
mv * $out/opt/${binaryName}
chmod +x $out/opt/${binaryName}/${binaryName}
''
)
+ ''
wrapProgramShell $out/opt/${binaryName}/${binaryName} \
"''${gappsWrapperArgs[@]}" \
@@ -205,6 +292,7 @@ stdenv.mkDerivation (finalAttrs: {
--prefix XDG_DATA_DIRS : "${gtk3}/share/gsettings-schemas/${gtk3.name}/" \
--prefix LD_LIBRARY_PATH : ${finalAttrs.libPath}:$out/opt/${binaryName} \
${lib.strings.optionalString disableUpdates "--run ${lib.getExe disableBreakingUpdates}"} \
${lib.strings.optionalString isDistro ''--run "${stageModules} $out/opt/${binaryName}/modules"''} \
--add-flags ${lib.escapeShellArg commandLineArgs}
ln -s $out/opt/${binaryName}/${binaryName} $out/bin/
@@ -258,6 +346,8 @@ stdenv.mkDerivation (finalAttrs: {
passthru = {
# make it possible to run disableBreakingUpdates standalone
inherit disableBreakingUpdates;
# Exposed so reviewers can inspect which distro modules are pinned
inherit source moduleVersions;
updateScript = ./update.py;
tests = {
@@ -1,42 +1,242 @@
{
"linux-canary": {
"hash": "sha256-wTAmrtGcJpI/DIrLQu/++WuVzMr9EcJs+AIFkAilFvk=",
"url": "https://canary.dl2.discordapp.net/apps/linux/0.0.927/discord-canary-0.0.927.tar.gz",
"version": "0.0.927"
"distro": {
"hash": "sha256-4xA+Kh6UiwtoUt5A2kPI5rcbSEc/SoZzJbwFdjMNVpE=",
"url": "https://canary.dl2.discordapp.net/distro/app/canary/linux/x64/1.0.962/full.distro"
},
"kind": "distro",
"modules": {
"discord_cloudsync": {
"hash": "sha256-3dcVKm/Sz9bqhEm8xVMtRcWrU6JWtv2Dh8oz/3K489E=",
"url": "https://canary.dl2.discordapp.net/distro/app/canary/linux/x64/1.0.962/discord_cloudsync/1/full.distro",
"version": 1
},
"discord_desktop_core": {
"hash": "sha256-7zcFkh3f9BVjHlevCkZXUFNdeLCkM2+sysuSUyPcQlA=",
"url": "https://canary.dl2.discordapp.net/distro/app/canary/linux/x64/1.0.962/discord_desktop_core/1/full.distro",
"version": 1
},
"discord_dispatch": {
"hash": "sha256-TD1vWHt4y3xkAEc/BV2HZe3tNdP0PTZsXhVeeHvRAU4=",
"url": "https://canary.dl2.discordapp.net/distro/app/canary/linux/x64/1.0.962/discord_dispatch/1/full.distro",
"version": 1
},
"discord_erlpack": {
"hash": "sha256-nv6kKT+ZWfwkH4BvOWtjF8VZoznzYRN0hK8kiTbcX/I=",
"url": "https://canary.dl2.discordapp.net/distro/app/canary/linux/x64/1.0.962/discord_erlpack/1/full.distro",
"version": 1
},
"discord_game_utils": {
"hash": "sha256-Ru8zXxDGwHrpMnnraAVR0Z7jRA4PRLHX7pBz8jctar8=",
"url": "https://canary.dl2.discordapp.net/distro/app/canary/linux/x64/1.0.962/discord_game_utils/1/full.distro",
"version": 1
},
"discord_krisp": {
"hash": "sha256-zkhTv+8qzyrZBHpZeCzJX6z8HCK0M4Jt6Kr+InbVT80=",
"url": "https://canary.dl2.discordapp.net/distro/app/canary/linux/x64/1.0.962/discord_krisp/1/full.distro",
"version": 1
},
"discord_modules": {
"hash": "sha256-j5UE4ZugIF4HTsOgxK3Dfp0WP3Sya1fbDjERxGu2qSM=",
"url": "https://canary.dl2.discordapp.net/distro/app/canary/linux/x64/1.0.962/discord_modules/1/full.distro",
"version": 1
},
"discord_rpc": {
"hash": "sha256-HaUt+G05Im2uNkUB9Ry1c6X/iadc7xLrcEgJCVgpgsY=",
"url": "https://canary.dl2.discordapp.net/distro/app/canary/linux/x64/1.0.962/discord_rpc/1/full.distro",
"version": 1
},
"discord_spellcheck": {
"hash": "sha256-8cOwisLqnxGsbqPYStBAaKEaALIM0M5d3RPLtoHxClw=",
"url": "https://canary.dl2.discordapp.net/distro/app/canary/linux/x64/1.0.962/discord_spellcheck/1/full.distro",
"version": 1
},
"discord_utils": {
"hash": "sha256-hfQu3fT9ON0j8xEAwCRY7/ALYYq/a70W/8HeT3KhZF0=",
"url": "https://canary.dl2.discordapp.net/distro/app/canary/linux/x64/1.0.962/discord_utils/1/full.distro",
"version": 1
},
"discord_voice": {
"hash": "sha256-JJXt5wHat6AtxW1GIzsPnMHJPzX1DlPgB3kDcg9Bnts=",
"url": "https://canary.dl2.discordapp.net/distro/app/canary/linux/x64/1.0.962/discord_voice/1/full.distro",
"version": 1
},
"discord_zstd": {
"hash": "sha256-PMlqdAI44AngVh75Eq66zHLEoJkI0262FcFUffPykCk=",
"url": "https://canary.dl2.discordapp.net/distro/app/canary/linux/x64/1.0.962/discord_zstd/1/full.distro",
"version": 1
}
},
"version": "1.0.962"
},
"linux-development": {
"hash": "sha256-Xe5PjHDWXU+eIAcBID34gjuADmAl1JAQLmXUAi/p4tg=",
"url": "https://development.dl2.discordapp.net/apps/linux/0.0.99/discord-development-0.0.99.tar.gz",
"version": "0.0.99"
"distro": {
"hash": "sha256-6MLiRO2GYXkBRQjzOwmKHVqMqGsVjk0E3ocvdYyg8qs=",
"url": "https://development.dl2.discordapp.net/distro/app/development/linux/x64/1.0.976/full.distro"
},
"kind": "distro",
"modules": {
"discord_cloudsync": {
"hash": "sha256-6yEFPNHqs5Mk+1V2x4R/ZwFgwb2Z+O9iOrX78/G3ynM=",
"url": "https://development.dl2.discordapp.net/distro/app/development/linux/x64/1.0.976/discord_cloudsync/1/full.distro",
"version": 1
},
"discord_desktop_core": {
"hash": "sha256-Z82Y9I4brUOvGiZhgL5V+wXRFfDH/XazWJG3OMYyo3s=",
"url": "https://development.dl2.discordapp.net/distro/app/development/linux/x64/1.0.976/discord_desktop_core/1/full.distro",
"version": 1
},
"discord_dispatch": {
"hash": "sha256-MTjgc8SfL7i4O+QkYQu0Os04iAM1x85AAZxuooI5Oog=",
"url": "https://development.dl2.discordapp.net/distro/app/development/linux/x64/1.0.976/discord_dispatch/1/full.distro",
"version": 1
},
"discord_erlpack": {
"hash": "sha256-JQAgkIpev2+1dlfPW/bg/Iek/cL3+V92IpZIa8e+Fp8=",
"url": "https://development.dl2.discordapp.net/distro/app/development/linux/x64/1.0.976/discord_erlpack/1/full.distro",
"version": 1
},
"discord_game_utils": {
"hash": "sha256-ixKdUlXhDVmk3P41gFixSpPJCMnd3c/yLQF1J7DYI+M=",
"url": "https://development.dl2.discordapp.net/distro/app/development/linux/x64/1.0.976/discord_game_utils/1/full.distro",
"version": 1
},
"discord_krisp": {
"hash": "sha256-UJmBXQm0SXbCOjtJN72BZJCViULhpJgc0rgqDrhhNR0=",
"url": "https://development.dl2.discordapp.net/distro/app/development/linux/x64/1.0.976/discord_krisp/1/full.distro",
"version": 1
},
"discord_modules": {
"hash": "sha256-+TH9x91upIuX9nwvpF9pX4w58YRqu6+hbF1RSyiykdM=",
"url": "https://development.dl2.discordapp.net/distro/app/development/linux/x64/1.0.976/discord_modules/1/full.distro",
"version": 1
},
"discord_rpc": {
"hash": "sha256-cGK1qN8jUiw2l9KNbb7eucWWPw191AHzh/4C6T8gI44=",
"url": "https://development.dl2.discordapp.net/distro/app/development/linux/x64/1.0.976/discord_rpc/1/full.distro",
"version": 1
},
"discord_spellcheck": {
"hash": "sha256-b/X24WJi2Mi0x7Ynqphmtr6FSYUMSR9dDPPUF/k/TN8=",
"url": "https://development.dl2.discordapp.net/distro/app/development/linux/x64/1.0.976/discord_spellcheck/1/full.distro",
"version": 1
},
"discord_utils": {
"hash": "sha256-+dslIrgHH5r7sz95Ixb+5DDOQA8PEr4BcqkJTchf3WQ=",
"url": "https://development.dl2.discordapp.net/distro/app/development/linux/x64/1.0.976/discord_utils/1/full.distro",
"version": 1
},
"discord_voice": {
"hash": "sha256-7rg5Ct/BPJaOak0HOeTuYMbfdhHBvI4qML1nnYzZ+IE=",
"url": "https://development.dl2.discordapp.net/distro/app/development/linux/x64/1.0.976/discord_voice/1/full.distro",
"version": 1
},
"discord_zstd": {
"hash": "sha256-XKJNd/9l3PJX1GTHT0hvsB9pqI6oDkAvay2L3bOIk/0=",
"url": "https://development.dl2.discordapp.net/distro/app/development/linux/x64/1.0.976/discord_zstd/1/full.distro",
"version": 1
}
},
"version": "1.0.976"
},
"linux-ptb": {
"hash": "sha256-vnzEamdX8pCzFtYLoWHvxcznHD1FCcgKnwQOx8BkWu0=",
"url": "https://ptb.dl2.discordapp.net/apps/linux/0.0.183/discord-ptb-0.0.183.tar.gz",
"version": "0.0.183"
"distro": {
"hash": "sha256-dVLGOL//a1mCw0NyekrCemfu9u7pA9t1+UnQ8zmMCwI=",
"url": "https://ptb.dl2.discordapp.net/distro/app/ptb/linux/x64/1.0.188/full.distro"
},
"kind": "distro",
"modules": {
"discord_cloudsync": {
"hash": "sha256-cLqmwjFWUnRnBY3iEqpyv2mtuHI4TanGxbI1cT4V9Ic=",
"url": "https://ptb.dl2.discordapp.net/distro/app/ptb/linux/x64/1.0.188/discord_cloudsync/1/full.distro",
"version": 1
},
"discord_desktop_core": {
"hash": "sha256-UmAYyAVVwVZINIobQejXSEK01C/oQJBjxBQ1p45AMOg=",
"url": "https://ptb.dl2.discordapp.net/distro/app/ptb/linux/x64/1.0.188/discord_desktop_core/1/full.distro",
"version": 1
},
"discord_dispatch": {
"hash": "sha256-/WfqrWgqCXSUodMqlhgiHTQAiRSfg1dfA4c3CxKZuJk=",
"url": "https://ptb.dl2.discordapp.net/distro/app/ptb/linux/x64/1.0.188/discord_dispatch/1/full.distro",
"version": 1
},
"discord_erlpack": {
"hash": "sha256-MXBUoaeC1Refb80mqyZQ6vE8lRV04K5yZbhHMN5XuTY=",
"url": "https://ptb.dl2.discordapp.net/distro/app/ptb/linux/x64/1.0.188/discord_erlpack/1/full.distro",
"version": 1
},
"discord_game_utils": {
"hash": "sha256-P4s5/5aeLjzaD0HwHlpHCWaiVlsq+TFlfEqfCym4rH0=",
"url": "https://ptb.dl2.discordapp.net/distro/app/ptb/linux/x64/1.0.188/discord_game_utils/1/full.distro",
"version": 1
},
"discord_krisp": {
"hash": "sha256-hwLHumM78aJv/OSX1LAxqpUGZmiVWGg2Sc6SmOdg+Oc=",
"url": "https://ptb.dl2.discordapp.net/distro/app/ptb/linux/x64/1.0.188/discord_krisp/1/full.distro",
"version": 1
},
"discord_modules": {
"hash": "sha256-UGS9Znl5QWiQ5nG2I2GxUmQ3sRaaXcCyPPzh3AjAyQA=",
"url": "https://ptb.dl2.discordapp.net/distro/app/ptb/linux/x64/1.0.188/discord_modules/1/full.distro",
"version": 1
},
"discord_rpc": {
"hash": "sha256-izqFS9ePjv+4VTqrrnwdlNCdpQik5Y7D3pmznP74ULA=",
"url": "https://ptb.dl2.discordapp.net/distro/app/ptb/linux/x64/1.0.188/discord_rpc/1/full.distro",
"version": 1
},
"discord_spellcheck": {
"hash": "sha256-ykwur5DajvoAvNyhTb3wbtduv2NgLhrEEKKI2O/CqC8=",
"url": "https://ptb.dl2.discordapp.net/distro/app/ptb/linux/x64/1.0.188/discord_spellcheck/1/full.distro",
"version": 1
},
"discord_utils": {
"hash": "sha256-HKHbGqYK8AyJu+PnVaZIv+HJr6pc6LtnnXI29rSFPmI=",
"url": "https://ptb.dl2.discordapp.net/distro/app/ptb/linux/x64/1.0.188/discord_utils/1/full.distro",
"version": 1
},
"discord_voice": {
"hash": "sha256-HL/eZQpc0r+5N7LBFZdUVZWQT4nUtX3gBZlOPR1pLfs=",
"url": "https://ptb.dl2.discordapp.net/distro/app/ptb/linux/x64/1.0.188/discord_voice/1/full.distro",
"version": 1
},
"discord_zstd": {
"hash": "sha256-P/8cVdak4Sz7SfHzi8DJPgZi9LRu6FWWI9hjoe8LY8o=",
"url": "https://ptb.dl2.discordapp.net/distro/app/ptb/linux/x64/1.0.188/discord_zstd/1/full.distro",
"version": 1
}
},
"version": "1.0.188"
},
"linux-stable": {
"hash": "sha256-FMIM/CWPk3Kcqp8iIg+gxiCpjD2DNk7dSBqnCBvzn5w=",
"url": "https://stable.dl2.discordapp.net/apps/linux/0.0.130/discord-0.0.130.tar.gz",
"version": "0.0.130"
"hash": "sha256-N4gdcj8LYiXxvkbZhZyiWr375vaXt6JnwcoqLOKMsGg=",
"kind": "legacy",
"url": "https://stable.dl2.discordapp.net/apps/linux/0.0.134/discord-0.0.134.tar.gz",
"version": "0.0.134"
},
"osx-canary": {
"hash": "sha256-9bknJCpZv07BaF5v0g7t+YrGR3+HsYCHl4gjduQsABo=",
"url": "https://canary.dl2.discordapp.net/apps/osx/0.0.1040/DiscordCanary.dmg",
"version": "0.0.1040"
"hash": "sha256-tR52aLUTW5iLzOQnXVS6muqsu44rXplHeXNKsR3u4Sw=",
"kind": "legacy",
"url": "https://canary.dl2.discordapp.net/apps/osx/0.0.1088/DiscordCanary.dmg",
"version": "0.0.1088"
},
"osx-development": {
"hash": "sha256-dCRl0gpxFdnEa81sSymj8Te0+KmmI9uXsmCXSY1EeNw=",
"url": "https://development.dl2.discordapp.net/apps/osx/0.0.119/DiscordDevelopment.dmg",
"version": "0.0.119"
"hash": "sha256-gyZ2H9dsGYRlkPtRSE7hwRfFl6sr4pK2kB6Jq/51dhE=",
"kind": "legacy",
"url": "https://development.dl2.discordapp.net/apps/osx/1.0.982/DiscordDevelopment.dmg",
"version": "1.0.982"
},
"osx-ptb": {
"hash": "sha256-SV6ZdD0CCnNNkSWyIDqCmg9dpdQr4JLbrD3+k8943PQ=",
"url": "https://ptb.dl2.discordapp.net/apps/osx/0.0.226/DiscordPTB.dmg",
"version": "0.0.226"
"hash": "sha256-WlygpcUv+CrmQdZyZ0t3EEb58oopHetbKQbKsLUdI1w=",
"kind": "legacy",
"url": "https://ptb.dl2.discordapp.net/apps/osx/0.0.231/DiscordPTB.dmg",
"version": "0.0.231"
},
"osx-stable": {
"hash": "sha256-vBadXUHrYhvkqzkCvGnKf25A19TKcFs5D0tzC54E0Hk=",
"url": "https://stable.dl2.discordapp.net/apps/osx/0.0.382/Discord.dmg",
"version": "0.0.382"
"hash": "sha256-H+Odwt2Ry3C1+OMhVdr9PjAXOnI8vmsUkSBL8dieakc=",
"kind": "legacy",
"url": "https://stable.dl2.discordapp.net/apps/osx/0.0.387/Discord.dmg",
"version": "0.0.387"
}
}
@@ -1,8 +1,9 @@
#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p python3
from dataclasses import asdict, dataclass, field
from enum import StrEnum
from typing import List, Tuple
from typing import List
from subprocess import PIPE, Popen
import json
import urllib.request
@@ -33,18 +34,35 @@ class Branch(StrEnum):
DEVELOPMENT = "development"
Variant = Tuple[Platform, Branch]
class Kind(StrEnum):
# Single tarball or dmg fetched via discord.com/api/download redirect
LEGACY = "legacy"
# Brotli-compressed host + module distros from the distributions API
DISTRO = "distro"
@dataclass(frozen=True)
class Variant:
platform: Platform
branch: Branch
kind: Kind
# The distributions API rejects requests that don't send a Discord-Updater
# User-Agent, so we can't identify ourselves as Nixpkgs here
DISTRO_USER_AGENT = "Discord-Updater/1"
def serialize_variant(variant: Variant) -> str:
platform, branch = variant
return f"{platform}-{branch}"
return f"{variant.platform}-{variant.branch}"
def url_for_variant(variant: Variant) -> str:
platform, branch = variant
return f"https://discord.com/api/download/{variant.branch.value}?platform={variant.platform.value}&format={variant.platform.format_type()}"
return f"https://discord.com/api/download/{branch.value}?platform={platform.value}&format={platform.format_type()}"
def distro_manifest_url_for_variant(variant: Variant) -> str:
return f"https://updates.discord.com/distributions/app/manifests/latest?channel={variant.branch.value}&platform={variant.platform.value}&arch=x64"
def fetch_redirect_url(url: str) -> str:
@@ -75,31 +93,94 @@ def prefetch(url: str) -> str:
return sri_hash
@dataclass
class LegacySource:
version: str
url: str
hash: str
kind: Kind = Kind.LEGACY
@dataclass
class DistroRef:
url: str
hash: str
@dataclass
class DistroModule:
version: int
url: str
hash: str
@dataclass
class DistroSource:
version: str
distro: DistroRef
modules: dict[str, DistroModule] = field(default_factory=dict)
kind: Kind = Kind.DISTRO
def fetch_distro_manifest(variant: Variant) -> dict:
url = distro_manifest_url_for_variant(variant)
req = urllib.request.Request(url, headers={"User-Agent": DISTRO_USER_AGENT})
with urllib.request.urlopen(req) as response:
return json.loads(response.read())
def version_triple_to_str(triple: list) -> str:
return ".".join(str(x) for x in triple)
def fetch_distro_source(variant: Variant) -> DistroSource:
manifest = fetch_distro_manifest(variant)
distro_url = manifest["full"]["url"]
modules = {
name: DistroModule(
version=mod["full"]["module_version"],
url=mod["full"]["url"],
hash=prefetch(mod["full"]["url"]),
)
for name, mod in manifest["modules"].items()
}
return DistroSource(
version=version_triple_to_str(manifest["full"]["host_version"]),
distro=DistroRef(url=distro_url, hash=prefetch(distro_url)),
modules=modules,
)
def fetch_legacy_source(variant: Variant) -> LegacySource:
url = fetch_redirect_url(url_for_variant(variant))
return LegacySource(
version=version_from_url(url),
url=url,
hash=prefetch(url),
)
def main():
variants: List[Variant] = [
(Platform.LINUX, Branch.STABLE),
(Platform.LINUX, Branch.PTB),
(Platform.LINUX, Branch.CANARY),
(Platform.LINUX, Branch.DEVELOPMENT),
(Platform.MACOS, Branch.STABLE),
(Platform.MACOS, Branch.PTB),
(Platform.MACOS, Branch.CANARY),
(Platform.MACOS, Branch.DEVELOPMENT),
Variant(Platform.LINUX, Branch.STABLE, Kind.LEGACY),
Variant(Platform.LINUX, Branch.PTB, Kind.DISTRO),
Variant(Platform.LINUX, Branch.CANARY, Kind.DISTRO),
Variant(Platform.LINUX, Branch.DEVELOPMENT, Kind.DISTRO),
Variant(Platform.MACOS, Branch.STABLE, Kind.LEGACY),
Variant(Platform.MACOS, Branch.PTB, Kind.LEGACY),
Variant(Platform.MACOS, Branch.CANARY, Kind.LEGACY),
Variant(Platform.MACOS, Branch.DEVELOPMENT, Kind.LEGACY),
]
sources = {}
for v in variants:
url = url_for_variant(v)
url = fetch_redirect_url(url)
version = version_from_url(url)
sri_hash = prefetch(url)
sources[serialize_variant(v)] = {
"url": url,
"version": version,
"hash": sri_hash,
}
source = (
fetch_distro_source(v) if v.kind == Kind.DISTRO else fetch_legacy_source(v)
)
sources[serialize_variant(v)] = asdict(source)
with open(os.path.join(os.path.dirname(__file__), "sources.json"), "w") as f:
json.dump(sources, f, indent=2, sort_keys=True)