We are rewriting large chunks of the codebase, to bring about a modern and stable NewPipe! You can download nightly builds here.
-
Please work on the refactor branch if you want to contribute new features. The current codebase is in maintenance mode and will only receive bugfixes.
+# Straw
-
-
NewPipe
-
A libre lightweight streaming front-end for Android.
+A Sulkta fork of [NewPipe](https://github.com/TeamNewPipe/NewPipe). Android YouTube
+client, Compose UI, Media3 player, with [SponsorBlock](https://sponsor.ajay.app/)
+and [Return YouTube Dislike](https://returnyoutubedislike.com/) baked in.
-
+The extractor is `strawcore`, a Rust port of NewPipeExtractor exposed to Kotlin
+via UniFFI. No InnerTube/JS deobf code path lives on the JVM anymore.
-
-
+Add the repo in your F-Droid client of choice, then install Straw.
-*Read this document in other languages: [Deutsch](doc/README.de.md), [English](README.md), [Español](doc/README.es.md), [Français](doc/README.fr.md), [हिन्दी](doc/README.hi.md), [Italiano](doc/README.it.md), [한국어](doc/README.ko.md), [Português Brasil](doc/README.pt_BR.md), [Polski](doc/README.pl.md), [ਪੰਜਾਬੀ ](doc/README.pa.md), [日本語](doc/README.ja.md), [Română](doc/README.ro.md), [Soomaali](doc/README.so.md), [Türkçe](doc/README.tr.md), [正體中文](doc/README.zh_TW.md), [অসমীয়া](doc/README.asm.md), [Српски](doc/README.sr.md), [العربية](README.ar.md)*
+The app also self-updates from the same repo when an APK lands there with a
+higher `versionCode`.
-> [!warning]
-> THIS APP IS IN BETA, SO YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE IN OUR GITHUB REPOSITORY BY FILLING OUT THE ISSUE TEMPLATE.
->
-> PUTTING NEWPIPE, OR ANY FORK OF IT, INTO THE GOOGLE PLAY STORE VIOLATES THEIR TERMS AND CONDITIONS.
+## What's in
-## Screenshots
+- Search, video detail, channel pages, playlists
+- Inline player + fullscreen + minibar + background audio + PiP
+- Media3 ExoPlayer (DASH / HLS / progressive / merged DASH chunks)
+- SponsorBlock auto-skip (categories user-toggleable)
+- Return YouTube Dislike on video detail
+- RSS-based subscription feed (fast — ~1s for 50 subs)
+- Hide-shorts / hide-paid / hide-age-restricted feed filters
+- Resume positions + watch history + search history
+- Local playlists, downloads (video + audio)
+- NewPipe-format settings import (subs + playlists + history)
-[](fastlane/metadata/android/en-US/images/phoneScreenshots/00.png)
-[](fastlane/metadata/android/en-US/images/phoneScreenshots/01.png)
-[](fastlane/metadata/android/en-US/images/phoneScreenshots/02.png)
-[](fastlane/metadata/android/en-US/images/phoneScreenshots/03.png)
-[](fastlane/metadata/android/en-US/images/phoneScreenshots/04.png)
-[](fastlane/metadata/android/en-US/images/phoneScreenshots/05.png)
-[](fastlane/metadata/android/en-US/images/phoneScreenshots/06.png)
-[](fastlane/metadata/android/en-US/images/phoneScreenshots/07.png)
-[](fastlane/metadata/android/en-US/images/phoneScreenshots/08.png)
-
-[](fastlane/metadata/android/en-US/images/tenInchScreenshots/09.png)
-[](fastlane/metadata/android/en-US/images/tenInchScreenshots/10.png)
+## What's out (on purpose)
-### Supported Services
+- Trending / algorithmic feeds. Subscriptions only.
+- iOS / desktop targets. Android-only for now.
+- Google Play Services anything.
-NewPipe currently supports these services:
+## Layout
-
-* YouTube ([website](https://www.youtube.com/)) and YouTube Music ([website](https://music.youtube.com/)) ([wiki](https://en.wikipedia.org/wiki/YouTube))
-* PeerTube ([website](https://joinpeertube.org/)) and all its instances (open the website to know what that means!) ([wiki](https://en.wikipedia.org/wiki/PeerTube))
-* Bandcamp ([website](https://bandcamp.com/)) ([wiki](https://en.wikipedia.org/wiki/Bandcamp))
-* SoundCloud ([website](https://soundcloud.com/)) ([wiki](https://en.wikipedia.org/wiki/SoundCloud))
-* media.ccc.de ([website](https://media.ccc.de/)) ([wiki](https://en.wikipedia.org/wiki/Chaos_Computer_Club))
-
-As you can see, NewPipe supports multiple video and audio services. Though it started off with YouTube, other people have added more services over the years, making NewPipe more and more versatile!
-
-Partially due to circumstance, and partially due to its popularity, YouTube is the best supported out of these services. If you use or are familiar with any of these other services, please help us improve support for them! We're looking for maintainers for SoundCloud and PeerTube.
-
-If you intend to add a new service, please get in touch with us first! Our [docs](https://teamnewpipe.github.io/documentation/) provide more information on how a new service can be added to the app and to the [NewPipe Extractor](https://github.com/TeamNewPipe/NewPipeExtractor).
-
-## Description
-
-NewPipe works by fetching the required data from the official API (e.g. PeerTube) of the service you're using. If the official API is restricted (e.g. YouTube) for our purposes, or is proprietary, the app parses the website or uses an internal API instead. This means that you don't need an account on any service to use NewPipe.
-
-Also, since they are free and open source software, neither the app nor the Extractor use any proprietary libraries or frameworks, such as Google Play Services. This means you can use NewPipe on devices or custom ROMs that do not have Google apps installed.
-
-### Features
-
-* Watch videos at resolutions up to 4K
-* Listen to audio in the background, only loading the audio stream to save data
-* Popup mode (floating player, aka Picture-in-Picture)
-* Watch live streams
-* Show/hide subtitles/closed captions
-* Search videos and audios (on YouTube, you can specify the content language as well)
-* Enqueue videos (and optionally save them as local playlists)
-* Show/hide general information about videos (such as description and tags)
-* Show/hide next/related videos
-* Show/hide comments
-* Search videos, audios, channels, playlists and albums
-* Browse videos and audios within a channel
-* Subscribe to channels (yes, without logging into any account!)
-* Get notifications about new videos from channels you're subscribed to
-* Create and edit channel groups (for easier browsing and management)
-* Browse video feeds generated from your channel groups
-* View and search your watch history
-* Search and watch playlists (these are remote playlists, which means they're fetched from the service you're browsing)
-* Create and edit local playlists (these are created and saved within the app, and have nothing to do with any service)
-* Download videos/audios/subtitles (closed captions)
-* Open in Kodi
-* Watch/Block age-restricted material
-
-
-
-
-## Installation and updates
-You can install NewPipe using one of the following methods:
- 1. Add our custom repo to F-Droid and install it from there. The instructions are here: https://newpipe.net/FAQ/tutorials/install-add-fdroid-repo/
- 2. Download the APK from [GitHub Releases](https://github.com/TeamNewPipe/NewPipe/releases), [compare the signing key](#apk-info) and install it.
- 3. Update via F-Droid. This is the slowest method of getting updates, as F-Droid must recognize changes, build the APK itself, sign it, and then push the update to users.
- 4. Build a debug APK yourself. This is the fastest way to get new features on your device, but is much more complicated, so we recommend using one of the other methods.
- 5. If you're interested in a specific feature or bugfix provided in a Pull Request in this repo, you can also download its APK from within the PR. Read the PR description for instructions. The great thing about PR-specific APKs is that they're installed side-by-side the official app, so you don't have to worry about losing your data or messing anything up.
-
-We recommend method 1 for most users. APKs installed using method 1 or 2 are compatible with each other (meaning that if you installed NewPipe using either method 1 or 2, you can also update NewPipe using the other), but not with those installed using method 3. This is due to the same signing key (ours) being used for 1 and 2, but a different signing key (F-Droid's) being used for 3. Building a debug APK using method 4 excludes a key entirely. Signing keys help ensure that a user isn't tricked into installing a malicious update to an app. When using method 5, each APK is signed with a different random key supplied by GitHub Actions, so you cannot even update it. You will have to backup and restore the app data each time you wish to use a new APK.
-
-In the meanwhile, if you want to switch sources for some reason (e.g. NewPipe's core functionality breaks and F-Droid doesn't have the latest update yet), we recommend following this procedure:
-1. Back up your data via Settings > Backup and Restore > Export Database so you keep your history, subscriptions, and playlists
-2. Uninstall NewPipe
-3. Download the APK from the new source and install it
-4. Import the data from step 1 via Settings > Backup and Restore > Import Database
-
-> [!Note]
-> When you're importing a database into the official app, always make sure that it is the one you exported _from_ the official app. If you import a database exported from an APK other than the official app, it may break things. Such an action is unsupported, and you should only do so when you're absolutely certain you know what you're doing.
-
-### APK Info
-
-This is the SHA fingerprint of NewPipe's signing key to verify downloaded APKs which are signed by us. The fingerprint is also available on [NewPipe's website](https://newpipe.net#download). This is relevant for method 2.
```
-CB:84:06:9B:D6:81:16:BA:FA:E5:EE:4E:E5:B0:8A:56:7A:A6:D8:98:40:4E:7C:B1:2F:9E:75:6D:F5:CF:5C:AB
+strawApp/ Sulkta-authored app — Compose UI, Media3 wiring, SB + RYD clients
+rust/ strawcore — UniFFI wrapper around the Rust extractor
+shared/ KMP scaffold inherited from upstream NewPipe (unused for now)
+app/ Upstream NewPipe :app module — kept for reference
```
-## Contribution
-Whether you have ideas, translations, design changes, code cleaning, or even major code changes, help is always welcome. The app gets better and better with each contribution, no matter how big or small! If you'd like to get involved, check our [contribution notes](.github/CONTRIBUTING.md).
+## Build
-
-
-
+```
+./gradlew :strawApp:assembleDebug
+```
-## Donate
-If you like NewPipe, you're welcome to send a donation. We prefer Liberapay, as it is both open-source and non-profit. For further info on donating to NewPipe, please visit our [website](https://newpipe.net/donate).
+Requires the Rust toolchain plus the four Android targets:
-
-
-
-
-
-
-
+```
+rustup target add aarch64-linux-android armv7-linux-androideabi \
+ x86_64-linux-android i686-linux-android
+cargo install cargo-ndk uniffi-bindgen
+```
-## Privacy Policy
-
-The NewPipe project aims to provide a private, anonymous experience for using web-based media services. Therefore, the app does not collect any data without your consent. NewPipe's privacy policy explains in detail what data is sent and stored when you send a crash report, or leave a comment in our blog. You can find the document [here](https://newpipe.net/legal/privacy/).
+…and `ANDROID_NDK_HOME` pointing at NDK r27c (or newer). The Gradle build runs
+`cargo ndk` + `uniffi-bindgen` automatically.
## License
-[](https://www.gnu.org/licenses/gpl-3.0.en.html)
-NewPipe is Free Software: You can use, study, share, and improve it at will. Specifically you can redistribute and/or modify it under the terms of the [GNU General Public License](https://www.gnu.org/licenses/gpl.html) as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+GPL-3.0-or-later, inherited from upstream NewPipe.
+
+## Upstream
+
+This repo tracks . Upstream changes
+get pulled periodically via the `upstream` remote.
+
+## Disclaimer
+
+Not affiliated with YouTube, Google, NewPipe e.V., the SponsorBlock project,
+or Return YouTube Dislike. Trademarks belong to their owners. Straw uses
+public web endpoints; nothing here authenticates to any account.
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 7ee7f3ec7..880e1f4b7 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -14,7 +14,7 @@ members = ["strawcore"]
edition = "2021"
license = "GPL-3.0-or-later"
authors = ["Sulkta-Coop"]
-repository = "http://192.168.0.5:3001/Sulkta-Coop/straw"
+repository = "https://git.sulkta.com/Sulkta-Coop/straw"
[profile.release]
# Strip debug info, run thin LTO. APK size matters more than build time here.
@@ -29,6 +29,6 @@ opt-level = "z"
url = "2"
[profile.dev]
-# Keep debug builds fast — we're rebuilding constantly during U-1..U-5.
+# Keep debug builds fast — we rebuild often during NDK cross-compile.
opt-level = 0
debug = 1
diff --git a/rust/README.md b/rust/README.md
index 2aa515b4c..720f81fa9 100644
--- a/rust/README.md
+++ b/rust/README.md
@@ -20,12 +20,12 @@ moves to Rust.
## Build chain
```
-crafting-table
+Build container (Sulkta uses one; any toolchain matching this layout works)
├── rustup stable (target add: aarch64-linux-android, armv7-linux-androideabi,
│ x86_64-linux-android, i686-linux-android)
├── cargo-ndk (cross-compile helper)
├── android-sdk (ANDROID_HOME, sdkmanager, build-tools, platforms)
-└── android-ndk (ANDROID_NDK_HOME, r27c LTS at /caches/android-sdk/ndk/...)
+└── android-ndk (ANDROID_NDK_HOME, r27c LTS)
Gradle (strawApp/build.gradle.kts)
├── cargoBuild Exec task → cargo ndk -t ... -o jniLibs/ build --release
diff --git a/rust/strawcore/Cargo.toml b/rust/strawcore/Cargo.toml
index 047f10f69..6ccd53fd5 100644
--- a/rust/strawcore/Cargo.toml
+++ b/rust/strawcore/Cargo.toml
@@ -30,14 +30,14 @@ strawcore-core = { path = "../../../strawcore" }
# Android target has no pre-generated bindings — flip on the `bindgen`
# feature so cargo regenerates at build time. Direct dep so the feature
# flag propagates (cargo's unified feature resolver lifts this to the
-# transitive use). Crafting-table has libclang preinstalled.
+# transitive use). Build host needs libclang installed.
rquickjs-sys = { version = "0.11", default-features = false, features = ["bindgen"] }
# Error glue.
thiserror = "1"
# Android log integration — `log::info!()` ends up in `adb logcat -s strawcore`.
log = "0.4"
android_logger = { version = "0.14", default-features = false }
-# vc=56 — subscription RSS feed fan-out. reqwest dedupes against
+# subscription RSS feed fan-out. reqwest dedupes against
# strawcore-core's already-pulled reqwest; quick-xml is small (~200KB);
# futures for buffer_unordered. rustls-tls avoids the NDK openssl headers
# headache.
diff --git a/rust/strawcore/src/error.rs b/rust/strawcore/src/error.rs
index 7b840fbe3..2703813b5 100644
--- a/rust/strawcore/src/error.rs
+++ b/rust/strawcore/src/error.rs
@@ -69,7 +69,7 @@ impl From for StrawcoreError {
// catches googlevideo.com hosts. The challenge URL
// itself still solves without `continue=`, so the
// user can tap to unblock without leaking the
- // signature/expire/pot token. Round-4 audit LOW-1.
+ // signature/expire/pot token.
StrawcoreError::RequiresLogin {
detail: format!("reCAPTCHA challenge: {}", strip_continue_param(&url)),
}
diff --git a/rust/strawcore/src/feed.rs b/rust/strawcore/src/feed.rs
index 54a372df5..c50b2942c 100644
--- a/rust/strawcore/src/feed.rs
+++ b/rust/strawcore/src/feed.rs
@@ -1,4 +1,4 @@
-// vc=56 — fast subscription feed via YouTube's per-channel RSS endpoint.
+// fast subscription feed via YouTube's per-channel RSS endpoint.
//
// YouTube serves `https://www.youtube.com/feeds/videos.xml?channel_id=UCxxx`
// — small Atom XML, no auth, no JS, no InnerTube round-trip. Replaces the
@@ -28,18 +28,15 @@ const PER_CHANNEL_TIMEOUT_S: u64 = 8;
/// Cap on the body bytes we'll read for a single RSS fetch. Real YT
/// Atom feeds are ~5-30 KB; 2 MiB leaves comfortable headroom while
/// blocking a hostile or compromised host from streaming GB-scale
-/// bodies into JVM memory inside the 8s timeout. Round-67 audit
-/// rust-HIGH-5.
+/// bodies into JVM memory inside the 8s timeout.
const RSS_MAX_BYTES: usize = 2 * 1024 * 1024;
/// Cap on parsed entries per channel — RSS normally returns 15.
/// 50 leaves headroom for one-off legitimate variance; anything
/// past that is a sign the feed isn't what we expect.
-/// Round-67 audit rust-MED-6.
const RSS_MAX_ENTRIES: usize = 50;
/// Year range we trust civil-to-days math for. Strawcore RSS only
/// emits real-world recent uploads; clamping here turns adversarial
/// year fields into a parse failure rather than i64 overflow.
-/// Round-67 audit rust-CRIT-1.
const YEAR_MIN: i32 = 1970;
const YEAR_MAX: i32 = 2200;
@@ -48,7 +45,7 @@ const YEAR_MAX: i32 = 2200;
/// items after the RSS-fed paint to fill in the gaps that
/// channel_feed_rss leaves empty.
///
-/// vc=66 — built specifically so the subs feed can show 'N views ·
+/// built specifically so the subs feed can show 'N views ·
/// X duration' the way YT does, without paying the full channel_info
/// page-scrape cost on initial paint. The underlying stream_info IS
/// heavier than we'd like (~500ms each, runs JS deobf for play URLs
@@ -75,7 +72,7 @@ pub async fn enrich_feed_item(
/// Shared reqwest Client — DNS resolver + TLS keepalive + connection
/// pool live here so a 50-channel fan-out reuses one pool instead of
-/// paying 50 handshakes. Round-67 audit rust-HIGH-4.
+/// paying 50 handshakes.
static RSS_CLIENT: OnceLock = OnceLock::new();
fn rss_client() -> Result<&'static Client, StrawcoreError> {
@@ -86,7 +83,7 @@ fn rss_client() -> Result<&'static Client, StrawcoreError> {
.timeout(Duration::from_secs(PER_CHANNEL_TIMEOUT_S))
.user_agent(concat!("Mozilla/5.0 (Android; Mobile; Straw/", env!("CARGO_PKG_VERSION"), ")"))
// Cap redirect chains so a misconfigured/hostile feed can't
- // spin a server out of our 8s budget. Round-67 audit rust-LOW-8.
+ // spin a server out of our 8s budget.
.redirect(reqwest::redirect::Policy::limited(3))
.build()
.map_err(|e| StrawcoreError::Extractor {
@@ -133,9 +130,9 @@ pub async fn subscription_feed(
// Per-channel ordering is RSS-served-newest-first. Cross-channel
// interleave is the caller's responsibility — Kotlin's mergeFromCache
// sorts by parsed recency, which is the source of truth. Returning
- // the flat list as-is. (vc=66 prior code sorted lexicographically
+ // the flat list as-is. (an earlier version sorted lexicographically
// on the relative-date STRING, which is wrong because "10 hours
- // ago" < "2 hours ago" in cmp order — round-67 audit rust-HIGH-6.)
+ // ago" < "2 hours ago" in cmp order)
Ok(results.into_iter().flatten().collect())
}
@@ -150,13 +147,13 @@ async fn fetch_channel_rss(client: &Client, channel_url: &str) -> Option Option {
use futures::StreamExt;
let mut total = 0usize;
@@ -168,8 +165,7 @@ async fn read_capped_body(resp: reqwest::Response) -> Option {
// large (HTTP allows multi-GiB chunks). Reject any one chunk
// bigger than the whole body cap before we even add it to the
// running total — protects against hyper having already
- // allocated the chunk on our behalf. Round-68 audit
- // rust-HIGH-1.
+ // allocated the chunk on our behalf.
if chunk.len() > RSS_MAX_BYTES {
log::warn!("strawcore::rss single chunk {} exceeds cap; aborting", chunk.len());
return None;
@@ -181,7 +177,7 @@ async fn read_capped_body(resp: reqwest::Response) -> Option {
}
buf.extend_from_slice(&chunk);
}
- // Lossy decode — round-68 audit rust-HIGH-2. A strict from_utf8
+ // Lossy decode — A strict from_utf8
// returns None on any invalid byte, so a single mojibake title
// would silently drop the entire channel from the feed. quick-xml
// tolerates U+FFFD replacement chars and the per-entry skip-on-
@@ -199,7 +195,6 @@ async fn read_capped_body(resp: reqwest::Response) -> Option {
/// * raw `UCxxx...` (already an ID)
///
/// Real YT channel IDs are EXACTLY 24 chars (`UC` + 22 base64-ish).
-/// Round-67 audit rust-HIGH-1.
///
/// `@handle` URLs are NOT supported here — RSS requires the channel ID.
/// Callers with @handles should resolve via channel_info() once and
@@ -210,7 +205,7 @@ fn extract_channel_id(input: &str) -> Option {
// Match the ":///channel/" prefix in a single sweep
// so we accept http/https + www./m. variants without four-way
// string-strip ladders. ANCHORED at the start of the string —
- // round-68 audit rust-HIGH-3: prior `find()` accepted any input
+ // prior `find()` accepted any input
// containing the prefix as a substring, so a pasted
// `evil.com/?redir=https://www.youtube.com/channel/UCxxx` would
// silently rewrite to the wrong channel.
@@ -237,7 +232,7 @@ fn extract_channel_id(input: &str) -> Option {
}
/// A real YouTube channel ID is `UC` followed by exactly 22 chars from
-/// `[A-Za-z0-9_-]`. Round-67 audit rust-HIGH-1.
+/// `[A-Za-z0-9_-]`.
fn validate_channel_id(id: &str) -> Option {
if id.len() != 24 || !id.starts_with("UC") {
return None;
@@ -343,7 +338,7 @@ fn parse_rss(body: &str, channel_id: String) -> Option> {
// Skip entries missing the load-bearing fields —
// an empty title renders as a blank card the user
// can't tap, and an empty published collapses the
- // recency sort. Round-67 audit rust-HIGH-2.
+ // recency sort.
if !video_id.is_empty() && !title.is_empty() && !published.is_empty() {
items.push(SearchItem {
url: format!("https://www.youtube.com/watch?v={video_id}"),
@@ -360,7 +355,7 @@ fn parse_rss(body: &str, channel_id: String) -> Option> {
// RSS gives RFC3339 timestamps. Convert to
// the human-relative format Kotlin's
// recencyScore parser expects ("N units
- // ago"). vc=56 was passing the raw ISO
+ // ago"). An earlier build was passing the raw ISO
// through, which broke the sort comparator
// — every item tied at MIN_VALUE so the
// feed order was effectively random; LTT +
@@ -371,7 +366,6 @@ fn parse_rss(body: &str, channel_id: String) -> Option> {
if items.len() >= RSS_MAX_ENTRIES {
// Defense-in-depth against a feed that
// ships thousands of blocks.
- // Round-67 audit rust-MED-6.
return Some(items);
}
}
@@ -387,7 +381,6 @@ fn parse_rss(body: &str, channel_id: String) -> Option> {
// collected rather than throwing the whole batch away.
// A truncated body (EOF mid-stream on a flaky network)
// would otherwise silently disappear the channel.
- // Round-67 audit rust-CRIT-3.
Err(e) => {
log::warn!("strawcore::rss parse error after {} items: {e}", items.len());
return Some(items);
@@ -428,7 +421,7 @@ fn iso_to_relative(iso: &str) -> String {
// top, which is the LTT/WTYP-recurrence vector. Treat future
// dates as "just now" so the relative-string sort behaves and
// a single skewed item doesn't pin itself at the top of the
- // feed. Round-67 audit rust-HIGH-7.
+ // feed.
if secs > now_secs {
return "just now".to_string();
}
@@ -455,7 +448,6 @@ fn parse_rfc3339_secs(s: &str) -> Option {
// Year clamp BEFORE civil_to_days — out-of-range years overflow
// the era arithmetic in debug, wrap in release. A hostile feed
// serving year=2147483647 must not produce junk timestamps.
- // Round-67 audit rust-CRIT-1.
if !(YEAR_MIN..=YEAR_MAX).contains(&y) {
return None;
}
diff --git a/rust/strawcore/src/runtime.rs b/rust/strawcore/src/runtime.rs
index 7e1ef14c8..336d6b4bb 100644
--- a/rust/strawcore/src/runtime.rs
+++ b/rust/strawcore/src/runtime.rs
@@ -3,7 +3,7 @@
// strawcore-core Downloader + Localization singleton so the extractor
// has an HTTP client to use.
//
-// Round-4 audit HIGH-1: the prior shape used `Once::call_once` and
+// the prior shape used `Once::call_once` and
// silently swallowed errors. If the FIRST call ran while the network
// stack wasn't ready (cold boot in airplane mode, SELinux denial on
// first TLS init, transient resolver failure), the Once slot was
@@ -60,7 +60,7 @@ pub fn ensure_initialized() {
// DownloaderMissing once from the extractor and recover on
// the next user action; the alternative (blocking N tokio
// workers for the full duration of a slow init) freezes the
- // UI. Round-6 audit HIGH-2 was the regression on round-5's
+ // UI. was the regression on round-5's
// mutex-first ordering.
let _guard = match INIT_LOCK.try_lock() {
Ok(g) => g,
diff --git a/rust/strawcore/src/search.rs b/rust/strawcore/src/search.rs
index 056af6977..f99c29da9 100644
--- a/rust/strawcore/src/search.rs
+++ b/rust/strawcore/src/search.rs
@@ -58,9 +58,9 @@ pub async fn search(query: String) -> Result, StrawcoreError> {
// names, sometimes embarrassing) and android_logger emits at
// info-level in release builds, which means they'd ride the
// Settings → Export Logs path straight into a user's chat. Log
- // shape, not content. vc=36 audit CVE HIGH-2.
+ // shape, not content.
log::info!("strawcore::search query_len={}", query.len());
- // Round-5 audit MED-1: ensure_initialized was only wired into
+ // ensure_initialized was only wired into
// init_logging() so the 5s-backoff retry path never fired from
// the hot entry points. Now every extractor entry re-asserts
// — cheap when INITIALIZED is true (single Acquire load).
diff --git a/strawApp/build.gradle.kts b/strawApp/build.gradle.kts
index e6458448e..13fbac20b 100644
--- a/strawApp/build.gradle.kts
+++ b/strawApp/build.gradle.kts
@@ -154,7 +154,7 @@ dependencies {
// - cargo + rustup with the four Android targets installed
// - cargo-ndk on PATH
// - ANDROID_NDK_HOME pointing at an NDK with the right toolchains
-// All of that lives in the crafting-table container.
+// All of that lives in the Sulkta build container.
// =============================================================================
val rustRoot = file("../rust").absolutePath
@@ -166,9 +166,10 @@ val cargoBin: String = "$cargoHome/bin/cargo"
val ndkHome: String = System.getenv("ANDROID_NDK_HOME")
?: System.getenv("ANDROID_NDK_ROOT")
?: "/caches/android-sdk/ndk/27.2.12479018"
-// Honor CARGO_TARGET_DIR if set (we redirect it to /caches on crafting-table
-// because the container's writable rootfs hits 100% before the cross-compile
-// for 4 ABIs finishes). Falls back to the default `/target`.
+// Honor CARGO_TARGET_DIR if set (our build container redirects it to a
+// cache mount because the container's writable rootfs hits 100% before
+// the cross-compile for 4 ABIs finishes). Falls back to the default
+// `/target`.
val cargoTargetDir: String = System.getenv("CARGO_TARGET_DIR")
?: "$rustRoot/target"
diff --git a/strawApp/src/main/AndroidManifest.xml b/strawApp/src/main/AndroidManifest.xml
index e751d37f5..56adc1f94 100644
--- a/strawApp/src/main/AndroidManifest.xml
+++ b/strawApp/src/main/AndroidManifest.xml
@@ -38,11 +38,11 @@
+ with ALLOWED_YT_HOSTS in util/YtUrl.kt (canonical home).
+ Was previously inlined in StrawActivity.kt under YT_HOSTS;
+ the two lists drifted (music.youtube.com etc. accepted by
+ code but never offered by the launcher disambig), so the
+ canonical list lives in one place now. -->
@@ -63,11 +63,11 @@
-
+
@@ -74,7 +74,7 @@ class StrawApp : Application() {
Playlists.init(this)
Resume.init(this)
FeedEnrichment.init(this)
- // vc=36 audit HIGH-R3: FeedCache (~225 KB) + SearchCache
+ // FeedCache (~225 KB) + SearchCache
// (~150 KB) JSON-decode at construction. Stash the
// applicationContext eagerly (cheap) so `get()` is callable
// anywhere; the actual store construction (and the disk
@@ -83,7 +83,7 @@ class StrawApp : Application() {
// main thread.
FeedCache.init(this)
SearchCache.init(this)
- // vc=36 audit CVE HIGH-5: sweepStale's deleteRecursively()
+ // sweepStale's deleteRecursively
// can walk ~256 MB if a previous import was LMK-killed
// mid-extraction. Strictly off the main thread.
appScope.launch {
diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/StrawHome.kt b/strawApp/src/main/kotlin/com/sulkta/straw/StrawHome.kt
index 94ac9fe69..d62c68852 100644
--- a/strawApp/src/main/kotlin/com/sulkta/straw/StrawHome.kt
+++ b/strawApp/src/main/kotlin/com/sulkta/straw/StrawHome.kt
@@ -295,7 +295,7 @@ private fun SubsPane(
LaunchedEffect(subs) { feedVm.refreshIfStale() }
// Filter + pagination state. hideWatched is sticky for the session
- // (no SharedPreferences yet — easy to add if Cobb wants persistence).
+ // (no SharedPreferences yet — easy to add if persistence is wanted).
// visibleCount starts at PAGE_SIZE and grows by PAGE_SIZE every time
// the scroll passes ~5 items from the bottom of what's currently
// visible.
@@ -324,7 +324,7 @@ private fun SubsPane(
}
}
// remember the page-slice so we don't allocate a new ArrayList on
- // every recomposition (scroll hitch vc=67).
+ // every recomposition (scroll hitch).
val displayed = remember(filteredItems, visibleCount) {
filteredItems.take(visibleCount)
}
@@ -373,7 +373,7 @@ private fun SubsPane(
Spacer(modifier = Modifier.height(16.dp))
// Show a slim error banner above cached items even if we have data —
- // audit HIGH-7: previously a 401/429 looked identical to a successful
+ // previously a 401/429 looked identical to a successful
// refresh because the error chip was hidden whenever items != empty.
if (feed.error != null && feed.items.isNotEmpty()) {
Text(
@@ -425,7 +425,7 @@ private fun SubsPane(
// (displayed.size, hasMore) was mutated BY this effect,
// which cancelled the snapshotFlow collector mid-stream
// and produced the "scrolled to bottom, nothing loads"
- // bug from the vc=34 audit.
+ // bug from the audit.
//
// hasMore and filteredItems are read inside the
// snapshotFlow producer (not closed over from outside)
@@ -598,7 +598,7 @@ private fun SubChip(
// width breaks the prior 2-line wrap mid-word ("NoCopyrightS
// / ounds", "DEFCONConfe / rence") — uglier than a clean
// "NoCopyrigh…". Centered text alignment so the ellipsis
- // sits over the chip's icon column. vc=64.
+ // sits over the chip's icon column.
Text(
text = ch.name,
style = MaterialTheme.typography.labelSmall,
diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/data/EnrichmentStore.kt b/strawApp/src/main/kotlin/com/sulkta/straw/data/EnrichmentStore.kt
index 5851fda9c..597688f1e 100644
--- a/strawApp/src/main/kotlin/com/sulkta/straw/data/EnrichmentStore.kt
+++ b/strawApp/src/main/kotlin/com/sulkta/straw/data/EnrichmentStore.kt
@@ -76,7 +76,7 @@ class EnrichmentStore(context: Context) {
)
val before = _entries.value
val next = _entries.updateAndGet { current ->
- // Round-67 audit HIGH-4: short-circuit when the cached
+ // short-circuit when the cached
// value is already the same view+duration — re-enriching
// within TTL otherwise allocates a new Map every call
// and the `before !== next` guard never triggers, so a
@@ -110,7 +110,7 @@ class EnrichmentStore(context: Context) {
private fun load(): Map = runCatching {
val s = sp.getString(KEY, null) ?: return emptyMap()
val loaded = json.decodeFromString