Compare commits

...

16 Commits

Author SHA1 Message Date
semantic-release-bot
a38f635514 chore: Release v5.31.0-dev.17 [skip ci]
# [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)) ([b3e6c21](b3e6c215cc))

### Features

* **Spotify:** Remove support for old versions ([#5404](https://github.com/ReVanced/revanced-patches/issues/5404)) ([c9cc3d5](c9cc3d5c41))
2025-07-11 15:41:53 +00:00
Nuckyz
b3e6c215cc fix(Spotify - Unlock Premium): Remove wrongfully hidden non ad browse sections (#5403) 2025-07-11 17:38:33 +02:00
Nuckyz
c9cc3d5c41 feat(Spotify): Remove support for old versions (#5404) 2025-07-11 17:37:59 +02:00
semantic-release-bot
536e64565c chore: Release v5.31.0-dev.16 [skip ci]
# [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)) ([65cbf3c](65cbf3c1eb))
* **YouTube:** Disable two-finger tap gesture for skipping chapters ([#5374](https://github.com/ReVanced/revanced-patches/issues/5374)) ([61c1a7a](61c1a7a75a))
2025-07-11 15:37:29 +00:00
Dawid Krajcarz
65cbf3c1eb feat(Spotify - Spoof client): Fix issues like songs skipping by spoofing to iOS (#5388)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-07-11 17:34:02 +02:00
abel1502
61c1a7a75a feat(YouTube): Disable two-finger tap gesture for skipping chapters (#5374)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-07-11 17:32:59 +02:00
Pun Butrach
1e39db06b8 ci: Remove fetch-depth from checkout (#5311) 2025-07-11 17:31:12 +02:00
Pun Butrach
e019f83232 ci: Group all Dependabot update into one PR (#5336) 2025-07-11 17:31:03 +02:00
semantic-release-bot
3b57a5f8c0 chore: Release v5.31.0-dev.15 [skip ci]
# [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 ([eafe3df](eafe3dfc45))
2025-07-11 09:31:21 +00:00
oSumAtrIX
eafe3dfc45 fix: Handle empty list of announcements 2025-07-11 11:28:13 +02:00
semantic-release-bot
d56d8d990c chore: Release v5.31.0-dev.14 [skip ci]
# [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)) ([37a8682](37a8682901))
2025-07-10 18:51:55 +00:00
Chirag Gada
37a8682901 fix(Bacon Reader - Spoof client): Use www instead of ssl API to fix auth related issues (#5402) 2025-07-10 20:49:04 +02:00
semantic-release-bot
11ba7d4e3e chore: Release v5.31.0-dev.13 [skip ci]
# [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)) ([6833d37](6833d37c26))
2025-07-10 13:38:38 +00:00
LisoUseInAIKyrios
6833d37c26 fix(YouTube - Slide to seek): Show tap and hold 2x speed overlay when active (#5398) 2025-07-10 17:35:08 +04:00
github-actions[bot]
e6f72bcb7d chore: Sync translations (#5399) 2025-07-10 17:34:47 +04:00
LisoUseInAIKyrios
e8a227c082 chore: Fix api dump 2025-07-10 15:15:34 +04:00
52 changed files with 752 additions and 1216 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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)
} }

View File

@@ -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);
} }

View File

@@ -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;
}
}

View File

@@ -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 "";
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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 +
')';
}
}

View File

@@ -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();
});
}
} }

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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_;
} }

View File

@@ -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_;
}

View File

@@ -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")
}
}

View File

@@ -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]+
;

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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.

View File

@@ -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() {
} }

View File

@@ -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);

View File

@@ -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

View File

@@ -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;
} }

View File

@@ -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",

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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,
)
}

View File

@@ -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",
)
}
}

View File

@@ -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 =

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
} }
} }

View File

@@ -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;")

View File

@@ -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(

View File

@@ -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.

View File

@@ -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!!
}

View File

@@ -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
"""
)
}
}

View File

@@ -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;"
)
}

View File

@@ -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(

View File

@@ -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")

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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 &amp; 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 &amp; Orijinal miniatürlər</string> <string name="revanced_alt_thumbnail_options_entry_2">DeArrow &amp; 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>

View File

@@ -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. -->

View File

@@ -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>

View File

@@ -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 &amp; 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>

View File

@@ -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>