mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-16 14:17:09 +02:00
feat(action): bulk remove friends
This commit is contained in:
parent
368878abd7
commit
f66859b1fd
@ -115,7 +115,8 @@
|
||||
"refresh_mappings": "Refresh Mappings",
|
||||
"open_map": "Choose location on map",
|
||||
"check_for_updates": "Check for updates",
|
||||
"export_chat_messages": "Export Chat Messages"
|
||||
"export_chat_messages": "Export Chat Messages",
|
||||
"bulk_remove_friends": "Bulk Remove Friends"
|
||||
},
|
||||
|
||||
"features": {
|
||||
@ -835,7 +836,9 @@
|
||||
"snapchat_plus_state": {
|
||||
"subscribed": "Subscribed",
|
||||
"not_subscribed": "Not Subscribed"
|
||||
}
|
||||
},
|
||||
|
||||
"friendship_link_type": {
|
||||
"mutual": "Mutual",
|
||||
"outgoing": "Outgoing",
|
||||
@ -845,6 +848,16 @@
|
||||
"suggested": "Suggested",
|
||||
"incoming": "Incoming",
|
||||
"incoming_follower": "Incoming Follower"
|
||||
},
|
||||
|
||||
"bulk_remove_friends": {
|
||||
"title": "Bulk Remove Friend",
|
||||
"progress_status": "Removing friends {index} of {total}",
|
||||
"selection_dialog_title": "Select friends to remove",
|
||||
"selection_dialog_remove_button": "Remove Selection",
|
||||
"confirmation_dialog": {
|
||||
"title": "Are you sure?",
|
||||
"message": "This will remove all selected friends. This action cannot be undone."
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -8,7 +8,8 @@ enum class EnumAction(
|
||||
val isCritical: Boolean = false,
|
||||
) {
|
||||
CLEAN_CACHE("clean_snapchat_cache", exitOnFinish = true),
|
||||
EXPORT_CHAT_MESSAGES("export_chat_messages");
|
||||
EXPORT_CHAT_MESSAGES("export_chat_messages"),
|
||||
BULK_REMOVE_FRIENDS("bulk_remove_friends");
|
||||
|
||||
companion object {
|
||||
const val ACTION_PARAMETER = "se_action"
|
||||
|
@ -22,8 +22,8 @@ data class FriendInfo(
|
||||
var friendmojiCategories: String? = null,
|
||||
var snapScore: Int = 0,
|
||||
var birthday: Long = 0,
|
||||
var addedTimestamp: Long = 0,
|
||||
var reverseAddedTimestamp: Long = 0,
|
||||
var addedTimestamp: Long = -1,
|
||||
var reverseAddedTimestamp: Long = -1,
|
||||
var serverDisplayName: String? = null,
|
||||
var streakLength: Int = 0,
|
||||
var streakExpirationTimestamp: Long = 0,
|
||||
|
@ -0,0 +1,113 @@
|
||||
package me.rhunk.snapenhance.core.action.impl
|
||||
|
||||
import android.widget.ProgressBar
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.rhunk.snapenhance.common.data.FriendLinkType
|
||||
import me.rhunk.snapenhance.core.action.AbstractAction
|
||||
import me.rhunk.snapenhance.core.features.impl.experiments.AddFriendSourceSpoof
|
||||
import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
|
||||
|
||||
class BulkRemoveFriends : AbstractAction() {
|
||||
private val translation by lazy { context.translation.getCategory("bulk_remove_friends") }
|
||||
|
||||
private fun removeFriends(friendIds: List<String>) {
|
||||
var index = 0
|
||||
val dialog = ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity)
|
||||
.setTitle("...")
|
||||
.setView(ProgressBar(context.mainActivity))
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
|
||||
context.coroutineScope.launch {
|
||||
friendIds.forEach {
|
||||
removeFriend(it)
|
||||
index++
|
||||
withContext(Dispatchers.Main) {
|
||||
dialog.setTitle(
|
||||
translation.format("progress_status", "index" to index.toString(), "total" to friendIds.size.toString())
|
||||
)
|
||||
}
|
||||
delay(500)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun confirmationDialog(onConfirm: () -> Unit) {
|
||||
ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity)
|
||||
.setTitle(translation["confirmation_dialog.title"])
|
||||
.setMessage(translation["confirmation_dialog.message"])
|
||||
.setPositiveButton(context.translation["button.positive"]) { _, _ ->
|
||||
onConfirm()
|
||||
}
|
||||
.setNegativeButton(context.translation["button.negative"]) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
val userIdBlacklist = arrayOf(
|
||||
context.database.myUserId,
|
||||
"b42f1f70-5a8b-4c53-8c25-34e7ec9e6781", // myai
|
||||
"84ee8839-3911-492d-8b94-72dd80f3713a", // teamsnapchat
|
||||
)
|
||||
context.coroutineScope.launch(Dispatchers.Main) {
|
||||
val friends = context.database.getAllFriends().filter {
|
||||
it.userId !in userIdBlacklist &&
|
||||
it.addedTimestamp != -1L &&
|
||||
it.friendLinkType == FriendLinkType.MUTUAL.value ||
|
||||
it.friendLinkType == FriendLinkType.OUTGOING.value
|
||||
}.sortedByDescending {
|
||||
it.friendLinkType == FriendLinkType.OUTGOING.value
|
||||
}
|
||||
|
||||
val selectedFriends = mutableListOf<String>()
|
||||
|
||||
ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity)
|
||||
.setTitle(translation["selection_dialog_title"])
|
||||
.setMultiChoiceItems(friends.map { friend ->
|
||||
(friend.displayName?.let {
|
||||
"$it (${friend.mutableUsername})"
|
||||
} ?: friend.mutableUsername) +
|
||||
": ${context.translation["friendship_link_type.${FriendLinkType.fromValue(friend.friendLinkType).shortName}"]}"
|
||||
}.toTypedArray(), null) { _, which, isChecked ->
|
||||
if (isChecked) {
|
||||
selectedFriends.add(friends[which].userId!!)
|
||||
} else {
|
||||
selectedFriends.remove(friends[which].userId)
|
||||
}
|
||||
}
|
||||
.setPositiveButton(translation["selection_dialog_remove_button"]) { _, _ ->
|
||||
confirmationDialog {
|
||||
removeFriends(selectedFriends)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(context.translation["button.cancel"]) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeFriend(userId: String) {
|
||||
val friendRelationshipChangerMapping = context.mappings.getMappedMap("FriendRelationshipChanger")
|
||||
val friendRelationshipChangerInstance = context.feature(AddFriendSourceSpoof::class).friendRelationshipChangerInstance!!
|
||||
|
||||
val removeFriendMethod = friendRelationshipChangerInstance::class.java.methods.first {
|
||||
it.name == friendRelationshipChangerMapping["removeFriendMethod"].toString()
|
||||
}
|
||||
|
||||
val completable = removeFriendMethod.invoke(friendRelationshipChangerInstance,
|
||||
userId, // userId
|
||||
removeFriendMethod.parameterTypes[1].enumConstants.first { it.toString() == "DELETED_BY_MY_FRIENDS" }, // source
|
||||
null, // unknown
|
||||
null, // unknown
|
||||
null // InteractionPlacementInfo
|
||||
)!!
|
||||
completable::class.java.methods.first {
|
||||
it.name == "subscribe" && it.parameterTypes.isEmpty()
|
||||
}.invoke(completable)
|
||||
}
|
||||
}
|
@ -187,6 +187,25 @@ class DatabaseAccess(
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllFriends(): List<FriendInfo> {
|
||||
return mainDb?.performOperation {
|
||||
safeRawQuery(
|
||||
"SELECT * FROM FriendWithUsername",
|
||||
null
|
||||
)?.use { query ->
|
||||
val list = mutableListOf<FriendInfo>()
|
||||
while (query.moveToNext()) {
|
||||
val friendInfo = FriendInfo()
|
||||
try {
|
||||
friendInfo.write(query)
|
||||
} catch (_: Throwable) {}
|
||||
list.add(friendInfo)
|
||||
}
|
||||
list
|
||||
}
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
||||
fun getFeedEntries(limit: Int): List<FriendFeedEntry> {
|
||||
return mainDb?.performOperation {
|
||||
safeRawQuery(
|
||||
|
@ -4,17 +4,23 @@ import me.rhunk.snapenhance.core.features.Feature
|
||||
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||
import me.rhunk.snapenhance.core.util.hook.hook
|
||||
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
||||
|
||||
class AddFriendSourceSpoof : Feature("AddFriendSourceSpoof", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||
override fun asyncOnActivityCreate() {
|
||||
class AddFriendSourceSpoof : Feature("AddFriendSourceSpoof", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||
var friendRelationshipChangerInstance: Any? = null
|
||||
private set
|
||||
|
||||
override fun onActivityCreate() {
|
||||
val friendRelationshipChangerMapping = context.mappings.getMappedMap("FriendRelationshipChanger")
|
||||
|
||||
findClass(friendRelationshipChangerMapping["class"].toString()).hookConstructor(HookStage.AFTER) { param ->
|
||||
friendRelationshipChangerInstance = param.thisObject()
|
||||
}
|
||||
|
||||
findClass(friendRelationshipChangerMapping["class"].toString())
|
||||
.hook(friendRelationshipChangerMapping["addFriendMethod"].toString(), HookStage.BEFORE) { param ->
|
||||
val spoofedSource = context.config.experimental.addFriendSourceSpoof.getNullable() ?: return@hook
|
||||
|
||||
context.log.verbose("addFriendMethod: ${param.args().toList()}", featureKey)
|
||||
|
||||
fun setEnum(index: Int, value: String) {
|
||||
val enumData = param.arg<Any>(index)
|
||||
enumData::class.java.enumConstants.first { it.toString() == value }.let {
|
||||
|
@ -3,6 +3,7 @@ package me.rhunk.snapenhance.core.manager.impl
|
||||
import android.content.Intent
|
||||
import me.rhunk.snapenhance.common.action.EnumAction
|
||||
import me.rhunk.snapenhance.core.ModContext
|
||||
import me.rhunk.snapenhance.core.action.impl.BulkRemoveFriends
|
||||
import me.rhunk.snapenhance.core.action.impl.CleanCache
|
||||
import me.rhunk.snapenhance.core.action.impl.ExportChatMessages
|
||||
import me.rhunk.snapenhance.core.manager.Manager
|
||||
@ -15,6 +16,7 @@ class ActionManager(
|
||||
mapOf(
|
||||
EnumAction.CLEAN_CACHE to CleanCache::class,
|
||||
EnumAction.EXPORT_CHAT_MESSAGES to ExportChatMessages::class,
|
||||
EnumAction.BULK_REMOVE_FRIENDS to BulkRemoveFriends::class,
|
||||
).map {
|
||||
it.key to it.value.java.getConstructor().newInstance().apply {
|
||||
this.context = modContext
|
||||
|
@ -94,7 +94,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
||||
"day" to profile.birthday.toInt().toString())
|
||||
},
|
||||
translation["friendship"] to run {
|
||||
translation.getCategory("friendship_link_type")[FriendLinkType.fromValue(profile.friendLinkType).shortName]
|
||||
context.translation["friendship_link_type.${FriendLinkType.fromValue(profile.friendLinkType).shortName}"]
|
||||
},
|
||||
translation["add_source"] to context.database.getAddSource(profile.userId!!)?.takeIf { it.isNotEmpty() },
|
||||
translation["snapchat_plus"] to run {
|
||||
|
@ -18,9 +18,18 @@ class FriendRelationshipChangerMapper : AbstractClassMapper() {
|
||||
it.parameters[4].type == "Ljava/lang/String;"
|
||||
}
|
||||
|
||||
val removeFriendMethod = classDef.methods.first {
|
||||
it.parameterTypes.size == 5 &&
|
||||
it.parameterTypes[0] == "Ljava/lang/String;" &&
|
||||
getClass(it.parameterTypes[1])?.isEnum() == true &&
|
||||
it.parameterTypes[2] == "Ljava/lang/String;" &&
|
||||
it.parameterTypes[3] == "Ljava/lang/String;"
|
||||
}
|
||||
|
||||
addMapping("FriendRelationshipChanger",
|
||||
"class" to classDef.getClassName(),
|
||||
"addFriendMethod" to addFriendMethod.name
|
||||
"addFriendMethod" to addFriendMethod.name,
|
||||
"removeFriendMethod" to removeFriendMethod.name
|
||||
)
|
||||
return@mapper
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user