fix: hide story sections

This commit is contained in:
rhunk
2024-01-13 17:39:08 +01:00
parent 5328dec620
commit 5d8cdc3cfc
8 changed files with 155 additions and 140 deletions

View File

@ -296,9 +296,9 @@
"name": "Hide Quick Add in Friend Feed", "name": "Hide Quick Add in Friend Feed",
"description": "Hides the Quick Add section in the friend feed" "description": "Hides the Quick Add section in the friend feed"
}, },
"hide_story_sections": { "hide_story_suggestions": {
"name": "Hide Story Section", "name": "Hide Story Suggestions",
"description": "Hide certain UI Elements shown in the story section" "description": "Removes suggestions from the Stories page"
}, },
"hide_ui_components": { "hide_ui_components": {
"name": "Hide UI Components", "name": "Hide UI Components",
@ -494,9 +494,9 @@
"name": "Disable Metrics", "name": "Disable Metrics",
"description": "Blocks sending specific analytic data to Snapchat" "description": "Blocks sending specific analytic data to Snapchat"
}, },
"disable_public_stories": { "disable_story_sections": {
"name": "Disable Public Stories", "name": "Disable Story Sections",
"description": "Removes every public story from the Discover page\nMay require a clean cache to work properly" "description": "Removes sections from the Stories page\nMay require a refresh to work properly"
}, },
"block_ads": { "block_ads": {
"name": "Block Ads", "name": "Block Ads",
@ -809,12 +809,9 @@
"hide_voice_record_button": "Remove Voice Record Button", "hide_voice_record_button": "Remove Voice Record Button",
"hide_unread_chat_hint": "Remove Unread Chat Hint" "hide_unread_chat_hint": "Remove Unread Chat Hint"
}, },
"hide_story_sections": { "hide_story_suggestions": {
"hide_friend_suggestions": "Hide friend suggestions", "hide_friend_suggestions": "Hide friend suggestions",
"hide_suggested_friend_stories": "Hide suggested friend stories", "hide_suggested_friend_stories": "Hide suggested friend stories"
"hide_friends": "Hide friends section",
"hide_suggested": "Hide suggested section",
"hide_for_you": "Hide For You section"
}, },
"home_tab": { "home_tab": {
"map": "Map", "map": "Map",
@ -868,6 +865,11 @@
"1_month": "1 Month", "1_month": "1 Month",
"3_months": "3 Months", "3_months": "3 Months",
"6_months": "6 Months" "6_months": "6 Months"
},
"disable_story_sections": {
"friends": "Friends",
"following": "Following",
"discover": "Discover"
} }
} }
}, },

View File

