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",
|
"refresh_mappings": "Refresh Mappings",
|
||||||
"open_map": "Choose location on map",
|
"open_map": "Choose location on map",
|
||||||
"check_for_updates": "Check for updates",
|
"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": {
|
"features": {
|
||||||
@ -835,16 +836,28 @@
|
|||||||
"snapchat_plus_state": {
|
"snapchat_plus_state": {
|
||||||
"subscribed": "Subscribed",
|
"subscribed": "Subscribed",
|
||||||
"not_subscribed": "Not Subscribed"
|
"not_subscribed": "Not Subscribed"
|
||||||
},
|
}
|
||||||
"friendship_link_type": {
|
},
|
||||||
"mutual": "Mutual",
|
|
||||||
"outgoing": "Outgoing",
|
"friendship_link_type": {
|
||||||
"blocked": "Blocked",
|
"mutual": "Mutual",
|
||||||
"deleted": "Deleted",
|
"outgoing": "Outgoing",
|
||||||
"following": "Following",
|
"blocked": "Blocked",
|
||||||
"suggested": "Suggested",
|
"deleted": "Deleted",
|
||||||
"incoming": "Incoming",
|
"following": "Following",
|
||||||
"incoming_follower": "Incoming Follower"
|
"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,
|
val isCritical: Boolean = false,
|
||||||
) {
|
) {
|
||||||
CLEAN_CACHE("clean_snapchat_cache", exitOnFinish = true),
|
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 {
|
companion object {
|
||||||
const val ACTION_PARAMETER = "se_action"
|
const val ACTION_PARAMETER = "se_action"
|
||||||
|
@ -22,8 +22,8 @@ data class FriendInfo(
|
|||||||
var friendmojiCategories: String? = null,
|
var friendmojiCategories: String? = null,
|
||||||
var snapScore: Int = 0,
|
var snapScore: Int = 0,
|
||||||
var birthday: Long = 0,
|
var birthday: Long = 0,
|
||||||
var addedTimestamp: Long = 0,
|
var addedTimestamp: Long = -1,
|
||||||
var reverseAddedTimestamp: Long = 0,
|
var reverseAddedTimestamp: Long = -1,
|
||||||
var serverDisplayName: String? = null,
|
var serverDisplayName: String? = null,
|
||||||
var streakLength: Int = 0,
|
var streakLength: Int = 0,
|
||||||
var streakExpirationTimestamp: Long = 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> {
|
fun getFeedEntries(limit: Int): List<FriendFeedEntry> {
|
||||||
return mainDb?.performOperation {
|
return mainDb?.performOperation {
|
||||||
safeRawQuery(
|
safeRawQuery(
|
||||||
|
@ -4,17 +4,23 @@ import me.rhunk.snapenhance.core.features.Feature
|
|||||||
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||||
import me.rhunk.snapenhance.core.util.hook.hook
|
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) {
|
class AddFriendSourceSpoof : Feature("AddFriendSourceSpoof", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||||
override fun asyncOnActivityCreate() {
|
var friendRelationshipChangerInstance: Any? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
override fun onActivityCreate() {
|
||||||
val friendRelationshipChangerMapping = context.mappings.getMappedMap("FriendRelationshipChanger")
|
val friendRelationshipChangerMapping = context.mappings.getMappedMap("FriendRelationshipChanger")
|
||||||
|
|
||||||
|
findClass(friendRelationshipChangerMapping["class"].toString()).hookConstructor(HookStage.AFTER) { param ->
|
||||||
|
friendRelationshipChangerInstance = param.thisObject()
|
||||||
|
}
|
||||||
|
|
||||||
findClass(friendRelationshipChangerMapping["class"].toString())
|
findClass(friendRelationshipChangerMapping["class"].toString())
|
||||||
.hook(friendRelationshipChangerMapping["addFriendMethod"].toString(), HookStage.BEFORE) { param ->
|
.hook(friendRelationshipChangerMapping["addFriendMethod"].toString(), HookStage.BEFORE) { param ->
|
||||||
val spoofedSource = context.config.experimental.addFriendSourceSpoof.getNullable() ?: return@hook
|
val spoofedSource = context.config.experimental.addFriendSourceSpoof.getNullable() ?: return@hook
|
||||||
|
|
||||||
context.log.verbose("addFriendMethod: ${param.args().toList()}", featureKey)
|
|
||||||
|
|
||||||
fun setEnum(index: Int, value: String) {
|
fun setEnum(index: Int, value: String) {
|
||||||
val enumData = param.arg<Any>(index)
|
val enumData = param.arg<Any>(index)
|
||||||
enumData::class.java.enumConstants.first { it.toString() == value }.let {
|
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 android.content.Intent
|
||||||
import me.rhunk.snapenhance.common.action.EnumAction
|
import me.rhunk.snapenhance.common.action.EnumAction
|
||||||
import me.rhunk.snapenhance.core.ModContext
|
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.CleanCache
|
||||||
import me.rhunk.snapenhance.core.action.impl.ExportChatMessages
|
import me.rhunk.snapenhance.core.action.impl.ExportChatMessages
|
||||||
import me.rhunk.snapenhance.core.manager.Manager
|
import me.rhunk.snapenhance.core.manager.Manager
|
||||||
@ -15,6 +16,7 @@ class ActionManager(
|
|||||||
mapOf(
|
mapOf(
|
||||||
EnumAction.CLEAN_CACHE to CleanCache::class,
|
EnumAction.CLEAN_CACHE to CleanCache::class,
|
||||||
EnumAction.EXPORT_CHAT_MESSAGES to ExportChatMessages::class,
|
EnumAction.EXPORT_CHAT_MESSAGES to ExportChatMessages::class,
|
||||||
|
EnumAction.BULK_REMOVE_FRIENDS to BulkRemoveFriends::class,
|
||||||
).map {
|
).map {
|
||||||
it.key to it.value.java.getConstructor().newInstance().apply {
|
it.key to it.value.java.getConstructor().newInstance().apply {
|
||||||
this.context = modContext
|
this.context = modContext
|
||||||
|
@ -94,7 +94,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
|||||||
"day" to profile.birthday.toInt().toString())
|
"day" to profile.birthday.toInt().toString())
|
||||||
},
|
},
|
||||||
translation["friendship"] to run {
|
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["add_source"] to context.database.getAddSource(profile.userId!!)?.takeIf { it.isNotEmpty() },
|
||||||
translation["snapchat_plus"] to run {
|
translation["snapchat_plus"] to run {
|
||||||
|
@ -18,9 +18,18 @@ class FriendRelationshipChangerMapper : AbstractClassMapper() {
|
|||||||
it.parameters[4].type == "Ljava/lang/String;"
|
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",
|
addMapping("FriendRelationshipChanger",
|
||||||
"class" to classDef.getClassName(),
|
"class" to classDef.getClassName(),
|
||||||
"addFriendMethod" to addFriendMethod.name
|
"addFriendMethod" to addFriendMethod.name,
|
||||||
|
"removeFriendMethod" to removeFriendMethod.name
|
||||||
)
|
)
|
||||||
return@mapper
|
return@mapper
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user