Skip to content

Commit ebd4dcc

Browse files
authored
feat(Spotify): Remove ads section from browse (#5193)
1 parent ba242a3 commit ebd4dcc

File tree

4 files changed

+107
-32
lines changed

4 files changed

+107
-32
lines changed

extensions/spotify/src/main/java/app/revanced/extension/spotify/misc/UnlockPremiumPatch.java

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import static java.lang.Boolean.TRUE;
55

66
import app.revanced.extension.spotify.shared.ComponentFilters.*;
7-
import com.spotify.home.evopage.homeapi.proto.Section;
87

98
import java.util.Iterator;
109
import java.util.List;
@@ -99,8 +98,16 @@ private static class OverrideAttribute {
9998
* response which delivers home sections.
10099
*/
101100
private static final List<Integer> REMOVED_HOME_SECTIONS = List.of(
102-
Section.VIDEO_BRAND_AD_FIELD_NUMBER,
103-
Section.IMAGE_BRAND_AD_FIELD_NUMBER
101+
com.spotify.home.evopage.homeapi.proto.Section.VIDEO_BRAND_AD_FIELD_NUMBER,
102+
com.spotify.home.evopage.homeapi.proto.Section.IMAGE_BRAND_AD_FIELD_NUMBER
103+
);
104+
105+
/**
106+
* A list of browse sections feature types ids which should be removed. These ids match the ones from the protobuf
107+
* response which delivers browse sections.
108+
*/
109+
private static final List<Integer> REMOVED_BROWSE_SECTIONS = List.of(
110+
com.spotify.browsita.v1.resolved.Section.BRAND_ADS_FIELD_NUMBER
104111
);
105112

106113
/**
@@ -174,26 +181,58 @@ public static String removeStationString(String spotifyUriOrUrl) {
174181
}
175182
}
176183

177-
/**
178-
* Injection point. Remove ads sections from home.
179-
* Depends on patching abstract protobuf list ensureIsMutable method.
180-
*/
181-
public static void removeHomeSections(List<Section> sections) {
184+
185+
private interface FeatureTypeIdProvider<T> {
186+
int getFeatureTypeId(T section);
187+
}
188+
189+
private static <T> void removeSections(
190+
List<T> sections,
191+
FeatureTypeIdProvider<T> featureTypeExtractor,
192+
List<Integer> idsToRemove
193+
) {
182194
try {
183-
Iterator<Section> iterator = sections.iterator();
195+
Iterator<T> iterator = sections.iterator();
184196

185197
while (iterator.hasNext()) {
186-
Section section = iterator.next();
187-
if (REMOVED_HOME_SECTIONS.contains(section.featureTypeCase_)) {
188-
Logger.printInfo(() -> "Removing home section with feature type id " + section.featureTypeCase_);
198+
T section = iterator.next();
199+
int featureTypeId = featureTypeExtractor.getFeatureTypeId(section);
200+
if (idsToRemove.contains(featureTypeId)) {
201+
Logger.printInfo(() -> "Removing section with feature type id " + featureTypeId);
189202
iterator.remove();
190203
}
191204
}
192205
} catch (Exception ex) {
193-
Logger.printException(() -> "removeHomeSections failure", ex);
206+
Logger.printException(() -> "removeSections failure", ex);
194207
}
195208
}
196209

210+
/**
211+
* Injection point. Remove ads sections from home.
212+
* Depends on patching abstract protobuf list ensureIsMutable method.
213+
*/
214+
public static void removeHomeSections(List<com.spotify.home.evopage.homeapi.proto.Section> sections) {
215+
Logger.printInfo(() -> "Removing ads section from home");
216+
removeSections(
217+
sections,
218+
section -> section.featureTypeCase_,
219+
REMOVED_HOME_SECTIONS
220+
);
221+
}
222+
223+
/**
224+
* Injection point. Remove ads sections from browse.
225+
* Depends on patching abstract protobuf list ensureIsMutable method.
226+
*/
227+
public static void removeBrowseSections(List<com.spotify.browsita.v1.resolved.Section> sections) {
228+
Logger.printInfo(() -> "Removing ads section from browse");
229+
removeSections(
230+
sections,
231+
section -> section.sectionTypeCase_,
232+
REMOVED_BROWSE_SECTIONS
233+
);
234+
}
235+
197236
/**
198237
* Injection point. Returns whether the context menu item is a Premium ad.
199238
*/
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.spotify.browsita.v1.resolved;
2+
3+
public final class Section {
4+
public static final int BRAND_ADS_FIELD_NUMBER = 6;
5+
public int sectionTypeCase_;
6+
}

patches/src/main/kotlin/app/revanced/patches/spotify/misc/Fingerprints.kt

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,18 +93,28 @@ internal val abstractProtobufListEnsureIsMutableFingerprint = fingerprint {
9393
}
9494
}
9595

96-
internal val homeSectionFingerprint = fingerprint {
97-
custom { _, classDef -> classDef.endsWith("homeapi/proto/Section;") }
98-
}
99-
100-
internal val homeStructureGetSectionsFingerprint = fingerprint {
96+
private fun structureGetSectionsFingerprint(className: String) = fingerprint {
10197
custom { method, classDef ->
102-
classDef.endsWith("homeapi/proto/HomeStructure;") && method.indexOfFirstInstruction {
98+
classDef.endsWith(className) && method.indexOfFirstInstruction {
10399
opcode == Opcode.IGET_OBJECT && getReference<FieldReference>()?.name == "sections_"
104100
} >= 0
105101
}
106102
}
107103

104+
internal val homeSectionFingerprint = fingerprint {
105+
custom { _, classDef -> classDef.endsWith("homeapi/proto/Section;") }
106+
}
107+
108+
internal val homeStructureGetSectionsFingerprint =
109+
structureGetSectionsFingerprint("homeapi/proto/HomeStructure;")
110+
111+
internal val browseSectionFingerprint = fingerprint {
112+
custom { _, classDef -> classDef.endsWith("browsita/v1/resolved/Section;") }
113+
}
114+
115+
internal val browseStructureGetSectionsFingerprint =
116+
structureGetSectionsFingerprint("browsita/v1/resolved/BrowseStructure;")
117+
108118
internal fun reactivexFunctionApplyWithClassInitFingerprint(className: String) = fingerprint {
109119
returns("Ljava/lang/Object;")
110120
parameters("Ljava/lang/Object;")

patches/src/main/kotlin/app/revanced/patches/spotify/misc/UnlockPremiumPatch.kt

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package app.revanced.patches.spotify.misc
22

3+
import app.revanced.patcher.Fingerprint
34
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
45
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
56
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
@@ -178,21 +179,40 @@ val unlockPremiumPatch = bytecodePatch(
178179
abstractProtobufListEnsureIsMutableFingerprint.match(abstractProtobufListClassDef)
179180
.method.returnEarly()
180181

181-
// Make featureTypeCase_ accessible so we can check the home section type in the extension.
182-
homeSectionFingerprint.classDef.publicizeField("featureTypeCase_")
183-
184-
// Remove ads sections from home.
185-
homeStructureGetSectionsFingerprint.method.apply {
186-
val getSectionsIndex = indexOfFirstInstructionOrThrow(Opcode.IGET_OBJECT)
187-
val sectionsRegister = getInstruction<TwoRegisterInstruction>(getSectionsIndex).registerA
188-
189-
addInstruction(
190-
getSectionsIndex + 1,
191-
"invoke-static { v$sectionsRegister }, " +
192-
"$EXTENSION_CLASS_DESCRIPTOR->removeHomeSections(Ljava/util/List;)V"
193-
)
182+
fun injectRemoveSectionCall(
183+
sectionFingerprint: Fingerprint,
184+
structureFingerprint: Fingerprint,
185+
fieldName: String,
186+
methodName: String
187+
) {
188+
// Make field accessible so we can check the home/browse section type in the extension.
189+
sectionFingerprint.classDef.publicizeField(fieldName)
190+
191+
structureFingerprint.method.apply {
192+
val getSectionsIndex = indexOfFirstInstructionOrThrow(Opcode.IGET_OBJECT)
193+
val sectionsRegister = getInstruction<TwoRegisterInstruction>(getSectionsIndex).registerA
194+
195+
addInstruction(
196+
getSectionsIndex + 1,
197+
"invoke-static { v$sectionsRegister }, " +
198+
"$EXTENSION_CLASS_DESCRIPTOR->$methodName(Ljava/util/List;)V"
199+
)
200+
}
194201
}
195202

203+
injectRemoveSectionCall(
204+
homeSectionFingerprint,
205+
homeStructureGetSectionsFingerprint,
206+
"featureTypeCase_",
207+
"removeHomeSections"
208+
)
209+
210+
injectRemoveSectionCall(
211+
browseSectionFingerprint,
212+
browseStructureGetSectionsFingerprint,
213+
"sectionTypeCase_",
214+
"removeBrowseSections"
215+
)
196216

197217
// Replace a fetch request that returns and maps Singles with their static onErrorReturn value.
198218
fun MutableMethod.replaceFetchRequestSingleWithError(requestClassName: String) {

0 commit comments

Comments
 (0)