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",
"description": "Hides the Quick Add section in the friend feed"
},
"hide_story_sections": {
"name": "Hide Story Section",
"description": "Hide certain UI Elements shown in the story section"
"hide_story_suggestions": {
"name": "Hide Story Suggestions",
"description": "Removes suggestions from the Stories page"
},
"hide_ui_components": {
"name": "Hide UI Components",
@ -494,9 +494,9 @@
"name": "Disable Metrics",
"description": "Blocks sending specific analytic data to Snapchat"
},
"disable_public_stories": {
"name": "Disable Public Stories",
"description": "Removes every public story from the Discover page\nMay require a clean cache to work properly"
"disable_story_sections": {
"name": "Disable Story Sections",
"description": "Removes sections from the Stories page\nMay require a refresh to work properly"
},
"block_ads": {
"name": "Block Ads",
@ -809,12 +809,9 @@
"hide_voice_record_button": "Remove Voice Record Button",
"hide_unread_chat_hint": "Remove Unread Chat Hint"
},
"hide_story_sections": {
"hide_story_suggestions": {
"hide_friend_suggestions": "Hide friend suggestions",
"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"
"hide_suggested_friend_stories": "Hide suggested friend stories"
},
"home_tab": {
"map": "Map",
@ -868,6 +865,11 @@
"1_month": "1 Month",
"3_months": "3 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 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 disablePublicStories = boolean("disable_public_stories") { requireRestart(); requireCleanCache() }
val disableStorySections = multiple("disable_story_sections", "friends", "following", "discover") { requireRestart(); requireCleanCache() }
val blockAds = boolean("block_ads")
val spotlightCommentsUsername = boolean("spotlight_comments_username") { requireRestart() }
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 hideStreakRestore = boolean("hide_streak_restore") { requireRestart() }
val hideQuickAddFriendFeed = boolean("hide_quick_add_friend_feed") { requireRestart() }
val hideStorySections = multiple("hide_story_sections",
"hide_friend_suggestions", "hide_suggested_friend_stories", "hide_friends", "hide_suggested", "hide_for_you") { requireRestart() }
val hideStorySuggestions = multiple("hide_story_suggestions", "hide_friend_suggestions", "hide_suggested_friend_stories") { requireRestart() }
val hideUiComponents = multiple("hide_ui_components",
"hide_voice_record_button",
"hide_stickers_button",

View File

@ -143,3 +143,19 @@ enum class FriendLinkType(val value: Int, val shortName: String) {
}
}
}
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() {
val blockAds by context.config.global.blockAds
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 displayMetrics = context.resources.displayMetrics
@ -77,7 +77,7 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE
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
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 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?
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
if (layoutParams.width == -1 &&
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")) {
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.OperaViewerParamsOverride
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.ProfilePictureDownloader
import me.rhunk.snapenhance.core.features.impl.experiments.*
@ -113,7 +113,7 @@ class FeatureManager(
BypassScreenshotDetection::class,
HalfSwipeNotifier::class,
DisableConfirmationDialogs::class,
Stories::class,
MixerStories::class,
DisableComposerModules::class,
FideliusIndicator::class,
EditTextOverride::class,