@ -12,7 +12,7 @@ class Global : ConfigContainer() {
val snapchatPlus = boolean("snapchat_plus") { requireRestart() } val snapchatPlus = boolean("snapchat_plus") { requireRestart() }
val disableConfirmationDialogs = multiple("disable_confirmation_dialogs", "remove_friend", "block_friend", "ignore_friend", "hide_friend", "hide_conversation", "clear_conversation") { requireRestart() } val disableConfirmationDialogs = multiple("disable_confirmation_dialogs", "remove_friend", "block_friend", "ignore_friend", "hide_friend", "hide_conversation", "clear_conversation") { requireRestart() }
val disableMetrics = boolean("disable_metrics") { requireRestart() } val disableMetrics = boolean("disable_metrics") { requireRestart() }
val disablePublicStories = boolean("disable_public_stories") { requireRestart(); requireCleanCache() } val disableStorySections = multiple("disable_story_sections", "friends", "following", "discover") { requireRestart(); requireCleanCache() }
val blockAds = boolean("block_ads") val blockAds = boolean("block_ads")
val spotlightCommentsUsername = boolean("spotlight_comments_username") { requireRestart() } val spotlightCommentsUsername = boolean("spotlight_comments_username") { requireRestart() }
val bypassVideoLengthRestriction = unique("bypass_video_length_restriction", "split", "single") { addNotices( val bypassVideoLengthRestriction = unique("bypass_video_length_restriction", "split", "single") { addNotices(

View File

@ -34,8 +34,7 @@ class UserInterfaceTweaks : ConfigContainer() {
val hideFriendFeedEntry = boolean("hide_friend_feed_entry") { requireRestart() } val hideFriendFeedEntry = boolean("hide_friend_feed_entry") { requireRestart() }
val hideStreakRestore = boolean("hide_streak_restore") { requireRestart() } val hideStreakRestore = boolean("hide_streak_restore") { requireRestart() }
val hideQuickAddFriendFeed = boolean("hide_quick_add_friend_feed") { requireRestart() } val hideQuickAddFriendFeed = boolean("hide_quick_add_friend_feed") { requireRestart() }
val hideStorySections = multiple("hide_story_sections", val hideStorySuggestions = multiple("hide_story_suggestions", "hide_friend_suggestions", "hide_suggested_friend_stories") { requireRestart() }
"hide_friend_suggestions", "hide_suggested_friend_stories", "hide_friends", "hide_suggested", "hide_for_you") { requireRestart() }
val hideUiComponents = multiple("hide_ui_components", val hideUiComponents = multiple("hide_ui_components",
"hide_voice_record_button", "hide_voice_record_button",
"hide_stickers_button", "hide_stickers_button",

View File

@ -142,4 +142,20 @@ enum class FriendLinkType(val value: Int, val shortName: String) {
return entries.firstOrNull { it.value == value } ?: MUTUAL return entries.firstOrNull { it.value == value } ?: MUTUAL
} }
} }
}
enum class MixerStoryType(
val index: Int,
) {
UNKNOWN(-1),
SUBSCRIPTIONS(2),
DISCOVER(3),
FRIENDS(5),
MY_STORIES(6);
companion object {
fun fromIndex(index: Int): MixerStoryType {
return entries.firstOrNull { it.index == index } ?: UNKNOWN
}
}
} }

View File

@ -0,0 +1,119 @@
package me.rhunk.snapenhance.core.features.impl
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import me.rhunk.snapenhance.common.data.StoryData
import me.rhunk.snapenhance.common.data.MixerStoryType
import me.rhunk.snapenhance.common.util.protobuf.ProtoEditor
import me.rhunk.snapenhance.core.event.events.impl.NetworkApiRequestEvent
import me.rhunk.snapenhance.core.features.Feature
import me.rhunk.snapenhance.core.features.FeatureLoadParams
import java.nio.ByteBuffer
import kotlin.coroutines.suspendCoroutine
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
class MixerStories : Feature("MixerStories", loadParams = FeatureLoadParams.INIT_SYNC) {
@OptIn(ExperimentalEncodingApi::class)
override fun init() {
val disableDiscoverSections by context.config.global.disableStorySections
fun canRemoveDiscoverSection(id: Int): Boolean {
val storyType = MixerStoryType.fromIndex(id)
return (storyType == MixerStoryType.SUBSCRIPTIONS && disableDiscoverSections.contains("following")) ||
(storyType == MixerStoryType.DISCOVER && disableDiscoverSections.contains("discover")) ||
(storyType == MixerStoryType.FRIENDS && disableDiscoverSections.contains("friends"))
}
context.event.subscribe(NetworkApiRequestEvent::class) { event ->
fun cancelRequest() {
runBlocking {
suspendCoroutine {
context.httpServer.ensureServerStarted()?.let { server ->
event.url = "http://127.0.0.1:${server.port}"
it.resumeWith(Result.success(Unit))
} ?: run {
event.canceled = true
it.resumeWith(Result.success(Unit))
}
}
}
}
if (event.url.endsWith("readreceipt-indexer/batchuploadreadreceipts")) {
if (context.config.messaging.anonymousStoryViewing.get()) {
cancelRequest()
return@subscribe
}
if (!context.config.messaging.preventStoryRewatchIndicator.get()) return@subscribe
event.hookRequestBuffer { buffer ->
ProtoEditor(buffer).apply {
edit {
get(2).removeIf {
it.toReader().getVarInt(7, 4) == 1L
}
}
}.toByteArray()
}
return@subscribe
}
if (event.url.endsWith("df-mixer-prod/stories") ||
event.url.endsWith("df-mixer-prod/batch_stories") ||
event.url.endsWith("df-mixer-prod/soma/stories") ||
event.url.endsWith("df-mixer-prod/soma/batch_stories")
) {
event.onSuccess { buffer ->
val editor = ProtoEditor(buffer ?: return@onSuccess)
editor.edit {
editEach(3) {
val sectionType = firstOrNull(10)?.toReader()?.getVarInt(1)?.toInt() ?: return@editEach
if (sectionType == MixerStoryType.FRIENDS.index && context.config.experimental.storyLogger.get()) {
val storyMap = mutableMapOf<String, MutableList<StoryData>>()
firstOrNull(3)?.toReader()?.eachBuffer(3) {
followPath(36) {
eachBuffer(1) data@{
val userId = getString(8, 1) ?: return@data
storyMap.getOrPut(userId) {
mutableListOf()
}.add(StoryData(
url = getString(2, 2)?.substringBefore("?") ?: return@data,
postedAt = getVarInt(3) ?: -1L,
createdAt = getVarInt(27) ?: -1L,
key = Base64.decode(getString(2, 5) ?: return@data),
iv = Base64.decode(getString(2, 4) ?: return@data)
))
}
}
}
context.coroutineScope.launch {
storyMap.forEach { (userId, stories) ->
stories.forEach { story ->
runCatching {
context.bridgeClient.getMessageLogger().addStory(userId, story.url, story.postedAt, story.createdAt, story.key, story.iv)
}.onFailure {
context.log.error("Failed to log story", it)
}
}
}
}
}
if (canRemoveDiscoverSection(sectionType)) {
remove(3)
addBuffer(3, byteArrayOf())
}
}
}
setArg(2, ByteBuffer.wrap(editor.toByteArray()))
}
return@subscribe
}
}
}
}

View File

@ -1,101 +0,0 @@
package me.rhunk.snapenhance.core.features.impl
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import me.rhunk.snapenhance.common.data.StoryData
import me.rhunk.snapenhance.common.util.protobuf.ProtoEditor
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
import me.rhunk.snapenhance.core.event.events.impl.NetworkApiRequestEvent
import me.rhunk.snapenhance.core.features.Feature
import me.rhunk.snapenhance.core.features.FeatureLoadParams
import java.nio.ByteBuffer
import kotlin.coroutines.suspendCoroutine
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
class Stories : Feature("Stories", loadParams = FeatureLoadParams.INIT_SYNC) {
@OptIn(ExperimentalEncodingApi::class)
override fun init() {
val disablePublicStories by context.config.global.disablePublicStories
val storyLogger by context.config.experimental.storyLogger
context.event.subscribe(NetworkApiRequestEvent::class) { event ->
fun cancelRequest() {
runBlocking {
suspendCoroutine {
context.httpServer.ensureServerStarted()?.let { server ->
event.url = "http://127.0.0.1:${server.port}"
it.resumeWith(Result.success(Unit))
} ?: run {
event.canceled = true
it.resumeWith(Result.success(Unit))
}
}
}
}
if (event.url.endsWith("readreceipt-indexer/batchuploadreadreceipts")) {
if (context.config.messaging.anonymousStoryViewing.get()) {
cancelRequest()
return@subscribe
}
if (!context.config.messaging.preventStoryRewatchIndicator.get()) return@subscribe
event.hookRequestBuffer { buffer ->
ProtoEditor(buffer).apply {
edit {
get(2).removeIf {
it.toReader().getVarInt(7, 4) == 1L
}
}
}.toByteArray()
}
return@subscribe
}
if (disablePublicStories && (event.url.endsWith("df-mixer-prod/stories") || event.url.endsWith("df-mixer-prod/batch_stories"))) {
event.onSuccess { buffer ->
val payload = ProtoEditor(buffer ?: return@onSuccess).apply {
edit(3) { remove(3) }
}.toByteArray()
setArg(2, ByteBuffer.wrap(payload))
}
return@subscribe
}
if (storyLogger && event.url.endsWith("df-mixer-prod/soma/batch_stories")) {
event.onSuccess { buffer ->
val stories = mutableMapOf<String, MutableList<StoryData>>()
val reader = ProtoReader(buffer ?: return@onSuccess)
reader.followPath(3, 3) {
eachBuffer(3) {
followPath(36) {
eachBuffer(1) data@{
val userId = getString(8, 1) ?: return@data
stories.getOrPut(userId) {
mutableListOf()
}.add(StoryData(
url = getString(2, 2)?.substringBefore("?") ?: return@data,
postedAt = getVarInt(3) ?: -1L,
createdAt = getVarInt(27) ?: -1L,
key = Base64.decode(getString(2, 5) ?: return@data),
iv = Base64.decode(getString(2, 4) ?: return@data)
))
}
}
}
}
context.coroutineScope.launch {
stories.forEach { (userId, stories) ->
stories.forEach { story ->
context.bridgeClient.getMessageLogger().addStory(userId, story.url, story.postedAt, story.createdAt, story.key, story.iv)
}
}
}
}
return@subscribe
}
}
}
}

View File

@ -42,7 +42,7 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE
override fun onActivityCreate() { override fun onActivityCreate() {
val blockAds by context.config.global.blockAds val blockAds by context.config.global.blockAds
val hiddenElements by context.config.userInterface.hideUiComponents val hiddenElements by context.config.userInterface.hideUiComponents
val hideStorySections by context.config.userInterface.hideStorySections val hideStorySuggestions by context.config.userInterface.hideStorySuggestions
val isImmersiveCamera by context.config.camera.immersiveCameraPreview val isImmersiveCamera by context.config.camera.immersiveCameraPreview
val displayMetrics = context.resources.displayMetrics val displayMetrics = context.resources.displayMetrics
@ -77,7 +77,7 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE
var friendCardFrameSize: Size? = null var friendCardFrameSize: Size? = null
context.event.subscribe(BindViewEvent::class, { hideStorySections.contains("hide_suggested_friend_stories") }) { event -> context.event.subscribe(BindViewEvent::class, { hideStorySuggestions.contains("hide_suggested_friend_stories") }) { event ->
if (event.view.id != friendCardFrame) return@subscribe if (event.view.id != friendCardFrame) return@subscribe
val friendStoryData = event.prevModel::class.java.findFieldsToString(event.prevModel, once = true) { _, value -> val friendStoryData = event.prevModel::class.java.findFieldsToString(event.prevModel, once = true) { _, value ->
@ -105,23 +105,8 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE
val viewId = event.view.id val viewId = event.view.id
val view = event.view val view = event.view
if (hideStorySections.contains("hide_for_you")) {
if (viewId == getId("df_large_story", "id") ||
viewId == getId("df_promoted_story", "id")) {
hideStorySection(event)
return@subscribe
}
if (viewId == getId("stories_load_progress_layout", "id")) {
event.canceled = true
}
}
if (hideStorySections.contains("hide_friends") && viewId == getId("friend_card_frame", "id")) {
hideStorySection(event)
}
//mappings? //mappings?
if (hideStorySections.contains("hide_friend_suggestions") && view.javaClass.superclass?.name?.endsWith("StackDrawLayout") == true) { if (hideStorySuggestions.contains("hide_friend_suggestions") && view.javaClass.superclass?.name?.endsWith("StackDrawLayout") == true) {
val layoutParams = view.layoutParams as? FrameLayout.LayoutParams ?: return@subscribe val layoutParams = view.layoutParams as? FrameLayout.LayoutParams ?: return@subscribe
if (layoutParams.width == -1 && if (layoutParams.width == -1 &&
layoutParams.height == -2 && layoutParams.height == -2 &&
@ -134,11 +119,6 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE
} }
} }
if (hideStorySections.contains("hide_suggested") && (viewId == getId("df_small_story", "id"))
) {
hideStorySection(event)
}
if (blockAds && viewId == getId("df_promoted_story", "id")) { if (blockAds && viewId == getId("df_promoted_story", "id")) {
hideStorySection(event) hideStorySection(event)
} }

View File

@ -10,7 +10,7 @@ import me.rhunk.snapenhance.core.features.MessagingRuleFeature
import me.rhunk.snapenhance.core.features.impl.ConfigurationOverride import me.rhunk.snapenhance.core.features.impl.ConfigurationOverride
import me.rhunk.snapenhance.core.features.impl.OperaViewerParamsOverride import me.rhunk.snapenhance.core.features.impl.OperaViewerParamsOverride
import me.rhunk.snapenhance.core.features.impl.ScopeSync import me.rhunk.snapenhance.core.features.impl.ScopeSync
import me.rhunk.snapenhance.core.features.impl.Stories import me.rhunk.snapenhance.core.features.impl.MixerStories
import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader
import me.rhunk.snapenhance.core.features.impl.downloader.ProfilePictureDownloader import me.rhunk.snapenhance.core.features.impl.downloader.ProfilePictureDownloader
import me.rhunk.snapenhance.core.features.impl.experiments.* import me.rhunk.snapenhance.core.features.impl.experiments.*
@ -113,7 +113,7 @@ class FeatureManager(
BypassScreenshotDetection::class, BypassScreenshotDetection::class,
HalfSwipeNotifier::class, HalfSwipeNotifier::class,
DisableConfirmationDialogs::class, DisableConfirmationDialogs::class,
Stories::class, MixerStories::class,
DisableComposerModules::class, DisableComposerModules::class,
FideliusIndicator::class, FideliusIndicator::class,
EditTextOverride::class, EditTextOverride::class,