mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2026-01-23 18:51:03 +00:00
Compare commits
16 Commits
v5.31.0-de
...
v5.31.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a38f635514 | ||
|
|
b3e6c215cc | ||
|
|
c9cc3d5c41 | ||
|
|
536e64565c | ||
|
|
65cbf3c1eb | ||
|
|
61c1a7a75a | ||
|
|
1e39db06b8 | ||
|
|
e019f83232 | ||
|
|
3b57a5f8c0 | ||
|
|
eafe3dfc45 | ||
|
|
d56d8d990c | ||
|
|
37a8682901 | ||
|
|
11ba7d4e3e | ||
|
|
6833d37c26 | ||
|
|
e6f72bcb7d | ||
|
|
e8a227c082 |
28
.github/dependabot.yml
vendored
28
.github/dependabot.yml
vendored
@@ -1,22 +1,26 @@
|
|||||||
version: 2
|
version: 2
|
||||||
|
multi-ecosystem-groups:
|
||||||
|
dependency:
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
target-branch: dev
|
||||||
|
labels: [ ]
|
||||||
|
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: github-actions
|
- package-ecosystem: github-actions
|
||||||
labels: []
|
multi-ecosystem-group: "dependency"
|
||||||
directory: /
|
directory: /
|
||||||
target-branch: dev
|
patterns:
|
||||||
schedule:
|
- "*"
|
||||||
interval: monthly
|
|
||||||
|
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
labels: []
|
multi-ecosystem-group: "dependency"
|
||||||
directory: /
|
directory: /
|
||||||
target-branch: dev
|
patterns:
|
||||||
schedule:
|
- "*"
|
||||||
interval: monthly
|
|
||||||
|
|
||||||
- package-ecosystem: gradle
|
- package-ecosystem: gradle
|
||||||
labels: []
|
multi-ecosystem-group: "dependency"
|
||||||
directory: /
|
directory: /
|
||||||
target-branch: dev
|
patterns:
|
||||||
schedule:
|
- "*"
|
||||||
interval: monthly
|
|
||||||
|
|||||||
2
.github/workflows/build_pull_request.yml
vendored
2
.github/workflows/build_pull_request.yml
vendored
@@ -13,8 +13,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
|
|||||||
1
.github/workflows/pull_strings.yml
vendored
1
.github/workflows/pull_strings.yml
vendored
@@ -17,7 +17,6 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
fetch-depth: 0
|
|
||||||
clean: true
|
clean: true
|
||||||
|
|
||||||
- name: Pull strings
|
- name: Pull strings
|
||||||
|
|||||||
2
.github/workflows/push_strings.yml
vendored
2
.github/workflows/push_strings.yml
vendored
@@ -15,8 +15,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Preprocess strings
|
- name: Preprocess strings
|
||||||
env:
|
env:
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -19,8 +19,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
|
|||||||
41
CHANGELOG.md
41
CHANGELOG.md
@@ -1,3 +1,44 @@
|
|||||||
|
# [5.31.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.16...v5.31.0-dev.17) (2025-07-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify - Unlock Premium:** Remove wrongfully hidden non ad browse sections ([#5403](https://github.com/ReVanced/revanced-patches/issues/5403)) ([8633544](https://github.com/ReVanced/revanced-patches/commit/8633544decc0814d7a548fbc5576b4bdd1d7eee0))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Spotify:** Remove support for old versions ([#5404](https://github.com/ReVanced/revanced-patches/issues/5404)) ([9d31238](https://github.com/ReVanced/revanced-patches/commit/9d31238803a45e957472760fc40c3862da2cf3f0))
|
||||||
|
|
||||||
|
# [5.31.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.15...v5.31.0-dev.16) (2025-07-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Spotify - Spoof client:** Fix issues like songs skipping by spoofing to iOS ([#5388](https://github.com/ReVanced/revanced-patches/issues/5388)) ([e36d4c1](https://github.com/ReVanced/revanced-patches/commit/e36d4c1986b58815c7659e6ef44011166873f9c8))
|
||||||
|
* **YouTube:** Disable two-finger tap gesture for skipping chapters ([#5374](https://github.com/ReVanced/revanced-patches/issues/5374)) ([71db0a2](https://github.com/ReVanced/revanced-patches/commit/71db0a2661b5f76eb5048cdeed83f26fbfdf4fee))
|
||||||
|
|
||||||
|
# [5.31.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.14...v5.31.0-dev.15) (2025-07-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Handle empty list of announcements ([de9d720](https://github.com/ReVanced/revanced-patches/commit/de9d7209f4e818a618a7fd9000013ae8ebd728f2))
|
||||||
|
|
||||||
|
# [5.31.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.13...v5.31.0-dev.14) (2025-07-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Bacon Reader - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5402](https://github.com/ReVanced/revanced-patches/issues/5402)) ([72459bb](https://github.com/ReVanced/revanced-patches/commit/72459bb2eaf4691e32822dfdd1db3240e2fe98dd))
|
||||||
|
|
||||||
|
# [5.31.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.12...v5.31.0-dev.13) (2025-07-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Slide to seek:** Show tap and hold 2x speed overlay when active ([#5398](https://github.com/ReVanced/revanced-patches/issues/5398)) ([dbc9c5f](https://github.com/ReVanced/revanced-patches/commit/dbc9c5f00c1f5bbb95f8822667cc1ac3c613fa00))
|
||||||
|
|
||||||
# [5.31.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.11...v5.31.0-dev.12) (2025-07-09)
|
# [5.31.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.11...v5.31.0-dev.12) (2025-07-09)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ dependencies {
|
|||||||
compileOnly(project(":extensions:spotify:stub"))
|
compileOnly(project(":extensions:spotify:stub"))
|
||||||
compileOnly(libs.annotation)
|
compileOnly(libs.annotation)
|
||||||
|
|
||||||
implementation(project(":extensions:spotify:utils"))
|
|
||||||
implementation(libs.nanohttpd)
|
implementation(libs.nanohttpd)
|
||||||
implementation(libs.protobuf.javalite)
|
implementation(libs.protobuf.javalite)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package app.revanced.extension.spotify.layout.hide.createbutton;
|
package app.revanced.extension.spotify.layout.hide.createbutton;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.spotify.shared.ComponentFilters.*;
|
import app.revanced.extension.spotify.shared.ComponentFilters.ComponentFilter;
|
||||||
|
import app.revanced.extension.spotify.shared.ComponentFilters.ResourceIdComponentFilter;
|
||||||
|
import app.revanced.extension.spotify.shared.ComponentFilters.StringComponentFilter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class HideCreateButtonPatch {
|
public final class HideCreateButtonPatch {
|
||||||
@@ -53,7 +55,9 @@ public final class HideCreateButtonPatch {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Throwable ex) {
|
||||||
|
// Catch Throwable as calling toString can cause crashes with wrongfully generated code that throws
|
||||||
|
// NoSuchMethod errors.
|
||||||
Logger.printException(() -> "returnNullIfIsCreateButton failure", ex);
|
Logger.printException(() -> "returnNullIfIsCreateButton failure", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package app.revanced.extension.spotify.misc.fix;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.spotify.misc.fix.clienttoken.data.v0.ClienttokenHttp.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import static app.revanced.extension.spotify.misc.fix.Constants.*;
|
||||||
|
|
||||||
|
class ClientTokenService {
|
||||||
|
private static final String IOS_CLIENT_ID = "58bd3c95768941ea9eb4350aaa033eb3";
|
||||||
|
private static final String IOS_USER_AGENT;
|
||||||
|
|
||||||
|
static {
|
||||||
|
String clientVersion = getClientVersion();
|
||||||
|
int commitHashIndex = clientVersion.lastIndexOf(".");
|
||||||
|
String version = clientVersion.substring(
|
||||||
|
clientVersion.indexOf("-") + 1,
|
||||||
|
clientVersion.lastIndexOf(".", commitHashIndex - 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
IOS_USER_AGENT = "Spotify/" + version + " iOS/" + getSystemVersion() + " (" + getHardwareMachine() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final ConnectivitySdkData.Builder IOS_CONNECTIVITY_SDK_DATA =
|
||||||
|
ConnectivitySdkData.newBuilder()
|
||||||
|
.setPlatformSpecificData(PlatformSpecificData.newBuilder()
|
||||||
|
.setIos(NativeIOSData.newBuilder()
|
||||||
|
.setHwMachine(getHardwareMachine())
|
||||||
|
.setSystemVersion(getSystemVersion())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final ClientDataRequest.Builder IOS_CLIENT_DATA_REQUEST =
|
||||||
|
ClientDataRequest.newBuilder()
|
||||||
|
.setClientVersion(getClientVersion())
|
||||||
|
.setClientId(IOS_CLIENT_ID);
|
||||||
|
|
||||||
|
private static final ClientTokenRequest.Builder IOS_CLIENT_TOKEN_REQUEST =
|
||||||
|
ClientTokenRequest.newBuilder()
|
||||||
|
.setRequestType(ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST);
|
||||||
|
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
static ClientTokenRequest newIOSClientTokenRequest(String deviceId) {
|
||||||
|
Logger.printInfo(() -> "Creating new iOS client token request with device ID: " + deviceId);
|
||||||
|
|
||||||
|
return IOS_CLIENT_TOKEN_REQUEST
|
||||||
|
.setClientData(IOS_CLIENT_DATA_REQUEST
|
||||||
|
.setConnectivitySdkData(IOS_CONNECTIVITY_SDK_DATA
|
||||||
|
.setDeviceId(deviceId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static ClientTokenResponse getClientTokenResponse(@NonNull ClientTokenRequest request) {
|
||||||
|
if (request.getRequestType() == ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST) {
|
||||||
|
Logger.printInfo(() -> "Requesting iOS client token");
|
||||||
|
String deviceId = request.getClientData().getConnectivitySdkData().getDeviceId();
|
||||||
|
request = newIOSClientTokenRequest(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientTokenResponse response;
|
||||||
|
try {
|
||||||
|
response = requestClientToken(request);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.printException(() -> "Failed to handle request", ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static ClientTokenResponse requestClientToken(@NonNull ClientTokenRequest request) throws IOException {
|
||||||
|
HttpURLConnection urlConnection = (HttpURLConnection) new URL(CLIENT_TOKEN_API_URL).openConnection();
|
||||||
|
urlConnection.setRequestMethod("POST");
|
||||||
|
urlConnection.setDoOutput(true);
|
||||||
|
urlConnection.setRequestProperty("Content-Type", "application/x-protobuf");
|
||||||
|
urlConnection.setRequestProperty("Accept", "application/x-protobuf");
|
||||||
|
urlConnection.setRequestProperty("User-Agent", IOS_USER_AGENT);
|
||||||
|
|
||||||
|
byte[] requestArray = request.toByteArray();
|
||||||
|
urlConnection.setFixedLengthStreamingMode(requestArray.length);
|
||||||
|
urlConnection.getOutputStream().write(requestArray);
|
||||||
|
|
||||||
|
try (InputStream inputStream = urlConnection.getInputStream()) {
|
||||||
|
return ClientTokenResponse.parseFrom(inputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static ClientTokenResponse serveClientTokenRequest(@NonNull InputStream inputStream) {
|
||||||
|
ClientTokenRequest request;
|
||||||
|
try {
|
||||||
|
request = ClientTokenRequest.parseFrom(inputStream);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.printException(() -> "Failed to parse request from input stream", ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Logger.printInfo(() -> "Request of type: " + request.getRequestType());
|
||||||
|
|
||||||
|
ClientTokenResponse response = getClientTokenResponse(request);
|
||||||
|
if (response != null) Logger.printInfo(() -> "Response of type: " + response.getResponseType());
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package app.revanced.extension.spotify.misc.fix;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
class Constants {
|
||||||
|
static final String CLIENT_TOKEN_API_PATH = "/v1/clienttoken";
|
||||||
|
static final String CLIENT_TOKEN_API_URL = "https://clienttoken.spotify.com" + CLIENT_TOKEN_API_PATH;
|
||||||
|
|
||||||
|
// Modified by a patch. Do not touch.
|
||||||
|
@NonNull
|
||||||
|
static String getClientVersion() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified by a patch. Do not touch.
|
||||||
|
@NonNull
|
||||||
|
static String getSystemVersion() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified by a patch. Do not touch.
|
||||||
|
@NonNull
|
||||||
|
static String getHardwareMachine() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
package app.revanced.extension.spotify.misc.fix;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import app.revanced.extension.shared.Logger;
|
|
||||||
import app.revanced.extension.spotify.login5.v4.proto.Login5.*;
|
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
import com.google.protobuf.MessageLite;
|
|
||||||
import fi.iki.elonen.NanoHTTPD;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.FilterInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static app.revanced.extension.spotify.misc.fix.Session.FAILED_TO_RENEW_SESSION;
|
|
||||||
import static fi.iki.elonen.NanoHTTPD.Response.Status.INTERNAL_ERROR;
|
|
||||||
|
|
||||||
class LoginRequestListener extends NanoHTTPD {
|
|
||||||
LoginRequestListener(int port) {
|
|
||||||
super(port);
|
|
||||||
|
|
||||||
try {
|
|
||||||
start();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
Logger.printException(() -> "Failed to start login request listener on port " + port, ex);
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Response serve(IHTTPSession request) {
|
|
||||||
Logger.printInfo(() -> "Serving request for URI: " + request.getUri());
|
|
||||||
|
|
||||||
InputStream requestBodyInputStream = getRequestBodyInputStream(request);
|
|
||||||
|
|
||||||
LoginRequest loginRequest;
|
|
||||||
try {
|
|
||||||
loginRequest = LoginRequest.parseFrom(requestBodyInputStream);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
Logger.printException(() -> "Failed to parse LoginRequest", ex);
|
|
||||||
return newResponse(INTERNAL_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageLite loginResponse;
|
|
||||||
|
|
||||||
// A request may be made concurrently by Spotify,
|
|
||||||
// however a webview can only handle one request at a time due to singleton cookie manager.
|
|
||||||
// Therefore, synchronize to ensure that only one webview handles the request at a time.
|
|
||||||
synchronized (this) {
|
|
||||||
try {
|
|
||||||
loginResponse = getLoginResponse(loginRequest);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "Failed to get login response", ex);
|
|
||||||
return newResponse(INTERNAL_ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newResponse(Response.Status.OK, loginResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static LoginResponse getLoginResponse(@NonNull LoginRequest loginRequest) {
|
|
||||||
Session session;
|
|
||||||
|
|
||||||
if (!loginRequest.hasStoredCredential()) {
|
|
||||||
Logger.printInfo(() -> "Received request for initial login");
|
|
||||||
session = WebApp.currentSession; // Session obtained from WebApp.launchLogin, can be null if still in progress.
|
|
||||||
} else {
|
|
||||||
Logger.printInfo(() -> "Received request to restore saved session");
|
|
||||||
session = Session.read(loginRequest.getStoredCredential().getUsername());
|
|
||||||
}
|
|
||||||
|
|
||||||
return toLoginResponse(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static LoginResponse toLoginResponse(@Nullable Session session) {
|
|
||||||
LoginResponse.Builder builder = LoginResponse.newBuilder();
|
|
||||||
|
|
||||||
if (session == null) {
|
|
||||||
Logger.printException(() -> "Session is null. An initial login may still be in progress, returning try again later error");
|
|
||||||
builder.setError(LoginError.TRY_AGAIN_LATER);
|
|
||||||
} else if (session.accessTokenExpired()) {
|
|
||||||
Logger.printInfo(() -> "Access token expired, renewing session");
|
|
||||||
WebApp.renewSessionBlocking(session.cookies);
|
|
||||||
return toLoginResponse(WebApp.currentSession);
|
|
||||||
} else if (session.username == null) {
|
|
||||||
Logger.printException(() -> "Session username is null, likely caused by invalid cookies, returning invalid credentials error");
|
|
||||||
session.delete();
|
|
||||||
builder.setError(LoginError.INVALID_CREDENTIALS);
|
|
||||||
} else if (session == FAILED_TO_RENEW_SESSION) {
|
|
||||||
Logger.printException(() -> "Failed to renew session, likely caused by a timeout, returning try again later error");
|
|
||||||
builder.setError(LoginError.TRY_AGAIN_LATER);
|
|
||||||
} else {
|
|
||||||
session.save();
|
|
||||||
Logger.printInfo(() -> "Returning session for username: " + session.username);
|
|
||||||
builder.setOk(LoginOk.newBuilder()
|
|
||||||
.setUsername(session.username)
|
|
||||||
.setAccessToken(session.accessToken)
|
|
||||||
.setStoredCredential(ByteString.fromHex("00")) // Placeholder, as it cannot be null or empty.
|
|
||||||
.setAccessTokenExpiresIn(session.accessTokenExpiresInSeconds())
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private static InputStream limitedInputStream(InputStream inputStream, long contentLength) {
|
|
||||||
return new FilterInputStream(inputStream) {
|
|
||||||
private long remaining = contentLength;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
if (remaining <= 0) return -1;
|
|
||||||
int result = super.read();
|
|
||||||
if (result != -1) remaining--;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(byte[] b, int off, int len) throws IOException {
|
|
||||||
if (remaining <= 0) return -1;
|
|
||||||
len = (int) Math.min(len, remaining);
|
|
||||||
int result = super.read(b, off, len);
|
|
||||||
if (result != -1) remaining -= result;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private static InputStream getRequestBodyInputStream(@NonNull IHTTPSession request) {
|
|
||||||
long requestContentLength =
|
|
||||||
Long.parseLong(Objects.requireNonNull(request.getHeaders().get("content-length")));
|
|
||||||
return limitedInputStream(request.getInputStream(), requestContentLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
|
||||||
@NonNull
|
|
||||||
private static Response newResponse(Response.Status status) {
|
|
||||||
return newResponse(status, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private static Response newResponse(Response.IStatus status, MessageLite messageLite) {
|
|
||||||
if (messageLite == null) {
|
|
||||||
return newFixedLengthResponse(status, "application/x-protobuf", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] messageBytes = messageLite.toByteArray();
|
|
||||||
InputStream stream = new ByteArrayInputStream(messageBytes);
|
|
||||||
return newFixedLengthResponse(status, "application/x-protobuf", stream, messageBytes.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package app.revanced.extension.spotify.misc.fix;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.spotify.misc.fix.clienttoken.data.v0.ClienttokenHttp.ClientTokenResponse;
|
||||||
|
import com.google.protobuf.MessageLite;
|
||||||
|
import fi.iki.elonen.NanoHTTPD;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static app.revanced.extension.spotify.misc.fix.ClientTokenService.serveClientTokenRequest;
|
||||||
|
import static app.revanced.extension.spotify.misc.fix.Constants.CLIENT_TOKEN_API_PATH;
|
||||||
|
import static fi.iki.elonen.NanoHTTPD.Response.Status.INTERNAL_ERROR;
|
||||||
|
|
||||||
|
class RequestListener extends NanoHTTPD {
|
||||||
|
RequestListener(int port) {
|
||||||
|
super(port);
|
||||||
|
|
||||||
|
try {
|
||||||
|
start();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.printException(() -> "Failed to start request listener on port " + port, ex);
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Response serve(@NonNull IHTTPSession session) {
|
||||||
|
String uri = session.getUri();
|
||||||
|
if (!uri.equals(CLIENT_TOKEN_API_PATH)) return INTERNAL_ERROR_RESPONSE;
|
||||||
|
|
||||||
|
Logger.printInfo(() -> "Serving request for URI: " + uri);
|
||||||
|
|
||||||
|
ClientTokenResponse response = serveClientTokenRequest(getInputStream(session));
|
||||||
|
if (response != null) return newResponse(Response.Status.OK, response);
|
||||||
|
|
||||||
|
Logger.printException(() -> "Failed to serve client token request");
|
||||||
|
return INTERNAL_ERROR_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static InputStream newLimitedInputStream(InputStream inputStream, long contentLength) {
|
||||||
|
return new FilterInputStream(inputStream) {
|
||||||
|
private long remaining = contentLength;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
if (remaining <= 0) return -1;
|
||||||
|
int result = super.read();
|
||||||
|
if (result != -1) remaining--;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (remaining <= 0) return -1;
|
||||||
|
len = (int) Math.min(len, remaining);
|
||||||
|
int result = super.read(b, off, len);
|
||||||
|
if (result != -1) remaining -= result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static InputStream getInputStream(@NonNull IHTTPSession session) {
|
||||||
|
long requestContentLength = Long.parseLong(Objects.requireNonNull(session.getHeaders().get("content-length")));
|
||||||
|
return newLimitedInputStream(session.getInputStream(), requestContentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Response INTERNAL_ERROR_RESPONSE = newResponse(INTERNAL_ERROR);
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
@NonNull
|
||||||
|
private static Response newResponse(Response.Status status) {
|
||||||
|
return newResponse(status, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static Response newResponse(Response.IStatus status, MessageLite messageLite) {
|
||||||
|
if (messageLite == null) {
|
||||||
|
return newFixedLengthResponse(status, "application/x-protobuf", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] messageBytes = messageLite.toByteArray();
|
||||||
|
InputStream stream = new ByteArrayInputStream(messageBytes);
|
||||||
|
return newFixedLengthResponse(status, "application/x-protobuf", stream, messageBytes.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
package app.revanced.extension.spotify.misc.fix;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import app.revanced.extension.shared.Logger;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import static android.content.Context.MODE_PRIVATE;
|
|
||||||
|
|
||||||
class Session {
|
|
||||||
/**
|
|
||||||
* Username of the account. Null if this session does not have an authenticated user.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
final String username;
|
|
||||||
/**
|
|
||||||
* Access token for this session.
|
|
||||||
*/
|
|
||||||
final String accessToken;
|
|
||||||
/**
|
|
||||||
* Session expiration timestamp in milliseconds.
|
|
||||||
*/
|
|
||||||
final Long expirationTime;
|
|
||||||
/**
|
|
||||||
* Authentication cookies for this session.
|
|
||||||
*/
|
|
||||||
final String cookies;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Session that represents a failed attempt to renew the session.
|
|
||||||
*/
|
|
||||||
static final Session FAILED_TO_RENEW_SESSION = new Session("", "", "");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param username Username of the account. Empty if this session does not have an authenticated user.
|
|
||||||
* @param accessToken Access token for this session.
|
|
||||||
* @param cookies Authentication cookies for this session.
|
|
||||||
*/
|
|
||||||
Session(@Nullable String username, String accessToken, String cookies) {
|
|
||||||
this(username, accessToken, System.currentTimeMillis() + 60 * 60 * 1000, cookies);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Session(@Nullable String username, String accessToken, long expirationTime, String cookies) {
|
|
||||||
this.username = username;
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
this.expirationTime = expirationTime;
|
|
||||||
this.cookies = cookies;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The number of milliseconds until the access token expires.
|
|
||||||
*/
|
|
||||||
long accessTokenExpiresInMillis() {
|
|
||||||
long currentTime = System.currentTimeMillis();
|
|
||||||
return expirationTime - currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The number of seconds until the access token expires.
|
|
||||||
*/
|
|
||||||
int accessTokenExpiresInSeconds() {
|
|
||||||
return (int) accessTokenExpiresInMillis() / 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return True if the access token has expired, false otherwise.
|
|
||||||
*/
|
|
||||||
boolean accessTokenExpired() {
|
|
||||||
return accessTokenExpiresInMillis() <= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void save() {
|
|
||||||
Logger.printInfo(() -> "Saving session: " + this);
|
|
||||||
|
|
||||||
SharedPreferences.Editor editor = Utils.getContext().getSharedPreferences("revanced", MODE_PRIVATE).edit();
|
|
||||||
|
|
||||||
String json;
|
|
||||||
try {
|
|
||||||
json = new JSONObject()
|
|
||||||
.put("accessToken", accessToken)
|
|
||||||
.put("expirationTime", expirationTime)
|
|
||||||
.put("cookies", cookies).toString();
|
|
||||||
} catch (JSONException ex) {
|
|
||||||
Logger.printException(() -> "Failed to convert session to stored credential", ex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
editor.putString("session_" + username, json);
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
void delete() {
|
|
||||||
Logger.printInfo(() -> "Deleting saved session for username: " + username);
|
|
||||||
SharedPreferences.Editor editor = Utils.getContext().getSharedPreferences("revanced", MODE_PRIVATE).edit();
|
|
||||||
editor.remove("session_" + username);
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
static Session read(String username) {
|
|
||||||
Logger.printInfo(() -> "Reading saved session for username: " + username);
|
|
||||||
|
|
||||||
SharedPreferences sharedPreferences = Utils.getContext().getSharedPreferences("revanced", MODE_PRIVATE);
|
|
||||||
String savedJson = sharedPreferences.getString("session_" + username, null);
|
|
||||||
if (savedJson == null) {
|
|
||||||
Logger.printInfo(() -> "No session found in shared preferences");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
JSONObject json = new JSONObject(savedJson);
|
|
||||||
String accessToken = json.getString("accessToken");
|
|
||||||
long expirationTime = json.getLong("expirationTime");
|
|
||||||
String cookies = json.getString("cookies");
|
|
||||||
|
|
||||||
return new Session(username, accessToken, expirationTime, cookies);
|
|
||||||
} catch (JSONException ex) {
|
|
||||||
Logger.printException(() -> "Failed to read session from shared preferences", ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Session(" +
|
|
||||||
"username=" + username +
|
|
||||||
", accessToken=" + accessToken +
|
|
||||||
", expirationTime=" + expirationTime +
|
|
||||||
", cookies=" + cookies +
|
|
||||||
')';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,15 @@
|
|||||||
package app.revanced.extension.spotify.misc.fix;
|
package app.revanced.extension.spotify.misc.fix;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class SpoofClientPatch {
|
public class SpoofClientPatch {
|
||||||
private static LoginRequestListener listener;
|
private static RequestListener listener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point. Launch requests listener server.
|
||||||
* <br>
|
|
||||||
* Launch login server.
|
|
||||||
*/
|
*/
|
||||||
public static void launchListener(int port) {
|
public synchronized static void launchListener(int port) {
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
Logger.printInfo(() -> "Listener already running on port " + port);
|
Logger.printInfo(() -> "Listener already running on port " + port);
|
||||||
return;
|
return;
|
||||||
@@ -21,34 +17,9 @@ public class SpoofClientPatch {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Logger.printInfo(() -> "Launching listener on port " + port);
|
Logger.printInfo(() -> "Launching listener on port " + port);
|
||||||
listener = new LoginRequestListener(port);
|
listener = new RequestListener(port);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "launchListener failure", ex);
|
Logger.printException(() -> "launchListener failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
* <br>
|
|
||||||
* Launch login web view.
|
|
||||||
*/
|
|
||||||
public static void launchLogin(LayoutInflater inflater) {
|
|
||||||
try {
|
|
||||||
WebApp.launchLogin(inflater.getContext());
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "launchLogin failure", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
* <br>
|
|
||||||
* Set handler to call the native login after the webview login.
|
|
||||||
*/
|
|
||||||
public static void setNativeLoginHandler(View startLoginButton) {
|
|
||||||
WebApp.nativeLoginHandler = (() -> {
|
|
||||||
startLoginButton.setSoundEffectsEnabled(false);
|
|
||||||
startLoginButton.performClick();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,297 +0,0 @@
|
|||||||
package app.revanced.extension.spotify.misc.fix;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.view.Window;
|
|
||||||
import android.view.WindowInsets;
|
|
||||||
import android.webkit.*;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import app.revanced.extension.shared.Logger;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
|
||||||
import app.revanced.extension.spotify.UserAgent;
|
|
||||||
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import static app.revanced.extension.spotify.misc.fix.Session.FAILED_TO_RENEW_SESSION;
|
|
||||||
|
|
||||||
class WebApp {
|
|
||||||
private static final String OPEN_SPOTIFY_COM = "open.spotify.com";
|
|
||||||
private static final String OPEN_SPOTIFY_COM_URL = "https://" + OPEN_SPOTIFY_COM;
|
|
||||||
private static final String OPEN_SPOTIFY_COM_PREFERENCES_URL = OPEN_SPOTIFY_COM_URL + "/preferences";
|
|
||||||
private static final String ACCOUNTS_SPOTIFY_COM_LOGIN_URL = "https://accounts.spotify.com/login?allow_password=1"
|
|
||||||
+ "&continue=https%3A%2F%2Fopen.spotify.com%2Fpreferences";
|
|
||||||
|
|
||||||
private static final int GET_SESSION_TIMEOUT_SECONDS = 10;
|
|
||||||
private static final String JAVASCRIPT_INTERFACE_NAME = "androidInterface";
|
|
||||||
private static final String USER_AGENT = getWebUserAgent();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A session obtained from the webview after logging in.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
static volatile Session currentSession = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Current webview in use. Any use of the object must be done on the main thread.
|
|
||||||
*/
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
private static volatile WebView currentWebView;
|
|
||||||
|
|
||||||
interface NativeLoginHandler {
|
|
||||||
void login();
|
|
||||||
}
|
|
||||||
|
|
||||||
static NativeLoginHandler nativeLoginHandler;
|
|
||||||
|
|
||||||
static void launchLogin(Context context) {
|
|
||||||
final Dialog dialog = newDialog(context);
|
|
||||||
|
|
||||||
Utils.runOnBackgroundThread(() -> {
|
|
||||||
Logger.printInfo(() -> "Launching login");
|
|
||||||
|
|
||||||
// A session must be obtained from a login. Repeat until a session is acquired.
|
|
||||||
boolean isAcquired = false;
|
|
||||||
do {
|
|
||||||
CountDownLatch onLoggedInLatch = new CountDownLatch(1);
|
|
||||||
CountDownLatch getSessionLatch = new CountDownLatch(1);
|
|
||||||
|
|
||||||
// Can't use Utils.getContext() here, because autofill won't work.
|
|
||||||
// See https://stackoverflow.com/a/79182053/11213244.
|
|
||||||
launchWebView(context, ACCOUNTS_SPOTIFY_COM_LOGIN_URL, new WebViewCallback() {
|
|
||||||
@Override
|
|
||||||
void onInitialized(WebView webView) {
|
|
||||||
super.onInitialized(webView);
|
|
||||||
|
|
||||||
dialog.setContentView(webView);
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void onLoggedIn(String cookies) {
|
|
||||||
onLoggedInLatch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void onReceivedSession(Session session) {
|
|
||||||
super.onReceivedSession(session);
|
|
||||||
|
|
||||||
getSessionLatch.countDown();
|
|
||||||
dialog.dismiss();
|
|
||||||
|
|
||||||
try {
|
|
||||||
nativeLoginHandler.login();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "nativeLoginHandler failure", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Wait indefinitely until the user logs in.
|
|
||||||
onLoggedInLatch.await();
|
|
||||||
// Wait until the session is received, or timeout.
|
|
||||||
isAcquired = getSessionLatch.await(GET_SESSION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
Logger.printException(() -> "Login interrupted", ex);
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
} while (!isAcquired);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static void renewSessionBlocking(String cookies) {
|
|
||||||
Logger.printInfo(() -> "Renewing session with cookies: " + cookies);
|
|
||||||
|
|
||||||
CountDownLatch getSessionLatch = new CountDownLatch(1);
|
|
||||||
|
|
||||||
launchWebView(Utils.getContext(), OPEN_SPOTIFY_COM_PREFERENCES_URL, new WebViewCallback() {
|
|
||||||
@Override
|
|
||||||
public void onInitialized(WebView webView) {
|
|
||||||
setCookies(cookies);
|
|
||||||
super.onInitialized(webView);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onReceivedSession(Session session) {
|
|
||||||
super.onReceivedSession(session);
|
|
||||||
getSessionLatch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
boolean isAcquired = false;
|
|
||||||
try {
|
|
||||||
isAcquired = getSessionLatch.await(GET_SESSION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
Logger.printException(() -> "Session renewal interrupted", ex);
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isAcquired) {
|
|
||||||
Logger.printException(() -> "Failed to retrieve session within " + GET_SESSION_TIMEOUT_SECONDS + " seconds");
|
|
||||||
currentSession = FAILED_TO_RENEW_SESSION;
|
|
||||||
destructWebView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All methods are called on the main thread.
|
|
||||||
*/
|
|
||||||
abstract static class WebViewCallback {
|
|
||||||
void onInitialized(WebView webView) {
|
|
||||||
currentWebView = webView;
|
|
||||||
currentSession = null; // Reset current session.
|
|
||||||
}
|
|
||||||
|
|
||||||
void onLoggedIn(String cookies) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onReceivedSession(Session session) {
|
|
||||||
Logger.printInfo(() -> "Received session: " + session);
|
|
||||||
currentSession = session;
|
|
||||||
|
|
||||||
destructWebView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
|
||||||
private static void launchWebView(
|
|
||||||
Context context,
|
|
||||||
String initialUrl,
|
|
||||||
WebViewCallback webViewCallback
|
|
||||||
) {
|
|
||||||
Utils.runOnMainThreadNowOrLater(() -> {
|
|
||||||
WebView webView = new WebView(context);
|
|
||||||
WebSettings settings = webView.getSettings();
|
|
||||||
settings.setDomStorageEnabled(true);
|
|
||||||
settings.setJavaScriptEnabled(true);
|
|
||||||
settings.setUserAgentString(USER_AGENT);
|
|
||||||
|
|
||||||
// WebViewClient is always called off the main thread,
|
|
||||||
// but callback interface methods are called on the main thread.
|
|
||||||
webView.setWebViewClient(new WebViewClient() {
|
|
||||||
@Override
|
|
||||||
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
|
|
||||||
if (OPEN_SPOTIFY_COM.equals(request.getUrl().getHost())) {
|
|
||||||
Utils.runOnMainThread(() -> webViewCallback.onLoggedIn(getCurrentCookies()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.shouldInterceptRequest(view, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
|
||||||
Logger.printInfo(() -> "Page started loading: " + url);
|
|
||||||
|
|
||||||
if (!url.startsWith(OPEN_SPOTIFY_COM_URL)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.printInfo(() -> "Evaluating script to get session on url: " + url);
|
|
||||||
String getSessionScript = "Object.defineProperty(Object.prototype, \"_username\", {" +
|
|
||||||
" configurable: true," +
|
|
||||||
" set(username) {" +
|
|
||||||
" accessToken = this._builder?.accessToken;" +
|
|
||||||
" if (accessToken) {" +
|
|
||||||
" " + JAVASCRIPT_INTERFACE_NAME + ".getSession(username, accessToken);" +
|
|
||||||
" delete Object.prototype._username;" +
|
|
||||||
" }" +
|
|
||||||
" " +
|
|
||||||
" Object.defineProperty(this, \"_username\", {" +
|
|
||||||
" configurable: true," +
|
|
||||||
" enumerable: true," +
|
|
||||||
" writable: true," +
|
|
||||||
" value: username" +
|
|
||||||
" })" +
|
|
||||||
" " +
|
|
||||||
" }" +
|
|
||||||
"});" +
|
|
||||||
"if (new URLSearchParams(window.location.search).get('_authfailed') != null) {" +
|
|
||||||
" " + JAVASCRIPT_INTERFACE_NAME + ".getSession(null, null);" +
|
|
||||||
"}";
|
|
||||||
|
|
||||||
view.evaluateJavascript(getSessionScript, null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
webView.addJavascriptInterface(new Object() {
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@JavascriptInterface
|
|
||||||
public void getSession(String username, String accessToken) {
|
|
||||||
Session session = new Session(username, accessToken, getCurrentCookies());
|
|
||||||
Utils.runOnMainThread(() -> webViewCallback.onReceivedSession(session));
|
|
||||||
}
|
|
||||||
}, JAVASCRIPT_INTERFACE_NAME);
|
|
||||||
|
|
||||||
CookieManager.getInstance().removeAllCookies((anyRemoved) -> {
|
|
||||||
Logger.printInfo(() -> "Loading URL: " + initialUrl);
|
|
||||||
webView.loadUrl(initialUrl);
|
|
||||||
|
|
||||||
Logger.printInfo(() -> "WebView initialized with user agent: " + USER_AGENT);
|
|
||||||
webViewCallback.onInitialized(webView);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void destructWebView() {
|
|
||||||
Utils.runOnMainThreadNowOrLater(() -> {
|
|
||||||
currentWebView.stopLoading();
|
|
||||||
currentWebView.destroy();
|
|
||||||
currentWebView = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getWebUserAgent() {
|
|
||||||
String userAgentString = WebSettings.getDefaultUserAgent(Utils.getContext());
|
|
||||||
try {
|
|
||||||
return new UserAgent(userAgentString)
|
|
||||||
.withCommentReplaced("Android", "Windows NT 10.0; Win64; x64")
|
|
||||||
.withoutProduct("Mobile")
|
|
||||||
.toString();
|
|
||||||
} catch (IllegalArgumentException ex) {
|
|
||||||
userAgentString = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
|
|
||||||
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edge/137.0.0.0";
|
|
||||||
String fallback = userAgentString;
|
|
||||||
Logger.printException(() -> "Failed to get user agent, falling back to " + fallback, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return userAgentString;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private static Dialog newDialog(Context context) {
|
|
||||||
Dialog dialog = new Dialog(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
|
|
||||||
dialog.setCancelable(false);
|
|
||||||
|
|
||||||
// Ensure that the keyboard does not cover the webview content.
|
|
||||||
Window window = dialog.getWindow();
|
|
||||||
//noinspection StatementWithEmptyBody
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
||||||
window.getDecorView().setOnApplyWindowInsetsListener((v, insets) -> {
|
|
||||||
v.setPadding(0, 0, 0, insets.getInsets(WindowInsets.Type.ime()).bottom);
|
|
||||||
|
|
||||||
return WindowInsets.CONSUMED;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// TODO: Implement for lower Android versions.
|
|
||||||
}
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getCurrentCookies() {
|
|
||||||
CookieManager cookieManager = CookieManager.getInstance();
|
|
||||||
return cookieManager.getCookie(OPEN_SPOTIFY_COM_URL);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void setCookies(@NonNull String cookies) {
|
|
||||||
CookieManager cookieManager = CookieManager.getInstance();
|
|
||||||
|
|
||||||
String[] cookiesList = cookies.split(";");
|
|
||||||
for (String cookie : cookiesList) {
|
|
||||||
cookieManager.setCookie(OPEN_SPOTIFY_COM_URL, cookie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package spotify.clienttoken.data.v0;
|
||||||
|
|
||||||
|
option optimize_for = LITE_RUNTIME;
|
||||||
|
option java_package = "app.revanced.extension.spotify.misc.fix.clienttoken.data.v0";
|
||||||
|
|
||||||
|
message ClientTokenRequest {
|
||||||
|
ClientTokenRequestType request_type = 1;
|
||||||
|
|
||||||
|
oneof request {
|
||||||
|
ClientDataRequest client_data = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ClientTokenRequestType {
|
||||||
|
REQUEST_UNKNOWN = 0;
|
||||||
|
REQUEST_CLIENT_DATA_REQUEST = 1;
|
||||||
|
REQUEST_CHALLENGE_ANSWERS_REQUEST = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ClientDataRequest {
|
||||||
|
string client_version = 1;
|
||||||
|
string client_id = 2;
|
||||||
|
|
||||||
|
oneof data {
|
||||||
|
ConnectivitySdkData connectivity_sdk_data = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ConnectivitySdkData {
|
||||||
|
PlatformSpecificData platform_specific_data = 1;
|
||||||
|
string device_id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PlatformSpecificData {
|
||||||
|
oneof data {
|
||||||
|
NativeIOSData ios = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message NativeIOSData {
|
||||||
|
int32 user_interface_idiom = 1;
|
||||||
|
bool target_iphone_simulator = 2;
|
||||||
|
string hw_machine = 3;
|
||||||
|
string system_version = 4;
|
||||||
|
string simulator_model_identifier = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ClientTokenResponse {
|
||||||
|
ClientTokenResponseType response_type = 1;
|
||||||
|
|
||||||
|
oneof response {
|
||||||
|
GrantedTokenResponse granted_token = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ClientTokenResponseType {
|
||||||
|
RESPONSE_UNKNOWN = 0;
|
||||||
|
RESPONSE_GRANTED_TOKEN_RESPONSE = 1;
|
||||||
|
RESPONSE_CHALLENGES_RESPONSE = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GrantedTokenResponse {
|
||||||
|
string token = 1;
|
||||||
|
int32 expires_after_seconds = 2;
|
||||||
|
int32 refresh_after_seconds = 3;
|
||||||
|
repeated TokenDomain domains = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TokenDomain {
|
||||||
|
string domain = 1;
|
||||||
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package spotify.login5.v4;
|
|
||||||
|
|
||||||
option optimize_for = LITE_RUNTIME;
|
|
||||||
option java_package = "app.revanced.extension.spotify.login5.v4.proto";
|
|
||||||
|
|
||||||
message StoredCredential {
|
|
||||||
string username = 1;
|
|
||||||
bytes data = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message LoginRequest {
|
|
||||||
oneof login_method {
|
|
||||||
StoredCredential stored_credential = 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message LoginOk {
|
|
||||||
string username = 1;
|
|
||||||
string access_token = 2;
|
|
||||||
bytes stored_credential = 3;
|
|
||||||
int32 access_token_expires_in = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message LoginResponse {
|
|
||||||
oneof response {
|
|
||||||
LoginOk ok = 1;
|
|
||||||
LoginError error = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum LoginError {
|
|
||||||
UNKNOWN_ERROR = 0;
|
|
||||||
INVALID_CREDENTIALS = 1;
|
|
||||||
BAD_REQUEST = 2;
|
|
||||||
UNSUPPORTED_LOGIN_PROTOCOL = 3;
|
|
||||||
TIMEOUT = 4;
|
|
||||||
UNKNOWN_IDENTIFIER = 5;
|
|
||||||
TOO_MANY_ATTEMPTS = 6;
|
|
||||||
INVALID_PHONENUMBER = 7;
|
|
||||||
TRY_AGAIN_LATER = 8;
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,5 @@ package com.spotify.browsita.v1.resolved;
|
|||||||
|
|
||||||
public final class Section {
|
public final class Section {
|
||||||
public static final int BRAND_ADS_FIELD_NUMBER = 6;
|
public static final int BRAND_ADS_FIELD_NUMBER = 6;
|
||||||
public static final int PROMOTION_V1_FIELD_NUMBER = 3;
|
|
||||||
public static final int PROMOTION_V3_FIELD_NUMBER = 5;
|
|
||||||
public int sectionTypeCase_;
|
public int sectionTypeCase_;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.spotify.useraccount.v1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for target 8.6.98.900. Class is still present in newer app targets.
|
|
||||||
*/
|
|
||||||
public class AccountAttribute {
|
|
||||||
public Object value_;
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
plugins {
|
|
||||||
java
|
|
||||||
antlr
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
antlr(libs.antlr4)
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks {
|
|
||||||
generateGrammarSource {
|
|
||||||
arguments = listOf("-visitor")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
grammar UserAgent;
|
|
||||||
|
|
||||||
@header { package app.revanced.extension.spotify; }
|
|
||||||
|
|
||||||
userAgent
|
|
||||||
: product (WS product)* EOF
|
|
||||||
;
|
|
||||||
|
|
||||||
product
|
|
||||||
: name ('/' version)? (WS comment)?
|
|
||||||
;
|
|
||||||
|
|
||||||
name
|
|
||||||
: STRING
|
|
||||||
;
|
|
||||||
|
|
||||||
version
|
|
||||||
: STRING ('.' STRING)*
|
|
||||||
;
|
|
||||||
|
|
||||||
comment
|
|
||||||
: COMMENT
|
|
||||||
;
|
|
||||||
|
|
||||||
COMMENT
|
|
||||||
: '(' ~ ')'* ')'
|
|
||||||
;
|
|
||||||
|
|
||||||
STRING
|
|
||||||
: [a-zA-Z0-9]+
|
|
||||||
;
|
|
||||||
|
|
||||||
WS
|
|
||||||
: [ \r\n]+
|
|
||||||
;
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package app.revanced.extension.spotify;
|
|
||||||
|
|
||||||
import org.antlr.v4.runtime.CharStream;
|
|
||||||
import org.antlr.v4.runtime.CharStreams;
|
|
||||||
import org.antlr.v4.runtime.CommonTokenStream;
|
|
||||||
import org.antlr.v4.runtime.TokenStreamRewriter;
|
|
||||||
import org.antlr.v4.runtime.tree.ParseTreeWalker;
|
|
||||||
|
|
||||||
public class UserAgent {
|
|
||||||
private final UserAgentParser.UserAgentContext tree;
|
|
||||||
private final TokenStreamRewriter rewriter;
|
|
||||||
private final ParseTreeWalker walker;
|
|
||||||
|
|
||||||
public UserAgent(String userAgentString) {
|
|
||||||
CharStream input = CharStreams.fromString(userAgentString);
|
|
||||||
UserAgentLexer lexer = new UserAgentLexer(input);
|
|
||||||
CommonTokenStream tokens = new CommonTokenStream(lexer);
|
|
||||||
|
|
||||||
tree = new UserAgentParser(tokens).userAgent();
|
|
||||||
walker = new ParseTreeWalker();
|
|
||||||
rewriter = new TokenStreamRewriter(tokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserAgent withoutProduct(String name) {
|
|
||||||
walker.walk(new UserAgentBaseListener() {
|
|
||||||
@Override
|
|
||||||
public void exitProduct(UserAgentParser.ProductContext ctx) {
|
|
||||||
if (!ctx.name().getText().contains(name)) return;
|
|
||||||
|
|
||||||
int startIndex = ctx.getStart().getTokenIndex();
|
|
||||||
if (startIndex != 0) startIndex -= 1; // Also remove the preceding whitespace.
|
|
||||||
|
|
||||||
int stopIndex = ctx.getStop().getTokenIndex();
|
|
||||||
|
|
||||||
|
|
||||||
rewriter.delete(startIndex, stopIndex);
|
|
||||||
}
|
|
||||||
}, tree);
|
|
||||||
|
|
||||||
return new UserAgent(rewriter.getText().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserAgent withCommentReplaced(String containing, String replacement) {
|
|
||||||
walker.walk(new UserAgentBaseListener() {
|
|
||||||
@Override
|
|
||||||
public void exitComment(UserAgentParser.CommentContext ctx) {
|
|
||||||
if (ctx.getText().contains(containing)) {
|
|
||||||
rewriter.replace(ctx.getStart(), ctx.getStop(), "(" + replacement + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, tree);
|
|
||||||
|
|
||||||
return new UserAgent(rewriter.getText());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return rewriter.getText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class DisableChapterSkipDoubleTapPatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
|
* @return If "should skip to chapter start" flag is set.
|
||||||
|
*/
|
||||||
|
public static boolean disableDoubleTapChapters(boolean original) {
|
||||||
|
return original && !Settings.DISABLE_CHAPTER_SKIP_DOUBLE_TAP.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,10 +59,11 @@ public final class AnnouncementsPatch {
|
|||||||
int id = Settings.ANNOUNCEMENT_LAST_ID.defaultValue;
|
int id = Settings.ANNOUNCEMENT_LAST_ID.defaultValue;
|
||||||
try {
|
try {
|
||||||
final var announcementIds = new JSONArray(jsonString);
|
final var announcementIds = new JSONArray(jsonString);
|
||||||
|
if (announcementIds.length() == 0) return true;
|
||||||
|
|
||||||
id = announcementIds.getJSONObject(0).getInt("id");
|
id = announcementIds.getJSONObject(0).getInt("id");
|
||||||
|
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
Logger.printException(() -> "Failed to parse announcement IDs", ex);
|
Logger.printException(() -> "Failed to parse announcement ID", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not show the announcement, if the last announcement id is the same as the current one.
|
// Do not show the announcement, if the last announcement id is the same as the current one.
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import static app.revanced.extension.shared.requests.Route.Method.GET;
|
|||||||
|
|
||||||
public class AnnouncementsRoutes {
|
public class AnnouncementsRoutes {
|
||||||
private static final String ANNOUNCEMENTS_PROVIDER = "https://api.revanced.app/v4";
|
private static final String ANNOUNCEMENTS_PROVIDER = "https://api.revanced.app/v4";
|
||||||
public static final Route GET_LATEST_ANNOUNCEMENT_IDS = new Route(GET, "/announcements/latest/id?tag=youtube");
|
public static final Route GET_LATEST_ANNOUNCEMENT_IDS = new Route(GET, "/announcements/latest/id?tag=\uD83C\uDF9E\uFE0F YouTube");
|
||||||
public static final Route GET_LATEST_ANNOUNCEMENTS = new Route(GET, "/announcements/latest?tag=youtube");
|
public static final Route GET_LATEST_ANNOUNCEMENTS = new Route(GET, "/announcements/latest?tag=\uD83C\uDF9E\uFE0F YouTube");
|
||||||
|
|
||||||
private AnnouncementsRoutes() {
|
private AnnouncementsRoutes() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting COPY_VIDEO_URL = new BooleanSetting("revanced_copy_video_url", FALSE);
|
public static final BooleanSetting COPY_VIDEO_URL = new BooleanSetting("revanced_copy_video_url", FALSE);
|
||||||
public static final BooleanSetting COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_copy_video_url_timestamp", TRUE);
|
public static final BooleanSetting COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_copy_video_url_timestamp", TRUE);
|
||||||
public static final BooleanSetting DISABLE_AUTO_CAPTIONS = new BooleanSetting("revanced_disable_auto_captions", FALSE, true);
|
public static final BooleanSetting DISABLE_AUTO_CAPTIONS = new BooleanSetting("revanced_disable_auto_captions", FALSE, true);
|
||||||
|
public static final BooleanSetting DISABLE_CHAPTER_SKIP_DOUBLE_TAP = new BooleanSetting("revanced_disable_chapter_skip_double_tap", FALSE);
|
||||||
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
|
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
|
||||||
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
|
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
|
||||||
public static final EnumSetting<FullscreenMode> EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED);
|
public static final EnumSetting<FullscreenMode> EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED);
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 5.31.0-dev.12
|
version = 5.31.0-dev.17
|
||||||
|
|||||||
@@ -904,6 +904,10 @@ public final class app/revanced/patches/shared/misc/spoof/UserAgentClientSpoofPa
|
|||||||
public static final fun userAgentClientSpoofPatch (Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun userAgentClientSpoofPatch (Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/shared/misc/string/ReplaceStringPatchKt {
|
||||||
|
public static final fun replaceStringPatch (Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/solidexplorer2/functionality/filesize/RemoveFileSizeLimitPatchKt {
|
public final class app/revanced/patches/solidexplorer2/functionality/filesize/RemoveFileSizeLimitPatchKt {
|
||||||
public static final fun getRemoveFileSizeLimitPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getRemoveFileSizeLimitPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@@ -1232,6 +1236,10 @@ public final class app/revanced/patches/youtube/interaction/dialog/RemoveViewerD
|
|||||||
public static final fun getRemoveViewerDiscretionDialogPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getRemoveViewerDiscretionDialogPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/youtube/interaction/doubletap/DisableChapterSkipDoubleTapPatchKt {
|
||||||
|
public static final fun getDisableChapterSkipDoubleTapPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/youtube/interaction/downloads/DownloadsPatchKt {
|
public final class app/revanced/patches/youtube/interaction/downloads/DownloadsPatchKt {
|
||||||
public static final fun getDownloadsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getDownloadsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,16 @@ import app.revanced.patcher.Fingerprint
|
|||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
import app.revanced.patches.reddit.customclients.spoofClientPatch
|
import app.revanced.patches.reddit.customclients.spoofClientPatch
|
||||||
|
import app.revanced.patches.shared.misc.string.replaceStringPatch
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
|
|
||||||
val spoofClientPatch = spoofClientPatch(redirectUri = "http://baconreader.com/auth") { clientIdOption ->
|
val spoofClientPatch = spoofClientPatch(redirectUri = "http://baconreader.com/auth") { clientIdOption ->
|
||||||
|
dependsOn(
|
||||||
|
// Redirects from SSL to WWW domain are bugged causing auth problems.
|
||||||
|
// Manually rewrite the URLs to fix this.
|
||||||
|
replaceStringPatch("ssl.reddit.com", "www.reddit.com")
|
||||||
|
)
|
||||||
|
|
||||||
compatibleWith(
|
compatibleWith(
|
||||||
"com.onelouder.baconreader",
|
"com.onelouder.baconreader",
|
||||||
"com.onelouder.baconreader.premium",
|
"com.onelouder.baconreader.premium",
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
|||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.util.smali.ExternalLabel
|
import app.revanced.patcher.util.smali.ExternalLabel
|
||||||
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
import java.util.logging.Logger
|
|
||||||
|
|
||||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
"Lapp/revanced/extension/spotify/layout/hide/createbutton/HideCreateButtonPatch;"
|
"Lapp/revanced/extension/spotify/layout/hide/createbutton/HideCreateButtonPatch;"
|
||||||
@@ -26,13 +24,6 @@ val hideCreateButtonPatch = bytecodePatch(
|
|||||||
dependsOn(sharedExtensionPatch)
|
dependsOn(sharedExtensionPatch)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
|
||||||
Logger.getLogger(this::class.java.name).warning(
|
|
||||||
"Create button does not exist in legacy app target. No changes applied."
|
|
||||||
)
|
|
||||||
return@execute
|
|
||||||
}
|
|
||||||
|
|
||||||
val oldNavigationBarAddItemMethod = oldNavigationBarAddItemFingerprint.originalMethodOrNull
|
val oldNavigationBarAddItemMethod = oldNavigationBarAddItemFingerprint.originalMethodOrNull
|
||||||
// Only throw the fingerprint error when oldNavigationBarAddItemMethod does not exist.
|
// Only throw the fingerprint error when oldNavigationBarAddItemMethod does not exist.
|
||||||
val navigationBarItemSetClassDef = if (oldNavigationBarAddItemMethod == null) {
|
val navigationBarItemSetClassDef = if (oldNavigationBarAddItemMethod == null) {
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import app.revanced.patcher.patch.bytecodePatch
|
|||||||
import app.revanced.patcher.patch.resourcePatch
|
import app.revanced.patcher.patch.resourcePatch
|
||||||
import app.revanced.patcher.patch.stringOption
|
import app.revanced.patcher.patch.stringOption
|
||||||
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
import app.revanced.util.getReference
|
||||||
import app.revanced.util.*
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
@@ -19,12 +19,6 @@ private val customThemeBytecodePatch = bytecodePatch {
|
|||||||
dependsOn(sharedExtensionPatch)
|
dependsOn(sharedExtensionPatch)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
|
||||||
// Bytecode changes are not needed for legacy app target.
|
|
||||||
// Player background color is changed with existing resource patch.
|
|
||||||
return@execute
|
|
||||||
}
|
|
||||||
|
|
||||||
val colorSpaceUtilsClassDef = colorSpaceUtilsClassFingerprint.originalClassDef
|
val colorSpaceUtilsClassDef = colorSpaceUtilsClassFingerprint.originalClassDef
|
||||||
|
|
||||||
// Hook a util method that converts ARGB to RGBA in the sRGB color space to replace hardcoded accent colors.
|
// Hook a util method that converts ARGB to RGBA in the sRGB color space to replace hardcoded accent colors.
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
package app.revanced.patches.spotify.lite.ondemand
|
|
||||||
|
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
|
||||||
import app.revanced.patcher.fingerprint
|
|
||||||
|
|
||||||
internal val onDemandFingerprint = fingerprint(fuzzyPatternScanThreshold = 2) {
|
|
||||||
returns("L")
|
|
||||||
parameters()
|
|
||||||
opcodes(
|
|
||||||
Opcode.INVOKE_STATIC,
|
|
||||||
Opcode.MOVE_RESULT,
|
|
||||||
Opcode.INVOKE_STATIC,
|
|
||||||
Opcode.MOVE_RESULT_OBJECT,
|
|
||||||
Opcode.IF_EQZ,
|
|
||||||
Opcode.SGET_OBJECT,
|
|
||||||
Opcode.GOTO,
|
|
||||||
Opcode.SGET_OBJECT,
|
|
||||||
Opcode.INVOKE_VIRTUAL,
|
|
||||||
Opcode.MOVE_RESULT,
|
|
||||||
Opcode.IPUT,
|
|
||||||
Opcode.RETURN_OBJECT,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,21 +1,9 @@
|
|||||||
package app.revanced.patches.spotify.lite.ondemand
|
package app.revanced.patches.spotify.lite.ondemand
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
|
||||||
@Deprecated("Patch no longer works and will be deleted soon")
|
@Deprecated("Patch no longer works and will be deleted soon")
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val onDemandPatch = bytecodePatch(
|
val onDemandPatch = bytecodePatch(
|
||||||
description = "Enables listening to songs on-demand, allowing to play any song from playlists, albums or artists without limitations. This does not remove ads.",
|
description = "Enables listening to songs on-demand, allowing to play any song from playlists, albums or artists without limitations. This does not remove ads.",
|
||||||
) {
|
)
|
||||||
compatibleWith("com.spotify.lite")
|
|
||||||
|
|
||||||
execute {
|
|
||||||
// Spoof a premium account
|
|
||||||
|
|
||||||
onDemandFingerprint.method.addInstruction(
|
|
||||||
onDemandFingerprint.patternMatch!!.endIndex - 1,
|
|
||||||
"const/4 v0, 0x2",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package app.revanced.patches.spotify.misc
|
|||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
import app.revanced.patcher.patch.BytecodePatchContext
|
import app.revanced.patcher.patch.BytecodePatchContext
|
||||||
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
import app.revanced.util.indexOfFirstInstruction
|
import app.revanced.util.indexOfFirstInstruction
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
@@ -13,25 +12,13 @@ import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
|||||||
|
|
||||||
context(BytecodePatchContext)
|
context(BytecodePatchContext)
|
||||||
internal val accountAttributeFingerprint get() = fingerprint {
|
internal val accountAttributeFingerprint get() = fingerprint {
|
||||||
custom { _, classDef ->
|
custom { _, classDef -> classDef.type == "Lcom/spotify/remoteconfig/internal/AccountAttribute;" }
|
||||||
classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
|
||||||
"Lcom/spotify/useraccount/v1/AccountAttribute;"
|
|
||||||
} else {
|
|
||||||
"Lcom/spotify/remoteconfig/internal/AccountAttribute;"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
context(BytecodePatchContext)
|
context(BytecodePatchContext)
|
||||||
internal val productStateProtoGetMapFingerprint get() = fingerprint {
|
internal val productStateProtoGetMapFingerprint get() = fingerprint {
|
||||||
returns("Ljava/util/Map;")
|
returns("Ljava/util/Map;")
|
||||||
custom { _, classDef ->
|
custom { _, classDef -> classDef.type == "Lcom/spotify/remoteconfig/internal/ProductStateProto;" }
|
||||||
classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
|
||||||
"Lcom/spotify/ucs/proto/v0/UcsResponseWrapper${'$'}AccountAttributesResponse;"
|
|
||||||
} else {
|
|
||||||
"Lcom/spotify/remoteconfig/internal/ProductStateProto;"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val buildQueryParametersFingerprint = fingerprint {
|
internal val buildQueryParametersFingerprint = fingerprint {
|
||||||
@@ -90,14 +77,14 @@ internal val contextFromJsonFingerprint = fingerprint {
|
|||||||
)
|
)
|
||||||
custom { method, classDef ->
|
custom { method, classDef ->
|
||||||
method.name == "fromJson" &&
|
method.name == "fromJson" &&
|
||||||
classDef.endsWith("voiceassistants/playermodels/ContextJsonAdapter;")
|
classDef.type.endsWith("voiceassistants/playermodels/ContextJsonAdapter;")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val readPlayerOptionOverridesFingerprint = fingerprint {
|
internal val readPlayerOptionOverridesFingerprint = fingerprint {
|
||||||
custom { method, classDef ->
|
custom { method, classDef ->
|
||||||
method.name == "readPlayerOptionOverrides" &&
|
method.name == "readPlayerOptionOverrides" &&
|
||||||
classDef.endsWith("voiceassistants/playermodels/PreparePlayOptionsJsonAdapter;")
|
classDef.type.endsWith("voiceassistants/playermodels/PreparePlayOptionsJsonAdapter;")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,21 +106,21 @@ internal val abstractProtobufListEnsureIsMutableFingerprint = fingerprint {
|
|||||||
|
|
||||||
internal fun structureGetSectionsFingerprint(className: String) = fingerprint {
|
internal fun structureGetSectionsFingerprint(className: String) = fingerprint {
|
||||||
custom { method, classDef ->
|
custom { method, classDef ->
|
||||||
classDef.endsWith(className) && method.indexOfFirstInstruction {
|
classDef.type.endsWith(className) && method.indexOfFirstInstruction {
|
||||||
opcode == Opcode.IGET_OBJECT && getReference<FieldReference>()?.name == "sections_"
|
opcode == Opcode.IGET_OBJECT && getReference<FieldReference>()?.name == "sections_"
|
||||||
} >= 0
|
} >= 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val homeSectionFingerprint = fingerprint {
|
internal val homeSectionFingerprint = fingerprint {
|
||||||
custom { _, classDef -> classDef.endsWith("homeapi/proto/Section;") }
|
custom { _, classDef -> classDef.type.endsWith("homeapi/proto/Section;") }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val homeStructureGetSectionsFingerprint =
|
internal val homeStructureGetSectionsFingerprint =
|
||||||
structureGetSectionsFingerprint("homeapi/proto/HomeStructure;")
|
structureGetSectionsFingerprint("homeapi/proto/HomeStructure;")
|
||||||
|
|
||||||
internal val browseSectionFingerprint = fingerprint {
|
internal val browseSectionFingerprint = fingerprint {
|
||||||
custom { _, classDef-> classDef.endsWith("browsita/v1/resolved/Section;") }
|
custom { _, classDef-> classDef.type.endsWith("browsita/v1/resolved/Section;") }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val browseStructureGetSectionsFingerprint =
|
internal val browseStructureGetSectionsFingerprint =
|
||||||
|
|||||||
@@ -7,37 +7,12 @@ import com.android.tools.smali.dexlib2.AccessFlags
|
|||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
|
||||||
internal val getPackageInfoFingerprint = fingerprint {
|
|
||||||
strings(
|
|
||||||
"Failed to get the application signatures"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val loadOrbitLibraryFingerprint = fingerprint {
|
internal val loadOrbitLibraryFingerprint = fingerprint {
|
||||||
strings("/liborbit-jni-spotify.so")
|
strings("/liborbit-jni-spotify.so")
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val startupPageLayoutInflateFingerprint = fingerprint {
|
internal val extensionFixConstantsFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
custom { _, classDef -> classDef.type == "Lapp/revanced/extension/spotify/misc/fix/Constants;" }
|
||||||
returns("Landroid/view/View;")
|
|
||||||
parameters("Landroid/view/LayoutInflater;", "Landroid/view/ViewGroup;", "Landroid/os/Bundle;")
|
|
||||||
strings("blueprintContainer", "gradient", "valuePropositionTextView")
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val renderStartLoginScreenFingerprint = fingerprint {
|
|
||||||
strings("authenticationButtonFactory", "MORE_OPTIONS")
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val renderSecondLoginScreenFingerprint = fingerprint {
|
|
||||||
strings("authenticationButtonFactory", "intent_login")
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val renderThirdLoginScreenFingerprint = fingerprint {
|
|
||||||
strings("EMAIL_OR_USERNAME", "listener")
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val thirdLoginScreenLoginOnClickFingerprint = fingerprint {
|
|
||||||
strings("login", "listener", "none")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val runIntegrityVerificationFingerprint = fingerprint {
|
internal val runIntegrityVerificationFingerprint = fingerprint {
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
package app.revanced.patches.spotify.misc.fix
|
package app.revanced.patches.spotify.misc.fix
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.patch.intOption
|
import app.revanced.patcher.patch.intOption
|
||||||
|
import app.revanced.patcher.patch.stringOption
|
||||||
import app.revanced.patches.shared.misc.hex.HexPatchBuilder
|
import app.revanced.patches.shared.misc.hex.HexPatchBuilder
|
||||||
import app.revanced.patches.shared.misc.hex.hexPatch
|
import app.revanced.patches.shared.misc.hex.hexPatch
|
||||||
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.util.*
|
import app.revanced.util.returnEarly
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
|
||||||
|
|
||||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/misc/fix/SpoofClientPatch;"
|
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/misc/fix/SpoofClientPatch;"
|
||||||
|
|
||||||
@@ -25,16 +19,40 @@ val spoofClientPatch = bytecodePatch(
|
|||||||
val requestListenerPort by intOption(
|
val requestListenerPort by intOption(
|
||||||
key = "requestListenerPort",
|
key = "requestListenerPort",
|
||||||
default = 4345,
|
default = 4345,
|
||||||
title = " Login request listener port",
|
title = "Request listener port",
|
||||||
description = "The port to use for the listener that intercepts and handles login requests. " +
|
description = "The port to use for the listener that intercepts and handles spoofed requests. " +
|
||||||
"Port must be between 0 and 65535.",
|
"Port must be between 0 and 65535. " +
|
||||||
required = true,
|
"Do not change this option, if you do not know what you are doing.",
|
||||||
validator = {
|
validator = {
|
||||||
it!!
|
it!!
|
||||||
!(it < 0 || it > 65535)
|
!(it < 0 || it > 65535)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val clientVersion by stringOption(
|
||||||
|
key = "clientVersion",
|
||||||
|
default = "iphone-9.0.58.558.g200011c",
|
||||||
|
title = "Client version",
|
||||||
|
description = "The client version used for spoofing the client token. " +
|
||||||
|
"Do not change this option, if you do not know what you are doing."
|
||||||
|
)
|
||||||
|
|
||||||
|
val hardwareMachine by stringOption(
|
||||||
|
key = "hardwareMachine",
|
||||||
|
default = "iPhone16,1",
|
||||||
|
title = "Hardware machine",
|
||||||
|
description = "The hardware machine used for spoofing the client token. " +
|
||||||
|
"Do not change this option, if you do not know what you are doing."
|
||||||
|
)
|
||||||
|
|
||||||
|
val systemVersion by stringOption(
|
||||||
|
key = "systemVersion",
|
||||||
|
default = "17.7.2",
|
||||||
|
title = "System version",
|
||||||
|
description = "The system version used for spoofing the client token. " +
|
||||||
|
"Do not change this option, if you do not know what you are doing."
|
||||||
|
)
|
||||||
|
|
||||||
dependsOn(
|
dependsOn(
|
||||||
sharedExtensionPatch,
|
sharedExtensionPatch,
|
||||||
hexPatch(ignoreMissingTargetFiles = true, block = fun HexPatchBuilder.() {
|
hexPatch(ignoreMissingTargetFiles = true, block = fun HexPatchBuilder.() {
|
||||||
@@ -44,10 +62,8 @@ val spoofClientPatch = bytecodePatch(
|
|||||||
"x86",
|
"x86",
|
||||||
"x86_64"
|
"x86_64"
|
||||||
).forEach { architecture ->
|
).forEach { architecture ->
|
||||||
"https://login5.spotify.com/v3/login" to "http://127.0.0.1:$requestListenerPort/v3/login" inFile
|
"https://clienttoken.spotify.com/v1/clienttoken" to
|
||||||
"lib/$architecture/liborbit-jni-spotify.so"
|
"http://127.0.0.1:$requestListenerPort/v1/clienttoken" inFile
|
||||||
|
|
||||||
"https://login5.spotify.com/v4/login" to "http://127.0.0.1:$requestListenerPort/v4/login" inFile
|
|
||||||
"lib/$architecture/liborbit-jni-spotify.so"
|
"lib/$architecture/liborbit-jni-spotify.so"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -56,51 +72,6 @@ val spoofClientPatch = bytecodePatch(
|
|||||||
compatibleWith("com.spotify.music")
|
compatibleWith("com.spotify.music")
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
// region Spoof package info.
|
|
||||||
|
|
||||||
getPackageInfoFingerprint.method.apply {
|
|
||||||
// region Spoof signature.
|
|
||||||
|
|
||||||
val failedToGetSignaturesStringIndex =
|
|
||||||
getPackageInfoFingerprint.stringMatches!!.first().index
|
|
||||||
|
|
||||||
val concatSignaturesIndex = indexOfFirstInstructionReversedOrThrow(
|
|
||||||
failedToGetSignaturesStringIndex,
|
|
||||||
Opcode.MOVE_RESULT_OBJECT,
|
|
||||||
)
|
|
||||||
|
|
||||||
val signatureRegister = getInstruction<OneRegisterInstruction>(concatSignaturesIndex).registerA
|
|
||||||
val expectedSignature = "d6a6dced4a85f24204bf9505ccc1fce114cadb32"
|
|
||||||
|
|
||||||
replaceInstruction(concatSignaturesIndex, "const-string v$signatureRegister, \"$expectedSignature\"")
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Spoof installer name.
|
|
||||||
|
|
||||||
val expectedInstallerName = "com.android.vending"
|
|
||||||
|
|
||||||
findInstructionIndicesReversedOrThrow {
|
|
||||||
val reference = getReference<MethodReference>()
|
|
||||||
reference?.name == "getInstallerPackageName" || reference?.name == "getInstallingPackageName"
|
|
||||||
}.forEach { index ->
|
|
||||||
val returnObjectIndex = index + 1
|
|
||||||
|
|
||||||
val installerPackageNameRegister = getInstruction<OneRegisterInstruction>(
|
|
||||||
returnObjectIndex
|
|
||||||
).registerA
|
|
||||||
|
|
||||||
addInstruction(
|
|
||||||
returnObjectIndex + 1,
|
|
||||||
"const-string v$installerPackageNameRegister, \"$expectedInstallerName\""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Spoof client.
|
// region Spoof client.
|
||||||
|
|
||||||
loadOrbitLibraryFingerprint.method.addInstructions(
|
loadOrbitLibraryFingerprint.method.addInstructions(
|
||||||
@@ -111,72 +82,12 @@ val spoofClientPatch = bytecodePatch(
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
startupPageLayoutInflateFingerprint.method.apply {
|
mapOf(
|
||||||
val openLoginWebViewDescriptor =
|
"getClientVersion" to clientVersion!!,
|
||||||
"$EXTENSION_CLASS_DESCRIPTOR->launchLogin(Landroid/view/LayoutInflater;)V"
|
"getSystemVersion" to systemVersion!!,
|
||||||
|
"getHardwareMachine" to hardwareMachine!!
|
||||||
addInstructions(
|
).forEach { (methodName, value) ->
|
||||||
0,
|
extensionFixConstantsFingerprint.classDef.methods.single { it.name == methodName }.returnEarly(value)
|
||||||
"invoke-static/range { p1 .. p1 }, $openLoginWebViewDescriptor"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderStartLoginScreenFingerprint.method.apply {
|
|
||||||
val onEventIndex = indexOfFirstInstructionOrThrow {
|
|
||||||
opcode == Opcode.INVOKE_INTERFACE && getReference<MethodReference>()?.name == "getView"
|
|
||||||
}
|
|
||||||
|
|
||||||
val buttonRegister = getInstruction<OneRegisterInstruction>(onEventIndex + 1).registerA
|
|
||||||
|
|
||||||
addInstruction(
|
|
||||||
onEventIndex + 2,
|
|
||||||
"invoke-static { v$buttonRegister }, $EXTENSION_CLASS_DESCRIPTOR->setNativeLoginHandler(Landroid/view/View;)V"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSecondLoginScreenFingerprint.method.apply {
|
|
||||||
val getViewIndex = indexOfFirstInstructionOrThrow {
|
|
||||||
opcode == Opcode.INVOKE_INTERFACE && getReference<MethodReference>()?.name == "getView"
|
|
||||||
}
|
|
||||||
|
|
||||||
val buttonRegister = getInstruction<OneRegisterInstruction>(getViewIndex + 1).registerA
|
|
||||||
|
|
||||||
// Early return the render for loop since the first item of the loop is the login button.
|
|
||||||
addInstructions(
|
|
||||||
getViewIndex + 2,
|
|
||||||
"""
|
|
||||||
invoke-virtual { v$buttonRegister }, Landroid/view/View;->performClick()Z
|
|
||||||
return-void
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderThirdLoginScreenFingerprint.method.apply {
|
|
||||||
val invokeSetListenerIndex = indexOfFirstInstructionOrThrow {
|
|
||||||
val reference = getReference<MethodReference>()
|
|
||||||
reference?.definingClass == "Landroid/view/View;" && reference.name == "setOnClickListener"
|
|
||||||
}
|
|
||||||
|
|
||||||
val buttonRegister = getInstruction<FiveRegisterInstruction>(invokeSetListenerIndex).registerC
|
|
||||||
|
|
||||||
addInstruction(
|
|
||||||
invokeSetListenerIndex + 1,
|
|
||||||
"invoke-virtual { v$buttonRegister }, Landroid/view/View;->performClick()Z"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
thirdLoginScreenLoginOnClickFingerprint.method.apply {
|
|
||||||
// Use placeholder credentials to pass the login screen.
|
|
||||||
val loginActionIndex = indexOfFirstInstructionOrThrow(Opcode.RETURN_VOID) - 1
|
|
||||||
val loginActionInstruction = getInstruction<FiveRegisterInstruction>(loginActionIndex)
|
|
||||||
|
|
||||||
addInstructions(
|
|
||||||
loginActionIndex,
|
|
||||||
"""
|
|
||||||
const-string v${loginActionInstruction.registerD}, "placeholder"
|
|
||||||
const-string v${loginActionInstruction.registerE}, "placeholder"
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
|||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.patch.stringOption
|
import app.revanced.patcher.patch.stringOption
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||||
@@ -57,16 +56,10 @@ val changeLyricsProviderPatch = bytecodePatch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
|
||||||
Logger.getLogger(this::class.java.name).severe(
|
|
||||||
"Change lyrics provider patch is not supported for this target version."
|
|
||||||
)
|
|
||||||
return@execute
|
|
||||||
}
|
|
||||||
|
|
||||||
val httpClientBuilderMethod = httpClientBuilderFingerprint.originalMethod
|
val httpClientBuilderMethod = httpClientBuilderFingerprint.originalMethod
|
||||||
|
|
||||||
// region Create a modified copy of the HTTP client builder method with the custom lyrics provider host.
|
// region Create a modified copy of the HTTP client builder method with the custom lyrics provider host.
|
||||||
|
|
||||||
val patchedHttpClientBuilderMethod = with(httpClientBuilderMethod) {
|
val patchedHttpClientBuilderMethod = with(httpClientBuilderMethod) {
|
||||||
val invokeBuildUrlIndex = indexOfFirstInstructionOrThrow {
|
val invokeBuildUrlIndex = indexOfFirstInstructionOrThrow {
|
||||||
getReference<MethodReference>()?.returnType == "Lokhttp3/HttpUrl;"
|
getReference<MethodReference>()?.returnType == "Lokhttp3/HttpUrl;"
|
||||||
@@ -89,9 +82,11 @@ val changeLyricsProviderPatch = bytecodePatch(
|
|||||||
httpClientBuilderFingerprint.classDef.methods.add(this)
|
httpClientBuilderFingerprint.classDef.methods.add(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
// region Replace the call to the HTTP client builder method used exclusively for lyrics by the modified one.
|
// region Replace the call to the HTTP client builder method used exclusively for lyrics by the modified one.
|
||||||
|
|
||||||
getLyricsHttpClientFingerprint(httpClientBuilderMethod).method.apply {
|
getLyricsHttpClientFingerprint(httpClientBuilderMethod).method.apply {
|
||||||
val getLyricsHttpClientIndex = indexOfFirstInstructionOrThrow {
|
val getLyricsHttpClientIndex = indexOfFirstInstructionOrThrow {
|
||||||
getReference<MethodReference>() == httpClientBuilderMethod
|
getReference<MethodReference>() == httpClientBuilderMethod
|
||||||
@@ -118,6 +113,7 @@ val changeLyricsProviderPatch = bytecodePatch(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ internal val shareCopyUrlFingerprint = fingerprint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val shareCopyUrlLegacyFingerprint = fingerprint {
|
internal val oldShareCopyUrlFingerprint = fingerprint {
|
||||||
returns("Ljava/lang/Object;")
|
returns("Ljava/lang/Object;")
|
||||||
parameters("Ljava/lang/Object;")
|
parameters("Ljava/lang/Object;")
|
||||||
strings("clipboard", "createNewSession failed")
|
strings("clipboard", "createNewSession failed")
|
||||||
@@ -38,7 +38,7 @@ internal val formatAndroidShareSheetUrlFingerprint = fingerprint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val formatAndroidShareSheetUrlLegacyFingerprint = fingerprint {
|
internal val oldFormatAndroidShareSheetUrlFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PUBLIC)
|
accessFlags(AccessFlags.PUBLIC)
|
||||||
returns("Ljava/lang/String;")
|
returns("Ljava/lang/String;")
|
||||||
parameters("Lcom/spotify/share/social/sharedata/ShareData;", "Ljava/lang/String;")
|
parameters("Lcom/spotify/share/social/sharedata/ShareData;", "Ljava/lang/String;")
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package app.revanced.patches.spotify.misc.privacy
|
package app.revanced.patches.spotify.misc.privacy
|
||||||
|
|
||||||
import app.revanced.patcher.Fingerprint
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
@@ -28,10 +26,10 @@ val sanitizeSharingLinksPatch = bytecodePatch(
|
|||||||
val extensionMethodDescriptor = "$EXTENSION_CLASS_DESCRIPTOR->" +
|
val extensionMethodDescriptor = "$EXTENSION_CLASS_DESCRIPTOR->" +
|
||||||
"sanitizeUrl(Ljava/lang/String;)Ljava/lang/String;"
|
"sanitizeUrl(Ljava/lang/String;)Ljava/lang/String;"
|
||||||
|
|
||||||
val copyFingerprint = if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
val copyFingerprint = if (shareCopyUrlFingerprint.originalMethodOrNull != null) {
|
||||||
shareCopyUrlLegacyFingerprint
|
|
||||||
} else {
|
|
||||||
shareCopyUrlFingerprint
|
shareCopyUrlFingerprint
|
||||||
|
} else {
|
||||||
|
oldShareCopyUrlFingerprint
|
||||||
}
|
}
|
||||||
|
|
||||||
copyFingerprint.method.apply {
|
copyFingerprint.method.apply {
|
||||||
@@ -50,15 +48,10 @@ val sanitizeSharingLinksPatch = bytecodePatch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Android native share sheet is used for all other quick share types (X, WhatsApp, etc).
|
// Android native share sheet is used for all other quick share types (X, WhatsApp, etc).
|
||||||
val shareUrlParameter : String
|
val shareUrlParameter: String
|
||||||
val shareSheetFingerprint : Fingerprint
|
val shareSheetFingerprint = if (formatAndroidShareSheetUrlFingerprint.originalMethodOrNull != null) {
|
||||||
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
val methodAccessFlags = formatAndroidShareSheetUrlFingerprint.originalMethod
|
||||||
shareSheetFingerprint = formatAndroidShareSheetUrlLegacyFingerprint
|
shareUrlParameter = if (AccessFlags.STATIC.isSet(methodAccessFlags.accessFlags)) {
|
||||||
shareUrlParameter = "p2"
|
|
||||||
} else {
|
|
||||||
shareSheetFingerprint = formatAndroidShareSheetUrlFingerprint
|
|
||||||
val methodAccessFlags = formatAndroidShareSheetUrlFingerprint.originalMethod.accessFlags
|
|
||||||
shareUrlParameter = if (AccessFlags.STATIC.isSet(methodAccessFlags)) {
|
|
||||||
// In newer implementations the method is static, so p0 is not `this`.
|
// In newer implementations the method is static, so p0 is not `this`.
|
||||||
"p1"
|
"p1"
|
||||||
} else {
|
} else {
|
||||||
@@ -66,6 +59,11 @@ val sanitizeSharingLinksPatch = bytecodePatch(
|
|||||||
// For that reason, add one to the parameter register.
|
// For that reason, add one to the parameter register.
|
||||||
"p2"
|
"p2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formatAndroidShareSheetUrlFingerprint
|
||||||
|
} else {
|
||||||
|
shareUrlParameter = "p2"
|
||||||
|
oldFormatAndroidShareSheetUrlFingerprint
|
||||||
}
|
}
|
||||||
|
|
||||||
shareSheetFingerprint.method.addInstructions(
|
shareSheetFingerprint.method.addInstructions(
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package app.revanced.patches.spotify.misc.widgets
|
package app.revanced.patches.spotify.misc.widgets
|
||||||
|
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
|
|
||||||
import app.revanced.util.returnEarly
|
import app.revanced.util.returnEarly
|
||||||
import java.util.logging.Logger
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val fixThirdPartyLaunchersWidgets = bytecodePatch(
|
val fixThirdPartyLaunchersWidgets = bytecodePatch(
|
||||||
@@ -13,14 +11,6 @@ val fixThirdPartyLaunchersWidgets = bytecodePatch(
|
|||||||
compatibleWith("com.spotify.music")
|
compatibleWith("com.spotify.music")
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
|
|
||||||
// The permission check does not exist in legacy versions.
|
|
||||||
Logger.getLogger(this::class.java.name).warning(
|
|
||||||
"Legacy app target does not have any third party launcher restrictions. No changes applied."
|
|
||||||
)
|
|
||||||
return@execute
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only system app launchers are granted the BIND_APPWIDGET permission.
|
// Only system app launchers are granted the BIND_APPWIDGET permission.
|
||||||
// Override the method that checks for it to always return true, as this permission is not actually required
|
// Override the method that checks for it to always return true, as this permission is not actually required
|
||||||
// for the widgets to work.
|
// for the widgets to work.
|
||||||
|
|||||||
@@ -1,38 +1,15 @@
|
|||||||
package app.revanced.patches.spotify.shared
|
package app.revanced.patches.spotify.shared
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
import app.revanced.patcher.patch.BytecodePatchContext
|
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
|
||||||
private const val SPOTIFY_MAIN_ACTIVITY = "Lcom/spotify/music/SpotifyMainActivity;"
|
private const val SPOTIFY_MAIN_ACTIVITY = "Lcom/spotify/music/SpotifyMainActivity;"
|
||||||
|
|
||||||
/**
|
|
||||||
* Main activity of target 8.6.98.900.
|
|
||||||
*/
|
|
||||||
internal const val SPOTIFY_MAIN_ACTIVITY_LEGACY = "Lcom/spotify/music/MainActivity;"
|
|
||||||
|
|
||||||
internal val mainActivityOnCreateFingerprint = fingerprint {
|
internal val mainActivityOnCreateFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
returns("V")
|
returns("V")
|
||||||
parameters("Landroid/os/Bundle;")
|
parameters("Landroid/os/Bundle;")
|
||||||
custom { method, classDef ->
|
custom { method, classDef ->
|
||||||
method.name == "onCreate" && (classDef.type == SPOTIFY_MAIN_ACTIVITY
|
method.name == "onCreate" && classDef.type == SPOTIFY_MAIN_ACTIVITY
|
||||||
|| classDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isLegacyAppTarget: Boolean? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If patching a legacy 8.x target. This may also be set if patching slightly older/newer app targets,
|
|
||||||
* but the only legacy target of interest is 8.6.98.900 as it's the last version that
|
|
||||||
* supports Spotify integration on Kenwood/Pioneer car stereos.
|
|
||||||
*/
|
|
||||||
context(BytecodePatchContext)
|
|
||||||
internal val IS_SPOTIFY_LEGACY_APP_TARGET
|
|
||||||
get(): Boolean {
|
|
||||||
if (isLegacyAppTarget == null) {
|
|
||||||
isLegacyAppTarget = mainActivityOnCreateFingerprint.originalClassDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY
|
|
||||||
}
|
|
||||||
return isLegacyAppTarget!!
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package app.revanced.patches.youtube.interaction.doubletap
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
|
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
|
|
||||||
|
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
|
"Lapp/revanced/extension/youtube/patches/DisableChapterSkipDoubleTapPatch;"
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val disableChapterSkipDoubleTapPatch = bytecodePatch(
|
||||||
|
name = "Disable double tap actions",
|
||||||
|
description = "Adds an option to disable player double tap gestures.",
|
||||||
|
) {
|
||||||
|
dependsOn(
|
||||||
|
sharedExtensionPatch,
|
||||||
|
settingsPatch,
|
||||||
|
addResourcesPatch,
|
||||||
|
)
|
||||||
|
|
||||||
|
compatibleWith(
|
||||||
|
"com.google.android.youtube"(
|
||||||
|
"19.34.42",
|
||||||
|
"19.43.41",
|
||||||
|
"19.47.53",
|
||||||
|
"20.07.39",
|
||||||
|
"20.12.46",
|
||||||
|
"20.13.41",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
addResources("youtube", "interaction.doubletap.disableChapterSkipDoubleTapPatch")
|
||||||
|
|
||||||
|
PreferenceScreen.PLAYER.addPreferences(
|
||||||
|
SwitchPreference("revanced_disable_chapter_skip_double_tap"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Force isChapterSeek flag to false.
|
||||||
|
doubleTapInfoGetSeekSourceFingerprint.method.addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->disableDoubleTapChapters(Z)Z
|
||||||
|
move-result p1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
doubleTapInfoCtorFingerprint.match(
|
||||||
|
doubleTapInfoGetSeekSourceFingerprint.classDef
|
||||||
|
).method.addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
invoke-static { p3 }, $EXTENSION_CLASS_DESCRIPTOR->disableDoubleTapChapters(Z)Z
|
||||||
|
move-result p3
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package app.revanced.patches.youtube.interaction.doubletap
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
|
internal val doubleTapInfoGetSeekSourceFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
|
parameters("Z")
|
||||||
|
returns("L") // Enum SeekSource, but name obfuscated.
|
||||||
|
opcodes(
|
||||||
|
Opcode.IF_EQZ,
|
||||||
|
Opcode.SGET_OBJECT,
|
||||||
|
Opcode.RETURN_OBJECT,
|
||||||
|
Opcode.SGET_OBJECT,
|
||||||
|
Opcode.RETURN_OBJECT,
|
||||||
|
)
|
||||||
|
custom { _, classDef ->
|
||||||
|
classDef.fields.count() == 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val doubleTapInfoCtorFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||||
|
parameters(
|
||||||
|
"Landroid/view/MotionEvent;",
|
||||||
|
"I",
|
||||||
|
"Z",
|
||||||
|
"Lj\$/time/Duration;"
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -73,12 +73,9 @@ val enableSlideToSeekPatch = bytecodePatch(
|
|||||||
|
|
||||||
// Disable the double speed seek gesture.
|
// Disable the double speed seek gesture.
|
||||||
if (is_19_17_or_greater) {
|
if (is_19_17_or_greater) {
|
||||||
arrayOf(
|
disableFastForwardGestureFingerprint.let {
|
||||||
disableFastForwardGestureFingerprint,
|
it.method.apply {
|
||||||
disableFastForwardNoticeFingerprint,
|
val targetIndex = it.patternMatch!!.endIndex
|
||||||
).forEach { fingerprint ->
|
|
||||||
fingerprint.method.apply {
|
|
||||||
val targetIndex = fingerprint.patternMatch!!.endIndex
|
|
||||||
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||||
|
|
||||||
addInstructions(
|
addInstructions(
|
||||||
|
|||||||
@@ -3,14 +3,12 @@ package app.revanced.patches.youtube.interaction.seekbar
|
|||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
import app.revanced.util.containsLiteralInstruction
|
import app.revanced.util.containsLiteralInstruction
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
import app.revanced.util.indexOfFirstInstruction
|
|
||||||
import app.revanced.util.indexOfFirstInstructionReversed
|
import app.revanced.util.indexOfFirstInstructionReversed
|
||||||
import app.revanced.util.literal
|
import app.revanced.util.literal
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
import com.android.tools.smali.dexlib2.iface.Method
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
|
||||||
|
|
||||||
internal val swipingUpGestureParentFingerprint = fingerprint {
|
internal val swipingUpGestureParentFingerprint = fingerprint {
|
||||||
returns("Z")
|
returns("Z")
|
||||||
@@ -59,25 +57,6 @@ internal val disableFastForwardGestureFingerprint = fingerprint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val disableFastForwardNoticeFingerprint = fingerprint {
|
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
|
||||||
returns("V")
|
|
||||||
parameters()
|
|
||||||
opcodes(
|
|
||||||
Opcode.CHECK_CAST,
|
|
||||||
Opcode.IGET_OBJECT,
|
|
||||||
Opcode.INVOKE_VIRTUAL,
|
|
||||||
Opcode.MOVE_RESULT,
|
|
||||||
)
|
|
||||||
custom { method, _ ->
|
|
||||||
method.name == "run" && method.indexOfFirstInstruction {
|
|
||||||
// In later targets the code is found in different methods with different strings.
|
|
||||||
val string = getReference<StringReference>()?.string
|
|
||||||
string == "Failed to easy seek haptics vibrate." || string == "search_landing_cache_key"
|
|
||||||
} >= 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val onTouchEventHandlerFingerprint = fingerprint {
|
internal val onTouchEventHandlerFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.PUBLIC)
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.PUBLIC)
|
||||||
returns("Z")
|
returns("Z")
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
|
|||||||
import app.revanced.patches.shared.misc.settings.preference.InputType
|
import app.revanced.patches.shared.misc.settings.preference.InputType
|
||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
import app.revanced.patches.shared.misc.settings.preference.TextPreference
|
import app.revanced.patches.shared.misc.settings.preference.TextPreference
|
||||||
import app.revanced.patches.youtube.interaction.seekbar.disableFastForwardNoticeFingerprint
|
|
||||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
|
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package app.revanced.patches.youtube.video.speed.custom
|
package app.revanced.patches.youtube.video.speed.custom
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstruction
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||||
|
|
||||||
internal val speedLimiterFingerprint = fingerprint {
|
internal val speedLimiterFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
@@ -19,3 +22,16 @@ internal val speedLimiterFingerprint = fingerprint {
|
|||||||
Opcode.INVOKE_STATIC,
|
Opcode.INVOKE_STATIC,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val disableFastForwardNoticeFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
|
returns("V")
|
||||||
|
parameters()
|
||||||
|
custom { method, _ ->
|
||||||
|
method.name == "run" && method.indexOfFirstInstruction {
|
||||||
|
// In later targets the code is found in different methods with different strings.
|
||||||
|
val string = getReference<StringReference>()?.string
|
||||||
|
string == "Failed to easy seek haptics vibrate." || string == "search_landing_cache_key"
|
||||||
|
} >= 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -168,7 +168,17 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
|||||||
<string name="revanced_hide_feed_survey_summary_on">Axın sorğuları gizlidir</string>
|
<string name="revanced_hide_feed_survey_summary_on">Axın sorğuları gizlidir</string>
|
||||||
<string name="revanced_hide_feed_survey_summary_off">Axın sorğuları göstərilir</string>
|
<string name="revanced_hide_feed_survey_summary_off">Axın sorğuları göstərilir</string>
|
||||||
<string name="revanced_hide_floating_microphone_button_title">Üzən mikrofon düyməsini gizlət</string>
|
<string name="revanced_hide_floating_microphone_button_title">Üzən mikrofon düyməsini gizlət</string>
|
||||||
|
<string name="revanced_hide_floating_microphone_button_summary_on">Axtarışda üzən mikrofon düyməsi gizlidir</string>
|
||||||
|
<string name="revanced_hide_floating_microphone_button_summary_off">Üzən mikrofon düyməsi axtarışda göstərilir</string>
|
||||||
<string name="revanced_hide_horizontal_shelves_title">Üfüqi hissələri gizlət</string>
|
<string name="revanced_hide_horizontal_shelves_title">Üfüqi hissələri gizlət</string>
|
||||||
|
<string name="revanced_hide_horizontal_shelves_summary_on">"Üfüqi cərgələr gizlidir, məsələn:
|
||||||
|
• Son xəbərlər
|
||||||
|
• İzləməyə davam et
|
||||||
|
• Daha çox kanal kəşf et
|
||||||
|
• Ən uyğun
|
||||||
|
• Alış-veriş
|
||||||
|
• Yenidən izləyin"</string>
|
||||||
|
<string name="revanced_hide_horizontal_shelves_summary_off">Üfüqi cərgələr görünür</string>
|
||||||
<string name="revanced_hide_image_shelf_title">Şəkil cərgəsin gizlət</string>
|
<string name="revanced_hide_image_shelf_title">Şəkil cərgəsin gizlət</string>
|
||||||
<string name="revanced_hide_image_shelf_summary_on">Şəkil cərgəsi axtarış nəticələrində gizlidir</string>
|
<string name="revanced_hide_image_shelf_summary_on">Şəkil cərgəsi axtarış nəticələrində gizlidir</string>
|
||||||
<string name="revanced_hide_image_shelf_summary_off">Şəkil cərgəsi axtarış nəticələrində görünür</string>
|
<string name="revanced_hide_image_shelf_summary_off">Şəkil cərgəsi axtarış nəticələrində görünür</string>
|
||||||
@@ -184,18 +194,27 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
|||||||
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
||||||
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
||||||
<string name="revanced_hide_notify_me_button_title">\"Mənə bildir\" düyməsini gizlət</string>
|
<string name="revanced_hide_notify_me_button_title">\"Mənə bildir\" düyməsini gizlət</string>
|
||||||
|
<string name="revanced_hide_notify_me_button_summary_on">Mənə bildir düyməsi gizlidir</string>
|
||||||
|
<string name="revanced_hide_notify_me_button_summary_off">Mənə bildir düyməsi görünür</string>
|
||||||
<string name="revanced_hide_playables_title">Oynadılan elementləri gizlət</string>
|
<string name="revanced_hide_playables_title">Oynadılan elementləri gizlət</string>
|
||||||
<string name="revanced_hide_playables_summary_on">Oynadılanlar gizlidir</string>
|
<string name="revanced_hide_playables_summary_on">Oynadılanlar gizlidir</string>
|
||||||
<string name="revanced_hide_playables_summary_off">Oynadılanlar göstərilir</string>
|
<string name="revanced_hide_playables_summary_off">Oynadılanlar göstərilir</string>
|
||||||
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
|
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
|
||||||
This button usually appears when searching for a YT creator. -->
|
This button usually appears when searching for a YT creator. -->
|
||||||
<string name="revanced_hide_show_more_button_title">\'Daha çox göstər\' düyməsini gizlət</string>
|
<string name="revanced_hide_show_more_button_title">\'Daha çox göstər\' düyməsini gizlət</string>
|
||||||
|
<string name="revanced_hide_show_more_button_summary_on">Daha çox göstər düyməsi axtarış nəticələrində gizlidir</string>
|
||||||
|
<string name="revanced_hide_show_more_button_summary_off">Daha çox göstər düyməsi axtarış nəticələrində görünür</string>
|
||||||
<string name="revanced_hide_ticket_shelf_title">Bilet bölməsin gizlət</string>
|
<string name="revanced_hide_ticket_shelf_title">Bilet bölməsin gizlət</string>
|
||||||
<string name="revanced_hide_ticket_shelf_summary_on">Bilet bölməsi gizlidir</string>
|
<string name="revanced_hide_ticket_shelf_summary_on">Bilet bölməsi gizlidir</string>
|
||||||
<string name="revanced_hide_ticket_shelf_summary_off">Bilet bölməsi görünür</string>
|
<string name="revanced_hide_ticket_shelf_summary_off">Bilet bölməsi görünür</string>
|
||||||
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
|
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
|
||||||
|
<string name="revanced_hide_video_recommendation_labels_title">Video tövsiyə etiketlərini gizlət</string>
|
||||||
|
<string name="revanced_hide_video_recommendation_labels_summary_on">\'İnsanlar həmçinin izləyiblər\' və \'Bunu da bəyənə bilərsiniz\' etiketləri axtarış nəticələrində gizlədilib</string>
|
||||||
|
<string name="revanced_hide_video_recommendation_labels_summary_off">\'İnsanlar həmçinin izləyiblər\' və \'Bunu da bəyənə bilərsiniz\' etiketləri axtarış nəticələrində görünür</string>
|
||||||
<!-- https://logos.fandom.com/wiki/YouTube/Yoodles -->
|
<!-- https://logos.fandom.com/wiki/YouTube/Yoodles -->
|
||||||
<string name="revanced_hide_doodles_title">YouTube Doodle-ları gizlət</string>
|
<string name="revanced_hide_doodles_title">YouTube Doodle-ları gizlət</string>
|
||||||
|
<string name="revanced_hide_doodles_summary_on">YouTube Doodles animasiyası simvolda gizlidir</string>
|
||||||
|
<string name="revanced_hide_doodles_summary_off">YouTube Doodles animasiyası simvolda görünür</string>
|
||||||
<string name="revanced_hide_doodles_user_dialog_message">"YouTube Doodle-ları hər il bir neçə gün görünür.
|
<string name="revanced_hide_doodles_user_dialog_message">"YouTube Doodle-ları hər il bir neçə gün görünür.
|
||||||
|
|
||||||
Əgər hazırda bölgənizdə Doodle göstərilirsə və bu gizlətmə seçimi aktivdirsə, axtarış cizgisi aşağısındakı filtr sahəsi də gizlədiləcək."</string>
|
Əgər hazırda bölgənizdə Doodle göstərilirsə və bu gizlətmə seçimi aktivdirsə, axtarış cizgisi aşağısındakı filtr sahəsi də gizlədiləcək."</string>
|
||||||
@@ -214,6 +233,8 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
|
|||||||
<!-- 'Join' should be translated using the same localized wording YouTube displays.
|
<!-- 'Join' should be translated using the same localized wording YouTube displays.
|
||||||
This appears in the video player for certain videos. -->
|
This appears in the video player for certain videos. -->
|
||||||
<string name="revanced_hide_join_membership_button_title">Qoşul düyməsin gizlət</string>
|
<string name="revanced_hide_join_membership_button_title">Qoşul düyməsin gizlət</string>
|
||||||
|
<string name="revanced_hide_join_membership_button_summary_on">Qoşul düyməsi gizlidir</string>
|
||||||
|
<string name="revanced_hide_join_membership_button_summary_off">Qoşul düyməsi görünür</string>
|
||||||
<string name="revanced_hide_medical_panels_title">Tibbi lövhələri gizlət</string>
|
<string name="revanced_hide_medical_panels_title">Tibbi lövhələri gizlət</string>
|
||||||
<string name="revanced_hide_medical_panels_summary_on">Tibbi lövhələr gizlidir</string>
|
<string name="revanced_hide_medical_panels_summary_on">Tibbi lövhələr gizlidir</string>
|
||||||
<string name="revanced_hide_medical_panels_summary_off">Tibbi lövhələr göstərilir</string>
|
<string name="revanced_hide_medical_panels_summary_off">Tibbi lövhələr göstərilir</string>
|
||||||
@@ -371,7 +392,11 @@ Məhdudiyyətlər
|
|||||||
</patch>
|
</patch>
|
||||||
<patch id="ad.general.hideAdsResourcePatch">
|
<patch id="ad.general.hideAdsResourcePatch">
|
||||||
<string name="revanced_hide_creator_store_shelf_title">Yaradıcı mağaza bölümün gizlət</string>
|
<string name="revanced_hide_creator_store_shelf_title">Yaradıcı mağaza bölümün gizlət</string>
|
||||||
|
<string name="revanced_hide_creator_store_shelf_summary_on">Yaradıcı alış-veriş cərgəsi video oynadıcı altında gizlidir</string>
|
||||||
|
<string name="revanced_hide_creator_store_shelf_summary_off">Yaradıcı alış-veriş cərgəsi video oynadıcı altında görünür</string>
|
||||||
<string name="revanced_hide_end_screen_store_banner_title">Son ekran mağaza etiketini gizlət</string>
|
<string name="revanced_hide_end_screen_store_banner_title">Son ekran mağaza etiketini gizlət</string>
|
||||||
|
<string name="revanced_hide_end_screen_store_banner_summary_on">Son ekran alış-veriş etiketi gizlədilib</string>
|
||||||
|
<string name="revanced_hide_end_screen_store_banner_summary_off">Son ekran alış-veriş etiketi görünür</string>
|
||||||
<string name="revanced_hide_fullscreen_ads_title">Tam ekran reklamlarını gizlət</string>
|
<string name="revanced_hide_fullscreen_ads_title">Tam ekran reklamlarını gizlət</string>
|
||||||
<string name="revanced_hide_fullscreen_ads_summary_on">"Tam ekran reklamları gizlidir
|
<string name="revanced_hide_fullscreen_ads_summary_on">"Tam ekran reklamları gizlidir
|
||||||
|
|
||||||
@@ -392,8 +417,12 @@ Bu xüsusiyyət yalnız köhnə cihazlar üçün mövcuddur"</string>
|
|||||||
<string name="revanced_hide_self_sponsor_ads_summary_on">Özünə sponsorluq edilən kartlar gizlidir</string>
|
<string name="revanced_hide_self_sponsor_ads_summary_on">Özünə sponsorluq edilən kartlar gizlidir</string>
|
||||||
<string name="revanced_hide_self_sponsor_ads_summary_off">Özünə sponsorluq edilən kartlar göstərilir</string>
|
<string name="revanced_hide_self_sponsor_ads_summary_off">Özünə sponsorluq edilən kartlar göstərilir</string>
|
||||||
<string name="revanced_hide_shopping_links_title">Alış-veriş linklərini gizlət</string>
|
<string name="revanced_hide_shopping_links_title">Alış-veriş linklərini gizlət</string>
|
||||||
|
<string name="revanced_hide_shopping_links_summary_on">Alış-veriş linkləri video təsvirdə gizlidir</string>
|
||||||
|
<string name="revanced_hide_shopping_links_summary_off">Alış-veriş linkləri video təsvirdə görünür</string>
|
||||||
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
||||||
<string name="revanced_hide_view_products_banner_title">“Məhsullara baxın” panelin gizlət</string>
|
<string name="revanced_hide_view_products_banner_title">“Məhsullara baxın” panelin gizlət</string>
|
||||||
|
<string name="revanced_hide_view_products_banner_summary_on">Məhsullara baxış etiketi video örtüyündə gizlidir</string>
|
||||||
|
<string name="revanced_hide_view_products_banner_summary_off">Məhsullara baxış etiketi video örtüyündə görünür</string>
|
||||||
<string name="revanced_hide_web_search_results_title">Veb axtarış nəticələrini gizlət</string>
|
<string name="revanced_hide_web_search_results_title">Veb axtarış nəticələrini gizlət</string>
|
||||||
<string name="revanced_hide_web_search_results_summary_on">Veb axtarış nəticələri gizlədilir</string>
|
<string name="revanced_hide_web_search_results_summary_on">Veb axtarış nəticələri gizlədilir</string>
|
||||||
<string name="revanced_hide_web_search_results_summary_off">Veb axtarış nəticələri göstərilir</string>
|
<string name="revanced_hide_web_search_results_summary_off">Veb axtarış nəticələri göstərilir</string>
|
||||||
@@ -707,7 +736,13 @@ Audio trek seçimin göstərmək üçün \"Video axınları saxtalaşdır\"ı iO
|
|||||||
<string name="revanced_shorts_player_screen_title">Shorts oynadıcı</string>
|
<string name="revanced_shorts_player_screen_title">Shorts oynadıcı</string>
|
||||||
<string name="revanced_shorts_player_screen_summary">Shorts oynadıcıda hissəcikləri gizlət və ya göstər</string>
|
<string name="revanced_shorts_player_screen_summary">Shorts oynadıcıda hissəcikləri gizlət və ya göstər</string>
|
||||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||||
|
<string name="revanced_hide_shorts_home_title">Shorts-u Ev axınında gizlət</string>
|
||||||
|
<string name="revanced_hide_shorts_home_summary_on">Ev axını və əlaqəli videolarda gizlidir</string>
|
||||||
|
<string name="revanced_hide_shorts_home_summary_off">Ev axını və əlaqəli videolarda görünür</string>
|
||||||
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
|
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
|
||||||
|
<string name="revanced_hide_shorts_subscriptions_title">Shorts-u Abunəliklər axınında gizlət</string>
|
||||||
|
<string name="revanced_hide_shorts_subscriptions_summary_on">Abunəliklər axınında gizlidir</string>
|
||||||
|
<string name="revanced_hide_shorts_subscriptions_summary_off">Abunəliklər axınında görünür</string>
|
||||||
<string name="revanced_hide_shorts_search_title">Axtarış nəticələrindəki \"Shorts\"u gizlət</string>
|
<string name="revanced_hide_shorts_search_title">Axtarış nəticələrindəki \"Shorts\"u gizlət</string>
|
||||||
<string name="revanced_hide_shorts_search_summary_on">Axtarış nəticələrində gizlidir</string>
|
<string name="revanced_hide_shorts_search_summary_on">Axtarış nəticələrində gizlidir</string>
|
||||||
<string name="revanced_hide_shorts_search_summary_off">Axtarış nəticələrində görünür</string>
|
<string name="revanced_hide_shorts_search_summary_off">Axtarış nəticələrində görünür</string>
|
||||||
@@ -715,6 +750,8 @@ Audio trek seçimin göstərmək üçün \"Video axınları saxtalaşdır\"ı iO
|
|||||||
<string name="revanced_hide_shorts_history_summary_on">Baxış tarixçəsində gizlidir</string>
|
<string name="revanced_hide_shorts_history_summary_on">Baxış tarixçəsində gizlidir</string>
|
||||||
<string name="revanced_hide_shorts_history_summary_off">Baxış tarixçəsində göstərilib</string>
|
<string name="revanced_hide_shorts_history_summary_off">Baxış tarixçəsində göstərilib</string>
|
||||||
<string name="revanced_hide_shorts_super_thanks_button_title">Super Təşəkkür Al düyməsini gizlət</string>
|
<string name="revanced_hide_shorts_super_thanks_button_title">Super Təşəkkür Al düyməsini gizlət</string>
|
||||||
|
<string name="revanced_hide_shorts_super_thanks_button_summary_on">Super Təşəkkürlər Al düyməsi gizlidir</string>
|
||||||
|
<string name="revanced_hide_shorts_super_thanks_button_summary_off">Super Təşəkkürlər Al düyməsi görünür</string>
|
||||||
<string name="revanced_hide_shorts_effect_button_title">Effekt düyməsini gizlət</string>
|
<string name="revanced_hide_shorts_effect_button_title">Effekt düyməsini gizlət</string>
|
||||||
<string name="revanced_hide_shorts_effect_button_summary_on">Effekt düyməsi gizlidir</string>
|
<string name="revanced_hide_shorts_effect_button_summary_on">Effekt düyməsi gizlidir</string>
|
||||||
<string name="revanced_hide_shorts_effect_button_summary_off">Effekt düyməsi görünür</string>
|
<string name="revanced_hide_shorts_effect_button_summary_off">Effekt düyməsi görünür</string>
|
||||||
@@ -797,7 +834,11 @@ Audio trek seçimin göstərmək üçün \"Video axınları saxtalaşdır\"ı iO
|
|||||||
<string name="revanced_hide_shorts_channel_bar_summary_on">Kanal çubuğu gizlidir</string>
|
<string name="revanced_hide_shorts_channel_bar_summary_on">Kanal çubuğu gizlidir</string>
|
||||||
<string name="revanced_hide_shorts_channel_bar_summary_off">Kanal çubuğu göstərilir</string>
|
<string name="revanced_hide_shorts_channel_bar_summary_off">Kanal çubuğu göstərilir</string>
|
||||||
<string name="revanced_hide_shorts_video_title_title">Video başlığını gizlət</string>
|
<string name="revanced_hide_shorts_video_title_title">Video başlığını gizlət</string>
|
||||||
|
<string name="revanced_hide_shorts_video_title_summary_on">Video başlığı gizlidir</string>
|
||||||
|
<string name="revanced_hide_shorts_video_title_summary_off">Video başlığı görünür</string>
|
||||||
<string name="revanced_hide_shorts_sound_metadata_label_title">Səs üst məlumat etiketini gizlət</string>
|
<string name="revanced_hide_shorts_sound_metadata_label_title">Səs üst məlumat etiketini gizlət</string>
|
||||||
|
<string name="revanced_hide_shorts_sound_metadata_label_summary_on">Səs üst məlumat etiketi gizlədilib</string>
|
||||||
|
<string name="revanced_hide_shorts_sound_metadata_label_summary_off">Səs üst məlumat etiketi görünür</string>
|
||||||
<string name="revanced_hide_shorts_full_video_link_label_title">Video keçidi etiketini gizlət</string>
|
<string name="revanced_hide_shorts_full_video_link_label_title">Video keçidi etiketini gizlət</string>
|
||||||
<string name="revanced_hide_shorts_full_video_link_label_summary_on">Video linki etiketi gizlidir</string>
|
<string name="revanced_hide_shorts_full_video_link_label_summary_on">Video linki etiketi gizlidir</string>
|
||||||
<string name="revanced_hide_shorts_full_video_link_label_summary_off">Video link etiketi göstərilir</string>
|
<string name="revanced_hide_shorts_full_video_link_label_summary_off">Video link etiketi göstərilir</string>
|
||||||
@@ -813,6 +854,9 @@ Avtomatik oynatma YouTube ayarlarında dəyişdirilə bilər: Ayarlar → Oxunu
|
|||||||
<string name="revanced_end_screen_suggested_video_summary_off">Son ekranda bildirilən video göstərilir</string>
|
<string name="revanced_end_screen_suggested_video_summary_off">Son ekranda bildirilən video göstərilir</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
|
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
|
||||||
|
<string name="revanced_hide_related_videos_overlay_title">Əlaqəli videolar örtüyünü gizlət</string>
|
||||||
|
<string name="revanced_hide_related_videos_overlay_summary_on">Əlaqəli videolar yerləşməsi tam ekranda gizlidir</string>
|
||||||
|
<string name="revanced_hide_related_videos_overlay_summary_off">Əlaqəli videolar yerləşməsi tam ekranda görünür</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="layout.hide.time.hideTimestampPatch">
|
<patch id="layout.hide.time.hideTimestampPatch">
|
||||||
<string name="revanced_hide_timestamp_title">Video vaxt möhürünü gizlət</string>
|
<string name="revanced_hide_timestamp_title">Video vaxt möhürünü gizlət</string>
|
||||||
@@ -1279,8 +1323,10 @@ Bunu aktivləşdirmə, bəzi regionlarda əngəllənib silinən şəkilləri dü
|
|||||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||||
<string name="revanced_alt_thumbnail_home_title">Ev paneli</string>
|
<string name="revanced_alt_thumbnail_home_title">Ev paneli</string>
|
||||||
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
|
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
|
||||||
|
<string name="revanced_alt_thumbnail_subscription_title">Abunəliklər bölməsi</string>
|
||||||
<!-- 'You' should be translated using the same localized wording YouTube displays for the You (Library) tab. -->
|
<!-- 'You' should be translated using the same localized wording YouTube displays for the You (Library) tab. -->
|
||||||
<string name="revanced_alt_thumbnail_library_title">\"Siz\" paneli</string>
|
<string name="revanced_alt_thumbnail_library_title">\"Siz\" paneli</string>
|
||||||
|
<string name="revanced_alt_thumbnail_player_title">Oynadıcı pleylistləri & tövsiyələri</string>
|
||||||
<string name="revanced_alt_thumbnail_search_title">Axtarış nəticələri</string>
|
<string name="revanced_alt_thumbnail_search_title">Axtarış nəticələri</string>
|
||||||
<string name="revanced_alt_thumbnail_options_entry_1">Orijinal miniatürlər</string>
|
<string name="revanced_alt_thumbnail_options_entry_1">Orijinal miniatürlər</string>
|
||||||
<string name="revanced_alt_thumbnail_options_entry_2">DeArrow & Orijinal miniatürlər</string>
|
<string name="revanced_alt_thumbnail_options_entry_2">DeArrow & Orijinal miniatürlər</string>
|
||||||
@@ -1496,6 +1542,7 @@ AVC maksimum 1080p görüntü imkanına malikdir, Opus audio kodlama olmur və v
|
|||||||
<string name="revanced_block_video_ads_summary_off">Video reklamlar bloklanmır</string>
|
<string name="revanced_block_video_ads_summary_off">Video reklamlar bloklanmır</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="chat.antidelete.showDeletedMessagesPatch">
|
<patch id="chat.antidelete.showDeletedMessagesPatch">
|
||||||
|
<string name="revanced_deleted_msg">Məlumat silindi</string>
|
||||||
<string name="revanced_show_deleted_messages_title">Silinən mesajları göstər</string>
|
<string name="revanced_show_deleted_messages_title">Silinən mesajları göstər</string>
|
||||||
<string name="revanced_show_deleted_messages_entry_1">Silinən mesajlar göstərilməsin</string>
|
<string name="revanced_show_deleted_messages_entry_1">Silinən mesajlar göstərilməsin</string>
|
||||||
<string name="revanced_show_deleted_messages_entry_2">Silinmiş mesajları boz panel arxasında gizlət</string>
|
<string name="revanced_show_deleted_messages_entry_2">Silinmiş mesajları boz panel arxasında gizlət</string>
|
||||||
@@ -1516,8 +1563,11 @@ AVC maksimum 1080p görüntü imkanına malikdir, Opus audio kodlama olmur və v
|
|||||||
<string name="revanced_settings">ReVanced Tənzimləmələri</string>
|
<string name="revanced_settings">ReVanced Tənzimləmələri</string>
|
||||||
<string name="revanced_about_title">Haqqında</string>
|
<string name="revanced_about_title">Haqqında</string>
|
||||||
<string name="revanced_about_summary">ReVanced Haqqında</string>
|
<string name="revanced_about_summary">ReVanced Haqqında</string>
|
||||||
|
<string name="revanced_ads_screen_title">Reklam Əngəlləmə</string>
|
||||||
|
<string name="revanced_ads_screen_summary">Reklam Əngəlləmə tənzimləmələri</string>
|
||||||
<string name="revanced_chat_screen_title">Söhbət</string>
|
<string name="revanced_chat_screen_title">Söhbət</string>
|
||||||
<string name="revanced_chat_screen_summary">Söhbət tənzimləmələri</string>
|
<string name="revanced_chat_screen_summary">Söhbət tənzimləmələri</string>
|
||||||
|
<string name="revanced_misc_screen_title">Çoxvariantlı</string>
|
||||||
<string name="revanced_misc_screen_summary">Müxtəlif tənzimləmələr</string>
|
<string name="revanced_misc_screen_summary">Müxtəlif tənzimləmələr</string>
|
||||||
<string name="revanced_general_category_title">Ümumi tənzimləmələr</string>
|
<string name="revanced_general_category_title">Ümumi tənzimləmələr</string>
|
||||||
<string name="revanced_other_category_title">Digər tənzimləmələr</string>
|
<string name="revanced_other_category_title">Digər tənzimləmələr</string>
|
||||||
|
|||||||
@@ -161,11 +161,19 @@ Et saa ilmoituksia odottamattomista tapahtumista."</string>
|
|||||||
<string name="revanced_hide_crowdfunding_box_title">Piilota joukkorahoituslaatikko</string>
|
<string name="revanced_hide_crowdfunding_box_title">Piilota joukkorahoituslaatikko</string>
|
||||||
<string name="revanced_hide_crowdfunding_box_summary_on">Joukkorahoituslaatikko on piilotettu</string>
|
<string name="revanced_hide_crowdfunding_box_summary_on">Joukkorahoituslaatikko on piilotettu</string>
|
||||||
<string name="revanced_hide_crowdfunding_box_summary_off">Joukkorahoituslaatikko näytetään</string>
|
<string name="revanced_hide_crowdfunding_box_summary_off">Joukkorahoituslaatikko näytetään</string>
|
||||||
|
<string name="revanced_hide_expandable_card_title">Piilota laajennettava kortti</string>
|
||||||
|
<string name="revanced_hide_expandable_card_summary_on">Laajennettava kortti on piilotettu videoiden alla</string>
|
||||||
|
<string name="revanced_hide_expandable_card_summary_off">Laajennettava kortti näytetään videoiden alla</string>
|
||||||
<string name="revanced_hide_feed_survey_title">Piilota syötteen kyselyt</string>
|
<string name="revanced_hide_feed_survey_title">Piilota syötteen kyselyt</string>
|
||||||
<string name="revanced_hide_feed_survey_summary_on">Syötteen kyselyt on piilotettu</string>
|
<string name="revanced_hide_feed_survey_summary_on">Syötteen kyselyt on piilotettu</string>
|
||||||
<string name="revanced_hide_feed_survey_summary_off">Syötteen kyselyt näytetään</string>
|
<string name="revanced_hide_feed_survey_summary_off">Syötteen kyselyt näytetään</string>
|
||||||
<string name="revanced_hide_floating_microphone_button_title">Piilota kelluva mikrofonipainike</string>
|
<string name="revanced_hide_floating_microphone_button_title">Piilota kelluva mikrofonipainike</string>
|
||||||
|
<string name="revanced_hide_floating_microphone_button_summary_on">Kelluva mikrofonipainike on piilotettu haussa</string>
|
||||||
|
<string name="revanced_hide_floating_microphone_button_summary_off">Kelluva mikrofonipainike näytetään haussa</string>
|
||||||
<string name="revanced_hide_horizontal_shelves_title">Piilota vaakasuuntaiset hyllyt</string>
|
<string name="revanced_hide_horizontal_shelves_title">Piilota vaakasuuntaiset hyllyt</string>
|
||||||
|
<string name="revanced_hide_image_shelf_title">Piilota kuvahylly</string>
|
||||||
|
<string name="revanced_hide_image_shelf_summary_on">Kuvahylly on piilotettu hakutuloksissa</string>
|
||||||
|
<string name="revanced_hide_image_shelf_summary_off">Kuvahylly näytetään hakutuloksissa</string>
|
||||||
<string name="revanced_hide_latest_posts_title">Piilota uusimmat postaukset</string>
|
<string name="revanced_hide_latest_posts_title">Piilota uusimmat postaukset</string>
|
||||||
<string name="revanced_hide_latest_posts_summary_on">Uusimmat postaukset on piilotettu</string>
|
<string name="revanced_hide_latest_posts_summary_on">Uusimmat postaukset on piilotettu</string>
|
||||||
<string name="revanced_hide_latest_posts_summary_off">Uusimmat postaukset näytetään</string>
|
<string name="revanced_hide_latest_posts_summary_off">Uusimmat postaukset näytetään</string>
|
||||||
@@ -178,12 +186,16 @@ Et saa ilmoituksia odottamattomista tapahtumista."</string>
|
|||||||
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
|
||||||
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
This item appear in the subscription feed for future livestreams or unreleased videos. -->
|
||||||
<string name="revanced_hide_notify_me_button_title">Piilota \"Ilmoita minulle\" -painike</string>
|
<string name="revanced_hide_notify_me_button_title">Piilota \"Ilmoita minulle\" -painike</string>
|
||||||
|
<string name="revanced_hide_notify_me_button_summary_on">Ilmoita minulle -painike on piilotettu</string>
|
||||||
|
<string name="revanced_hide_notify_me_button_summary_off">Ilmoita minulle -painike näytetään</string>
|
||||||
<string name="revanced_hide_playables_title">Piilota Pelattavat</string>
|
<string name="revanced_hide_playables_title">Piilota Pelattavat</string>
|
||||||
<string name="revanced_hide_playables_summary_on">Pelattavat on piilotettu</string>
|
<string name="revanced_hide_playables_summary_on">Pelattavat on piilotettu</string>
|
||||||
<string name="revanced_hide_playables_summary_off">Pelattavat näytetään</string>
|
<string name="revanced_hide_playables_summary_off">Pelattavat näytetään</string>
|
||||||
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
|
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
|
||||||
This button usually appears when searching for a YT creator. -->
|
This button usually appears when searching for a YT creator. -->
|
||||||
<string name="revanced_hide_show_more_button_title">Piilota \"Näytä lisää\" -painike</string>
|
<string name="revanced_hide_show_more_button_title">Piilota \"Näytä lisää\" -painike</string>
|
||||||
|
<string name="revanced_hide_show_more_button_summary_on">Näytä lisää -painike on piilotettu hakutuloksissa</string>
|
||||||
|
<string name="revanced_hide_show_more_button_summary_off">Näytä lisää -painike näytetään hakutuloksissa</string>
|
||||||
<string name="revanced_hide_ticket_shelf_title">Piilota lippuhylly</string>
|
<string name="revanced_hide_ticket_shelf_title">Piilota lippuhylly</string>
|
||||||
<string name="revanced_hide_ticket_shelf_summary_on">Lippuhylly on piilotettu</string>
|
<string name="revanced_hide_ticket_shelf_summary_on">Lippuhylly on piilotettu</string>
|
||||||
<string name="revanced_hide_ticket_shelf_summary_off">Lippuhylly näytetään</string>
|
<string name="revanced_hide_ticket_shelf_summary_off">Lippuhylly näytetään</string>
|
||||||
@@ -208,9 +220,13 @@ Jos Doodle näkyy tällä hetkellä alueellasi ja tämä piilotusasetus on käyt
|
|||||||
<!-- 'Join' should be translated using the same localized wording YouTube displays.
|
<!-- 'Join' should be translated using the same localized wording YouTube displays.
|
||||||
This appears in the video player for certain videos. -->
|
This appears in the video player for certain videos. -->
|
||||||
<string name="revanced_hide_join_membership_button_title">Piilota Liity-painike</string>
|
<string name="revanced_hide_join_membership_button_title">Piilota Liity-painike</string>
|
||||||
|
<string name="revanced_hide_join_membership_button_summary_on">Liity-painike on piilotettu</string>
|
||||||
|
<string name="revanced_hide_join_membership_button_summary_off">Liity-painike näytetään</string>
|
||||||
<string name="revanced_hide_medical_panels_title">Piilota lääketieteelliset paneelit</string>
|
<string name="revanced_hide_medical_panels_title">Piilota lääketieteelliset paneelit</string>
|
||||||
<string name="revanced_hide_medical_panels_summary_on">Lääketieteelliset paneelit on piilotettu</string>
|
<string name="revanced_hide_medical_panels_summary_on">Lääketieteelliset paneelit on piilotettu</string>
|
||||||
<string name="revanced_hide_medical_panels_summary_off">Lääketieteelliset paneelit näytetään</string>
|
<string name="revanced_hide_medical_panels_summary_off">Lääketieteelliset paneelit näytetään</string>
|
||||||
|
<string name="revanced_hide_quick_actions_title">Piilota pikatoiminnot</string>
|
||||||
|
<string name="revanced_hide_related_videos_title">Piilota liittyvät videot</string>
|
||||||
<string name="revanced_hide_subscribers_community_guidelines_title">Piilota tilaajien ohjeet</string>
|
<string name="revanced_hide_subscribers_community_guidelines_title">Piilota tilaajien ohjeet</string>
|
||||||
<string name="revanced_hide_subscribers_community_guidelines_summary_on">Tilaajien yhteisön säännöt on piilotettu</string>
|
<string name="revanced_hide_subscribers_community_guidelines_summary_on">Tilaajien yhteisön säännöt on piilotettu</string>
|
||||||
<string name="revanced_hide_subscribers_community_guidelines_summary_off">Tilaajien yhteisön säännöt näytetään</string>
|
<string name="revanced_hide_subscribers_community_guidelines_summary_off">Tilaajien yhteisön säännöt näytetään</string>
|
||||||
@@ -260,6 +276,7 @@ Jos Doodle näkyy tällä hetkellä alueellasi ja tämä piilotusasetus on käyt
|
|||||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_title">Piilota liittyvissä videoissa</string>
|
<string name="revanced_hide_filter_bar_feed_in_related_videos_title">Piilota liittyvissä videoissa</string>
|
||||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_on">Piilotettu liittyvissä videoissa</string>
|
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_on">Piilotettu liittyvissä videoissa</string>
|
||||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_off">Näytetään liittyvissä videoissa</string>
|
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_off">Näytetään liittyvissä videoissa</string>
|
||||||
|
<string name="revanced_channel_screen_title">Kanavasivu</string>
|
||||||
<!-- 'For You' should be translated using the same localized wording YouTube displays. -->
|
<!-- 'For You' should be translated using the same localized wording YouTube displays. -->
|
||||||
<string name="revanced_hide_for_you_shelf_title">Piilota \"\'Sinulle\" -hylly</string>
|
<string name="revanced_hide_for_you_shelf_title">Piilota \"\'Sinulle\" -hylly</string>
|
||||||
<!-- 'Visit Community' should be translated with the same localized wording that YouTube displays. -->
|
<!-- 'Visit Community' should be translated with the same localized wording that YouTube displays. -->
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Second \"item\" text"</string>
|
|||||||
</patch>
|
</patch>
|
||||||
<patch id="misc.settings.settingsResourcePatch">
|
<patch id="misc.settings.settingsResourcePatch">
|
||||||
<string name="revanced_settings_submenu_title">設定</string>
|
<string name="revanced_settings_submenu_title">設定</string>
|
||||||
<string name="revanced_settings_confirm_user_dialog_title">本当に続行しますか?</string>
|
<string name="revanced_settings_confirm_user_dialog_title">続行してもよろしいですか?</string>
|
||||||
<string name="revanced_settings_reset">リセット</string>
|
<string name="revanced_settings_reset">リセット</string>
|
||||||
<string name="revanced_settings_reset_color">色をリセット</string>
|
<string name="revanced_settings_reset_color">色をリセット</string>
|
||||||
<string name="revanced_settings_color_invalid">色の値が無効です</string>
|
<string name="revanced_settings_color_invalid">色の値が無効です</string>
|
||||||
@@ -301,9 +301,9 @@ GmsCore の電池の最適化を無効にしても、バッテリーの使用に
|
|||||||
<string name="revanced_hide_for_you_shelf_title">「おすすめ」欄を非表示</string>
|
<string name="revanced_hide_for_you_shelf_title">「おすすめ」欄を非表示</string>
|
||||||
<string name="revanced_hide_for_you_shelf_summary_on">「おすすめ」欄は表示されません</string>
|
<string name="revanced_hide_for_you_shelf_summary_on">「おすすめ」欄は表示されません</string>
|
||||||
<string name="revanced_hide_for_you_shelf_summary_off">「おすすめ」欄は表示されます</string>
|
<string name="revanced_hide_for_you_shelf_summary_off">「おすすめ」欄は表示されます</string>
|
||||||
<string name="revanced_hide_links_preview_title">リンク プレビューを非表示</string>
|
<string name="revanced_hide_links_preview_title">リンク集のプレビューを非表示</string>
|
||||||
<string name="revanced_hide_links_preview_summary_on">リンク プレビューは表示されません</string>
|
<string name="revanced_hide_links_preview_summary_on">リンク集のプレビューは表示されません</string>
|
||||||
<string name="revanced_hide_links_preview_summary_off">リンク プレビューは表示されます</string>
|
<string name="revanced_hide_links_preview_summary_off">リンク集のプレビューは表示されます</string>
|
||||||
<string name="revanced_hide_members_shelf_title">メンバー欄を非表示</string>
|
<string name="revanced_hide_members_shelf_title">メンバー欄を非表示</string>
|
||||||
<string name="revanced_hide_members_shelf_summary_on">メンバー欄は表示されません</string>
|
<string name="revanced_hide_members_shelf_summary_on">メンバー欄は表示されません</string>
|
||||||
<string name="revanced_hide_members_shelf_summary_off">メンバー欄は表示されます</string>
|
<string name="revanced_hide_members_shelf_summary_off">メンバー欄は表示されます</string>
|
||||||
@@ -412,9 +412,9 @@ GmsCore の電池の最適化を無効にしても、バッテリーの使用に
|
|||||||
<string name="revanced_hide_merchandise_banners_title">商品バナーを非表示</string>
|
<string name="revanced_hide_merchandise_banners_title">商品バナーを非表示</string>
|
||||||
<string name="revanced_hide_merchandise_banners_summary_on">商品バナーは表示されません</string>
|
<string name="revanced_hide_merchandise_banners_summary_on">商品バナーは表示されません</string>
|
||||||
<string name="revanced_hide_merchandise_banners_summary_off">商品バナーは表示されます</string>
|
<string name="revanced_hide_merchandise_banners_summary_off">商品バナーは表示されます</string>
|
||||||
<string name="revanced_hide_paid_promotion_label_title">「プロモーションを含みます」ボタンを非表示</string>
|
<string name="revanced_hide_paid_promotion_label_title">「プロモーションを含みます」ラベルを非表示</string>
|
||||||
<string name="revanced_hide_paid_promotion_label_summary_on">プレーヤー画面の「プロモーションを含みます」ボタンは表示されません</string>
|
<string name="revanced_hide_paid_promotion_label_summary_on">「プロモーションを含みます」ラベルは表示されません</string>
|
||||||
<string name="revanced_hide_paid_promotion_label_summary_off">プレーヤー画面の「プロモーションを含みます」ボタンは表示されます</string>
|
<string name="revanced_hide_paid_promotion_label_summary_off">「プロモーションを含みます」ラベルは表示されます</string>
|
||||||
<string name="revanced_hide_self_sponsor_ads_title">自己スポンサー カードを非表示</string>
|
<string name="revanced_hide_self_sponsor_ads_title">自己スポンサー カードを非表示</string>
|
||||||
<string name="revanced_hide_self_sponsor_ads_summary_on">自己スポンサー カードは表示されません</string>
|
<string name="revanced_hide_self_sponsor_ads_summary_on">自己スポンサー カードは表示されません</string>
|
||||||
<string name="revanced_hide_self_sponsor_ads_summary_off">自己スポンサー カードは表示されます</string>
|
<string name="revanced_hide_self_sponsor_ads_summary_off">自己スポンサー カードは表示されます</string>
|
||||||
@@ -422,7 +422,7 @@ GmsCore の電池の最適化を無効にしても、バッテリーの使用に
|
|||||||
<string name="revanced_hide_shopping_links_summary_on">動画の概要欄の商品へのリンクは表示されません</string>
|
<string name="revanced_hide_shopping_links_summary_on">動画の概要欄の商品へのリンクは表示されません</string>
|
||||||
<string name="revanced_hide_shopping_links_summary_off">動画の概要欄の商品へのリンクは表示されます</string>
|
<string name="revanced_hide_shopping_links_summary_off">動画の概要欄の商品へのリンクは表示されます</string>
|
||||||
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
<!-- 'View products' should be translated with the same localized wording that YouTube displays. -->
|
||||||
<string name="revanced_hide_view_products_banner_title">「商品を表示」ボタンを非表示</string>
|
<string name="revanced_hide_view_products_banner_title">「商品を表示」バナーを非表示</string>
|
||||||
<string name="revanced_hide_view_products_banner_summary_on">動画オーバーレイの「商品を表示」バナーは表示されません</string>
|
<string name="revanced_hide_view_products_banner_summary_on">動画オーバーレイの「商品を表示」バナーは表示されません</string>
|
||||||
<string name="revanced_hide_view_products_banner_summary_off">動画オーバーレイの「商品を表示」バナーは表示されます</string>
|
<string name="revanced_hide_view_products_banner_summary_off">動画オーバーレイの「商品を表示」バナーは表示されます</string>
|
||||||
<string name="revanced_hide_web_search_results_title">ウェブ検索結果を非表示</string>
|
<string name="revanced_hide_web_search_results_title">ウェブ検索結果を非表示</string>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ Second \"item\" text"</string>
|
|||||||
<string name="revanced_show_menu_icons_title">Visa ikoner för ReVanced-inställningar</string>
|
<string name="revanced_show_menu_icons_title">Visa ikoner för ReVanced-inställningar</string>
|
||||||
<string name="revanced_show_menu_icons_summary_on">Inställningsikoner visas</string>
|
<string name="revanced_show_menu_icons_summary_on">Inställningsikoner visas</string>
|
||||||
<string name="revanced_show_menu_icons_summary_off">Ikoner för inställningar visas inte</string>
|
<string name="revanced_show_menu_icons_summary_off">Ikoner för inställningar visas inte</string>
|
||||||
<string name="revanced_language_title">Språket för ReVanced</string>
|
<string name="revanced_language_title">Språk för ReVanced</string>
|
||||||
<string name="revanced_language_user_dialog_message">"Översättningar till vissa språk kan vara ofullständiga eller saknas.
|
<string name="revanced_language_user_dialog_message">"Översättningar till vissa språk kan vara ofullständiga eller saknas.
|
||||||
|
|
||||||
För att översätta till nya språk besök translate.revanced.app"</string>
|
För att översätta till nya språk besök translate.revanced.app"</string>
|
||||||
@@ -95,7 +95,7 @@ Tryck på Fortsätt-knappen och tillåt optimeringsändringar."</string>
|
|||||||
<string name="revanced_settings_screen_05_player_title">Spelare</string>
|
<string name="revanced_settings_screen_05_player_title">Spelare</string>
|
||||||
<string name="revanced_settings_screen_07_seekbar_title">Sökreglage</string>
|
<string name="revanced_settings_screen_07_seekbar_title">Sökreglage</string>
|
||||||
<string name="revanced_settings_screen_08_swipe_controls_title">Svepkontroller</string>
|
<string name="revanced_settings_screen_08_swipe_controls_title">Svepkontroller</string>
|
||||||
<string name="revanced_settings_screen_11_misc_title">Diverse</string>
|
<string name="revanced_settings_screen_11_misc_title">Övrigt</string>
|
||||||
<string name="revanced_settings_screen_12_video_title">Video</string>
|
<string name="revanced_settings_screen_12_video_title">Video</string>
|
||||||
<string name="revanced_restore_old_settings_menus_title">Återställ gamla inställningsmenyer</string>
|
<string name="revanced_restore_old_settings_menus_title">Återställ gamla inställningsmenyer</string>
|
||||||
<string name="revanced_restore_old_settings_menus_summary_on">Gamla inställningsmenyer visas</string>
|
<string name="revanced_restore_old_settings_menus_summary_on">Gamla inställningsmenyer visas</string>
|
||||||
@@ -106,8 +106,8 @@ Tryck på Fortsätt-knappen och tillåt optimeringsändringar."</string>
|
|||||||
</patch>
|
</patch>
|
||||||
<patch id="misc.backgroundplayback.backgroundPlaybackPatch">
|
<patch id="misc.backgroundplayback.backgroundPlaybackPatch">
|
||||||
<string name="revanced_shorts_disable_background_playback_title">Inaktivera uppspelning av Shorts-videor i bakgrunden</string>
|
<string name="revanced_shorts_disable_background_playback_title">Inaktivera uppspelning av Shorts-videor i bakgrunden</string>
|
||||||
<string name="revanced_shorts_disable_background_playback_summary_on">Uppspelning av Shorts-videor i bakgrunden är inaktiverat</string>
|
<string name="revanced_shorts_disable_background_playback_summary_on">Uppspelning av Shorts-videor i bakgrunden är inaktiverad</string>
|
||||||
<string name="revanced_shorts_disable_background_playback_summary_off">Uppspelning av Shorts-videor i bakgrunden är aktiverat</string>
|
<string name="revanced_shorts_disable_background_playback_summary_off">Uppspelning av Shorts-videor i bakgrunden är aktiverad</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="misc.debugging.enableDebuggingPatch">
|
<patch id="misc.debugging.enableDebuggingPatch">
|
||||||
<string name="revanced_debug_screen_title">Felsökning</string>
|
<string name="revanced_debug_screen_title">Felsökning</string>
|
||||||
@@ -126,10 +126,10 @@ Men om du aktiverar detta kommer även vissa användardata, t.ex. din IP-adress,
|
|||||||
<string name="revanced_debug_stacktrace_title">Logga stackspårning</string>
|
<string name="revanced_debug_stacktrace_title">Logga stackspårning</string>
|
||||||
<string name="revanced_debug_stacktrace_summary_on">Felsökningsloggar inkluderar stackspårning</string>
|
<string name="revanced_debug_stacktrace_summary_on">Felsökningsloggar inkluderar stackspårning</string>
|
||||||
<string name="revanced_debug_stacktrace_summary_off">Felsökningsloggar inkluderar inte stackspårning</string>
|
<string name="revanced_debug_stacktrace_summary_off">Felsökningsloggar inkluderar inte stackspårning</string>
|
||||||
<string name="revanced_debug_toast_on_error_title">Visa ett meddelande om ett fel uppstår med ReVanced</string>
|
<string name="revanced_debug_toast_on_error_title">Visa ett popup-meddelande om det uppstår ett ReVanced-fel</string>
|
||||||
<string name="revanced_debug_toast_on_error_summary_on">Meddelande visas om fel uppstår</string>
|
<string name="revanced_debug_toast_on_error_summary_on">Ett popup-meddelande visas om ett fel uppstår</string>
|
||||||
<string name="revanced_debug_toast_on_error_summary_off">Meddelande visas inte om fel uppstår</string>
|
<string name="revanced_debug_toast_on_error_summary_off">Inget popup-meddelande visas om ett fel uppstår</string>
|
||||||
<string name="revanced_debug_toast_on_error_user_dialog_message">"Om du stänger av felmeddelanden döljs alla ReVanced-felmeddelanden.
|
<string name="revanced_debug_toast_on_error_user_dialog_message">"Om du stänger av pupup-felmeddelanden döljs alla ReVanced-felmeddelanden.
|
||||||
|
|
||||||
Du kommer inte att bli meddelad om oväntade händelser."</string>
|
Du kommer inte att bli meddelad om oväntade händelser."</string>
|
||||||
<string name="revanced_debug_export_logs_to_clipboard_title">Exportera felsökningsloggar</string>
|
<string name="revanced_debug_export_logs_to_clipboard_title">Exportera felsökningsloggar</string>
|
||||||
@@ -144,11 +144,11 @@ Du kommer inte att bli meddelad om oväntade händelser."</string>
|
|||||||
</patch>
|
</patch>
|
||||||
<patch id="layout.hide.general.hideLayoutComponentsPatch">
|
<patch id="layout.hide.general.hideLayoutComponentsPatch">
|
||||||
<string name="revanced_hide_album_cards_title">Dölj albumkort</string>
|
<string name="revanced_hide_album_cards_title">Dölj albumkort</string>
|
||||||
<string name="revanced_hide_album_cards_summary_on">Skivkorten är dolda</string>
|
<string name="revanced_hide_album_cards_summary_on">Albumkort är dolda</string>
|
||||||
<string name="revanced_hide_album_cards_summary_off">Albumkort är synliga</string>
|
<string name="revanced_hide_album_cards_summary_off">Albumkort visas</string>
|
||||||
<string name="revanced_hide_artist_cards_title">Dölj artistkort</string>
|
<string name="revanced_hide_artist_cards_title">Dölj artistkort</string>
|
||||||
<string name="revanced_hide_artist_cards_summary_on">Konstnärskort är dolda</string>
|
<string name="revanced_hide_artist_cards_summary_on">Artistkort är dolda</string>
|
||||||
<string name="revanced_hide_artist_cards_summary_off">Artistkort är synliga</string>
|
<string name="revanced_hide_artist_cards_summary_off">Artistkort visas</string>
|
||||||
<string name="revanced_hide_chips_shelf_title">Dölj chip-hylla</string>
|
<string name="revanced_hide_chips_shelf_title">Dölj chip-hylla</string>
|
||||||
<string name="revanced_hide_chips_shelf_summary_on">Chip-hyllan är dold</string>
|
<string name="revanced_hide_chips_shelf_summary_on">Chip-hyllan är dold</string>
|
||||||
<string name="revanced_hide_chips_shelf_summary_off">Chip-hyllan visas</string>
|
<string name="revanced_hide_chips_shelf_summary_off">Chip-hyllan visas</string>
|
||||||
@@ -161,9 +161,9 @@ Du kommer inte att bli meddelad om oväntade händelser."</string>
|
|||||||
<string name="revanced_hide_crowdfunding_box_title">Dölj insamlingsrutan</string>
|
<string name="revanced_hide_crowdfunding_box_title">Dölj insamlingsrutan</string>
|
||||||
<string name="revanced_hide_crowdfunding_box_summary_on">Insamlingsrutan är dold</string>
|
<string name="revanced_hide_crowdfunding_box_summary_on">Insamlingsrutan är dold</string>
|
||||||
<string name="revanced_hide_crowdfunding_box_summary_off">Insamlingsrutan visas</string>
|
<string name="revanced_hide_crowdfunding_box_summary_off">Insamlingsrutan visas</string>
|
||||||
<string name="revanced_hide_expandable_card_title">Dölj utökbart kort</string>
|
<string name="revanced_hide_expandable_card_title">Dölj expanderbart kort</string>
|
||||||
<string name="revanced_hide_expandable_card_summary_on">Utökbart kort under videor är dolt</string>
|
<string name="revanced_hide_expandable_card_summary_on">Expanderbart kort under videor är dolt</string>
|
||||||
<string name="revanced_hide_expandable_card_summary_off">Utökbart kort under videor visas</string>
|
<string name="revanced_hide_expandable_card_summary_off">Expanderbart kort under videor visas</string>
|
||||||
<string name="revanced_hide_feed_survey_title">Dölj enkäter i flödet</string>
|
<string name="revanced_hide_feed_survey_title">Dölj enkäter i flödet</string>
|
||||||
<string name="revanced_hide_feed_survey_summary_on">Enkäter i flödet är dolda</string>
|
<string name="revanced_hide_feed_survey_summary_on">Enkäter i flödet är dolda</string>
|
||||||
<string name="revanced_hide_feed_survey_summary_off">Enkäter i flödet visas</string>
|
<string name="revanced_hide_feed_survey_summary_off">Enkäter i flödet visas</string>
|
||||||
@@ -171,7 +171,7 @@ Du kommer inte att bli meddelad om oväntade händelser."</string>
|
|||||||
<string name="revanced_hide_floating_microphone_button_summary_on">Flytande mikrofonknapp i sökning är dold</string>
|
<string name="revanced_hide_floating_microphone_button_summary_on">Flytande mikrofonknapp i sökning är dold</string>
|
||||||
<string name="revanced_hide_floating_microphone_button_summary_off">Flytande mikrofonknapp i sökning visas</string>
|
<string name="revanced_hide_floating_microphone_button_summary_off">Flytande mikrofonknapp i sökning visas</string>
|
||||||
<string name="revanced_hide_horizontal_shelves_title">Dölj horisontella hyllor</string>
|
<string name="revanced_hide_horizontal_shelves_title">Dölj horisontella hyllor</string>
|
||||||
<string name="revanced_hide_horizontal_shelves_summary_on">"Hyllor är dolda, till exempel:
|
<string name="revanced_hide_horizontal_shelves_summary_on">"Horisontella hyllor är dolda, till exempel:
|
||||||
• Senaste nytt
|
• Senaste nytt
|
||||||
• Fortsätt titta
|
• Fortsätt titta
|
||||||
• Utforska fler kanaler
|
• Utforska fler kanaler
|
||||||
@@ -196,12 +196,12 @@ Du kommer inte att bli meddelad om oväntade händelser."</string>
|
|||||||
<string name="revanced_hide_notify_me_button_title">Dölj knappen \"Meddela mig\"</string>
|
<string name="revanced_hide_notify_me_button_title">Dölj knappen \"Meddela mig\"</string>
|
||||||
<string name="revanced_hide_notify_me_button_summary_on">Knappen Meddela mig är dold</string>
|
<string name="revanced_hide_notify_me_button_summary_on">Knappen Meddela mig är dold</string>
|
||||||
<string name="revanced_hide_notify_me_button_summary_off">Knappen Meddela mig visas</string>
|
<string name="revanced_hide_notify_me_button_summary_off">Knappen Meddela mig visas</string>
|
||||||
<string name="revanced_hide_playables_title">Dölj Playables</string>
|
<string name="revanced_hide_playables_title">Dölj Spelhörna</string>
|
||||||
<string name="revanced_hide_playables_summary_on">Playables är dolda</string>
|
<string name="revanced_hide_playables_summary_on">Spelhörna är dold</string>
|
||||||
<string name="revanced_hide_playables_summary_off">Spelbara är synliga</string>
|
<string name="revanced_hide_playables_summary_off">Spelhörna visas</string>
|
||||||
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
|
<!-- 'Show more' should be translated with the same localized wording that YouTube displays.
|
||||||
This button usually appears when searching for a YT creator. -->
|
This button usually appears when searching for a YT creator. -->
|
||||||
<string name="revanced_hide_show_more_button_title">Dölj knappen \"Visa mer\"</string>
|
<string name="revanced_hide_show_more_button_title">Dölj knappen Visa mer</string>
|
||||||
<string name="revanced_hide_show_more_button_summary_on">Knappen Visa mer i sökresultat är dold</string>
|
<string name="revanced_hide_show_more_button_summary_on">Knappen Visa mer i sökresultat är dold</string>
|
||||||
<string name="revanced_hide_show_more_button_summary_off">Knappen Visa mer i sökresultat visas</string>
|
<string name="revanced_hide_show_more_button_summary_off">Knappen Visa mer i sökresultat visas</string>
|
||||||
<string name="revanced_hide_ticket_shelf_title">Dölj biljetthylla</string>
|
<string name="revanced_hide_ticket_shelf_title">Dölj biljetthylla</string>
|
||||||
@@ -209,12 +209,12 @@ Du kommer inte att bli meddelad om oväntade händelser."</string>
|
|||||||
<string name="revanced_hide_ticket_shelf_summary_off">Biljetthyllan visas</string>
|
<string name="revanced_hide_ticket_shelf_summary_off">Biljetthyllan visas</string>
|
||||||
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
|
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
|
||||||
<string name="revanced_hide_video_recommendation_labels_title">Dölj etiketter för videorekommendationer</string>
|
<string name="revanced_hide_video_recommendation_labels_title">Dölj etiketter för videorekommendationer</string>
|
||||||
<string name="revanced_hide_video_recommendation_labels_summary_on">Etiketter för \"Andra har även tittat på\" och \"Du kanske även gillar\" i sökresultaten är dolda</string>
|
<string name="revanced_hide_video_recommendation_labels_summary_on">\"Andra har även tittat på\"- och \"Du kanske även gillar\"-etiketterna i sökresultaten är dolda</string>
|
||||||
<string name="revanced_hide_video_recommendation_labels_summary_off">Etiketter för \"Andra har även tittat på\" och \"Du kanske även gillar\" i sökresultaten visas</string>
|
<string name="revanced_hide_video_recommendation_labels_summary_off">\"Andra har även tittat på\"- och \"Du kanske även gillar\"-etiketterna i sökresultaten visas</string>
|
||||||
<!-- https://logos.fandom.com/wiki/YouTube/Yoodles -->
|
<!-- https://logos.fandom.com/wiki/YouTube/Yoodles -->
|
||||||
<string name="revanced_hide_doodles_title">Dölj YouTube-doodles</string>
|
<string name="revanced_hide_doodles_title">Dölj YouTube-doodles</string>
|
||||||
<string name="revanced_hide_doodles_summary_on">YouTube Doodles-animation på logotypen är dold</string>
|
<string name="revanced_hide_doodles_summary_on">YouTube-doodles-animation på logotypen är dold</string>
|
||||||
<string name="revanced_hide_doodles_summary_off">YouTube Doodles-animering på logotypen visas</string>
|
<string name="revanced_hide_doodles_summary_off">YouTube-doodles-animering på logotypen visas</string>
|
||||||
<string name="revanced_hide_doodles_user_dialog_message">"YouTube-doodles visas några dagar varje år.
|
<string name="revanced_hide_doodles_user_dialog_message">"YouTube-doodles visas några dagar varje år.
|
||||||
|
|
||||||
Om en doodle visas för närvarande i din region och den här döljningsinställningen är på, kommer filterfältet under sökfältet också att döljas."</string>
|
Om en doodle visas för närvarande i din region och den här döljningsinställningen är på, kommer filterfältet under sökfältet också att döljas."</string>
|
||||||
@@ -278,30 +278,30 @@ Om en doodle visas för närvarande i din region och den här döljningsinställ
|
|||||||
<string name="revanced_hide_transcript_section_summary_on">Avsnittet Manuskript är dolt</string>
|
<string name="revanced_hide_transcript_section_summary_on">Avsnittet Manuskript är dolt</string>
|
||||||
<string name="revanced_hide_transcript_section_summary_off">Avsnittet Manuskript visas</string>
|
<string name="revanced_hide_transcript_section_summary_off">Avsnittet Manuskript visas</string>
|
||||||
<string name="revanced_hide_description_components_screen_title">Videobeskrivning</string>
|
<string name="revanced_hide_description_components_screen_title">Videobeskrivning</string>
|
||||||
<string name="revanced_hide_description_components_screen_summary">Dölj eller visa videobeskrivningskomponenter</string>
|
<string name="revanced_hide_description_components_screen_summary">Dölj eller visa komponenter i videobeskrivningen</string>
|
||||||
<string name="revanced_hide_filter_bar_screen_title">Filterfält</string>
|
<string name="revanced_hide_filter_bar_screen_title">Filterfält</string>
|
||||||
<string name="revanced_hide_filter_bar_screen_summary">Dölj eller visa filterfältet i flödena, historiken, sökresultaten och relaterade videor</string>
|
<string name="revanced_hide_filter_bar_screen_summary">Dölj eller visa filterfältet i flödena, historiken, sökresultaten och liknande videor</string>
|
||||||
<string name="revanced_hide_filter_bar_feed_in_feed_title">Dölj i flöden</string>
|
<string name="revanced_hide_filter_bar_feed_in_feed_title">Dölj i flöden</string>
|
||||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_on">Dold i flöden</string>
|
<string name="revanced_hide_filter_bar_feed_in_feed_summary_on">Dolt i flöden</string>
|
||||||
<string name="revanced_hide_filter_bar_feed_in_feed_summary_off">Visas i flöden</string>
|
<string name="revanced_hide_filter_bar_feed_in_feed_summary_off">Visas i flöden</string>
|
||||||
<string name="revanced_hide_filter_bar_feed_in_history_title">Dölj i historik</string>
|
<string name="revanced_hide_filter_bar_feed_in_history_title">Dölj i historik</string>
|
||||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Dold i historik</string>
|
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Dolt i historik</string>
|
||||||
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Visas i historik</string>
|
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Visas i historik</string>
|
||||||
<string name="revanced_hide_filter_bar_feed_in_search_title">Dölj i sökresultat</string>
|
<string name="revanced_hide_filter_bar_feed_in_search_title">Dölj i sökresultat</string>
|
||||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Dold i sökresultat</string>
|
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Dolt i sökresultat</string>
|
||||||
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Visas i sökresultat</string>
|
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Visas i sökresultat</string>
|
||||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_title">Dölj i relaterade videor</string>
|
<string name="revanced_hide_filter_bar_feed_in_related_videos_title">Dölj i liknande videor</string>
|
||||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_on">Dold i relaterade videor</string>
|
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_on">Dolt i liknande videor</string>
|
||||||
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_off">Visas i relaterade videor</string>
|
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_off">Visas i liknande videor</string>
|
||||||
<string name="revanced_channel_screen_title">Kanalsida</string>
|
<string name="revanced_channel_screen_title">Kanalsida</string>
|
||||||
<string name="revanced_channel_screen_summary">Dölj eller visa komponenter på kanalsidan</string>
|
<string name="revanced_channel_screen_summary">Dölj eller visa komponenter på kanalsidan</string>
|
||||||
<!-- 'For You' should be translated using the same localized wording YouTube displays. -->
|
<!-- 'For You' should be translated using the same localized wording YouTube displays. -->
|
||||||
<string name="revanced_hide_for_you_shelf_title">Dölj hyllan \"För dig\"</string>
|
<string name="revanced_hide_for_you_shelf_title">Dölj hyllan \"För dig\"</string>
|
||||||
<string name="revanced_hide_for_you_shelf_summary_on">Hyllan \"För dig\" är dold</string>
|
<string name="revanced_hide_for_you_shelf_summary_on">Hyllan För dig är dold</string>
|
||||||
<string name="revanced_hide_for_you_shelf_summary_off">Hyllan \"För dig\" visas</string>
|
<string name="revanced_hide_for_you_shelf_summary_off">Hyllan För dig visas</string>
|
||||||
<string name="revanced_hide_links_preview_title">Dölj förhandsvisning av länkar</string>
|
<string name="revanced_hide_links_preview_title">Dölj förhandsgranskning av länkar</string>
|
||||||
<string name="revanced_hide_links_preview_summary_on">Förhandsvisning av länkar är dold</string>
|
<string name="revanced_hide_links_preview_summary_on">Förhandsgranskning av länkar är dold</string>
|
||||||
<string name="revanced_hide_links_preview_summary_off">Förhandsvisning av länkar visas</string>
|
<string name="revanced_hide_links_preview_summary_off">Förhandsgranskning av länkar visas</string>
|
||||||
<string name="revanced_hide_members_shelf_title">Dölj medlemshylla</string>
|
<string name="revanced_hide_members_shelf_title">Dölj medlemshylla</string>
|
||||||
<string name="revanced_hide_members_shelf_summary_on">Medlemshyllan är dold</string>
|
<string name="revanced_hide_members_shelf_summary_on">Medlemshyllan är dold</string>
|
||||||
<string name="revanced_hide_members_shelf_summary_off">Medlemshyllan visas</string>
|
<string name="revanced_hide_members_shelf_summary_off">Medlemshyllan visas</string>
|
||||||
@@ -677,7 +677,7 @@ Om du ändrar den här inställningen och det inte får effekt kan du försöka
|
|||||||
<!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. -->
|
<!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. -->
|
||||||
<string name="revanced_hide_player_flyout_audio_track_not_available">"Ljudspårsmenyn är dold
|
<string name="revanced_hide_player_flyout_audio_track_not_available">"Ljudspårsmenyn är dold
|
||||||
|
|
||||||
För att visa ljudspårsmenyn, ändra \"Spoof video streams\" till iOS TV"</string>
|
För att visa ljudspårsmenyn, ändra \"Förfalska videoströmmar\" till iOS TV"</string>
|
||||||
<!-- 'Watch in VR' should be translated using the same localized wording YouTube displays for the menu item. -->
|
<!-- 'Watch in VR' should be translated using the same localized wording YouTube displays for the menu item. -->
|
||||||
<string name="revanced_hide_player_flyout_watch_in_vr_title">Dölj Titta i VR</string>
|
<string name="revanced_hide_player_flyout_watch_in_vr_title">Dölj Titta i VR</string>
|
||||||
<string name="revanced_hide_player_flyout_watch_in_vr_summary_on">Menyn Titta i VR är dold</string>
|
<string name="revanced_hide_player_flyout_watch_in_vr_summary_on">Menyn Titta i VR är dold</string>
|
||||||
@@ -972,11 +972,11 @@ Den här funktionen fungerar bäst med en videokvalitet på 720p eller lägre oc
|
|||||||
<string name="revanced_sb_enable_auto_hide_skip_segment_button_sum_off">Hoppa över-knappen visas för hela segmentet</string>
|
<string name="revanced_sb_enable_auto_hide_skip_segment_button_sum_off">Hoppa över-knappen visas för hela segmentet</string>
|
||||||
<string name="revanced_sb_auto_hide_skip_button_duration">Varaktighet för Hoppa över-knappen</string>
|
<string name="revanced_sb_auto_hide_skip_button_duration">Varaktighet för Hoppa över-knappen</string>
|
||||||
<string name="revanced_sb_auto_hide_skip_button_duration_sum">Hur länge knappen för att hoppa över och hoppa till höjdpunkt ska visas innan den döljs automatiskt</string>
|
<string name="revanced_sb_auto_hide_skip_button_duration_sum">Hur länge knappen för att hoppa över och hoppa till höjdpunkt ska visas innan den döljs automatiskt</string>
|
||||||
<string name="revanced_sb_general_skiptoast">Visa toastmeddelande för ångra överhoppning</string>
|
<string name="revanced_sb_general_skiptoast">Visa popup-meddelande för att ångra överhoppning</string>
|
||||||
<string name="revanced_sb_general_skiptoast_sum_on">Meddelande visas när ett segment hoppas över automatiskt. Tryck på toastmeddelandet för att ångra överhoppningen</string>
|
<string name="revanced_sb_general_skiptoast_sum_on">Popup-meddelande visas när ett segment hoppas över automatiskt. Tryck på popup-meddelandet för att ångra överhoppningen</string>
|
||||||
<string name="revanced_sb_general_skiptoast_sum_off">Toastmeddelande visas inte</string>
|
<string name="revanced_sb_general_skiptoast_sum_off">Toastmeddelande visas inte</string>
|
||||||
<string name="revanced_sb_toast_on_skip_duration">Varaktighet för överhoppningstoastmeddelande</string>
|
<string name="revanced_sb_toast_on_skip_duration">Varaktighet för överhoppningsmeddelande</string>
|
||||||
<string name="revanced_sb_toast_on_skip_duration_sum">Hur länge toast-meddelandet för ångra överhoppning ska visas</string>
|
<string name="revanced_sb_toast_on_skip_duration_sum">Hur länge popup-meddelandet för att ångra överhoppning ska visas</string>
|
||||||
<string name="revanced_sb_duration_1s">1 sekund</string>
|
<string name="revanced_sb_duration_1s">1 sekund</string>
|
||||||
<string name="revanced_sb_duration_2s">2 sekunder</string>
|
<string name="revanced_sb_duration_2s">2 sekunder</string>
|
||||||
<string name="revanced_sb_duration_3s">3 sekunder</string>
|
<string name="revanced_sb_duration_3s">3 sekunder</string>
|
||||||
@@ -1112,7 +1112,7 @@ Redan finns"</string>
|
|||||||
<string name="revanced_sb_new_segment_disabled_category">Kategorin är inaktiverad i inställningar. Aktivera kategori för att skicka.</string>
|
<string name="revanced_sb_new_segment_disabled_category">Kategorin är inaktiverad i inställningar. Aktivera kategori för att skicka.</string>
|
||||||
<string name="revanced_sb_new_segment_title">Nytt Sponsorblock-segment</string>
|
<string name="revanced_sb_new_segment_title">Nytt Sponsorblock-segment</string>
|
||||||
<string name="revanced_sb_new_segment_mark_time_as_question">Ange %s som början eller slutet av ett nytt segment?</string>
|
<string name="revanced_sb_new_segment_mark_time_as_question">Ange %s som början eller slutet av ett nytt segment?</string>
|
||||||
<string name="revanced_sb_new_segment_mark_start">Start</string>
|
<string name="revanced_sb_new_segment_mark_start">Början</string>
|
||||||
<string name="revanced_sb_new_segment_mark_end">Slut</string>
|
<string name="revanced_sb_new_segment_mark_end">Slut</string>
|
||||||
<string name="revanced_sb_new_segment_now">Nu</string>
|
<string name="revanced_sb_new_segment_now">Nu</string>
|
||||||
<string name="revanced_sb_new_segment_time_start">Tid då segmentet börjar på</string>
|
<string name="revanced_sb_new_segment_time_start">Tid då segmentet börjar på</string>
|
||||||
@@ -1176,7 +1176,7 @@ Billayout
|
|||||||
• Flödet ordnas efter ämnen och kanaler"</string>
|
• Flödet ordnas efter ämnen och kanaler"</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||||
<string name="revanced_spoof_app_version_title">Spoof app-version</string>
|
<string name="revanced_spoof_app_version_title">Förfalska appversionen</string>
|
||||||
<string name="revanced_spoof_app_version_summary_on">Version förfalskad</string>
|
<string name="revanced_spoof_app_version_summary_on">Version förfalskad</string>
|
||||||
<string name="revanced_spoof_app_version_summary_off">Version inte förfalskad</string>
|
<string name="revanced_spoof_app_version_summary_off">Version inte förfalskad</string>
|
||||||
<string name="revanced_spoof_app_version_user_dialog_message">"Appversionen kommer att förfalskas till en äldre version av YouTube.
|
<string name="revanced_spoof_app_version_user_dialog_message">"Appversionen kommer att förfalskas till en äldre version av YouTube.
|
||||||
@@ -1186,7 +1186,7 @@ Detta kommer att ändra utseendet och funktionerna i appen, men okända bieffekt
|
|||||||
Om det senare stängs av rekommenderas det att rensa appens data för att förhindra UI-buggar."</string>
|
Om det senare stängs av rekommenderas det att rensa appens data för att förhindra UI-buggar."</string>
|
||||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
|
||||||
<string name="revanced_spoof_app_version_target_title">Spoof app-versionsmål</string>
|
<string name="revanced_spoof_app_version_target_title">Mål för Förfalska appversionen</string>
|
||||||
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Återställ gamla ikoner i Shorts-spelaren</string>
|
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Återställ gamla ikoner i Shorts-spelaren</string>
|
||||||
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Återställ gamla navigeringsikoner</string>
|
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Återställ gamla navigeringsikoner</string>
|
||||||
</patch>
|
</patch>
|
||||||
@@ -1216,8 +1216,8 @@ Om det senare stängs av rekommenderas det att rensa appens data för att förhi
|
|||||||
<string name="revanced_change_start_page_entry_virtual_reality">Virtuell verklighet</string>
|
<string name="revanced_change_start_page_entry_virtual_reality">Virtuell verklighet</string>
|
||||||
<string name="revanced_change_start_page_entry_watch_later">Titta senare</string>
|
<string name="revanced_change_start_page_entry_watch_later">Titta senare</string>
|
||||||
<string name="revanced_change_start_page_entry_your_clips">Dina klipp</string>
|
<string name="revanced_change_start_page_entry_your_clips">Dina klipp</string>
|
||||||
<string name="revanced_change_start_page_always_title">Ändra alltid startsida</string>
|
<string name="revanced_change_start_page_always_title">Byt alltid startsida</string>
|
||||||
<string name="revanced_change_start_page_always_summary_on">"Startsidan ändras alltid
|
<string name="revanced_change_start_page_always_summary_on">"Startsidan byts alltid
|
||||||
|
|
||||||
Begränsning: Bakåtknappen i verktygsfältet kanske inte fungerar"</string>
|
Begränsning: Bakåtknappen i verktygsfältet kanske inte fungerar"</string>
|
||||||
<string name="revanced_change_start_page_always_summary_off">Startsidan ändras endast vid appstart</string>
|
<string name="revanced_change_start_page_always_summary_off">Startsidan ändras endast vid appstart</string>
|
||||||
@@ -1313,20 +1313,20 @@ Minispelaren kan dras utanför skärmen till vänster eller höger"</string>
|
|||||||
<string name="revanced_header_logo_entry_6">Anpassad</string>
|
<string name="revanced_header_logo_entry_6">Anpassad</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="layout.thumbnails.bypassImageRegionRestrictionsPatch">
|
<patch id="layout.thumbnails.bypassImageRegionRestrictionsPatch">
|
||||||
<string name="revanced_bypass_image_region_restrictions_title">Begränsningar för förbipassering av bildregionen</string>
|
<string name="revanced_bypass_image_region_restrictions_title">Kringgå regionsbegränsningar för bilder</string>
|
||||||
<string name="revanced_bypass_image_region_restrictions_summary_on">Använder bildvärd yt4.ggpht.com</string>
|
<string name="revanced_bypass_image_region_restrictions_summary_on">Använder bildvärden yt4.ggpht.com</string>
|
||||||
<string name="revanced_bypass_image_region_restrictions_summary_off">"Använder ursprunglig bildvärd
|
<string name="revanced_bypass_image_region_restrictions_summary_off">"Använder ursprunglig bildvärd
|
||||||
|
|
||||||
Om du aktiverar detta kan det åtgärda saknade bilder som blockeras i vissa regioner"</string>
|
Om du aktiverar detta kan det åtgärda saknade bilder som blockeras i vissa regioner"</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="layout.thumbnails.alternativeThumbnailsPatch">
|
<patch id="layout.thumbnails.alternativeThumbnailsPatch">
|
||||||
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
|
||||||
<string name="revanced_alt_thumbnail_home_title">Hemflik</string>
|
<string name="revanced_alt_thumbnail_home_title">Fliken Hem</string>
|
||||||
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
|
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
|
||||||
<string name="revanced_alt_thumbnail_subscription_title">Fliken Prenumerationer</string>
|
<string name="revanced_alt_thumbnail_subscription_title">Fliken Prenumerationer</string>
|
||||||
<!-- 'You' should be translated using the same localized wording YouTube displays for the You (Library) tab. -->
|
<!-- 'You' should be translated using the same localized wording YouTube displays for the You (Library) tab. -->
|
||||||
<string name="revanced_alt_thumbnail_library_title">Du flik</string>
|
<string name="revanced_alt_thumbnail_library_title">Fliken Ditt YouTube</string>
|
||||||
<string name="revanced_alt_thumbnail_player_title">Spelarlistor & rekommendationer för spelare</string>
|
<string name="revanced_alt_thumbnail_player_title">Spellistor och rekommendationer i spelaren</string>
|
||||||
<string name="revanced_alt_thumbnail_search_title">Sökresultat</string>
|
<string name="revanced_alt_thumbnail_search_title">Sökresultat</string>
|
||||||
<string name="revanced_alt_thumbnail_options_entry_1">Ursprungliga miniatyrer</string>
|
<string name="revanced_alt_thumbnail_options_entry_1">Ursprungliga miniatyrer</string>
|
||||||
<string name="revanced_alt_thumbnail_options_entry_2">DeArrow och ursprungliga miniatyrer</string>
|
<string name="revanced_alt_thumbnail_options_entry_2">DeArrow och ursprungliga miniatyrer</string>
|
||||||
@@ -1341,7 +1341,7 @@ Tryck här för att läsa mer om DeArrow"</string>
|
|||||||
<string name="revanced_alt_thumbnail_dearrow_connection_toast_summary_on">Meddelande visas om DeArrow inte är tillgängligt</string>
|
<string name="revanced_alt_thumbnail_dearrow_connection_toast_summary_on">Meddelande visas om DeArrow inte är tillgängligt</string>
|
||||||
<string name="revanced_alt_thumbnail_dearrow_connection_toast_summary_off">Meddelande visas inte om DeArrow inte är tillgängligt</string>
|
<string name="revanced_alt_thumbnail_dearrow_connection_toast_summary_off">Meddelande visas inte om DeArrow inte är tillgängligt</string>
|
||||||
<string name="revanced_alt_thumbnail_dearrow_api_url_title">DeArrow API-slutpunkt</string>
|
<string name="revanced_alt_thumbnail_dearrow_api_url_title">DeArrow API-slutpunkt</string>
|
||||||
<string name="revanced_alt_thumbnail_dearrow_api_url_summary">Slutpunktsadressen för DeArrow-miniatyrbildscachen</string>
|
<string name="revanced_alt_thumbnail_dearrow_api_url_summary">DeArrow-miniatyrcacheminnets slutpunktsadress</string>
|
||||||
<string name="revanced_alt_thumbnail_stills_about_title">Stillbilder från videor</string>
|
<string name="revanced_alt_thumbnail_stills_about_title">Stillbilder från videor</string>
|
||||||
<string name="revanced_alt_thumbnail_stills_about_summary">Stillbilder tas från början/mitten/slutet av varje video. Dessa bilder är inbyggda i YouTube och inget externt API används</string>
|
<string name="revanced_alt_thumbnail_stills_about_summary">Stillbilder tas från början/mitten/slutet av varje video. Dessa bilder är inbyggda i YouTube och inget externt API används</string>
|
||||||
<string name="revanced_alt_thumbnail_stills_fast_title">Använd snabbt stillbilder</string>
|
<string name="revanced_alt_thumbnail_stills_fast_title">Använd snabbt stillbilder</string>
|
||||||
@@ -1374,13 +1374,13 @@ Tryck här för att läsa mer om DeArrow"</string>
|
|||||||
<string name="revanced_auto_repeat_summary_off">Automatisk upprepning är inaktiverad</string>
|
<string name="revanced_auto_repeat_summary_off">Automatisk upprepning är inaktiverad</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
|
||||||
<string name="revanced_spoof_device_dimensions_title">Enhetsdimensioner för Spoof</string>
|
<string name="revanced_spoof_device_dimensions_title">Förfalska enhetens mått</string>
|
||||||
<string name="revanced_spoof_device_dimensions_summary_on">"Enhetens dimensioner förfalskas
|
<string name="revanced_spoof_device_dimensions_summary_on">"Enhetens mått förfalskas
|
||||||
|
|
||||||
Högre videokvalitet kan låsas upp men du kan uppleva hackig videouppspelning, sämre batteritid och okända bieffekter"</string>
|
Högre videokvaliteter kan låsas upp men du kan uppleva hackig videouppspelning, sämre batteritid och okända bieffekter"</string>
|
||||||
<string name="revanced_spoof_device_dimensions_summary_off">"Enhetens dimensioner förfalskas inte
|
<string name="revanced_spoof_device_dimensions_summary_off">"Enhetens mått förfalskas inte
|
||||||
|
|
||||||
Att aktivera detta kan låsa upp högre videokvalitet"</string>
|
Om du aktiverar detta kan högre videokvaliteter låsas upp"</string>
|
||||||
<string name="revanced_spoof_device_dimensions_user_dialog_message">Om du aktiverar detta kan det leda till hackig videouppspelning, sämre batteritid och okända biverkningar.</string>
|
<string name="revanced_spoof_device_dimensions_user_dialog_message">Om du aktiverar detta kan det leda till hackig videouppspelning, sämre batteritid och okända biverkningar.</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="misc.gms.gmsCoreSupportResourcePatch">
|
<patch id="misc.gms.gmsCoreSupportResourcePatch">
|
||||||
@@ -1396,9 +1396,9 @@ Att aktivera detta kan låsa upp högre videokvalitet"</string>
|
|||||||
<string name="revanced_disable_haptic_feedback_precise_seeking_title">Inaktivera haptik för exakt sökning</string>
|
<string name="revanced_disable_haptic_feedback_precise_seeking_title">Inaktivera haptik för exakt sökning</string>
|
||||||
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_on">Haptik för exakt sökning är inaktiverad</string>
|
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_on">Haptik för exakt sökning är inaktiverad</string>
|
||||||
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_off">Haptik för exakt sökning är aktiverad</string>
|
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_off">Haptik för exakt sökning är aktiverad</string>
|
||||||
<string name="revanced_disable_haptic_feedback_seek_undo_title">Inaktivera ångra-sökning haptik</string>
|
<string name="revanced_disable_haptic_feedback_seek_undo_title">Inaktivera haptik för att ångra sökning</string>
|
||||||
<string name="revanced_disable_haptic_feedback_seek_undo_summary_on">Ångra-sökning haptik är inaktiverad</string>
|
<string name="revanced_disable_haptic_feedback_seek_undo_summary_on">Haptik för att ångra sökning är inaktiverad</string>
|
||||||
<string name="revanced_disable_haptic_feedback_seek_undo_summary_off">Ångra-sökning haptik är aktiverad</string>
|
<string name="revanced_disable_haptic_feedback_seek_undo_summary_off">Haptik för att ångra sökning är aktiverad</string>
|
||||||
<string name="revanced_disable_haptic_feedback_zoom_title">Inaktivera zoomhaptik</string>
|
<string name="revanced_disable_haptic_feedback_zoom_title">Inaktivera zoomhaptik</string>
|
||||||
<string name="revanced_disable_haptic_feedback_zoom_summary_on">Zoomhaptik är inaktiverad</string>
|
<string name="revanced_disable_haptic_feedback_zoom_summary_on">Zoomhaptik är inaktiverad</string>
|
||||||
<string name="revanced_disable_haptic_feedback_zoom_summary_off">Zoomhaptik är aktiverad</string>
|
<string name="revanced_disable_haptic_feedback_zoom_summary_off">Zoomhaptik är aktiverad</string>
|
||||||
@@ -1407,9 +1407,9 @@ Att aktivera detta kan låsa upp högre videokvalitet"</string>
|
|||||||
<string name="microg_offline_account_login_error">Om du nyligen har ändrat dina inloggningsuppgifter, avinstallera och installera om MicroG.</string>
|
<string name="microg_offline_account_login_error">Om du nyligen har ändrat dina inloggningsuppgifter, avinstallera och installera om MicroG.</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="misc.links.bypassURLRedirectsPatch">
|
<patch id="misc.links.bypassURLRedirectsPatch">
|
||||||
<string name="revanced_bypass_url_redirects_title">Bypass URL omdirigerar</string>
|
<string name="revanced_bypass_url_redirects_title">Hoppa över webbadressomdirigeringar</string>
|
||||||
<string name="revanced_bypass_url_redirects_summary_on">URL-omdirigeringar förbigås</string>
|
<string name="revanced_bypass_url_redirects_summary_on">Webbadressomdirigeringar hoppas över</string>
|
||||||
<string name="revanced_bypass_url_redirects_summary_off">URL-omdirigeringar förbigås inte</string>
|
<string name="revanced_bypass_url_redirects_summary_off">Webbadressomdirigeringar hoppas inte över</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="misc.links.openLinksExternallyPatch">
|
<patch id="misc.links.openLinksExternallyPatch">
|
||||||
<string name="revanced_external_browser_title">Öppna länkar i webbläsaren</string>
|
<string name="revanced_external_browser_title">Öppna länkar i webbläsaren</string>
|
||||||
@@ -1422,7 +1422,7 @@ Att aktivera detta kan låsa upp högre videokvalitet"</string>
|
|||||||
<string name="revanced_remove_tracking_query_parameter_summary_off">Spårnings frågeparameter har inte tagits bort från länkar</string>
|
<string name="revanced_remove_tracking_query_parameter_summary_off">Spårnings frågeparameter har inte tagits bort från länkar</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="video.audio.forceOriginalAudioPatch">
|
<patch id="video.audio.forceOriginalAudioPatch">
|
||||||
<string name="revanced_force_original_audio_title">Forcera originalljudspråk</string>
|
<string name="revanced_force_original_audio_title">Tvinga fram ursprungligt ljudspråk</string>
|
||||||
<string name="revanced_force_original_audio_summary_on">Använder originalljudspråk</string>
|
<string name="revanced_force_original_audio_summary_on">Använder originalljudspråk</string>
|
||||||
<string name="revanced_force_original_audio_summary_off">Använder standardljud</string>
|
<string name="revanced_force_original_audio_summary_off">Använder standardljud</string>
|
||||||
<!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. -->
|
<!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. -->
|
||||||
@@ -1492,16 +1492,16 @@ Att aktivera detta kan låsa upp högre videokvalitet"</string>
|
|||||||
<string name="revanced_slide_to_seek_summary_off">Dra för att söka är inaktiverat</string>
|
<string name="revanced_slide_to_seek_summary_off">Dra för att söka är inaktiverat</string>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="misc.fix.playback.spoofVideoStreamsPatch">
|
<patch id="misc.fix.playback.spoofVideoStreamsPatch">
|
||||||
<string name="revanced_spoof_video_streams_screen_title">Spoof videoströmmar</string>
|
<string name="revanced_spoof_video_streams_screen_title">Förfalska videoströmmar</string>
|
||||||
<string name="revanced_spoof_video_streams_screen_summary">Spoof klientens videoströmmar för att förhindra uppspelningsproblem</string>
|
<string name="revanced_spoof_video_streams_screen_summary">Förfalska klientens videoströmmar för att förhindra uppspelningsproblem</string>
|
||||||
<string name="revanced_spoof_video_streams_title">Spoof videoströmmar</string>
|
<string name="revanced_spoof_video_streams_title">Förfalska videoströmmar</string>
|
||||||
<string name="revanced_spoof_video_streams_summary_on">Videoströmmar är förfalskade</string>
|
<string name="revanced_spoof_video_streams_summary_on">Videoströmmar är förfalskade</string>
|
||||||
<string name="revanced_spoof_video_streams_summary_off">"Videoströmmar förfalskas inte
|
<string name="revanced_spoof_video_streams_summary_off">"Videoströmmar förfalskas inte
|
||||||
|
|
||||||
Videouppspelning kanske inte fungerar"</string>
|
Videouppspelning kanske inte fungerar"</string>
|
||||||
<string name="revanced_spoof_video_streams_user_dialog_message">Att stänga av den här inställningen kan orsaka videouppspelningsproblem.</string>
|
<string name="revanced_spoof_video_streams_user_dialog_message">Om du stänger av den här inställningen kan det leda till problem med videouppspelning.</string>
|
||||||
<string name="revanced_spoof_video_streams_client_type_title">Standardklient</string>
|
<string name="revanced_spoof_video_streams_client_type_title">Standardklient</string>
|
||||||
<string name="revanced_spoof_video_streams_ios_force_avc_title">Tvinga iOS AVC (H.264)</string>
|
<string name="revanced_spoof_video_streams_ios_force_avc_title">Tvinga fram iOS AVC (H.264)</string>
|
||||||
<string name="revanced_spoof_video_streams_ios_force_avc_summary_on">Videokodek tvingas till AVC (H.264)</string>
|
<string name="revanced_spoof_video_streams_ios_force_avc_summary_on">Videokodek tvingas till AVC (H.264)</string>
|
||||||
<string name="revanced_spoof_video_streams_ios_force_avc_summary_off">Videokodeken bestäms automatiskt</string>
|
<string name="revanced_spoof_video_streams_ios_force_avc_summary_off">Videokodeken bestäms automatiskt</string>
|
||||||
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">"Om du aktiverar detta kan det förbättra batteritiden och åtgärda hackig uppspelning.
|
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">"Om du aktiverar detta kan det förbättra batteritiden och åtgärda hackig uppspelning.
|
||||||
@@ -1509,14 +1509,14 @@ Videouppspelning kanske inte fungerar"</string>
|
|||||||
AVC har en maximal upplösning på 1080p, Opus-ljudkodek är inte tillgängligt och videouppspelning använder mer internetdata än VP9 eller AV1."</string>
|
AVC har en maximal upplösning på 1080p, Opus-ljudkodek är inte tillgängligt och videouppspelning använder mer internetdata än VP9 eller AV1."</string>
|
||||||
<string name="revanced_spoof_video_streams_about_ios_tv_title">Biverkningar av iOS-förfalskning</string>
|
<string name="revanced_spoof_video_streams_about_ios_tv_title">Biverkningar av iOS-förfalskning</string>
|
||||||
<string name="revanced_spoof_video_streams_about_ios_tv_summary">"• Filmer eller betalvideor kanske inte spelas upp
|
<string name="revanced_spoof_video_streams_about_ios_tv_summary">"• Filmer eller betalvideor kanske inte spelas upp
|
||||||
• Jämn volym är inte tillgängligt
|
• Jämn volym är inte tillgänglig
|
||||||
• Videor slutar 1 sekund för tidigt"</string>
|
• Videor slutar 1 sekund för tidigt"</string>
|
||||||
<string name="revanced_spoof_video_streams_about_android_title">Biverkningar av Android-förfalskning</string>
|
<string name="revanced_spoof_video_streams_about_android_title">Biverkningar av Android-förfalskning</string>
|
||||||
<string name="revanced_spoof_video_streams_about_android_summary">"• Ljudspårsmenyn saknas
|
<string name="revanced_spoof_video_streams_about_android_summary">"• Ljudspårsmenyn saknas
|
||||||
• Stabilt ljud är inte tillgängligt
|
• Jämn volym är inte tillgänglig
|
||||||
• Tvinga originalspråk är inte tillgängligt"</string>
|
• Tvinga fram ursprungligt ljud är inte tillgängligt"</string>
|
||||||
<string name="revanced_spoof_video_streams_about_no_av1">• Ingen AV1-videocodec</string>
|
<string name="revanced_spoof_video_streams_about_no_av1">• Ingen AV1-videokodek</string>
|
||||||
<string name="revanced_spoof_video_streams_about_kids_videos">• Barnvideor kanske inte spelas upp när du är utloggad eller i inkognitoläge</string>
|
<string name="revanced_spoof_video_streams_about_kids_videos">• Videor för barn kanske inte spelas upp när du är utloggad eller i inkognitoläge</string>
|
||||||
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Visa i Statistik för nördar</string>
|
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Visa i Statistik för nördar</string>
|
||||||
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_on">Klienttypen visas i Statistik för nördar</string>
|
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_on">Klienttypen visas i Statistik för nördar</string>
|
||||||
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_off">Klienten är dold i Statistik för nördar</string>
|
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_off">Klienten är dold i Statistik för nördar</string>
|
||||||
|
|||||||
@@ -516,6 +516,11 @@ This feature is only available for older devices"</string>
|
|||||||
<string name="revanced_remove_viewer_discretion_dialog_summary_off">Dialog will be shown</string>
|
<string name="revanced_remove_viewer_discretion_dialog_summary_off">Dialog will be shown</string>
|
||||||
<string name="revanced_remove_viewer_discretion_dialog_user_dialog_message">This does not bypass the age restriction. It just accepts it automatically.</string>
|
<string name="revanced_remove_viewer_discretion_dialog_user_dialog_message">This does not bypass the age restriction. It just accepts it automatically.</string>
|
||||||
</patch>
|
</patch>
|
||||||
|
<patch id="interaction.doubletap.disableChapterSkipDoubleTapPatch">
|
||||||
|
<string name="revanced_disable_chapter_skip_double_tap_title">Disable double tap chapter skip</string>
|
||||||
|
<string name="revanced_disable_chapter_skip_double_tap_summary_on">Double tap can never trigger a skip to the next/previous chapter</string>
|
||||||
|
<string name="revanced_disable_chapter_skip_double_tap_summary_off">Double tap can occasionally trigger a skip to the next/previous chapter</string>
|
||||||
|
</patch>
|
||||||
<patch id="interaction.downloads.downloadsResourcePatch">
|
<patch id="interaction.downloads.downloadsResourcePatch">
|
||||||
<string name="revanced_external_downloader_screen_title">External downloads</string>
|
<string name="revanced_external_downloader_screen_title">External downloads</string>
|
||||||
<string name="revanced_external_downloader_screen_summary">Settings for using an external downloader</string>
|
<string name="revanced_external_downloader_screen_summary">Settings for using an external downloader</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user