mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-29 21:10:20 +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
|
package me.rhunk.snapenhance
|
||||||
|
|
||||||
import android.content.Intent
|
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.OnSnapInteractionEvent
|
||||||
import me.rhunk.snapenhance.core.eventbus.events.impl.SendMessageWithContentEvent
|
import me.rhunk.snapenhance.core.eventbus.events.impl.SendMessageWithContentEvent
|
||||||
import me.rhunk.snapenhance.core.eventbus.events.impl.SnapWidgetBroadcastReceiveEvent
|
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.HookStage
|
||||||
import me.rhunk.snapenhance.hook.hook
|
import me.rhunk.snapenhance.hook.hook
|
||||||
import me.rhunk.snapenhance.manager.Manager
|
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
|
import me.rhunk.snapenhance.util.snap.SnapWidgetBroadcastReceiverHelper
|
||||||
|
|
||||||
class EventDispatcher(
|
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.features.Feature
|
||||||
import me.rhunk.snapenhance.manager.impl.ActionManager
|
import me.rhunk.snapenhance.manager.impl.ActionManager
|
||||||
import me.rhunk.snapenhance.manager.impl.FeatureManager
|
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.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@ -50,7 +50,7 @@ class ModContext {
|
|||||||
val mappings = MappingsWrapper()
|
val mappings = MappingsWrapper()
|
||||||
val actionManager = ActionManager(this)
|
val actionManager = ActionManager(this)
|
||||||
val database = DatabaseAccess(this)
|
val database = DatabaseAccess(this)
|
||||||
val downloadServer = DownloadServer()
|
val httpServer = HttpServer()
|
||||||
val messageSender = MessageSender(this)
|
val messageSender = MessageSender(this)
|
||||||
val classCache get() = SnapEnhance.classCache
|
val classCache get() = SnapEnhance.classCache
|
||||||
val resources: Resources get() = androidContext.resources
|
val resources: Resources get() = androidContext.resources
|
||||||
|
@ -25,13 +25,13 @@ class UserInterfaceTweaks : ConfigContainer() {
|
|||||||
"hide_call_buttons"
|
"hide_call_buttons"
|
||||||
)
|
)
|
||||||
val disableSpotlight = boolean("disable_spotlight")
|
val disableSpotlight = boolean("disable_spotlight")
|
||||||
val startupTab = unique("startup_tab", "ngs_map_icon_container",
|
val startupTab = unique("startup_tab",
|
||||||
"ngs_map_icon_container",
|
"ngs_map_icon_container",
|
||||||
"ngs_chat_icon_container",
|
"ngs_chat_icon_container",
|
||||||
"ngs_camera_icon_container",
|
"ngs_camera_icon_container",
|
||||||
"ngs_community_icon_container",
|
"ngs_community_icon_container",
|
||||||
"ngs_spotlight_icon_container",
|
"ngs_spotlight_icon_container",
|
||||||
"ngs_search_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) }
|
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);
|
UNKNOWN("dat", "application/octet-stream", false, false, false);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val fileSignatures = HashMap<String, FileType>()
|
private val fileSignatures = mapOf(
|
||||||
|
"52494646" to WEBP,
|
||||||
init {
|
"504b0304" to ZIP,
|
||||||
fileSignatures["52494646"] = WEBP
|
"89504e47" to PNG,
|
||||||
fileSignatures["504b0304"] = ZIP
|
"00000020" to MP4,
|
||||||
fileSignatures["89504e47"] = PNG
|
"00000018" to MP4,
|
||||||
fileSignatures["00000020"] = MP4
|
"0000001c" to MP4,
|
||||||
fileSignatures["00000018"] = MP4
|
"ffd8ff" to JPG,
|
||||||
fileSignatures["0000001c"] = MP4
|
)
|
||||||
fileSignatures["ffd8ffe0"] = JPG
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fromString(string: String?): FileType {
|
fun fromString(string: String?): FileType {
|
||||||
return values().firstOrNull { it.fileExtension.equals(string, ignoreCase = true) } ?: UNKNOWN
|
return values().firstOrNull { it.fileExtension.equals(string, ignoreCase = true) } ?: UNKNOWN
|
||||||
|
@ -8,7 +8,8 @@ enum class MediaFilter(
|
|||||||
PENDING("pending", true),
|
PENDING("pending", true),
|
||||||
CHAT_MEDIA("chat_media"),
|
CHAT_MEDIA("chat_media"),
|
||||||
STORY("story"),
|
STORY("story"),
|
||||||
SPOTLIGHT("spotlight");
|
SPOTLIGHT("spotlight"),
|
||||||
|
PROFILE_PICTURE("profile_picture");
|
||||||
|
|
||||||
fun matches(source: String?): Boolean {
|
fun matches(source: String?): Boolean {
|
||||||
if (source == null) return false
|
if (source == null) return false
|
||||||
|
@ -165,7 +165,7 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
Uri.parse(path).let { uri ->
|
Uri.parse(path).let { uri ->
|
||||||
if (uri.scheme == "file") {
|
if (uri.scheme == "file") {
|
||||||
return@let suspendCoroutine<String> { continuation ->
|
return@let suspendCoroutine<String> { continuation ->
|
||||||
context.downloadServer.ensureServerStarted {
|
context.httpServer.ensureServerStarted {
|
||||||
val file = Paths.get(uri.path).toFile()
|
val file = Paths.get(uri.path).toFile()
|
||||||
val url = putDownloadableContent(file.inputStream(), file.length())
|
val url = putDownloadableContent(file.inputStream(), file.length())
|
||||||
continuation.resumeWith(Result.success(url))
|
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
|
* 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
|
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.Feature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.hook.HookStage
|
import me.rhunk.snapenhance.hook.HookStage
|
||||||
@ -20,20 +20,10 @@ class DisableMetrics : Feature("DisableMetrics", loadParams = FeatureLoadParams.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Hooker.hook(context.classCache.networkApi, "submit", HookStage.BEFORE,
|
context.event.subscribe(NetworkApiRequestEvent::class, { disableMetrics }) { param ->
|
||||||
{ disableMetrics }) { param ->
|
val url = param.url
|
||||||
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")
|
|
||||||
}*/
|
|
||||||
if (url.contains("app-analytics") || url.endsWith("v1/metrics")) {
|
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
|
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.Feature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.hook.HookStage
|
import me.rhunk.snapenhance.util.download.HttpServer
|
||||||
import me.rhunk.snapenhance.hook.Hooker
|
import kotlin.coroutines.suspendCoroutine
|
||||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
|
||||||
import me.rhunk.snapenhance.util.ktx.setObjectField
|
|
||||||
|
|
||||||
class AnonymousStoryViewing : Feature("Anonymous Story Viewing", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
class AnonymousStoryViewing : Feature("Anonymous Story Viewing", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||||
override fun asyncOnActivityCreate() {
|
override fun asyncOnActivityCreate() {
|
||||||
val anonymousStoryViewProperty by context.config.messaging.anonymousStoryViewing
|
val anonymousStoryViewProperty by context.config.messaging.anonymousStoryViewing
|
||||||
Hooker.hook(context.classCache.networkApi,"submit", HookStage.BEFORE, { anonymousStoryViewProperty }) {
|
val httpServer = HttpServer()
|
||||||
val httpRequest: Any = it.arg(0)
|
|
||||||
val url = httpRequest.getObjectField("mUrl") as String
|
context.event.subscribe(NetworkApiRequestEvent::class, { anonymousStoryViewProperty }) { event ->
|
||||||
if (url.endsWith("readreceipt-indexer/batchuploadreadreceipts")) {
|
if (!event.url.endsWith("readreceipt-indexer/batchuploadreadreceipts")) return@subscribe
|
||||||
httpRequest.setObjectField("mUrl", "http://127.0.0.1")
|
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.annotation.SuppressLint
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import me.rhunk.snapenhance.Constants
|
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.Feature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.hook.HookStage
|
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 ngsIconId = context.androidContext.resources.getIdentifier(ngsIconName, "id", Constants.SNAPCHAT_PACKAGE_NAME)
|
||||||
val unhooks = mutableListOf<() -> Unit>()
|
|
||||||
|
|
||||||
ViewGroup::class.java.getMethod(
|
lateinit var unhook: () -> Unit
|
||||||
"addView",
|
|
||||||
View::class.java,
|
context.event.subscribe(AddViewEvent::class) { event ->
|
||||||
Int::class.javaPrimitiveType,
|
if (event.parent !is LinearLayout) return@subscribe
|
||||||
ViewGroup.LayoutParams::class.java
|
with(event.view) {
|
||||||
).hook(HookStage.AFTER) { param ->
|
|
||||||
if (param.thisObject<ViewGroup>() !is LinearLayout) return@hook
|
|
||||||
with(param.arg<View>(0)) {
|
|
||||||
if (id == ngsIconId) {
|
if (id == ngsIconId) {
|
||||||
ngsIcon = this
|
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.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import me.rhunk.snapenhance.Constants
|
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.Feature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.hook.HookAdapter
|
|
||||||
import me.rhunk.snapenhance.hook.HookStage
|
import me.rhunk.snapenhance.hook.HookStage
|
||||||
import me.rhunk.snapenhance.hook.Hooker
|
import me.rhunk.snapenhance.hook.Hooker
|
||||||
import me.rhunk.snapenhance.hook.hook
|
import me.rhunk.snapenhance.hook.hook
|
||||||
@ -25,12 +25,12 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hideStorySection(param: HookAdapter) {
|
private fun hideStorySection(event: AddViewEvent) {
|
||||||
val parent = param.thisObject() as ViewGroup
|
val parent = event.parent
|
||||||
parent.visibility = View.GONE
|
parent.visibility = View.GONE
|
||||||
val marginLayoutParams = parent.layoutParams as ViewGroup.MarginLayoutParams
|
val marginLayoutParams = parent.layoutParams as ViewGroup.MarginLayoutParams
|
||||||
marginLayoutParams.setMargins(-99999, -99999, -99999, -99999)
|
marginLayoutParams.setMargins(-99999, -99999, -99999, -99999)
|
||||||
param.setResult(null)
|
event.canceled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("DiscouragedApi", "InternalInsetResource")
|
@SuppressLint("DiscouragedApi", "InternalInsetResource")
|
||||||
@ -69,33 +69,29 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewGroup::class.java.getMethod(
|
|
||||||
"addView",
|
context.event.subscribe(AddViewEvent::class) { event ->
|
||||||
View::class.java,
|
val viewId = event.view.id
|
||||||
Int::class.javaPrimitiveType,
|
val view = event.view
|
||||||
ViewGroup.LayoutParams::class.java
|
|
||||||
).hook(HookStage.BEFORE) { param ->
|
|
||||||
val view: View = param.arg(0)
|
|
||||||
val viewId = view.id
|
|
||||||
|
|
||||||
if (hideStorySections.contains("hide_for_you")) {
|
if (hideStorySections.contains("hide_for_you")) {
|
||||||
if (viewId == getIdentifier("df_large_story", "id") ||
|
if (viewId == getIdentifier("df_large_story", "id") ||
|
||||||
viewId == getIdentifier("df_promoted_story", "id")) {
|
viewId == getIdentifier("df_promoted_story", "id")) {
|
||||||
hideStorySection(param)
|
hideStorySection(event)
|
||||||
return@hook
|
return@subscribe
|
||||||
}
|
}
|
||||||
if (viewId == getIdentifier("stories_load_progress_layout", "id")) {
|
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")) {
|
if (hideStorySections.contains("hide_friends") && viewId == getIdentifier("friend_card_frame", "id")) {
|
||||||
hideStorySection(param)
|
hideStorySection(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
//mappings?
|
//mappings?
|
||||||
if (hideStorySections.contains("hide_friend_suggestions") && view.javaClass.superclass?.name?.endsWith("StackDrawLayout") == true) {
|
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 &&
|
if (layoutParams.width == -1 &&
|
||||||
layoutParams.height == -2 &&
|
layoutParams.height == -2 &&
|
||||||
view.javaClass.let { clazz ->
|
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 }
|
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"))
|
if (hideStorySections.contains("hide_following") && (viewId == getIdentifier("df_small_story", "id"))
|
||||||
) {
|
) {
|
||||||
hideStorySection(param)
|
hideStorySection(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockAds && viewId == getIdentifier("df_promoted_story", "id")) {
|
if (blockAds && viewId == getIdentifier("df_promoted_story", "id")) {
|
||||||
hideStorySection(param)
|
hideStorySection(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isImmersiveCamera) {
|
if (isImmersiveCamera) {
|
||||||
@ -145,15 +141,15 @@ class UITweaks : Feature("UITweaks", loadParams = FeatureLoadParams.ACTIVITY_CRE
|
|||||||
view.visibility = View.GONE
|
view.visibility = View.GONE
|
||||||
}
|
}
|
||||||
if (getIdentifier("chat_input_bar_sharing_drawer_button", "id") == viewId && hiddenElements.contains("hide_live_location_share_button")) {
|
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 (viewId == callButton1 || viewId == callButton2) {
|
||||||
if (!hiddenElements.contains("hide_call_buttons")) return@hook
|
if (!hiddenElements.contains("hide_call_buttons")) return@subscribe
|
||||||
if (view.visibility == View.GONE) return@hook
|
if (view.visibility == View.GONE) return@subscribe
|
||||||
}
|
}
|
||||||
if (viewId == callButtonsStub) {
|
if (viewId == callButtonsStub) {
|
||||||
if (!hiddenElements.contains("hide_call_buttons")) return@hook
|
if (!hiddenElements.contains("hide_call_buttons")) return@subscribe
|
||||||
param.setResult(null)
|
event.canceled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,8 +111,8 @@ object Hooker {
|
|||||||
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
|
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
|
||||||
hook(clazz, methodName, stage) { param->
|
hook(clazz, methodName, stage) { param->
|
||||||
if (param.nullableThisObject<Any>() != instance) return@hook
|
if (param.nullableThisObject<Any>() != instance) return@hook
|
||||||
|
unhooks.forEach { it.unhook() }
|
||||||
hookConsumer(param)
|
hookConsumer(param)
|
||||||
unhooks.forEach{ it.unhook() }
|
|
||||||
}.also { unhooks.addAll(it) }
|
}.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.ConfigurationOverride
|
||||||
import me.rhunk.snapenhance.features.impl.Messaging
|
import me.rhunk.snapenhance.features.impl.Messaging
|
||||||
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
|
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.AmoledDarkMode
|
||||||
import me.rhunk.snapenhance.features.impl.experiments.AppPasscode
|
import me.rhunk.snapenhance.features.impl.experiments.AppPasscode
|
||||||
import me.rhunk.snapenhance.features.impl.experiments.DeviceSpooferHook
|
import me.rhunk.snapenhance.features.impl.experiments.DeviceSpooferHook
|
||||||
@ -91,6 +92,7 @@ class FeatureManager(private val context: ModContext) : Manager {
|
|||||||
register(StartupPageOverride::class)
|
register(StartupPageOverride::class)
|
||||||
register(GooglePlayServicesDialogs::class)
|
register(GooglePlayServicesDialogs::class)
|
||||||
register(NoFriendScoreDelay::class)
|
register(NoFriendScoreDelay::class)
|
||||||
|
register(ProfilePictureDownloader::class)
|
||||||
|
|
||||||
initializeFeatures()
|
initializeFeatures()
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,10 @@ import android.view.ViewGroup
|
|||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import me.rhunk.snapenhance.Constants
|
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.Feature
|
||||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.features.impl.Messaging
|
import me.rhunk.snapenhance.features.impl.Messaging
|
||||||
import me.rhunk.snapenhance.hook.HookStage
|
|
||||||
import me.rhunk.snapenhance.hook.Hooker
|
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
|
|
||||||
@SuppressLint("DiscouragedApi")
|
@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 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 feedNewChat = context.resources.getIdentifier("feed_new_chat", "id", Constants.SNAPCHAT_PACKAGE_NAME)
|
||||||
|
|
||||||
val addViewMethod = ViewGroup::class.java.getMethod(
|
context.event.subscribe(AddViewEvent::class) { event ->
|
||||||
"addView",
|
|
||||||
View::class.java,
|
|
||||||
Int::class.javaPrimitiveType,
|
|
||||||
ViewGroup.LayoutParams::class.java
|
|
||||||
)
|
|
||||||
|
|
||||||
Hooker.hook(addViewMethod, HookStage.BEFORE) { param ->
|
|
||||||
val viewGroup: ViewGroup = param.thisObject()
|
|
||||||
val originalAddView: (View) -> Unit = {
|
val originalAddView: (View) -> Unit = {
|
||||||
param.invokeOriginal(arrayOf(it, -1,
|
event.adapter.invokeOriginal(arrayOf(it, -1,
|
||||||
FrameLayout.LayoutParams(
|
FrameLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
@ -60,19 +51,20 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val childView: View = param.arg(0)
|
val viewGroup: ViewGroup = event.parent
|
||||||
operaContextActionMenu.inject(viewGroup, childView)
|
val childView: View = event.view
|
||||||
|
operaContextActionMenu.inject(event.parent, childView)
|
||||||
|
|
||||||
if (viewGroup.id == componentsHolder && childView.id == feedNewChat) {
|
if (event.parent.id == componentsHolder && childView.id == feedNewChat) {
|
||||||
settingsGearInjector.inject(viewGroup, childView)
|
settingsGearInjector.inject(event.parent, childView)
|
||||||
return@hook
|
return@subscribe
|
||||||
}
|
}
|
||||||
|
|
||||||
//download in chat snaps and notes from the chat action menu
|
//download in chat snaps and notes from the chat action menu
|
||||||
if (viewGroup.javaClass.name.endsWith("ActionMenuChatItemContainer")) {
|
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)
|
chatActionMenu.inject(viewGroup)
|
||||||
return@hook
|
return@subscribe
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: inject in group chat menus
|
//TODO: inject in group chat menus
|
||||||
@ -101,7 +93,7 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
|
|||||||
viewList.reversed().forEach { injectedLayout.addView(it, 0) }
|
viewList.reversed().forEach { injectedLayout.addView(it, 0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
param.setArg(0, injectedLayout)
|
event.view = injectedLayout
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewGroup is LinearLayout && viewGroup.id == actionSheetItemsContainerLayoutId) {
|
if (viewGroup is LinearLayout && viewGroup.id == actionSheetItemsContainerLayoutId) {
|
||||||
@ -125,12 +117,12 @@ class MenuViewInjector : Feature("MenuViewInjector", loadParams = FeatureLoadPar
|
|||||||
//context.config.writeConfig()
|
//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
|
//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)
|
friendFeedInfoMenu.inject(viewGroup, originalAddView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,10 @@ import java.util.StringTokenizer
|
|||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
class DownloadServer(
|
class HttpServer(
|
||||||
private val timeout: Int = 10000
|
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 val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||||
private var timeoutJob: Job? = null
|
private var timeoutJob: Job? = null
|
||||||
@ -30,16 +30,16 @@ class DownloadServer(
|
|||||||
private val cachedData = ConcurrentHashMap<String, Pair<InputStream, Long>>()
|
private val cachedData = ConcurrentHashMap<String, Pair<InputStream, Long>>()
|
||||||
private var serverSocket: ServerSocket? = null
|
private var serverSocket: ServerSocket? = null
|
||||||
|
|
||||||
fun ensureServerStarted(callback: DownloadServer.() -> Unit) {
|
fun ensureServerStarted(callback: HttpServer.() -> Unit) {
|
||||||
if (serverSocket != null && !serverSocket!!.isClosed) {
|
if (serverSocket != null && !serverSocket!!.isClosed) {
|
||||||
callback(this)
|
callback(this)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
Logger.debug("starting download server on port $port")
|
Logger.debug("starting http server on port $port")
|
||||||
serverSocket = ServerSocket(port)
|
serverSocket = ServerSocket(port)
|
||||||
callback(this@DownloadServer)
|
callback(this@HttpServer)
|
||||||
while (!serverSocket!!.isClosed) {
|
while (!serverSocket!!.isClosed) {
|
||||||
try {
|
try {
|
||||||
val socket = serverSocket!!.accept()
|
val socket = serverSocket!!.accept()
|
||||||
@ -48,7 +48,7 @@ class DownloadServer(
|
|||||||
handleRequest(socket)
|
handleRequest(socket)
|
||||||
timeoutJob = launch {
|
timeoutJob = launch {
|
||||||
delay(timeout.toLong())
|
delay(timeout.toLong())
|
||||||
Logger.debug("download server closed due to timeout")
|
Logger.debug("http server closed due to timeout")
|
||||||
runCatching {
|
runCatching {
|
||||||
socketJob?.cancel()
|
socketJob?.cancel()
|
||||||
socket.close()
|
socket.close()
|
||||||
@ -59,7 +59,7 @@ class DownloadServer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: SocketException) {
|
} catch (e: SocketException) {
|
||||||
Logger.debug("download server timed out")
|
Logger.debug("http server timed out")
|
||||||
break;
|
break;
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Logger.error("failed to handle request", e)
|
Logger.error("failed to handle request", e)
|
||||||
@ -96,6 +96,8 @@ class DownloadServer(
|
|||||||
val parse = StringTokenizer(line)
|
val parse = StringTokenizer(line)
|
||||||
val method = parse.nextToken().uppercase(Locale.getDefault())
|
val method = parse.nextToken().uppercase(Locale.getDefault())
|
||||||
var fileRequested = parse.nextToken().lowercase(Locale.getDefault())
|
var fileRequested = parse.nextToken().lowercase(Locale.getDefault())
|
||||||
|
Logger.debug("[http-server:${port}] $method $fileRequested")
|
||||||
|
|
||||||
if (method != "GET") {
|
if (method != "GET") {
|
||||||
with(writer) {
|
with(writer) {
|
||||||
println("HTTP/1.1 501 Not Implemented")
|
println("HTTP/1.1 501 Not Implemented")
|
Loading…
x
Reference in New Issue
Block a user