diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle index c1d9a13be..50e6c24ec 100644 --- a/TMessagesProj/build.gradle +++ b/TMessagesProj/build.gradle @@ -5,8 +5,8 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' -def verName = "7.1.3-preview04" -def verCode = 79 +def verName = "7.1.3-rc04" +def verCode = 80 def serviceAccountCredentialsFile = rootProject.file("service_account_credentials.json") @@ -57,7 +57,7 @@ buildscript { } dependencies { - classpath 'cn.hutool:hutool-all:5.4.3' + classpath 'cn.hutool:hutool-all:5.4.5' } } @@ -74,7 +74,7 @@ dependencies { implementation 'androidx.core:core-ktx:1.5.0-alpha04' implementation 'androidx.palette:palette-ktx:1.0.0' implementation 'androidx.viewpager:viewpager:1.0.0' - implementation 'androidx.exifinterface:exifinterface:1.3.0' + implementation 'androidx.exifinterface:exifinterface:1.3.1' implementation "androidx.interpolator:interpolator:1.0.0" implementation 'androidx.dynamicanimation:dynamicanimation:1.0.0' implementation 'androidx.multidex:multidex:2.0.1' @@ -101,8 +101,8 @@ dependencies { implementation 'dnsjava:dnsjava:3.3.0' implementation 'org.dizitart:nitrite:3.4.2' - implementation 'cn.hutool:hutool-core:5.4.4' - implementation 'cn.hutool:hutool-crypto:5.4.4' + implementation 'cn.hutool:hutool-core:5.4.5' + implementation 'cn.hutool:hutool-crypto:5.4.5' implementation project(":openpgp-api") @@ -291,7 +291,6 @@ android { multiDexEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' matchingFallbacks = ['debug'] - signingConfig signingConfigs.release } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java index e96125502..49b416ce1 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java @@ -58,6 +58,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import cn.hutool.core.util.StrUtil; +import tw.nekomimi.nekogram.NekoConfig; import tw.nekomimi.nekogram.NekoXConfig; public class MessageObject { @@ -171,7 +172,7 @@ public class MessageObject { public ArrayList highlightedWords; - static final String[] excludeWords = new String[] { + static final String[] excludeWords = new String[]{ " vs. ", " vs ", " versus ", @@ -379,18 +380,18 @@ public class MessageObject { public float[] heights; public MessageGroupedLayoutAttempt(int i1, int i2, float f1, float f2) { - lineCounts = new int[] {i1, i2}; - heights = new float[] {f1, f2}; + lineCounts = new int[]{i1, i2}; + heights = new float[]{f1, f2}; } public MessageGroupedLayoutAttempt(int i1, int i2, int i3, float f1, float f2, float f3) { - lineCounts = new int[] {i1, i2, i3}; - heights = new float[] {f1, f2, f3}; + lineCounts = new int[]{i1, i2, i3}; + heights = new float[]{f1, f2, f3}; } public MessageGroupedLayoutAttempt(int i1, int i2, int i3, int i4, float f1, float f2, float f3, float f4) { - lineCounts = new int[] {i1, i2, i3, i4}; - heights = new float[] {f1, f2, f3, f4}; + lineCounts = new int[]{i1, i2, i3, i4}; + heights = new float[]{f1, f2, f3, f4}; } } @@ -431,8 +432,8 @@ public class MessageObject { isOut = messageObject.isOutOwner(); needShare = !isOut && ( messageObject.messageOwner.fwd_from != null && messageObject.messageOwner.fwd_from.saved_from_peer != null || - messageObject.messageOwner.from_id instanceof TLRPC.TL_peerUser && (messageObject.messageOwner.peer_id.channel_id != 0 || messageObject.messageOwner.peer_id.chat_id != 0 || - messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) + messageObject.messageOwner.from_id instanceof TLRPC.TL_peerUser && (messageObject.messageOwner.peer_id.channel_id != 0 || messageObject.messageOwner.peer_id.chat_id != 0 || + messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) ); } TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); @@ -522,7 +523,7 @@ public class MessageObject { position3.set(0, 1, 1, 1, rightWidth, thirdHeight / maxSizeHeight, POSITION_FLAG_RIGHT | POSITION_FLAG_BOTTOM); position3.spanSize = maxSizeWidth; - position1.siblingHeights = new float[] {thirdHeight / maxSizeHeight, secondHeight / maxSizeHeight}; + position1.siblingHeights = new float[]{thirdHeight / maxSizeHeight, secondHeight / maxSizeHeight}; if (isOut) { position1.spanSize = maxSizeWidth - rightWidth; @@ -597,7 +598,7 @@ public class MessageObject { position3.leftSpanOffset = w0; position4.leftSpanOffset = w0; } - position1.siblingHeights = new float[] {h0, h1, h2}; + position1.siblingHeights = new float[]{h0, h1, h2}; hasSibling = true; maxX = 1; } @@ -655,7 +656,7 @@ public class MessageObject { MessageGroupedLayoutAttempt attempt = attempts.get(a); float height = 0; float minLineHeight = Float.MAX_VALUE; - for (int b = 0; b < attempt.heights.length; b++){ + for (int b = 0; b < attempt.heights.length; b++) { height += attempt.heights[b]; if (attempt.heights[b] < minLineHeight) { minLineHeight = attempt.heights[b]; @@ -766,7 +767,7 @@ public class MessageObject { for (int i = 0; i < messages.size(); i++) { MessageObject object = messages.get(i); MessageObject.GroupedMessagePosition position = positions.get(object); - if (position != null && (position.flags & (MessageObject.POSITION_FLAG_TOP | MessageObject.POSITION_FLAG_LEFT)) != 0) { + if (position != null && (position.flags & (MessageObject.POSITION_FLAG_TOP | MessageObject.POSITION_FLAG_LEFT)) != 0) { return object; } } @@ -1619,9 +1620,9 @@ public class MessageObject { if (message != null) { message.out = false; message.id = mid[0]++; - message.flags &=~ TLRPC.MESSAGE_FLAG_REPLY; + message.flags &= ~TLRPC.MESSAGE_FLAG_REPLY; message.reply_to = null; - message.flags = message.flags &~ TLRPC.MESSAGE_FLAG_EDITED; + message.flags = message.flags & ~TLRPC.MESSAGE_FLAG_EDITED; if (chat.megagroup) { message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; } @@ -2579,7 +2580,7 @@ public class MessageObject { } else { isRestrictedMessage = false; String restrictionReason = MessagesController.getRestrictionReason(messageOwner.restriction_reason); - if (!TextUtils.isEmpty(restrictionReason)) { + if (!TextUtils.isEmpty(restrictionReason) && !NekoConfig.ignoreContentRestrictions) { messageText = restrictionReason; isRestrictedMessage = true; } else if (!isMediaEmpty()) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java index 516304cba..ea960ab81 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java @@ -3211,7 +3211,7 @@ public class MessagesController extends BaseController implements NotificationCe SharedPreferences.Editor editor = notificationsPreferences.edit(); boolean bar_hidden = !settings.report_spam && !settings.add_contact && !settings.block_contact && !settings.share_contact && !settings.report_geo; if (BuildVars.LOGS_ENABLED) { - FileLog.d("peer settings loaded for " + dialogId + " add = " + settings.add_contact + " block = " + settings.block_contact + " spam = " + settings.report_spam + " share = " + settings.share_contact + " geo = " + settings.report_geo + " hide = " + bar_hidden + " distance = " + settings.geo_distance); + FileLog.d("peer settings loaded for " + dialogId + " add = " + settings.add_contact + " block = " + settings.block_contact + " spam = " + settings.report_spam + " share = " + settings.share_contact + " geo = " + settings.report_geo + " hide = " + bar_hidden + " distance = " + settings.geo_distance); } editor.putInt("dialog_bar_vis3" + dialogId, bar_hidden ? 1 : 2); editor.putBoolean("dialog_bar_share" + dialogId, settings.share_contact); @@ -5745,7 +5745,7 @@ public class MessagesController extends BaseController implements NotificationCe } public void processLoadedMessages(TLRPC.messages_Messages messagesRes, long dialogId, long mergeDialogId, int count, int max_id, int offset_date, boolean isCache, int classGuid, - int first_unread, int last_message_id, int unread_count, int last_date, int load_type, boolean isChannel, boolean isEnd, boolean scheduled, int threadMessageId, int loadIndex, boolean queryFromServer, int mentionsCount) { + int first_unread, int last_message_id, int unread_count, int last_date, int load_type, boolean isChannel, boolean isEnd, boolean scheduled, int threadMessageId, int loadIndex, boolean queryFromServer, int mentionsCount) { if (BuildVars.LOGS_ENABLED) { FileLog.d("processLoadedMessages size " + messagesRes.messages.size() + " in chat " + dialogId + " count " + count + " max_id " + max_id + " cache " + isCache + " guid " + classGuid + " load_type " + load_type + " last_message_id " + last_message_id + " isChannel " + isChannel + " index " + loadIndex + " firstUnread " + first_unread + " unread_count " + unread_count + " last_date " + last_date + " queryFromServer " + queryFromServer); } @@ -13304,7 +13304,7 @@ public class MessagesController extends BaseController implements NotificationCe } if (reason != null) { showCantOpenAlert(fragment, reason); - return false; + if (!NekoConfig.ignoreContentRestrictions) return false; } if (messageId != 0 && originalMessage != null && chat != null && chat.access_hash == 0) { int did = (int) originalMessage.getDialogId(); @@ -13369,23 +13369,25 @@ public class MessagesController extends BaseController implements NotificationCe closeLast = true; } } - if (reason != null && !NekoConfig.ignoreContentRestrictions) { + if (reason != null) { showCantOpenAlert(fragment, reason); - } else { - Bundle args = new Bundle(); - if (chat != null) { - args.putInt("chat_id", chat.id); - } else { - args.putInt("user_id", user.id); - } - if (type == 0) { - fragment.presentFragment(new ProfileActivity(args)); - } else if (type == 2) { - fragment.presentFragment(new ChatActivity(args), true, true); - } else { - fragment.presentFragment(new ChatActivity(args), closeLast); - } + if (!NekoConfig.ignoreContentRestrictions) return; } + + Bundle args = new Bundle(); + if (chat != null) { + args.putInt("chat_id", chat.id); + } else { + args.putInt("user_id", user.id); + } + if (type == 0) { + fragment.presentFragment(new ProfileActivity(args)); + } else if (type == 2) { + fragment.presentFragment(new ChatActivity(args), true, true); + } else { + fragment.presentFragment(new ChatActivity(args), closeLast); + } + } public void openByUserName(String username, final BaseFragment fragment, final int type) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java index af04eb1de..35b43db2d 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java @@ -44,6 +44,8 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicLong; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; import tw.nekomimi.nekogram.NekoConfig; public class MessagesStorage extends BaseController { @@ -10888,7 +10890,7 @@ public class MessagesStorage extends BaseController { } if (!usersToLoad.isEmpty()) { - cursor = getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, status, name FROM users WHERE uid IN(%s)", TextUtils.join(",", usersToLoad))); + cursor = getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, status, name, uid FROM users WHERE uid IN(%s)", TextUtils.join(",", usersToLoad))); while (cursor.next()) { String name = cursor.stringValue(2); String tName = LocaleController.getInstance().getTranslitString(name); @@ -10901,11 +10903,14 @@ public class MessagesStorage extends BaseController { username = name.substring(usernamePos + 3); } int found = 0; + int uid = cursor.intValue(3); for (String q : search) { if (name.startsWith(q) || name.contains(" " + q) || tName != null && (tName.startsWith(q) || tName.contains(" " + q))) { found = 1; } else if (username != null && username.startsWith(q)) { found = 2; + } else if (NumberUtil.isInteger(q) && (NumberUtil.parseInt(q) == uid || q.length() > 3 && StrUtil.utf8Str(uid).contains(q))) { + found = 3; } if (found != 0) { NativeByteBuffer data = cursor.byteBufferValue(0); @@ -10918,8 +10923,10 @@ public class MessagesStorage extends BaseController { } if (found == 1) { dialogSearchResult.name = AndroidUtilities.generateSearchName(user.first_name, user.last_name, q); - } else { + } else if (found == 2) { dialogSearchResult.name = AndroidUtilities.generateSearchName("@" + user.username, null, "@" + q); + } else { + dialogSearchResult.name = AndroidUtilities.generateSearchName("ID: " + uid, null, q); } dialogSearchResult.object = user; resultCount++; @@ -10932,15 +10939,22 @@ public class MessagesStorage extends BaseController { } if (!chatsToLoad.isEmpty()) { - cursor = getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, name FROM chats WHERE uid IN(%s)", TextUtils.join(",", chatsToLoad))); + cursor = getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, name, uid FROM chats WHERE uid IN(%s)", TextUtils.join(",", chatsToLoad))); while (cursor.next()) { String name = cursor.stringValue(1); String tName = LocaleController.getInstance().getTranslitString(name); if (name.equals(tName)) { tName = null; } + int chatId = cursor.intValue(2); for (String q : search) { + int found = 0; if (name.startsWith(q) || name.contains(" " + q) || tName != null && (tName.startsWith(q) || tName.contains(" " + q))) { + found = 1; + } else if (NumberUtil.isInteger(q) && (NumberUtil.parseInt(q) == chatId || q.length() > 3 && StrUtil.utf8Str(chatId).contains(q))) { + found = 2; + } + if (found > 0) { NativeByteBuffer data = cursor.byteBufferValue(0); if (data != null) { TLRPC.Chat chat = TLRPC.Chat.TLdeserialize(data, data.readInt32(false), false); @@ -10948,7 +10962,11 @@ public class MessagesStorage extends BaseController { if (!(chat == null || chat.deactivated || ChatObject.isChannel(chat) && ChatObject.isNotInChat(chat))) { long dialog_id = -chat.id; DialogsSearchAdapter.DialogSearchResult dialogSearchResult = dialogsResult.get(dialog_id); - dialogSearchResult.name = AndroidUtilities.generateSearchName(chat.title, null, q); + if (found == 1) { + dialogSearchResult.name = AndroidUtilities.generateSearchName(chat.title, null, q); + } else { + dialogSearchResult.name = AndroidUtilities.generateSearchName("ID: " + chatId, null, q); + } dialogSearchResult.object = chat; resultCount++; } @@ -10974,6 +10992,8 @@ public class MessagesStorage extends BaseController { if (usernamePos != -1) { username = name.substring(usernamePos + 2); } + + int user_id = cursor.intValue(2); int found = 0; for (int a = 0; a < search.length; a++) { String q = search[a]; @@ -10981,6 +11001,8 @@ public class MessagesStorage extends BaseController { found = 1; } else if (username != null && username.startsWith(q)) { found = 2; + } else if (NumberUtil.isInteger(q) && (NumberUtil.parseInt(q) == user_id || q.length() > 3 && StrUtil.utf8Str(user_id).contains(q))) { + found = 3; } if (found != 0) { @@ -10998,7 +11020,7 @@ public class MessagesStorage extends BaseController { } if (chat != null && user != null) { DialogsSearchAdapter.DialogSearchResult dialogSearchResult = dialogsResult.get((long) chat.id << 32); - chat.user_id = cursor.intValue(2); + chat.user_id = user_id; chat.a_or_b = cursor.byteArrayValue(3); chat.auth_key = cursor.byteArrayValue(4); chat.ttl = cursor.intValue(5); @@ -11026,8 +11048,10 @@ public class MessagesStorage extends BaseController { if (found == 1) { dialogSearchResult.name = new SpannableStringBuilder(ContactsController.formatName(user.first_name, user.last_name)); ((SpannableStringBuilder) dialogSearchResult.name).setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_chats_secretName)), 0, dialogSearchResult.name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { + } else if (found == 2) { dialogSearchResult.name = AndroidUtilities.generateSearchName("@" + user.username, null, "@" + q); + } else { + dialogSearchResult.name = AndroidUtilities.generateSearchName("ID: " + user_id, null, q); } dialogSearchResult.object = chat; encUsers.add(user); @@ -11086,6 +11110,8 @@ public class MessagesStorage extends BaseController { found = 1; } else if (username != null && username.startsWith(q)) { found = 2; + } else if (NumberUtil.isInteger(q) && (NumberUtil.parseInt(q) == uid || q.length() > 3 && StrUtil.utf8Str(uid).contains(q))) { + found = 3; } if (found != 0) { NativeByteBuffer data = cursor.byteBufferValue(0); @@ -11097,8 +11123,10 @@ public class MessagesStorage extends BaseController { } if (found == 1) { resultArrayNames.add(AndroidUtilities.generateSearchName(user.first_name, user.last_name, q)); - } else { + } else if (found == 2) { resultArrayNames.add(AndroidUtilities.generateSearchName("@" + user.username, null, "@" + q)); + } else { + resultArrayNames.add(AndroidUtilities.generateSearchName("ID: " + uid, null, q)); } resultArray.add(user); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java index 2b46ded09..d8796e1f4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java @@ -962,7 +962,13 @@ public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { cell.useSeparator = (position != getItemCount() - 1 && position != localCount + phoneCount2 + localServerCount - 1 && position != localCount + globalCount + phoneCount + localServerCount - 1); if (position < searchResult.size()) { name = searchResultNames.get(position); - if (name != null && user != null && user.username != null && user.username.length() > 0) { + if (name != null && name.toString().startsWith("ID: ")) { + username = name; + name = null; + if (username instanceof SpannableStringBuilder) { + username = new SpannableStringBuilder(username); + } + } else if (name != null && user != null && user.username != null && user.username.length() > 0) { if (name.toString().startsWith("@" + user.username)) { username = name; name = null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatRightsEditActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatRightsEditActivity.java index 7d6812581..25355efbc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatRightsEditActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatRightsEditActivity.java @@ -315,13 +315,12 @@ public class ChatRightsEditActivity extends BaseFragment { }); listView.setOnItemClickListener((view, position) -> { - if (!canEdit && (!currentChat.creator || currentType != TYPE_ADMIN || position != anonymousRow)) { - return; - } if (position == 0) { Bundle args = new Bundle(); args.putInt("user_id", currentUser.id); presentFragment(new ProfileActivity(args)); + } else if (!canEdit && (!currentChat.creator || currentType != TYPE_ADMIN || position != anonymousRow)) { + return; } else if (position == removeAdminRow) { if (currentType == TYPE_ADMIN) { MessagesController.getInstance(currentAccount).setUserAdminRole(chatId, currentUser, new TLRPC.TL_chatAdminRights(), currentRank, isChannel, getFragmentForAlert(0), isAddingNew); diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/NekoConfig.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/NekoConfig.java index bc8fe1039..f13cd5470 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/NekoConfig.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/NekoConfig.java @@ -250,6 +250,7 @@ public class NekoConfig { disableVibration = preferences.getBoolean("disableVibration", false); autoPauseVideo = preferences.getBoolean("autoPauseVideo", false); + disableProximityEvents = preferences.getBoolean("disableProximityEvents", false); ignoreContentRestrictions = preferences.getBoolean("ignoreContentRestrictions", false); diff --git a/bin/native_libs.sh b/bin/native_libs.sh index c384e9e57..dd720107b 100755 --- a/bin/native_libs.sh +++ b/bin/native_libs.sh @@ -12,8 +12,8 @@ if [ ! -x "$(command -v go)" ]; then # # fi # -# gvm install go1.14 -B -# gvm use go1.14 --default +# gvm install go1.15 -B +# gvm use go1.15 --default echo "install golang please!" diff --git a/bin/update_libs.sh b/bin/update_libs.sh index 58e7cb41f..f5e48e7ab 100755 --- a/bin/update_libs.sh +++ b/bin/update_libs.sh @@ -1,6 +1,6 @@ #!/bin/bash -V2RAY_CORE_VERSION="4.31.0" +V2RAY_CORE_VERSION="4.31.2" if [ ! -x "$(command -v go)" ]; then diff --git a/bin/update_libwebp.kts b/bin/update_libwebp.kts old mode 100644 new mode 100755 diff --git a/bin/upload_alaha.sh b/bin/upload_alaha.sh index 0698369be..2e84d5aac 100755 --- a/bin/upload_alaha.sh +++ b/bin/upload_alaha.sh @@ -1,7 +1,7 @@ #!/bin/bash ./gradlew TMessagesProj:assembleFullRelease \ - TMessagesProj:assembleFullReleaseNoGcm + TMessagesProj:assembleFullReleaseNoGcm || exit 1 trap 'kill $(jobs -p)' SIGINT diff --git a/bin/upload_debug.sh b/bin/upload_debug.sh new file mode 100755 index 000000000..f98db06dc --- /dev/null +++ b/bin/upload_debug.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +./gradlew TMessagesProj:assembleFullRelease || exit 1 + +trap 'kill $(jobs -p)' SIGINT + +function upload() { + + for apk in $outPath/*arm64*.apk; do + + echo ">> Uploading $apk" + + curl https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendDocument \ + -X POST \ + -F chat_id="$TELEGRAM_CHANNEL" \ + -F document="@$apk" \ + --silent --show-error --fail >/dev/null & + + done + + for job in $(jobs -p); do + wait $job + done + +} + +outPath="TMessagesProj/build/outputs/apk/full/release" + +upload \ No newline at end of file