Unverified Commit c153f6b7 authored by FlameFlag's avatar FlameFlag
Browse files

discord: fetch distros at build time

`discord-development` linux tarball changed layout in 0.0.235,
`discord-canary` followed in 1.0.927, and `discord-ptb` followed in
1.0.188

Instead of bundling the channel binary directly, these channels now ship
only a small `updater_bootstrap` ELF that downloads the real app into
`$XDG_CONFIG_HOME/<channel>/app-<version>/` on first run

That bootstrap always hits `/manifests/latest` with no way to pin a
version, which would make the build impure and the nix version a lie

Reverse engineering the Rust ELF confirmed there is no CLI flag, env
var, or config to override this

First seen downstream in FlameFlag/nixcord workflow run 24025186671

Fetch the brotli-compressed `.distro` tars directly from the
distributions API at build time instead

`update.py` resolves the latest version via
`updates.discord.com/distributions/app/manifests/latest?channel=<ch>`
and pins the host + all module URLs with SHA256s into `sources.json`

`installPhase` detects the distro layout via `isDistro`, brotli-decodes
the host tar into `$out/opt/<binaryName>`, and extracts each module
under `$out/opt/<binaryName>/modules/<name>/`. `autoPatchelfHook`
handles the binary and native `.node` modules as usual

Discord's JS `moduleUpdater` looks for modules under
`<host_dir>/<version>/modules/<name>/`

A `stageModules` wrapper symlinks them from the nix store on first
launch and pre-writes `installed.json` so the updater treats them as
already installed, otherwise the first launch crashes with `Cannot find
module 'discord_desktop_core'`

The bundled native modules link against openssl 1.1 and libpulseaudio,
added both to `buildInputs` gated on `isDistro` so the legacy stable
package is unaffected
parent 73d9eac7
Loading
Loading
Loading
Loading
+6 −2
Original line number Diff line number Diff line
{
  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 −8
Original line number Diff line number Diff line
{
  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;
      };
+143 −53
Original line number Diff line number Diff line
{
  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,56 +88,31 @@ let
  ];
  enabledDiscordModsCount = builtins.length (lib.filter (x: x) discordMods);

  disableBreakingUpdates =
    runCommand "disable-breaking-updates.py"
      {
        pythonInterpreter = "${python3.interpreter}";
        configDirName = lib.toLower binaryName;
        meta.mainProgram = "disable-breaking-updates.py";
      }
      ''
        mkdir -p $out/bin
        cp ${./disable-breaking-updates.py} $out/bin/disable-breaking-updates.py
        substituteAllInPlace $out/bin/disable-breaking-updates.py
        chmod +x $out/bin/disable-breaking-updates.py
      '';
in
assert lib.assertMsg (
  enabledDiscordModsCount <= 1
) "discord: Only one of Vencord, Equicord or Moonlight can be enabled at the same time";
stdenv.mkDerivation (finalAttrs: {
  inherit
    pname
    version
    src
    meta
    ;
  # 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";

  nativeBuildInputs = [
    autoPatchelfHook
    cups
    libdrm
    libuuid
    libxdamage
    libx11
    libxscrnsaver
    libxtst
    libxcb
    libxshmfence
    wrapGAppsHook3
    makeShellWrapper
  ];
  inherit (source) version;

  dontWrapGApps = true;
  src =
    if isDistro then
      fetchurl { inherit (source.distro) url hash; }
    else
      fetchurl { inherit (source) url hash; };

  buildInputs = [
    alsa-lib
    libgbm
    nspr
    nss
  ];
  moduleSrcs = lib.optionalAttrs isDistro (
    lib.mapAttrs (_: mod: fetchurl { inherit (mod) url hash; }) source.modules
  );

  strictDeps = true;
  moduleVersions = lib.optionalAttrs isDistro (lib.mapAttrs (_: mod: mod.version) source.modules);

  libPath = lib.makeLibraryPath (
    [
@@ -173,7 +151,9 @@ stdenv.mkDerivation (finalAttrs: {
      libxrender
      libxtst
      nspr
      nss
      libxcb
      libxkbcommon
      pango
      pipewire
      libxscrnsaver
@@ -184,15 +164,122 @@ stdenv.mkDerivation (finalAttrs: {
    ++ 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"
      {
        pythonInterpreter = "${python3.interpreter}";
        configDirName = lib.toLower binaryName;
        meta.mainProgram = "disable-breaking-updates.py";
      }
      ''
        mkdir -p $out/bin
        cp ${./disable-breaking-updates.py} $out/bin/disable-breaking-updates.py
        substituteAllInPlace $out/bin/disable-breaking-updates.py
        chmod +x $out/bin/disable-breaking-updates.py
      '';
in
assert lib.assertMsg (
  enabledDiscordModsCount <= 1
) "discord: Only one of Vencord, Equicord or Moonlight can be enabled at the same time";
stdenv.mkDerivation (finalAttrs: {
  inherit
    pname
    version
    src
    meta
    ;

  nativeBuildInputs = [
    autoPatchelfHook
    cups
    libdrm
    libuuid
    libxdamage
    libx11
    libxscrnsaver
    libxtst
    libxcb
    libxshmfence
    wrapGAppsHook3
    makeShellWrapper
  ]
  ++ lib.optionals isDistro [ brotli ];

  dontWrapGApps = true;

  buildInputs = [
    alsa-lib
    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;

  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)

        brotli -d < $src | tar xf - --strip-components=1 -C $out/opt/${binaryName}
        chmod +x $out/opt/${binaryName}/${binaryName}
    patchelf --set-interpreter ${stdenv.cc.bintools.dynamicLinker} \
        $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 = {
+224 −24
Original line number Diff line number Diff line
{
  "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"
  }
}
+105 −24

File changed.

Preview size limit exceeded, changes collapsed.