diff options
Diffstat (limited to 'pkgs/sources/comments')
-rw-r--r-- | pkgs/sources/comments/.envrc | 4 | ||||
-rw-r--r-- | pkgs/sources/comments/.gitignore | 3 | ||||
-rw-r--r-- | pkgs/sources/comments/Cargo.lock | 446 | ||||
-rw-r--r-- | pkgs/sources/comments/Cargo.toml | 16 | ||||
-rw-r--r-- | pkgs/sources/comments/comments.nix | 25 | ||||
-rw-r--r-- | pkgs/sources/comments/default.nix | 18 | ||||
-rw-r--r-- | pkgs/sources/comments/flake.lock | 61 | ||||
-rw-r--r-- | pkgs/sources/comments/flake.nix | 31 | ||||
-rw-r--r-- | pkgs/sources/comments/src/info_json.rs | 223 | ||||
-rw-r--r-- | pkgs/sources/comments/src/main.rs | 322 | ||||
-rwxr-xr-x | pkgs/sources/comments/update.sh | 8 |
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 |