fix: NPE in highlighting messages for OGv2, deletion and moderation working

This commit is contained in:
jubb 2021-04-28 14:46:50 +10:00
parent 7f0962b3d4
commit 9d4a2d1505
3 changed files with 72 additions and 36 deletions

View File

@ -409,11 +409,11 @@ public class ConversationFragment extends Fragment
} }
menu.findItem(R.id.menu_context_copy_public_key).setVisible(selectedMessageCount == 1 && !areAllSentByUser); menu.findItem(R.id.menu_context_copy_public_key).setVisible(selectedMessageCount == 1 && !areAllSentByUser);
menu.findItem(R.id.menu_context_reply).setVisible(selectedMessageCount == 1); menu.findItem(R.id.menu_context_reply).setVisible(selectedMessageCount == 1);
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext()); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(requireContext());
boolean userCanModerate = boolean userCanModerate =
(isPublicChat && (isPublicChat &&
(OpenGroupAPI.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer()) ((publicChat != null && OpenGroupAPI.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer()))
|| OpenGroupAPIV2.isUserModerator(userHexEncodedPublicKey, openGroupChat.getRoom(), openGroupChat.getServer())) || (openGroupChat != null && OpenGroupAPIV2.isUserModerator(userHexEncodedPublicKey, openGroupChat.getRoom(), openGroupChat.getServer())))
); );
boolean isDeleteOptionVisible = !isPublicChat || (areAllSentByUser || userCanModerate); boolean isDeleteOptionVisible = !isPublicChat || (areAllSentByUser || userCanModerate);
// allow banning if moderating a public chat and only one user's messages are selected // allow banning if moderating a public chat and only one user's messages are selected
@ -515,6 +515,7 @@ public class ConversationFragment extends Fragment
builder.setCancelable(true); builder.setCancelable(true);
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId); PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
OpenGroupV2 openGroupChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getOpenGroupChat(threadId);
builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() { builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
@Override @Override
@ -525,7 +526,7 @@ public class ConversationFragment extends Fragment
{ {
@Override @Override
protected Void doInBackground(MessageRecord... messageRecords) { protected Void doInBackground(MessageRecord... messageRecords) {
if (publicChat != null) { if (publicChat != null || openGroupChat != null) {
ArrayList<Long> serverIDs = new ArrayList<>(); ArrayList<Long> serverIDs = new ArrayList<>();
ArrayList<Long> ignoredMessages = new ArrayList<>(); ArrayList<Long> ignoredMessages = new ArrayList<>();
ArrayList<Long> failedMessages = new ArrayList<>(); ArrayList<Long> failedMessages = new ArrayList<>();
@ -561,6 +562,28 @@ public class ConversationFragment extends Fragment
Log.w("Loki", "Couldn't delete message due to error: " + e.toString() + "."); Log.w("Loki", "Couldn't delete message due to error: " + e.toString() + ".");
return null; return null;
}); });
} else if (openGroupChat != null) {
for (Long serverId : serverIDs) {
OpenGroupAPIV2
.deleteMessage(serverId, openGroupChat.getRoom(), openGroupChat.getServer())
.success(l -> {
for (MessageRecord messageRecord : messageRecords) {
Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id);
if (serverID != null && serverID.equals(serverId)) {
if (messageRecord.isMms()) {
DatabaseFactory.getMmsDatabase(getContext()).delete(messageRecord.getId());
} else {
DatabaseFactory.getSmsDatabase(getContext()).deleteMessage(messageRecord.getId());
}
break;
}
}
return null;
}).fail(e->{
Log.e("Loki", "Couldn't delete message due to error",e);
return null;
});
}
} }
} else { } else {
for (MessageRecord messageRecord : messageRecords) { for (MessageRecord messageRecord : messageRecords) {
@ -597,7 +620,8 @@ public class ConversationFragment extends Fragment
builder.setTitle(R.string.ConversationFragment_ban_selected_user); builder.setTitle(R.string.ConversationFragment_ban_selected_user);
builder.setCancelable(true); builder.setCancelable(true);
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId); final PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
final OpenGroupV2 openGroupChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getOpenGroupChat(threadId);
builder.setPositiveButton(R.string.ban, (dialog, which) -> { builder.setPositiveButton(R.string.ban, (dialog, which) -> {
ConversationAdapter chatAdapter = getListAdapter(); ConversationAdapter chatAdapter = getListAdapter();
@ -616,7 +640,17 @@ public class ConversationFragment extends Fragment
Log.d("Loki", "User banned"); Log.d("Loki", "User banned");
return Unit.INSTANCE; return Unit.INSTANCE;
}).fail(e -> { }).fail(e -> {
Log.d("Loki", "Couldn't ban user due to error: " + e.toString() + "."); Log.e("Loki", "Couldn't ban user due to error",e);
return null;
});
} else if (openGroupChat != null) {
OpenGroupAPIV2
.ban(userPublicKey, openGroupChat.getRoom(), openGroupChat.getServer())
.success(l -> {
Log.d("Loki", "User banned");
return Unit.INSTANCE;
}).fail(e -> {
Log.e("Loki", "Failed to ban user",e);
return null; return null;
}); });
} else { } else {

View File

@ -580,7 +580,9 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
val mmsDb = DatabaseFactory.getMmsDatabase(context) val mmsDb = DatabaseFactory.getMmsDatabase(context)
val cursor = mmsDb.getMessage(mmsId) val cursor = mmsDb.getMessage(mmsId)
val reader = mmsDb.readerFor(cursor) val reader = mmsDb.readerFor(cursor)
return reader.next.threadId val threadId = reader.next.threadId
cursor.close()
return threadId
} }
override fun getSessionRequestSentTimestamp(publicKey: String): Long? { override fun getSessionRequestSentTimestamp(publicKey: String): Long? {

View File

@ -1,5 +1,7 @@
package org.session.libsession.messaging.opengroups package org.session.libsession.messaging.opengroups
import com.fasterxml.jackson.databind.PropertyNamingStrategy
import com.fasterxml.jackson.databind.annotation.JsonNaming
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
@ -36,8 +38,6 @@ object OpenGroupAPIV2 {
const val DEFAULT_SERVER = "https://sog.ibolpap.finance" const val DEFAULT_SERVER = "https://sog.ibolpap.finance"
private const val DEFAULT_SERVER_PUBLIC_KEY = "b464aa186530c97d6bcf663a3a3b7465a5f782beaa67c83bee99468824b4aa10" private const val DEFAULT_SERVER_PUBLIC_KEY = "b464aa186530c97d6bcf663a3a3b7465a5f782beaa67c83bee99468824b4aa10"
// https://sog.ibolpap.finance/main?public_key=b464aa186530c97d6bcf663a3a3b7465a5f782beaa67c83bee99468824b4aa10
val defaultRooms = MutableSharedFlow<List<DefaultGroup>>(replay = 1) val defaultRooms = MutableSharedFlow<List<DefaultGroup>>(replay = 1)
private val sharedContext = Kovenant.createContext() private val sharedContext = Kovenant.createContext()
@ -64,6 +64,13 @@ object OpenGroupAPIV2 {
val imageID: String? val imageID: String?
) )
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy::class)
data class CompactPollRequest(val roomId: String,
val authToken: String,
val fromDeletionServerId: Long?,
val fromMessageServerId: Long?
)
data class CompactPollResult(val messages: List<OpenGroupMessageV2>, data class CompactPollResult(val messages: List<OpenGroupMessageV2>,
val deletions: List<Long>, val deletions: List<Long>,
val moderators: List<String> val moderators: List<String>
@ -83,7 +90,9 @@ object OpenGroupAPIV2 {
val useOnionRouting: Boolean = true val useOnionRouting: Boolean = true
) )
private fun createBody(parameters: Any): RequestBody { private fun createBody(parameters: Any?): RequestBody? {
if (parameters == null) return null
val parametersAsJSON = JsonUtil.toJson(parameters) val parametersAsJSON = JsonUtil.toJson(parameters)
return RequestBody.create(MediaType.get("application/json"), parametersAsJSON) return RequestBody.create(MediaType.get("application/json"), parametersAsJSON)
} }
@ -111,9 +120,9 @@ object OpenGroupAPIV2 {
} }
when (request.verb) { when (request.verb) {
GET -> requestBuilder.get() GET -> requestBuilder.get()
PUT -> requestBuilder.put(createBody(request.parameters!!)) PUT -> requestBuilder.put(createBody(request.parameters))
POST -> requestBuilder.post(createBody(request.parameters!!)) POST -> requestBuilder.post(createBody(request.parameters))
DELETE -> requestBuilder.delete(createBody(request.parameters!!)) DELETE -> requestBuilder.delete(createBody(request.parameters))
} }
if (!request.room.isNullOrEmpty()) { if (!request.room.isNullOrEmpty()) {
@ -145,21 +154,6 @@ object OpenGroupAPIV2 {
} }
} }
fun downloadOpenGroupProfilePicture(imageUrl: String): ByteArray? {
Log.d("Loki", "Downloading open group profile picture from \"$imageUrl\".")
val outputStream = ByteArrayOutputStream()
try {
DownloadUtilities.downloadFile(outputStream, imageUrl, FileServerAPI.maxFileSize, null)
Log.d("Loki", "Open group profile picture was successfully loaded from \"$imageUrl\"")
return outputStream.toByteArray()
} catch (e: Exception) {
Log.d("Loki", "Failed to download open group profile picture from \"$imageUrl\" due to error: $e.")
return null
} finally {
outputStream.close()
}
}
fun downloadOpenGroupProfilePicture(roomID: String, server: String): Promise<ByteArray, Exception> { fun downloadOpenGroupProfilePicture(roomID: String, server: String): Promise<ByteArray, Exception> {
val request = Request(verb = GET, room = roomID, server = server, endpoint = "rooms/$roomID/image", isAuthRequired = false) val request = Request(verb = GET, room = roomID, server = server, endpoint = "rooms/$roomID/image", isAuthRequired = false)
return send(request).map(sharedContext) { json -> return send(request).map(sharedContext) { json ->
@ -291,8 +285,9 @@ object OpenGroupAPIV2 {
// endregion // endregion
// region Message Deletion // region Message Deletion
@JvmStatic
fun deleteMessage(serverID: Long, room: String, server: String): Promise<Unit, Exception> { fun deleteMessage(serverID: Long, room: String, server: String): Promise<Unit, Exception> {
val request = Request(verb = DELETE, room = room, server = server, endpoint = "message/$serverID") val request = Request(verb = DELETE, room = room, server = server, endpoint = "messages/$serverID")
return send(request).map(sharedContext) { return send(request).map(sharedContext) {
Log.d("Loki", "Deleted server message") Log.d("Loki", "Deleted server message")
} }
@ -306,9 +301,9 @@ object OpenGroupAPIV2 {
} }
val request = Request(verb = GET, room = room, server = server, endpoint = "deleted_messages", queryParameters = queryParameters) val request = Request(verb = GET, room = room, server = server, endpoint = "deleted_messages", queryParameters = queryParameters)
return send(request).map(sharedContext) { json -> return send(request).map(sharedContext) { json ->
@Suppress("UNCHECKED_CAST") val serverIDs = json["ids"] as? List<Long> @Suppress("UNCHECKED_CAST") val serverIDs = (json["ids"] as? List<Int>)?.map { it.toLong() }
?: throw Error.PARSING_FAILED ?: throw Error.PARSING_FAILED
val lastMessageServerId = storage.getLastMessageServerId(room, server) ?: 0 val lastMessageServerId = storage.getLastDeletionServerId(room, server) ?: 0
val serverID = serverIDs.maxOrNull() ?: 0 val serverID = serverIDs.maxOrNull() ?: 0
if (serverID > lastMessageServerId) { if (serverID > lastMessageServerId) {
storage.setLastDeletionServerId(room, server, serverID) storage.setLastDeletionServerId(room, server, serverID)
@ -330,6 +325,7 @@ object OpenGroupAPIV2 {
} }
} }
@JvmStatic
fun ban(publicKey: String, room: String, server: String): Promise<Unit, Exception> { fun ban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
val parameters = mapOf("public_key" to publicKey) val parameters = mapOf("public_key" to publicKey)
val request = Request(verb = POST, room = room, server = server, endpoint = "block_list", parameters = parameters) val request = Request(verb = POST, room = room, server = server, endpoint = "block_list", parameters = parameters)
@ -351,8 +347,11 @@ object OpenGroupAPIV2 {
// endregion // endregion
// region General // region General
// fun getCompactPoll(): Promise<CompactPollResult, Exception> { // fun getCompactPoll(rooms: List<String>, server: String): Promise<Map<String,CompactPollResult>, Exception> {
// val request = Request() // val request = Request(verb = POST, room = null, server = server, endpoint = "compact_poll", isAuthRequired = false)
//
// // build a request for all rooms
//
// } // }
fun getDefaultRoomsIfNeeded(): Promise<List<DefaultGroup>, Exception> { fun getDefaultRoomsIfNeeded(): Promise<List<DefaultGroup>, Exception> {
@ -362,8 +361,9 @@ object OpenGroupAPIV2 {
val earlyGroups = groups.map { group -> val earlyGroups = groups.map { group ->
DefaultGroup(group.id, group.name, null) DefaultGroup(group.id, group.name, null)
} }
defaultRooms.replayCache.firstOrNull()?.let { groups -> // see if we have any cached rooms, and if they already have images, don't overwrite with early non-image results
if (groups.none { it.image?.isNotEmpty() == true}) { defaultRooms.replayCache.firstOrNull()?.let { replayed ->
if (replayed.none { it.image?.isNotEmpty() == true}) {
defaultRooms.tryEmit(earlyGroups) defaultRooms.tryEmit(earlyGroups)
} }
} }