From 270c2af5c12f73b68911444fa8e9d52fb7135174 Mon Sep 17 00:00:00 2001 From: Vologhat <47892605+Vologhat@users.noreply.github.com> Date: Sat, 10 Dec 2022 22:41:31 +0300 Subject: [PATCH] small messages downloader refactoring --- .../lite/downloaders/MessagesDownloader.java | 620 ------------------ .../lite/downloaders/messages/Constants.java | 96 +++ .../DialogDownloaderFormatProvider.java | 17 + .../HtmlDialogDownloaderFormatProvider.java | 435 ++++++++++++ .../messages/MessagesDownloader.java | 162 +++++ .../downloaders/messages/items/MiniMsg.java | 42 ++ .../downloaders/messages/items/MiniUser.java | 20 + 7 files changed, 772 insertions(+), 620 deletions(-) delete mode 100644 app/src/main/java/ru/vtosters/lite/downloaders/MessagesDownloader.java create mode 100644 app/src/main/java/ru/vtosters/lite/downloaders/messages/Constants.java create mode 100644 app/src/main/java/ru/vtosters/lite/downloaders/messages/DialogDownloaderFormatProvider.java create mode 100644 app/src/main/java/ru/vtosters/lite/downloaders/messages/HtmlDialogDownloaderFormatProvider.java create mode 100644 app/src/main/java/ru/vtosters/lite/downloaders/messages/MessagesDownloader.java create mode 100644 app/src/main/java/ru/vtosters/lite/downloaders/messages/items/MiniMsg.java create mode 100644 app/src/main/java/ru/vtosters/lite/downloaders/messages/items/MiniUser.java diff --git a/app/src/main/java/ru/vtosters/lite/downloaders/MessagesDownloader.java b/app/src/main/java/ru/vtosters/lite/downloaders/MessagesDownloader.java deleted file mode 100644 index c2bfffc3f..000000000 --- a/app/src/main/java/ru/vtosters/lite/downloaders/MessagesDownloader.java +++ /dev/null @@ -1,620 +0,0 @@ -package ru.vtosters.lite.downloaders; - -import static android.widget.Toast.LENGTH_SHORT; -import static android.widget.Toast.makeText; -import static ru.vtosters.lite.encryption.EncryptProvider.decryptMessage; -import static ru.vtosters.lite.net.Request.makeRequest; -import static ru.vtosters.lite.proxy.ProxyUtils.getApi; -import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId; -import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken; -import static ru.vtosters.lite.utils.AndroidUtils.getArray; -import static ru.vtosters.lite.utils.AndroidUtils.getGlobalContext; -import static ru.vtosters.lite.utils.AndroidUtils.getResources; - -import android.util.SparseArray; - -import androidx.annotation.Nullable; - -import com.vtosters.lite.R; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; -import java.util.regex.Pattern; - -import ru.vtosters.lite.utils.AndroidUtils; -import ru.vtosters.lite.utils.LifecycleUtils; - -public class MessagesDownloader { - private static final String VK_DOMAIN = "vk.com"; - private static SimpleDateFormat sdformat; - private static SparseArray usersArray; - - public static String formatTime(long now) { - return sdformat.format(new Date(now)); - } - - public static String join(CharSequence delimiter, Iterable elements) { - Objects.requireNonNull(delimiter); - Objects.requireNonNull(elements); - - StringBuilder builder = new StringBuilder(); - - for (CharSequence cs : elements) { - builder.append(cs).append(delimiter); - } - - return builder.toString(); - } - - private static String getPollHtml(JSONObject poll) throws JSONException { - StringBuilder pollHtml = new StringBuilder( - "
" + - "

" + poll.optString("question") + "

" - ); - - var answers = poll.optJSONArray("answers"); - - for (int i = 0; i < answers.length(); i++) { - var jsonObject = answers.optJSONObject(i); - if (jsonObject != null) - pollHtml.append("

").append(jsonObject.optString("text")).append(" · ").append(jsonObject.optString("votes")).append(" (").append(jsonObject.optString("rate")).append("%)

"); - } - - pollHtml.append("

") - .append(AndroidUtils.getString(R.string.chat_export_poll_voted)) - .append(" ") - .append(poll.optString("votes")) - .append(" ") - .append(AndroidUtils.getString(R.string.chat_export_users_name)) - .append("

"); - - return pollHtml.append("
").toString(); - } - - private static String getVideoHtml(JSONObject files) throws JSONException { - if (files == null) return AndroidUtils.getString(R.string.chat_export_video_nolinks); - - Iterator keys = files.keys(); - StringBuilder videoHtml = new StringBuilder(); - - int qualityIndex = 0; - String[] quality = {"144", "240", "360", "480", "720", "1080", "1440", "2160"}; - - while (keys.hasNext()) { - String key = (String) keys.next(); - if (key.equals("mp4_" + quality[qualityIndex])) { - videoHtml.append("").append(quality[qualityIndex]).append("p").append(" "); - - if (qualityIndex == quality.length - 1) break; - ++qualityIndex; - } - } - - return videoHtml.toString(); - } - - public void downloadDialog(int peer_id, DialogDownloaderFormatProvider format, File out) throws Exception { - sdformat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss", getResources().getConfiguration().locale); - - FileOutputStream fos = new FileOutputStream(out); - - // fix TODO with conversation name (API method messages.getConversationsById) - fos.write(format.provideDocumentStart("TODO", formatTime(System.currentTimeMillis())).getBytes(StandardCharsets.UTF_8)); - - fetchDialogInfo(peer_id, 0, ((users, messages) -> { - try { - fos.write(format.provideHeader("TODO", formatTime(System.currentTimeMillis())).getBytes(StandardCharsets.UTF_8)); - for (MiniMsg msg : messages) { - fos.write(format.provideMessage(msg, users.get(msg.fromId)).getBytes(StandardCharsets.UTF_8)); - } - fos.write(format.provideDocumentEnd().getBytes(StandardCharsets.UTF_8)); - fos.close(); - LifecycleUtils.getCurrentActivity().runOnUiThread(() -> makeText(getGlobalContext(), AndroidUtils.getString(R.string.saved_as) + " " + out.getAbsolutePath(), LENGTH_SHORT).show()); - } catch (IOException | JSONException e) { - e.printStackTrace(); - } - })); - } - - public void fetchDialogInfo(int peer_id, int offset, FetchListener fl) { - makeRequest("https://" + getApi() + "/method/messages.getHistory?extended=1&v=5.140&offset=" + offset + "&count=200&peer_id=" + peer_id + "&access_token=" + getUserToken(), - response -> { - try { - JSONObject mainJson = new JSONObject(response); - JSONObject obj = mainJson.getJSONObject("response"); - - SparseArray users = new SparseArray<>(); - parseUsers(users, obj.optJSONArray("profiles"), false); - parseUsers(users, obj.optJSONArray("groups"), true); - - List msgs = parseMessages(obj.getJSONArray("items")); - - usersArray = users; - - fl.onReceive(users, msgs); - } catch (JSONException e) { - e.printStackTrace(); - } - }); - } - - private List parseMessages(JSONArray jsonArray) throws JSONException { - ArrayList thing = new ArrayList<>(); - - for (int i = 0; i < jsonArray.length(); i++) { - MiniMsg msg = new MiniMsg(jsonArray.getJSONObject(i)); - thing.add(msg); - } - - return thing; - } - - private void parseUsers(SparseArray array, @Nullable JSONArray jsonArray, boolean groups) throws JSONException { - if (jsonArray == null) return; - - for (int i = 0; i < jsonArray.length(); i++) { - MiniUser user = new MiniUser(jsonArray.getJSONObject(i), groups); - array.put(groups ? -user.id : user.id, user); - } - } - - interface FetchListener { - void onReceive(SparseArray users, List messages); - } - - static class MiniUser { - public String firstName; - public String lastName; - public boolean isGroup; - int id; - String photo100; - - public MiniUser(JSONObject obj, boolean is_group) throws JSONException { - id = obj.getInt("id"); - firstName = obj.optString("first_name", obj.optString("name")); - lastName = obj.optString("last_name", ""); - photo100 = obj.getString("photo_100"); - isGroup = is_group; - } - } - - static class MiniMsg { - long date; - int fromId; - int id; // message id - String text; - List attachments; - List fwd_messages; - JSONObject reply_message; // one reply - JSONObject chat_action; - JSONObject geo; - - public MiniMsg(JSONObject obj) throws JSONException { - date = obj.getLong("date") * 1000; - fromId = obj.getInt("from_id"); - id = obj.getInt("id"); - text = decryptMessage(obj.getString("text"), obj.getInt("peer_id")); - reply_message = obj.optJSONObject("reply_message"); - chat_action = obj.optJSONObject("action"); - geo = obj.optJSONObject("geo"); - - attachments = new ArrayList<>(); - fwd_messages = new ArrayList<>(); - - JSONArray arr = obj.optJSONArray("attachments"); - if (arr != null) - for (int i = 0; i < arr.length(); i++) attachments.add(arr.getJSONObject(i)); - - JSONArray fwdArr = obj.optJSONArray("fwd_messages"); - if (fwdArr != null) - for (int i = 0; i < fwdArr.length(); i++) fwd_messages.add(fwdArr.getJSONObject(i)); - } - } - - // This class defines everything which is needed to create an output file - abstract static class DialogDownloaderFormatProvider { - // Internal, for JSON/HTML - abstract String provideDocumentStart(String dialogName, String date); - - abstract String provideDocumentEnd(); - - abstract String provideHeader(String dialogName, String date); - - abstract String provideMessage(MiniMsg message, MiniUser user) throws JSONException; - } - - public static class HtmlDialogDownloaderFormatProvider extends DialogDownloaderFormatProvider { - @Override - String provideDocumentStart(String dialogName, String date) { - String style = ".vtex-milk-msg {\n" + - " border-radius: 8px;\n" + - " padding: 24px;\n" + - " background: white;\n" + - " margin: 16px;\n" + - " box-shadow: 0 4px 8px 0 rgba(0,0,0,0.07);\n" + - " word-wrap: break-word;\n" + - "}\n" + - "\n" + - ".vtex-milk-msgcont br {\n" + - " display: none;\n" + - "}\n" + - "\n" + - ".vtex-milk-msg p {\n" + - " margin: 0;\n" + - "}\n" + - "\n" + - ".vtex-milk-msg.vtex-milk-msg-out {\n" + - " background: #ADD3FF;\n" + - "}\n" + - "\n" + - "p.msg-attaches {\n" + - " margin-top: 8px;\n" + - " font-style: italic;\n" + - "}\n" + - "p.chat-action {\n" + - " font-style: italic;\n" + - "}\n" + - "\n" + - ".vtex-milk-header {\n" + - " border-radius: 8px;\n" + - " padding: 24px;\n" + - " background: white;\n" + - " margin: 16px;\n" + - " box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);\n" + - "}\n" + - "\n" + - "h4.vtex-milk-header-txt {\n" + - " margin: 0;\n" + - "}\n" + - "\n" + - "h5.vtex-milk-header-sub {\n" + - " margin: 0;\n" + - " padding-top: 8px;\n" + - "}\n" + - "\n" + - "a.msg-attach-link {\n" + - " color: #5181B8;\n" + - " text-decoration: none;\n" + - "}\n" + - "\n" + - ".msg-from a {\n" + - " color: #5181B8;\n" + - " text-decoration: none;\n" + - "}\n" + - "\n" + - ".msg-from-date {\n" + - " color: rgba(0, 0, 0, 0.75);\n" + - "}\n" + - "\n" + - "body {\n" + - " font-family: Roboto, system-ui, sans-serif;\n" + - "}\n" + - "\n" + - ".msg-body {\n" + - " padding-top: 4px;\n" + - "}\n" + - ".msg-reply {\n" + - " border-left: 3px solid #7aa7f6;\n" + - " margin-top: 5px;\n" + - " padding-left: 3px;\n" + - "}\n\n" + - ".msg-reply p {\n" + - " padding-left: 5px;\n" + - "}\n\n" + - ".msg-reply p.reply-header {\n" + - " color: #5181B8;\n" + - "}\n\n" + - ".msg-reply p.reply-content {\n" + - " color: #000000;\n" + - "}\n" + - ".vtex-milk-msg.poll {\n" + - " width: 45%;\n" + - " background: linear-gradient(to bottom right, #707c8c, #4d5565);\n" + - " font-style: normal;" + - "}\n" + - ".vtex-milk-msg.poll p { color: white; }\n" + - ".vtex-milk-msg.poll hr { border-top: 1px; background: #ffffff; }\n" + - "\n" + - ""; - - return "" - + AndroidUtils.getString(R.string.chat_export_title) - + ""; - } - - @Override - String provideDocumentEnd() { - return ""; - } - - @Override - String provideHeader(String dialogName, String date) { - return "

VTLite

" + AndroidUtils.getString(R.string.chat_export_header) + " (" + date + ")
"; - } - - @Override - String provideMessage(MiniMsg message, MiniUser user) throws JSONException { - String isOut = (user.id == getUserId()) ? " vtex-milk-msg-out" : ""; - - String geoPosition = ""; - if (message.geo != null) { - var coordinates = message.geo.optJSONObject("coordinates"); - if (coordinates != null) { - var latitude = coordinates.optDouble("latitude"); - var longitude = coordinates.optDouble("longitude"); - var ll = longitude + "," + latitude; - geoPosition = "" + - "" + - ""; - } - } - - String chatAction = (message.chat_action != null) ? getHtmlForChatAction(message.chat_action) : ""; - String attaches = (message.attachments.size() > 0) ? "

" + AndroidUtils.getString(R.string.attachments) + ": " + getHtmlForAttach(message.attachments) + "

" : ""; - String reply = (message.reply_message != null) ? provideReply(message.reply_message) : ""; - String fwdMessages = (message.fwd_messages.size() > 0) ? provideForwardMessages(message.fwd_messages) : ""; - String userLink = (user.isGroup ? "club" : "id") + user.id; - - return "
" + - "

" + AndroidUtils.getString(R.string.from_lower) + " " + user.firstName + " " + user.lastName + " " + formatTime(message.date) + "

" + // TODO photo100 - "

" + mentionsReplace(message.text) + "

" + - geoPosition + - chatAction + - attaches + - reply + - fwdMessages + - "
"; - } - - private String provideReply(JSONObject replyMessage) { - var messageId = replyMessage.optInt("id"); - var userId = replyMessage.optInt("from_id"); - var peerId = replyMessage.optInt("peer_id"); - var text = mentionsReplace(decryptMessage(replyMessage.optString("text"), peerId)); - var fwdMessages = replyMessage.optJSONArray("fwd_messages"); - - var user = usersArray.get(userId); - - if (text.isEmpty()) { - if (fwdMessages != null) { - var length = fwdMessages.length(); - if (length > 0) text = length + " " + pluralMsg(length); - } else text = AndroidUtils.getString(R.string.attachment); // Вложение - } - - return "" + - "
" + - "

" + AndroidUtils.getString(R.string.vkim_msg_fwd_single) + "

" + - "

" + user.firstName + " " + user.lastName + ": " + text + "

" + - "
" + - "
"; - } - - private String provideForwardMessages(List fwd_messages) throws JSONException { - StringBuilder fwdmsgs = new StringBuilder("
"); - - for (JSONObject fwd_message : fwd_messages) { - - if (fwd_message == null) continue; - - List attaches = new ArrayList<>(); - - JSONArray arr = fwd_message.optJSONArray("attachments"); - if (arr != null) { - for (int i = 0; i < arr.length(); i++) { - try { - attaches.add(arr.getJSONObject(i)); - } catch (JSONException e) { - e.printStackTrace(); - } - } - } - - List fwdsMsg = new ArrayList<>(); - - JSONArray fwd_msgs = fwd_message.optJSONArray("fwd_messages"); - if (fwd_msgs != null) { - for (int i = 0; i < fwd_msgs.length(); i++) { - try { - fwdsMsg.add(fwd_msgs.getJSONObject(i)); - } catch (JSONException e) { - e.printStackTrace(); - } - } - } - - JSONObject reply_message = fwd_message.optJSONObject("reply_message"); - - var attachesHtml = (attaches.size() > 0) ? AndroidUtils.getString(R.string.attachments) + ": " + getHtmlForAttach(attaches) : ""; - var replyHtml = (reply_message != null) ? provideReply(reply_message) : ""; - var fwdsHtml = (fwdsMsg.size() > 0) ? provideForwardMessages(fwdsMsg) : ""; // recursion XD :> - - var userId = fwd_message.optInt("from_id"); - var peerId = fwd_message.optInt("peer_id"); - var text = mentionsReplace(decryptMessage(fwd_message.optString("text"), peerId)); - - var userLink = ((userId < 0) ? "club" : "id") + Math.abs(userId); - - fwdmsgs.append("

@").append(userLink).append(": ").append(text).append(attachesHtml).append(replyHtml).append(fwdsHtml); - } - - return fwdmsgs.append("

").toString(); - } - - private String mentionsReplace(String text) { - var matcher = Pattern.compile("\\[((?:club|id|public)\\d+)\\|([^]]+)\\]").matcher(text); - - return matcher.replaceAll("$2"); - } - - private String pluralMsg(int count) { - String[] plurals = getArray(R.array.msg_plurals); - - var hundredModulo = count % 100; - var tenModulo = count % 10; - - if (hundredModulo > 10 && hundredModulo < 20) return plurals[2]; - else if (tenModulo > 1 && tenModulo < 5) return plurals[1]; - else if (tenModulo == 1) return plurals[0]; - - return plurals[2]; - } - - private String getHtmlForChatAction(JSONObject action) { - var type = action.optString("type"); - - StringBuilder htmlAction = new StringBuilder("

"); - - switch (type) { - case "chat_photo_update": - htmlAction.append(AndroidUtils.getString(R.string.chat_export_avatar_update)); - break; - case "chat_photo_remove": - htmlAction.append(AndroidUtils.getString(R.string.chat_export_avatar_remove)); - break; - case "chat_create": - var name = action.optString("text"); - htmlAction.append(AndroidUtils.getString(R.string.chat_export_chat_created)).append((!name.isEmpty()) ? " \"" + name + "\"" : ""); - break; - case "chat_title_update": - var newName = action.optString("text"); - htmlAction.append(AndroidUtils.getString(R.string.chat_export_name_changed)).append((!newName.isEmpty()) ? (" " + AndroidUtils.getString(R.string.chat_export_to) + " \"" + newName + "\"") : ""); - break; - case "chat_invite_user": - var newUserId = action.optInt("member_id"); - htmlAction.append(AndroidUtils.getString(R.string.chat_export_user_added) + " ").append((newUserId > 0) ? AndroidUtils.getString(R.string.chat_export_type_user) : AndroidUtils.getString(R.string.chat_export_type_group)); - if (newUserId != 0) { - var userLink = ((newUserId < 0) ? "club" : "id") + Math.abs(newUserId); - htmlAction.append(" @").append(userLink).append(""); - } - break; - case "chat_kick_user": - var removedUserId = action.optInt("member_id"); - htmlAction.append(AndroidUtils.getString(R.string.chat_export_user_kicked) + " ").append((removedUserId > 0) ? AndroidUtils.getString(R.string.chat_export_type_user) : AndroidUtils.getString(R.string.chat_export_type_group)); - if (removedUserId != 0) { - var userLink = ((removedUserId < 0) ? "club" : "id") + Math.abs(removedUserId); - htmlAction.append(" @").append(userLink).append(""); - } - break; - case "chat_pin_message": - var message = action.optString("message"); - htmlAction.append(AndroidUtils.getString(R.string.chat_export_msg_pinned)).append((!message.isEmpty()) ? (" \"" + message + "\"") : ""); - break; - case "chat_unpin_message": - htmlAction.append(AndroidUtils.getString(R.string.chat_export_msg_unpinned)); - break; - case "chat_invite_user_by_link": - htmlAction.append(AndroidUtils.getString(R.string.chat_export_join_by_link)); - break; - case "chat_screenshot": - htmlAction.append(AndroidUtils.getString(R.string.chat_export_chat_screenshot)); - break; - case "custom": - htmlAction.append(action.optString("message")); - break; - default: - htmlAction.append(AndroidUtils.getString(R.string.vkim_msg_unsupported) + " (").append(type).append(")"); - break; - } - - return htmlAction.append("

").toString(); - } - - private String getHtmlForAttach(List attaches) throws JSONException { - StringBuilder rs = new StringBuilder(); - - for (JSONObject attach : attaches) { - String type = attach.getString("type"); - JSONObject item = attach.optJSONObject(type); - - if (item == null) continue; - - switch (type) { // market_album is not supported - case "photo": - var sizes = item.getJSONArray("sizes"); - rs.append("").append(AndroidUtils.getString(R.string.chat_export_type_photo)).append(""); - break; - case "doc": - rs.append("").append(AndroidUtils.getString(R.string.chat_export_type_doc)).append(""); - break; - case "link": - rs.append("").append(AndroidUtils.getString(R.string.chat_export_type_link)).append(""); - break; - case "wall": - rs.append("").append(AndroidUtils.getString(R.string.chat_export_type_wallpost)).append(""); - break; - case "sticker": - rs.append(""); - break; - case "audio_message": - rs.append("").append(AndroidUtils.getString(R.string.chat_export_type_audiomsg)).append(""); - break; - case "audio": - rs.append(AndroidUtils.getString(R.string.chat_export_type_audio)).append(" (").append(item.optString("artist")).append(" — ").append(item.optString("title")).append(")"); - break; - case "video": - rs.append(AndroidUtils.getString(R.string.chat_export_type_video)).append(" (").append(getVideoHtml(item.optJSONObject("files"))).append(")"); - break; - case "wall_reply": - var link = "https://" + VK_DOMAIN + "/wall" + item.optString("owner_id") + "_" + item.optString("post_id") + "?reply=" + item.optString("id"); - rs.append("").append(AndroidUtils.getString(R.string.chat_export_type_wallreply)).append(""); - break; - case "story": - var story_link = "https://" + VK_DOMAIN + "/story" + item.optString("owner_id") + "_" + item.optString("id"); - rs.append("").append(AndroidUtils.getString(R.string.chat_export_type_story)).append(""); - break; - case "podcast": - var podcast_link = "https://" + VK_DOMAIN + "/podcast" + item.optString("owner_id") + "_" + item.optString("id"); - rs.append("").append(AndroidUtils.getString(R.string.chat_export_type_podcast)).append(""); - break; - case "gift": - rs.append(""); - break; - case "mini_app": - var app = item.optJSONObject("app"); - - if (app != null) { - var miniapp_link = "https://" + VK_DOMAIN + "/app" + app.optString("id"); - rs.append("").append(AndroidUtils.getString(R.string.chat_export_type_miniapp)).append(""); - break; - } - - rs.append(AndroidUtils.getString(R.string.chat_export_error)); - break; - case "audio_playlist": - var playlist_link = "https://" + VK_DOMAIN + "/music/album/" + item.optString("owner_id") + "_" + item.optString("id") + "_" + item.optString("access_key"); - rs.append("").append(AndroidUtils.getString(R.string.chat_export_type_playlist)).append(" (").append(item.optString("title")).append(")"); - break; - case "poll": - rs.append(AndroidUtils.getString(item.optBoolean("anonymous") ? R.string.chat_export_poll_anon : R.string.chat_export_poll_publ)).append(" ").append(AndroidUtils.getString(R.string.chat_export_type_poll)).append(" ").append(getPollHtml(item)); - break; - case "market": - var market_item_link = "https://" + VK_DOMAIN + "/market" + item.optString("owner_id") + "_" + item.optString("id"); - rs.append("").append(AndroidUtils.getString(R.string.chat_export_type_market)).append(""); - break; - default: - rs.append(AndroidUtils.getString(R.string.vkim_msg_unsupported)).append(" (").append(type).append(")"); - break; - } - - rs.append(" "); - } - - return rs.toString(); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/vtosters/lite/downloaders/messages/Constants.java b/app/src/main/java/ru/vtosters/lite/downloaders/messages/Constants.java new file mode 100644 index 000000000..354363f1b --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/downloaders/messages/Constants.java @@ -0,0 +1,96 @@ +package ru.vtosters.lite.downloaders.messages; + +public class Constants { + public static final String VK_DOMAIN = "vk.com"; + public static final String HTML_DOC_STYLE = ".vtex-milk-msg {\n" + + " border-radius: 8px;\n" + + " padding: 24px;\n" + + " background: white;\n" + + " margin: 16px;\n" + + " box-shadow: 0 4px 8px 0 rgba(0,0,0,0.07);\n" + + " word-wrap: break-word;\n" + + "}\n" + + "\n" + + ".vtex-milk-msgcont br {\n" + + " display: none;\n" + + "}\n" + + "\n" + + ".vtex-milk-msg p {\n" + + " margin: 0;\n" + + "}\n" + + "\n" + + ".vtex-milk-msg.vtex-milk-msg-out {\n" + + " background: #ADD3FF;\n" + + "}\n" + + "\n" + + "p.msg-attaches {\n" + + " margin-top: 8px;\n" + + " font-style: italic;\n" + + "}\n" + + "p.chat-action {\n" + + " font-style: italic;\n" + + "}\n" + + "\n" + + ".vtex-milk-header {\n" + + " border-radius: 8px;\n" + + " padding: 24px;\n" + + " background: white;\n" + + " margin: 16px;\n" + + " box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);\n" + + "}\n" + + "\n" + + "h4.vtex-milk-header-txt {\n" + + " margin: 0;\n" + + "}\n" + + "\n" + + "h5.vtex-milk-header-sub {\n" + + " margin: 0;\n" + + " padding-top: 8px;\n" + + "}\n" + + "\n" + + "a.msg-attach-link {\n" + + " color: #5181B8;\n" + + " text-decoration: none;\n" + + "}\n" + + "\n" + + ".msg-from a {\n" + + " color: #5181B8;\n" + + " text-decoration: none;\n" + + "}\n" + + "\n" + + ".msg-from-date {\n" + + " color: rgba(0, 0, 0, 0.75);\n" + + "}\n" + + "\n" + + "body {\n" + + " font-family: Roboto, system-ui, sans-serif;\n" + + "}\n" + + "\n" + + ".msg-body {\n" + + " padding-top: 4px;\n" + + "}\n" + + ".msg-reply {\n" + + " border-left: 3px solid #7aa7f6;\n" + + " margin-top: 5px;\n" + + " padding-left: 3px;\n" + + "}\n\n" + + ".msg-reply p {\n" + + " padding-left: 5px;\n" + + "}\n\n" + + ".msg-reply p.reply-header {\n" + + " color: #5181B8;\n" + + "}\n\n" + + ".msg-reply p.reply-content {\n" + + " color: #000000;\n" + + "}\n" + + ".vtex-milk-msg.poll {\n" + + " width: 45%;\n" + + " background: linear-gradient(to bottom right, #707c8c, #4d5565);\n" + + " font-style: normal;" + + "}\n" + + ".vtex-milk-msg.poll p { color: white; }\n" + + ".vtex-milk-msg.poll hr { border-top: 1px; background: #ffffff; }\n" + + "\n" + + ""; + +} diff --git a/app/src/main/java/ru/vtosters/lite/downloaders/messages/DialogDownloaderFormatProvider.java b/app/src/main/java/ru/vtosters/lite/downloaders/messages/DialogDownloaderFormatProvider.java new file mode 100644 index 000000000..4295882cd --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/downloaders/messages/DialogDownloaderFormatProvider.java @@ -0,0 +1,17 @@ +package ru.vtosters.lite.downloaders.messages; + +import org.json.JSONException; +import ru.vtosters.lite.downloaders.messages.items.MiniMsg; +import ru.vtosters.lite.downloaders.messages.items.MiniUser; + +// This class defines everything which is needed to create an output file +public abstract class DialogDownloaderFormatProvider { + // Internal, for JSON/HTML + public abstract String provideDocumentStart(String dialogName, String date); + + public abstract String provideDocumentEnd(); + + public abstract String provideHeader(String dialogName, String date); + + public abstract String provideMessage(MiniMsg message, MiniUser user) throws JSONException; +} diff --git a/app/src/main/java/ru/vtosters/lite/downloaders/messages/HtmlDialogDownloaderFormatProvider.java b/app/src/main/java/ru/vtosters/lite/downloaders/messages/HtmlDialogDownloaderFormatProvider.java new file mode 100644 index 000000000..f524a66e0 --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/downloaders/messages/HtmlDialogDownloaderFormatProvider.java @@ -0,0 +1,435 @@ +package ru.vtosters.lite.downloaders.messages; + +import com.vtosters.lite.R; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import ru.vtosters.lite.downloaders.messages.items.MiniMsg; +import ru.vtosters.lite.downloaders.messages.items.MiniUser; +import ru.vtosters.lite.utils.AndroidUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import static ru.vtosters.lite.encryption.EncryptProvider.decryptMessage; +import static ru.vtosters.lite.utils.AccountManagerUtils.getUserId; +import static ru.vtosters.lite.utils.AndroidUtils.getArray; + +public class HtmlDialogDownloaderFormatProvider extends DialogDownloaderFormatProvider { + + @Override + public String provideDocumentStart(String dialogName, String date) { + + return "" + + AndroidUtils.getString(R.string.chat_export_title) + + ""; + } + + @Override + public String provideDocumentEnd() { + return "
"; + } + + @Override + public String provideHeader(String dialogName, String date) { + return "

VTLite

" + + AndroidUtils.getString(R.string.chat_export_header) + + " (" + date + ")
"; + } + @Override + public String provideMessage(MiniMsg message, MiniUser user) throws JSONException { + String isOut = (user.id == getUserId()) ? " vtex-milk-msg-out" : ""; + + String geoPosition = ""; + if (message.geo != null) { + var coordinates = message.geo.optJSONObject("coordinates"); + if (coordinates != null) { + var latitude = coordinates.optDouble("latitude"); + var longitude = coordinates.optDouble("longitude"); + var ll = longitude + "," + latitude; + geoPosition = "" + + "" + + ""; + } + } + + String chatAction = (message.chat_action != null) ? getHtmlForChatAction(message.chat_action) : ""; + String attaches = (message.attachments.size() > 0) ? "

" + AndroidUtils.getString(R.string.attachments) + ": " + getHtmlForAttach(message.attachments) + "

" : ""; + String reply = (message.reply_message != null) ? provideReply(message.reply_message) : ""; + String fwdMessages = (message.fwd_messages.size() > 0) ? provideForwardMessages(message.fwd_messages) : ""; + String userLink = (user.isGroup ? "club" : "id") + user.id; + + return "
" + + "

" + AndroidUtils.getString(R.string.from_lower) + + " " + user.firstName + " " + user.lastName + + " " + MessagesDownloader.formatTime(message.date) + "

" + // TODO photo100 + "

" + mentionsReplace(message.text) + "

" + + geoPosition + + chatAction + + attaches + + reply + + fwdMessages + + "
"; + } + + private String provideReply(JSONObject replyMessage) { + var text = mentionsReplace(decryptMessage(replyMessage.optString("text"), replyMessage.optInt("peer_id"))); + var fwdMessages = replyMessage.optJSONArray("fwd_messages"); + + var user = MessagesDownloader.usersArray.get(replyMessage.optInt("from_id")); + + if (text.isEmpty()) { + if (fwdMessages != null) { + var length = fwdMessages.length(); + if (length > 0) text = length + " " + pluralMsg(length); + } else text = AndroidUtils.getString(R.string.attachment); // Вложение + } + + return "" + + "
" + + "

" + AndroidUtils.getString(R.string.vkim_msg_fwd_single) + "

" + + "

" + user.firstName + " " + user.lastName + ": " + text + "

" + + "
" + + "
"; + } + + private String provideForwardMessages(List fwd_messages) throws JSONException { + StringBuilder fwdmsgs = new StringBuilder("
"); + + for (JSONObject fwd_message : fwd_messages) { + + if (fwd_message == null) continue; + + List attaches = new ArrayList<>(); + + JSONArray arr = fwd_message.optJSONArray("attachments"); + if (arr != null) { + for (int i = 0; i < arr.length(); i++) { + try { + attaches.add(arr.getJSONObject(i)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + + List fwdsMsg = new ArrayList<>(); + + JSONArray fwd_msgs = fwd_message.optJSONArray("fwd_messages"); + if (fwd_msgs != null) { + for (int i = 0; i < fwd_msgs.length(); i++) { + try { + fwdsMsg.add(fwd_msgs.getJSONObject(i)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + + JSONObject reply_message = fwd_message.optJSONObject("reply_message"); + + var attachesHtml = (attaches.size() > 0) ? AndroidUtils.getString(R.string.attachments) + ": " + getHtmlForAttach(attaches) : ""; + var replyHtml = (reply_message != null) ? provideReply(reply_message) : ""; + var fwdsHtml = (fwdsMsg.size() > 0) ? provideForwardMessages(fwdsMsg) : ""; // recursion XD :> + + var userId = fwd_message.optInt("from_id"); + var peerId = fwd_message.optInt("peer_id"); + var text = mentionsReplace(decryptMessage(fwd_message.optString("text"), peerId)); + + var userLink = ((userId < 0) ? "club" : "id") + Math.abs(userId); + + fwdmsgs.append("

@") + .append(userLink) + .append(": ") + .append(text) + .append(attachesHtml) + .append(replyHtml) + .append(fwdsHtml); + } + + return fwdmsgs.append("

").toString(); + } + + private String mentionsReplace(String text) { + var matcher = Pattern.compile("\\[((?:club|id|public)\\d+)\\|([^]]+)\\]").matcher(text); + + return matcher.replaceAll("$2"); + } + + private String pluralMsg(int count) { + String[] plurals = getArray(R.array.msg_plurals); + + var hundredModulo = count % 100; + var tenModulo = count % 10; + + if (hundredModulo > 10 && hundredModulo < 20) return plurals[2]; + else if (tenModulo > 1 && tenModulo < 5) return plurals[1]; + else if (tenModulo == 1) return plurals[0]; + + return plurals[2]; + } + + private String getHtmlForChatAction(JSONObject action) { + var type = action.optString("type"); + + StringBuilder htmlAction = new StringBuilder("

