feat: bypass video length restriction

- single and split mode
This commit is contained in:
rhunk 2023-10-08 18:41:18 +02:00
parent e4443279d6
commit 0ba1eb4a8b
6 changed files with 94 additions and 55 deletions

View File

@ -362,9 +362,9 @@
"name": "Block Ads",
"description": "Prevents Advertisements from being displayed"
},
"disable_video_length_restrictions": {
"name": "Disable Video Length Restrictions",
"description": "Disables Snapchat's maximum video length restriction"
"bypass_video_length_restriction": {
"name": "Bypass Video Length Restrictions",
"description": "Single: sends a single video\nSplit: split videos after editing"
},
"disable_google_play_dialogs": {
"name": "Disable Google Play Services Dialogs",
@ -664,6 +664,10 @@
"added_by_group_chat": "By Group Chat",
"added_by_qr_code": "By QR Code",
"added_by_community": "By Community"
},
"bypass_video_length_restriction": {
"single": "Single media",
"split": "Split media"
}
}
},

View File

@ -7,7 +7,7 @@ class Global : ConfigContainer() {
val snapchatPlus = boolean("snapchat_plus") { addNotices(FeatureNotice.BAN_RISK); requireRestart() }
val disableMetrics = boolean("disable_metrics")
val blockAds = boolean("block_ads")
val disableVideoLengthRestrictions = boolean("disable_video_length_restrictions") { addNotices(FeatureNotice.BAN_RISK) }
val bypassVideoLengthRestriction = unique("bypass_video_length_restriction", "split", "single") { addNotices(FeatureNotice.BAN_RISK); requireRestart() }
val disableGooglePlayDialogs = boolean("disable_google_play_dialogs") { requireRestart() }
val forceMediaSourceQuality = boolean("force_media_source_quality")
val disableSnapSplitting = boolean("disable_snap_splitting") { addNotices(FeatureNotice.INTERNAL_BEHAVIOR) }

View File

@ -0,0 +1,72 @@
package me.rhunk.snapenhance.features.impl.tweaks
import android.os.Build
import android.os.FileObserver
import com.google.gson.JsonParser
import me.rhunk.snapenhance.core.event.events.impl.SendMessageWithContentEvent
import me.rhunk.snapenhance.core.util.ktx.setObjectField
import me.rhunk.snapenhance.features.Feature
import me.rhunk.snapenhance.features.FeatureLoadParams
import me.rhunk.snapenhance.hook.HookStage
import me.rhunk.snapenhance.hook.hookConstructor
import java.io.File
class BypassVideoLengthRestriction :
Feature("BypassVideoLengthRestriction", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
private lateinit var fileObserver: FileObserver
override fun asyncOnActivityCreate() {
val mode = context.config.global.bypassVideoLengthRestriction.getNullable()
if (mode == "single") {
//fix black videos when story is posted
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val postedStorySnapFolder =
File(context.androidContext.filesDir, "file_manager/posted_story_snap")
fileObserver = (object : FileObserver(postedStorySnapFolder, MOVED_TO) {
override fun onEvent(event: Int, path: String?) {
if (event != MOVED_TO || path?.endsWith("posted_story_snap.2") != true) return
fileObserver.stopWatching()
val file = File(postedStorySnapFolder, path)
runCatching {
val fileContent = JsonParser.parseReader(file.reader()).asJsonObject
if (fileContent["timerOrDuration"].asLong < 0) file.delete()
}.onFailure {
context.log.error("Failed to read story metadata file", it)
}
}
})
context.event.subscribe(SendMessageWithContentEvent::class) { event ->
if (event.destinations.stories.isEmpty()) return@subscribe
fileObserver.startWatching()
}
}
context.mappings.getMappedClass("DefaultMediaItem")
.hookConstructor(HookStage.BEFORE) { param ->
//set the video length argument
param.setArg(5, -1L)
}
}
//TODO: allow split from any source
if (mode == "split") {
val cameraRollId = context.mappings.getMappedMap("CameraRollMediaId")
// memories grid
findClass(cameraRollId["class"].toString()).hookConstructor(HookStage.AFTER) { param ->
//set the durationMs field
param.thisObject<Any>()
.setObjectField(cameraRollId["durationMsField"].toString(), -1L)
}
// chat camera roll grid
findClass("com.snap.impala.common.media.MediaLibraryItem").hookConstructor(HookStage.BEFORE) { param ->
//set the video length argument
param.setArg(3, -1L)
}
}
}
}

View File

@ -1,50 +0,0 @@
package me.rhunk.snapenhance.features.impl.tweaks
import android.os.Build
import android.os.FileObserver
import com.google.gson.JsonParser
import me.rhunk.snapenhance.core.event.events.impl.SendMessageWithContentEvent
import me.rhunk.snapenhance.features.Feature
import me.rhunk.snapenhance.features.FeatureLoadParams
import me.rhunk.snapenhance.hook.HookStage
import me.rhunk.snapenhance.hook.Hooker
import java.io.File
class DisableVideoLengthRestriction : Feature("DisableVideoLengthRestriction", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
private lateinit var fileObserver: FileObserver
override fun asyncOnActivityCreate() {
val defaultMediaItem = context.mappings.getMappedClass("DefaultMediaItem")
val isState by context.config.global.disableVideoLengthRestrictions
//fix black videos when story is posted
if (isState && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val postedStorySnapFolder = File(context.androidContext.filesDir, "file_manager/posted_story_snap")
fileObserver = (object : FileObserver(postedStorySnapFolder, MOVED_TO) {
override fun onEvent(event: Int, path: String?) {
if (event != MOVED_TO || path?.endsWith("posted_story_snap.2") != true) return
fileObserver.stopWatching()
val file = File(postedStorySnapFolder, path)
runCatching {
val fileContent = JsonParser.parseReader(file.reader()).asJsonObject
if (fileContent["timerOrDuration"].asLong < 0) file.delete()
}.onFailure {
context.log.error("Failed to read story metadata file", it)
}
}
})
context.event.subscribe(SendMessageWithContentEvent::class) { event ->
if (event.destinations.stories.isEmpty()) return@subscribe
fileObserver.startWatching()
}
}
Hooker.hookConstructor(defaultMediaItem, HookStage.BEFORE, { isState }) { param ->
//set the video length argument
param.setArg(5, -1L)
}
}
}

View File

@ -84,7 +84,7 @@ class FeatureManager(
ConfigurationOverride::class,
SendOverride::class,
UnlimitedSnapViewTime::class,
DisableVideoLengthRestriction::class,
BypassVideoLengthRestriction::class,
MediaQualityLevelOverride::class,
MeoPasscodeBypass::class,
AppPasscode::class,

View File

@ -1,11 +1,24 @@
package me.rhunk.snapenhance.mapper.impl
import me.rhunk.snapenhance.mapper.AbstractClassMapper
import me.rhunk.snapenhance.mapper.ext.findConstString
import me.rhunk.snapenhance.mapper.ext.getClassName
import me.rhunk.snapenhance.mapper.ext.isAbstract
class DefaultMediaItemMapper : AbstractClassMapper() {
init {
mapper {
for (clazz in classes) {
if (clazz.methods.find { it.name == "toString" }?.implementation?.findConstString("CameraRollMediaId", contains = true) != true) {
continue
}
val durationMsField = clazz.fields.firstOrNull { it.type == "J" } ?: continue
addMapping("CameraRollMediaId", "class" to clazz.getClassName(), "durationMsField" to durationMsField.name)
return@mapper
}
}
mapper {
for (clazz in classes) {
val superClass = getClass(clazz.superclass) ?: continue