mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-28 12:30:12 +02:00
feat: profile picture downloader
- new events: add view, network request - fix anonymous story viewing
This commit is contained in:
parent
05990a4b72
commit
d0668b67d4
@ -1,6 +1,11 @@
|
||||
package me.rhunk.snapenhance
|
||||
|
||||
import android.content.Intent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.AddViewEvent
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.NetworkApiRequestEvent
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.OnSnapInteractionEvent
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.SendMessageWithContentEvent
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.SnapWidgetBroadcastReceiveEvent
|
||||
@ -9,6 +14,8 @@ import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.hook
|
||||
import me.rhunk.snapenhance.manager.Manager
|
||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.setObjectField
|
||||
import me.rhunk.snapenhance.util.snap.SnapWidgetBroadcastReceiverHelper
|
||||
|
||||
class EventDispatcher(
|
||||
@ -57,5 +64,48 @@ class EventDispatcher(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ViewGroup::class.java.getMethod(
|
||||
"addView",
|
||||
View::class.java,
|
||||
Int::class.javaPrimitiveType,
|
||||
LayoutParams::class.java
|
||||
).hook(HookStage.BEFORE) { param ->
|
||||
context.event.post(
|
||||
AddViewEvent(
|
||||
parent = param.thisObject(),
|
||||
view = param.arg(0),
|
||||
index = param.arg(1),
|
||||
layoutParams = param.arg(2)
|
||||
).apply {
|
||||
adapter = param
|
||||
}
|
||||
)?.also { event ->
|
||||
with(param) {
|
||||
setArg(0, event.view)
|
||||
setArg(1, event.index)
|
||||
setArg(2, event.layoutParams)
|
||||
}
|
||||
if (event.canceled) param.setResult(null)
|
||||
}
|
||||
}
|
||||
|
||||
context.classCache.networkApi.hook("submit", HookStage.BEFORE) { param ->
|
||||
val request = param.arg<Any>(0)
|
||||
|
||||
context.event.post(
|
||||
NetworkApiRequestEvent(
|
||||
url = request.getObjectField("mUrl") as String,
|
||||
callback = param.arg(4),
|
||||
request = request,
|
||||
).apply {
|
||||
adapter = param
|
||||
}
|
||||
)?.also { event ->
|
||||
event.request.setObjectField("mUrl", event.url)
|
||||
if (event.canceled) param.setResult(null)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ import me.rhunk.snapenhance.database.DatabaseAccess
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.manager.impl.ActionManager
|
||||
import me.rhunk.snapenhance.manager.impl.FeatureManager
|
||||
import me.rhunk.snapenhance.util.download.DownloadServer
|
||||
import me.rhunk.snapenhance.util.download.HttpServer
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.reflect.KClass
|
||||
@ -50,7 +50,7 @@ class ModContext {
|
||||
val mappings = MappingsWrapper()
|
||||
val actionManager = ActionManager(this)
|
||||
val database = DatabaseAccess(this)
|
||||
val downloadServer = DownloadServer()
|
||||
val httpServer = HttpServer()
|
||||
val messageSender = MessageSender(this)
|
||||
val classCache get() = SnapEnhance.classCache
|
||||
val resources: Resources get() = androidContext.resources
|
||||
|
@ -25,13 +25,13 @@ class UserInterfaceTweaks : ConfigContainer() {
|
||||
"hide_call_buttons"
|
||||
)
|
||||
val disableSpotlight = boolean("disable_spotlight")
|
||||
val startupTab = unique("startup_tab", "ngs_map_icon_container",
|
||||
val startupTab = unique("startup_tab",
|
||||
"ngs_map_icon_container",
|
||||
"ngs_chat_icon_container",
|
||||
"ngs_camera_icon_container",
|
||||
"ngs_community_icon_container",
|
||||
"ngs_spotlight_icon_container",
|
||||
"ngs_search_icon_container"
|
||||
)
|
||||
) { addNotices(FeatureNotice.MAY_BREAK_INTERNAL_BEHAVIOR) }
|
||||
val storyViewerOverride = unique("story_viewer_override", "DISCOVER_PLAYBACK_SEEKBAR", "VERTICAL_STORY_VIEWER") { addNotices(FeatureNotice.UNSTABLE) }
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package me.rhunk.snapenhance.core.eventbus.events.impl
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import me.rhunk.snapenhance.core.eventbus.events.AbstractHookEvent
|
||||
|
||||
class AddViewEvent(
|
||||
val parent: ViewGroup,
|
||||
var view: View,
|
||||
var index: Int,
|
||||
var layoutParams: ViewGroup.LayoutParams
|
||||
) : AbstractHookEvent()
|
@ -0,0 +1,9 @@
|
||||
package me.rhunk.snapenhance.core.eventbus.events.impl
|
||||
|
||||
import me.rhunk.snapenhance.core.eventbus.events.AbstractHookEvent
|
||||
|
||||
class NetworkApiRequestEvent(
|
||||
val request: Any,
|
||||
val callback: Any,
|
||||
var url: String,
|
||||
) : AbstractHookEvent()
|
@ -21,17 +21,15 @@ enum class FileType(
|
||||
UNKNOWN("dat", "application/octet-stream", false, false, false);
|
||||
|
||||
companion object {
|
||||
private val fileSignatures = HashMap<String, FileType>()
|
||||
|
||||
init {
|
||||
fileSignatures["52494646"] = WEBP
|
||||
fileSignatures["504b0304"] = ZIP
|
||||
fileSignatures["89504e47"] = PNG
|
||||
fileSignatures["00000020"] = MP4
|
||||
fileSignatures["00000018"] = MP4
|
||||
fileSignatures["0000001c"] = MP4
|
||||
fileSignatures["ffd8ffe0"] = JPG
|
||||
}
|
||||
private val fileSignatures = mapOf(
|
||||
"52494646" to WEBP,
|
||||
"504b0304" to ZIP,
|
||||
"89504e47" to PNG,
|
||||
"00000020" to MP4,
|
||||
"00000018" to MP4,
|
||||
"0000001c" to MP4,
|
||||
"ffd8ff" to JPG,
|
||||
)
|
||||
|
||||
fun fromString(string: String?): FileType {
|
||||
return values().firstOrNull { it.fileExtension.equals(string, ignoreCase = true) } ?: UNKNOWN
|
||||
|
@ -8,7 +8,8 @@ enum class MediaFilter(
|
||||
PENDING("pending", true),
|
||||
CHAT_MEDIA("chat_media"),
|
||||
STORY("story"),
|
||||
SPOTLIGHT("spotlight");
|
||||
SPOTLIGHT("spotlight"),
|
||||
PROFILE_PICTURE("profile_picture");
|
||||
|
||||
fun matches(source: String?): Boolean {
|
||||
if (source == null) return false
|
||||
|
@ -165,7 +165,7 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
||||
Uri.parse(path).let { uri ->
|
||||
if (uri.scheme == "file") {
|
||||
return@let suspendCoroutine<String> { continuation ->
|
||||
context.downloadServer.ensureServerStarted {
|
||||
context.httpServer.ensureServerStarted {
|
||||
val file = Paths.get(uri.path).toFile()
|
||||
val url = putDownloadableContent(file.inputStream(), file.length())
|
||||
continuation.resumeWith(Result.success(url))
|
||||
@ -532,6 +532,18 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadProfilePicture(url: String, author: String) {
|
||||
provideDownloadManagerClient(
|
||||
pathSuffix = "Profile Pictures",
|
||||
mediaIdentifier = url.hashCode().toString(16).replaceFirst("-", ""),
|
||||
mediaDisplaySource = author,
|
||||
mediaDisplayType = MediaFilter.PROFILE_PICTURE.key
|
||||
).downloadSingleMedia(
|
||||
url,
|
||||
DownloadMediaType.REMOTE_MEDIA
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a message is focused in chat
|
||||
*/
|
||||
|
@ -0,0 +1,77 @@
|
||||
package me.rhunk.snapenhance.features.impl.downloader
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.widget.Button
|
||||
import android.widget.RelativeLayout
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.AddViewEvent
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.NetworkApiRequestEvent
|
||||
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 me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
||||
import me.rhunk.snapenhance.util.protobuf.ProtoReader
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class ProfilePictureDownloader : Feature("ProfilePictureDownloader", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun asyncOnActivityCreate() {
|
||||
var friendUsername: String? = null
|
||||
var backgroundUrl: String? = null
|
||||
var avatarUrl: String? = null
|
||||
|
||||
context.event.subscribe(AddViewEvent::class) { event ->
|
||||
if (event.view::class.java.name != "com.snap.unifiedpublicprofile.UnifiedPublicProfileView") return@subscribe
|
||||
|
||||
event.parent.addView(Button(event.parent.context).apply {
|
||||
text = "Download"
|
||||
layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT).apply {
|
||||
setMargins(0, 200, 0, 0)
|
||||
}
|
||||
setOnClickListener {
|
||||
ViewAppearanceHelper.newAlertDialogBuilder(
|
||||
this@ProfilePictureDownloader.context.mainActivity!!
|
||||
).apply {
|
||||
setTitle("Download profile picture")
|
||||
val choices = mutableMapOf<String, String>()
|
||||
backgroundUrl?.let { choices["Background"] = it }
|
||||
avatarUrl?.let { choices["Avatar"] = it }
|
||||
|
||||
setItems(choices.keys.toTypedArray()) { _, which ->
|
||||
runCatching {
|
||||
this@ProfilePictureDownloader.context.feature(MediaDownloader::class).downloadProfilePicture(
|
||||
choices.values.elementAt(which),
|
||||
friendUsername!!
|
||||
)
|
||||
}.onFailure {
|
||||
Logger.error("Failed to download profile picture", it)
|
||||
}
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
context.event.subscribe(NetworkApiRequestEvent::class) { event ->
|
||||
if (!event.url.endsWith("/rpc/getPublicProfile")) return@subscribe
|
||||
Hooker.ephemeralHookObjectMethod(event.callback::class.java, event.callback, "onSucceeded", HookStage.BEFORE) { methodParams ->
|
||||
val content = methodParams.arg<ByteBuffer>(2).run {
|
||||
ByteArray(capacity()).also {
|
||||
get(it)
|
||||
position(0)
|
||||
}
|
||||
}
|
||||
|
||||
ProtoReader(content).readPath(1, 1, 2) {
|
||||
friendUsername = getString(2) ?: return@readPath
|
||||
readPath(4) {
|
||||
backgroundUrl = getString(2)
|
||||
avatarUrl = getString(100)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package me.rhunk.snapenhance.features.impl.privacy
|
||||
|
||||
import de.robv.android.xposed.XposedHelpers
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.NetworkApiRequestEvent
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
@ -20,20 +20,10 @@ class DisableMetrics : Feature("DisableMetrics", loadParams = FeatureLoadParams.
|
||||
}
|
||||
}
|
||||
|
||||
Hooker.hook(context.classCache.networkApi, "submit", HookStage.BEFORE,
|
||||
{ disableMetrics }) { param ->
|
||||
val httpRequest: Any = param.arg(0)
|
||||
val url = XposedHelpers.getObjectField(httpRequest, "mUrl").toString()
|
||||
/*if (url.contains("resolve?co=")) {
|
||||
val index = url.indexOf("co=")
|
||||
val end = url.lastIndexOf("&")
|
||||
val co = url.substring(index + 3, end)
|
||||
val decoded = Base64.getDecoder().decode(co.toByteArray(StandardCharsets.UTF_8))
|
||||
debug("decoded : " + decoded.toString(Charsets.UTF_8))
|
||||
debug("content: $co")
|
||||
}*/
|
||||
context.event.subscribe(NetworkApiRequestEvent::class, { disableMetrics }) { param ->
|
||||
val url = param.url
|
||||
if (url.contains("app-analytics") || url.endsWith("v1/metrics")) {
|
||||
param.setResult(null)
|
||||
param.canceled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,27 @@
|
||||
package me.rhunk.snapenhance.features.impl.spying
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.NetworkApiRequestEvent
|
||||
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 me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.setObjectField
|
||||
import me.rhunk.snapenhance.util.download.HttpServer
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class AnonymousStoryViewing : Feature("Anonymous Story Viewing", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||
override fun asyncOnActivityCreate() {
|
||||
val anonymousStoryViewProperty by context.config.messaging.anonymousStoryViewing
|
||||
Hooker.hook(context.classCache.networkApi,"submit", HookStage.BEFORE, { anonymousStoryViewProperty }) {
|
||||
val httpRequest: Any = it.arg(0)
|
||||
val url = httpRequest.getObjectField("mUrl") as String
|
||||
if (url.endsWith("readreceipt-indexer/batchuploadreadreceipts")) {
|
||||
httpRequest.setObjectField("mUrl", "http://127.0.0.1")
|
||||
val httpServer = HttpServer()
|
||||
|
||||
context.event.subscribe(NetworkApiRequestEvent::class, { anonymousStoryViewProperty }) { event ->
|
||||
if (!event.url.endsWith("readreceipt-indexer/batchuploadreadreceipts")) return@subscribe
|
||||
runBlocking {
|
||||
suspendCoroutine {
|
||||
httpServer.ensureServerStarted {
|
||||
event.url = "http://127.0.0.1:${httpServer.port}"
|
||||
it.resumeWith(Result.success(Unit))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,9 @@ package me.rhunk.snapenhance.features.impl.ui
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Handler
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.AddViewEvent
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
@ -38,21 +38,17 @@ class StartupPageOverride : Feature("StartupPageOverride", loadParams = FeatureL
|
||||
}
|
||||
|
||||
val ngsIconId = context.androidContext.resources.getIdentifier(ngsIconName, "id", Constants.SNAPCHAT_PACKAGE_NAME)
|
||||
val unhooks = mutableListOf<() -> Unit>()
|
||||
|
||||
ViewGroup::class.java.getMethod(
|
||||
"addView",
|
||||
View::class.java,
|
||||
Int::class.javaPrimitiveType,
|
||||
ViewGroup.LayoutParams::class.java
|
||||
).hook(HookStage.AFTER) { param ->
|
||||
if (param.thisObject<ViewGroup>() !is LinearLayout) return@hook
|
||||
with(param.arg<View>(0)) {
|
||||
lateinit var unhook: () -> Unit
|
||||
|
||||
context.event.subscribe(AddViewEvent::class) { event ->
|
||||
if (event.parent !is LinearLayout) return@subscribe
|
||||
with(event.view) {
|
||||
if (id == ngsIconId) {
|
||||
ngsIcon = this
|
||||
unhooks.forEach { it() }
|
||||
unhook()
|
||||
}
|
||||
}
|
||||
}.also { unhooks.add(it::unhook) }
|
||||
}.also { unhook = it }
|
||||
}
|
||||
}
|
@ -8,9 +8,9 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.AddViewEvent
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookAdapter
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.Hooker
|
||||
import me.rhunk.snapenhance.hook.hook
|
||||
@ -25,12 +25,12 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideStorySection(param: HookAdapter) {
|
||||
val parent = param.thisObject() as ViewGroup
|
||||
private fun hideStorySection(event: AddViewEvent) {
|
||||
val parent = event.parent
|
||||
parent.visibility = View.GONE
|
||||
val marginLayoutParams = parent.layoutParams as ViewGroup.MarginLayoutParams
|
||||
marginLayoutParams.setMargins(-99999, -99999, -99999, -99999)
|
||||
param.setResult(null)
|
||||
event.canceled = true
|
||||
}
|
||||
|
||||
@SuppressLint("DiscouragedApi", "InternalInsetResource")
|
||||
@ -69,33 +69,29 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE
|
||||
}
|
||||
}
|
||||
|
||||
ViewGroup::class.java.getMethod(
|
||||
"addView",
|
||||
View::class.java,
|
||||
Int::class.javaPrimitiveType,
|
||||
ViewGroup.LayoutParams::class.java
|
||||
).hook(HookStage.BEFORE) { param ->
|
||||
val view: View = param.arg(0)
|
||||
val viewId = view.id
|
||||
|
||||
context.event.subscribe(AddViewEvent::class) { event ->
|
||||
val viewId = event.view.id
|
||||
val view = event.view
|
||||
|
||||
if (hideStorySections.contains("hide_for_you")) {
|
||||
if (viewId == getIdentifier("df_large_story", "id") ||
|
||||
viewId == getIdentifier("df_promoted_story", "id")) {
|
||||
hideStorySection(param)
|
||||
return@hook
|
||||
hideStorySection(event)
|
||||
return@subscribe
|
||||
}
|
||||
if (viewId == getIdentifier("stories_load_progress_layout", "id")) {
|
||||
param.setResult(null)
|
||||
event.canceled = true
|
||||
}
|
||||
}
|
||||
|
||||
if (hideStorySections.contains("hide_friends") && viewId == getIdentifier("friend_card_frame", "id")) {
|
||||
hideStorySection(param)
|
||||
hideStorySection(event)
|
||||
}
|
||||
|
||||
//mappings?
|
||||
if (hideStorySections.contains("hide_friend_suggestions") && view.javaClass.superclass?.name?.endsWith("StackDrawLayout") == true) {
|
||||
val layoutParams = view.layoutParams as? FrameLayout.LayoutParams ?: return@hook
|
||||
val layoutParams = view.layoutParams as? FrameLayout.LayoutParams ?: return@subscribe
|
||||
if (layoutParams.width == -1 &&
|
||||
layoutParams.height == -2 &&
|
||||
view.javaClass.let { clazz ->
|
||||
@ -103,17 +99,17 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE
|
||||
clazz.constructors.any { it.parameterCount == 1 && it.parameterTypes[0] == Context::class.java }
|
||||
}
|
||||
) {
|
||||
hideStorySection(param)
|
||||
hideStorySection(event)
|
||||
}
|
||||
}
|
||||
|
||||
if (hideStorySections.contains("hide_following") && (viewId == getIdentifier("df_small_story", "id"))
|
||||
) {
|
||||
hideStorySection(param)
|
||||
hideStorySection(event)
|
||||
}
|
||||
|
||||
if (blockAds && viewId == getIdentifier("df_promoted_story", "id")) {
|
||||
hideStorySection(param)
|
||||
hideStorySection(event)
|
||||
}
|
||||
|
||||
if (isImmersiveCamera) {
|
||||
@ -145,15 +141,15 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE
|
||||
view.visibility = View.GONE
|
||||
}
|
||||
if (getIdentifier("chat_input_bar_sharing_drawer_button", "id") == viewId && hiddenElements.contains("hide_live_location_share_button")) {
|
||||
param.setResult(null)
|
||||
event.canceled = true
|
||||
}
|
||||
if (viewId == callButton1 || viewId == callButton2) {
|
||||
if (!hiddenElements.contains("hide_call_buttons")) return@hook
|
||||
if (view.visibility == View.GONE) return@hook
|
||||
if (!hiddenElements.contains("hide_call_buttons")) return@subscribe
|
||||
if (view.visibility == View.GONE) return@subscribe
|
||||
}
|
||||
if (viewId == callButtonsStub) {
|
||||
if (!hiddenElements.contains("hide_call_buttons")) return@hook
|
||||
param.setResult(null)
|
||||
if (!hiddenElements.contains("hide_call_buttons")) return@subscribe
|
||||
event.canceled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,8 +111,8 @@ object Hooker {
|
||||
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
|
||||
hook(clazz, methodName, stage) { param->
|
||||
if (param.nullableThisObject<Any>() != instance) return@hook
|
||||
unhooks.forEach { it.unhook() }
|
||||
hookConsumer(param)
|
||||
unhooks.forEach{ it.unhook() }
|
||||
}.also { unhooks.addAll(it) }
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import me.rhunk.snapenhance.features.impl.AutoUpdater
|
||||
import me.rhunk.snapenhance.features.impl.ConfigurationOverride
|
||||
import me.rhunk.snapenhance.features.impl.Messaging
|
||||
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
||||
import me.rhunk.snapenhance.features.impl.downloader.ProfilePictureDownloader
|
||||
import me.rhunk.snapenhance.features.impl.experiments.AmoledDarkMode
|
||||
import me.rhunk.snapenhance.features.impl.experiments.AppPasscode
|
||||
import me.rhunk.snapenhance.features.impl.experiments.DeviceSpooferHook
|
||||
@ -91,6 +92,7 @@ class FeatureManager(private val context: ModContext) : Manager {
|
||||
register(StartupPageOverride::class)
|
||||
register(GooglePlayServicesDialogs::class)
|
||||
register(NoFriendScoreDelay::class)
|
||||
register(ProfilePictureDownloader::class)
|
||||
|
||||
initializeFeatures()
|
||||
}
|
||||
|
@ -7,11 +7,10 @@ import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.AddViewEvent
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.features.impl.Messaging
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.Hooker
|
||||
import java.lang.reflect.Modifier
|
||||
|
||||
@SuppressLint("DiscouragedApi")
|
||||
@ -42,17 +41,9 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
|
||||
val componentsHolder = context.resources.getIdentifier("components_holder", "id", Constants.SNAPCHAT_PACKAGE_NAME)
|
||||
val feedNewChat = context.resources.getIdentifier("feed_new_chat", "id", Constants.SNAPCHAT_PACKAGE_NAME)
|
||||
|
||||
val addViewMethod = ViewGroup::class.java.getMethod(
|
||||
"addView",
|
||||
View::class.java,
|
||||
Int::class.javaPrimitiveType,
|
||||
ViewGroup.LayoutParams::class.java
|
||||
)
|
||||
|
||||
Hooker.hook(addViewMethod, HookStage.BEFORE) { param ->
|
||||
val viewGroup: ViewGroup = param.thisObject()
|
||||
context.event.subscribe(AddViewEvent::class) { event ->
|
||||
val originalAddView: (View) -> Unit = {
|
||||
param.invokeOriginal(arrayOf(it, -1,
|
||||
event.adapter.invokeOriginal(arrayOf(it, -1,
|
||||
FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
@ -60,19 +51,20 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
|
||||
)
|
||||
}
|
||||
|
||||
val childView: View = param.arg(0)
|
||||
operaContextActionMenu.inject(viewGroup, childView)
|
||||
val viewGroup: ViewGroup = event.parent
|
||||
val childView: View = event.view
|
||||
operaContextActionMenu.inject(event.parent, childView)
|
||||
|
||||
if (viewGroup.id == componentsHolder && childView.id == feedNewChat) {
|
||||
settingsGearInjector.inject(viewGroup, childView)
|
||||
return@hook
|
||||
if (event.parent.id == componentsHolder && childView.id == feedNewChat) {
|
||||
settingsGearInjector.inject(event.parent, childView)
|
||||
return@subscribe
|
||||
}
|
||||
|
||||
//download in chat snaps and notes from the chat action menu
|
||||
if (viewGroup.javaClass.name.endsWith("ActionMenuChatItemContainer")) {
|
||||
if (viewGroup.parent == null || viewGroup.parent.parent == null) return@hook
|
||||
if (viewGroup.parent == null || viewGroup.parent.parent == null) return@subscribe
|
||||
chatActionMenu.inject(viewGroup)
|
||||
return@hook
|
||||
return@subscribe
|
||||
}
|
||||
|
||||
//TODO: inject in group chat menus
|
||||
@ -101,7 +93,7 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
|
||||
viewList.reversed().forEach { injectedLayout.addView(it, 0) }
|
||||
}
|
||||
|
||||
param.setArg(0, injectedLayout)
|
||||
event.view = injectedLayout
|
||||
}
|
||||
|
||||
if (viewGroup is LinearLayout && viewGroup.id == actionSheetItemsContainerLayoutId) {
|
||||
@ -125,12 +117,12 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
|
||||
//context.config.writeConfig()
|
||||
}
|
||||
})
|
||||
return@hook
|
||||
return@subscribe
|
||||
}
|
||||
if (messaging.lastFetchConversationUUID == null || messaging.lastFetchConversationUserUUID == null) return@hook
|
||||
if (messaging.lastFetchConversationUUID == null || messaging.lastFetchConversationUserUUID == null) return@subscribe
|
||||
|
||||
//filter by the slot index
|
||||
if (viewGroup.getChildCount() != context.config.userInterface.friendFeedMenuPosition.get()) return@hook
|
||||
if (viewGroup.getChildCount() != context.config.userInterface.friendFeedMenuPosition.get()) return@subscribe
|
||||
friendFeedInfoMenu.inject(viewGroup, originalAddView)
|
||||
}
|
||||
}
|
||||
|
@ -18,10 +18,10 @@ import java.util.StringTokenizer
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.random.Random
|
||||
|
||||
class DownloadServer(
|
||||
class HttpServer(
|
||||
private val timeout: Int = 10000
|
||||
) {
|
||||
private val port = Random.nextInt(10000, 65535)
|
||||
val port = Random.nextInt(10000, 65535)
|
||||
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
private var timeoutJob: Job? = null
|
||||
@ -30,16 +30,16 @@ class DownloadServer(
|
||||
private val cachedData = ConcurrentHashMap<String, Pair<InputStream, Long>>()
|
||||
private var serverSocket: ServerSocket? = null
|
||||
|
||||
fun ensureServerStarted(callback: DownloadServer.() -> Unit) {
|
||||
fun ensureServerStarted(callback: HttpServer.() -> Unit) {
|
||||
if (serverSocket != null && !serverSocket!!.isClosed) {
|
||||
callback(this)
|
||||
return
|
||||
}
|
||||
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
Logger.debug("starting download server on port $port")
|
||||
Logger.debug("starting http server on port $port")
|
||||
serverSocket = ServerSocket(port)
|
||||
callback(this@DownloadServer)
|
||||
callback(this@HttpServer)
|
||||
while (!serverSocket!!.isClosed) {
|
||||
try {
|
||||
val socket = serverSocket!!.accept()
|
||||
@ -48,7 +48,7 @@ class DownloadServer(
|
||||
handleRequest(socket)
|
||||
timeoutJob = launch {
|
||||
delay(timeout.toLong())
|
||||
Logger.debug("download server closed due to timeout")
|
||||
Logger.debug("http server closed due to timeout")
|
||||
runCatching {
|
||||
socketJob?.cancel()
|
||||
socket.close()
|
||||
@ -59,7 +59,7 @@ class DownloadServer(
|
||||
}
|
||||
}
|
||||
} catch (e: SocketException) {
|
||||
Logger.debug("download server timed out")
|
||||
Logger.debug("http server timed out")
|
||||
break;
|
||||
} catch (e: Throwable) {
|
||||
Logger.error("failed to handle request", e)
|
||||
@ -96,6 +96,8 @@ class DownloadServer(
|
||||
val parse = StringTokenizer(line)
|
||||
val method = parse.nextToken().uppercase(Locale.getDefault())
|
||||
var fileRequested = parse.nextToken().lowercase(Locale.getDefault())
|
||||
Logger.debug("[http-server:${port}] $method $fileRequested")
|
||||
|
||||
if (method != "GET") {
|
||||
with(writer) {
|
||||
println("HTTP/1.1 501 Not Implemented")
|
Loading…
x
Reference in New Issue
Block a user