"); + + switch (type) { + case "chat_photo_update": + htmlAction.append(AndroidUtils.getString(R.string.chat_export_avatar_update)); + break; + case "chat_photo_remove": + htmlAction.append(AndroidUtils.getString(R.string.chat_export_avatar_remove)); + break; + case "chat_create": + htmlAction.append(AndroidUtils.getString(R.string.chat_export_chat_created)); + var name = action.optString("text"); + if (!name.isEmpty()) + htmlAction.append(" \"").append(name).append("\""); + break; + case "chat_title_update": + var newName = action.optString("text"); + htmlAction.append(AndroidUtils.getString(R.string.chat_export_name_changed)); + if (!newName.isEmpty()) + htmlAction.append(" ") + .append(AndroidUtils.getString(R.string.chat_export_to)) + .append(" \"") + .append(newName) + .append("\""); + break; + case "chat_invite_user": + var newMemberId = action.optInt("member_id"); + htmlAction.append(AndroidUtils.getString(R.string.chat_export_user_added)) + .append(" "); + if (newMemberId != 0) { + int id; + String endpoint; + if (newMemberId >= 0) { + id = R.string.chat_export_type_user; + endpoint = "id" + newMemberId; + } else { + id = R.string.chat_export_type_group; + endpoint = "club" + -newMemberId; + } + htmlAction.append(AndroidUtils.getString(id)) + .append(" @") + .append(endpoint) + .append(""); + } + break; + case "chat_kick_user": + var removedMemberId = action.optInt("member_id"); + htmlAction.append(AndroidUtils.getString(R.string.chat_export_user_kicked)) + .append(" "); + if (removedMemberId != 0) { + int id; + String endpoint; + if (removedMemberId >= 0) { + id = R.string.chat_export_type_user; + endpoint = "id" + removedMemberId; + } else { + id = R.string.chat_export_type_group; + endpoint = "club" + -removedMemberId; + } + htmlAction.append(AndroidUtils.getString(id)) + .append(" @") + .append(endpoint) + .append(""); + } + break; + case "chat_pin_message": + var message = action.optString("message"); + htmlAction.append(AndroidUtils.getString(R.string.chat_export_msg_pinned)); + if (!message.isEmpty()) htmlAction.append(" \"").append(message).append("\""); + break; + case "chat_unpin_message": + htmlAction.append(AndroidUtils.getString(R.string.chat_export_msg_unpinned)); + break; + case "chat_invite_user_by_link": + htmlAction.append(AndroidUtils.getString(R.string.chat_export_join_by_link)); + break; + case "chat_screenshot": + htmlAction.append(AndroidUtils.getString(R.string.chat_export_chat_screenshot)); + break; + case "custom": + htmlAction.append(action.optString("message")); + break; + default: + htmlAction.append(AndroidUtils.getString(R.string.vkim_msg_unsupported)).append(" (").append(type).append(")"); + break; + } + + return htmlAction.append("

").toString(); + } + + private String getHtmlForAttach(List attaches) throws JSONException { + StringBuilder rs = new StringBuilder(); + + for (JSONObject attach : attaches) { + String type = attach.getString("type"); + JSONObject item = attach.optJSONObject(type); + + if (item == null) continue; + + switch (type) { // market_album is not supported + case "photo": + var sizes = item.getJSONArray("sizes"); + rs.append("") + .append(AndroidUtils.getString(R.string.chat_export_type_photo)) + .append(""); + break; + case "doc": + rs.append("") + .append(AndroidUtils.getString(R.string.chat_export_type_doc)) + .append(""); + break; + case "link": + rs.append("") + .append(AndroidUtils.getString(R.string.chat_export_type_link)) + .append(""); + break; + case "wall": + rs.append("") + .append(AndroidUtils.getString(R.string.chat_export_type_wallpost)) + .append(""); + break; + case "sticker": + rs.append(""); + break; + case "audio_message": + rs.append("") + .append(AndroidUtils.getString(R.string.chat_export_type_audiomsg)) + .append(""); + break; + case "audio": + rs.append(AndroidUtils.getString(R.string.chat_export_type_audio)) + .append(" (") + .append(item.optString("artist")) + .append(" — ") + .append(item.optString("title")) + .append(")"); + break; + case "video": + rs.append(AndroidUtils.getString(R.string.chat_export_type_video)) + .append(" (") + .append(MessagesDownloader.getVideoHtml(item.optJSONObject("files"))) + .append(")"); + break; + case "wall_reply": + var link = String.format("https://" + Constants.VK_DOMAIN + "/wall%s_%s?reply=%s", + item.optString("owner_id"), + item.optString("post_id"), + item.optString("id")); + rs.append("") + .append(AndroidUtils.getString(R.string.chat_export_type_wallreply)) + .append(""); + break; + case "story": + var story_link = String.format("https://" + Constants.VK_DOMAIN + "/story%s_%s", + item.optString("owner_id"), + item.optString("id")); + rs.append("") + .append(AndroidUtils.getString(R.string.chat_export_type_story)) + .append(""); + break; + case "podcast": + var podcast_link = String.format("https://" + Constants.VK_DOMAIN + "/podcast%s_%s", + item.optString("owner_id"), + item.optString("id")); + rs.append("") + .append(AndroidUtils.getString(R.string.chat_export_type_podcast)) + .append(""); + break; + case "gift": + rs.append(""); + break; + case "mini_app": + var app = item.optJSONObject("app"); + + if (app != null) { + var miniapp_link = String.format("https://" + Constants.VK_DOMAIN + "/app%s", app.optString("id")); + rs.append("") + .append(AndroidUtils.getString(R.string.chat_export_type_miniapp)) + .append(""); + break; + } + + rs.append(AndroidUtils.getString(R.string.chat_export_error)); + break; + case "audio_playlist": + var playlist_link = String.format("https://" + Constants.VK_DOMAIN + "/music/album/%s_%s_%s", + item.optString("owner_id"), + item.optString("id"), + item.optString("access_key")); + rs.append("") + .append(AndroidUtils.getString(R.string.chat_export_type_playlist)) + .append(" (") + .append(item.optString("title")) + .append(")"); + break; + case "poll": + rs.append(AndroidUtils.getString(item.optBoolean("anonymous") ? R.string.chat_export_poll_anon : R.string.chat_export_poll_publ)) + .append(" ") + .append(AndroidUtils.getString(R.string.chat_export_type_poll)) + .append(" ") + .append(MessagesDownloader.getPollHtml(item)); + break; + case "market": + var market_item_link = String.format("https://" + Constants.VK_DOMAIN + "/market%s_%s", + item.optString("owner_id"), + item.optString("id")); + rs.append("") + .append(AndroidUtils.getString(R.string.chat_export_type_market)) + .append(""); + break; + default: + rs.append(AndroidUtils.getString(R.string.vkim_msg_unsupported)) + .append(" (") + .append(type) + .append(")"); + break; + } + + rs.append(" "); + } + + return rs.toString(); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/vtosters/lite/downloaders/messages/MessagesDownloader.java b/app/src/main/java/ru/vtosters/lite/downloaders/messages/MessagesDownloader.java new file mode 100644 index 000000000..ceb016f9d --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/downloaders/messages/MessagesDownloader.java @@ -0,0 +1,162 @@ +package ru.vtosters.lite.downloaders.messages; + +import android.util.Log; +import android.util.SparseArray; +import androidx.annotation.Nullable; +import com.vk.core.util.ToastUtils; +import com.vtosters.lite.R; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Request; +import okhttp3.Response; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import ru.vtosters.lite.di.singleton.VtOkHttpClient; +import ru.vtosters.lite.downloaders.messages.items.MiniMsg; +import ru.vtosters.lite.downloaders.messages.items.MiniUser; +import ru.vtosters.lite.proxy.ProxyUtils; +import ru.vtosters.lite.utils.AccountManagerUtils; +import ru.vtosters.lite.utils.AndroidUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +public class MessagesDownloader { + private static final int[] VIDEO_QUALITIES = { 144, 240, 360, 480, 720, 1080, 1440, 2160 }; + + private static final SimpleDateFormat SDFORMAT = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss", AndroidUtils.getResources().getConfiguration().locale); + + public static SparseArray usersArray; + + public static String formatTime(long now) { + return SDFORMAT.format(new Date(now)); + } + + public static String getPollHtml(JSONObject poll) { + StringBuilder pollHtml = new StringBuilder( + "
" + + "

" + poll.optString("question") + "

" + ); + + var answers = poll.optJSONArray("answers"); + + for (int i = 0; i < answers.length(); i++) { + var jsonObject = answers.optJSONObject(i); + if (jsonObject != null) + pollHtml.append("

") + .append(jsonObject.optString("text")) + .append(" · ") + .append(jsonObject.optString("votes")) + .append(" (") + .append(jsonObject.optString("rate")) + .append("%)

"); + } + + pollHtml.append("

") + .append(AndroidUtils.getString(R.string.chat_export_poll_voted)) + .append(" ") + .append(poll.optString("votes")) + .append(" ") + .append(AndroidUtils.getString(R.string.chat_export_users_name)) + .append("

") + .append("
"); + + return pollHtml.toString(); + } + + public static String getVideoHtml(JSONObject files) throws JSONException { + if (files == null) return AndroidUtils.getString(R.string.chat_export_video_nolinks); + + Iterator keys = files.keys(); + StringBuilder videoHtml = new StringBuilder(); + + int i = 0; + while (keys.hasNext() && i < VIDEO_QUALITIES.length) { + var key = keys.next(); + if (key.equals("mp4_" + VIDEO_QUALITIES[i])) { + videoHtml.append("") + .append(VIDEO_QUALITIES[i]) + .append("p") + .append(" "); + } + ++i; + } + + return videoHtml.toString(); + } + + public void downloadDialog(int peer_id, DialogDownloaderFormatProvider format, File out) throws Exception { + FileOutputStream fos = new FileOutputStream(out); + + // fix TODO with conversation name (API method messages.getConversationsById) + fos.write(format.provideDocumentStart("TODO", formatTime(System.currentTimeMillis())).getBytes(StandardCharsets.UTF_8)); + + var url = String.format("https://" + ProxyUtils.getApi() + "/method/messages.getHistory?extended=1&v=5.140&offset=%d&count=200&peer_id=%d&access_token=%s", + 0, + peer_id, + AccountManagerUtils.getUserToken()); + var req = new Request.a() + .b(url) + .a(); + VtOkHttpClient.getInstance().a(req).a(new Callback() { + @Override + public void a(Call call, IOException e) { + Log.d("MessagesDownloader", e+""); + } + + @Override + public void a(Call call, Response response) + throws IOException { + try { + JSONObject obj = new JSONObject(response.a().g()).getJSONObject("response"); + + SparseArray users = new SparseArray<>(); + parseUsers(users, obj.optJSONArray("profiles"), false); + parseUsers(users, obj.optJSONArray("groups"), true); + + List msgs = parseMessages(obj.getJSONArray("items")); + + usersArray = users; + + fos.write(format.provideHeader("TODO", formatTime(System.currentTimeMillis())).getBytes(StandardCharsets.UTF_8)); + for (MiniMsg msg : msgs) { + fos.write(format.provideMessage(msg, users.get(msg.fromId)).getBytes(StandardCharsets.UTF_8)); + } + fos.write(format.provideDocumentEnd().getBytes(StandardCharsets.UTF_8)); + fos.close(); + ToastUtils.a(AndroidUtils.getString(R.string.saved_as) + " " + out.getAbsolutePath()); + } catch (IOException | JSONException e) { + e.printStackTrace(); + } + } + }); + } + + private List parseMessages(JSONArray jsonArray) throws JSONException { + List thing = new ArrayList<>(); + + for (int i = 0; i < jsonArray.length(); i++) + thing.add(new MiniMsg(jsonArray.getJSONObject(i))); + + return thing; + } + + private void parseUsers(SparseArray array, @Nullable JSONArray jsonArray, boolean groups) throws JSONException { + if (jsonArray == null) return; + + for (int i = 0; i < jsonArray.length(); i++) { + MiniUser user = new MiniUser(jsonArray.getJSONObject(i), groups); + array.put(groups ? -user.id : user.id, user); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/vtosters/lite/downloaders/messages/items/MiniMsg.java b/app/src/main/java/ru/vtosters/lite/downloaders/messages/items/MiniMsg.java new file mode 100644 index 000000000..9a2684089 --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/downloaders/messages/items/MiniMsg.java @@ -0,0 +1,42 @@ +package ru.vtosters.lite.downloaders.messages.items; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +import static ru.vtosters.lite.encryption.EncryptProvider.decryptMessage; +public class MiniMsg { + public long date; + public int fromId; + public int id; // message id + public String text; + public List attachments; + public List fwd_messages; + public JSONObject reply_message; // one reply + public JSONObject chat_action; + public JSONObject geo; + + public MiniMsg(JSONObject obj) throws JSONException { + date = obj.getLong("date") * 1000; + fromId = obj.getInt("from_id"); + id = obj.getInt("id"); + text = decryptMessage(obj.getString("text"), obj.getInt("peer_id")); + reply_message = obj.optJSONObject("reply_message"); + chat_action = obj.optJSONObject("action"); + geo = obj.optJSONObject("geo"); + + attachments = new ArrayList<>(); + fwd_messages = new ArrayList<>(); + + JSONArray arr = obj.optJSONArray("attachments"); + if (arr != null) + for (int i = 0; i < arr.length(); i++) attachments.add(arr.getJSONObject(i)); + + JSONArray fwdArr = obj.optJSONArray("fwd_messages"); + if (fwdArr != null) + for (int i = 0; i < fwdArr.length(); i++) fwd_messages.add(fwdArr.getJSONObject(i)); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/downloaders/messages/items/MiniUser.java b/app/src/main/java/ru/vtosters/lite/downloaders/messages/items/MiniUser.java new file mode 100644 index 000000000..b71e109ec --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/downloaders/messages/items/MiniUser.java @@ -0,0 +1,20 @@ +package ru.vtosters.lite.downloaders.messages.items; + +import org.json.JSONException; +import org.json.JSONObject; + +public class MiniUser { + public String firstName; + public String lastName; + public boolean isGroup; + public int id; + public String photo100; + + public MiniUser(JSONObject obj, boolean is_group) throws JSONException { + id = obj.getInt("id"); + firstName = obj.optString("first_name", obj.optString("name")); + lastName = obj.optString("last_name", ""); + photo100 = obj.getString("photo_100"); + isGroup = is_group; + } +}