diff --git a/pkgs/applications/networking/instant-messengers/discord/darwin.nix b/pkgs/applications/networking/instant-messengers/discord/darwin.nix index 87cce491f157..aabb23a155a1 100644 --- a/pkgs/applications/networking/instant-messengers/discord/darwin.nix +++ b/pkgs/applications/networking/instant-messengers/discord/darwin.nix @@ -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 = { diff --git a/pkgs/applications/networking/instant-messengers/discord/default.nix b/pkgs/applications/networking/instant-messengers/discord/default.nix index 182394149aa8..409078b739ec 100644 --- a/pkgs/applications/networking/instant-messengers/discord/default.nix +++ b/pkgs/applications/networking/instant-messengers/discord/default.nix @@ -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; }; diff --git a/pkgs/applications/networking/instant-messengers/discord/linux.nix b/pkgs/applications/networking/instant-messengers/discord/linux.nix index f4616191e3bd..8304d23ce9ce 100644 --- a/pkgs/applications/networking/instant-messengers/discord/linux.nix +++ b/pkgs/applications/networking/instant-messengers/discord/linux.nix @@ -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// (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 = { diff --git a/pkgs/applications/networking/instant-messengers/discord/sources.json b/pkgs/applications/networking/instant-messengers/discord/sources.json index 63eca3c991a1..2ca81a052f0e 100644 --- a/pkgs/applications/networking/instant-messengers/discord/sources.json +++ b/pkgs/applications/networking/instant-messengers/discord/sources.json @@ -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" } } diff --git a/pkgs/applications/networking/instant-messengers/discord/update.py b/pkgs/applications/networking/instant-messengers/discord/update.py index d2577eab8c91..49f90ca9e1c5 100755 --- a/pkgs/applications/networking/instant-messengers/discord/update.py +++ b/pkgs/applications/networking/instant-messengers/discord/update.py @@ -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)