Compare commits

..

467 Commits

Author SHA1 Message Date
semantic-release-bot
7e675e6f62 chore: Release v5.50.0-dev.1 [skip ci]
# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.49.0-dev.1...v5.50.0-dev.1) (2026-01-22)

### Features

* **Nothing X:** Add `Show K1 token(s)` patch ([#6490](https://github.com/ReVanced/revanced-patches/issues/6490)) ([421cb28](421cb2899e))
* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([5edd9dc](5edd9dccae))
2026-01-22 18:20:51 +00:00
Pa1NarK
421cb2899e feat(Nothing X): Add Show K1 token(s) patch (#6490)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-22 19:17:45 +01:00
semantic-release-bot
97e74157fa chore: Release v5.50.0-dev.1 [skip ci]
# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.50.0-dev.1) (2026-01-22)

### Features

* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([89645dc](89645dcc2e))
2026-01-22 19:10:39 +01:00
Johannes Obermeier
5edd9dccae feat(YouTube Music): Add Unlock Android Auto Media Browser patch (#6477)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
(cherry picked from commit 89645dcc2e13603b8f2fedb5e16231cb396e5965)
2026-01-22 19:04:09 +01:00
semantic-release-bot
9c18e1e649 chore: Release v5.49.0-dev.1 [skip ci]
# [5.49.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.1-dev.1...v5.49.0-dev.1) (2026-01-22)

### Features

* **YouTube Music:** Add `Hide layout components` patch ([#6365](https://github.com/ReVanced/revanced-patches/issues/6365)) ([71ce823](71ce8230a9))
2026-01-22 17:43:08 +00:00
rospino74
71ce8230a9 feat(YouTube Music): Add Hide layout components patch (#6365)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-22 18:36:37 +01:00
github-actions[bot]
156441d3cf chore: Sync translations (#6516)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-01-22 18:21:28 +01:00
oSumAtrIX
634f47ef84 ci: Set necessary env vars for running Gradle task 2026-01-22 17:37:44 +01:00
oSumAtrIX
eeb133325e ci: Properly implement Crowdin strings processing 2026-01-22 17:37:44 +01:00
semantic-release-bot
e8d58ca9af chore: Release v5.48.1-dev.1 [skip ci]
## [5.48.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.48.1-dev.1) (2026-01-21)

### Bug Fixes

* Disable `Prevent screenshot detection` by default ([#6511](https://github.com/ReVanced/revanced-patches/issues/6511)) ([5b5c502](5b5c50254d))
2026-01-21 17:49:31 +00:00
Sayanth
5b5c50254d fix: Disable Prevent screenshot detection by default (#6511) 2026-01-21 18:44:04 +01:00
semantic-release-bot
ef052c0d8f chore: Release v5.48.0 [skip ci]
# [5.48.0](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0) (2026-01-19)

### Bug Fixes

* **Boost for Reddit - Fix missing audio in video downloads:** Make it work again by reflecting Reddits latest changes ([#6500](https://github.com/ReVanced/revanced-patches/issues/6500)) ([eecc44b](eecc44b956))
* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](44e7dbcf4d))
* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](789f0a5628))
* Fix compilation error introduced in `6bb6281` ([#6409](https://github.com/ReVanced/revanced-patches/issues/6409)) ([71c6cb5](71c6cb569e))
* Fix compilation error introduced in dc69f243 ([#6392](https://github.com/ReVanced/revanced-patches/issues/6392)) ([a429824](a429824bb7))
* **Instagram:** `Sanitize sharing links` ([#6483](https://github.com/ReVanced/revanced-patches/issues/6483)) ([8724759](87247590de))
* **YouTube - Hide layout components:** Hide new type of crowdfunding box ([#6380](https://github.com/ReVanced/revanced-patches/issues/6380)) ([dc69f24](dc69f2433e))

### Features

* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](4cc315952d))
* Add `Prevent screenshot detection` patch ([#6482](https://github.com/ReVanced/revanced-patches/issues/6482)) ([83c0127](83c0127ebb))
* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](6312fe8d60))
* **Instagram - Hides navigation buttons:** Add more buttons to hide ([#6390](https://github.com/ReVanced/revanced-patches/issues/6390)) ([6bb6281](6bb6281149))
* **Instagram:** Add `Hide highlights tray` patch ([#6489](https://github.com/ReVanced/revanced-patches/issues/6489)) ([8725a49](8725a49ba3))
* **Instagram:** Add `Remove build expired popup` patch ([#6488](https://github.com/ReVanced/revanced-patches/issues/6488)) ([18c0b04](18c0b04f0c))
* **Instagram:** Disable `Disable Reels scrolling` by default ([3401467](3401467a6d))
* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](d25dcfe49a))
* **ProtonVPN:** Add `Unlock split tunneling` patch ([#6353](https://github.com/ReVanced/revanced-patches/issues/6353)) ([e0f3346](e0f33468e6))
* **SBS On Demand:** Add `Remove ads` patch ([#6378](https://github.com/ReVanced/revanced-patches/issues/6378)) ([315931c](315931cbf8))
* **Strava:** Add `Add 'Give Kudos' button to 'Group Activity'` patch ([#6475](https://github.com/ReVanced/revanced-patches/issues/6475)) ([4c4ba1c](4c4ba1c78c))
* **Strava:** Add `Add media download` patch ([#6449](https://github.com/ReVanced/revanced-patches/issues/6449)) ([778d13c](778d13ce8b))
* **Strava:** Add `Block Snowplow tracking` patch ([#6413](https://github.com/ReVanced/revanced-patches/issues/6413)) ([c47beae](c47beae213))
* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](f5cbb31724))
* **Strava:** Add `Enable password login` patch ([#6396](https://github.com/ReVanced/revanced-patches/issues/6396)) ([8f3f4c9](8f3f4c95bb))
* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](b42ae27ce6))
* **YouTube:** Add `Pause on audio interrupt` patch ([#6464](https://github.com/ReVanced/revanced-patches/issues/6464)) ([19f146c](19f146c01d))
2026-01-19 09:28:58 +00:00
oSumAtrIX
d9fa580222 chore: Merge branch dev to main (#6385) 2026-01-19 10:25:41 +01:00
semantic-release-bot
182224c79d chore: Release v5.48.0-dev.13 [skip ci]
# [5.48.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.12...v5.48.0-dev.13) (2026-01-19)

### Features

* Add `Prevent screenshot detection` patch ([#6482](https://github.com/ReVanced/revanced-patches/issues/6482)) ([83c0127](83c0127ebb))
2026-01-19 09:21:21 +00:00
xehpuk
83c0127ebb feat: Add Prevent screenshot detection patch (#6482)
Co-authored-by: Swakshan <56347042+Swakshan@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-19 10:16:30 +01:00
semantic-release-bot
3762f1de08 chore: Release v5.48.0-dev.12 [skip ci]
# [5.48.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.11...v5.48.0-dev.12) (2026-01-19)

### Features

* **Instagram:** Add `Remove build expired popup` patch ([#6488](https://github.com/ReVanced/revanced-patches/issues/6488)) ([18c0b04](18c0b04f0c))
* **Strava:** Add `Add 'Give Kudos' button to 'Group Activity'` patch ([#6475](https://github.com/ReVanced/revanced-patches/issues/6475)) ([4c4ba1c](4c4ba1c78c))
2026-01-19 09:06:41 +00:00
Swakshan
18c0b04f0c feat(Instagram): Add Remove build expired popup patch (#6488)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-19 10:01:42 +01:00
xehpuk
4c4ba1c78c feat(Strava): Add Add 'Give Kudos' button to 'Group Activity' patch (#6475)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-19 10:00:25 +01:00
semantic-release-bot
7cef24a5e9 chore: Release v5.48.0-dev.11 [skip ci]
# [5.48.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.10...v5.48.0-dev.11) (2026-01-19)

### Features

* **Instagram:** Add `Hide highlights tray` patch ([#6489](https://github.com/ReVanced/revanced-patches/issues/6489)) ([8725a49](8725a49ba3))
2026-01-19 08:56:06 +00:00
Swakshan
8725a49ba3 feat(Instagram): Add Hide highlights tray patch (#6489)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-19 09:52:36 +01:00
semantic-release-bot
8b6360e34f chore: Release v5.48.0-dev.10 [skip ci]
# [5.48.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.9...v5.48.0-dev.10) (2026-01-19)

### Bug Fixes

* **Boost for Reddit - Fix missing audio in video downloads:** Make it work again by reflecting Reddits latest changes ([#6500](https://github.com/ReVanced/revanced-patches/issues/6500)) ([eecc44b](eecc44b956))
* **Instagram:** `Sanitize sharing links` ([#6483](https://github.com/ReVanced/revanced-patches/issues/6483)) ([8724759](87247590de))

### Features

* **Instagram:** Disable `Disable Reels scrolling` by default ([3401467](3401467a6d))
* **Strava:** Add `Add media download` patch ([#6449](https://github.com/ReVanced/revanced-patches/issues/6449)) ([778d13c](778d13ce8b))
* **YouTube:** Add `Pause on audio interrupt` patch ([#6464](https://github.com/ReVanced/revanced-patches/issues/6464)) ([19f146c](19f146c01d))
2026-01-19 08:40:58 +00:00
Pun Butrach
a10c51f160 ci: Include require environment variables in release step (#6493) 2026-01-19 09:38:25 +01:00
scruz
eecc44b956 fix(Boost for Reddit - Fix missing audio in video downloads): Make it work again by reflecting Reddits latest changes (#6500) 2026-01-18 16:21:59 +01:00
oSumAtrIX
3401467a6d feat(Instagram): Disable Disable Reels scrolling by default 2026-01-15 23:30:17 +01:00
Swakshan
87247590de fix(Instagram): Sanitize sharing links (#6483) 2026-01-13 20:10:25 +01:00
xehpuk
41e2590584 chore(Strava): Restructure media package (#6480) 2026-01-13 15:46:49 +01:00
xehpuk
778d13ce8b feat(Strava): Add Add media download patch (#6449)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-12 23:28:15 +01:00
ekaunt
19f146c01d feat(YouTube): Add Pause on audio interrupt patch (#6464)
Co-authored-by: bengross <bengross@vecta.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-12 23:18:09 +01:00
Pun Butrach
12b819d20e ci: Schedule Crowdin to runs weekly instead of every 12 hours (#6466) 2026-01-12 02:40:19 +01:00
Pun Butrach
004b5908db build: Use Gradle credentials system (#6467) 2026-01-11 16:59:48 +01:00
semantic-release-bot
f4af27dfec chore: Release v5.48.0-dev.9 [skip ci]
# [5.48.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.8...v5.48.0-dev.9) (2026-01-08)

### Features

* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](4cc315952d))
* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](6312fe8d60))
2026-01-08 00:11:57 +00:00
xehpuk
4cc315952d feat: Add Disable Sentry telemetry patch (#6416) 2026-01-08 01:08:52 +01:00
1fexd
6312fe8d60 feat: Disable Play Integrity patch (#6412)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-08 01:06:05 +01:00
Ushie
3d754575a4 ci: Simplify Crowdin translation file destination path (#6463) 2026-01-07 22:31:15 +01:00
semantic-release-bot
a3f7609fe3 chore: Release v5.48.0-dev.8 [skip ci]
# [5.48.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.7...v5.48.0-dev.8) (2026-01-04)

### Features

* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](d25dcfe49a))
2026-01-04 13:17:54 +00:00
Swakshan
d25dcfe49a feat(Letterboxd): Add Unlock app icons patch (#6415) 2026-01-04 14:14:29 +01:00
semantic-release-bot
1cc2cb9cb2 chore: Release v5.48.0-dev.7 [skip ci]
# [5.48.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.6...v5.48.0-dev.7) (2026-01-04)

### Features

* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](f5cbb31724))
* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](b42ae27ce6))
2026-01-04 02:43:29 +00:00
xehpuk
f5cbb31724 feat(Strava): Add Disable Quick Edit patch (#6452)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-04 03:38:04 +01:00
xehpuk
b42ae27ce6 feat(Strava): Add Overwrite media upload parameters patch (#6410) 2026-01-04 03:36:08 +01:00
semantic-release-bot
43ab29d03d chore: Release v5.48.0-dev.6 [skip ci]
# [5.48.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.5...v5.48.0-dev.6) (2026-01-04)

### Bug Fixes

* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](789f0a5628))
2026-01-04 02:07:07 +00:00
xehpuk
789f0a5628 fix: Fix build error introduced in 4046bee (#6417) 2026-01-04 03:03:44 +01:00
semantic-release-bot
da836b667c chore: Release v5.48.0-dev.5 [skip ci]
# [5.48.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.4...v5.48.0-dev.5) (2025-12-30)

### Bug Fixes

* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](44e7dbcf4d))
2025-12-30 18:37:28 +00:00
ILoveOpenSourceApplications
44e7dbcf4d fix(Disney+ - Skip ads): Remove unsupported package names (#6422) 2025-12-30 19:32:17 +01:00
semantic-release-bot
195c239000 chore: Release v5.48.0-dev.4 [skip ci]
# [5.48.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.3...v5.48.0-dev.4) (2025-12-29)

### Features

* **Strava:** Add `Block Snowplow tracking` patch ([#6413](https://github.com/ReVanced/revanced-patches/issues/6413)) ([c47beae](c47beae213))
2025-12-29 21:25:35 +00:00
xehpuk
c47beae213 feat(Strava): Add Block Snowplow tracking patch (#6413)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-29 22:20:26 +01:00
semantic-release-bot
cebcfab86a chore: Release v5.48.0-dev.3 [skip ci]
# [5.48.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.2...v5.48.0-dev.3) (2025-12-28)

### Bug Fixes

* Fix compilation error introduced in `6bb6281` ([#6409](https://github.com/ReVanced/revanced-patches/issues/6409)) ([71c6cb5](71c6cb569e))

### Features

* **Instagram - Hides navigation buttons:** Add more buttons to hide ([#6390](https://github.com/ReVanced/revanced-patches/issues/6390)) ([6bb6281](6bb6281149))
2025-12-28 22:33:37 +00:00
xehpuk
71c6cb569e fix: Fix compilation error introduced in 6bb6281 (#6409) 2025-12-28 23:30:32 +01:00
PainfulPaladins
6bb6281149 feat(Instagram - Hides navigation buttons): Add more buttons to hide (#6390) 2025-12-27 18:50:08 +01:00
semantic-release-bot
16bd96e2bb chore: Release v5.48.0-dev.2 [skip ci]
# [5.48.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.1...v5.48.0-dev.2) (2025-12-27)

### Features

* **Strava:** Add `Enable password login` patch ([#6396](https://github.com/ReVanced/revanced-patches/issues/6396)) ([8f3f4c9](8f3f4c95bb))
2025-12-27 17:48:32 +00:00
xehpuk
8f3f4c95bb feat(Strava): Add Enable password login patch (#6396)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-27 18:45:09 +01:00
semantic-release-bot
da02d68587 chore: Release v5.48.0-dev.1 [skip ci]
# [5.48.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0-dev.1) (2025-12-23)

### Bug Fixes

* Fix compilation error introduced in dc69f243 ([#6392](https://github.com/ReVanced/revanced-patches/issues/6392)) ([a429824](a429824bb7))
* **YouTube - Hide layout components:** Hide new type of crowdfunding box ([#6380](https://github.com/ReVanced/revanced-patches/issues/6380)) ([dc69f24](dc69f2433e))

### Features

* **ProtonVPN:** Add `Unlock split tunneling` patch ([#6353](https://github.com/ReVanced/revanced-patches/issues/6353)) ([e0f3346](e0f33468e6))
* **SBS On Demand:** Add `Remove ads` patch ([#6378](https://github.com/ReVanced/revanced-patches/issues/6378)) ([315931c](315931cbf8))
2025-12-23 01:30:13 +00:00
xehpuk
a429824bb7 fix: Fix compilation error introduced in dc69f243 (#6392) 2025-12-23 02:26:54 +01:00
Sylvain Finot
e0f33468e6 feat(ProtonVPN): Add Unlock split tunneling patch (#6353)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-22 15:00:08 +01:00
trespyian
315931cbf8 feat(SBS On Demand): Add Remove ads patch (#6378)
Co-authored-by: Trespyian <trespyian@nowhere.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-22 14:49:19 +01:00
ILoveOpenSourceApplications
dc69f2433e fix(YouTube - Hide layout components): Hide new type of crowdfunding box (#6380) 2025-12-21 23:10:35 +01:00
semantic-release-bot
73e43b2a49 chore: Release v5.47.0 [skip ci]
# [5.47.0](https://github.com/ReVanced/revanced-patches/compare/v5.46.0...v5.47.0) (2025-12-18)

### Bug Fixes

* **Instagram - Disable signature check:** Change patch to default excluded ([#6283](https://github.com/ReVanced/revanced-patches/issues/6283)) ([bb745b5](bb745b555b))
* **Lightroom:** Add `Disable version check` patch to fix opening the app  ([#6315](https://github.com/ReVanced/revanced-patches/issues/6315)) ([018d176](018d176914))
* **Reddit - Hide ads:** Update patch for new versions of Reddit ([#6342](https://github.com/ReVanced/revanced-patches/issues/6342)) ([f8bd123](f8bd1239cc))
* **Spotify:** Make patches work with latest versions again ([#6359](https://github.com/ReVanced/revanced-patches/issues/6359)) ([34830ba](34830ba63b))
* **YouTube - Hide layout components:** Fix "Hide Subscribe button" in channel page not working ([#6363](https://github.com/ReVanced/revanced-patches/issues/6363)) ([ded8370](ded8370207))
* **YouTube - Hide player flyout menu items:** Allow hiding audio menu with 'Android No SDK' client type ([9495cf4](9495cf49ef))
* **YouTube - Sanitize sharing links:** Handle non hierarchical urls ([654d091](654d091e65))

### Features

* **Disney+ - SkipAds:** Add other package names the patch is compatible with ([#6372](https://github.com/ReVanced/revanced-patches/issues/6372)) ([1f4f252](1f4f252c81))
* **Disney+:** Add `Skip ads` patch ([#6343](https://github.com/ReVanced/revanced-patches/issues/6343)) ([6bd7dca](6bd7dca75b))
* **IdAustria - Remove device integrity check:** Update patch to work with latest version ([#6360](https://github.com/ReVanced/revanced-patches/issues/6360)) ([0ea3491](0ea3491227))
* **Instagram:** Add `Anonymous story viewing` patch ([#6263](https://github.com/ReVanced/revanced-patches/issues/6263)) ([94ae84a](94ae84ad0f))
* **Instagram:** Add `Disable auto story flipping` patch ([#6262](https://github.com/ReVanced/revanced-patches/issues/6262)) ([2f0de15](2f0de15e67))
* **Instagram:** Add `Disable Reels scrolling` patch ([#6317](https://github.com/ReVanced/revanced-patches/issues/6317)) ([0928dcd](0928dcd00d))
* **Letterboxd:** Add `Hide ads` patch ([#6309](https://github.com/ReVanced/revanced-patches/issues/6309)) ([0af0ee9](0af0ee92c4))
* **Peacock TV:** Add `Hide ads` patch ([#6348](https://github.com/ReVanced/revanced-patches/issues/6348)) ([847ee18](847ee189a9))
* **ProtonVPN:** Add `Remove delay` patch ([#6326](https://github.com/ReVanced/revanced-patches/issues/6326)) ([bbd8932](bbd8932b2e))
* **Spoof SIM provider:** Spoof additional TelephonyManager methods ([#6293](https://github.com/ReVanced/revanced-patches/issues/6293)) ([ac583d4](ac583d40d0))
* **YouTube - Hide layout components:** Add "Hide cell divider", "Hide featured links", and "Hide featured videos" options ([#6335](https://github.com/ReVanced/revanced-patches/issues/6335)) ([a5d197b](a5d197b977))
* **YouTube - Hide layout components:** Add "Hide Join button" and "Hide Subscribe button" options for channel page ([#6345](https://github.com/ReVanced/revanced-patches/issues/6345)) ([02831a6](02831a6069))
* **YouTube - Hide Shorts components:** Add "Hide auto-dubbed label" and "Hide live preview" options ([#6334](https://github.com/ReVanced/revanced-patches/issues/6334)) ([a7c220a](a7c220a4ae))
2025-12-18 12:14:21 +00:00
oSumAtrIX
918f04793f chore: Merge branch dev to main (#6282) 2025-12-18 13:10:41 +01:00
semantic-release-bot
f1a9537f01 chore: Release v5.47.0-dev.18 [skip ci]
# [5.47.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.17...v5.47.0-dev.18) (2025-12-18)

### Features

* **Disney+ - SkipAds:** Add other package names the patch is compatible with ([#6372](https://github.com/ReVanced/revanced-patches/issues/6372)) ([1f4f252](1f4f252c81))
2025-12-18 12:09:57 +00:00
vippium
1f4f252c81 feat(Disney+ - SkipAds): Add other package names the patch is compatible with (#6372) 2025-12-18 12:59:47 +01:00
semantic-release-bot
2b560f5fe9 chore: Release v5.47.0-dev.17 [skip ci]
# [5.47.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.16...v5.47.0-dev.17) (2025-12-18)

### Bug Fixes

* **Reddit - Hide ads:** Update patch for new versions of Reddit ([#6342](https://github.com/ReVanced/revanced-patches/issues/6342)) ([f8bd123](f8bd1239cc))
2025-12-18 02:05:14 +00:00
g9q
f8bd1239cc fix(Reddit - Hide ads): Update patch for new versions of Reddit (#6342)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-18 03:01:25 +01:00
semantic-release-bot
c825ebda37 chore: Release v5.47.0-dev.16 [skip ci]
# [5.47.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.15...v5.47.0-dev.16) (2025-12-15)

### Bug Fixes

* **Lightroom:** Add `Disable version check` patch to fix opening the app  ([#6315](https://github.com/ReVanced/revanced-patches/issues/6315)) ([018d176](018d176914))

### Features

* **IdAustria - Remove device integrity check:** Update patch to work with latest version ([#6360](https://github.com/ReVanced/revanced-patches/issues/6360)) ([0ea3491](0ea3491227))
2025-12-15 11:34:05 +00:00
oSumAtrIX
255c00b183 chore: Fix minor syntax error 2025-12-15 12:28:53 +01:00
Alex Katlein
0ea3491227 feat(IdAustria - Remove device integrity check): Update patch to work with latest version (#6360)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-15 12:26:59 +01:00
Pun Butrach
5d437b08dd docs: Use American spelling (#6233) 2025-12-14 16:38:55 +01:00
f1re4xx
018d176914 fix(Lightroom): Add Disable version check patch to fix opening the app (#6315)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-14 16:38:26 +01:00
semantic-release-bot
9a77beea8a chore: Release v5.47.0-dev.15 [skip ci]
# [5.47.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.14...v5.47.0-dev.15) (2025-12-13)

### Bug Fixes

* **YouTube - Hide layout components:** Fix "Hide Subscribe button" in channel page not working ([#6363](https://github.com/ReVanced/revanced-patches/issues/6363)) ([ded8370](ded8370207))
2025-12-13 20:26:05 +00:00
ILoveOpenSourceApplications
ded8370207 fix(YouTube - Hide layout components): Fix "Hide Subscribe button" in channel page not working (#6363) 2025-12-13 21:22:35 +01:00
semantic-release-bot
4d1104fc32 chore: Release v5.47.0-dev.14 [skip ci]
# [5.47.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.13...v5.47.0-dev.14) (2025-12-13)

### Bug Fixes

* **Spotify:** Make patches work with latest versions again ([#6359](https://github.com/ReVanced/revanced-patches/issues/6359)) ([34830ba](34830ba63b))
2025-12-13 08:52:03 +00:00
Cilly Leang
34830ba63b fix(Spotify): Make patches work with latest versions again (#6359) 2025-12-13 09:48:39 +01:00
github-actions[bot]
7a6894d809 chore: Sync translations (#6344)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-12-10 03:58:45 +01:00
semantic-release-bot
144e6e2694 chore: Release v5.47.0-dev.13 [skip ci]
# [5.47.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.12...v5.47.0-dev.13) (2025-12-10)

### Features

* **Peacock TV:** Add `Hide ads` patch ([#6348](https://github.com/ReVanced/revanced-patches/issues/6348)) ([847ee18](847ee189a9))
2025-12-10 02:58:06 +00:00
g9q
847ee189a9 feat(Peacock TV): Add Hide ads patch (#6348) 2025-12-10 03:55:08 +01:00
semantic-release-bot
dc813fe617 chore: Release v5.47.0-dev.12 [skip ci]
# [5.47.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.11...v5.47.0-dev.12) (2025-12-08)

### Features

* **YouTube - Hide layout components:** Add "Hide Join button" and "Hide Subscribe button" options for channel page ([#6345](https://github.com/ReVanced/revanced-patches/issues/6345)) ([02831a6](02831a6069))
2025-12-08 21:14:39 +00:00
ILoveOpenSourceApplications
02831a6069 feat(YouTube - Hide layout components): Add "Hide Join button" and "Hide Subscribe button" options for channel page (#6345) 2025-12-08 22:10:35 +01:00
semantic-release-bot
5228fd4b58 chore: Release v5.47.0-dev.11 [skip ci]
# [5.47.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.10...v5.47.0-dev.11) (2025-12-08)

### Features

* **Disney+:** Add `Skip ads` patch ([#6343](https://github.com/ReVanced/revanced-patches/issues/6343)) ([6bd7dca](6bd7dca75b))
2025-12-08 13:51:15 +00:00
g9q
6bd7dca75b feat(Disney+): Add Skip ads patch (#6343)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-08 14:45:41 +01:00
semantic-release-bot
22ed7bfbb3 chore: Release v5.47.0-dev.10 [skip ci]
# [5.47.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.9...v5.47.0-dev.10) (2025-12-08)

### Features

* **YouTube - Hide Shorts components:** Add "Hide auto-dubbed label" and "Hide live preview" options ([#6334](https://github.com/ReVanced/revanced-patches/issues/6334)) ([a7c220a](a7c220a4ae))
2025-12-08 12:57:23 +00:00
ILoveOpenSourceApplications
a7c220a4ae feat(YouTube - Hide Shorts components): Add "Hide auto-dubbed label" and "Hide live preview" options (#6334) 2025-12-08 13:51:57 +01:00
semantic-release-bot
d8ca4ee931 chore: Release v5.47.0-dev.9 [skip ci]
# [5.47.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.8...v5.47.0-dev.9) (2025-12-08)

### Features

* **YouTube - Hide layout components:** Add "Hide cell divider", "Hide featured links", and "Hide featured videos" options ([#6335](https://github.com/ReVanced/revanced-patches/issues/6335)) ([a5d197b](a5d197b977))
2025-12-08 12:06:03 +00:00
ILoveOpenSourceApplications
a5d197b977 feat(YouTube - Hide layout components): Add "Hide cell divider", "Hide featured links", and "Hide featured videos" options (#6335)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-08 13:02:22 +01:00
semantic-release-bot
a0ec4c07f7 chore: Release v5.47.0-dev.8 [skip ci]
# [5.47.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.7...v5.47.0-dev.8) (2025-12-08)

### Features

* **Instagram:** Add `Disable Reels scrolling` patch ([#6317](https://github.com/ReVanced/revanced-patches/issues/6317)) ([0928dcd](0928dcd00d))
* **ProtonVPN:** Add `Remove delay` patch ([#6326](https://github.com/ReVanced/revanced-patches/issues/6326)) ([bbd8932](bbd8932b2e))
2025-12-08 11:36:43 +00:00
Alexey Gorbachev
0928dcd00d feat(Instagram): Add Disable Reels scrolling patch (#6317)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-08 12:31:54 +01:00
Sylvain Finot
bbd8932b2e feat(ProtonVPN): Add Remove delay patch (#6326)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-08 12:30:47 +01:00
semantic-release-bot
300b12f948 chore: Release v5.47.0-dev.7 [skip ci]
# [5.47.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.6...v5.47.0-dev.7) (2025-12-03)

### Features

* **Spoof SIM provider:** Spoof additional TelephonyManager methods ([#6293](https://github.com/ReVanced/revanced-patches/issues/6293)) ([ac583d4](ac583d40d0))
2025-12-03 15:05:05 +00:00
rospino74
ac583d40d0 feat(Spoof SIM provider): Spoof additional TelephonyManager methods (#6293) 2025-12-03 16:01:08 +01:00
semantic-release-bot
c400188c38 chore: Release v5.47.0-dev.6 [skip ci]
# [5.47.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.5...v5.47.0-dev.6) (2025-11-24)

### Features

* **Letterboxd:** Add `Hide ads` patch ([#6309](https://github.com/ReVanced/revanced-patches/issues/6309)) ([0af0ee9](0af0ee92c4))
2025-11-24 12:01:20 +00:00
Swakshan
0af0ee92c4 feat(Letterboxd): Add Hide ads patch (#6309)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-11-24 12:56:55 +01:00
semantic-release-bot
fff29544b9 chore: Release v5.47.0-dev.5 [skip ci]
# [5.47.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.4...v5.47.0-dev.5) (2025-11-13)

### Bug Fixes

* **YouTube - Hide player flyout menu items:** Allow hiding audio menu with 'Android No SDK' client type ([9495cf4](9495cf49ef))
2025-11-13 07:44:09 +00:00
LisoUseInAIKyrios
9495cf49ef fix(YouTube - Hide player flyout menu items): Allow hiding audio menu with 'Android No SDK' client type 2025-11-13 09:40:28 +02:00
semantic-release-bot
15675b5164 chore: Release v5.47.0-dev.4 [skip ci]
# [5.47.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.3...v5.47.0-dev.4) (2025-11-12)

### Bug Fixes

* **YouTube - Sanitize sharing links:** Handle non hierarchical urls ([654d091](654d091e65))
2025-11-12 19:01:00 +00:00
LisoUseInAIKyrios
654d091e65 fix(YouTube - Sanitize sharing links): Handle non hierarchical urls 2025-11-12 20:55:32 +02:00
semantic-release-bot
98371be33c chore: Release v5.47.0-dev.3 [skip ci]
# [5.47.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.2...v5.47.0-dev.3) (2025-11-12)

### Features

* **Instagram:** Add `Disable auto story flipping` patch ([#6262](https://github.com/ReVanced/revanced-patches/issues/6262)) ([2f0de15](2f0de15e67))
2025-11-12 07:46:04 +00:00
brosssh
2f0de15e67 feat(Instagram): Add Disable auto story flipping patch (#6262)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-11-12 08:41:15 +01:00
semantic-release-bot
df160370e2 chore: Release v5.47.0-dev.2 [skip ci]
# [5.47.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.1...v5.47.0-dev.2) (2025-11-12)

### Bug Fixes

* **Instagram - Disable signature check:** Change patch to default excluded ([#6283](https://github.com/ReVanced/revanced-patches/issues/6283)) ([bb745b5](bb745b555b))
2025-11-12 06:19:34 +00:00
LisoUseInAIKyrios
bb745b555b fix(Instagram - Disable signature check): Change patch to default excluded (#6283) 2025-11-12 08:14:16 +02:00
semantic-release-bot
8df9a46721 chore: Release v5.47.0-dev.1 [skip ci]
# [5.47.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.46.0...v5.47.0-dev.1) (2025-11-12)

### Features

* **Instagram:** Add `Anonymous story viewing` patch ([#6263](https://github.com/ReVanced/revanced-patches/issues/6263)) ([94ae84a](94ae84ad0f))
2025-11-12 05:32:16 +00:00
brosssh
94ae84ad0f feat(Instagram): Add Anonymous story viewing patch (#6263) 2025-11-12 07:29:13 +02:00
github-actions[bot]
4febb2e2e9 chore: Sync translations (#6280) 2025-11-12 07:28:43 +02:00
semantic-release-bot
b9bc7e3e58 chore: Release v5.46.0 [skip ci]
# [5.46.0](https://github.com/ReVanced/revanced-patches/compare/v5.45.0...v5.46.0) (2025-11-10)

### Bug Fixes

* **Duolingo - Disable ads:** Constrain patch to last working app target ([f238ae9](f238ae9895))
* **Instagram - Hide navigation buttons:** Constrain patch to last working app target ([e030e9c](e030e9c07a))
* **Spotify - Hide Create button:** Remove obsolete patch that is no longer needed ([#6252](https://github.com/ReVanced/revanced-patches/issues/6252)) ([59d85b2](59d85b28a7))
* **YouTube - Check watch history domain name resolution:** Fix false positive warning message if the internet connection fails halfway into the DNS check ([5726353](57263538c7))
* **YouTube - Hide layout components:** Fix "Hide Hype points" ([#6247](https://github.com/ReVanced/revanced-patches/issues/6247)) ([5821440](582144026d))
* **YouTube - Settings:** Add additional languages to ReVanced language preference ([d390b54](d390b54dab))
* **YouTube - Settings:** Resolve settings search crash when searching for specific words ([#6231](https://github.com/ReVanced/revanced-patches/issues/6231)) ([76dcfae](76dcfaefd8))

### Features

* **YouTube - Debugging:** Add setting to block experimental client flags ([#6196](https://github.com/ReVanced/revanced-patches/issues/6196)) ([2e9d695](2e9d6959c9))
* **YouTube - Hide layout components:** Add "Hide Hype points" ([#6230](https://github.com/ReVanced/revanced-patches/issues/6230)) ([a52c015](a52c0153b1))
* **YouTube - Hide layout components:** Add video description "Hide Featured content" and "Hide Subscribe button" ([#6253](https://github.com/ReVanced/revanced-patches/issues/6253)) ([da4cf94](da4cf94091))
* **YouTube - Hide player flyout menu items:** Add "Hide Listen with YouTube Music" ([#6232](https://github.com/ReVanced/revanced-patches/issues/6232)) ([858edbf](858edbf3e7))
* **YouTube Music:** Add `Change miniplayer color` patch ([#6259](https://github.com/ReVanced/revanced-patches/issues/6259)) ([ab808ae](ab808aeb77))
* **YouTube Music:** Add `Hide buttons` patch ([#6255](https://github.com/ReVanced/revanced-patches/issues/6255)) ([7a18ebc](7a18ebc7ab))
2025-11-10 10:06:40 +00:00
LisoUseInAIKyrios
9f3bb26cb9 chore: Merge branch dev to main (#6237) 2025-11-10 12:03:02 +02:00
github-actions[bot]
d64dfc2884 chore: Sync translations (#6276) 2025-11-10 12:00:41 +02:00
LisoUseInAIKyrios
a39ef1e0a4 refactor(YouTube Music - Custom branding): Resolve startup app crash when patching unsupported newer app versions 2025-11-10 11:23:09 +02:00
semantic-release-bot
1d8e977a43 chore: Release v5.46.0-dev.10 [skip ci]
# [5.46.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.9...v5.46.0-dev.10) (2025-11-09)

### Features

* **YouTube - Hide layout components:** Add video description "Hide Featured content" and "Hide Subscribe button" ([#6253](https://github.com/ReVanced/revanced-patches/issues/6253)) ([da4cf94](da4cf94091))
2025-11-09 15:35:27 +00:00
ILoveOpenSourceApplications
da4cf94091 feat(YouTube - Hide layout components): Add video description "Hide Featured content" and "Hide Subscribe button" (#6253) 2025-11-09 17:30:07 +02:00
github-actions[bot]
d23fa5e3b7 chore: Sync translations (#6270) 2025-11-09 17:29:11 +02:00
semantic-release-bot
34d29abdfa chore: Release v5.46.0-dev.9 [skip ci]
# [5.46.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.8...v5.46.0-dev.9) (2025-11-09)

### Features

* **YouTube Music:** Add `Change miniplayer color` patch ([#6259](https://github.com/ReVanced/revanced-patches/issues/6259)) ([ab808ae](ab808aeb77))
2025-11-09 07:43:26 +00:00
MarcaD
ab808aeb77 feat(YouTube Music): Add Change miniplayer color patch (#6259)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-11-09 09:39:51 +02:00
github-actions[bot]
a6b07cceb1 chore: Sync translations (#6266) 2025-11-09 09:39:32 +02:00
semantic-release-bot
d291881215 chore: Release v5.46.0-dev.8 [skip ci]
# [5.46.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.7...v5.46.0-dev.8) (2025-11-09)

### Features

* **YouTube Music:** Add `Hide buttons` patch ([#6255](https://github.com/ReVanced/revanced-patches/issues/6255)) ([7a18ebc](7a18ebc7ab))
2025-11-09 07:09:07 +00:00
MarcaD
7a18ebc7ab feat(YouTube Music): Add Hide buttons patch (#6255) 2025-11-09 09:05:31 +02:00
semantic-release-bot
475197af45 chore: Release v5.46.0-dev.7 [skip ci]
# [5.46.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.6...v5.46.0-dev.7) (2025-11-08)

### Bug Fixes

* **YouTube - Settings:** Add additional languages to ReVanced language preference ([d390b54](d390b54dab))
2025-11-08 12:32:08 +00:00
LisoUseInAIKyrios
d390b54dab fix(YouTube - Settings): Add additional languages to ReVanced language preference 2025-11-08 14:28:52 +02:00
github-actions[bot]
4d1eaa6b14 chore: Sync translations (#6260) 2025-11-08 14:26:20 +02:00
LisoUseInAIKyrios
c6364f5b49 chore: Fix compilation warning 2025-11-08 10:08:25 +02:00
semantic-release-bot
f177eae385 chore: Release v5.46.0-dev.6 [skip ci]
# [5.46.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.5...v5.46.0-dev.6) (2025-11-08)

### Features

* **YouTube - Debugging:** Add setting to block experimental client flags ([#6196](https://github.com/ReVanced/revanced-patches/issues/6196)) ([2e9d695](2e9d6959c9))
2025-11-08 08:07:41 +00:00
MarcaD
2e9d6959c9 feat(YouTube - Debugging): Add setting to block experimental client flags (#6196) 2025-11-08 10:01:46 +02:00
semantic-release-bot
81f83690d6 chore: Release v5.46.0-dev.5 [skip ci]
# [5.46.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.4...v5.46.0-dev.5) (2025-11-07)

### Bug Fixes

* **Duolingo - Disable ads:** Constrain patch to last working app target ([f238ae9](f238ae9895))
* **Instagram - Hide navigation buttons:** Constrain patch to last working app target ([e030e9c](e030e9c07a))
* **Spotify - Hide Create button:** Remove obsolete patch that is no longer needed ([#6252](https://github.com/ReVanced/revanced-patches/issues/6252)) ([59d85b2](59d85b28a7))
2025-11-07 11:39:00 +00:00
LisoUseInAIKyrios
f1bd6848c9 chore(deps-dev): Revert bump semantic-release from 24.2.9 to 25.0.1 (#6204)
This reverts commit 55e1a6784b.
2025-11-07 13:36:18 +02:00
LisoUseInAIKyrios
59d85b28a7 fix(Spotify - Hide Create button): Remove obsolete patch that is no longer needed (#6252) 2025-11-07 13:29:34 +02:00
LisoUseInAIKyrios
f238ae9895 fix(Duolingo - Disable ads): Constrain patch to last working app target 2025-11-07 13:27:31 +02:00
LisoUseInAIKyrios
e030e9c07a fix(Instagram - Hide navigation buttons): Constrain patch to last working app target 2025-11-07 13:25:24 +02:00
dependabot[bot]
5029e979be chore(deps): Bump actions/upload-artifact from 4 to 5 (#6201) 2025-11-07 09:34:24 +02:00
dependabot[bot]
55e1a6784b chore(deps-dev): Bump semantic-release from 24.2.9 to 25.0.1 (#6204) 2025-11-07 09:33:23 +02:00
dependabot[bot]
0cad5e73f0 chore(deps): Bump actions/setup-node from 5 to 6 (#6200) 2025-11-07 09:33:05 +02:00
semantic-release-bot
ce503d5b58 chore: Release v5.46.0-dev.4 [skip ci]
# [5.46.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.3...v5.46.0-dev.4) (2025-11-07)

### Bug Fixes

* **YouTube - Check watch history domain name resolution:** Fix false positive warning message if the internet connection fails halfway into the DNS check ([5726353](57263538c7))
2025-11-07 07:17:59 +00:00
LisoUseInAIKyrios
57263538c7 fix(YouTube - Check watch history domain name resolution): Fix false positive warning message if the internet connection fails halfway into the DNS check 2025-11-07 09:14:21 +02:00
semantic-release-bot
70f4955e89 chore: Release v5.46.0-dev.3 [skip ci]
# [5.46.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.2...v5.46.0-dev.3) (2025-11-06)

### Bug Fixes

* **YouTube - Hide layout components:** Fix "Hide Hype points" ([#6247](https://github.com/ReVanced/revanced-patches/issues/6247)) ([5821440](582144026d))
2025-11-06 12:12:38 +00:00
ILoveOpenSourceApplications
582144026d fix(YouTube - Hide layout components): Fix "Hide Hype points" (#6247) 2025-11-06 14:09:43 +02:00
github-actions[bot]
d80892cc0e chore: Sync translations (#6248) 2025-11-06 14:09:25 +02:00
semantic-release-bot
6c4b931b8a chore: Release v5.46.0-dev.2 [skip ci]
# [5.46.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.1...v5.46.0-dev.2) (2025-11-04)

### Bug Fixes

* **YouTube - Settings:** Resolve settings search crash when searching for specific words ([#6231](https://github.com/ReVanced/revanced-patches/issues/6231)) ([76dcfae](76dcfaefd8))
2025-11-04 13:03:33 +00:00
MarcaD
76dcfaefd8 fix(YouTube - Settings): Resolve settings search crash when searching for specific words (#6231) 2025-11-04 14:59:43 +02:00
github-actions[bot]
e4f52343c0 chore: Sync translations (#6239) 2025-11-04 14:59:20 +02:00
semantic-release-bot
1196b1a147 chore: Release v5.46.0-dev.1 [skip ci]
# [5.46.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.45.0...v5.46.0-dev.1) (2025-11-04)

### Features

* **YouTube - Hide layout components:** Add "Hide Hype points" ([#6230](https://github.com/ReVanced/revanced-patches/issues/6230)) ([a52c015](a52c0153b1))
* **YouTube - Hide player flyout menu items:** Add "Hide Listen with YouTube Music" ([#6232](https://github.com/ReVanced/revanced-patches/issues/6232)) ([858edbf](858edbf3e7))
2025-11-04 07:10:24 +00:00
ILoveOpenSourceApplications
858edbf3e7 feat(YouTube - Hide player flyout menu items): Add "Hide Listen with YouTube Music" (#6232) 2025-11-04 09:07:11 +02:00
ILoveOpenSourceApplications
a52c0153b1 feat(YouTube - Hide layout components): Add "Hide Hype points" (#6230) 2025-11-04 09:05:28 +02:00
semantic-release-bot
cd9ef81354 chore: Release v5.45.0 [skip ci]
# [5.45.0](https://github.com/ReVanced/revanced-patches/compare/v5.44.0...v5.45.0) (2025-11-01)

### Bug Fixes

* **Instagram:** Update failing fingerprints on newer versions ([#6181](https://github.com/ReVanced/revanced-patches/issues/6181)) ([c73a03c](c73a03c9e1))
* **TikTok - Downloads:** Fix download path setting  ([#6191](https://github.com/ReVanced/revanced-patches/issues/6191)) ([3e4990a](3e4990afff))
* **YouTube - Change header:** Do not mirror header graphic with RTL languages ([a0c5604](a0c5604951))
* **YouTube - Force original audio:** Fall back to visionOS and not Android Studio if Android VR is not available ([6d01863](6d01863ec7))
* **YouTube - Spoof video streams:** Remove spoof stream audio selector that no longer works ([292fae4](292fae440c))
* **YouTube Music - Hide category bar:** Correctly hide the category bar in newer app targets ([#6175](https://github.com/ReVanced/revanced-patches/issues/6175)) ([13cf172](13cf1724bf))

### Features

* **Spoof video streams:** Add experimental "Android No SDK" client type ([5f23bfe](5f23bfe833))
* **TikTok:** Add `Sanitize sharing links` patch ([#6176](https://github.com/ReVanced/revanced-patches/issues/6176)) ([ef44eaa](ef44eaa119))
* **YouTube - Change Header:** Use SVG for header logo ([#6178](https://github.com/ReVanced/revanced-patches/issues/6178)) ([e9f45ce](e9f45ce926))
2025-11-01 15:36:32 +00:00
LisoUseInAIKyrios
1b2cd64a86 chore: Merge branch dev to main (#6174) 2025-11-01 16:32:23 +01:00
semantic-release-bot
0c03599f07 chore: Release v5.45.0-dev.6 [skip ci]
# [5.45.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.5...v5.45.0-dev.6) (2025-11-01)

### Features

* **Spoof video streams:** Add experimental "Android No SDK" client type ([5f23bfe](5f23bfe833))
2025-11-01 08:25:19 +00:00
LisoUseInAIKyrios
5f23bfe833 feat(Spoof video streams): Add experimental "Android No SDK" client type 2025-11-01 09:19:49 +01:00
github-actions[bot]
2cf8f0e636 chore: Sync translations (#6197) 2025-11-01 09:17:01 +01:00
semantic-release-bot
c17cf98c7e chore: Release v5.45.0-dev.5 [skip ci]
# [5.45.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.4...v5.45.0-dev.5) (2025-11-01)

### Bug Fixes

* **TikTok - Downloads:** Fix download path setting  ([#6191](https://github.com/ReVanced/revanced-patches/issues/6191)) ([3e4990a](3e4990afff))
* **YouTube - Spoof video streams:** Remove spoof stream audio selector that no longer works ([292fae4](292fae440c))
2025-11-01 07:36:49 +00:00
hxreborn
3e4990afff fix(TikTok - Downloads): Fix download path setting (#6191) 2025-11-01 08:32:26 +01:00
LisoUseInAIKyrios
292fae440c fix(YouTube - Spoof video streams): Remove spoof stream audio selector that no longer works 2025-11-01 08:31:17 +01:00
semantic-release-bot
12e7c0943a chore: Release v5.45.0-dev.4 [skip ci]
# [5.45.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.3...v5.45.0-dev.4) (2025-10-30)

### Bug Fixes

* **YouTube - Change header:** Do not mirror header graphic with RTL languages ([a0c5604](a0c5604951))
2025-10-30 08:32:28 +00:00
LisoUseInAIKyrios
a0c5604951 fix(YouTube - Change header): Do not mirror header graphic with RTL languages 2025-10-30 09:27:27 +01:00
LisoUseInAIKyrios
38d9299dfe chore: Add branding license text file (#6179) 2025-10-30 09:26:18 +01:00
semantic-release-bot
dfdbbfa047 chore: Release v5.45.0-dev.3 [skip ci]
# [5.45.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.2...v5.45.0-dev.3) (2025-10-27)

### Features

* **YouTube - Change Header:** Use SVG for header logo ([#6178](https://github.com/ReVanced/revanced-patches/issues/6178)) ([e9f45ce](e9f45ce926))
2025-10-27 17:14:52 +00:00
MarcaD
e9f45ce926 feat(YouTube - Change Header): Use SVG for header logo (#6178)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-10-27 21:10:49 +04:00
semantic-release-bot
1b38b1a3c8 chore: Release v5.45.0-dev.2 [skip ci]
# [5.45.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.1...v5.45.0-dev.2) (2025-10-26)

### Bug Fixes

* **YouTube - Force original audio:** Fall back to visionOS and not Android Studio if Android VR is not available ([6d01863](6d01863ec7))
* **YouTube Music - Hide category bar:** Correctly hide the category bar in newer app targets ([#6175](https://github.com/ReVanced/revanced-patches/issues/6175)) ([13cf172](13cf1724bf))
2025-10-26 15:27:23 +00:00
MarcaD
13cf1724bf fix(YouTube Music - Hide category bar): Correctly hide the category bar in newer app targets (#6175) 2025-10-26 19:23:40 +04:00
LisoUseInAIKyrios
6d01863ec7 fix(YouTube - Force original audio): Fall back to visionOS and not Android Studio if Android VR is not available 2025-10-26 19:23:04 +04:00
semantic-release-bot
a32ed30b4c chore: Release v5.45.0-dev.1 [skip ci]
# [5.45.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.44.0...v5.45.0-dev.1) (2025-10-26)

### Bug Fixes

* **Instagram:** Update failing fingerprints on newer versions ([#6181](https://github.com/ReVanced/revanced-patches/issues/6181)) ([c73a03c](c73a03c9e1))

### Features

* **TikTok:** Add `Sanitize sharing links` patch ([#6176](https://github.com/ReVanced/revanced-patches/issues/6176)) ([ef44eaa](ef44eaa119))
2025-10-26 10:05:58 +00:00
hxreborn
ef44eaa119 feat(TikTok): Add Sanitize sharing links patch (#6176) 2025-10-26 14:01:32 +04:00
rospino74
c73a03c9e1 fix(Instagram): Update failing fingerprints on newer versions (#6181) 2025-10-26 11:01:06 +01:00
LisoUseInAIKyrios
c1681f982a chore: Dump api 2025-10-25 08:11:57 +04:00
semantic-release-bot
1502bf7524 chore: Release v5.44.0 [skip ci]
# [5.44.0](https://github.com/ReVanced/revanced-patches/compare/v5.43.1...v5.44.0) (2025-10-24)

### Bug Fixes

* **Google Photos - Spoof features:** Add support for Pixel 10 devices ([#6161](https://github.com/ReVanced/revanced-patches/issues/6161)) ([754b719](754b71959a))
* **X / Twitter - Change link sharing domain:** Use bytecode patching to resolve patching with Manager ([#6125](https://github.com/ReVanced/revanced-patches/issues/6125)) ([0af8c8a](0af8c8a766))
* **YouTube - Hide layout components:** Hide new kind of community post ([#6146](https://github.com/ReVanced/revanced-patches/issues/6146)) ([cfd244b](cfd244b408))
* **YouTube Music:** Resolve patching 7.29 target ([2e4c6fd](2e4c6fdcad))

### Features

* Add `Custom network security` patch ([#6151](https://github.com/ReVanced/revanced-patches/issues/6151)) ([e7336d2](e7336d2ef3))
* **Duolingo - Enable debug menu:** Support latest app target ([#6163](https://github.com/ReVanced/revanced-patches/issues/6163)) ([08baa19](08baa19b4a))
* **Duolingo:** Add `Skip energy recharge ads` patch ([#6167](https://github.com/ReVanced/revanced-patches/issues/6167)) ([591e106](591e106098))
* **Samsung Radio:** Add `Disable device checks` patch ([#6145](https://github.com/ReVanced/revanced-patches/issues/6145)) ([de97562](de97562c5d))
2025-10-24 12:03:24 +00:00
LisoUseInAIKyrios
dffb1e6525 chore: Merge branch dev to main (#6135) 2025-10-24 15:59:21 +04:00
semantic-release-bot
b8c2ede2bf chore: Release v5.44.0-dev.4 [skip ci]
# [5.44.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.44.0-dev.3...v5.44.0-dev.4) (2025-10-24)

### Features

* Add `Custom network security` patch ([#6151](https://github.com/ReVanced/revanced-patches/issues/6151)) ([e7336d2](e7336d2ef3))
* **Duolingo:** Add `Skip energy recharge ads` patch ([#6167](https://github.com/ReVanced/revanced-patches/issues/6167)) ([591e106](591e106098))
2025-10-24 05:32:12 +00:00
hckrman101
591e106098 feat(Duolingo): Add Skip energy recharge ads patch (#6167) 2025-10-24 09:28:34 +04:00
Pawloland
e7336d2ef3 feat: Add Custom network security patch (#6151) 2025-10-24 09:27:12 +04:00
LisoUseInAIKyrios
7a53f8f62d chore(YouTube Music): Simplify fingerprint for upcoming app versions 2025-10-24 09:25:44 +04:00
github-actions[bot]
3466d9d210 chore: Sync translations (#6172) 2025-10-24 09:19:08 +04:00
LisoUseInAIKyrios
876d7a6b03 chore(X / Twitter - Change link sharing domain): Add disclaimer to description 2025-10-24 09:18:20 +04:00
semantic-release-bot
a2aa9cac27 chore: Release v5.44.0-dev.3 [skip ci]
# [5.44.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.44.0-dev.2...v5.44.0-dev.3) (2025-10-22)

### Features

* **Duolingo - Enable debug menu:** Support latest app target ([#6163](https://github.com/ReVanced/revanced-patches/issues/6163)) ([08baa19](08baa19b4a))
2025-10-22 20:24:37 +00:00
LisoUseInAIKyrios
08baa19b4a feat(Duolingo - Enable debug menu): Support latest app target (#6163) 2025-10-23 00:21:30 +04:00
semantic-release-bot
7283b93cea chore: Release v5.44.0-dev.2 [skip ci]
# [5.44.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.44.0-dev.1...v5.44.0-dev.2) (2025-10-22)

### Bug Fixes

* **Google Photos - Spoof features:** Add support for Pixel 10 devices ([#6161](https://github.com/ReVanced/revanced-patches/issues/6161)) ([754b719](754b71959a))
2025-10-22 16:53:15 +00:00
Lassie111
754b71959a fix(Google Photos - Spoof features): Add support for Pixel 10 devices (#6161) 2025-10-22 20:50:10 +04:00
LisoUseInAIKyrios
c64e29ec57 refactor(YouTube - Seekbar): Remove obsolete splash screen color code 2025-10-22 11:47:39 +04:00
semantic-release-bot
b2dd008aee chore: Release v5.44.0-dev.1 [skip ci]
# [5.44.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.43.2-dev.3...v5.44.0-dev.1) (2025-10-22)

### Features

* **Samsung Radio:** Add `Disable device checks` patch ([#6145](https://github.com/ReVanced/revanced-patches/issues/6145)) ([de97562](de97562c5d))
2025-10-22 06:14:00 +00:00
rospino74
de97562c5d feat(Samsung Radio): Add Disable device checks patch (#6145) 2025-10-22 10:09:34 +04:00
semantic-release-bot
6373829fd6 chore: Release v5.43.2-dev.3 [skip ci]
## [5.43.2-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.43.2-dev.2...v5.43.2-dev.3) (2025-10-19)

### Bug Fixes

* **YouTube - Hide layout components:** Hide new kind of community post ([#6146](https://github.com/ReVanced/revanced-patches/issues/6146)) ([cfd244b](cfd244b408))
2025-10-19 14:29:38 +00:00
Bceez
cfd244b408 fix(YouTube - Hide layout components): Hide new kind of community post (#6146) 2025-10-19 18:24:45 +04:00
semantic-release-bot
e8e28e2b6a chore: Release v5.43.2-dev.2 [skip ci]
## [5.43.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.43.2-dev.1...v5.43.2-dev.2) (2025-10-17)

### Bug Fixes

* **YouTube Music:** Resolve patching 7.29 target ([2e4c6fd](2e4c6fdcad))
2025-10-17 21:15:51 +00:00
LisoUseInAIKyrios
2e4c6fdcad fix(YouTube Music): Resolve patching 7.29 target 2025-10-18 01:12:34 +04:00
github-actions[bot]
644d6dcb51 chore: Sync translations (#6142) 2025-10-18 01:08:27 +04:00
semantic-release-bot
14dd7346a8 chore: Release v5.43.2-dev.1 [skip ci]
## [5.43.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.43.1...v5.43.2-dev.1) (2025-10-16)

### Bug Fixes

* **X / Twitter - Change link sharing domain:** Use bytecode patching to resolve patching with Manager ([#6125](https://github.com/ReVanced/revanced-patches/issues/6125)) ([0af8c8a](0af8c8a766))
2025-10-16 19:37:32 +00:00
ADudeCalledLeo
0af8c8a766 fix(X / Twitter - Change link sharing domain): Use bytecode patching to resolve patching with Manager (#6125) 2025-10-16 23:29:01 +04:00
semantic-release-bot
96454c843b chore: Release v5.43.1 [skip ci]
## [5.43.1](https://github.com/ReVanced/revanced-patches/compare/v5.43.0...v5.43.1) (2025-10-15)

### Bug Fixes

* **X / Twitter - Change link sharing domain:** Resolve duplicate patch option ([#6119](https://github.com/ReVanced/revanced-patches/issues/6119)) ([7563990](7563990750))
* **X / Twitter:** Do not crash Manager when clicking on domain patch option ([2a1e318](2a1e31860f))
2025-10-15 08:15:15 +00:00
LisoUseInAIKyrios
476ef0fae1 chore: Merge branch dev to main (#6121) 2025-10-15 12:12:25 +04:00
github-actions[bot]
bbec724afb chore: Sync translations (#6124) 2025-10-15 12:11:47 +04:00
semantic-release-bot
7a1dcbd4ee chore: Release v5.43.1-dev.2 [skip ci]
## [5.43.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.43.1-dev.1...v5.43.1-dev.2) (2025-10-14)

### Bug Fixes

* **X / Twitter:** Do not crash Manager when clicking on domain patch option ([2a1e318](2a1e31860f))
2025-10-14 19:47:15 +00:00
LisoUseInAIKyrios
2a1e31860f fix(X / Twitter): Do not crash Manager when clicking on domain patch option 2025-10-14 23:42:15 +04:00
semantic-release-bot
949d6bdd19 chore: Release v5.43.1-dev.1 [skip ci]
## [5.43.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.43.0...v5.43.1-dev.1) (2025-10-14)

### Bug Fixes

* **X / Twitter - Change link sharing domain:** Resolve duplicate patch option ([#6119](https://github.com/ReVanced/revanced-patches/issues/6119)) ([7563990](7563990750))
2025-10-14 18:19:42 +00:00
ADudeCalledLeo
7563990750 fix(X / Twitter - Change link sharing domain): Resolve duplicate patch option (#6119) 2025-10-14 22:16:01 +04:00
semantic-release-bot
4b605eb270 chore: Release v5.43.0 [skip ci]
# [5.43.0](https://github.com/ReVanced/revanced-patches/compare/v5.42.1...v5.43.0) (2025-10-14)

### Bug Fixes

* **Custom branding:** Use white notification icon for expanded status bar panel ([95eee59](95eee59a87))
* **Instagram - Change sharing domain:** Display patch option ([#6089](https://github.com/ReVanced/revanced-patches/issues/6089)) ([be2b144](be2b144cc9))
* **X / Twitter - Change Link Sharing Domain:** Change link domain of share copy action ([#6091](https://github.com/ReVanced/revanced-patches/issues/6091)) ([5484625](54846253d7))
* **YouTube - Custom branding:** Do not add a broken custom icon if the user provides an invalid custom icon path ([6555f6e](6555f6e6f8))
* **YouTube - Custom branding:** Use ReVanced icon for status bar notification icon ([#6108](https://github.com/ReVanced/revanced-patches/issues/6108)) ([10ea250](10ea250d4a))
* **YouTube - Force original audio:** Do not use translated audio if stream spoofing is off and force audio is on ([0c19dba](0c19dbaf30))

### Features

* **Instagram:** Add `Hide suggested content` patch ([#6075](https://github.com/ReVanced/revanced-patches/issues/6075)) ([50f0b9c](50f0b9c5ee))
2025-10-14 12:03:58 +00:00
LisoUseInAIKyrios
c2d7a7fb8b chore: Merge branch dev to main (#6090) 2025-10-14 16:00:00 +04:00
github-actions[bot]
a55560dc25 chore: Sync translations (#6118) 2025-10-14 15:58:38 +04:00
semantic-release-bot
e8522d703e chore: Release v5.43.0-dev.4 [skip ci]
# [5.43.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.43.0-dev.3...v5.43.0-dev.4) (2025-10-14)

### Bug Fixes

* **YouTube - Force original audio:** Do not use translated audio if stream spoofing is off and force audio is on ([0c19dba](0c19dbaf30))
2025-10-14 11:46:49 +00:00
LisoUseInAIKyrios
068d029a03 refactor: Use notNull delegate to prevent wasting more time in the future 2025-10-14 15:43:05 +04:00
LisoUseInAIKyrios
0c19dbaf30 fix(YouTube - Force original audio): Do not use translated audio if stream spoofing is off and force audio is on 2025-10-14 15:38:05 +04:00
semantic-release-bot
bf73ac8316 chore: Release v5.43.0-dev.3 [skip ci]
# [5.43.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.43.0-dev.2...v5.43.0-dev.3) (2025-10-14)

### Bug Fixes

* **Custom branding:** Use white notification icon for expanded status bar panel ([95eee59](95eee59a87))
2025-10-14 10:39:28 +00:00
LisoUseInAIKyrios
95eee59a87 fix(Custom branding): Use white notification icon for expanded status bar panel 2025-10-14 14:36:09 +04:00
semantic-release-bot
566875ea53 chore: Release v5.43.0-dev.2 [skip ci]
# [5.43.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.43.0-dev.1...v5.43.0-dev.2) (2025-10-14)

### Bug Fixes

* **YouTube - Custom branding:** Use ReVanced icon for status bar notification icon ([#6108](https://github.com/ReVanced/revanced-patches/issues/6108)) ([10ea250](10ea250d4a))
2025-10-14 05:58:15 +00:00
LisoUseInAIKyrios
10ea250d4a fix(YouTube - Custom branding): Use ReVanced icon for status bar notification icon (#6108) 2025-10-14 09:54:14 +04:00
github-actions[bot]
5bd0f11630 chore: Sync translations (#6117) 2025-10-14 09:53:13 +04:00
semantic-release-bot
4547ecb73c chore: Release v5.43.0-dev.1 [skip ci]
# [5.43.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.42.2-dev.3...v5.43.0-dev.1) (2025-10-11)

### Features

* **Instagram:** Add `Hide suggested content` patch ([#6075](https://github.com/ReVanced/revanced-patches/issues/6075)) ([50f0b9c](50f0b9c5ee))
2025-10-11 11:22:05 +00:00
Swakshan
50f0b9c5ee feat(Instagram): Add Hide suggested content patch (#6075) 2025-10-11 15:17:24 +04:00
semantic-release-bot
a8c4bdb8a6 chore: Release v5.42.2-dev.3 [skip ci]
## [5.42.2-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.42.2-dev.2...v5.42.2-dev.3) (2025-10-11)

### Bug Fixes

* **YouTube - Custom branding:** Do not add a broken custom icon if the user provides an invalid custom icon path ([6555f6e](6555f6e6f8))
2025-10-11 08:04:05 +00:00
LisoUseInAIKyrios
6555f6e6f8 fix(YouTube - Custom branding): Do not add a broken custom icon if the user provides an invalid custom icon path 2025-10-11 12:00:26 +04:00
semantic-release-bot
a0e2c5c7b9 chore: Release v5.42.2-dev.2 [skip ci]
## [5.42.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.42.2-dev.1...v5.42.2-dev.2) (2025-10-10)

### Bug Fixes

* **X / Twitter - Change Link Sharing Domain:** Change link domain of share copy action ([#6091](https://github.com/ReVanced/revanced-patches/issues/6091)) ([5484625](54846253d7))
2025-10-10 22:05:28 +00:00
ADudeCalledLeo
54846253d7 fix(X / Twitter - Change Link Sharing Domain): Change link domain of share copy action (#6091)
Co-authored-by: nyraa <112930946+nyraa@users.noreply.github.com>
2025-10-11 02:01:44 +04:00
github-actions[bot]
a98e8f7370 chore: Sync translations (#6097) 2025-10-11 02:01:17 +04:00
semantic-release-bot
2d928e0cd6 chore: Release v5.42.2-dev.1 [skip ci]
## [5.42.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.42.1...v5.42.2-dev.1) (2025-10-09)

### Bug Fixes

* **Instagram - Change sharing domain:** Display patch option ([#6089](https://github.com/ReVanced/revanced-patches/issues/6089)) ([be2b144](be2b144cc9))
2025-10-09 08:03:53 +00:00
brosssh
be2b144cc9 fix(Instagram - Change sharing domain): Display patch option (#6089) 2025-10-09 10:00:17 +02:00
semantic-release-bot
52c0bb6aa2 chore: Release v5.42.1 [skip ci]
## [5.42.1](https://github.com/ReVanced/revanced-patches/compare/v5.42.0...v5.42.1) (2025-10-08)

### Bug Fixes

* **YouTube - Custom Branding:** Resolve startup crash with root installation ([fd4b2e1](fd4b2e1bb9))
2025-10-08 07:47:41 +00:00
LisoUseInAIKyrios
38a49cc2a1 chore: Merge branch dev to main (#6080) 2025-10-08 11:44:39 +04:00
semantic-release-bot
91044b3a50 chore: Release v5.42.1-dev.1 [skip ci]
## [5.42.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.42.0...v5.42.1-dev.1) (2025-10-08)

### Bug Fixes

* **YouTube - Custom Branding:** Resolve startup crash with root installation ([fd4b2e1](fd4b2e1bb9))
2025-10-08 07:42:49 +00:00
LisoUseInAIKyrios
fd4b2e1bb9 fix(YouTube - Custom Branding): Resolve startup crash with root installation 2025-10-08 11:39:48 +04:00
semantic-release-bot
d0f20c8c7f chore: Release v5.42.0 [skip ci]
# [5.42.0](https://github.com/ReVanced/revanced-patches/compare/v5.41.0...v5.42.0) (2025-10-08)

### Bug Fixes

* **Custom branding:** Update ReVanced logo ([#6049](https://github.com/ReVanced/revanced-patches/issues/6049)) ([9441e7a](9441e7acb4))
* **Custom branding:** Update ReVanced logo sizing ([#6029](https://github.com/ReVanced/revanced-patches/issues/6029)) ([ae4b947](ae4b9474d3))
* **Instagram - Hide navigation buttons:** Resolve app startup crash ([080a226](080a226614))
* **Spotify:** Change `Hide Create button` patch to default off ([#6067](https://github.com/ReVanced/revanced-patches/issues/6067)) ([19949e1](19949e1695))
* **X / Twitter:** Remove non functional and obsolete patch `Open links with app chooser` ([#6033](https://github.com/ReVanced/revanced-patches/issues/6033)) ([673609c](673609c2aa))
* **YouTube - Force original audio:** Change patch to default on ([#6070](https://github.com/ReVanced/revanced-patches/issues/6070)) ([bd4ba2d](bd4ba2dae8))
* **YouTube - Force original language:** Resolve some videos using Swedish audio track ([9d67316](9d6731660b))
* **YouTube - Hide end screen cards:** Hide new type of end screen card ([#6027](https://github.com/ReVanced/revanced-patches/issues/6027)) ([76b0364](76b0364c5b))
* **YouTube - Spoof video streams:** Add "Allow Android VR AV1" setting ([#6071](https://github.com/ReVanced/revanced-patches/issues/6071)) ([f03256c](f03256c471))
* **YouTube - Spoof video streams:** Do not allow VR AV1 if "Force AVC" is enabled ([7afeaeb](7afeaebb5c))
* **YouTube - Spoof video streams:** Resolve playback dropping frames ([#6051](https://github.com/ReVanced/revanced-patches/issues/6051)) ([a62ee43](a62ee43441))
* **YouTube Music - GmsCore support:** Handle sharing links to certain apps such as Instagram ([#6026](https://github.com/ReVanced/revanced-patches/issues/6026)) ([328234f](328234f39a))
* **YouTube Music - Hide cast button:** Fix patching error ([28799a5](28799a548a))
* **YouTube Music - Hide cast button:** Resolve button not hiding ([7817885](7817885cff))
* **YouTube:** Resolve UI components not hiding for some users ([#6054](https://github.com/ReVanced/revanced-patches/issues/6054)) ([6b26346](6b26346914))

### Features

* **Custom branding:** Add in-app settings to change icon and name ([#6059](https://github.com/ReVanced/revanced-patches/issues/6059)) ([a50f3b5](a50f3b5177))
* **Instagram:** Add `Custom share domain` patch ([#5998](https://github.com/ReVanced/revanced-patches/issues/5998)) ([20c4131](20c413120b))
* **Instagram:** Add `Enable developer menu` patch ([#6043](https://github.com/ReVanced/revanced-patches/issues/6043)) ([2154d89](2154d89242))
* **Instagram:** Add `Open links externally` patch ([#6012](https://github.com/ReVanced/revanced-patches/issues/6012)) ([08e8ead](08e8ead04f))
* **Instagram:** Add `Sanitize sharing links` patch ([#5986](https://github.com/ReVanced/revanced-patches/issues/5986)) ([963a4ef](963a4ef43f))
* **Viber:** Add `Hide navigation buttons` patch ([#5991](https://github.com/ReVanced/revanced-patches/issues/5991)) ([5cb46c4](5cb46c4e91))
* **YouTube Music:** Add `Custom branding` patch ([#6007](https://github.com/ReVanced/revanced-patches/issues/6007)) ([4c8b56f](4c8b56f546))
* **YouTube Music:** Add `Force original audio` patch ([#6036](https://github.com/ReVanced/revanced-patches/issues/6036)) ([d0d53d1](d0d53d109e))
2025-10-08 06:16:06 +00:00
semantic-release-bot
d65dbc749c chore: Release v5.42.0 [skip ci]
# [5.42.0](https://github.com/ReVanced/revanced-patches/compare/v5.41.0...v5.42.0) (2025-10-08)

### Bug Fixes

* **Custom branding:** Update ReVanced logo ([#6049](https://github.com/ReVanced/revanced-patches/issues/6049)) ([9441e7a](9441e7acb4))
* **Custom branding:** Update ReVanced logo sizing ([#6029](https://github.com/ReVanced/revanced-patches/issues/6029)) ([ae4b947](ae4b9474d3))
* **Instagram - Hide navigation buttons:** Resolve app startup crash ([080a226](080a226614))
* **Spotify:** Change `Hide Create button` patch to default off ([#6067](https://github.com/ReVanced/revanced-patches/issues/6067)) ([19949e1](19949e1695))
* **X / Twitter:** Remove non functional and obsolete patch `Open links with app chooser` ([#6033](https://github.com/ReVanced/revanced-patches/issues/6033)) ([673609c](673609c2aa))
* **YouTube - Force original audio:** Change patch to default on ([#6070](https://github.com/ReVanced/revanced-patches/issues/6070)) ([bd4ba2d](bd4ba2dae8))
* **YouTube - Force original language:** Resolve some videos using Swedish audio track ([9d67316](9d6731660b))
* **YouTube - Hide end screen cards:** Hide new type of end screen card ([#6027](https://github.com/ReVanced/revanced-patches/issues/6027)) ([76b0364](76b0364c5b))
* **YouTube - Spoof video streams:** Add "Allow Android VR AV1" setting ([#6071](https://github.com/ReVanced/revanced-patches/issues/6071)) ([f03256c](f03256c471))
* **YouTube - Spoof video streams:** Do not allow VR AV1 if "Force AVC" is enabled ([7afeaeb](7afeaebb5c))
* **YouTube - Spoof video streams:** Resolve playback dropping frames ([#6051](https://github.com/ReVanced/revanced-patches/issues/6051)) ([a62ee43](a62ee43441))
* **YouTube Music - GmsCore support:** Handle sharing links to certain apps such as Instagram ([#6026](https://github.com/ReVanced/revanced-patches/issues/6026)) ([328234f](328234f39a))
* **YouTube Music - Hide cast button:** Fix patching error ([28799a5](28799a548a))
* **YouTube Music - Hide cast button:** Resolve button not hiding ([7817885](7817885cff))
* **YouTube:** Resolve UI components not hiding for some users ([#6054](https://github.com/ReVanced/revanced-patches/issues/6054)) ([6b26346](6b26346914))

### Features

* **Custom branding:** Add in-app settings to change icon and name ([#6059](https://github.com/ReVanced/revanced-patches/issues/6059)) ([a50f3b5](a50f3b5177))
* **Instagram:** Add `Custom share domain` patch ([#5998](https://github.com/ReVanced/revanced-patches/issues/5998)) ([20c4131](20c413120b))
* **Instagram:** Add `Enable developer menu` patch ([#6043](https://github.com/ReVanced/revanced-patches/issues/6043)) ([2154d89](2154d89242))
* **Instagram:** Add `Open links externally` patch ([#6012](https://github.com/ReVanced/revanced-patches/issues/6012)) ([08e8ead](08e8ead04f))
* **Instagram:** Add `Sanitize sharing links` patch ([#5986](https://github.com/ReVanced/revanced-patches/issues/5986)) ([963a4ef](963a4ef43f))
* **Viber:** Add `Hide navigation buttons` patch ([#5991](https://github.com/ReVanced/revanced-patches/issues/5991)) ([5cb46c4](5cb46c4e91))
* **YouTube Music:** Add `Custom branding` patch ([#6007](https://github.com/ReVanced/revanced-patches/issues/6007)) ([4c8b56f](4c8b56f546))
* **YouTube Music:** Add `Force original audio` patch ([#6036](https://github.com/ReVanced/revanced-patches/issues/6036)) ([d0d53d1](d0d53d109e))
2025-10-08 06:01:45 +00:00
LisoUseInAIKyrios
143dcef2b8 chore: Merge branch dev to main (#6015) 2025-10-08 09:57:48 +04:00
github-actions[bot]
dbfc5be464 chore: Sync translations (#6078) 2025-10-08 09:54:53 +04:00
LisoUseInAIKyrios
0fe545cad6 chore: Add links to the ReVanced brand guidelines 2025-10-08 09:47:27 +04:00
semantic-release-bot
feca17be68 chore: Release v5.42.0-dev.19 [skip ci]
# [5.42.0-dev.19](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.18...v5.42.0-dev.19) (2025-10-07)

### Bug Fixes

* **YouTube - Spoof video streams:** Do not allow VR AV1 if "Force AVC" is enabled ([7afeaeb](7afeaebb5c))
2025-10-07 20:37:44 +00:00
LisoUseInAIKyrios
7afeaebb5c fix(YouTube - Spoof video streams): Do not allow VR AV1 if "Force AVC" is enabled 2025-10-08 00:34:45 +04:00
github-actions[bot]
60a581a632 chore: Sync translations (#6077) 2025-10-08 00:30:56 +04:00
LisoUseInAIKyrios
104d096ada chore: Change brand name to untranslatable 2025-10-07 23:53:51 +04:00
semantic-release-bot
19dcbd8efb chore: Release v5.42.0-dev.18 [skip ci]
# [5.42.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.17...v5.42.0-dev.18) (2025-10-07)

### Features

* **Custom branding:** Add in-app settings to change icon and name ([#6059](https://github.com/ReVanced/revanced-patches/issues/6059)) ([a50f3b5](a50f3b5177))
2025-10-07 19:25:25 +00:00
MarcaD
a50f3b5177 feat(Custom branding): Add in-app settings to change icon and name (#6059)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-10-07 23:21:12 +04:00
semantic-release-bot
64d22a9c31 chore: Release v5.42.0-dev.17 [skip ci]
# [5.42.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.16...v5.42.0-dev.17) (2025-10-07)

### Bug Fixes

* **YouTube - Force original audio:** Change patch to default on ([#6070](https://github.com/ReVanced/revanced-patches/issues/6070)) ([bd4ba2d](bd4ba2dae8))
2025-10-07 15:46:50 +00:00
LisoUseInAIKyrios
bd4ba2dae8 fix(YouTube - Force original audio): Change patch to default on (#6070) 2025-10-07 19:41:32 +04:00
github-actions[bot]
f51b260d1d chore: Sync translations (#6073) 2025-10-07 19:40:18 +04:00
LisoUseInAIKyrios
63be54dd09 chore: Remove unneeded binary compatibility for code that was never released to main 2025-10-07 19:39:54 +04:00
semantic-release-bot
bb222d7a26 chore: Release v5.42.0-dev.16 [skip ci]
# [5.42.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.15...v5.42.0-dev.16) (2025-10-07)

### Bug Fixes

* **YouTube - Spoof video streams:** Add "Allow Android VR AV1" setting ([#6071](https://github.com/ReVanced/revanced-patches/issues/6071)) ([f03256c](f03256c471))
2025-10-07 15:19:06 +00:00
LisoUseInAIKyrios
f03256c471 fix(YouTube - Spoof video streams): Add "Allow Android VR AV1" setting (#6071) 2025-10-07 19:15:37 +04:00
semantic-release-bot
fe16433f20 chore: Release v5.42.0-dev.15 [skip ci]
# [5.42.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.14...v5.42.0-dev.15) (2025-10-07)

### Features

* **Instagram:** Add `Enable developer menu` patch ([#6043](https://github.com/ReVanced/revanced-patches/issues/6043)) ([2154d89](2154d89242))
2025-10-07 12:42:11 +00:00
brosssh
2154d89242 feat(Instagram): Add Enable developer menu patch (#6043) 2025-10-07 16:37:20 +04:00
semantic-release-bot
277a8b6b47 chore: Release v5.42.0-dev.14 [skip ci]
# [5.42.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.13...v5.42.0-dev.14) (2025-10-07)

### Features

* **Instagram:** Add `Custom share domain` patch ([#5998](https://github.com/ReVanced/revanced-patches/issues/5998)) ([20c4131](20c413120b))
2025-10-07 11:45:19 +00:00
brosssh
20c413120b feat(Instagram): Add Custom share domain patch (#5998) 2025-10-07 15:40:37 +04:00
semantic-release-bot
5ed092bb7d chore: Release v5.42.0-dev.13 [skip ci]
# [5.42.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.12...v5.42.0-dev.13) (2025-10-07)

### Bug Fixes

* **Spotify:** Change `Hide Create button` patch to default off ([#6067](https://github.com/ReVanced/revanced-patches/issues/6067)) ([19949e1](19949e1695))
2025-10-07 07:14:51 +00:00
Dawid Krajcarz
19949e1695 fix(Spotify): Change Hide Create button patch to default off (#6067) 2025-10-07 11:11:41 +04:00
github-actions[bot]
ec0acc0f13 chore: Sync translations (#6069) 2025-10-07 11:11:18 +04:00
LisoUseInAIKyrios
a30a849e6e refactor: Extract shared patch names/descriptions (#6056) 2025-10-07 01:15:03 +04:00
semantic-release-bot
603025a122 chore: Release v5.42.0-dev.12 [skip ci]
# [5.42.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.11...v5.42.0-dev.12) (2025-10-03)

### Bug Fixes

* **Custom branding:** Update ReVanced logo ([#6049](https://github.com/ReVanced/revanced-patches/issues/6049)) ([9441e7a](9441e7acb4))

### Features

* **Instagram:** Add `Sanitize sharing links` patch ([#5986](https://github.com/ReVanced/revanced-patches/issues/5986)) ([963a4ef](963a4ef43f))
2025-10-03 07:25:03 +00:00
MarcaD
9441e7acb4 fix(Custom branding): Update ReVanced logo (#6049) 2025-10-03 11:19:27 +04:00
brosssh
963a4ef43f feat(Instagram): Add Sanitize sharing links patch (#5986)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-10-03 11:19:01 +04:00
semantic-release-bot
0acba30245 chore: Release v5.42.0-dev.11 [skip ci]
# [5.42.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.10...v5.42.0-dev.11) (2025-10-03)

### Bug Fixes

* **YouTube:** Resolve UI components not hiding for some users ([#6054](https://github.com/ReVanced/revanced-patches/issues/6054)) ([6b26346](6b26346914))
2025-10-03 06:57:20 +00:00
LisoUseInAIKyrios
6b26346914 fix(YouTube): Resolve UI components not hiding for some users (#6054) 2025-10-03 10:54:44 +04:00
github-actions[bot]
b1511c732d chore: Sync translations (#6055) 2025-10-03 10:53:07 +04:00
semantic-release-bot
26117e744c chore: Release v5.42.0-dev.10 [skip ci]
# [5.42.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.9...v5.42.0-dev.10) (2025-10-02)

### Bug Fixes

* **YouTube - Spoof video streams:** Resolve playback dropping frames ([#6051](https://github.com/ReVanced/revanced-patches/issues/6051)) ([a62ee43](a62ee43441))
2025-10-02 20:09:02 +00:00
LisoUseInAIKyrios
a62ee43441 fix(YouTube - Spoof video streams): Resolve playback dropping frames (#6051) 2025-10-03 00:05:23 +04:00
LisoUseInAIKyrios
6a799110d7 refactor(YouTube - Spoof video streams): Add 'supportsMultiAudioTracks' field 2025-10-01 22:37:10 +04:00
dependabot[bot]
aec17b93f7 chore(deps): Bump com.google.guava:guava from 33.4.0-jre to 33.5.0-jre (#6042) 2025-10-01 22:18:24 +04:00
dependabot[bot]
e7a1706be4 chore(deps): Bump actions/setup-node from 4 to 5 (#6038) 2025-10-01 22:16:40 +04:00
dependabot[bot]
9469604fe0 chore(deps-dev): Bump semantic-release from 24.2.7 to 24.2.9 (#6040) 2025-10-01 22:15:52 +04:00
semantic-release-bot
1a3a12df1a chore: Release v5.42.0-dev.9 [skip ci]
# [5.42.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.8...v5.42.0-dev.9) (2025-10-01)

### Bug Fixes

* **Custom branding:** Update ReVanced logo sizing ([#6029](https://github.com/ReVanced/revanced-patches/issues/6029)) ([ae4b947](ae4b9474d3))
2025-10-01 17:33:14 +00:00
MarcaD
ae4b9474d3 fix(Custom branding): Update ReVanced logo sizing (#6029) 2025-10-01 21:29:05 +04:00
github-actions[bot]
83ccd9d3f1 chore: Sync translations (#6037) 2025-10-01 19:04:51 +04:00
semantic-release-bot
526c7c05e2 chore: Release v5.42.0-dev.8 [skip ci]
# [5.42.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.7...v5.42.0-dev.8) (2025-10-01)

### Bug Fixes

* **YouTube - Force original language:** Resolve some videos using Swedish audio track ([9d67316](9d6731660b))

### Features

* **YouTube Music:** Add `Force original audio` patch ([#6036](https://github.com/ReVanced/revanced-patches/issues/6036)) ([d0d53d1](d0d53d109e))
2025-10-01 15:04:39 +00:00
LisoUseInAIKyrios
d0d53d109e feat(YouTube Music): Add Force original audio patch (#6036) 2025-10-01 18:59:16 +04:00
LisoUseInAIKyrios
9d6731660b fix(YouTube - Force original language): Resolve some videos using Swedish audio track 2025-10-01 18:57:53 +04:00
semantic-release-bot
5a7e199162 chore: Release v5.42.0-dev.7 [skip ci]
# [5.42.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.6...v5.42.0-dev.7) (2025-10-01)

### Features

* **Instagram:** Add `Open links externally` patch ([#6012](https://github.com/ReVanced/revanced-patches/issues/6012)) ([08e8ead](08e8ead04f))
2025-10-01 06:25:01 +00:00
LisoUseInAIKyrios
0c662c8e3b chore(deps): Bump actions/checkout from 4 to 5 2025-10-01 10:19:09 +04:00
Swakshan
08e8ead04f feat(Instagram): Add Open links externally patch (#6012) 2025-10-01 10:17:19 +04:00
semantic-release-bot
d238a42708 chore: Release v5.42.0-dev.6 [skip ci]
# [5.42.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.5...v5.42.0-dev.6) (2025-09-30)

### Bug Fixes

* **X / Twitter:** Remove non functional and obsolete patch `Open links with app chooser` ([#6033](https://github.com/ReVanced/revanced-patches/issues/6033)) ([673609c](673609c2aa))
2025-09-30 20:09:49 +00:00
LisoUseInAIKyrios
673609c2aa fix(X / Twitter): Remove non functional and obsolete patch Open links with app chooser (#6033) 2025-10-01 00:06:40 +04:00
github-actions[bot]
5f1a485e8f chore: Sync translations (#6034) 2025-10-01 00:06:22 +04:00
LisoUseInAIKyrios
6961babee9 refactor(YouTube - Check watch history domain name resolution): Do not show redundant dialog cancel button 2025-09-30 12:11:06 +04:00
semantic-release-bot
328c9b6bbe chore: Release v5.42.0-dev.5 [skip ci]
# [5.42.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.4...v5.42.0-dev.5) (2025-09-28)

### Features

* **YouTube Music:** Add `Custom branding` patch ([#6007](https://github.com/ReVanced/revanced-patches/issues/6007)) ([4c8b56f](4c8b56f546))
2025-09-28 11:30:51 +00:00
MarcaD
4c8b56f546 feat(YouTube Music): Add Custom branding patch (#6007)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-09-28 15:26:12 +04:00
semantic-release-bot
1754023dd6 chore: Release v5.42.0-dev.4 [skip ci]
# [5.42.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.3...v5.42.0-dev.4) (2025-09-28)

### Bug Fixes

* **YouTube Music - GmsCore support:** Handle sharing links to certain apps such as Instagram ([#6026](https://github.com/ReVanced/revanced-patches/issues/6026)) ([328234f](328234f39a))
2025-09-28 10:34:39 +00:00
LisoUseInAIKyrios
328234f39a fix(YouTube Music - GmsCore support): Handle sharing links to certain apps such as Instagram (#6026) 2025-09-28 14:31:40 +04:00
github-actions[bot]
326953cfc3 chore: Sync translations (#6028) 2025-09-28 14:30:01 +04:00
semantic-release-bot
725d5dc974 chore: Release v5.42.0-dev.3 [skip ci]
# [5.42.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.2...v5.42.0-dev.3) (2025-09-28)

### Bug Fixes

* **YouTube - Hide end screen cards:** Hide new type of end screen card ([#6027](https://github.com/ReVanced/revanced-patches/issues/6027)) ([76b0364](76b0364c5b))
2025-09-28 10:25:37 +00:00
LisoUseInAIKyrios
76b0364c5b fix(YouTube - Hide end screen cards): Hide new type of end screen card (#6027) 2025-09-28 14:22:42 +04:00
semantic-release-bot
1cbff799ad chore: Release v5.42.0-dev.2 [skip ci]
# [5.42.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.42.0-dev.1...v5.42.0-dev.2) (2025-09-27)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Resolve app startup crash ([080a226](080a226614))
2025-09-27 19:57:01 +00:00
LisoUseInAIKyrios
080a226614 fix(Instagram - Hide navigation buttons): Resolve app startup crash 2025-09-27 23:53:35 +04:00
semantic-release-bot
2b71bd80c2 chore: Release v5.42.0-dev.1 [skip ci]
# [5.42.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.41.1-dev.2...v5.42.0-dev.1) (2025-09-27)

### Features

* **Viber:** Add `Hide navigation buttons` patch ([#5991](https://github.com/ReVanced/revanced-patches/issues/5991)) ([5cb46c4](5cb46c4e91))
2025-09-27 13:02:45 +00:00
Samo Hribar
5cb46c4e91 feat(Viber): Add Hide navigation buttons patch (#5991) 2025-09-27 16:59:51 +04:00
semantic-release-bot
52c369576d chore: Release v5.41.1-dev.2 [skip ci]
## [5.41.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.41.1-dev.1...v5.41.1-dev.2) (2025-09-27)

### Bug Fixes

* **YouTube Music - Hide cast button:** Fix patching error ([28799a5](28799a548a))
2025-09-27 12:35:45 +00:00
LisoUseInAIKyrios
28799a548a fix(YouTube Music - Hide cast button): Fix patching error 2025-09-27 16:31:22 +04:00
semantic-release-bot
1c80774d79 chore: Release v5.41.1-dev.1 [skip ci]
## [5.41.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.41.0...v5.41.1-dev.1) (2025-09-27)

### Bug Fixes

* **YouTube Music - Hide cast button:** Resolve button not hiding ([7817885](7817885cff))
2025-09-27 12:24:18 +00:00
LisoUseInAIKyrios
7817885cff fix(YouTube Music - Hide cast button): Resolve button not hiding 2025-09-27 16:21:32 +04:00
LisoUseInAIKyrios
9afe9afc67 chore(YouTube): Fix patch description 2025-09-27 11:53:21 +04:00
semantic-release-bot
3a8091ae00 chore: Release v5.41.0 [skip ci]
# [5.41.0](https://github.com/ReVanced/revanced-patches/compare/v5.40.0...v5.41.0) (2025-09-27)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Remove button based on name ([#5971](https://github.com/ReVanced/revanced-patches/issues/5971)) ([6fa4043](6fa404331b))
* **Instagram - Limit feed to followed profiles:** Preserve favorites feed ([#5963](https://github.com/ReVanced/revanced-patches/issues/5963)) ([ef51401](ef514017f4))
* **TikTok:** Show correct dialog restart text, use correct font color for non-dark mode ([d1a1293](d1a12930c3))
* **Twitch - Settings:** Fix missing style resources ([#5970](https://github.com/ReVanced/revanced-patches/issues/5970)) ([8c22995](8c229954d7))
* **YouTube - Hide Shorts components:** Fix "Hide preview comment" ([#5990](https://github.com/ReVanced/revanced-patches/issues/5990)) ([dd4e2cd](dd4e2cd085))
* **YouTube - Return YouTube Dislike:** Do not show error toast if API returns 401 status ([#5949](https://github.com/ReVanced/revanced-patches/issues/5949)) ([58d088a](58d088ab30))
* **YouTube - Settings:** Handle on screen back swipe gesture ([#6002](https://github.com/ReVanced/revanced-patches/issues/6002)) ([6f92b6c](6f92b6c50b))
* **YouTube - Settings:** Use an overlay to show search results ([#5806](https://github.com/ReVanced/revanced-patches/issues/5806)) ([ece8076](ece8076f7c))
* **YouTube - SponsorBlock:** Show category color dot in voting dialog menu ([4be00d0](4be00d09b7))
* **YouTube - SponsorBlock:** Show category color in create new segment menu ([#5987](https://github.com/ReVanced/revanced-patches/issues/5987)) ([ffd933c](ffd933c673))
* **YouTube - Spoof video streams:** Update client side effects summary text ([a0a62dd](a0a62ddad2))

### Features

* **Tumblr:** Add `Disable Tumblr TV` patch ([#5959](https://github.com/ReVanced/revanced-patches/issues/5959)) ([212418b](212418b8db))
* **YouTube - Hide layout components:** Add "Hide Emoji and Timestamp buttons" setting ([#5992](https://github.com/ReVanced/revanced-patches/issues/5992)) ([2b555f6](2b555f67f0))
* **YouTube - Hide layout components:** Add "Hide view count" and "Hide upload time" settings ([#5983](https://github.com/ReVanced/revanced-patches/issues/5983)) ([7a37d85](7a37d858fb))
* **YouTube - Loop video:** Add player button to change loop video state ([#5961](https://github.com/ReVanced/revanced-patches/issues/5961)) ([dfb5407](dfb5407e67))
* **YouTube - Spoof app version:** Add spoof target `20.05.46` that fixes transcript functionality ([5823f0e](5823f0e982))
* **YouTube Music:** Add `Check watch history domain name resolution` ([#5979](https://github.com/ReVanced/revanced-patches/issues/5979)) ([8af70fe](8af70fe2d1))
* **YouTube Music:** Add `Sanitize sharing links` patch ([#5952](https://github.com/ReVanced/revanced-patches/issues/5952)) ([45c1ee8](45c1ee8a12))
* **YouTube Music:** Add `Theme` patch ([#5984](https://github.com/ReVanced/revanced-patches/issues/5984)) ([3bd76d6](3bd76d60d6))
* **YouTube:** Add `Disable video codecs` patch ([#5981](https://github.com/ReVanced/revanced-patches/issues/5981)) ([bfbffbd](bfbffbd1f5))
2025-09-27 07:21:33 +00:00
LisoUseInAIKyrios
6192ece114 chore: Merge branch dev to main (#5950) 2025-09-27 11:17:09 +04:00
github-actions[bot]
5d9971444e chore: Sync translations (#6014) 2025-09-27 11:15:04 +04:00
semantic-release-bot
cdfa75dd5a chore: Release v5.41.0-dev.18 [skip ci]
# [5.41.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.17...v5.41.0-dev.18) (2025-09-26)

### Bug Fixes

* **YouTube - Settings:** Handle on screen back swipe gesture ([#6002](https://github.com/ReVanced/revanced-patches/issues/6002)) ([6f92b6c](6f92b6c50b))
2025-09-26 15:05:03 +00:00
LisoUseInAIKyrios
6f92b6c50b fix(YouTube - Settings): Handle on screen back swipe gesture (#6002) 2025-09-26 19:00:12 +04:00
github-actions[bot]
1e023fa1f3 chore: Sync translations (#6010) 2025-09-26 18:59:48 +04:00
semantic-release-bot
00477bfebc chore: Release v5.41.0-dev.17 [skip ci]
# [5.41.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.16...v5.41.0-dev.17) (2025-09-26)

### Bug Fixes

* **YouTube - SponsorBlock:** Show category color dot in voting dialog menu ([4be00d0](4be00d09b7))
2025-09-26 08:28:43 +00:00
LisoUseInAIKyrios
4be00d09b7 fix(YouTube - SponsorBlock): Show category color dot in voting dialog menu 2025-09-26 12:25:17 +04:00
github-actions[bot]
50aca3314f chore: Sync translations (#6005) 2025-09-26 12:24:08 +04:00
LisoUseInAIKyrios
15a7e540de refactor(YouTube - Miniplayer): Change ReVanced settings that are now YouTube default on into "Disable" style settings (#6003) 2025-09-26 12:14:20 +04:00
semantic-release-bot
041f7e0140 chore: Release v5.41.0-dev.16 [skip ci]
# [5.41.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.15...v5.41.0-dev.16) (2025-09-26)

### Features

* **YouTube Music:** Add `Theme` patch ([#5984](https://github.com/ReVanced/revanced-patches/issues/5984)) ([3bd76d6](3bd76d60d6))
2025-09-26 05:33:20 +00:00
MarcaD
3bd76d60d6 feat(YouTube Music): Add Theme patch (#5984)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-09-26 09:29:11 +04:00
github-actions[bot]
1587178ff8 chore: Sync translations (#6001) 2025-09-26 09:28:45 +04:00
semantic-release-bot
8a69240d66 chore: Release v5.41.0-dev.15 [skip ci]
# [5.41.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.14...v5.41.0-dev.15) (2025-09-25)

### Features

* **YouTube - Hide layout components:** Add "Hide view count" and "Hide upload time" settings ([#5983](https://github.com/ReVanced/revanced-patches/issues/5983)) ([7a37d85](7a37d858fb))
2025-09-25 12:47:40 +00:00
viSapio
7a37d858fb feat(YouTube - Hide layout components): Add "Hide view count" and "Hide upload time" settings (#5983) 2025-09-25 16:43:59 +04:00
github-actions[bot]
0ed7067459 chore: Sync translations (#5996) 2025-09-25 16:38:32 +04:00
LisoUseInAIKyrios
6102644194 chore(YouTube): Adjust patch strings 2025-09-25 00:32:15 +04:00
semantic-release-bot
a89556a017 chore: Release v5.41.0-dev.14 [skip ci]
# [5.41.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.13...v5.41.0-dev.14) (2025-09-24)

### Features

* **YouTube - Hide layout components:** Add "Hide Emoji and Timestamp buttons" setting ([#5992](https://github.com/ReVanced/revanced-patches/issues/5992)) ([2b555f6](2b555f67f0))
2025-09-24 20:21:22 +00:00
ILoveOpenSourceApplications
2b555f67f0 feat(YouTube - Hide layout components): Add "Hide Emoji and Timestamp buttons" setting (#5992) 2025-09-25 00:17:08 +04:00
semantic-release-bot
fb87199514 chore: Release v5.41.0-dev.13 [skip ci]
# [5.41.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.12...v5.41.0-dev.13) (2025-09-24)

### Bug Fixes

* **YouTube - Hide Shorts components:** Fix "Hide preview comment" ([#5990](https://github.com/ReVanced/revanced-patches/issues/5990)) ([dd4e2cd](dd4e2cd085))
2025-09-24 19:44:03 +00:00
ILoveOpenSourceApplications
dd4e2cd085 fix(YouTube - Hide Shorts components): Fix "Hide preview comment" (#5990) 2025-09-24 23:41:15 +04:00
semantic-release-bot
fadc66816d chore: Release v5.41.0-dev.12 [skip ci]
# [5.41.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.11...v5.41.0-dev.12) (2025-09-24)

### Bug Fixes

* **YouTube - SponsorBlock:** Show category color in create new segment menu ([#5987](https://github.com/ReVanced/revanced-patches/issues/5987)) ([ffd933c](ffd933c673))
2025-09-24 13:53:25 +00:00
MarcaD
ffd933c673 fix(YouTube - SponsorBlock): Show category color in create new segment menu (#5987) 2025-09-24 17:50:46 +04:00
github-actions[bot]
69883530b7 chore: Sync translations (#5989) 2025-09-24 17:49:49 +04:00
LisoUseInAIKyrios
39971291f3 chore: Fix typo 2025-09-24 15:56:29 +04:00
LisoUseInAIKyrios
51facf9321 chore(YouTube): Adjust UI dialog message 2025-09-24 09:46:37 +04:00
semantic-release-bot
b83d41ca88 chore: Release v5.41.0-dev.11 [skip ci]
# [5.41.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.10...v5.41.0-dev.11) (2025-09-23)

### Features

* **YouTube:** Add `Disable video codecs` patch ([#5981](https://github.com/ReVanced/revanced-patches/issues/5981)) ([bfbffbd](bfbffbd1f5))
2025-09-23 18:10:52 +00:00
LisoUseInAIKyrios
bfbffbd1f5 feat(YouTube): Add Disable video codecs patch (#5981) 2025-09-23 22:06:02 +04:00
semantic-release-bot
ee4755646b chore: Release v5.41.0-dev.10 [skip ci]
# [5.41.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.9...v5.41.0-dev.10) (2025-09-23)

### Bug Fixes

* **TikTok:** Show correct dialog restart text, use correct font color for non-dark mode ([d1a1293](d1a12930c3))
2025-09-23 17:46:05 +00:00
LisoUseInAIKyrios
d1a12930c3 fix(TikTok): Show correct dialog restart text, use correct font color for non-dark mode 2025-09-23 21:43:04 +04:00
semantic-release-bot
dfac836a8c chore: Release v5.41.0-dev.9 [skip ci]
# [5.41.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.8...v5.41.0-dev.9) (2025-09-23)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Remove button based on name ([#5971](https://github.com/ReVanced/revanced-patches/issues/5971)) ([6fa4043](6fa404331b))
2025-09-23 10:28:29 +00:00
brosssh
6fa404331b fix(Instagram - Hide navigation buttons): Remove button based on name (#5971) 2025-09-23 12:25:36 +02:00
semantic-release-bot
8bcb95adcd chore: Release v5.41.0-dev.8 [skip ci]
# [5.41.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.7...v5.41.0-dev.8) (2025-09-23)

### Features

* **YouTube Music:** Add `Check watch history domain name resolution` ([#5979](https://github.com/ReVanced/revanced-patches/issues/5979)) ([8af70fe](8af70fe2d1))
2025-09-23 09:38:14 +00:00
LisoUseInAIKyrios
8af70fe2d1 feat(YouTube Music): Add Check watch history domain name resolution (#5979) 2025-09-23 13:34:00 +04:00
semantic-release-bot
191b9169ff chore: Release v5.41.0-dev.7 [skip ci]
# [5.41.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.6...v5.41.0-dev.7) (2025-09-23)

### Features

* **Tumblr:** Add `Disable Tumblr TV` patch ([#5959](https://github.com/ReVanced/revanced-patches/issues/5959)) ([212418b](212418b8db))
2025-09-23 06:24:06 +00:00
Temm
212418b8db feat(Tumblr): Add Disable Tumblr TV patch (#5959) 2025-09-23 10:19:58 +04:00
github-actions[bot]
7dbc744be0 chore: Sync translations (#5978) 2025-09-23 10:18:20 +04:00
LisoUseInAIKyrios
150a3e7c60 chore(YouTube Music - GmsCore support): Add missing supported versions 2025-09-23 10:17:25 +04:00
semantic-release-bot
5027943470 chore: Release v5.41.0-dev.6 [skip ci]
# [5.41.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.5...v5.41.0-dev.6) (2025-09-22)

### Features

* **YouTube - Spoof app version:** Add spoof target `20.05.46` that fixes transcript functionality ([5823f0e](5823f0e982))
2025-09-22 18:04:18 +00:00
github-actions[bot]
fa9e590b3a chore: Sync translations (#5972) 2025-09-22 22:01:33 +04:00
LisoUseInAIKyrios
5823f0e982 feat(YouTube - Spoof app version): Add spoof target 20.05.46 that fixes transcript functionality 2025-09-22 22:01:14 +04:00
LisoUseInAIKyrios
f506a67e4a chore(YouTube): Drop 19.43.41
Playback speed has a patch error. Don't want to fix. Most users want the latest or the oldest app target, and don't care about anything in-between.
2025-09-22 21:57:42 +04:00
semantic-release-bot
ed6e1155f2 chore: Release v5.41.0-dev.5 [skip ci]
# [5.41.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.4...v5.41.0-dev.5) (2025-09-22)

### Bug Fixes

* **Twitch - Settings:** Fix missing style resources ([#5970](https://github.com/ReVanced/revanced-patches/issues/5970)) ([8c22995](8c229954d7))
2025-09-22 16:05:55 +00:00
MarcaD
8c229954d7 fix(Twitch - Settings): Fix missing style resources (#5970) 2025-09-22 20:02:34 +04:00
semantic-release-bot
c5eb88bbf6 chore: Release v5.41.0-dev.4 [skip ci]
# [5.41.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.3...v5.41.0-dev.4) (2025-09-22)

### Bug Fixes

* **Instagram - Limit feed to followed profiles:** Preserve favorites feed ([#5963](https://github.com/ReVanced/revanced-patches/issues/5963)) ([ef51401](ef514017f4))
2025-09-22 09:35:02 +00:00
brosssh
ef514017f4 fix(Instagram - Limit feed to followed profiles): Preserve favorites feed (#5963) 2025-09-22 13:32:30 +04:00
github-actions[bot]
c72d99518c chore: Sync translations (#5968) 2025-09-22 13:32:12 +04:00
semantic-release-bot
772df6eb73 chore: Release v5.41.0-dev.3 [skip ci]
# [5.41.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.2...v5.41.0-dev.3) (2025-09-22)

### Features

* **YouTube - Loop video:** Add player button to change loop video state ([#5961](https://github.com/ReVanced/revanced-patches/issues/5961)) ([dfb5407](dfb5407e67))
2025-09-22 08:57:43 +00:00
MarcaD
dfb5407e67 feat(YouTube - Loop video): Add player button to change loop video state (#5961) 2025-09-22 12:54:09 +04:00
semantic-release-bot
6d5f6ecdd2 chore: Release v5.41.0-dev.2 [skip ci]
# [5.41.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.41.0-dev.1...v5.41.0-dev.2) (2025-09-21)

### Bug Fixes

* **YouTube - Spoof video streams:** Update client side effects summary text ([a0a62dd](a0a62ddad2))
2025-09-21 19:45:41 +00:00
LisoUseInAIKyrios
a0a62ddad2 fix(YouTube - Spoof video streams): Update client side effects summary text 2025-09-21 23:41:38 +04:00
github-actions[bot]
512e50e892 chore: Sync translations (#5955) 2025-09-21 23:03:49 +04:00
semantic-release-bot
a2304c3310 chore: Release v5.41.0-dev.1 [skip ci]
# [5.41.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.40.1-dev.1...v5.41.0-dev.1) (2025-09-21)

### Features

* **YouTube Music:** Add `Sanitize sharing links` patch ([#5952](https://github.com/ReVanced/revanced-patches/issues/5952)) ([45c1ee8](45c1ee8a12))
2025-09-21 17:19:16 +00:00
LisoUseInAIKyrios
45c1ee8a12 feat(YouTube Music): Add Sanitize sharing links patch (#5952) 2025-09-21 21:14:19 +04:00
github-actions[bot]
74cdf550a5 chore: Sync translations (#5953) 2025-09-21 21:14:03 +04:00
semantic-release-bot
c36ea22975 chore: Release v5.40.1-dev.1 [skip ci]
## [5.40.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.40.0...v5.40.1-dev.1) (2025-09-21)

### Bug Fixes

* **YouTube - Return YouTube Dislike:** Do not show error toast if API returns 401 status ([#5949](https://github.com/ReVanced/revanced-patches/issues/5949)) ([58d088a](58d088ab30))
* **YouTube - Settings:** Use an overlay to show search results ([#5806](https://github.com/ReVanced/revanced-patches/issues/5806)) ([ece8076](ece8076f7c))
2025-09-21 13:25:05 +00:00
LisoUseInAIKyrios
58d088ab30 fix(YouTube - Return YouTube Dislike): Do not show error toast if API returns 401 status (#5949) 2025-09-21 17:20:13 +04:00
MarcaD
ece8076f7c fix(YouTube - Settings): Use an overlay to show search results (#5806)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-09-21 17:19:29 +04:00
semantic-release-bot
ebb446b22a chore: Release v5.40.0 [skip ci]
# [5.40.0](https://github.com/ReVanced/revanced-patches/compare/v5.39.0...v5.40.0) (2025-09-21)

### Bug Fixes

* **Instagram - Limit feed to followed profiles:** Change patch to default off ([767f1e3](767f1e3695))
* **Spoof video streams:** Resolve occasional playback stuttering ([5c7c8b5](5c7c8b5364))
* **YouTube - Force original audio:** Show UI setting summary if spoofing to Android Studio ([b7026b7](b7026b7086))
* **YouTube - Spoof video streams:** Add "Force original audio" disclaimer for Android Studio client ([f97d332](f97d33206b))
* **YouTube - Spoof video streams:** Add stream audio selector disclaimer for Android Studio client ([a8a4107](a8a410708d))

### Features

* **Instagram:** Add `Limit feed to followed profiles` patch ([#5908](https://github.com/ReVanced/revanced-patches/issues/5908)) ([8ba9a19](8ba9a19ade))
* **Viber - Hide ads:** Support latest app target ([#5863](https://github.com/ReVanced/revanced-patches/issues/5863)) ([e6cce85](e6cce85541))
* **YouTube - Hide video action buttons:** Add "Hide comments" button ([db796fb](db796fb883))
* **YouTube Music:** Add `Enable debugging` patch ([#5939](https://github.com/ReVanced/revanced-patches/issues/5939)) ([418f594](418f5945c2))
* **YouTube Music:** Add `Hide cast button` and `Navigation bar` patches ([#5934](https://github.com/ReVanced/revanced-patches/issues/5934)) ([651d358](651d358096))
* **YouTube Music:** Support version `8.10.52` ([#5941](https://github.com/ReVanced/revanced-patches/issues/5941)) ([01c0f1b](01c0f1bd1a))
* **YouTube:** Support version `20.14.43` ([#5940](https://github.com/ReVanced/revanced-patches/issues/5940)) ([f7f4a1b](f7f4a1b0f0))
2025-09-21 06:58:02 +00:00
LisoUseInAIKyrios
b44a369f59 chore: Merge branch dev to main (#5916) 2025-09-21 10:54:28 +04:00
github-actions[bot]
092a72c774 chore: Sync translations (#5946) 2025-09-21 10:52:29 +04:00
LisoUseInAIKyrios
6330773bfc chore(YouTube Music): Add missing target version 2025-09-21 10:50:35 +04:00
LisoUseInAIKyrios
43dbb4710b docs: Add new issue links to the FAQ and troubleshooting guide (#5929) 2025-09-21 10:42:02 +04:00
LisoUseInAIKyrios
966727ca2d chore(YouTube Music): Use string language similar to YouTube 2025-09-20 23:31:01 +04:00
semantic-release-bot
1f371c8156 chore: Release v5.40.0-dev.11 [skip ci]
# [5.40.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.10...v5.40.0-dev.11) (2025-09-20)

### Bug Fixes

* **YouTube - Spoof video streams:** Add stream audio selector disclaimer for Android Studio client ([a8a4107](a8a410708d))
2025-09-20 19:20:13 +00:00
LisoUseInAIKyrios
a8a410708d fix(YouTube - Spoof video streams): Add stream audio selector disclaimer for Android Studio client 2025-09-20 23:15:41 +04:00
semantic-release-bot
7651ef0881 chore: Release v5.40.0-dev.10 [skip ci]
# [5.40.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.9...v5.40.0-dev.10) (2025-09-20)

### Bug Fixes

* **YouTube - Spoof video streams:** Add "Force original audio" disclaimer for Android Studio client ([f97d332](f97d33206b))
2025-09-20 18:13:06 +00:00
LisoUseInAIKyrios
f97d33206b fix(YouTube - Spoof video streams): Add "Force original audio" disclaimer for Android Studio client 2025-09-20 22:08:50 +04:00
semantic-release-bot
3d986e6716 chore: Release v5.40.0-dev.9 [skip ci]
# [5.40.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.8...v5.40.0-dev.9) (2025-09-20)

### Features

* **YouTube Music:** Support version `8.10.52` ([#5941](https://github.com/ReVanced/revanced-patches/issues/5941)) ([01c0f1b](01c0f1bd1a))
2025-09-20 16:12:57 +00:00
LisoUseInAIKyrios
01c0f1bd1a feat(YouTube Music): Support version 8.10.52 (#5941) 2025-09-20 20:09:52 +04:00
github-actions[bot]
4178e8a64f chore: Sync translations (#5943) 2025-09-20 20:09:07 +04:00
semantic-release-bot
7e1bb8f3c7 chore: Release v5.40.0-dev.8 [skip ci]
# [5.40.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.7...v5.40.0-dev.8) (2025-09-20)

### Features

* **YouTube:** Support version `20.14.43` ([#5940](https://github.com/ReVanced/revanced-patches/issues/5940)) ([f7f4a1b](f7f4a1b0f0))
2025-09-20 15:33:42 +00:00
LisoUseInAIKyrios
f7f4a1b0f0 feat(YouTube): Support version 20.14.43 (#5940) 2025-09-20 19:30:05 +04:00
semantic-release-bot
e89660d234 chore: Release v5.40.0-dev.7 [skip ci]
# [5.40.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.6...v5.40.0-dev.7) (2025-09-20)

### Features

* **YouTube - Hide video action buttons:** Add "Hide comments" button ([db796fb](db796fb883))
2025-09-20 15:03:00 +00:00
LisoUseInAIKyrios
db796fb883 feat(YouTube - Hide video action buttons): Add "Hide comments" button
Button is only shown when using YouTube 20.14+ and the video information area is collapsed to a compact state
2025-09-20 19:00:00 +04:00
LisoUseInAIKyrios
6bb8bad8d7 chore(YouTube Music): Fix fingerprint typo, change hide cast button to default off 2025-09-20 18:03:41 +04:00
semantic-release-bot
aa1fb41ad8 chore: Release v5.40.0-dev.6 [skip ci]
# [5.40.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.5...v5.40.0-dev.6) (2025-09-20)

### Features

* **YouTube Music:** Add `Enable debugging` patch ([#5939](https://github.com/ReVanced/revanced-patches/issues/5939)) ([418f594](418f5945c2))
2025-09-20 12:37:33 +00:00
LisoUseInAIKyrios
418f5945c2 feat(YouTube Music): Add Enable debugging patch (#5939) 2025-09-20 16:33:03 +04:00
github-actions[bot]
e26c971067 chore: Sync translations (#5942) 2025-09-20 16:32:50 +04:00
semantic-release-bot
eb1d07fd98 chore: Release v5.40.0-dev.5 [skip ci]
# [5.40.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.4...v5.40.0-dev.5) (2025-09-20)

### Features

* **YouTube Music:** Add `Hide cast button` and `Navigation bar` patches ([#5934](https://github.com/ReVanced/revanced-patches/issues/5934)) ([651d358](651d358096))
2025-09-20 11:30:04 +00:00
MarcaD
651d358096 feat(YouTube Music): Add Hide cast button and Navigation bar patches (#5934) 2025-09-20 15:26:14 +04:00
semantic-release-bot
0d15c5f338 chore: Release v5.40.0-dev.4 [skip ci]
# [5.40.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.3...v5.40.0-dev.4) (2025-09-20)

### Bug Fixes

* **Spoof video streams:** Resolve occasional playback stuttering ([5c7c8b5](5c7c8b5364))
2025-09-20 10:39:29 +00:00
LisoUseInAIKyrios
5c7c8b5364 fix(Spoof video streams): Resolve occasional playback stuttering
Code adapted from:
2cf9db66ac
50d9c60374
2025-09-20 14:36:15 +04:00
semantic-release-bot
729997ec3e chore: Release v5.40.0-dev.3 [skip ci]
# [5.40.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.2...v5.40.0-dev.3) (2025-09-19)

### Bug Fixes

* **Instagram - Limit feed to followed profiles:** Change patch to default off ([767f1e3](767f1e3695))
2025-09-19 15:43:08 +00:00
LisoUseInAIKyrios
767f1e3695 fix(Instagram - Limit feed to followed profiles): Change patch to default off
Co-authored-by: brosssh <44944126+brosssh@users.noreply.github.com>
2025-09-19 19:40:32 +04:00
github-actions[bot]
7857876551 chore: Sync translations (#5933) 2025-09-19 19:40:03 +04:00
semantic-release-bot
04057c6e56 chore: Release v5.40.0-dev.2 [skip ci]
# [5.40.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.1...v5.40.0-dev.2) (2025-09-18)

### Features

* **Instagram:** Add `Limit feed to followed profiles` patch ([#5908](https://github.com/ReVanced/revanced-patches/issues/5908)) ([8ba9a19](8ba9a19ade))
2025-09-18 06:16:27 +00:00
brosssh
8ba9a19ade feat(Instagram): Add Limit feed to followed profiles patch (#5908) 2025-09-18 10:13:46 +04:00
LisoUseInAIKyrios
6862200a28 chore: Fix api dump 2025-09-17 23:42:11 +04:00
semantic-release-bot
dfff3d7c0a chore: Release v5.40.0-dev.1 [skip ci]
# [5.40.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.39.1-dev.1...v5.40.0-dev.1) (2025-09-17)

### Features

* **Viber - Hide ads:** Support latest app target ([#5863](https://github.com/ReVanced/revanced-patches/issues/5863)) ([e6cce85](e6cce85541))
2025-09-17 17:54:19 +00:00
Samo Hribar
e6cce85541 feat(Viber - Hide ads): Support latest app target (#5863) 2025-09-17 21:51:33 +04:00
github-actions[bot]
8502eb8eac chore: Sync translations (#5918) 2025-09-17 21:51:15 +04:00
semantic-release-bot
0652c56d0d chore: Release v5.39.1-dev.1 [skip ci]
## [5.39.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.39.0...v5.39.1-dev.1) (2025-09-17)

### Bug Fixes

* **YouTube - Force original audio:** Show UI setting summary if spoofing to Android Studio ([b7026b7](b7026b7086))
2025-09-17 16:18:22 +00:00
LisoUseInAIKyrios
b7026b7086 fix(YouTube - Force original audio): Show UI setting summary if spoofing to Android Studio 2025-09-17 20:13:44 +04:00
semantic-release-bot
fa4f422a15 chore: Release v5.39.0 [skip ci]
# [5.39.0](https://github.com/ReVanced/revanced-patches/compare/v5.38.0...v5.39.0) (2025-09-17)

### Bug Fixes

* **YouTube - Spoof video streams:** Do not use Android Creator for livestreams ([cbe576b](cbe576bc38))
* **YouTube - Spoof video streams:** Show Android Studio in spoof stream menu ([c9f741e](c9f741e616))
* **YouTube Music - Spoof video streams:** Remove iPadOS client ([7eeffd3](7eeffd3392))

### Features

* **YouTube - Hide video action buttons:** Add "Hide Shop button" setting ([a84db7b](a84db7be7f))
2025-09-17 09:15:36 +00:00
LisoUseInAIKyrios
38e0cbd724 chore: Merge branch dev to main (#5907) 2025-09-17 13:12:21 +04:00
semantic-release-bot
0bdebd927d chore: Release v5.39.0-dev.2 [skip ci]
# [5.39.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.39.0-dev.1...v5.39.0-dev.2) (2025-09-17)

### Bug Fixes

* **YouTube - Spoof video streams:** Show Android Studio in spoof stream menu ([c9f741e](c9f741e616))
2025-09-17 09:01:12 +00:00
github-actions[bot]
3eac25cf7f chore: Sync translations (#5914) 2025-09-17 12:56:47 +04:00
LisoUseInAIKyrios
c9f741e616 fix(YouTube - Spoof video streams): Show Android Studio in spoof stream menu 2025-09-17 12:54:52 +04:00
semantic-release-bot
cba44ccfc8 chore: Release v5.39.0-dev.1 [skip ci]
# [5.39.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.38.1-dev.2...v5.39.0-dev.1) (2025-09-17)

### Features

* **YouTube - Hide video action buttons:** Add "Hide Shop button" setting ([a84db7b](a84db7be7f))
2025-09-17 07:19:06 +00:00
LisoUseInAIKyrios
a84db7be7f feat(YouTube - Hide video action buttons): Add "Hide Shop button" setting 2025-09-17 11:14:24 +04:00
semantic-release-bot
2520129ace chore: Release v5.38.1-dev.2 [skip ci]
## [5.38.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.38.1-dev.1...v5.38.1-dev.2) (2025-09-16)

### Bug Fixes

* **YouTube Music - Spoof video streams:** Remove iPadOS client ([7eeffd3](7eeffd3392))
2025-09-16 21:49:36 +00:00
LisoUseInAIKyrios
7eeffd3392 fix(YouTube Music - Spoof video streams): Remove iPadOS client 2025-09-17 01:44:48 +04:00
LisoUseInAIKyrios
6c3391164e chore: Remove spoof stream data migration since iPadOS can cause 1 minute playback failure for users in some regions 2025-09-16 23:44:01 +04:00
semantic-release-bot
0b8b46c73e chore: Release v5.38.1-dev.1 [skip ci]
## [5.38.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.38.0...v5.38.1-dev.1) (2025-09-16)

### Bug Fixes

* **YouTube - Spoof video streams:** Do not use Android Creator for livestreams ([cbe576b](cbe576bc38))
2025-09-16 19:27:03 +00:00
LisoUseInAIKyrios
cbe576bc38 fix(YouTube - Spoof video streams): Do not use Android Creator for livestreams 2025-09-16 23:24:07 +04:00
github-actions[bot]
3a29f2a805 chore: Sync translations (#5909) 2025-09-16 23:21:01 +04:00
LisoUseInAIKyrios
50069c7e05 chore: Fix merge typo 2025-09-16 17:26:48 +04:00
semantic-release-bot
2e9c9dc244 chore: Release v5.38.0 [skip ci]
# [5.38.0](https://github.com/ReVanced/revanced-patches/compare/v5.37.0...v5.38.0) (2025-09-16)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Support v397.1.0.52.81 ([#5855](https://github.com/ReVanced/revanced-patches/issues/5855)) ([f11d1ef](f11d1ef990))
* **Spoof video streams:** Remove Android TV and iOS TV clients, add experimental VisionOS, add temporary fix for `Force original audio` to work with any spoof client ([#5861](https://github.com/ReVanced/revanced-patches/issues/5861)) ([abe3943](abe3943f98))
* **YouTube - Spoof video streams:** Show settings summary if `Force original audio` is enabled ([3776dda](3776dda710))
* **YouTube Music - Spoof video streams:** Fix playback issues when using a cellular network ([fa04c8e](fa04c8eecf))
* **YouTube Music:** Use correct light/dark mode settings UI ([1475643](1475643f84))

### Features

* **Instagram:** Add `Hide explore feed` patch ([#5856](https://github.com/ReVanced/revanced-patches/issues/5856)) ([1d65887](1d65887e01))
* **YouTube - Spoof video streams:** Add iPadOS client ([2726231](2726231404))
* **YouTube Music:** Add `Settings` patch ([#5838](https://github.com/ReVanced/revanced-patches/issues/5838)) ([5e20bd8](5e20bd80f1))
2025-09-16 13:01:23 +00:00
LisoUseInAIKyrios
56166896d9 chore: Merge branch dev to main (#5857) 2025-09-16 16:57:55 +04:00
semantic-release-bot
b4c695b1d5 chore: Release v5.38.0-dev.5 [skip ci]
# [5.38.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.4...v5.38.0-dev.5) (2025-09-16)

### Bug Fixes

* **YouTube Music:** Use correct light/dark mode settings UI ([1475643](1475643f84))
2025-09-16 12:34:52 +00:00
LisoUseInAIKyrios
1475643f84 fix(YouTube Music): Use correct light/dark mode settings UI 2025-09-16 16:31:04 +04:00
github-actions[bot]
9a7179f9cf chore: Sync translations (#5906) 2025-09-16 16:29:53 +04:00
semantic-release-bot
6fb94a7a41 chore: Release v5.38.0-dev.4 [skip ci]
# [5.38.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.3...v5.38.0-dev.4) (2025-09-16)

### Bug Fixes

* **YouTube - Spoof video streams:** Show settings summary if `Force original audio` is enabled ([3776dda](3776dda710))
2025-09-16 12:05:23 +00:00
LisoUseInAIKyrios
3776dda710 fix(YouTube - Spoof video streams): Show settings summary if Force original audio is enabled 2025-09-16 15:59:32 +04:00
LisoUseInAIKyrios
f88b3a5162 refactor(YouTube - Spoof video streams): Adjust preferred client order 2025-09-16 15:40:55 +04:00
semantic-release-bot
0eeaf7ad67 chore: Release v5.38.0-dev.3 [skip ci]
# [5.38.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.2...v5.38.0-dev.3) (2025-09-16)

### Features

* **YouTube - Spoof video streams:** Add iPadOS client ([2726231](2726231404))
2025-09-16 11:36:54 +00:00
LisoUseInAIKyrios
2726231404 feat(YouTube - Spoof video streams): Add iPadOS client 2025-09-16 15:33:55 +04:00
github-actions[bot]
9f0558e494 chore: Sync translations (#5905) 2025-09-16 15:11:04 +04:00
semantic-release-bot
01f7bc9f8d chore: Release v5.38.0-dev.2 [skip ci]
# [5.38.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.1...v5.38.0-dev.2) (2025-09-16)

### Features

* **YouTube Music:** Add `Settings` patch ([#5838](https://github.com/ReVanced/revanced-patches/issues/5838)) ([5e20bd8](5e20bd80f1))
2025-09-16 06:57:43 +00:00
MarcaD
5e20bd80f1 feat(YouTube Music): Add Settings patch (#5838)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-09-16 10:53:49 +04:00
semantic-release-bot
f304c178e2 chore: Release v5.38.0-dev.1 [skip ci]
# [5.38.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.3...v5.38.0-dev.1) (2025-09-15)

### Features

* **Instagram:** Add `Hide explore feed` patch ([#5856](https://github.com/ReVanced/revanced-patches/issues/5856)) ([1d65887](1d65887e01))
2025-09-15 19:30:52 +00:00
brosssh
1d65887e01 feat(Instagram): Add Hide explore feed patch (#5856) 2025-09-15 23:28:01 +04:00
github-actions[bot]
6b6eea8414 chore: Sync translations (#5864) 2025-09-15 23:26:07 +04:00
semantic-release-bot
1db131e90e chore: Release v5.37.1-dev.3 [skip ci]
## [5.37.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.2...v5.37.1-dev.3) (2025-09-15)

### Bug Fixes

* **Spoof video streams:** Remove Android TV and iOS TV clients, add experimental VisionOS, add temporary fix for `Force original audio` to work with any spoof client ([#5861](https://github.com/ReVanced/revanced-patches/issues/5861)) ([abe3943](abe3943f98))
2025-09-15 17:02:01 +00:00
LisoUseInAIKyrios
abe3943f98 fix(Spoof video streams): Remove Android TV and iOS TV clients, add experimental VisionOS, add temporary fix for Force original audio to work with any spoof client (#5861) 2025-09-15 20:58:56 +04:00
semantic-release-bot
cb6d802de3 chore: Release v5.37.1-dev.2 [skip ci]
## [5.37.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.1...v5.37.1-dev.2) (2025-09-15)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Support v397.1.0.52.81 ([#5855](https://github.com/ReVanced/revanced-patches/issues/5855)) ([f11d1ef](f11d1ef990))
2025-09-15 12:52:54 +00:00
brosssh
f11d1ef990 fix(Instagram - Hide navigation buttons): Support v397.1.0.52.81 (#5855) 2025-09-15 16:48:55 +04:00
semantic-release-bot
3d25da18bc chore: Release v5.37.1-dev.1 [skip ci]
## [5.37.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.37.0...v5.37.1-dev.1) (2025-09-15)

### Bug Fixes

* **YouTube Music - Spoof video streams:** Fix playback issues when using a cellular network ([fa04c8e](fa04c8eecf))
2025-09-15 12:47:02 +00:00
LisoUseInAIKyrios
fa04c8eecf fix(YouTube Music - Spoof video streams): Fix playback issues when using a cellular network
Code adapted from 5f35e51a27
2025-09-15 16:43:04 +04:00
semantic-release-bot
105f6e0e97 chore: Release v5.37.0 [skip ci]
# [5.37.0](https://github.com/ReVanced/revanced-patches/compare/v5.36.0...v5.37.0) (2025-09-15)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Add constrain to known working version ([e6c79f1](e6c79f1383))
* Resolve patching with dev branch ([09b941a](09b941abf0))
* **Spotify:** Remove broken `Spoof client` patch ([#5833](https://github.com/ReVanced/revanced-patches/issues/5833)) ([dcd4245](dcd42454bd))
* **Viber - Hide ads:** Add constrain to known working version ([2db0948](2db0948bea))
* **YouTube Music - Spoof streaming data:** Fix audio playback stuttering ([#5839](https://github.com/ReVanced/revanced-patches/issues/5839)) ([2a85a3b](2a85a3b290))

### Features

* **Viber:** Add `Hide ads` patch ([#5826](https://github.com/ReVanced/revanced-patches/issues/5826)) ([0abfab7](0abfab79d7))
2025-09-15 06:45:56 +00:00
LisoUseInAIKyrios
7d59efe05d chore: Merge branch dev to main (#5830) 2025-09-15 10:43:05 +04:00
github-actions[bot]
81ff5576b0 chore: Sync translations (#5854) 2025-09-15 10:41:42 +04:00
semantic-release-bot
9a5c102c0d chore: Release v5.37.0-dev.6 [skip ci]
# [5.37.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.5...v5.37.0-dev.6) (2025-09-15)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Add constrain to known working version ([e6c79f1](e6c79f1383))
2025-09-15 06:40:30 +00:00
LisoUseInAIKyrios
e6c79f1383 fix(Instagram - Hide navigation buttons): Add constrain to known working version 2025-09-15 10:36:57 +04:00
semantic-release-bot
2a582eced8 chore: Release v5.37.0-dev.5 [skip ci]
# [5.37.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.4...v5.37.0-dev.5) (2025-09-15)

### Bug Fixes

* **Viber - Hide ads:** Add constrain to known working version ([2db0948](2db0948bea))
2025-09-15 06:29:31 +00:00
LisoUseInAIKyrios
2db0948bea fix(Viber - Hide ads): Add constrain to known working version 2025-09-15 10:26:30 +04:00
semantic-release-bot
a3ba92e742 chore: Release v5.37.0-dev.4 [skip ci]
# [5.37.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.3...v5.37.0-dev.4) (2025-09-14)

### Bug Fixes

* **YouTube Music - Spoof streaming data:** Fix audio playback stuttering ([#5839](https://github.com/ReVanced/revanced-patches/issues/5839)) ([2a85a3b](2a85a3b290))
2025-09-14 18:22:57 +00:00
LisoUseInAIKyrios
2a85a3b290 fix(YouTube Music - Spoof streaming data): Fix audio playback stuttering (#5839) 2025-09-14 22:19:13 +04:00
semantic-release-bot
eee72208dd chore: Release v5.37.0-dev.3 [skip ci]
# [5.37.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.2...v5.37.0-dev.3) (2025-09-14)

### Bug Fixes

* **Spotify:** Remove broken `Spoof client` patch ([#5833](https://github.com/ReVanced/revanced-patches/issues/5833)) ([dcd4245](dcd42454bd))
2025-09-14 17:15:28 +00:00
LisoUseInAIKyrios
dcd42454bd fix(Spotify): Remove broken Spoof client patch (#5833) 2025-09-14 21:11:15 +04:00
LisoUseInAIKyrios
782353c18a refactor(Spoof video streams): Handle migration of default spoof client for users upgrading from very old patches 2025-09-14 18:06:40 +04:00
semantic-release-bot
b53b870e8f chore: Release v5.37.0-dev.2 [skip ci]
# [5.37.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.1...v5.37.0-dev.2) (2025-09-14)

### Bug Fixes

* Resolve patching with dev branch ([09b941a](09b941abf0))
2025-09-14 12:00:38 +00:00
LisoUseInAIKyrios
09b941abf0 fix: Resolve patching with dev branch 2025-09-14 15:58:05 +04:00
semantic-release-bot
678ef4052e chore: Release v5.37.0-dev.1 [skip ci]
# [5.37.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.36.0...v5.37.0-dev.1) (2025-09-14)

### Features

* **Viber:** Add `Hide ads` patch ([#5826](https://github.com/ReVanced/revanced-patches/issues/5826)) ([0abfab7](0abfab79d7))
2025-09-14 11:52:21 +00:00
Samo Hribar
0abfab79d7 feat(Viber): Add Hide ads patch (#5826) 2025-09-14 15:49:52 +04:00
LisoUseInAIKyrios
61cadf72cd refactor(Spoof video streams): Back port code from v22 branch to support patching the latest YT Music. Using any target above 7.49.52 is untested and only recommended for experimental or development purposes. 2025-09-14 15:49:35 +04:00
github-actions[bot]
e12359b94f chore: Sync translations (#5829) 2025-09-14 15:46:32 +04:00
semantic-release-bot
c001daba4a chore: Release v5.36.0 [skip ci]
# [5.36.0](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0) (2025-09-14)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))
* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](a53b00dd51))

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-14 06:56:22 +00:00
LisoUseInAIKyrios
e136f62d6e chore: Merge branch dev to main (#5800) 2025-09-14 10:53:28 +04:00
semantic-release-bot
8ec405a359 chore: Release v5.36.0-dev.1 [skip ci]
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))
* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](a53b00dd51))

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-13 15:31:54 +00:00
github-actions[bot]
2f4b3a887b chore: Sync translations (#5821) 2025-09-13 19:28:15 +04:00
semantic-release-bot
d1fabb242b chore: Release v5.36.0-dev.1 [skip ci]
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))
* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](a53b00dd51))

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-13 15:15:40 +00:00
LisoUseInAIKyrios
a53b00dd51 fix(YouTube Music): Resolve playback issues, change recommended app target to 7.29.52 (#5813) 2025-09-13 19:12:00 +04:00
semantic-release-bot
850c13e98e chore: Release v5.36.0-dev.1 [skip ci]
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-13 07:00:16 +00:00
LisoUseInAIKyrios
4310789a26 chore: Fix api 2025-09-13 10:56:43 +04:00
semantic-release-bot
c4a720fbd3 chore: Release v5.36.0-dev.1 [skip ci]
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-12)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-12 15:08:03 +00:00
LisoUseInAIKyrios
3bdb8dbce0 chore(YouTube - SponsorBlock): Adjust strings for consistency / clarity
Strings taken from https://github.com/ajayyy/ExtensionTranslations/blob/master/en/messages.json
2025-09-12 18:49:34 +04:00
LisoUseInAIKyrios
4894f33c96 chore: fix compilation 2025-09-12 18:49:33 +04:00
semantic-release-bot
7f6093ee66 chore: Release v5.36.0-dev.1 [skip ci]
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.1-dev.1...v5.36.0-dev.1) (2025-09-12)

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-12 01:59:47 +00:00
LisoUseInAIKyrios
9d4aa5cd16 feat(YouTube - SponsorBlock): Add 'Hook' segment category (#5783) 2025-09-12 05:56:50 +04:00
oSumAtrIX
5ace6f587c chore: Add ads.fund verification file [skip ci] (#5786) 2025-09-11 16:00:24 +02:00
semantic-release-bot
796f56745e chore: Release v5.35.1-dev.1 [skip ci]
## [5.35.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.35.1-dev.1) (2025-09-11)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))
2025-09-11 01:29:16 +00:00
hoodles
88b47ef414 fix(Duolingo - Disable ads): Support latest app target (#5782) 2025-09-11 03:26:28 +02:00
LisoUseInAIKyrios
8cd8e59bbc fix(YouTube - Hide layout components): Hide new type of Playable shelf 2025-09-11 03:24:09 +02:00
LisoUseInAIKyrios
6e72b14d07 refactor(YouTube - Video Quality): Handle extremely slow internet connections that initially can use -1 quality index 2025-09-11 02:58:33 +02:00
LisoUseInAIKyrios
52b088327b chore: Fix api dump 2025-09-10 21:58:27 +02:00
semantic-release-bot
8e934cc56b chore: Release v5.35.0 [skip ci]
# [5.35.0](https://github.com/ReVanced/revanced-patches/compare/v5.34.0...v5.35.0) (2025-09-09)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Fix Manager patching error ([0a8cd7a](0a8cd7a7db))
* **Proton mail:** Constrain patches to last working app target ([1895291](189529151a))
* Revert dependency updates to fix Manager pre-release patching ([9256aa4](9256aa4548))
* **Spotify - Unlock Premium:** Make compatible with latest versions again by fixing fingerprint ([#5684](https://github.com/ReVanced/revanced-patches/issues/5684)) ([23496c7](23496c7c36))
* **YouTube - Hide layout components:** Hide Playable shelf header ([1473db0](1473db0bef))

### Features

* **BaconReader:** Add `Fix Redgifs API` patch ([#5761](https://github.com/ReVanced/revanced-patches/issues/5761)) ([144af2f](144af2f07e))
* **Boost/Sync for Reddit:** Add `Fix Redgifs` patch  ([#5725](https://github.com/ReVanced/revanced-patches/issues/5725)) ([c66c42e](c66c42e946))
* **Instagram:** Add `Hide navigation buttons` patch ([#5678](https://github.com/ReVanced/revanced-patches/issues/5678)) ([1dbc2d4](1dbc2d4057))
* **Instagram:** Add `Hide Stories from Home` patch ([#5756](https://github.com/ReVanced/revanced-patches/issues/5756)) ([b8629aa](b8629aacb6))
2025-09-09 19:38:18 +00:00
LisoUseInAIKyrios
b3140d909b chore: Merge branch dev to main (#5691) 2025-09-09 21:34:30 +02:00
github-actions[bot]
97645aa9f4 chore: Sync translations (#5777) 2025-09-09 21:32:56 +02:00
semantic-release-bot
603e2d018c chore: Release v5.35.0-dev.5 [skip ci]
# [5.35.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.4...v5.35.0-dev.5) (2025-09-06)

### Features

* **BaconReader:** Add `Fix Redgifs API` patch ([#5761](https://github.com/ReVanced/revanced-patches/issues/5761)) ([144af2f](144af2f07e))
* **Instagram:** Add `Hide Stories from Home` patch ([#5756](https://github.com/ReVanced/revanced-patches/issues/5756)) ([b8629aa](b8629aacb6))
2025-09-06 10:56:54 +00:00
Eric Ahn
144af2f07e feat(BaconReader): Add Fix Redgifs API patch (#5761) 2025-09-06 12:53:26 +02:00
PainfulPaladins
b8629aacb6 feat(Instagram): Add Hide Stories from Home patch (#5756) 2025-09-06 12:53:08 +02:00
github-actions[bot]
3951527f51 chore: Sync translations (#5768) 2025-09-06 12:52:48 +02:00
semantic-release-bot
7a8b618c4e chore: Release v5.35.0-dev.4 [skip ci]
# [5.35.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.3...v5.35.0-dev.4) (2025-09-04)

### Features

* **Boost/Sync for Reddit:** Add `Fix Redgifs` patch  ([#5725](https://github.com/ReVanced/revanced-patches/issues/5725)) ([c66c42e](c66c42e946))
2025-09-04 21:33:33 +00:00
Eric Ahn
c66c42e946 feat(Boost/Sync for Reddit): Add Fix Redgifs patch (#5725) 2025-09-04 23:29:58 +02:00
semantic-release-bot
b340769cf3 chore: Release v5.35.0-dev.3 [skip ci]
# [5.35.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.2...v5.35.0-dev.3) (2025-09-04)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Fix Manager patching error ([0a8cd7a](0a8cd7a7db))
2025-09-04 14:06:03 +00:00
LisoUseInAIKyrios
0a8cd7a7db fix(Instagram - Hide navigation buttons): Fix Manager patching error 2025-09-04 16:01:50 +02:00
semantic-release-bot
39f90e4b11 chore: Release v5.35.0-dev.2 [skip ci]
# [5.35.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.1...v5.35.0-dev.2) (2025-09-04)

### Bug Fixes

* Revert dependency updates to fix Manager pre-release patching ([9256aa4](9256aa4548))
2025-09-04 10:27:39 +00:00
LisoUseInAIKyrios
9256aa4548 fix: Revert dependency updates to fix Manager pre-release patching 2025-09-04 12:23:56 +02:00
semantic-release-bot
7973c75552 chore: Release v5.35.0-dev.1 [skip ci]
# [5.35.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.34.1-dev.3...v5.35.0-dev.1) (2025-09-03)

### Features

* **Instagram:** Add `Hide navigation buttons` patch ([#5678](https://github.com/ReVanced/revanced-patches/issues/5678)) ([1dbc2d4](1dbc2d4057))
2025-09-03 17:43:47 +00:00
github-actions[bot]
2b2307416a chore: Sync translations (#5755) 2025-09-03 19:41:04 +02:00
PainfulPaladins
1dbc2d4057 feat(Instagram): Add Hide navigation buttons patch (#5678)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-09-03 19:39:25 +02:00
dependabot[bot]
f6917dc361 chore(deps): Bump com.google.protobuf:protoc from 4.31.1 to 4.32.0 (#5751) 2025-09-02 18:28:15 +02:00
dependabot[bot]
d2f043e11a chore(deps): Bump com.google.protobuf:protobuf-javalite from 4.31.1 to 4.32.0 (#5750) 2025-09-02 17:10:45 +02:00
dependabot[bot]
a392bc0dfd chore(deps): Bump actions/setup-java from 4 to 5 (#5746) 2025-09-02 12:43:12 +02:00
dependabot[bot]
dfc127048a chore(deps): Bump actions/attest-build-provenance from 2 to 3 (#5743) 2025-09-02 12:42:08 +02:00
dependabot[bot]
ed31d0cab6 chore(deps): Bump actions/checkout from 4 to 5 (#5745) 2025-09-02 12:41:29 +02:00
dependabot[bot]
0df6315f9c chore(deps): Bump cycjimmy/semantic-release-action from 4 to 5 (#5741) 2025-09-02 12:40:08 +02:00
semantic-release-bot
f14259f9ef chore: Release v5.34.1-dev.3 [skip ci]
## [5.34.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.34.1-dev.2...v5.34.1-dev.3) (2025-08-24)

### Bug Fixes

* **YouTube - Hide layout components:** Hide Playable shelf header ([1473db0](1473db0bef))
2025-08-24 03:30:00 +00:00
LisoUseInAIKyrios
1473db0bef fix(YouTube - Hide layout components): Hide Playable shelf header 2025-08-23 23:26:02 -04:00
github-actions[bot]
829ca58a55 chore: Sync translations (#5707) 2025-08-23 23:23:49 -04:00
714 changed files with 88050 additions and 62832 deletions

View File

@@ -72,6 +72,7 @@ body:
- **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-patches/issues?q=label%3A%22Bug+report%22).
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-patches/blob/main/CONTRIBUTING.md).
- **Check the troubleshooting guide**: A solution to your issue might be found in the [FAQ](https://github.com/ReVanced/revanced-documentation/blob/main/docs/revanced-resources/questions.md) or the [troubleshooting guide](https://github.com/ReVanced/revanced-documentation/blob/main/docs/revanced-resources/troubleshooting.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea
attributes:

View File

@@ -72,6 +72,7 @@ body:
- **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-patches/issues?q=label%3A%22Feature+request%22).
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-patches/blob/main/CONTRIBUTING.md).
- **Check the troubleshooting guide**: Information about your issue might be found in the [FAQ](https://github.com/ReVanced/revanced-documentation/blob/main/docs/revanced-resources/questions.md) or the [troubleshooting guide](https://github.com/ReVanced/revanced-documentation/blob/main/docs/revanced-resources/troubleshooting.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea
attributes:

View File

@@ -12,10 +12,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
@@ -25,11 +25,12 @@ jobs:
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ env.GITHUB_ACTOR }}
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew :patches:buildAndroid --no-daemon
- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: revanced-patches
path: patches/build/libs

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Open pull request
uses: repo-sync/pull-request@v2

View File

@@ -2,7 +2,7 @@ name: Pull strings
on:
schedule:
- cron: "0 */12 * * *"
- cron: "0 0 * * 0"
workflow_dispatch:
jobs:
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
ref: dev
clean: true

View File

@@ -14,12 +14,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Preprocess strings
- name: Process strings
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew clean preprocessCrowdinStrings
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew processStringsForCrowdin
- name: Push strings
uses: crowdin/github-action@v2

View File

@@ -18,10 +18,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
@@ -31,11 +31,12 @@ jobs:
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew :patches:buildAndroid clean
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 'lts/*'
cache: 'npm'
@@ -51,14 +52,16 @@ jobs:
fingerprint: ${{ vars.GPG_FINGERPRINT }}
- name: Release
uses: cycjimmy/semantic-release-action@v4
uses: cycjimmy/semantic-release-action@v5
id: release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
- name: Attest
if: steps.release.outputs.new_release_published == 'true'
uses: actions/attest-build-provenance@v2
uses: actions/attest-build-provenance@v3
with:
subject-name: 'ReVanced Patches ${{ steps.release.outputs.new_release_git_tag }}'
subject-path: patches/build/libs/patches-*.rvp

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Update Gradle Wrapper
uses: gradle-update/update-gradle-wrapper-action@v1

File diff suppressed because it is too large Load Diff

View File

@@ -97,9 +97,9 @@ Thank you for considering contributing to ReVanced Patches. You can find the con
To build ReVanced Patches, you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation).
## 📜 Licence
## 📜 License
ReVanced Patches is licensed under the GPLv3 license. Please see the [license file](LICENSE) for more information.
[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Patches as long as you track changes/dates in source files.
Any modifications to ReVanced Patches must also be made available under the GPL,
along with build & install instructions.
along with build & install instructions.

8
adsfund.json Normal file
View File

@@ -0,0 +1,8 @@
{
"info": "This is verification file for ads.fund project",
"project": {
"name": "Revanced Patches",
"walletAddress": "0x7ab4091e00363654bf84B34151225742cd92FCE5",
"tokenAddress": "0xadf325f255083a3f3d9a9d01ffb3db52a148d802"
}
}

View File

@@ -1,8 +1,9 @@
project_id_env: "CROWDIN_PROJECT_ID"
api_token_env: "CROWDIN_PERSONAL_TOKEN"
preserve_hierarchy: false
preserve_hierarchy: true
files:
- source: patches/src/main/resources/addresources/values/strings.xml
dest: patches.xml
translation: patches/src/main/resources/addresources/values-%android_code%/strings.xml
skip_untranslated_strings: true

View File

@@ -0,0 +1,20 @@
android {
namespace = "app.revanced.extension"
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
buildFeatures {
aidl = true
}
}
dependencies {
compileOnly(libs.annotation)
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,8 @@
package com.google.android.play.core.integrity.protocol;
import android.os.Bundle;
import com.google.android.play.core.integrity.protocol.IExpressIntegrityServiceCallback;
interface IExpressIntegrityService {
oneway void requestIntegrityToken(in Bundle request, IExpressIntegrityServiceCallback callback) = 2;
}

View File

@@ -0,0 +1,5 @@
package com.google.android.play.core.integrity.protocol;
interface IExpressIntegrityServiceCallback {
oneway void onRequestExpressIntegrityTokenResult(in Bundle result) = 2;
}

View File

@@ -0,0 +1,8 @@
package com.google.android.play.core.integrity.protocol;
import android.os.Bundle;
import com.google.android.play.core.integrity.protocol.IIntegrityServiceCallback;
interface IIntegrityService {
oneway void requestIntegrityToken(in Bundle request, IIntegrityServiceCallback callback) = 1;
}

View File

@@ -0,0 +1,7 @@
package com.google.android.play.core.integrity.protocol;
import android.os.Bundle;
interface IIntegrityServiceCallback {
oneway void onResult(in Bundle result) = 1;
}

View File

@@ -0,0 +1,10 @@
package android.ext;
/** @hide */
// Int values that are assigned to packages in this interface can be retrieved at runtime from
// ApplicationInfo.ext().getPackageId() or from AndroidPackage.ext().getPackageId() (in system_server).
//
// PackageIds are assigned to parsed APKs only after they are verified, either by a certificate check
// or by a check that the APK is stored on an immutable OS partition.
public interface PackageId {
String PLAY_STORE_NAME = "com.android.vending";
}

View File

@@ -0,0 +1,62 @@
package android.os;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.FileDescriptor;
/** @hide */
public class BinderWrapper implements IBinder {
protected final IBinder base;
public BinderWrapper(IBinder base) {
this.base = base;
}
@Override
public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
return base.transact(code, data, reply, flags);
}
@Nullable
@Override
public IInterface queryLocalInterface(@NonNull String descriptor) {
return base.queryLocalInterface(descriptor);
}
@Nullable
@Override
public String getInterfaceDescriptor() throws RemoteException {
return base.getInterfaceDescriptor();
}
@Override
public boolean pingBinder() {
return base.pingBinder();
}
@Override
public boolean isBinderAlive() {
return base.isBinderAlive();
}
@Override
public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException {
base.dump(fd, args);
}
@Override
public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException {
base.dumpAsync(fd, args);
}
@Override
public void linkToDeath(@NonNull DeathRecipient recipient, int flags) throws RemoteException {
base.linkToDeath(recipient, flags);
}
@Override
public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
return base.unlinkToDeath(recipient, flags);
}
}

View File

@@ -0,0 +1,41 @@
package app.grapheneos.gmscompat.lib.playintegrity;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.os.FakeBackgroundHandler;
import com.google.android.play.core.integrity.protocol.IIntegrityService;
import com.google.android.play.core.integrity.protocol.IIntegrityServiceCallback;
class ClassicPlayIntegrityServiceWrapper extends PlayIntegrityServiceWrapper {
ClassicPlayIntegrityServiceWrapper(IBinder base) {
super(base);
requestIntegrityTokenTxnCode = 2; // IIntegrityService.Stub.TRANSACTION_requestIntegrityToken
}
static class TokenRequestStub extends IIntegrityService.Stub {
public void requestIntegrityToken(Bundle request, IIntegrityServiceCallback callback) {
Runnable r = () -> {
var result = new Bundle();
// https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/model/IntegrityErrorCode.html#API_NOT_AVAILABLE
final int API_NOT_AVAILABLE = -1;
result.putInt("error", API_NOT_AVAILABLE);
try {
callback.onResult(result);
} catch (RemoteException e) {
Log.e("IIntegrityService.Stub", "", e);
}
};
FakeBackgroundHandler.getHandler().postDelayed(r, getTokenRequestResultDelay());
}
};
@Override
protected Binder createTokenRequestStub() {
return new TokenRequestStub();
}
}

View File

@@ -0,0 +1,48 @@
package app.grapheneos.gmscompat.lib.playintegrity;
import android.os.Binder;
import android.os.BinderWrapper;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.Log;
import androidx.annotation.Nullable;
abstract class PlayIntegrityServiceWrapper extends BinderWrapper {
final String TAG;
protected int requestIntegrityTokenTxnCode;
public PlayIntegrityServiceWrapper(IBinder base) {
super(base);
TAG = getClass().getSimpleName();
}
protected abstract Binder createTokenRequestStub();
@Override
public boolean transact(int code, Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
if (code == requestIntegrityTokenTxnCode) {
if (maybeStubOutIntegrityTokenRequest(code, data, reply, flags)) {
return true;
}
}
return super.transact(code, data, reply, flags);
}
private boolean maybeStubOutIntegrityTokenRequest(int code, Parcel data, @Nullable Parcel reply, int flags) {
Log.d(TAG, "integrity token request detected");
try {
createTokenRequestStub().transact(code, data, reply, flags);
} catch (RemoteException e) {
// this is a local call
throw new IllegalStateException(e);
}
return true;
}
protected static long getTokenRequestResultDelay() {
return 500L;
}
}

View File

@@ -0,0 +1,35 @@
package app.grapheneos.gmscompat.lib.playintegrity;
import android.content.Intent;
import android.content.ServiceConnection;
import android.ext.PackageId;
import android.os.IBinder;
import androidx.annotation.Nullable;
import app.grapheneos.gmscompat.lib.util.ServiceConnectionWrapper;
import java.util.function.UnaryOperator;
public class PlayIntegrityUtils {
public static @Nullable ServiceConnection maybeReplaceServiceConnection(Intent service, ServiceConnection orig) {
if (PackageId.PLAY_STORE_NAME.equals(service.getPackage())) {
UnaryOperator<IBinder> binderOverride = null;
final String CLASSIC_SERVICE =
"com.google.android.play.core.integrityservice.BIND_INTEGRITY_SERVICE";
final String STANDARD_SERVICE =
"com.google.android.play.core.expressintegrityservice.BIND_EXPRESS_INTEGRITY_SERVICE";
String action = service.getAction();
if (STANDARD_SERVICE.equals(action)) {
binderOverride = StandardPlayIntegrityServiceWrapper::new;
} else if (CLASSIC_SERVICE.equals(action)) {
binderOverride = ClassicPlayIntegrityServiceWrapper::new;
}
if (binderOverride != null) {
return new ServiceConnectionWrapper(orig, binderOverride);
}
}
return null;
}
}

View File

@@ -0,0 +1,42 @@
package app.grapheneos.gmscompat.lib.playintegrity;
import android.annotation.SuppressLint;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.os.FakeBackgroundHandler;
import com.google.android.play.core.integrity.protocol.IExpressIntegrityService;
import com.google.android.play.core.integrity.protocol.IExpressIntegrityServiceCallback;
@SuppressLint("LongLogTag")
class StandardPlayIntegrityServiceWrapper extends PlayIntegrityServiceWrapper {
StandardPlayIntegrityServiceWrapper(IBinder base) {
super(base);
requestIntegrityTokenTxnCode = 3; // IExpressIntegrityService.Stub.TRANSACTION_requestIntegrityToken
}
static class TokenRequestStub extends IExpressIntegrityService.Stub {
public void requestIntegrityToken(Bundle request, IExpressIntegrityServiceCallback callback) {
Runnable r = () -> {
var result = new Bundle();
// https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/model/StandardIntegrityErrorCode.html#API_NOT_AVAILABLE
final int API_NOT_AVAILABLE = -1;
result.putInt("error", API_NOT_AVAILABLE);
try {
callback.onRequestExpressIntegrityTokenResult(result);
} catch (RemoteException e) {
Log.e("IExpressIntegrityService.Stub", "", e);
}
};
FakeBackgroundHandler.getHandler().postDelayed(r, getTokenRequestResultDelay());
}
};
@Override
protected Binder createTokenRequestStub() {
return new TokenRequestStub();
}
}

View File

@@ -0,0 +1,49 @@
package app.grapheneos.gmscompat.lib.util;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.IBinder;
import java.util.function.UnaryOperator;
public class ServiceConnectionWrapper implements ServiceConnection {
private final ServiceConnection base;
private final UnaryOperator<IBinder> binderOverride;
public ServiceConnectionWrapper(ServiceConnection base, UnaryOperator<IBinder> binderOverride) {
this.base = base;
this.binderOverride = binderOverride;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
IBinder override = binderOverride.apply(service);
if (override != null) {
service = override;
}
}
base.onServiceConnected(name, service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
base.onServiceDisconnected(name);
}
@Override
public void onBindingDied(ComponentName name) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
base.onBindingDied(name);
}
}
@Override
public void onNullBinding(ComponentName name) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
base.onNullBinding(name);
}
}
}

View File

@@ -0,0 +1,17 @@
package app.revanced.extension.playintegrity;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import app.grapheneos.gmscompat.lib.playintegrity.PlayIntegrityUtils;
public class DisablePlayIntegrityPatch {
public static boolean bindService(Context context, Intent service, ServiceConnection conn, int flags) {
ServiceConnection override = PlayIntegrityUtils.maybeReplaceServiceConnection(service, conn);
if (override != null) {
conn = override;
}
return context.bindService(service, conn, flags);
}
}

View File

@@ -0,0 +1,11 @@
package com.android.internal.os;
import android.os.Handler;
import android.os.Looper;
public class FakeBackgroundHandler {
public static Handler getHandler() {
return new Handler(Looper.getMainLooper());
}
}

View File

@@ -0,0 +1,5 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(libs.annotation)
compileOnly(libs.okhttp)
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,22 @@
package app.revanced.extension.baconreader;
import app.revanced.extension.shared.fixes.redgifs.BaseFixRedgifsApiPatch;
import okhttp3.OkHttpClient;
/**
* @noinspection unused
*/
public class FixRedgifsApiPatch extends BaseFixRedgifsApiPatch {
static {
INSTANCE = new FixRedgifsApiPatch();
}
public String getDefaultUserAgent() {
// BaconReader uses a static user agent for Redgifs API calls
return "BaconReader";
}
public static OkHttpClient install(OkHttpClient.Builder builder) {
return builder.addInterceptor(INSTANCE).build();
}
}

View File

@@ -1,4 +1,6 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:boostforreddit:stub"))
compileOnly(libs.annotation)
compileOnly(libs.okhttp)
}

View File

@@ -0,0 +1,22 @@
package app.revanced.extension.boostforreddit;
import app.revanced.extension.shared.fixes.redgifs.BaseFixRedgifsApiPatch;
import okhttp3.OkHttpClient;
/**
* @noinspection unused
*/
public class FixRedgifsApiPatch extends BaseFixRedgifsApiPatch {
static {
INSTANCE = new FixRedgifsApiPatch();
}
public String getDefaultUserAgent() {
// Boost uses a static user agent for Redgifs API calls
return "Boost";
}
public static OkHttpClient createClient() {
return new OkHttpClient.Builder().addInterceptor(INSTANCE).build();
}
}

View File

@@ -0,0 +1,3 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,26 @@
package app.revanced.extension.instagram.feed;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("unused")
public class LimitFeedToFollowedProfiles {
/**
* Injection point.
*/
public static Map<String, String> setFollowingHeader(Map<String, String> requestHeaderMap) {
String paginationHeaderName = "pagination_source";
// Patch the header only if it's trying to fetch the default feed
String currentHeader = requestHeaderMap.get(paginationHeaderName);
if (currentHeader != null && !currentHeader.equals("feed_recs")) {
return requestHeaderMap;
}
// Create new map as original is unmodifiable.
Map<String, String> patchedRequestHeaderMap = new HashMap<>(requestHeaderMap);
patchedRequestHeaderMap.put(paginationHeaderName, "following");
return patchedRequestHeaderMap;
}
}

View File

@@ -0,0 +1,33 @@
package app.revanced.extension.instagram.hide.navigation;
import java.lang.reflect.Field;
import java.util.List;
@SuppressWarnings("unused")
public class HideNavigationButtonsPatch {
/**
* Injection point.
* @param navigationButtonsList the list of navigation buttons, as an (obfuscated) Enum type
* @param buttonNameToRemove the name of the button we want to remove
* @param enumNameField the field in the nav button enum class which contains the name of the button
* @return the patched list of navigation buttons
*/
public static List<Object> removeNavigationButtonByName(
List<Object> navigationButtonsList,
String buttonNameToRemove,
String enumNameField
)
throws IllegalAccessException, NoSuchFieldException {
for (Object button : navigationButtonsList) {
Field f = button.getClass().getDeclaredField(enumNameField);
String currentButtonEnumName = (String) f.get(button);
if (buttonNameToRemove.equals(currentButtonEnumName)) {
navigationButtonsList.remove(button);
break;
}
}
return navigationButtonsList;
}
}

View File

@@ -0,0 +1,30 @@
package app.revanced.extension.instagram.misc.links;
import android.net.Uri;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
@SuppressWarnings("unused")
public final class OpenLinksExternallyPatch {
/**
* Injection point.
*/
public static boolean openExternally(String url) {
try {
// The "url" parameter to this function will be of the form.
// https://l.instagram.com/?u=<actual url>&e=<tracking id>
String actualUrl = Uri.parse(url).getQueryParameter("u");
if (actualUrl != null) {
Utils.openLink(actualUrl);
return true;
}
} catch (Exception ex) {
Logger.printException(() -> "openExternally failure", ex);
}
return false;
}
}

View File

@@ -0,0 +1,15 @@
package app.revanced.extension.instagram.misc.privacy;
import app.revanced.extension.shared.privacy.LinkSanitizer;
@SuppressWarnings("unused")
public final class SanitizeSharingLinksPatch {
private static final LinkSanitizer sanitizer = new LinkSanitizer("igsh");
/**
* Injection point.
*/
public static String sanitizeSharingLink(String url) {
return sanitizer.sanitizeUrlString(url);
}
}

View File

@@ -0,0 +1,33 @@
package app.revanced.extension.instagram.misc.share.domain;
import android.net.Uri;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public final class ChangeLinkSharingDomainPatch {
private static String getCustomShareDomain() {
// Method is modified during patching.
throw new IllegalStateException();
}
/**
* Injection point.
*/
public static String setCustomShareDomain(String url) {
try {
Uri uri = Uri.parse(url);
Uri.Builder builder = uri
.buildUpon()
.authority(getCustomShareDomain())
.clearQuery();
String patchedUrl = builder.build().toString();
Logger.printInfo(() -> "Domain change from : " + url + " to: " + patchedUrl);
return patchedUrl;
} catch (Exception ex) {
Logger.printException(() -> "setCustomShareDomain failure with " + url, ex);
return url;
}
}
}

View File

@@ -0,0 +1,15 @@
package app.revanced.extension.instagram.misc.share.privacy;
import app.revanced.extension.shared.privacy.LinkSanitizer;
@SuppressWarnings("unused")
public final class SanitizeSharingLinksPatch {
private static final LinkSanitizer sanitizer = new LinkSanitizer("igsh");
/**
* Injection point.
*/
public static String sanitizeSharingLink(String url) {
return sanitizer.sanitizeUrlString(url);
}
}

View File

@@ -1,3 +1,9 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:youtube:stub"))
compileOnly(libs.annotation)
}
android {
defaultConfig {
minSdk = 26

View File

@@ -0,0 +1,14 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class ChangeMiniplayerColorPatch {
/**
* Injection point
*/
public static boolean changeMiniplayerColor() {
return Settings.CHANGE_MINIPLAYER_COLOR.get();
}
}

View File

@@ -0,0 +1,17 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class ForceOriginalAudioPatch {
/**
* Injection point.
*/
public static void setEnabled() {
app.revanced.extension.shared.patches.ForceOriginalAudioPatch.setEnabled(
Settings.FORCE_ORIGINAL_AUDIO.get(),
Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get()
);
}
}

View File

@@ -0,0 +1,49 @@
package app.revanced.extension.music.patches;
import static app.revanced.extension.shared.Utils.hideViewBy0dpUnderCondition;
import android.view.View;
import android.view.ViewGroup;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideButtonsPatch {
/**
* Injection point
*/
public static int hideCastButton(int original) {
return Settings.HIDE_CAST_BUTTON.get() ? View.GONE : original;
}
/**
* Injection point
*/
public static void hideCastButton(View view) {
hideViewBy0dpUnderCondition(Settings.HIDE_CAST_BUTTON, view);
}
/**
* Injection point
*/
public static boolean hideHistoryButton(boolean original) {
return original && !Settings.HIDE_HISTORY_BUTTON.get();
}
/**
* Injection point
*/
public static void hideNotificationButton(View view) {
if (view.getParent() instanceof ViewGroup viewGroup) {
hideViewBy0dpUnderCondition(Settings.HIDE_NOTIFICATION_BUTTON, viewGroup);
}
}
/**
* Injection point
*/
public static void hideSearchButton(View view) {
hideViewBy0dpUnderCondition(Settings.HIDE_SEARCH_BUTTON, view);
}
}

View File

@@ -0,0 +1,18 @@
package app.revanced.extension.music.patches;
import static app.revanced.extension.shared.Utils.hideViewBy0dpUnderCondition;
import android.view.View;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideCategoryBarPatch {
/**
* Injection point
*/
public static void hideCategoryBar(View view) {
hideViewBy0dpUnderCondition(Settings.HIDE_CATEGORY_BAR, view);
}
}

View File

@@ -0,0 +1,14 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideGetPremiumPatch {
/**
* Injection point
*/
public static boolean hideGetPremiumLabel() {
return Settings.HIDE_GET_PREMIUM_LABEL.get();
}
}

View File

@@ -0,0 +1,17 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideVideoAdsPatch {
/**
* Injection point
*/
public static boolean showVideoAds(boolean original) {
if (Settings.HIDE_VIDEO_ADS.get()) {
return false;
}
return original;
}
}

View File

@@ -0,0 +1,74 @@
package app.revanced.extension.music.patches;
import static app.revanced.extension.shared.Utils.hideViewUnderCondition;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class NavigationBarPatch {
@NonNull
private static String lastYTNavigationEnumName = "";
public static void setLastAppNavigationEnum(@Nullable Enum<?> ytNavigationEnumName) {
if (ytNavigationEnumName != null) {
lastYTNavigationEnumName = ytNavigationEnumName.name();
}
}
public static void hideNavigationLabel(TextView textview) {
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BAR_LABEL.get(), textview);
}
public static void hideNavigationButton(@NonNull View view) {
// Hide entire navigation bar.
if (Settings.HIDE_NAVIGATION_BAR.get() && view.getParent() != null) {
hideViewUnderCondition(true, (View) view.getParent());
return;
}
// Hide navigation buttons based on their type.
for (NavigationButton button : NavigationButton.values()) {
if (button.ytEnumNames.equals(lastYTNavigationEnumName)) {
hideViewUnderCondition(button.hidden, view);
break;
}
}
}
private enum NavigationButton {
HOME(
"TAB_HOME",
Settings.HIDE_NAVIGATION_BAR_HOME_BUTTON.get()
),
SAMPLES(
"TAB_SAMPLES",
Settings.HIDE_NAVIGATION_BAR_SAMPLES_BUTTON.get()
),
EXPLORE(
"TAB_EXPLORE",
Settings.HIDE_NAVIGATION_BAR_EXPLORE_BUTTON.get()
),
LIBRARY(
"LIBRARY_MUSIC",
Settings.HIDE_NAVIGATION_BAR_LIBRARY_BUTTON.get()
),
UPGRADE(
"TAB_MUSIC_PREMIUM",
Settings.HIDE_NAVIGATION_BAR_UPGRADE_BUTTON.get()
);
private final String ytEnumNames;
private final boolean hidden;
NavigationButton(@NonNull String ytEnumNames, boolean hidden) {
this.ytEnumNames = ytEnumNames;
this.hidden = hidden;
}
}
}

View File

@@ -0,0 +1,14 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class PermanentRepeatPatch {
/**
* Injection point
*/
public static boolean permanentRepeat() {
return Settings.PERMANENT_REPEAT.get();
}
}

View File

@@ -0,0 +1,30 @@
package app.revanced.extension.music.patches.spoof;
import static app.revanced.extension.music.settings.Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_NO_SDK;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
import static app.revanced.extension.shared.spoof.ClientType.VISIONOS;
import java.util.List;
import app.revanced.extension.shared.spoof.ClientType;
@SuppressWarnings("unused")
public class SpoofVideoStreamsPatch {
/**
* Injection point.
*/
public static void setClientOrderToUse() {
List<ClientType> availableClients = List.of(
ANDROID_VR_1_43_32,
ANDROID_NO_SDK,
VISIONOS,
ANDROID_VR_1_61_48
);
app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.setClientsToUse(
availableClients, SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get());
}
}

View File

@@ -0,0 +1,27 @@
package app.revanced.extension.music.patches.theme;
import app.revanced.extension.shared.theme.BaseThemePatch;
@SuppressWarnings("unused")
public class ThemePatch extends BaseThemePatch {
// Color constants used in relation with litho components.
private static final int[] DARK_VALUES = {
0xFF212121, // Comments box background.
0xFF030303, // Button container background in album.
0xFF000000, // Button container background in playlist.
};
/**
* Injection point.
* <p>
* Change the color of Litho components.
* If the color of the component matches one of the values, return the background color.
*
* @param originalValue The original color value.
* @return The new or original color value.
*/
public static int getValue(int originalValue) {
return processColorValue(originalValue, DARK_VALUES, null);
}
}

View File

@@ -0,0 +1,126 @@
package app.revanced.extension.music.settings;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.preference.PreferenceFragment;
import android.view.View;
import android.widget.Toolbar;
import app.revanced.extension.music.settings.preference.MusicPreferenceFragment;
import app.revanced.extension.music.settings.search.MusicSearchViewController;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseActivityHook;
/**
* Hooks GoogleApiActivity to inject a custom {@link MusicPreferenceFragment} with a toolbar and search.
*/
public class MusicActivityHook extends BaseActivityHook {
@SuppressLint("StaticFieldLeak")
public static MusicSearchViewController searchViewController;
/**
* Injection point.
*/
@SuppressWarnings("unused")
public static void initialize(Activity parentActivity) {
// Must touch the Music settings to ensure the class is loaded and
// the values can be found when setting the UI preferences.
// Logging anything under non debug ensures this is set.
Logger.printInfo(() -> "Permanent repeat enabled: " + Settings.PERMANENT_REPEAT.get());
// YT Music always uses dark mode.
Utils.setIsDarkModeEnabled(true);
BaseActivityHook.initialize(new MusicActivityHook(), parentActivity);
}
/**
* Sets the fixed theme for the activity.
*/
@Override
protected void customizeActivityTheme(Activity activity) {
// Override the default YouTube Music theme to increase start padding of list items.
// Custom style located in resources/music/values/style.xml
activity.setTheme(Utils.getResourceIdentifierOrThrow(
"Theme.ReVanced.YouTubeMusic.Settings", "style"));
}
/**
* Returns the resource ID for the YouTube Music settings layout.
*/
@Override
protected int getContentViewResourceId() {
return LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR;
}
/**
* Returns the fixed background color for the toolbar.
*/
@Override
protected int getToolbarBackgroundColor() {
return Utils.getResourceColor("ytm_color_black");
}
/**
* Returns the navigation icon with a color filter applied.
*/
@Override
protected Drawable getNavigationIcon() {
Drawable navigationIcon = MusicPreferenceFragment.getBackButtonDrawable();
navigationIcon.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
return navigationIcon;
}
/**
* Returns the click listener that finishes the activity when the navigation icon is clicked.
*/
@Override
protected View.OnClickListener getNavigationClickListener(Activity activity) {
return view -> {
if (searchViewController != null && searchViewController.isSearchActive()) {
searchViewController.closeSearch();
} else {
activity.finish();
}
};
}
/**
* Adds search view components to the toolbar for {@link MusicPreferenceFragment}.
*
* @param activity The activity hosting the toolbar.
* @param toolbar The configured toolbar.
* @param fragment The PreferenceFragment associated with the activity.
*/
@Override
protected void onPostToolbarSetup(Activity activity, Toolbar toolbar, PreferenceFragment fragment) {
if (fragment instanceof MusicPreferenceFragment) {
searchViewController = MusicSearchViewController.addSearchViewComponents(
activity, toolbar, (MusicPreferenceFragment) fragment);
}
}
/**
* Creates a new {@link MusicPreferenceFragment} for the activity.
*/
@Override
protected PreferenceFragment createPreferenceFragment() {
return new MusicPreferenceFragment();
}
/**
* Injection point.
* <p>
* Overrides {@link Activity#finish()} of the injection Activity.
*
* @return if the original activity finish method should be allowed to run.
*/
@SuppressWarnings("unused")
public static boolean handleFinish() {
return MusicSearchViewController.handleFinish(searchViewController);
}
}

View File

@@ -0,0 +1,41 @@
package app.revanced.extension.music.settings;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.settings.Setting.parent;
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.settings.EnumSetting;
import app.revanced.extension.shared.spoof.ClientType;
public class Settings extends YouTubeAndMusicSettings {
// Ads
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true);
public static final BooleanSetting HIDE_GET_PREMIUM_LABEL = new BooleanSetting("revanced_music_hide_get_premium_label", TRUE, true);
// General
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_music_hide_cast_button", TRUE, true);
public static final BooleanSetting HIDE_CATEGORY_BAR = new BooleanSetting("revanced_music_hide_category_bar", FALSE, true);
public static final BooleanSetting HIDE_HISTORY_BUTTON = new BooleanSetting("revanced_music_hide_history_button", FALSE, true);
public static final BooleanSetting HIDE_SEARCH_BUTTON = new BooleanSetting("revanced_music_hide_search_button", FALSE, true);
public static final BooleanSetting HIDE_NOTIFICATION_BUTTON = new BooleanSetting("revanced_music_hide_notification_button", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR_HOME_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_home_button", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR_SAMPLES_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_samples_button", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR_EXPLORE_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_explore_button", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR_LIBRARY_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_library_button", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR_UPGRADE_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_upgrade_button", TRUE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR = new BooleanSetting("revanced_music_hide_navigation_bar", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR_LABEL = new BooleanSetting("revanced_music_hide_navigation_bar_labels", FALSE, true);
// Player
public static final BooleanSetting CHANGE_MINIPLAYER_COLOR = new BooleanSetting("revanced_music_change_miniplayer_color", FALSE, true);
public static final BooleanSetting PERMANENT_REPEAT = new BooleanSetting("revanced_music_play_permanent_repeat", FALSE, true);
// Miscellaneous
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type",
ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS));
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", TRUE, true);
}

View File

@@ -0,0 +1,93 @@
package app.revanced.extension.music.settings.preference;
import android.app.Dialog;
import android.preference.PreferenceScreen;
import android.widget.Toolbar;
import app.revanced.extension.music.settings.MusicActivityHook;
import app.revanced.extension.shared.GmsCoreSupport;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
/**
* Preference fragment for ReVanced settings.
*/
@SuppressWarnings("deprecation")
public class MusicPreferenceFragment extends ToolbarPreferenceFragment {
/**
* The main PreferenceScreen used to display the current set of preferences.
*/
private PreferenceScreen preferenceScreen;
/**
* Initializes the preference fragment.
*/
@Override
protected void initialize() {
super.initialize();
try {
preferenceScreen = getPreferenceScreen();
Utils.sortPreferenceGroups(preferenceScreen);
setPreferenceScreenToolbar(preferenceScreen);
// Clunky work around until preferences are custom classes that manage themselves.
// Custom branding only works with non-root install. But the preferences must be
// added during patched because of difficulties detecting during patching if it's
// a root install. So instead the non-functional preferences are removed during
// runtime if the app is mount (root) installation.
if (GmsCoreSupport.isPackageNameOriginal()) {
removePreferences(
BaseSettings.CUSTOM_BRANDING_ICON.key,
BaseSettings.CUSTOM_BRANDING_NAME.key);
}
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
}
}
/**
* Called when the fragment starts.
*/
@Override
public void onStart() {
super.onStart();
try {
// Initialize search controller if needed
if (MusicActivityHook.searchViewController != null) {
// Trigger search data collection after fragment is ready.
MusicActivityHook.searchViewController.initializeSearchData();
}
} catch (Exception ex) {
Logger.printException(() -> "onStart failure", ex);
}
}
/**
* Sets toolbar for all nested preference screens.
*/
@Override
protected void customizeToolbar(Toolbar toolbar) {
MusicActivityHook.setToolbarLayoutParams(toolbar);
}
/**
* Perform actions after toolbar setup.
*/
@Override
protected void onPostToolbarSetup(Toolbar toolbar, Dialog preferenceScreenDialog) {
if (MusicActivityHook.searchViewController != null
&& MusicActivityHook.searchViewController.isSearchActive()) {
toolbar.post(() -> MusicActivityHook.searchViewController.closeSearch());
}
}
/**
* Returns the preference screen for external access by SearchViewController.
*/
public PreferenceScreen getPreferenceScreenForSearch() {
return preferenceScreen;
}
}

View File

@@ -0,0 +1,28 @@
package app.revanced.extension.music.settings.search;
import android.content.Context;
import android.preference.PreferenceScreen;
import app.revanced.extension.shared.settings.search.BaseSearchResultsAdapter;
import app.revanced.extension.shared.settings.search.BaseSearchViewController;
import app.revanced.extension.shared.settings.search.BaseSearchResultItem;
import java.util.List;
/**
* Music-specific search results adapter.
*/
@SuppressWarnings("deprecation")
public class MusicSearchResultsAdapter extends BaseSearchResultsAdapter {
public MusicSearchResultsAdapter(Context context, List<BaseSearchResultItem> items,
BaseSearchViewController.BasePreferenceFragment fragment,
BaseSearchViewController searchViewController) {
super(context, items, fragment, searchViewController);
}
@Override
protected PreferenceScreen getMainPreferenceScreen() {
return fragment.getPreferenceScreenForSearch();
}
}

View File

@@ -0,0 +1,71 @@
package app.revanced.extension.music.settings.search;
import android.app.Activity;
import android.preference.Preference;
import android.preference.PreferenceScreen;
import android.view.View;
import android.widget.Toolbar;
import app.revanced.extension.music.settings.preference.MusicPreferenceFragment;
import app.revanced.extension.shared.settings.search.*;
/**
* Music-specific search view controller implementation.
*/
@SuppressWarnings("deprecation")
public class MusicSearchViewController extends BaseSearchViewController {
public static MusicSearchViewController addSearchViewComponents(Activity activity, Toolbar toolbar,
MusicPreferenceFragment fragment) {
return new MusicSearchViewController(activity, toolbar, fragment);
}
private MusicSearchViewController(Activity activity, Toolbar toolbar, MusicPreferenceFragment fragment) {
super(activity, toolbar, new PreferenceFragmentAdapter(fragment));
}
@Override
protected BaseSearchResultsAdapter createSearchResultsAdapter() {
return new MusicSearchResultsAdapter(activity, filteredSearchItems, fragment, this);
}
@Override
protected boolean isSpecialPreferenceGroup(Preference preference) {
// Music doesn't have SponsorBlock, so no special groups.
return false;
}
@Override
protected void setupSpecialPreferenceListeners(BaseSearchResultItem item) {
// Music doesn't have special preferences.
// This method can be empty or handle music-specific preferences if any.
}
// Static method for handling Activity finish
public static boolean handleFinish(MusicSearchViewController searchViewController) {
if (searchViewController != null && searchViewController.isSearchActive()) {
searchViewController.closeSearch();
return true;
}
return false;
}
// Adapter to wrap MusicPreferenceFragment to BasePreferenceFragment interface.
private record PreferenceFragmentAdapter(MusicPreferenceFragment fragment) implements BasePreferenceFragment {
@Override
public PreferenceScreen getPreferenceScreenForSearch() {
return fragment.getPreferenceScreenForSearch();
}
@Override
public View getView() {
return fragment.getView();
}
@Override
public Activity getActivity() {
return fragment.getActivity();
}
}
}

View File

@@ -1,27 +0,0 @@
package app.revanced.extension.music.spoof;
/**
* @noinspection unused
*/
public class SpoofClientPatch {
private static final int CLIENT_TYPE_ID = 26;
private static final String CLIENT_VERSION = "6.21";
private static final String DEVICE_MODEL = "iPhone16,2";
private static final String OS_VERSION = "17.7.2.21H221";
public static int getClientId() {
return CLIENT_TYPE_ID;
}
public static String getClientVersion() {
return CLIENT_VERSION;
}
public static String getClientModel() {
return DEVICE_MODEL;
}
public static String getOsVersion() {
return OS_VERSION;
}
}

View File

@@ -0,0 +1,15 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:nothingx:stub"))
}
android {
defaultConfig {
minSdk = 26
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,590 @@
package app.revanced.extension.nothingx.patches;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Application;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Patches to expose the K1 token for Nothing X app to enable pairing with GadgetBridge.
*/
@SuppressWarnings("unused")
public class ShowK1TokensPatch {
private static final String TAG = "ReVanced";
private static final String PACKAGE_NAME = "com.nothing.smartcenter";
private static final String EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e";
private static final String PREFS_NAME = "revanced_nothingx_prefs";
private static final String KEY_DONT_SHOW_DIALOG = "dont_show_k1_dialog";
// Colors
private static final int COLOR_BG = 0xFF1E1E1E;
private static final int COLOR_CARD = 0xFF2D2D2D;
private static final int COLOR_TEXT_PRIMARY = 0xFFFFFFFF;
private static final int COLOR_TEXT_SECONDARY = 0xFFB0B0B0;
private static final int COLOR_ACCENT = 0xFFFF9500;
private static final int COLOR_TOKEN_BG = 0xFF3A3A3A;
private static final int COLOR_BUTTON_POSITIVE = 0xFFFF9500;
private static final int COLOR_BUTTON_NEGATIVE = 0xFFFF6B6B;
// Match standalone K1: k1:, K1:, k1>, etc.
private static final Pattern K1_STANDALONE_PATTERN = Pattern.compile("(?i)(?:k1\\s*[:>]\\s*)([0-9a-f]{32})");
// Match combined r3+k1: format (64 chars = r3(32) + k1(32))
private static final Pattern K1_COMBINED_PATTERN = Pattern.compile("(?i)r3\\+k1\\s*:\\s*([0-9a-f]{64})");
private static volatile boolean k1Logged = false;
private static volatile boolean lifecycleCallbacksRegistered = false;
private static Context appContext;
/**
* Get K1 tokens from database and log files.
* Call this after the app initializes.
*
* @param context Application context
*/
public static void showK1Tokens(Context context) {
if (k1Logged) {
return;
}
appContext = context.getApplicationContext();
Set<String> allTokens = new LinkedHashSet<>();
// First try to get from database.
String dbToken = getK1TokensFromDatabase();
if (dbToken != null) {
allTokens.add(dbToken);
}
// Then get from log files.
Set<String> logTokens = getK1TokensFromLogFiles();
allTokens.addAll(logTokens);
if (allTokens.isEmpty()) {
return;
}
// Log all found tokens.
int index = 1;
for (String token : allTokens) {
Log.i(TAG, "#" + index++ + ": " + token.toUpperCase());
}
// Register lifecycle callbacks to show dialog when an Activity is ready.
registerLifecycleCallbacks(allTokens);
k1Logged = true;
}
/**
* Register ActivityLifecycleCallbacks to show dialog when first Activity resumes.
*
* @param tokens Set of K1 tokens to display
*/
private static void registerLifecycleCallbacks(Set<String> tokens) {
if (lifecycleCallbacksRegistered || !(appContext instanceof Application)) {
return;
}
Application application = (Application) appContext;
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
// Check if user chose not to show dialog.
SharedPreferences prefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
if (prefs.getBoolean(KEY_DONT_SHOW_DIALOG, false)) {
application.unregisterActivityLifecycleCallbacks(this);
lifecycleCallbacksRegistered = false;
return;
}
// Show dialog on first Activity resume.
if (tokens != null && !tokens.isEmpty()) {
activity.runOnUiThread(() -> showK1TokensDialog(activity, tokens));
// Unregister after showing
application.unregisterActivityLifecycleCallbacks(this);
lifecycleCallbacksRegistered = false;
}
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
lifecycleCallbacksRegistered = true;
}
/**
* Show dialog with K1 tokens.
*
* @param activity Activity context
* @param tokens Set of K1 tokens
*/
private static void showK1TokensDialog(Activity activity, Set<String> tokens) {
try {
// Create main container.
LinearLayout mainLayout = new LinearLayout(activity);
mainLayout.setOrientation(LinearLayout.VERTICAL);
mainLayout.setBackgroundColor(COLOR_BG);
mainLayout.setPadding(dpToPx(activity, 24), dpToPx(activity, 16),
dpToPx(activity, 24), dpToPx(activity, 16));
// Title.
TextView titleView = new TextView(activity);
titleView.setText("K1 Token(s) Found");
titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
titleView.setTypeface(Typeface.DEFAULT_BOLD);
titleView.setTextColor(COLOR_TEXT_PRIMARY);
titleView.setGravity(Gravity.CENTER);
mainLayout.addView(titleView, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
// Subtitle.
TextView subtitleView = new TextView(activity);
subtitleView.setText(tokens.size() == 1 ? "1 token found • Tap to copy" : tokens.size() + " tokens found • Tap to copy");
subtitleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
subtitleView.setTextColor(COLOR_TEXT_SECONDARY);
subtitleView.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams subtitleParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
subtitleParams.topMargin = dpToPx(activity, 4);
subtitleParams.bottomMargin = dpToPx(activity, 16);
mainLayout.addView(subtitleView, subtitleParams);
// Scrollable content.
ScrollView scrollView = new ScrollView(activity);
scrollView.setVerticalScrollBarEnabled(false);
LinearLayout.LayoutParams scrollParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
0,
1.0f
);
scrollParams.topMargin = dpToPx(activity, 8);
scrollParams.bottomMargin = dpToPx(activity, 16);
mainLayout.addView(scrollView, scrollParams);
LinearLayout tokensContainer = new LinearLayout(activity);
tokensContainer.setOrientation(LinearLayout.VERTICAL);
scrollView.addView(tokensContainer);
// Add each token as a card.
boolean singleToken = tokens.size() == 1;
int index = 1;
for (String token : tokens) {
LinearLayout tokenCard = createTokenCard(activity, token, index++, singleToken);
LinearLayout.LayoutParams cardParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
cardParams.bottomMargin = dpToPx(activity, 12);
tokensContainer.addView(tokenCard, cardParams);
}
// Info text.
TextView infoView = new TextView(activity);
infoView.setText(tokens.size() == 1 ? "Tap the token to copy it" : "Tap any token to copy it");
infoView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
infoView.setTextColor(COLOR_TEXT_SECONDARY);
infoView.setGravity(Gravity.CENTER);
infoView.setAlpha(0.7f);
LinearLayout.LayoutParams infoParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
infoParams.topMargin = dpToPx(activity, 8);
mainLayout.addView(infoView, infoParams);
// Button row.
LinearLayout buttonRow = new LinearLayout(activity);
buttonRow.setOrientation(LinearLayout.HORIZONTAL);
buttonRow.setGravity(Gravity.END);
LinearLayout.LayoutParams buttonRowParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
buttonRowParams.topMargin = dpToPx(activity, 16);
mainLayout.addView(buttonRow, buttonRowParams);
// "Don't show again" button.
Button dontShowButton = new Button(activity);
dontShowButton.setText("Don't show again");
dontShowButton.setTextColor(Color.WHITE);
dontShowButton.setBackgroundColor(Color.TRANSPARENT);
dontShowButton.setAllCaps(false);
dontShowButton.setTypeface(Typeface.DEFAULT);
dontShowButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
dontShowButton.setPadding(dpToPx(activity, 16), dpToPx(activity, 8),
dpToPx(activity, 16), dpToPx(activity, 8));
LinearLayout.LayoutParams dontShowParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
dontShowParams.rightMargin = dpToPx(activity, 8);
buttonRow.addView(dontShowButton, dontShowParams);
// "OK" button.
Button okButton = new Button(activity);
okButton.setText("OK");
okButton.setTextColor(Color.BLACK);
okButton.setBackgroundColor(COLOR_BUTTON_POSITIVE);
okButton.setAllCaps(false);
okButton.setTypeface(Typeface.DEFAULT_BOLD);
okButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
okButton.setPadding(dpToPx(activity, 24), dpToPx(activity, 12),
dpToPx(activity, 24), dpToPx(activity, 12));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
okButton.setElevation(dpToPx(activity, 4));
}
buttonRow.addView(okButton, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
// Build dialog.
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setView(mainLayout);
final AlertDialog dialog = builder.create();
// Style the dialog with dark background.
if (dialog.getWindow() != null) {
dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
}
dialog.show();
// Set button click listeners after dialog is created.
dontShowButton.setOnClickListener(v -> {
SharedPreferences prefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putBoolean(KEY_DONT_SHOW_DIALOG, true).apply();
Toast.makeText(activity, "Dialog disabled. Clear app data to re-enable.",
Toast.LENGTH_SHORT).show();
dialog.dismiss();
});
okButton.setOnClickListener(v -> {
dialog.dismiss();
});
} catch (Exception e) {
Log.e(TAG, "Failed to show K1 dialog", e);
}
}
/**
* Create a card view for a single token.
*/
private static LinearLayout createTokenCard(Activity activity, String token, int index, boolean singleToken) {
LinearLayout card = new LinearLayout(activity);
card.setOrientation(LinearLayout.VERTICAL);
card.setBackgroundColor(COLOR_TOKEN_BG);
card.setPadding(dpToPx(activity, 16), dpToPx(activity, 12),
dpToPx(activity, 16), dpToPx(activity, 12));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
card.setElevation(dpToPx(activity, 2));
}
card.setClickable(true);
card.setFocusable(true);
// Token label (only show if multiple tokens).
if (!singleToken) {
TextView labelView = new TextView(activity);
labelView.setText("Token #" + index);
labelView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
labelView.setTextColor(COLOR_ACCENT);
labelView.setTypeface(Typeface.DEFAULT_BOLD);
card.addView(labelView);
}
// Token value.
TextView tokenView = new TextView(activity);
tokenView.setText(token.toUpperCase());
tokenView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
tokenView.setTextColor(COLOR_TEXT_PRIMARY);
tokenView.setTypeface(Typeface.MONOSPACE);
tokenView.setLetterSpacing(0.05f);
LinearLayout.LayoutParams tokenParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
if (!singleToken) {
tokenParams.topMargin = dpToPx(activity, 8);
}
card.addView(tokenView, tokenParams);
// Click to copy.
card.setOnClickListener(v -> {
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
if (clipboard != null) {
clipboard.setText(token.toUpperCase());
Toast.makeText(activity, "Token copied!", Toast.LENGTH_SHORT).show();
}
});
return card;
}
/**
* Convert dp to pixels.
*/
private static int dpToPx(Context context, float dp) {
return (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp,
context.getResources().getDisplayMetrics()
);
}
/**
* Get K1 tokens from log files.
* Prioritizes pairing K1 tokens over reconnect tokens.
*/
private static Set<String> getK1TokensFromLogFiles() {
Set<String> pairingTokens = new LinkedHashSet<>();
Set<String> reconnectTokens = new LinkedHashSet<>();
try {
File logDir = new File("/data/data/" + PACKAGE_NAME + "/files/log");
if (!logDir.exists() || !logDir.isDirectory()) {
return pairingTokens;
}
File[] logFiles = logDir.listFiles((dir, name) ->
name.endsWith(".log") || name.endsWith(".log.") || name.matches(".*\\.log\\.\\d+"));
if (logFiles == null || logFiles.length == 0) {
return pairingTokens;
}
for (File logFile : logFiles) {
try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) {
String line;
while ((line = reader.readLine()) != null) {
// Determine if this is a pairing or reconnect context.
boolean isPairingContext = line.toLowerCase().contains("watchbind");
boolean isReconnectContext = line.toLowerCase().contains("watchreconnect");
String k1Token = null;
// First check for combined r3+k1 format (priority).
Matcher combinedMatcher = K1_COMBINED_PATTERN.matcher(line);
if (combinedMatcher.find()) {
String combined = combinedMatcher.group(1);
if (combined.length() == 64) {
// Second half is the actual K1
k1Token = combined.substring(32).toLowerCase();
}
}
// Then check for standalone K1 format (only if not found in combined).
if (k1Token == null) {
Matcher standaloneMatcher = K1_STANDALONE_PATTERN.matcher(line);
if (standaloneMatcher.find()) {
String token = standaloneMatcher.group(1);
if (token != null && token.length() == 32) {
k1Token = token.toLowerCase();
}
}
}
// Add to appropriate set.
if (k1Token != null) {
if (isPairingContext && !isReconnectContext) {
pairingTokens.add(k1Token);
} else {
reconnectTokens.add(k1Token);
}
}
}
} catch (Exception e) {
// Skip unreadable files.
}
}
} catch (Exception ex) {
// Fail silently.
}
// Return pairing tokens first, add reconnect tokens if no pairing tokens found.
if (!pairingTokens.isEmpty()) {
Log.i(TAG, "Found " + pairingTokens.size() + " pairing K1 token(s)");
return pairingTokens;
}
if (!reconnectTokens.isEmpty()) {
Log.i(TAG, "Found " + reconnectTokens.size() + " reconnect K1 token(s) (may not work for initial pairing)");
}
return reconnectTokens;
}
/**
* Try to get K1 tokens from the database.
*/
private static String getK1TokensFromDatabase() {
try {
File dbDir = new File("/data/data/" + PACKAGE_NAME + "/databases");
if (!dbDir.exists() || !dbDir.isDirectory()) {
return null;
}
File[] dbFiles = dbDir.listFiles((dir, name) ->
name.endsWith(".db") && !name.startsWith("google_app_measurement") && !name.contains("firebase"));
if (dbFiles == null || dbFiles.length == 0) {
return null;
}
for (File dbFile : dbFiles) {
String token = getK1TokensFromDatabase(dbFile);
if (token != null) {
return token;
}
}
return null;
} catch (Exception ex) {
return null;
}
}
/**
* Extract K1 tokens from a database file.
*/
private static String getK1TokensFromDatabase(File dbFile) {
SQLiteDatabase db = null;
try {
db = SQLiteDatabase.openDatabase(dbFile.getPath(), null, SQLiteDatabase.OPEN_READONLY);
// Get all tables.
Cursor cursor = db.rawQuery(
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'",
null
);
List<String> tables = new ArrayList<>();
while (cursor.moveToNext()) {
tables.add(cursor.getString(0));
}
cursor.close();
// Scan all columns for 32-char hex strings.
for (String table : tables) {
Cursor schemaCursor = null;
try {
schemaCursor = db.rawQuery("PRAGMA table_info(" + table + ")", null);
List<String> columns = new ArrayList<>();
while (schemaCursor.moveToNext()) {
columns.add(schemaCursor.getString(1));
}
schemaCursor.close();
for (String column : columns) {
Cursor dataCursor = null;
try {
dataCursor = db.query(table, new String[]{column}, null, null, null, null, null);
while (dataCursor.moveToNext()) {
String value = dataCursor.getString(0);
if (value != null && value.length() == 32 && value.matches("[0-9a-fA-F]{32}")) {
// Skip obviously fake tokens (MD5 of empty string).
if (!value.equalsIgnoreCase(EMPTY_MD5)) {
dataCursor.close();
db.close();
return value.toLowerCase();
}
}
}
} catch (Exception e) {
// Skip non-string columns.
} finally {
if (dataCursor != null) {
dataCursor.close();
}
}
}
} catch (Exception e) {
// Continue to next table.
} finally {
if (schemaCursor != null && !schemaCursor.isClosed()) {
schemaCursor.close();
}
}
}
return null;
} catch (Exception ex) {
return null;
} finally {
if (db != null && db.isOpen()) {
db.close();
}
}
}
/**
* Reset the logged flag (useful for testing or re-pairing).
*/
public static void resetK1Logged() {
k1Logged = false;
lifecycleCallbacksRegistered = false;
}
/**
* Reset the "don't show again" preference.
*/
public static void resetDontShowPreference() {
if (appContext != null) {
SharedPreferences prefs = appContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putBoolean(KEY_DONT_SHOW_DIALOG, false).apply();
}
}
}

View File

@@ -0,0 +1,17 @@
plugins {
alias(libs.plugins.android.library)
}
android {
namespace = "app.revanced.extension"
compileSdk = 34
defaultConfig {
minSdk = 26
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -16,6 +16,7 @@ import java.util.Arrays;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.ui.Dim;
import com.amazon.video.sdk.player.Player;
@@ -64,9 +65,8 @@ public class PlaybackSpeedPatch {
SpeedIconDrawable speedIcon = new SpeedIconDrawable();
speedButton.setImageDrawable(speedIcon);
int buttonSize = Utils.dipToPixels(48);
speedButton.setMinimumWidth(buttonSize);
speedButton.setMinimumHeight(buttonSize);
speedButton.setMinimumWidth(Dim.dp48);
speedButton.setMinimumHeight(Dim.dp48);
return speedButton;
}
@@ -197,11 +197,11 @@ class SpeedIconDrawable extends Drawable {
@Override
public int getIntrinsicWidth() {
return Utils.dipToPixels(32);
return Dim.dp32;
}
@Override
public int getIntrinsicHeight() {
return Utils.dipToPixels(32);
return Dim.dp32;
}
}
}

View File

@@ -0,0 +1,4 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:samsung:radio:stub"))
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,24 @@
package app.revanced.extension.samsung.radio.misc.fix.crash;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@SuppressWarnings("unused")
public final class FixCrashPatch {
/**
* Injection point.
* <p>
* Add the required permissions to the request list to avoid crashes on API 34+.
**/
public static final String[] fixPermissionRequestList(String[] perms) {
List<String> permsList = new ArrayList<>(Arrays.asList(perms));
if (permsList.contains("android.permission.POST_NOTIFICATIONS")) {
permsList.addAll(Arrays.asList("android.permission.RECORD_AUDIO", "android.permission.READ_PHONE_STATE", "android.permission.FOREGROUND_SERVICE_MICROPHONE"));
}
if (permsList.contains("android.permission.RECORD_AUDIO")) {
permsList.add("android.permission.FOREGROUND_SERVICE_MICROPHONE");
}
return permsList.toArray(new String[0]);
}
}

View File

@@ -0,0 +1,19 @@
package app.revanced.extension.samsung.radio.restrictions.device;
import android.os.SemSystemProperties;
import java.util.Arrays;
@SuppressWarnings("unused")
public final class BypassDeviceChecksPatch {
/**
* Injection point.
* <p>
* Check if the device has the required hardware
**/
public static final boolean checkIfDeviceIsIncompatible(String[] deviceList) {
String currentDevice = SemSystemProperties.getSalesCode();
return Arrays.asList(deviceList).contains(currentDevice);
}
}

View File

@@ -0,0 +1,17 @@
plugins {
alias(libs.plugins.android.library)
}
android {
namespace = "app.revanced.extension"
compileSdk = 34
defaultConfig {
minSdk = 24
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,7 @@
package android.os;
public class SemSystemProperties {
public static String getSalesCode() {
throw new UnsupportedOperationException("Stub");
}
}

View File

@@ -1,3 +1,4 @@
dependencies {
implementation(project(":extensions:shared:library"))
compileOnly(libs.okhttp)
}

View File

@@ -18,4 +18,5 @@ android {
dependencies {
compileOnly(libs.annotation)
compileOnly(libs.okhttp)
}

View File

@@ -1,6 +1,4 @@
package app.revanced.extension.youtube;
import androidx.annotation.NonNull;
package app.revanced.extension.shared;
import java.nio.charset.StandardCharsets;
@@ -39,7 +37,7 @@ public final class ByteTrieSearch extends TrieSearch<byte[]> {
return replacement;
}
public ByteTrieSearch(@NonNull byte[]... patterns) {
public ByteTrieSearch(byte[]... patterns) {
super(new ByteTrieNode(), patterns);
}
}

View File

@@ -27,12 +27,10 @@ import java.util.Locale;
import app.revanced.extension.shared.requests.Requester;
import app.revanced.extension.shared.requests.Route;
import app.revanced.extension.shared.ui.CustomDialog;
@SuppressWarnings("unused")
public class GmsCoreSupport {
private static final String PACKAGE_NAME_YOUTUBE = "com.google.android.youtube";
private static final String PACKAGE_NAME_YOUTUBE_MUSIC = "com.google.android.apps.youtube.music";
private static final String GMS_CORE_PACKAGE_NAME
= getGmsCoreVendorGroupId() + ".android.gms";
private static final Uri GMS_CORE_PROVIDER
@@ -52,6 +50,20 @@ public class GmsCoreSupport {
@Nullable
private static volatile Boolean DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
private static String getOriginalPackageName() {
return null; // Modified during patching.
}
/**
* @return If the current package name is the same as the original unpatched app.
* If `GmsCore support` was not included during patching, this returns true;
*/
public static boolean isPackageNameOriginal() {
String originalPackageName = getOriginalPackageName();
return originalPackageName == null
|| originalPackageName.equals(Utils.getContext().getPackageName());
}
private static void open(String queryOrLink) {
Logger.printInfo(() -> "Opening link: " + queryOrLink);
@@ -80,17 +92,17 @@ public class GmsCoreSupport {
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
Utils.runOnMainThreadDelayed(() -> {
// Create the custom dialog.
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
context,
str("gms_core_dialog_title"), // Title.
str(dialogMessageRef), // Message.
null, // No EditText.
str(positiveButtonTextRef), // OK button text.
str(dialogMessageRef), // Message.
null, // No EditText.
str(positiveButtonTextRef), // OK button text.
() -> onPositiveClickListener.onClick(null, 0), // Convert DialogInterface.OnClickListener to Runnable.
null, // No Cancel button action.
null, // No Neutral button text.
null, // No Neutral button action.
true // Dismiss dialog when onNeutralClick.
null, // No Cancel button action.
null, // No Neutral button text.
null, // No Neutral button action.
true // Dismiss dialog when onNeutralClick.
);
Dialog dialog = dialogPair.first;
@@ -112,11 +124,10 @@ public class GmsCoreSupport {
// Verify the user has not included GmsCore for a root installation.
// GmsCore Support changes the package name, but with a mounted installation
// all manifest changes are ignored and the original package name is used.
String packageName = context.getPackageName();
if (packageName.equals(PACKAGE_NAME_YOUTUBE) || packageName.equals(PACKAGE_NAME_YOUTUBE_MUSIC)) {
if (isPackageNameOriginal()) {
Logger.printInfo(() -> "App is mounted with root, but GmsCore patch was included");
// Cannot use localize text here, since the app will load
// resources from the unpatched app and all patch strings are missing.
// Cannot use localize text here, since the app will load resources
// from the unpatched app and all patch strings are missing.
Utils.showToastLong("The 'GmsCore support' patch breaks mount installations");
// Do not exit. If the app exits before launch completes (and without
@@ -249,8 +260,7 @@ public class GmsCoreSupport {
};
}
// Modified by a patch. Do not touch.
private static String getGmsCoreVendorGroupId() {
return "app.revanced";
return "app.revanced"; // Modified during patching.
}
}

View File

@@ -1,6 +1,4 @@
package app.revanced.extension.youtube;
import androidx.annotation.NonNull;
package app.revanced.extension.shared;
/**
* Text pattern searching using a prefix tree (trie).
@@ -28,7 +26,7 @@ public final class StringTrieSearch extends TrieSearch<String> {
}
}
public StringTrieSearch(@NonNull String... patterns) {
public StringTrieSearch(String... patterns) {
super(new StringTrieNode(), patterns);
}
}

View File

@@ -1,6 +1,5 @@
package app.revanced.extension.youtube;
package app.revanced.extension.shared;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
@@ -57,11 +56,13 @@ public abstract class TrieSearch<T> {
if (searchTextLength - searchTextIndex < patternLength - patternStartIndex) {
return false; // Remaining search text is shorter than the remaining leaf pattern and they cannot match.
}
for (int i = searchTextIndex, j = patternStartIndex; j < patternLength; i++, j++) {
if (enclosingNode.getCharValue(searchText, i) != enclosingNode.getCharValue(pattern, j)) {
return false;
}
}
return callback == null || callback.patternMatched(searchText,
searchTextIndex - patternStartIndex, patternLength, callbackParameter);
}
@@ -136,7 +137,7 @@ public abstract class TrieSearch<T> {
* @param patternLength Length of the pattern.
* @param callback Callback, where a value of NULL indicates to always accept a pattern match.
*/
private void addPattern(@NonNull T pattern, int patternIndex, int patternLength,
private void addPattern(T pattern, int patternIndex, int patternLength,
@Nullable TriePatternMatchedCallback<T> callback) {
if (patternIndex == patternLength) { // Reached the end of the pattern.
if (endOfPatternCallback == null) {
@@ -145,6 +146,7 @@ public abstract class TrieSearch<T> {
endOfPatternCallback.add(callback);
return;
}
if (leaf != null) {
// Reached end of the graph and a leaf exist.
// Recursively call back into this method and push the existing leaf down 1 level.
@@ -159,6 +161,7 @@ public abstract class TrieSearch<T> {
leaf = new TrieCompressedPath<>(pattern, patternIndex, patternLength, callback);
return;
}
final char character = getCharValue(pattern, patternIndex);
final int arrayIndex = hashIndexForTableSize(children.length, character);
TrieNode<T> child = children[arrayIndex];
@@ -183,6 +186,7 @@ public abstract class TrieSearch<T> {
//noinspection unchecked
TrieNode<T>[] replacement = new TrieNode[replacementArraySize];
addNodeToArray(replacement, child);
boolean collision = false;
for (TrieNode<T> existingChild : children) {
if (existingChild != null) {
@@ -195,6 +199,7 @@ public abstract class TrieSearch<T> {
if (collision) {
continue;
}
children = replacement;
return;
}
@@ -234,6 +239,7 @@ public abstract class TrieSearch<T> {
if (leaf != null && leaf.matches(startNode, searchText, searchTextEndIndex, searchTextIndex, callbackParameter)) {
return true; // Leaf exists and it matched the search text.
}
List<TriePatternMatchedCallback<T>> endOfPatternCallback = node.endOfPatternCallback;
if (endOfPatternCallback != null) {
final int matchStartIndex = searchTextIndex - currentMatchLength;
@@ -246,6 +252,7 @@ public abstract class TrieSearch<T> {
}
}
}
TrieNode<T>[] children = node.children;
if (children == null) {
return false; // Reached a graph end point and there's no further patterns to search.
@@ -278,9 +285,11 @@ public abstract class TrieSearch<T> {
if (leaf != null) {
numberOfPointers += 4; // Number of fields in leaf node.
}
if (endOfPatternCallback != null) {
numberOfPointers += endOfPatternCallback.size();
}
if (children != null) {
numberOfPointers += children.length;
for (TrieNode<T> child : children) {
@@ -308,13 +317,13 @@ public abstract class TrieSearch<T> {
private final List<T> patterns = new ArrayList<>();
@SafeVarargs
TrieSearch(@NonNull TrieNode<T> root, @NonNull T... patterns) {
TrieSearch(TrieNode<T> root, T... patterns) {
this.root = Objects.requireNonNull(root);
addPatterns(patterns);
}
@SafeVarargs
public final void addPatterns(@NonNull T... patterns) {
public final void addPatterns(T... patterns) {
for (T pattern : patterns) {
addPattern(pattern);
}
@@ -325,7 +334,7 @@ public abstract class TrieSearch<T> {
*
* @param pattern Pattern to add. Calling this with a zero length pattern does nothing.
*/
public void addPattern(@NonNull T pattern) {
public void addPattern(T pattern) {
addPattern(pattern, root.getTextLength(pattern), null);
}
@@ -333,31 +342,31 @@ public abstract class TrieSearch<T> {
* @param pattern Pattern to add. Calling this with a zero length pattern does nothing.
* @param callback Callback to determine if searching should halt when a match is found.
*/
public void addPattern(@NonNull T pattern, @NonNull TriePatternMatchedCallback<T> callback) {
public void addPattern(T pattern, TriePatternMatchedCallback<T> callback) {
addPattern(pattern, root.getTextLength(pattern), Objects.requireNonNull(callback));
}
void addPattern(@NonNull T pattern, int patternLength, @Nullable TriePatternMatchedCallback<T> callback) {
void addPattern(T pattern, int patternLength, @Nullable TriePatternMatchedCallback<T> callback) {
if (patternLength == 0) return; // Nothing to match
patterns.add(pattern);
root.addPattern(pattern, 0, patternLength, callback);
}
public final boolean matches(@NonNull T textToSearch) {
public final boolean matches(T textToSearch) {
return matches(textToSearch, 0);
}
public boolean matches(@NonNull T textToSearch, @NonNull Object callbackParameter) {
public boolean matches(T textToSearch, Object callbackParameter) {
return matches(textToSearch, 0, root.getTextLength(textToSearch),
Objects.requireNonNull(callbackParameter));
}
public boolean matches(@NonNull T textToSearch, int startIndex) {
public boolean matches(T textToSearch, int startIndex) {
return matches(textToSearch, startIndex, root.getTextLength(textToSearch));
}
public final boolean matches(@NonNull T textToSearch, int startIndex, int endIndex) {
public final boolean matches(T textToSearch, int startIndex, int endIndex) {
return matches(textToSearch, startIndex, endIndex, null);
}
@@ -370,11 +379,11 @@ public abstract class TrieSearch<T> {
* @param callbackParameter Optional parameter passed to the callbacks.
* @return If any pattern matched, and it's callback halted searching.
*/
public boolean matches(@NonNull T textToSearch, int startIndex, int endIndex, @Nullable Object callbackParameter) {
public boolean matches(T textToSearch, int startIndex, int endIndex, @Nullable Object callbackParameter) {
return matches(textToSearch, root.getTextLength(textToSearch), startIndex, endIndex, callbackParameter);
}
private boolean matches(@NonNull T textToSearch, int textToSearchLength, int startIndex, int endIndex,
private boolean matches(T textToSearch, int textToSearchLength, int startIndex, int endIndex,
@Nullable Object callbackParameter) {
if (endIndex > textToSearchLength) {
throw new IllegalArgumentException("endIndex: " + endIndex

View File

@@ -4,6 +4,8 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -12,10 +14,8 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -23,12 +23,7 @@ import android.os.Looper;
import android.preference.Preference;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.util.DisplayMetrics;
import android.util.Pair;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@@ -37,21 +32,15 @@ import android.view.Window;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.Toolbar;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.text.Bidi;
import java.text.Collator;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -68,7 +57,9 @@ import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference;
import app.revanced.extension.shared.ui.Dim;
@SuppressWarnings("NewApi")
public class Utils {
@SuppressLint("StaticFieldLeak")
@@ -85,6 +76,15 @@ public class Utils {
@Nullable
private static Boolean isDarkModeEnabled;
// Cached Collator instance with its locale.
@Nullable
private static Locale cachedCollatorLocale;
@Nullable
private static Collator cachedCollator;
private static final Pattern PUNCTUATION_PATTERN = Pattern.compile("\\p{P}+");
private static final Pattern DIACRITICS_PATTERN = Pattern.compile("\\p{M}");
private Utils() {
} // utility class
@@ -116,7 +116,7 @@ public class Utils {
}
/**
* @return The version name of the app, such as 19.11.43
* @return The version name of the app, such as 20.13.41
*/
public static String getAppVersionName() {
if (versionName == null) {
@@ -278,41 +278,67 @@ public class Utils {
* @return zero, if the resource is not found.
*/
@SuppressLint("DiscouragedApi")
public static int getResourceIdentifier(Context context, String resourceIdentifierName, String type) {
public static int getResourceIdentifier(Context context, String resourceIdentifierName, @Nullable String type) {
return context.getResources().getIdentifier(resourceIdentifierName, type, context.getPackageName());
}
public static int getResourceIdentifierOrThrow(Context context, String resourceIdentifierName, @Nullable String type) {
final int resourceId = getResourceIdentifier(context, resourceIdentifierName, type);
if (resourceId == 0) {
throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName
+ " type: " + type);
}
return resourceId;
}
/**
* @return zero, if the resource is not found.
* @see #getResourceIdentifierOrThrow(String, String)
*/
public static int getResourceIdentifier(String resourceIdentifierName, String type) {
public static int getResourceIdentifier(String resourceIdentifierName, @Nullable String type) {
return getResourceIdentifier(getContext(), resourceIdentifierName, type);
}
/**
* @return The resource identifier, or throws an exception if not found.
*/
public static int getResourceIdentifierOrThrow(String resourceIdentifierName, @Nullable String type) {
final int resourceId = getResourceIdentifier(getContext(), resourceIdentifierName, type);
if (resourceId == 0) {
throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName
+ " type: " + type);
}
return resourceId;
}
public static String getResourceString(int id) throws Resources.NotFoundException {
return getContext().getResources().getString(id);
}
public static int getResourceInteger(String resourceIdentifierName) throws Resources.NotFoundException {
return getContext().getResources().getInteger(getResourceIdentifier(resourceIdentifierName, "integer"));
return getContext().getResources().getInteger(getResourceIdentifierOrThrow(resourceIdentifierName, "integer"));
}
public static Animation getResourceAnimation(String resourceIdentifierName) throws Resources.NotFoundException {
return AnimationUtils.loadAnimation(getContext(), getResourceIdentifier(resourceIdentifierName, "anim"));
return AnimationUtils.loadAnimation(getContext(), getResourceIdentifierOrThrow(resourceIdentifierName, "anim"));
}
@ColorInt
public static int getResourceColor(String resourceIdentifierName) throws Resources.NotFoundException {
//noinspection deprecation
return getContext().getResources().getColor(getResourceIdentifier(resourceIdentifierName, "color"));
return getContext().getResources().getColor(getResourceIdentifierOrThrow(resourceIdentifierName, "color"));
}
public static int getResourceDimensionPixelSize(String resourceIdentifierName) throws Resources.NotFoundException {
return getContext().getResources().getDimensionPixelSize(getResourceIdentifier(resourceIdentifierName, "dimen"));
return getContext().getResources().getDimensionPixelSize(getResourceIdentifierOrThrow(resourceIdentifierName, "dimen"));
}
public static float getResourceDimension(String resourceIdentifierName) throws Resources.NotFoundException {
return getContext().getResources().getDimension(getResourceIdentifier(resourceIdentifierName, "dimen"));
return getContext().getResources().getDimension(getResourceIdentifierOrThrow(resourceIdentifierName, "dimen"));
}
public static String[] getResourceStringArray(String resourceIdentifierName) throws Resources.NotFoundException {
return getContext().getResources().getStringArray(getResourceIdentifier(resourceIdentifierName, "array"));
return getContext().getResources().getStringArray(getResourceIdentifierOrThrow(resourceIdentifierName, "array"));
}
public interface MatchFilter<T> {
@@ -323,13 +349,9 @@ public class Utils {
* Includes sub children.
*/
public static <R extends View> R getChildViewByResourceName(View view, String str) {
var child = view.findViewById(Utils.getResourceIdentifier(str, "id"));
if (child != null) {
//noinspection unchecked
return (R) child;
}
throw new IllegalArgumentException("View with resource name not found: " + str);
var child = view.findViewById(Utils.getResourceIdentifierOrThrow(str, "id"));
//noinspection unchecked
return (R) child;
}
/**
@@ -415,9 +437,9 @@ public class Utils {
}
public static void setClipboard(CharSequence text) {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context
ClipboardManager clipboard = (ClipboardManager) context
.getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText("ReVanced", text);
ClipData clip = ClipData.newPlainText("ReVanced", text);
clipboard.setPrimaryClip(clip);
}
@@ -577,7 +599,13 @@ public class Utils {
showToast(messageToToast, Toast.LENGTH_LONG);
}
private static void showToast(String messageToToast, int toastDuration) {
/**
* Safe to call from any thread.
*
* @param messageToToast Message to show.
* @param toastDuration Either {@link Toast#LENGTH_SHORT} or {@link Toast#LENGTH_LONG}.
*/
public static void showToast(String messageToToast, int toastDuration) {
Objects.requireNonNull(messageToToast);
runOnMainThreadNowOrLater(() -> {
Context currentContext = context;
@@ -679,6 +707,18 @@ public class Utils {
}
}
public static void openLink(String url) {
try {
Intent intent = new Intent("android.intent.action.VIEW", Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Logger.printInfo(() -> "Opening link with external browser: " + intent);
getContext().startActivity(intent);
} catch (Exception ex) {
Logger.printException(() -> "openLink failure", ex);
}
}
public enum NetworkType {
NONE,
MOBILE,
@@ -719,436 +759,51 @@ public class Utils {
}
/**
* Hide a view by setting its layout params to 0x0
* @param view The view to hide.
* Hides a view by setting its layout width and height to 0dp.
* Handles null layout params safely.
*
* @param view The view to hide. If null, does nothing.
*/
public static void hideViewByLayoutParams(View view) {
if (view instanceof LinearLayout) {
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0, 0);
view.setLayoutParams(layoutParams);
} else if (view instanceof FrameLayout) {
FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(0, 0);
view.setLayoutParams(layoutParams2);
} else if (view instanceof RelativeLayout) {
RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(0, 0);
view.setLayoutParams(layoutParams3);
} else if (view instanceof Toolbar) {
Toolbar.LayoutParams layoutParams4 = new Toolbar.LayoutParams(0, 0);
view.setLayoutParams(layoutParams4);
} else if (view instanceof ViewGroup) {
ViewGroup.LayoutParams layoutParams5 = new ViewGroup.LayoutParams(0, 0);
view.setLayoutParams(layoutParams5);
public static void hideViewByLayoutParams(@Nullable View view) {
if (view == null) return;
ViewGroup.LayoutParams params = view.getLayoutParams();
if (params == null) {
// Create generic 0x0 layout params accepted by all ViewGroups.
params = new ViewGroup.LayoutParams(0, 0);
} else {
ViewGroup.LayoutParams params = view.getLayoutParams();
params.width = 0;
params.height = 0;
view.setLayoutParams(params);
}
view.setLayoutParams(params);
}
/**
* Creates a custom dialog with a styled layout, including a title, message, buttons, and an
* optional EditText. The dialog's appearance adapts to the app's dark mode setting, with
* rounded corners and customizable button actions. Buttons adjust dynamically to their text
* content and are arranged in a single row if they fit within 80% of the screen width,
* with the Neutral button aligned to the left and OK/Cancel buttons centered on the right.
* If buttons do not fit, each is placed on a separate row, all aligned to the right.
* Configures the parameters of a dialog window, including its width, gravity, vertical offset and background dimming.
* The width is calculated as a percentage of the screen's portrait width and the vertical offset is specified in DIP.
* The default dialog background is removed to allow for custom styling.
*
* @param context Context used to create the dialog.
* @param title Title text of the dialog.
* @param message Message text of the dialog (supports Spanned for HTML), or null if replaced by EditText.
* @param editText EditText to include in the dialog, or null if no EditText is needed.
* @param okButtonText OK button text, or null to use the default "OK" string.
* @param onOkClick Action to perform when the OK button is clicked.
* @param onCancelClick Action to perform when the Cancel button is clicked, or null if no Cancel button is needed.
* @param neutralButtonText Neutral button text, or null if no Neutral button is needed.
* @param onNeutralClick Action to perform when the Neutral button is clicked, or null if no Neutral button is needed.
* @param dismissDialogOnNeutralClick If the dialog should be dismissed when the Neutral button is clicked.
* @return The Dialog and its main LinearLayout container.
* @param window The {@link Window} object to configure.
* @param gravity The gravity for positioning the dialog (e.g., {@link Gravity#BOTTOM}).
* @param yOffsetDip The vertical offset from the gravity position in DIP.
* @param widthPercentage The width of the dialog as a percentage of the screen's portrait width (0-100).
* @param dimAmount If true, sets the background dim amount to 0 (no dimming); if false, leaves the default dim amount.
*/
@SuppressWarnings("ExtractMethodRecommender")
public static Pair<Dialog, LinearLayout> createCustomDialog(
Context context, String title, CharSequence message, @Nullable EditText editText,
String okButtonText, Runnable onOkClick, Runnable onCancelClick,
@Nullable String neutralButtonText, @Nullable Runnable onNeutralClick,
boolean dismissDialogOnNeutralClick
) {
Logger.printDebug(() -> "Creating custom dialog with title: " + title);
Dialog dialog = new Dialog(context);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); // Remove default title bar.
// Preset size constants.
final int dip4 = dipToPixels(4);
final int dip8 = dipToPixels(8);
final int dip16 = dipToPixels(16);
final int dip24 = dipToPixels(24);
// Create main layout.
LinearLayout mainLayout = new LinearLayout(context);
mainLayout.setOrientation(LinearLayout.VERTICAL);
mainLayout.setPadding(dip24, dip16, dip24, dip24);
// Set rounded rectangle background.
ShapeDrawable mainBackground = new ShapeDrawable(new RoundRectShape(
createCornerRadii(28), null, null));
mainBackground.getPaint().setColor(getDialogBackgroundColor()); // Dialog background.
mainLayout.setBackground(mainBackground);
// Title.
if (!TextUtils.isEmpty(title)) {
TextView titleView = new TextView(context);
titleView.setText(title);
titleView.setTypeface(Typeface.DEFAULT_BOLD);
titleView.setTextSize(18);
titleView.setTextColor(getAppForegroundColor());
titleView.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
layoutParams.setMargins(0, 0, 0, dip16);
titleView.setLayoutParams(layoutParams);
mainLayout.addView(titleView);
}
// Create content container (message/EditText) inside a ScrollView only if message or editText is provided.
ScrollView contentScrollView = null;
LinearLayout contentContainer;
if (message != null || editText != null) {
contentScrollView = new ScrollView(context);
contentScrollView.setVerticalScrollBarEnabled(false); // Disable the vertical scrollbar.
contentScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
if (editText != null) {
ShapeDrawable scrollViewBackground = new ShapeDrawable(new RoundRectShape(
createCornerRadii(10), null, null));
scrollViewBackground.getPaint().setColor(getEditTextBackground());
contentScrollView.setPadding(dip8, dip8, dip8, dip8);
contentScrollView.setBackground(scrollViewBackground);
contentScrollView.setClipToOutline(true);
}
LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
0,
1.0f // Weight to take available space.
);
contentScrollView.setLayoutParams(contentParams);
contentContainer = new LinearLayout(context);
contentContainer.setOrientation(LinearLayout.VERTICAL);
contentScrollView.addView(contentContainer);
// Message (if not replaced by EditText).
if (editText == null) {
TextView messageView = new TextView(context);
messageView.setText(message); // Supports Spanned (HTML).
messageView.setTextSize(16);
messageView.setTextColor(getAppForegroundColor());
// Enable HTML link clicking if the message contains links.
if (message instanceof Spanned) {
messageView.setMovementMethod(LinkMovementMethod.getInstance());
}
LinearLayout.LayoutParams messageParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
messageView.setLayoutParams(messageParams);
contentContainer.addView(messageView);
}
// EditText (if provided).
if (editText != null) {
// Remove EditText from its current parent, if any.
ViewGroup parent = (ViewGroup) editText.getParent();
if (parent != null) {
parent.removeView(editText);
}
// Style the EditText to match the dialog theme.
editText.setTextColor(getAppForegroundColor());
editText.setBackgroundColor(Color.TRANSPARENT);
editText.setPadding(0, 0, 0, 0);
LinearLayout.LayoutParams editTextParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
contentContainer.addView(editText, editTextParams);
}
}
// Button container.
LinearLayout buttonContainer = new LinearLayout(context);
buttonContainer.setOrientation(LinearLayout.VERTICAL);
buttonContainer.removeAllViews();
LinearLayout.LayoutParams buttonContainerParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
buttonContainerParams.setMargins(0, dip16, 0, 0);
buttonContainer.setLayoutParams(buttonContainerParams);
// Lists to track buttons.
List<Button> buttons = new ArrayList<>();
List<Integer> buttonWidths = new ArrayList<>();
// Create buttons in order: Neutral, Cancel, OK.
if (neutralButtonText != null && onNeutralClick != null) {
Button neutralButton = addButton(
context,
neutralButtonText,
onNeutralClick,
false,
dismissDialogOnNeutralClick,
dialog
);
buttons.add(neutralButton);
neutralButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
buttonWidths.add(neutralButton.getMeasuredWidth());
}
if (onCancelClick != null) {
Button cancelButton = addButton(
context,
context.getString(android.R.string.cancel),
onCancelClick,
false,
true,
dialog
);
buttons.add(cancelButton);
cancelButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
buttonWidths.add(cancelButton.getMeasuredWidth());
}
if (onOkClick != null) {
Button okButton = addButton(
context,
okButtonText != null ? okButtonText : context.getString(android.R.string.ok),
onOkClick,
true,
true,
dialog
);
buttons.add(okButton);
okButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
buttonWidths.add(okButton.getMeasuredWidth());
}
// Handle button layout.
int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
int totalWidth = 0;
for (Integer width : buttonWidths) {
totalWidth += width;
}
if (buttonWidths.size() > 1) {
totalWidth += (buttonWidths.size() - 1) * dip8; // Add margins for gaps.
}
if (buttons.size() == 1) {
// Single button: stretch to full width.
Button singleButton = buttons.get(0);
LinearLayout singleContainer = new LinearLayout(context);
singleContainer.setOrientation(LinearLayout.HORIZONTAL);
singleContainer.setGravity(Gravity.CENTER);
ViewGroup parent = (ViewGroup) singleButton.getParent();
if (parent != null) {
parent.removeView(singleButton);
}
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
dipToPixels(36)
);
params.setMargins(0, 0, 0, 0);
singleButton.setLayoutParams(params);
singleContainer.addView(singleButton);
buttonContainer.addView(singleContainer);
} else if (buttons.size() > 1) {
// Check if buttons fit in one row.
if (totalWidth <= screenWidth * 0.8) {
// Single row: Neutral, Cancel, OK.
LinearLayout rowContainer = new LinearLayout(context);
rowContainer.setOrientation(LinearLayout.HORIZONTAL);
rowContainer.setGravity(Gravity.CENTER);
rowContainer.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
// Add all buttons with proportional weights and specific margins.
for (int i = 0; i < buttons.size(); i++) {
Button button = buttons.get(i);
ViewGroup parent = (ViewGroup) button.getParent();
if (parent != null) {
parent.removeView(button);
}
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
0,
dipToPixels(36),
buttonWidths.get(i) // Use measured width as weight.
);
// Set margins based on button type and combination.
if (buttons.size() == 2) {
// Neutral + OK or Cancel + OK.
if (i == 0) { // Neutral or Cancel.
params.setMargins(0, 0, dip4, 0);
} else { // OK
params.setMargins(dip4, 0, 0, 0);
}
} else if (buttons.size() == 3) {
if (i == 0) { // Neutral.
params.setMargins(0, 0, dip4, 0);
} else if (i == 1) { // Cancel
params.setMargins(dip4, 0, dip4, 0);
} else { // OK
params.setMargins(dip4, 0, 0, 0);
}
}
button.setLayoutParams(params);
rowContainer.addView(button);
}
buttonContainer.addView(rowContainer);
} else {
// Multiple rows: OK, Cancel, Neutral.
List<Button> reorderedButtons = new ArrayList<>();
// Reorder: OK, Cancel, Neutral.
if (onOkClick != null) {
reorderedButtons.add(buttons.get(buttons.size() - 1));
}
if (onCancelClick != null) {
reorderedButtons.add(buttons.get((neutralButtonText != null && onNeutralClick != null) ? 1 : 0));
}
if (neutralButtonText != null && onNeutralClick != null) {
reorderedButtons.add(buttons.get(0));
}
// Add each button in its own row with spacers.
for (int i = 0; i < reorderedButtons.size(); i++) {
Button button = reorderedButtons.get(i);
LinearLayout singleContainer = new LinearLayout(context);
singleContainer.setOrientation(LinearLayout.HORIZONTAL);
singleContainer.setGravity(Gravity.CENTER);
singleContainer.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
dipToPixels(36)
));
ViewGroup parent = (ViewGroup) button.getParent();
if (parent != null) {
parent.removeView(button);
}
LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
dipToPixels(36)
);
buttonParams.setMargins(0, 0, 0, 0);
button.setLayoutParams(buttonParams);
singleContainer.addView(button);
buttonContainer.addView(singleContainer);
// Add a spacer between the buttons (except the last one).
// Adding a margin between buttons is not suitable, as it conflicts with the single row layout.
if (i < reorderedButtons.size() - 1) {
View spacer = new View(context);
LinearLayout.LayoutParams spacerParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
dipToPixels(8)
);
spacer.setLayoutParams(spacerParams);
buttonContainer.addView(spacer);
}
}
}
}
// Add ScrollView to main layout only if content exist.
if (contentScrollView != null) {
mainLayout.addView(contentScrollView);
}
mainLayout.addView(buttonContainer);
dialog.setContentView(mainLayout);
// Set dialog window attributes.
Window window = dialog.getWindow();
if (window != null) {
setDialogWindowParameters(window);
}
return new Pair<>(dialog, mainLayout);
}
public static void setDialogWindowParameters(Window window) {
public static void setDialogWindowParameters(Window window, int gravity, int yOffsetDip, int widthPercentage, boolean dimAmount) {
WindowManager.LayoutParams params = window.getAttributes();
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
int portraitWidth = (int) (displayMetrics.widthPixels * 0.9);
if (Resources.getSystem().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
portraitWidth = (int) Math.min(portraitWidth, displayMetrics.heightPixels * 0.9);
}
params.width = portraitWidth;
params.width = Dim.pctPortraitWidth(widthPercentage);
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.gravity = Gravity.CENTER;
window.setAttributes(params);
window.setBackgroundDrawable(null); // Remove default dialog background.
}
params.gravity = gravity;
params.y = yOffsetDip > 0 ? Dim.dp(yOffsetDip) : 0;
if (dimAmount) {
params.dimAmount = 0f;
}
/**
* Adds a styled button to a dialog's button container with customizable text, click behavior, and appearance.
* The button's background and text colors adapt to the app's dark mode setting. Buttons stretch to full width
* when on separate rows or proportionally based on content when in a single row (Neutral, Cancel, OK order).
* When wrapped to separate rows, buttons are ordered OK, Cancel, Neutral.
*
* @param context Context to create the button and access resources.
* @param buttonText Button text to display.
* @param onClick Action to perform when the button is clicked, or null if no action is required.
* @param isOkButton If this is the OK button, which uses distinct background and text colors.
* @param dismissDialog If the dialog should be dismissed when the button is clicked.
* @param dialog The Dialog to dismiss when the button is clicked.
* @return The created Button.
*/
private static Button addButton(Context context, String buttonText, Runnable onClick,
boolean isOkButton, boolean dismissDialog, Dialog dialog) {
Button button = new Button(context, null, 0);
button.setText(buttonText);
button.setTextSize(14);
button.setAllCaps(false);
button.setSingleLine(true);
button.setEllipsize(android.text.TextUtils.TruncateAt.END);
button.setGravity(Gravity.CENTER);
ShapeDrawable background = new ShapeDrawable(new RoundRectShape(createCornerRadii(20), null, null));
int backgroundColor = isOkButton
? getOkButtonBackgroundColor() // Background color for OK button (inversion).
: getCancelOrNeutralButtonBackgroundColor(); // Background color for Cancel or Neutral buttons.
background.getPaint().setColor(backgroundColor);
button.setBackground(background);
button.setTextColor(isDarkModeEnabled()
? (isOkButton ? Color.BLACK : Color.WHITE)
: (isOkButton ? Color.WHITE : Color.BLACK));
// Set internal padding.
final int dip16 = dipToPixels(16);
button.setPadding(dip16, 0, dip16, 0);
button.setOnClickListener(v -> {
if (onClick != null) {
onClick.run();
}
if (dismissDialog) {
dialog.dismiss();
}
});
return button;
}
/**
* Creates an array of corner radii for a rounded rectangle shape.
*
* @param dp Radius in density-independent pixels (dip) to apply to all corners.
* @return An array of eight float values representing the corner radii
* (top-left, top-right, bottom-right, bottom-left).
*/
public static float[] createCornerRadii(float dp) {
final float radius = dipToPixels(dp);
return new float[]{radius, radius, radius, radius, radius, radius, radius, radius};
window.setAttributes(params); // Apply window attributes.
window.setBackgroundDrawable(null); // Remove default dialog background
}
/**
@@ -1310,30 +965,60 @@ public class Utils {
}
}
private static final Pattern punctuationPattern = Pattern.compile("\\p{P}+");
/**
* Strips all punctuation and converts to lower case. A null parameter returns an empty string.
* Removes punctuation and converts text to lowercase. Returns an empty string if input is null.
*/
public static String removePunctuationToLowercase(@Nullable CharSequence original) {
if (original == null) return "";
return punctuationPattern.matcher(original).replaceAll("")
return PUNCTUATION_PATTERN.matcher(original).replaceAll("")
.toLowerCase(BaseSettings.REVANCED_LANGUAGE.get().getLocale());
}
/**
* Sort a PreferenceGroup and all it's sub groups by title or key.
* Normalizes text for search: applies NFD, removes diacritics, and lowercases (locale-neutral).
* Returns an empty string if input is null.
*/
public static String normalizeTextToLowercase(@Nullable CharSequence original) {
if (original == null) return "";
return DIACRITICS_PATTERN.matcher(Normalizer.normalize(original, Normalizer.Form.NFD))
.replaceAll("").toLowerCase(Locale.ROOT);
}
/**
* Returns a cached Collator for the current locale, or creates a new one if locale changed.
*/
private static Collator getCollator() {
Locale currentLocale = BaseSettings.REVANCED_LANGUAGE.get().getLocale();
if (cachedCollator == null || !currentLocale.equals(cachedCollatorLocale)) {
cachedCollatorLocale = currentLocale;
cachedCollator = Collator.getInstance(currentLocale);
cachedCollator.setStrength(Collator.SECONDARY); // Case-insensitive, diacritic-insensitive.
}
return cachedCollator;
}
/**
* Sorts a {@link PreferenceGroup} and all nested subgroups by title or key.
* <p>
* The sort order is controlled by the {@link Sort} suffix present in the preference key.
* Preferences without a key or without a {@link Sort} suffix remain in their original order.
* <p>
* Sorting is performed using {@link Collator} with the current user locale,
* ensuring correct alphabetical ordering for all supported languages
* (e.g., Ukrainian "і", German "ß", French accented characters, etc.).
*
* Sort order is determined by the preferences key {@link Sort} suffix.
*
* If a preference has no key or no {@link Sort} suffix,
* then the preferences are left unsorted.
* @param group the {@link PreferenceGroup} to sort
*/
@SuppressWarnings("deprecation")
public static void sortPreferenceGroups(PreferenceGroup group) {
Sort groupSort = Sort.fromKey(group.getKey(), Sort.UNSORTED);
List<Pair<String, Preference>> preferences = new ArrayList<>();
// Get cached Collator for locale-aware string comparison.
Collator collator = getCollator();
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
Preference preference = group.getPreference(i);
@@ -1364,10 +1049,11 @@ public class Utils {
preferences.add(new Pair<>(sortValue, preference));
}
//noinspection ComparatorCombinators
// Sort the list using locale-specific collation rules.
Collections.sort(preferences, (pair1, pair2)
-> pair1.first.compareTo(pair2.first));
-> collator.compare(pair1.first, pair2.first));
// Reassign order values to reflect the new sorted sequence
int index = 0;
for (Pair<String, Preference> pair : preferences) {
int order = index++;
@@ -1388,7 +1074,7 @@ public class Utils {
* Set all preferences to multiline titles if the device is not using an English variant.
* The English strings are heavily scrutinized and all titles fit on screen
* except 2 or 3 preference strings and those do not affect readability.
*
* <p>
* Allowing multiline for those 2 or 3 English preferences looks weird and out of place,
* and visually it looks better to clip the text and keep all titles 1 line.
*/
@@ -1424,42 +1110,6 @@ public class Utils {
return getResourceColor(colorString);
}
/**
* Converts dip value to actual device pixels.
*
* @param dip The density-independent pixels value.
* @return The device pixel value.
*/
public static int dipToPixels(float dip) {
return (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dip,
Resources.getSystem().getDisplayMetrics()
);
}
/**
* Converts a percentage of the screen height to actual device pixels.
*
* @param percentage The percentage of the screen height (e.g., 30 for 30%).
* @return The device pixel value corresponding to the percentage of screen height.
*/
public static int percentageHeightToPixels(int percentage) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int) (metrics.heightPixels * (percentage / 100.0f));
}
/**
* Converts a percentage of the screen width to actual device pixels.
*
* @param percentage The percentage of the screen width (e.g., 30 for 30%).
* @return The device pixel value corresponding to the percentage of screen width.
*/
public static int percentageWidthToPixels(int percentage) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int) (metrics.widthPixels * (percentage / 100.0f));
}
/**
* Uses {@link #adjustColorBrightness(int, float)} depending if light or dark mode is active.
*/
@@ -1497,9 +1147,9 @@ public class Utils {
blue = Math.round(blue + (255 - blue) * t);
} else {
// Darken or no change: Scale toward black.
red *= factor;
green *= factor;
blue *= factor;
red = Math.round(red * factor);
green = Math.round(green * factor);
blue = Math.round(blue * factor);
}
// Ensure values are within [0, 255].

View File

@@ -3,7 +3,6 @@ package app.revanced.extension.shared.checks;
import static android.text.Html.FROM_HTML_MODE_COMPACT;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.DialogFragmentOnStartAction;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.annotation.SuppressLint;
import android.app.Activity;
@@ -26,6 +25,7 @@ import java.util.Collection;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.ui.CustomDialog;
abstract class Check {
private static final int NUMBER_OF_TIMES_TO_IGNORE_WARNING_BEFORE_DISABLING = 2;
@@ -93,7 +93,7 @@ abstract class Check {
Utils.runOnMainThreadDelayed(() -> {
// Create the custom dialog.
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
activity,
str("revanced_check_environment_failed_title"), // Title.
message, // Message.
@@ -127,7 +127,8 @@ abstract class Check {
// Add icon to the dialog.
ImageView iconView = new ImageView(activity);
iconView.setImageResource(Utils.getResourceIdentifier("revanced_ic_dialog_alert", "drawable"));
iconView.setImageResource(Utils.getResourceIdentifierOrThrow(
"revanced_ic_dialog_alert", "drawable"));
iconView.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
iconView.setPadding(0, 0, 0, 0);
LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(
@@ -158,8 +159,8 @@ abstract class Check {
Button ignoreButton;
// Check if buttons are in a single-row layout (buttonContainer has one child: rowContainer).
if (buttonContainer.getChildCount() == 1 && buttonContainer.getChildAt(0) instanceof LinearLayout) {
LinearLayout rowContainer = (LinearLayout) buttonContainer.getChildAt(0);
if (buttonContainer.getChildCount() == 1
&& buttonContainer.getChildAt(0) instanceof LinearLayout rowContainer) {
// Neutral button is the first child (index 0).
ignoreButton = (Button) rowContainer.getChildAt(0);
// OK button is the last child.

View File

@@ -0,0 +1,71 @@
package app.revanced.extension.shared.fixes.redgifs;
import androidx.annotation.NonNull;
import org.json.JSONException;
import java.io.IOException;
import java.net.HttpURLConnection;
import app.revanced.extension.shared.Logger;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public abstract class BaseFixRedgifsApiPatch implements Interceptor {
protected static BaseFixRedgifsApiPatch INSTANCE;
public abstract String getDefaultUserAgent();
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
Request request = chain.request();
if (!request.url().host().equals("api.redgifs.com")) {
return chain.proceed(request);
}
String userAgent = getDefaultUserAgent();
if (request.header("Authorization") != null) {
Response response = chain.proceed(request.newBuilder().header("User-Agent", userAgent).build());
if (response.isSuccessful()) {
return response;
}
// It's possible that the user agent is being overwritten later down in the interceptor
// chain, so make sure we grab the new user agent from the request headers.
userAgent = response.request().header("User-Agent");
response.close();
}
try {
RedgifsTokenManager.RedgifsToken token = RedgifsTokenManager.refreshToken(userAgent);
// Emulate response for old OAuth endpoint
if (request.url().encodedPath().equals("/v2/oauth/client")) {
String responseBody = RedgifsTokenManager.getEmulatedOAuthResponseBody(token);
return new Response.Builder()
.message("OK")
.code(HttpURLConnection.HTTP_OK)
.protocol(Protocol.HTTP_1_1)
.request(request)
.header("Content-Type", "application/json")
.body(ResponseBody.create(
responseBody, MediaType.get("application/json")))
.build();
}
Request modifiedRequest = request.newBuilder()
.header("Authorization", "Bearer " + token.getAccessToken())
.header("User-Agent", userAgent)
.build();
return chain.proceed(modifiedRequest);
} catch (JSONException ex) {
Logger.printException(() -> "Could not parse Redgifs response", ex);
throw new IOException(ex);
}
}
}

View File

@@ -0,0 +1,94 @@
package app.revanced.extension.shared.fixes.redgifs;
import static app.revanced.extension.shared.requests.Route.Method.GET;
import androidx.annotation.GuardedBy;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import app.revanced.extension.shared.requests.Requester;
/**
* Manages Redgifs token lifecycle.
*/
public class RedgifsTokenManager {
public static class RedgifsToken {
// Expire after 23 hours to provide some breathing room
private static final long EXPIRY_SECONDS = 23 * 60 * 60;
private final String accessToken;
private final long refreshTimeInSeconds;
public RedgifsToken(String accessToken, long refreshTime) {
this.accessToken = accessToken;
this.refreshTimeInSeconds = refreshTime;
}
public String getAccessToken() {
return accessToken;
}
public long getExpiryTimeInSeconds() {
return refreshTimeInSeconds + EXPIRY_SECONDS;
}
public boolean isValid() {
if (accessToken == null) return false;
return getExpiryTimeInSeconds() >= System.currentTimeMillis() / 1000;
}
}
public static final String REDGIFS_API_HOST = "https://api.redgifs.com";
private static final String GET_TEMPORARY_TOKEN = REDGIFS_API_HOST + "/v2/auth/temporary";
@GuardedBy("itself")
private static final Map<String, RedgifsToken> tokenMap = new HashMap<>();
private static String getToken(String userAgent) throws IOException, JSONException {
HttpURLConnection connection = (HttpURLConnection) new URL(GET_TEMPORARY_TOKEN).openConnection();
connection.setFixedLengthStreamingMode(0);
connection.setRequestMethod(GET.name());
connection.setRequestProperty("User-Agent", userAgent);
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
connection.setUseCaches(false);
JSONObject responseObject = Requester.parseJSONObject(connection);
return responseObject.getString("token");
}
public static RedgifsToken refreshToken(String userAgent) throws IOException, JSONException {
synchronized(tokenMap) {
// Reference: https://github.com/JeffreyCA/Apollo-ImprovedCustomApi/pull/67
RedgifsToken token = tokenMap.get(userAgent);
if (token != null && token.isValid()) {
return token;
}
// Copy user agent from original request if present because Redgifs verifies
// that the user agent in subsequent requests matches the one in the OAuth token.
String accessToken = getToken(userAgent);
long refreshTime = System.currentTimeMillis() / 1000;
token = new RedgifsToken(accessToken, refreshTime);
tokenMap.put(userAgent, token);
return token;
}
}
public static String getEmulatedOAuthResponseBody(RedgifsToken token) throws JSONException {
// Reference: https://github.com/JeffreyCA/Apollo-ImprovedCustomApi/pull/67
JSONObject responseObject = new JSONObject();
responseObject.put("access_token", token.accessToken);
responseObject.put("expiry_time", token.getExpiryTimeInSeconds() - (System.currentTimeMillis() / 1000));
responseObject.put("scope", "read");
responseObject.put("token_type", "Bearer");
return responseObject.toString();
}
}

View File

@@ -1,4 +1,4 @@
package app.revanced.extension.youtube.patches;
package app.revanced.extension.shared.patches;
import static app.revanced.extension.shared.StringRef.str;
@@ -13,7 +13,8 @@ import java.net.UnknownHostException;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.ui.CustomDialog;
@SuppressWarnings("unused")
public class CheckWatchHistoryDomainNameResolutionPatch {
@@ -45,10 +46,10 @@ public class CheckWatchHistoryDomainNameResolutionPatch {
/**
* Injection point.
*
* Checks if s.youtube.com is blacklisted and playback history will fail to work.
* Checks if YouTube watch history endpoint cannot be reached.
*/
public static void checkDnsResolver(Activity context) {
if (!Utils.isNetworkConnected() || !Settings.CHECK_WATCH_HISTORY_DOMAIN_NAME.get()) return;
if (!Utils.isNetworkConnected() || !BaseSettings.CHECK_WATCH_HISTORY_DOMAIN_NAME.get()) return;
Utils.runOnBackgroundThread(() -> {
try {
@@ -60,34 +61,30 @@ public class CheckWatchHistoryDomainNameResolutionPatch {
// Prevent this false positive by verify youtube.com resolves.
// If youtube.com does not resolve, then it's not a watch history domain resolving error
// because the entire app will not work since no domains are resolving.
if (domainResolvesToValidIP(HISTORY_TRACKING_ENDPOINT)
|| !domainResolvesToValidIP("youtube.com")) {
String domainYouTube = "youtube.com";
if (!domainResolvesToValidIP(domainYouTube)
|| domainResolvesToValidIP(HISTORY_TRACKING_ENDPOINT)
// Check multiple times, so a false positive from a flaky connection is almost impossible.
|| !domainResolvesToValidIP(domainYouTube)
|| domainResolvesToValidIP(HISTORY_TRACKING_ENDPOINT)) {
return;
}
Utils.runOnMainThread(() -> {
try {
// Create the custom dialog.
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
str("revanced_check_watch_history_domain_name_dialog_title"), // Title.
Html.fromHtml(str("revanced_check_watch_history_domain_name_dialog_message")), // Message (HTML).
null, // No EditText.
null, // OK button text.
() -> {}, // OK button action (just dismiss).
() -> {}, // Cancel button action (just dismiss).
str("revanced_check_watch_history_domain_name_dialog_ignore"), // Neutral button text.
() -> Settings.CHECK_WATCH_HISTORY_DOMAIN_NAME.save(false), // Neutral button action (Ignore).
true // Dismiss dialog on Neutral button click.
);
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
context,
str("revanced_check_watch_history_domain_name_dialog_title"), // Title.
Html.fromHtml(str("revanced_check_watch_history_domain_name_dialog_message")), // Message (HTML).
null, // No EditText.
null, // OK button text.
() -> {}, // OK button action (just dismiss).
null, // No cancel button.
str("revanced_check_watch_history_domain_name_dialog_ignore"), // Neutral button text.
() -> BaseSettings.CHECK_WATCH_HISTORY_DOMAIN_NAME.save(false), // Neutral button action (Ignore).
true // Dismiss dialog on Neutral button click.
);
// Show the dialog.
Dialog dialog = dialogPair.first;
Utils.showDialog(context, dialog, false, null);
} catch (Exception ex) {
Logger.printException(() -> "checkDnsResolver dialog creation failure", ex);
}
Utils.showDialog(context, dialogPair.first, false, null);
});
} catch (Exception ex) {
Logger.printException(() -> "checkDnsResolver failure", ex);

View File

@@ -0,0 +1,179 @@
package app.revanced.extension.shared.patches;
import android.app.Notification;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import app.revanced.extension.shared.GmsCoreSupport;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
/**
* Patch shared by YouTube and YT Music.
*/
@SuppressWarnings("unused")
public class CustomBrandingPatch {
// Important: In the future, additional branding themes can be added but all existing and prior
// themes cannot be removed or renamed.
//
// This is because if a user has a branding theme selected, then only that launch alias is enabled.
// If a future update removes or renames that alias, then after updating the app is effectively
// broken and it cannot be opened and not even clearing the app data will fix it.
// In that situation the only fix is to completely uninstall and reinstall again.
//
// The most that can be done is to hide a theme from the UI and keep the alias with dummy data.
public enum BrandingTheme {
/**
* Original unpatched icon.
*/
ORIGINAL,
ROUNDED,
MINIMAL,
SCALED,
/**
* User provided custom icon.
*/
CUSTOM;
private String packageAndNameIndexToClassAlias(String packageName, int appIndex) {
if (appIndex <= 0) {
throw new IllegalArgumentException("App index starts at index 1");
}
return packageName + ".revanced_" + name().toLowerCase(Locale.US) + '_' + appIndex;
}
}
private static final int notificationSmallIcon;
static {
BrandingTheme branding = BaseSettings.CUSTOM_BRANDING_ICON.get();
if (branding == BrandingTheme.ORIGINAL) {
notificationSmallIcon = 0;
} else {
// Original icon is quantum_ic_video_youtube_white_24
String iconName = "revanced_notification_icon";
if (branding == BrandingTheme.CUSTOM) {
iconName += "_custom";
}
notificationSmallIcon = Utils.getResourceIdentifier(iconName, "drawable");
if (notificationSmallIcon == 0) {
Logger.printException(() -> "Could not load notification small icon");
}
}
}
/**
* Injection point.
*/
public static View getLottieViewOrNull(View lottieStartupView) {
if (BaseSettings.CUSTOM_BRANDING_ICON.get() == BrandingTheme.ORIGINAL) {
return lottieStartupView;
}
return null;
}
/**
* Injection point.
*/
public static void setNotificationIcon(Notification.Builder builder) {
try {
if (notificationSmallIcon != 0) {
builder.setSmallIcon(notificationSmallIcon)
.setColor(Color.TRANSPARENT); // Remove YT red tint.
}
} catch (Exception ex) {
Logger.printException(() -> "setNotificationIcon failure", ex);
}
}
/**
* Injection point.
*
* The total number of app name aliases, including dummy aliases.
*/
private static int numberOfPresetAppNames() {
// Modified during patching.
throw new IllegalStateException();
}
/**
* Injection point.
*/
@SuppressWarnings("ConstantConditions")
public static void setBranding() {
try {
if (GmsCoreSupport.isPackageNameOriginal()) {
Logger.printInfo(() -> "App is root mounted. Cannot dynamically change app icon");
return;
}
Context context = Utils.getContext();
PackageManager pm = context.getPackageManager();
String packageName = context.getPackageName();
BrandingTheme selectedBranding = BaseSettings.CUSTOM_BRANDING_ICON.get();
final int selectedNameIndex = BaseSettings.CUSTOM_BRANDING_NAME.get();
ComponentName componentToEnable = null;
ComponentName defaultComponent = null;
List<ComponentName> componentsToDisable = new ArrayList<>();
for (BrandingTheme theme : BrandingTheme.values()) {
// Must always update all aliases including custom alias (last index).
final int numberOfPresetAppNames = numberOfPresetAppNames();
// App name indices starts at 1.
for (int index = 1; index <= numberOfPresetAppNames; index++) {
String aliasClass = theme.packageAndNameIndexToClassAlias(packageName, index);
ComponentName component = new ComponentName(packageName, aliasClass);
if (defaultComponent == null) {
// Default is always the first alias.
defaultComponent = component;
}
if (index == selectedNameIndex && theme == selectedBranding) {
componentToEnable = component;
} else {
componentsToDisable.add(component);
}
}
}
if (componentToEnable == null) {
// User imported a bad app name index value. Either the imported data
// was corrupted, or they previously had custom name enabled and the app
// no longer has a custom name specified.
Utils.showToastLong("Custom branding reset");
BaseSettings.CUSTOM_BRANDING_ICON.resetToDefault();
BaseSettings.CUSTOM_BRANDING_NAME.resetToDefault();
componentToEnable = defaultComponent;
componentsToDisable.remove(defaultComponent);
}
for (ComponentName disable : componentsToDisable) {
pm.setComponentEnabledSetting(disable,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
}
// Use info logging because if the alias status become corrupt the app cannot launch.
ComponentName componentToEnableFinal = componentToEnable;
Logger.printInfo(() -> "Enabling: " + componentToEnableFinal.getClassName());
pm.setComponentEnabledSetting(componentToEnable,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
} catch (Exception ex) {
Logger.printException(() -> "setBranding failure", ex);
}
}
}

View File

@@ -1,5 +1,9 @@
package app.revanced.extension.youtube.patches;
package app.revanced.extension.shared.patches;
import static java.lang.Boolean.TRUE;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -21,12 +25,28 @@ public final class EnableDebuggingPatch {
? new ConcurrentHashMap<>(800, 0.5f, 1)
: null;
private static final Set<Long> DISABLED_FEATURE_FLAGS = parseFlags(BaseSettings.DISABLED_FEATURE_FLAGS.get());
// Log all disabled flags on app startup.
static {
if (LOG_FEATURE_FLAGS && !DISABLED_FEATURE_FLAGS.isEmpty()) {
StringBuilder sb = new StringBuilder("Disabled feature flags:\n");
for (Long flag : DISABLED_FEATURE_FLAGS) {
sb.append(" ").append(flag).append('\n');
}
Logger.printDebug(sb::toString);
}
}
/**
* Injection point.
*/
public static boolean isBooleanFeatureFlagEnabled(boolean value, Long flag) {
if (LOG_FEATURE_FLAGS && value) {
if (featureFlags.putIfAbsent(flag, true) == null) {
if (DISABLED_FEATURE_FLAGS.contains(flag)) {
return false;
}
if (featureFlags.putIfAbsent(flag, TRUE) == null) {
Logger.printDebug(() -> "boolean feature is enabled: " + flag);
}
}
@@ -70,10 +90,44 @@ public final class EnableDebuggingPatch {
if (LOG_FEATURE_FLAGS && !defaultValue.equals(value)) {
if (featureFlags.putIfAbsent(flag, true) == null) {
Logger.printDebug(() -> " string feature is enabled: " + flag
+ " value: " + value + (defaultValue.isEmpty() ? "" : " default: " + defaultValue));
+ " value: " + value + (defaultValue.isEmpty() ? "" : " default: " + defaultValue));
}
}
return value;
}
/**
* Get all logged feature flags.
* @return Set of all known flags
*/
public static Set<Long> getAllLoggedFlags() {
if (featureFlags != null) {
return new HashSet<>(featureFlags.keySet());
}
return new HashSet<>();
}
/**
* Public method for parsing flags.
* @param flags String containing newline-separated flag IDs
* @return Set of parsed flag IDs
*/
public static Set<Long> parseFlags(String flags) {
Set<Long> parsedFlags = new HashSet<>();
if (!flags.isBlank()) {
for (String flag : flags.split("\n")) {
String trimmedFlag = flag.trim();
if (trimmedFlag.isEmpty()) continue; // Skip empty lines.
try {
parsedFlags.add(Long.parseLong(trimmedFlag));
} catch (NumberFormatException e) {
Logger.printException(() -> "Invalid flag ID: " + flag);
}
}
}
return parsedFlags;
}
}

View File

@@ -0,0 +1,71 @@
package app.revanced.extension.shared.patches;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.spoof.ClientType;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
@SuppressWarnings("unused")
public class ForceOriginalAudioPatch {
private static final String DEFAULT_AUDIO_TRACKS_SUFFIX = ".4";
private static volatile boolean enabled;
public static void setEnabled(boolean isEnabled, ClientType client) {
enabled = isEnabled;
if (isEnabled && !client.useAuth && !client.supportsMultiAudioTracks) {
// If client spoofing does not use authentication and lacks multi-audio streams,
// then can use any language code for the request and if that requested language is
// not available YT uses the original audio language. Authenticated requests ignore
// the language code and always use the account language. Use a language that is
// not auto-dubbed by YouTube: https://support.google.com/youtube/answer/15569972
// but the language is also supported natively by the Meta Quest device that
// Android VR is spoofing.
AppLanguage override = AppLanguage.NB; // Norwegian Bokmal.
Logger.printDebug(() -> "Setting language override: " + override);
SpoofVideoStreamsPatch.setLanguageOverride(override);
}
}
/**
* Injection point.
*/
public static boolean ignoreDefaultAudioStream(boolean original) {
if (enabled) {
return false;
}
return original;
}
/**
* Injection point.
*/
public static boolean isDefaultAudioStream(boolean isDefault, String audioTrackId, String audioTrackDisplayName) {
try {
if (!enabled) {
return isDefault;
}
if (audioTrackId.isEmpty()) {
// Older app targets can have empty audio tracks and these might be placeholders.
// The real audio tracks are called after these.
return isDefault;
}
Logger.printDebug(() -> "default: " + String.format("%-5s", isDefault) + " id: "
+ String.format("%-8s", audioTrackId) + " name:" + audioTrackDisplayName);
final boolean isOriginal = audioTrackId.endsWith(DEFAULT_AUDIO_TRACKS_SUFFIX);
if (isOriginal) {
Logger.printDebug(() -> "Using audio: " + audioTrackId);
}
return isOriginal;
} catch (Exception ex) {
Logger.printException(() -> "isDefaultAudioStream failure", ex);
return isDefault;
}
}
}

View File

@@ -0,0 +1,31 @@
package app.revanced.extension.shared.patches;
import app.revanced.extension.shared.privacy.LinkSanitizer;
import app.revanced.extension.shared.settings.BaseSettings;
/**
* YouTube and YouTube Music.
*/
@SuppressWarnings("unused")
public final class SanitizeSharingLinksPatch {
private static final LinkSanitizer sanitizer = new LinkSanitizer(
"si",
"feature" // Old tracking parameter name, and may be obsolete.
);
/**
* Injection point.
*/
public static String sanitize(String url) {
if (BaseSettings.SANITIZE_SHARED_LINKS.get()) {
url = sanitizer.sanitizeUrlString(url);
}
if (BaseSettings.REPLACE_MUSIC_LINKS_WITH_YOUTUBE.get()) {
url = url.replace("music.youtube.com", "youtube.com");
}
return url;
}
}

View File

@@ -1,4 +1,4 @@
package app.revanced.extension.youtube.patches.components;
package app.revanced.extension.shared.patches.components;
import static app.revanced.extension.shared.StringRef.str;
@@ -14,14 +14,16 @@ import java.util.regex.Pattern;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.ByteTrieSearch;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
import app.revanced.extension.shared.patches.litho.Filter;
/**
* Allows custom filtering using a path and optionally a proto buffer string.
*/
@SuppressWarnings("unused")
final class CustomFilter extends Filter {
public final class CustomFilter extends Filter {
private static void showInvalidSyntaxToast(@NonNull String expression) {
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression));
@@ -45,7 +47,7 @@ final class CustomFilter extends Filter {
@NonNull
@SuppressWarnings("ConstantConditions")
static Collection<CustomFilterGroup> parseCustomFilterGroups() {
String rawCustomFilterText = Settings.CUSTOM_FILTER_STRINGS.get();
String rawCustomFilterText = YouTubeAndMusicSettings.CUSTOM_FILTER_STRINGS.get();
if (rawCustomFilterText.isBlank()) {
return Collections.emptyList();
}
@@ -100,7 +102,7 @@ final class CustomFilter extends Filter {
ByteTrieSearch bufferSearch;
CustomFilterGroup(boolean startsWith, @NonNull String path) {
super(Settings.CUSTOM_FILTER, path);
super(YouTubeAndMusicSettings.CUSTOM_FILTER, path);
this.startsWith = startsWith;
}
@@ -145,7 +147,7 @@ final class CustomFilter extends Filter {
}
@Override
boolean isFiltered(String identifier, String path, byte[] buffer,
public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
// All callbacks are custom filter groups.
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
@@ -159,4 +161,4 @@ final class CustomFilter extends Filter {
return custom.bufferSearch.matches(buffer);
}
}
}

View File

@@ -1,9 +1,12 @@
package app.revanced.extension.youtube.patches.components;
package app.revanced.extension.shared.patches.litho;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
/**
* Filters litho based components.
*
@@ -14,11 +17,11 @@ import java.util.List;
* either an identifier or a path.
* Then inside {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
* search for the buffer content using either a {@link ByteArrayFilterGroup} (if searching for 1 pattern)
* or a {@link ByteArrayFilterGroupList} (if searching for more than 1 pattern).
* or a {@link FilterGroupList.ByteArrayFilterGroupList} (if searching for more than 1 pattern).
*
* All callbacks must be registered before the constructor completes.
*/
abstract class Filter {
public abstract class Filter {
public enum FilterContentType {
IDENTIFIER,
@@ -65,7 +68,7 @@ abstract class Filter {
* @param contentIndex Matched index of the identifier or path.
* @return True if the litho component should be filtered out.
*/
boolean isFiltered(String identifier, String path, byte[] buffer,
public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
return true;
}

View File

@@ -0,0 +1,213 @@
package app.revanced.extension.shared.patches.litho;
import androidx.annotation.NonNull;
import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BooleanSetting;
public abstract class FilterGroup<T> {
public final static class FilterGroupResult {
private BooleanSetting setting;
private int matchedIndex;
private int matchedLength;
// In the future it might be useful to include which pattern matched,
// but for now that is not needed.
FilterGroupResult() {
this(null, -1, 0);
}
FilterGroupResult(BooleanSetting setting, int matchedIndex, int matchedLength) {
setValues(setting, matchedIndex, matchedLength);
}
public void setValues(BooleanSetting setting, int matchedIndex, int matchedLength) {
this.setting = setting;
this.matchedIndex = matchedIndex;
this.matchedLength = matchedLength;
}
/**
* A null value if the group has no setting,
* or if no match is returned from {@link FilterGroupList#check(Object)}.
*/
public BooleanSetting getSetting() {
return setting;
}
public boolean isFiltered() {
return matchedIndex >= 0;
}
/**
* Matched index of first pattern that matched, or -1 if nothing matched.
*/
public int getMatchedIndex() {
return matchedIndex;
}
/**
* Length of the matched filter pattern.
*/
public int getMatchedLength() {
return matchedLength;
}
}
protected final BooleanSetting setting;
protected final T[] filters;
/**
* Initialize a new filter group.
*
* @param setting The associated setting.
* @param filters The filters.
*/
@SafeVarargs
public FilterGroup(final BooleanSetting setting, final T... filters) {
this.setting = setting;
this.filters = filters;
if (filters.length == 0) {
throw new IllegalArgumentException("Must use one or more filter patterns (zero specified)");
}
}
public boolean isEnabled() {
return setting == null || setting.get();
}
/**
* @return If {@link FilterGroupList} should include this group when searching.
* By default, all filters are included except non enabled settings that require reboot.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean includeInSearch() {
return isEnabled() || !setting.rebootApp;
}
@NonNull
@Override
public String toString() {
return getClass().getSimpleName() + ": " + (setting == null ? "(null setting)" : setting);
}
public abstract FilterGroupResult check(final T stack);
public static class StringFilterGroup extends FilterGroup<String> {
public StringFilterGroup(final BooleanSetting setting, final String... filters) {
super(setting, filters);
}
@Override
public FilterGroupResult check(final String string) {
int matchedIndex = -1;
int matchedLength = 0;
if (isEnabled()) {
for (String pattern : filters) {
if (!string.isEmpty()) {
final int indexOf = string.indexOf(pattern);
if (indexOf >= 0) {
matchedIndex = indexOf;
matchedLength = pattern.length();
break;
}
}
}
}
return new FilterGroupResult(setting, matchedIndex, matchedLength);
}
}
/**
* If you have more than 1 filter patterns, then all instances of
* this class should filtered using {@link FilterGroupList.ByteArrayFilterGroupList#check(byte[])},
* which uses a prefix tree to give better performance.
*/
public static class ByteArrayFilterGroup extends FilterGroup<byte[]> {
private volatile int[][] failurePatterns;
// Modified implementation from https://stackoverflow.com/a/1507813
private static int indexOf(final byte[] data, final byte[] pattern, final int[] failure) {
// Finds the first occurrence of the pattern in the byte array using
// KMP matching algorithm.
int patternLength = pattern.length;
for (int i = 0, j = 0, dataLength = data.length; i < dataLength; i++) {
while (j > 0 && pattern[j] != data[i]) {
j = failure[j - 1];
}
if (pattern[j] == data[i]) {
j++;
}
if (j == patternLength) {
return i - patternLength + 1;
}
}
return -1;
}
private static int[] createFailurePattern(byte[] pattern) {
// Computes the failure function using a boot-strapping process,
// where the pattern is matched against itself.
final int patternLength = pattern.length;
final int[] failure = new int[patternLength];
for (int i = 1, j = 0; i < patternLength; i++) {
while (j > 0 && pattern[j] != pattern[i]) {
j = failure[j - 1];
}
if (pattern[j] == pattern[i]) {
j++;
}
failure[i] = j;
}
return failure;
}
public ByteArrayFilterGroup(BooleanSetting setting, byte[]... filters) {
super(setting, filters);
}
/**
* Converts the Strings into byte arrays. Used to search for text in binary data.
*/
public ByteArrayFilterGroup(BooleanSetting setting, String... filters) {
super(setting, ByteTrieSearch.convertStringsToBytes(filters));
}
private synchronized void buildFailurePatterns() {
if (failurePatterns != null) return; // Thread race and another thread already initialized the search.
Logger.printDebug(() -> "Building failure array for: " + this);
int[][] failurePatterns = new int[filters.length][];
int i = 0;
for (byte[] pattern : filters) {
failurePatterns[i++] = createFailurePattern(pattern);
}
this.failurePatterns = failurePatterns; // Must set after initialization finishes.
}
@Override
public FilterGroupResult check(final byte[] bytes) {
int matchedLength = 0;
int matchedIndex = -1;
if (isEnabled()) {
int[][] failures = failurePatterns;
if (failures == null) {
buildFailurePatterns(); // Lazy load.
failures = failurePatterns;
}
for (int i = 0, length = filters.length; i < length; i++) {
byte[] filter = filters[i];
matchedIndex = indexOf(bytes, filter, failures[i]);
if (matchedIndex >= 0) {
matchedLength = filter.length;
break;
}
}
}
return new FilterGroupResult(setting, matchedIndex, matchedLength);
}
}
}

View File

@@ -0,0 +1,70 @@
package app.revanced.extension.shared.patches.litho;
import androidx.annotation.NonNull;
import java.util.*;
import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.TrieSearch;
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
public abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
private final List<T> filterGroups = new ArrayList<>();
private final TrieSearch<V> search = createSearchGraph();
@SafeVarargs
public final void addAll(final T... groups) {
filterGroups.addAll(Arrays.asList(groups));
for (T group : groups) {
if (!group.includeInSearch()) {
continue;
}
for (V pattern : group.filters) {
search.addPattern(pattern, (textSearched, matchedStartIndex, matchedLength, callbackParameter) -> {
if (group.isEnabled()) {
FilterGroup.FilterGroupResult result = (FilterGroup.FilterGroupResult) callbackParameter;
result.setValues(group.setting, matchedStartIndex, matchedLength);
return true;
}
return false;
});
}
}
}
@NonNull
@Override
public Iterator<T> iterator() {
return filterGroups.iterator();
}
public FilterGroup.FilterGroupResult check(V stack) {
FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult();
search.matches(stack, result);
return result;
}
protected abstract TrieSearch<V> createSearchGraph();
public static final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> {
protected StringTrieSearch createSearchGraph() {
return new StringTrieSearch();
}
}
/**
* If searching for a single byte pattern, then it is slightly better to use
* {@link ByteArrayFilterGroup#check(byte[])} as it uses KMP which is faster
* than a prefix tree to search for only 1 pattern.
*/
public static final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> {
protected ByteTrieSearch createSearchGraph() {
return new ByteTrieSearch();
}
}
}

View File

@@ -1,4 +1,4 @@
package app.revanced.extension.youtube.patches.components;
package app.revanced.extension.shared.patches.litho;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -7,9 +7,11 @@ import java.nio.ByteBuffer;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.StringTrieSearch;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
@SuppressWarnings("unused")
public final class LithoFilterPatch {
@@ -36,7 +38,7 @@ public final class LithoFilterPatch {
builder.append(identifier);
builder.append(" Path: ");
builder.append(path);
if (Settings.DEBUG_PROTOBUFFER.get()) {
if (YouTubeAndMusicSettings.DEBUG_PROTOBUFFER.get()) {
builder.append(" BufferStrings: ");
findAsciiStrings(builder, buffer);
}

View File

@@ -0,0 +1,68 @@
package app.revanced.extension.shared.privacy;
import android.net.Uri;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import app.revanced.extension.shared.Logger;
/**
* Strips away specific parameters from URLs.
*/
public class LinkSanitizer {
private final Collection<String> parametersToRemove;
public LinkSanitizer(String ... parametersToRemove) {
final int parameterCount = parametersToRemove.length;
// List is faster if only checking a few parameters.
this.parametersToRemove = parameterCount > 4
? Set.of(parametersToRemove)
: List.of(parametersToRemove);
}
public String sanitizeUrlString(String url) {
try {
return sanitizeUri(Uri.parse(url)).toString();
} catch (Exception ex) {
Logger.printException(() -> "sanitizeUrlString failure: " + url, ex);
return url;
}
}
public Uri sanitizeUri(Uri uri) {
try {
String scheme = uri.getScheme();
if (scheme == null || !(scheme.equals("http") || scheme.equals("https"))) {
// Opening YouTube share sheet 'other' option passes the video title as a URI.
// Checking !uri.isHierarchical() works for all cases, except if the
// video title starts with / and then it's hierarchical but still an invalid URI.
Logger.printDebug(() -> "Ignoring uri: " + uri);
return uri;
}
Uri.Builder builder = uri.buildUpon().clearQuery();
if (!parametersToRemove.isEmpty()) {
for (String paramName : uri.getQueryParameterNames()) {
if (!parametersToRemove.contains(paramName)) {
for (String value : uri.getQueryParameters(paramName)) {
builder.appendQueryParameter(paramName, value);
}
}
}
}
Uri sanitizedUrl = builder.build();
Logger.printInfo(() -> "Sanitized url: " + uri + " to: " + sanitizedUrl);
return sanitizedUrl;
} catch (Exception ex) {
Logger.printException(() -> "sanitizeUri failure: " + uri, ex);
return uri;
}
}
}

View File

@@ -36,8 +36,8 @@ public enum AppLanguage {
FR,
GL,
GU,
HI,
HE, // App uses obsolete 'IW' and not the modern 'HE' ISO code.
HI,
HR,
HU,
HY,
@@ -60,9 +60,9 @@ public enum AppLanguage {
MR,
MS,
MY,
NB,
NE,
NL,
NB,
OR,
PA,
PL,

View File

@@ -0,0 +1,166 @@
package app.revanced.extension.shared.settings;
import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.preference.PreferenceFragment;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toolbar;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
import app.revanced.extension.shared.ui.Dim;
/**
* Base class for hooking activities to inject a custom PreferenceFragment with a toolbar.
* Provides common logic for initializing the activity and setting up the toolbar.
*/
@SuppressWarnings({"deprecation", "NewApi"})
public abstract class BaseActivityHook extends Activity {
private static final int ID_REVANCED_SETTINGS_FRAGMENTS =
getResourceIdentifierOrThrow("revanced_settings_fragments", "id");
private static final int ID_REVANCED_TOOLBAR_PARENT =
getResourceIdentifierOrThrow("revanced_toolbar_parent", "id");
public static final int LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR =
getResourceIdentifierOrThrow("revanced_settings_with_toolbar", "layout");
private static final int STRING_REVANCED_SETTINGS_TITLE =
getResourceIdentifierOrThrow("revanced_settings_title", "string");
/**
* Layout parameters for the toolbar, extracted from the dummy toolbar.
*/
protected static ViewGroup.LayoutParams toolbarLayoutParams;
/**
* Sets the layout parameters for the toolbar.
*/
public static void setToolbarLayoutParams(Toolbar toolbar) {
if (toolbarLayoutParams != null) {
toolbar.setLayoutParams(toolbarLayoutParams);
}
}
/**
* Initializes the activity by setting the theme, content view and injecting a PreferenceFragment.
*/
public static void initialize(BaseActivityHook hook, Activity activity) {
try {
hook.customizeActivityTheme(activity);
activity.setContentView(hook.getContentViewResourceId());
// Sanity check.
String dataString = activity.getIntent().getDataString();
if (!"revanced_settings_intent".equals(dataString)) {
Logger.printException(() -> "Unknown intent: " + dataString);
return;
}
PreferenceFragment fragment = hook.createPreferenceFragment();
hook.createToolbar(activity, fragment);
activity.getFragmentManager()
.beginTransaction()
.replace(ID_REVANCED_SETTINGS_FRAGMENTS, fragment)
.commit();
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
}
}
/**
* Injection point.
* Overrides the ReVanced settings language.
*/
@SuppressWarnings("unused")
public static Context getAttachBaseContext(Context original) {
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
if (language == AppLanguage.DEFAULT) {
return original;
}
return Utils.getContext();
}
/**
* Creates and configures a toolbar for the activity, replacing a dummy placeholder.
*/
@SuppressLint("UseCompatLoadingForDrawables")
protected void createToolbar(Activity activity, PreferenceFragment fragment) {
// Replace dummy placeholder toolbar.
// This is required to fix submenu title alignment issue with Android ASOP 15+
ViewGroup toolBarParent = activity.findViewById(ID_REVANCED_TOOLBAR_PARENT);
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent, "revanced_toolbar");
toolbarLayoutParams = dummyToolbar.getLayoutParams();
toolBarParent.removeView(dummyToolbar);
// Sets appropriate system navigation bar color for the activity.
ToolbarPreferenceFragment.setNavigationBarColor(activity.getWindow());
Toolbar toolbar = new Toolbar(toolBarParent.getContext());
toolbar.setBackgroundColor(getToolbarBackgroundColor());
toolbar.setNavigationIcon(getNavigationIcon());
toolbar.setNavigationOnClickListener(getNavigationClickListener(activity));
toolbar.setTitle(STRING_REVANCED_SETTINGS_TITLE);
toolbar.setTitleMarginStart(Dim.dp16);
toolbar.setTitleMarginEnd(Dim.dp16);
TextView toolbarTextView = Utils.getChildView(toolbar, false, view -> view instanceof TextView);
if (toolbarTextView != null) {
toolbarTextView.setTextColor(Utils.getAppForegroundColor());
toolbarTextView.setTextSize(20);
}
setToolbarLayoutParams(toolbar);
onPostToolbarSetup(activity, toolbar, fragment);
toolBarParent.addView(toolbar, 0);
}
/**
* Customizes the activity's theme.
*/
protected abstract void customizeActivityTheme(Activity activity);
/**
* Returns the resource ID for the content view layout.
*/
protected abstract int getContentViewResourceId();
/**
* Returns the background color for the toolbar.
*/
protected abstract int getToolbarBackgroundColor();
/**
* Returns the navigation icon drawable for the toolbar.
*/
protected abstract Drawable getNavigationIcon();
/**
* Returns the click listener for the toolbar's navigation icon.
*/
protected abstract View.OnClickListener getNavigationClickListener(Activity activity);
/**
* Creates the PreferenceFragment to be injected into the activity.
*/
protected PreferenceFragment createPreferenceFragment() {
return new ToolbarPreferenceFragment();
}
/**
* Performs additional setup after the toolbar is configured.
*
* @param activity The activity hosting the toolbar.
* @param toolbar The configured toolbar.
* @param fragment The PreferenceFragment associated with the activity.
*/
protected void onPostToolbarSetup(Activity activity, Toolbar toolbar, PreferenceFragment fragment) {}
}

View File

@@ -2,11 +2,8 @@ package app.revanced.extension.shared.settings;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.patches.CustomBrandingPatch.BrandingTheme;
import static app.revanced.extension.shared.settings.Setting.parent;
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.AudioStreamLanguageOverrideAvailability;
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.SpoofiOSAvailability;
import app.revanced.extension.shared.spoof.ClientType;
/**
* Settings shared across multiple apps.
@@ -28,12 +25,23 @@ public class BaseSettings {
*/
public static final BooleanSetting SHOW_MENU_ICONS = new BooleanSetting("revanced_show_menu_icons", TRUE, true);
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
public static final EnumSetting<AppLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofiOSAvailability());
// Client type must be last spoof setting due to cyclic references.
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_UNPLUGGED, true, parent(SPOOF_VIDEO_STREAMS));
public static final BooleanSetting SETTINGS_SEARCH_HISTORY = new BooleanSetting("revanced_settings_search_history", TRUE, true);
public static final StringSetting SETTINGS_SEARCH_ENTRIES = new StringSetting("revanced_settings_search_entries", "");
//
// Settings shared by YouTube and YouTube Music.
//
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
public static final BooleanSetting SANITIZE_SHARED_LINKS = new BooleanSetting("revanced_sanitize_sharing_links", TRUE);
public static final BooleanSetting REPLACE_MUSIC_LINKS_WITH_YOUTUBE = new BooleanSetting("revanced_replace_music_with_youtube", FALSE);
public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false);
public static final EnumSetting<BrandingTheme> CUSTOM_BRANDING_ICON = new EnumSetting<>("revanced_custom_branding_icon", BrandingTheme.ORIGINAL, true);
public static final IntegerSetting CUSTOM_BRANDING_NAME = new IntegerSetting("revanced_custom_branding_name", 1, true);
public static final StringSetting DISABLED_FEATURE_FLAGS = new StringSetting("revanced_disabled_feature_flags", "", true, parent(DEBUG));
}

View File

@@ -1,18 +1,27 @@
package app.revanced.extension.shared.settings;
import static app.revanced.extension.shared.StringRef.str;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.StringRef;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.*;
import static app.revanced.extension.shared.StringRef.str;
public abstract class Setting<T> {
@@ -23,24 +32,66 @@ public abstract class Setting<T> {
*/
public interface Availability {
boolean isAvailable();
/**
* @return parent settings (dependencies) of this availability.
*/
default List<Setting<?>> getParentSettings() {
return Collections.emptyList();
}
}
/**
* Availability based on a single parent setting being enabled.
*/
public static Availability parent(BooleanSetting parent) {
return parent::get;
return new Availability() {
@Override
public boolean isAvailable() {
return parent.get();
}
@Override
public List<Setting<?>> getParentSettings() {
return Collections.singletonList(parent);
}
};
}
/**
* Availability based on a single parent setting being disabled.
*/
public static Availability parentNot(BooleanSetting parent) {
return new Availability() {
@Override
public boolean isAvailable() {
return !parent.get();
}
@Override
public List<Setting<?>> getParentSettings() {
return Collections.singletonList(parent);
}
};
}
/**
* Availability based on all parents being enabled.
*/
public static Availability parentsAll(BooleanSetting... parents) {
return () -> {
for (BooleanSetting parent : parents) {
if (!parent.get()) return false;
return new Availability() {
@Override
public boolean isAvailable() {
for (BooleanSetting parent : parents) {
if (!parent.get()) return false;
}
return true;
}
@Override
public List<Setting<?>> getParentSettings() {
return Collections.unmodifiableList(Arrays.asList(parents));
}
return true;
};
}
@@ -48,11 +99,19 @@ public abstract class Setting<T> {
* Availability based on any parent being enabled.
*/
public static Availability parentsAny(BooleanSetting... parents) {
return () -> {
for (BooleanSetting parent : parents) {
if (parent.get()) return true;
return new Availability() {
@Override
public boolean isAvailable() {
for (BooleanSetting parent : parents) {
if (parent.get()) return true;
}
return false;
}
@Override
public List<Setting<?>> getParentSettings() {
return Collections.unmodifiableList(Arrays.asList(parents));
}
return false;
};
}
@@ -112,6 +171,7 @@ public abstract class Setting<T> {
* @return All settings that have been created, sorted by keys.
*/
private static List<Setting<?>> allLoadedSettingsSorted() {
//noinspection ComparatorCombinators
Collections.sort(SETTINGS, (Setting<?> o1, Setting<?> o2) -> o1.key.compareTo(o2.key));
return allLoadedSettings();
}
@@ -207,9 +267,7 @@ public abstract class Setting<T> {
SETTINGS.add(this);
if (PATH_TO_SETTINGS.put(key, this) != null) {
// Debug setting may not be created yet so using Logger may cause an initialization crash.
// Show a toast instead.
Utils.showToastLong(this.getClass().getSimpleName()
Logger.printException(() -> this.getClass().getSimpleName()
+ " error: Duplicate Setting key found: " + key);
}
@@ -231,10 +289,10 @@ public abstract class Setting<T> {
/**
* Migrate an old Setting value previously stored in a different SharedPreference.
*
* <p>
* This method will be deleted in the future.
*/
@SuppressWarnings("rawtypes")
@SuppressWarnings({"rawtypes", "NewApi"})
public static void migrateFromOldPreferences(SharedPrefCategory oldPrefs, Setting setting, String settingKey) {
if (!oldPrefs.preferences.contains(settingKey)) {
return; // Nothing to do.
@@ -254,7 +312,7 @@ public abstract class Setting<T> {
migratedValue = oldPrefs.getString(settingKey, (String) newValue);
} else {
Logger.printException(() -> "Unknown setting: " + setting);
// Remove otherwise it'll show a toast on every launch
// Remove otherwise it'll show a toast on every launch.
oldPrefs.preferences.edit().remove(settingKey).apply();
return;
}
@@ -273,7 +331,7 @@ public abstract class Setting<T> {
/**
* Sets, but does _not_ persistently save the value.
* This method is only to be used by the Settings preference code.
*
* <p>
* This intentionally is a static method to deter
* accidental usage when {@link #save(Object)} was intended.
*/
@@ -349,6 +407,17 @@ public abstract class Setting<T> {
return availability == null || availability.isAvailable();
}
/**
* Get the parent Settings that this setting depends on.
* @return List of parent Settings, or empty list if no dependencies exist.
* Defensive: handles null availability or missing getParentSettings() override.
*/
public List<Setting<?>> getParentSettings() {
return availability == null
? Collections.emptyList()
: Objects.requireNonNullElse(availability.getParentSettings(), Collections.emptyList());
}
/**
* @return if the currently set value is the same as {@link #defaultValue}
*/
@@ -467,9 +536,12 @@ public abstract class Setting<T> {
callback.settingsImported(alertDialogContext);
}
Utils.showToastLong(numberOfSettingsImported == 0
? str("revanced_settings_import_reset")
: str("revanced_settings_import_success", numberOfSettingsImported));
// Use a delay, otherwise the toast can move about on screen from the dismissing dialog.
final int numberOfSettingsImportedFinal = numberOfSettingsImported;
Utils.runOnMainThreadDelayed(() -> Utils.showToastLong(numberOfSettingsImportedFinal == 0
? str("revanced_settings_import_reset")
: str("revanced_settings_import_success", numberOfSettingsImportedFinal)),
150);
return rebootSettingChanged;
} catch (JSONException | IllegalArgumentException ex) {

View File

@@ -0,0 +1,14 @@
package app.revanced.extension.shared.settings;
import static app.revanced.extension.shared.settings.Setting.parent;
import static java.lang.Boolean.FALSE;
public class YouTubeAndMusicSettings extends BaseSettings {
// Custom filter
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
// Miscellaneous
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, false,
"revanced_debug_protobuffer_user_dialog_message", parent(BaseSettings.DEBUG));
}

View File

@@ -27,6 +27,7 @@ import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.ui.CustomDialog;
@SuppressWarnings("deprecation")
public abstract class AbstractPreferenceFragment extends PreferenceFragment {
@@ -52,7 +53,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
* Set by subclasses if Strings cannot be added as a resource.
*/
@Nullable
protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle, restartDialogMessage;
protected static CharSequence restartDialogTitle, restartDialogMessage, restartDialogButtonText, confirmDialogTitle;
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
try {
@@ -124,10 +125,13 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
showingUserDialogMessage = true;
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
CharSequence message = BulletPointPreference.formatIntoBulletPoints(
Objects.requireNonNull(setting.userDialogMessage).toString());
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
context,
confirmDialogTitle, // Title.
Objects.requireNonNull(setting.userDialogMessage).toString(), // No message.
message,
null, // No EditText.
null, // OK button text.
() -> {
@@ -151,6 +155,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
);
dialogPair.first.setOnDismissListener(d -> showingUserDialogMessage = false);
dialogPair.first.setCancelable(false);
// Show the dialog.
dialogPair.first.show();
@@ -248,7 +253,8 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
Setting.privateSetValueFromString(setting, listPref.getValue());
}
updateListPreferenceSummary(listPref, setting);
} else {
} else if (!pref.getClass().equals(Preference.class)) {
// Ignore root preference class because there is no data to sync.
Logger.printException(() -> "Setting cannot be handled: " + pref.getClass() + ": " + pref);
}
}
@@ -302,7 +308,8 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
restartDialogButtonText = str("revanced_settings_restart");
}
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(context,
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
context,
restartDialogTitle, // Title.
restartDialogMessage, // Message.
null, // No EditText.

View File

@@ -0,0 +1,86 @@
package app.revanced.extension.shared.settings.preference;
import android.content.Context;
import android.preference.Preference;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.TextUtils;
import android.text.style.BulletSpan;
import android.util.AttributeSet;
/**
* Formats the summary text bullet points into Spanned text for better presentation.
*/
@SuppressWarnings({"unused", "deprecation"})
public class BulletPointPreference extends Preference {
/**
* Replaces bullet points with styled spans.
*/
public static CharSequence formatIntoBulletPoints(CharSequence source) {
final char bulletPoint = '•';
if (TextUtils.indexOf(source, bulletPoint) < 0) {
return source; // Nothing to do.
}
SpannableStringBuilder builder = new SpannableStringBuilder(source);
int lineStart = 0;
int length = builder.length();
while (lineStart < length) {
int lineEnd = TextUtils.indexOf(builder, '\n', lineStart);
if (lineEnd < 0) lineEnd = length;
// Apply BulletSpan only if the line starts with the '•' character.
if (lineEnd > lineStart && builder.charAt(lineStart) == bulletPoint) {
int deleteEnd = lineStart + 1; // remove the bullet itself
// If there's a single space right after the bullet, remove that too.
if (deleteEnd < builder.length() && builder.charAt(deleteEnd) == ' ') {
deleteEnd++;
}
builder.delete(lineStart, deleteEnd);
// Apply the BulletSpan to the remainder of that line.
builder.setSpan(new BulletSpan(20),
lineStart,
lineEnd - (deleteEnd - lineStart), // adjust for deleted chars.
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
);
// Update total length and lineEnd after deletion.
length = builder.length();
final int removed = deleteEnd - lineStart;
lineEnd -= removed;
}
lineStart = lineEnd + 1;
}
return new SpannedString(builder);
}
public BulletPointPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public BulletPointPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public BulletPointPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BulletPointPreference(Context context) {
super(context);
}
@Override
public void setSummary(CharSequence summary) {
super.setSummary(formatIntoBulletPoints(summary));
}
}

View File

@@ -0,0 +1,45 @@
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.settings.preference.BulletPointPreference.formatIntoBulletPoints;
import android.content.Context;
import android.preference.SwitchPreference;
import android.util.AttributeSet;
/**
* Formats the summary text bullet points into Spanned text for better presentation.
*/
@SuppressWarnings({"unused", "deprecation"})
public class BulletPointSwitchPreference extends SwitchPreference {
public BulletPointSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public BulletPointSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public BulletPointSwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BulletPointSwitchPreference(Context context) {
super(context);
}
@Override
public void setSummary(CharSequence summary) {
super.setSummary(formatIntoBulletPoints(summary));
}
@Override
public void setSummaryOn(CharSequence summaryOn) {
super.setSummaryOn(formatIntoBulletPoints(summaryOn));
}
@Override
public void setSummaryOff(CharSequence summaryOff) {
super.setSummaryOff(formatIntoBulletPoints(summaryOff));
}
}

View File

@@ -1,9 +1,8 @@
package app.revanced.extension.youtube.settings.preference;
package app.revanced.extension.shared.settings.preference;
import android.content.Context;
import android.util.AttributeSet;
import android.preference.Preference;
import app.revanced.extension.shared.settings.preference.LogBufferManager;
/**
* A custom preference that clears the ReVanced debug log buffer when clicked.

Some files were not shown because too many files have changed in this diff Show More