about summary refs log tree commit diff stats
path: root/pkgs/sources/comments
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/sources/comments')
-rw-r--r--pkgs/sources/comments/.envrc4
-rw-r--r--pkgs/sources/comments/.gitignore3
-rw-r--r--pkgs/sources/comments/Cargo.lock446
-rw-r--r--pkgs/sources/comments/Cargo.toml16
-rw-r--r--pkgs/sources/comments/comments.nix25
-rw-r--r--pkgs/sources/comments/default.nix18
-rw-r--r--pkgs/sources/comments/flake.lock61
-rw-r--r--pkgs/sources/comments/flake.nix31
-rw-r--r--pkgs/sources/comments/src/info_json.rs223
-rw-r--r--pkgs/sources/comments/src/main.rs322
-rwxr-xr-xpkgs/sources/comments/update.sh8
11 files changed, 1157 insertions, 0 deletions
diff --git a/pkgs/sources/comments/.envrc b/pkgs/sources/comments/.envrc
new file mode 100644
index 00000000..2b5fbb29
--- /dev/null
+++ b/pkgs/sources/comments/.envrc
@@ -0,0 +1,4 @@
+use flake
+
+PATH_add ./target/debug
+PATH_add ./target/release
diff --git a/pkgs/sources/comments/.gitignore b/pkgs/sources/comments/.gitignore
new file mode 100644
index 00000000..c84fa049
--- /dev/null
+++ b/pkgs/sources/comments/.gitignore
@@ -0,0 +1,3 @@
+# build dirs
+/target
+/result
diff --git a/pkgs/sources/comments/Cargo.lock b/pkgs/sources/comments/Cargo.lock
new file mode 100644
index 00000000..54f19c46
--- /dev/null
+++ b/pkgs/sources/comments/Cargo.lock
@@ -0,0 +1,446 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "cc"
+version = "1.0.97"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-targets",
+]
+
+[[package]]
+name = "chrono-humanize"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799627e6b4d27827a814e837b9d8a504832086081806d45b1afa34dc982b023b"
+dependencies = [
+ "chrono",
+]
+
+[[package]]
+name = "cli-log"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d2ab00dc4c82ec28af25ac085aecc11ffeabf353755715a3113a7aa044ca5cc"
+dependencies = [
+ "chrono",
+ "file-size",
+ "log",
+ "proc-status",
+]
+
+[[package]]
+name = "comments"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "chrono",
+ "chrono-humanize",
+ "cli-log",
+ "log",
+ "regex",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "file-size"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9544f10105d33957765016b8a9baea7e689bf1f0f2f32c2fa2f568770c38d2b3"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.154"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
+
+[[package]]
+name = "log"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+
+[[package]]
+name = "memchr"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "proc-status"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0e0c0ac915e7b76b47850ba4ffc377abde6c6ff9eeace61d0a89623db449712"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[package]]
+name = "serde"
+version = "1.0.201"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.201"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
diff --git a/pkgs/sources/comments/Cargo.toml b/pkgs/sources/comments/Cargo.toml
new file mode 100644
index 00000000..3ae3aa4c
--- /dev/null
+++ b/pkgs/sources/comments/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "comments"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.82"
+chrono = "0.4.38"
+chrono-humanize = "0.2.3"
+cli-log = "2.0.0"
+log = "0.4.21"
+regex = "1.10.4"
+serde = { version = "1.0.200", features = ["derive"] }
+serde_json = "1.0.116"
diff --git a/pkgs/sources/comments/comments.nix b/pkgs/sources/comments/comments.nix
new file mode 100644
index 00000000..e8a33bff
--- /dev/null
+++ b/pkgs/sources/comments/comments.nix
@@ -0,0 +1,25 @@
+{
+  lib,
+  rustPlatform,
+  makeWrapper,
+  less,
+  coreutils,
+}:
+rustPlatform.buildRustPackage {
+  pname = "comments";
+  version = "0.1.0";
+
+  src = ./.;
+  cargoLock = {
+    lockFile = ./Cargo.lock;
+  };
+
+  nativeBuildInputs = [
+    makeWrapper
+  ];
+
+  postInstall = ''
+    wrapProgram $out/bin/comments \
+      --set PATH ${lib.makeBinPath [less coreutils]}
+  '';
+}
diff --git a/pkgs/sources/comments/default.nix b/pkgs/sources/comments/default.nix
new file mode 100644
index 00000000..6205dcbe
--- /dev/null
+++ b/pkgs/sources/comments/default.nix
@@ -0,0 +1,18 @@
+[
+  (
+    final: prev: {
+      comments = import ./comments.nix {
+        inherit
+          (prev)
+          lib
+          makeWrapper
+          rustPlatform
+          # dependencies
+          
+          less
+          coreutils
+          ;
+      };
+    }
+  )
+]
diff --git a/pkgs/sources/comments/flake.lock b/pkgs/sources/comments/flake.lock
new file mode 100644
index 00000000..50494465
--- /dev/null
+++ b/pkgs/sources/comments/flake.lock
@@ -0,0 +1,61 @@
+{
+  "nodes": {
+    "flake-utils": {
+      "inputs": {
+        "systems": "systems"
+      },
+      "locked": {
+        "lastModified": 1710146030,
+        "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1715087517,
+        "narHash": "sha256-CLU5Tsg24Ke4+7sH8azHWXKd0CFd4mhLWfhYgUiDBpQ=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "b211b392b8486ee79df6cdfb1157ad2133427a29",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixos-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "flake-utils": "flake-utils",
+        "nixpkgs": "nixpkgs"
+      }
+    },
+    "systems": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/pkgs/sources/comments/flake.nix b/pkgs/sources/comments/flake.nix
new file mode 100644
index 00000000..f5e44a65
--- /dev/null
+++ b/pkgs/sources/comments/flake.nix
@@ -0,0 +1,31 @@
+{
+  description = "comments";
+
+  inputs = {
+    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+
+    flake-utils.url = "github:numtide/flake-utils";
+  };
+
+  outputs = {
+    self,
+    nixpkgs,
+    flake-utils,
+  }: (flake-utils.lib.eachDefaultSystem (system: let
+    pkgs = nixpkgs.legacyPackages."${system}";
+  in {
+    devShells.default = pkgs.mkShell {
+      packages = with pkgs; [
+        # rust stuff
+        cargo
+        clippy
+        rustc
+        rustfmt
+
+        cargo-edit
+        cargo-expand
+        cargo-audit
+      ];
+    };
+  }));
+}
diff --git a/pkgs/sources/comments/src/info_json.rs b/pkgs/sources/comments/src/info_json.rs
new file mode 100644
index 00000000..eca4fae3
--- /dev/null
+++ b/pkgs/sources/comments/src/info_json.rs
@@ -0,0 +1,223 @@
+use std::collections::HashMap;
+
+use serde::{Deserialize, Deserializer};
+
+#[derive(Debug, Deserialize)]
+pub struct InfoJson {
+    pub id: String,
+    pub title: String,
+    pub formats: Vec<Format>,
+    pub thumbnails: Vec<ThumbNail>,
+    pub thumbnail: String,
+    pub description: String,
+    pub channel_id: String,
+    pub channel_url: String,
+    pub duration: u32,
+    pub view_count: u32,
+    pub age_limit: u32,
+    pub webpage_url: String,
+    pub categories: Vec<String>,
+    pub tags: Vec<String>,
+    pub playable_in_embed: bool,
+    pub live_status: String,
+    _format_sort_fields: Vec<String>,
+    pub automatic_captions: HashMap<String, Vec<Caption>>,
+    pub subtitles: Subtitles,
+    pub comment_count: u32,
+    pub like_count: u32,
+    pub channel: String,
+    pub channel_follower_count: u32,
+    pub channel_is_verified: Option<bool>,
+    pub uploader: String,
+    pub uploader_id: String,
+    pub uploader_url: String,
+    pub upload_date: String,
+    pub availability: String,
+    pub webpage_url_basename: String,
+    pub webpage_url_domain: String,
+    pub extractor: String,
+    pub extractor_key: String,
+    pub display_id: String,
+    pub fulltitle: String,
+    pub duration_string: String,
+    pub is_live: bool,
+    pub was_live: bool,
+    pub epoch: u32,
+    pub comments: Vec<Comment>,
+    pub sponsorblock_chapters: Option<Vec<SponsorblockChapter>>,
+    pub format: String,
+    pub format_id: String,
+    pub ext: String,
+    pub protocol: String,
+    pub language: Option<String>,
+    pub format_note: String,
+    pub filesize_approx: u64,
+    pub tbr: f64,
+    pub width: u32,
+    pub height: u32,
+    pub resolution: String,
+    pub fps: f64,
+    pub dynamic_range: String,
+    pub vcodec: String,
+    pub vbr: f64,
+    pub aspect_ratio: f64,
+    pub acodec: String,
+    pub abr: f64,
+    pub asr: u32,
+    pub audio_channels: u32,
+    _type: String,
+    _version: Version,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct Subtitles {}
+
+#[derive(Debug, Deserialize)]
+pub struct Version {
+    pub version: String,
+    pub release_git_head: String,
+    pub repository: String,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct SponsorblockChapter {}
+
+#[derive(Debug, Deserialize, Clone)]
+#[serde(from = "String")]
+pub enum Parent {
+    Root,
+    Id(String),
+}
+
+impl Parent {
+    pub fn id(&self) -> Option<&str> {
+        if let Self::Id(id) = self {
+            Some(id)
+        } else {
+            None
+        }
+    }
+}
+
+impl From<String> for Parent {
+    fn from(value: String) -> Self {
+        if value == "root" {
+            Self::Root
+        } else {
+            Self::Id(value)
+        }
+    }
+}
+
+#[derive(Debug, Deserialize, Clone)]
+#[serde(from = "String")]
+pub struct Id {
+    pub id: String,
+}
+impl From<String> for Id {
+    fn from(value: String) -> Self {
+        Self {
+            // Take the last element if the string is split with dots, otherwise take the full id
+            id: value.split('.').last().unwrap_or(&value).to_owned(),
+        }
+    }
+}
+
+#[derive(Debug, Deserialize, Clone)]
+pub struct Comment {
+    pub id: Id,
+    pub text: String,
+    #[serde(default = "zero")]
+    pub like_count: u32,
+    pub author_id: String,
+    #[serde(default = "unknown")]
+    pub author: String,
+    pub author_thumbnail: String,
+    pub parent: Parent,
+    #[serde(deserialize_with = "edited_from_time_text", alias = "_time_text")]
+    pub edited: bool,
+    // Can't also be deserialized, as it's already used in 'edited'
+    // _time_text: String,
+    pub timestamp: i64,
+    pub author_url: String,
+    pub author_is_uploader: bool,
+    pub is_favorited: bool,
+}
+fn unknown() -> String {
+    "<Unknown>".to_string()
+}
+fn zero() -> u32 {
+    0
+}
+fn edited_from_time_text<'de, D>(d: D) -> Result<bool, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    let s = String::deserialize(d)?;
+    if s.contains(" (edited)") {
+        Ok(true)
+    } else {
+        Ok(false)
+    }
+}
+
+#[derive(Debug, Deserialize)]
+pub struct Caption {
+    pub ext: String,
+    pub url: String,
+    pub name: Option<String>,
+    pub protocol: Option<String>,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct ThumbNail {
+    pub url: String,
+    pub preference: i32,
+    pub id: String,
+    pub height: Option<u32>,
+    pub width: Option<u32>,
+    pub resolution: Option<String>,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct Format {
+    pub format_id: String,
+    pub format_note: Option<String>,
+    pub ext: String,
+    pub protocol: String,
+    pub acodec: Option<String>,
+    pub vcodec: String,
+    pub url: String,
+    pub width: Option<u32>,
+    pub height: Option<u32>,
+    pub fps: Option<f64>,
+    pub rows: Option<u32>,
+    pub columns: Option<u32>,
+    pub fragments: Option<Vec<Fragment>>,
+    pub resolution: String,
+    pub aspect_ratio: Option<f64>,
+    pub http_headers: HttpHeader,
+    pub audio_ext: String,
+    pub video_ext: String,
+    pub vbr: Option<f64>,
+    pub abr: Option<f64>,
+    pub format: String,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct HttpHeader {
+    #[serde(alias = "User-Agent")]
+    pub user_agent: String,
+    #[serde(alias = "Accept")]
+    pub accept: String,
+    #[serde(alias = "Accept-Language")]
+    pub accept_language: String,
+    #[serde(alias = "Sec-Fetch-Mode")]
+    pub sec_fetch_mode: String,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct Fragment {
+    pub url: String,
+    pub duration: f64,
+}
diff --git a/pkgs/sources/comments/src/main.rs b/pkgs/sources/comments/src/main.rs
new file mode 100644
index 00000000..6e4f72e9
--- /dev/null
+++ b/pkgs/sources/comments/src/main.rs
@@ -0,0 +1,322 @@
+use std::{
+    env,
+    fmt::Display,
+    fs::{self, File},
+    io::{BufReader, Write},
+    mem,
+    path::PathBuf,
+    process::{Command, Stdio},
+};
+
+use anyhow::Context;
+use chrono::{Local, TimeZone};
+use chrono_humanize::{Accuracy, HumanTime, Tense};
+use info_json::{Comment, InfoJson, Parent};
+use regex::Regex;
+
+mod info_json;
+
+fn get_runtime_path(component: &'static str) -> anyhow::Result<PathBuf> {
+    let out: PathBuf = format!(
+        "{}/{}",
+        env::var("XDG_RUNTIME_DIR").expect("This should always exist"),
+        component
+    )
+    .into();
+    fs::create_dir_all(out.parent().expect("Parent should exist"))?;
+    Ok(out)
+}
+
+const STATUS_PATH: &str = "ytcc/running";
+pub fn status_path() -> anyhow::Result<PathBuf> {
+    get_runtime_path(STATUS_PATH)
+}
+
+#[derive(Debug, Clone)]
+pub struct CommentExt {
+    pub value: Comment,
+    pub replies: Vec<CommentExt>,
+}
+
+#[derive(Debug, Default)]
+pub struct Comments {
+    vec: Vec<CommentExt>,
+}
+
+impl Comments {
+    pub fn new() -> Self {
+        Self::default()
+    }
+    pub fn push(&mut self, value: CommentExt) {
+        self.vec.push(value);
+    }
+    pub fn get_mut(&mut self, key: &str) -> Option<&mut CommentExt> {
+        self.vec.iter_mut().filter(|c| c.value.id.id == key).last()
+    }
+    pub fn insert(&mut self, key: &str, value: CommentExt) {
+        let parent = self
+            .vec
+            .iter_mut()
+            .filter(|c| c.value.id.id == key)
+            .last()
+            .expect("One of these should exist");
+        parent.push_reply(value);
+    }
+}
+impl CommentExt {
+    pub fn push_reply(&mut self, value: CommentExt) {
+        self.replies.push(value)
+    }
+    pub fn get_mut_reply(&mut self, key: &str) -> Option<&mut CommentExt> {
+        self.replies
+            .iter_mut()
+            .filter(|c| c.value.id.id == key)
+            .last()
+    }
+}
+
+impl From<Comment> for CommentExt {
+    fn from(value: Comment) -> Self {
+        Self {
+            replies: vec![],
+            value,
+        }
+    }
+}
+
+impl Display for Comments {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        macro_rules! c {
+            ($color:expr, $write:ident) => {
+                $write.write_str(concat!("\x1b[", $color, "m"))?
+            };
+        }
+
+        fn format(
+            comment: &CommentExt,
+            f: &mut std::fmt::Formatter<'_>,
+            ident_count: u32,
+        ) -> std::fmt::Result {
+            let ident = &(0..ident_count).map(|_| " ").collect::<String>();
+            let value = &comment.value;
+
+            f.write_str(ident)?;
+
+            if value.author_is_uploader {
+                c!("91;1", f);
+            } else {
+                c!("35", f);
+            }
+
+            f.write_str(&value.author)?;
+            c!("0", f);
+            if value.edited || value.is_favorited {
+                f.write_str("[")?;
+                if value.edited {
+                    f.write_str("")?;
+                }
+                if value.edited && value.is_favorited {
+                    f.write_str(" ")?;
+                }
+                if value.is_favorited {
+                    f.write_str("")?;
+                }
+                f.write_str("]")?;
+            }
+
+            c!("36;1", f);
+            write!(
+                f,
+                " {}",
+                HumanTime::from(
+                    Local
+                        .timestamp_opt(value.timestamp, 0)
+                        .single()
+                        .expect("This should be valid")
+                )
+                .to_text_en(Accuracy::Rough, Tense::Past)
+            )?;
+            c!("0", f);
+
+            // c!("31;1", f);
+            // f.write_fmt(format_args!(" [{}]", comment.value.like_count))?;
+            // c!("0", f);
+
+            f.write_str(":\n")?;
+            f.write_str(ident)?;
+
+            f.write_str(&value.text.replace('\n', &format!("\n{}", ident)))?;
+            f.write_str("\n")?;
+
+            if !comment.replies.is_empty() {
+                let mut children = comment.replies.clone();
+                children.sort_by(|a, b| a.value.timestamp.cmp(&b.value.timestamp));
+
+                for child in children {
+                    format(&child, f, ident_count + 4)?;
+                }
+            } else {
+                f.write_str("\n")?;
+            }
+
+            Ok(())
+        }
+
+        if !&self.vec.is_empty() {
+            let mut children = self.vec.clone();
+            children.sort_by(|a, b| b.value.like_count.cmp(&a.value.like_count));
+
+            for child in children {
+                format(&child, f, 0)?
+            }
+        }
+        Ok(())
+    }
+}
+
+fn main() -> anyhow::Result<()> {
+    cli_log::init_cli_log!();
+    let args: Option<String> = env::args().skip(1).last();
+    let mut info_json: InfoJson = {
+        let status_path = if let Some(arg) = args {
+            PathBuf::from(arg)
+        } else {
+            status_path().context("Failed to get status path")?
+        };
+
+        let reader =
+            BufReader::new(File::open(&status_path).with_context(|| {
+                format!("Failed to open status file at {}", status_path.display())
+            })?);
+
+        serde_json::from_reader(reader)?
+    };
+
+    let base_comments = mem::take(&mut info_json.comments);
+    drop(info_json);
+
+    let mut comments = Comments::new();
+    base_comments.into_iter().for_each(|c| {
+        if let Parent::Id(id) = &c.parent {
+            comments.insert(&(id.clone()), CommentExt::from(c));
+        } else {
+            comments.push(CommentExt::from(c));
+        }
+    });
+
+    comments.vec.iter_mut().for_each(|comment| {
+        let replies = mem::take(&mut comment.replies);
+        let mut output_replies: Vec<CommentExt>  = vec![];
+
+        let re = Regex::new(r"\u{200b}?(@[^\t\s]+)\u{200b}?").unwrap();
+        for reply in replies {
+            if let Some(replyee_match) =  re.captures(&reply.value.text){
+                let full_match = replyee_match.get(0).expect("This always exists");
+                let text = reply.
+                    value.
+                    text[0..full_match.start()]
+                    .to_owned()
+                    +
+                    &reply
+                    .value
+                    .text[full_match.end()..];
+                let text: &str = text.trim().trim_matches('\u{200b}');
+
+                let replyee = replyee_match.get(1).expect("This should exist").as_str();
+
+
+                if let Some(parent) = output_replies
+                    .iter_mut()
+                    // .rev()
+                    .flat_map(|com| &mut com.replies)
+                    .flat_map(|com| &mut com.replies)
+                    .flat_map(|com| &mut com.replies)
+                    .filter(|com| com.value.author == replyee)
+                    .last()
+                {
+                    parent.replies.push(CommentExt::from(Comment {
+                        text: text.to_owned(),
+                        ..reply.value
+                    }))
+                } else if let Some(parent) = output_replies
+                    .iter_mut()
+                    // .rev()
+                    .flat_map(|com| &mut com.replies)
+                    .flat_map(|com| &mut com.replies)
+                    .filter(|com| com.value.author == replyee)
+                    .last()
+                {
+                    parent.replies.push(CommentExt::from(Comment {
+                        text: text.to_owned(),
+                        ..reply.value
+                    }))
+                } else if let Some(parent) = output_replies
+                    .iter_mut()
+                    // .rev()
+                    .flat_map(|com| &mut com.replies)
+                    .filter(|com| com.value.author == replyee)
+                    .last()
+                {
+                    parent.replies.push(CommentExt::from(Comment {
+                        text: text.to_owned(),
+                        ..reply.value
+                    }))
+                } else if let Some(parent) = output_replies.iter_mut()
+                    // .rev()
+                    .filter(|com| com.value.author == replyee)
+                    .last()
+                {
+                    parent.replies.push(CommentExt::from(Comment {
+                        text: text.to_owned(),
+                        ..reply.value
+                    }))
+                } else {
+                    eprintln!(
+                    "Failed to find a parent for ('{}') both directly and via replies! The reply text was:\n'{}'\n",
+                    replyee,
+                    reply.value.text
+                );
+                    output_replies.push(reply);
+                }
+            } else {
+                output_replies.push(reply);
+            }
+        }
+        comment.replies = output_replies;
+    });
+
+    let mut less = Command::new("less")
+        .args(["--raw-control-chars"])
+        .stdin(Stdio::piped())
+        .stderr(Stdio::inherit())
+        .spawn()
+        .context("Failed to run less")?;
+
+    let mut child = Command::new("fmt")
+        .args(["--uniform-spacing", "--split-only", "--width=90"])
+        .stdin(Stdio::piped())
+        .stderr(Stdio::inherit())
+        .stdout(less.stdin.take().expect("Should be open"))
+        .spawn()
+        .context("Failed to run fmt")?;
+
+    let mut stdin = child.stdin.take().context("Failed to open stdin")?;
+    std::thread::spawn(move || {
+        stdin
+            .write_all(comments.to_string().as_bytes())
+            .expect("Should be able to write to stdin of fmt");
+    });
+
+    let _ = less.wait().context("Failed to await less")?;
+
+    Ok(())
+}
+
+#[cfg(test)]
+mod test {
+    #[test]
+    fn test_string_replacement() {
+        let s = "A \n\nB\n\nC".to_owned();
+        assert_eq!("A \n  \n  B\n  \n  C", s.replace('\n', "\n  "))
+    }
+}
diff --git a/pkgs/sources/comments/update.sh b/pkgs/sources/comments/update.sh
new file mode 100755
index 00000000..e500bb23
--- /dev/null
+++ b/pkgs/sources/comments/update.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env sh
+
+nix flake update
+
+[ "$1" = "upgrade" ] && cargo upgrade
+cargo update
+
+# vim: ft=sh