diff --git a/app/build.gradle b/app/build.gradle index ae08a42f..e5b0c4fa 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { minSdkVersion 16 targetSdkVersion 29 - versionCode 45 - versionName '17.9' + versionCode 46 + versionName '18.0' multiDexEnabled true diff --git a/app/src/main/java/awais/instagrabber/activities/Main.java b/app/src/main/java/awais/instagrabber/activities/Main.java index 1d4f68c9..377568d7 100755 --- a/app/src/main/java/awais/instagrabber/activities/Main.java +++ b/app/src/main/java/awais/instagrabber/activities/Main.java @@ -1,5 +1,7 @@ package awais.instagrabber.activities; +import android.app.Notification; +import android.app.PendingIntent; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; @@ -7,8 +9,10 @@ import android.content.res.Resources; import android.database.MatrixCursor; import android.os.AsyncTask; import android.os.Bundle; +import android.os.Handler; import android.os.PersistableBundle; import android.provider.BaseColumns; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -20,9 +24,14 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.SearchView; +import androidx.core.app.NotificationCompat; import androidx.fragment.app.FragmentManager; import androidx.recyclerview.widget.GridLayoutManager; +import org.json.JSONObject; + +import java.net.HttpURLConnection; +import java.net.URL; import java.util.ArrayList; import java.util.Stack; @@ -58,6 +67,8 @@ import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.FlavorTown; import awais.instagrabber.utils.Utils; +import static awais.instagrabber.utils.Utils.CHANNEL_ID; +import static awais.instagrabber.utils.Utils.notificationManager; import static awais.instagrabber.utils.Utils.settingsHelper; public final class Main extends BaseLanguageActivity { @@ -97,7 +108,7 @@ public final class Main extends BaseLanguageActivity { public SearchView searchView; public MenuItem downloadAction, settingsAction, dmsAction, notifAction; public StoryModel[] storyModels; - public String userQuery = null; + public String userQuery = null, cookie, uid = null; public MainHelper mainHelper; public ProfileModel profileModel; public HashtagModel hashtagModel; @@ -117,8 +128,8 @@ public final class Main extends BaseLanguageActivity { FlavorTown.updateCheck(this); FlavorTown.changelogCheck(this); - final String cookie = settingsHelper.getString(Constants.COOKIE); - final String uid = Utils.getUserIdFromCookie(cookie); + cookie = settingsHelper.getString(Constants.COOKIE); + uid = Utils.getUserIdFromCookie(cookie); Utils.setupCookies(cookie); MainHelper.stopCurrentExecutor(); @@ -246,6 +257,14 @@ public final class Main extends BaseLanguageActivity { mainHelper.onRefresh(); mainHelper.onIntent(getIntent()); + + final Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + public void run() { + new GetActivity().execute(); + handler.postDelayed(this, 60000); + } + }, 200); } private void downloadSelectedItems() { @@ -536,4 +555,57 @@ public final class Main extends BaseLanguageActivity { } return false; } + + class GetActivity extends AsyncTask { + String ok = null; + + protected Void doInBackground(Void... lmao) { + final String url = "https://www.instagram.com/graphql/query/?query_hash=0f318e8cfff9cc9ef09f88479ff571fb" + + "&variables={\"id\":\""+uid+"\"}"; + if (!Utils.isEmpty(cookie)) try { + final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); + urlConnection.setUseCaches(false); + urlConnection.setRequestProperty("User-Agent", Constants.USER_AGENT); + urlConnection.setRequestProperty("x-csrftoken", cookie.split("csrftoken=")[1].split(";")[0]); + urlConnection.connect(); + if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { + final JSONObject data = new JSONObject(Utils.readFromConnection(urlConnection)).getJSONObject("data") + .getJSONObject("user").getJSONObject("edge_activity_count").getJSONArray("edges").getJSONObject(0) + .getJSONObject("node"); + ok = (getString(R.string.activity_count_prefix) + " " + String.join(", ", + data.getInt("relationships") == 0 ? null : getString(R.string.activity_count_relationship, data.getInt("relationships")), + data.getInt("usertags") == 0 ? null : getString(R.string.activity_count_usertags, data.getInt("usertags")), + data.getInt("comments") == 0 ? null : getString(R.string.activity_count_comments, data.getInt("comments")), + data.getInt("comment_likes") == 0 ? null : getString(R.string.activity_count_commentlikes, data.getInt("comment_likes")), + data.getInt("likes") == 0 ? null : getString(R.string.activity_count_likes, data.getInt("likes"))) + ".") + .replaceAll("null, ", "").replaceAll(",,+", ",").replaceAll("(,+|null).", "."); + } + urlConnection.disconnect(); + } catch (Throwable ex) { + Log.e("austin_debug", "getactivity: " + ex); + } + return null; + } + + @Override + protected void onPostExecute(Void result) { + if (ok == null) { + if (!Utils.isEmpty(cookie)) Toast.makeText(Main.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); + } + else if (!ok.equals(getString(R.string.activity_count_prefix) + " .")) { + final Notification notif = new NotificationCompat.Builder(Main.this, CHANNEL_ID) + .setCategory(NotificationCompat.CATEGORY_STATUS).setSmallIcon(R.drawable.ic_notif) + .setAutoCancel(true).setPriority(NotificationCompat.PRIORITY_MIN).setContentText(ok) + .setContentIntent( + PendingIntent.getActivity(getApplicationContext(), 1738, + new Intent(getApplicationContext(), NotificationsViewer.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + , PendingIntent.FLAG_UPDATE_CURRENT)) + .build(); + if (notificationManager != null) { + notificationManager.cancel(1800000000); + notificationManager.notify(1800000000, notif); + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/activities/NotificationsViewer.java b/app/src/main/java/awais/instagrabber/activities/NotificationsViewer.java index 79a7c7b1..799d2a8a 100755 --- a/app/src/main/java/awais/instagrabber/activities/NotificationsViewer.java +++ b/app/src/main/java/awais/instagrabber/activities/NotificationsViewer.java @@ -35,8 +35,9 @@ import awais.instagrabber.models.ProfileModel; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Utils; +import static awais.instagrabber.utils.Utils.notificationManager; + public final class NotificationsViewer extends BaseLanguageActivity implements SwipeRefreshLayout.OnRefreshListener { - private NotificationsAdapter notificationsAdapter; private NotificationModel notificationModel; private ActivityNotificationBinding notificationsBinding; private ArrayAdapter commmentDialogAdapter; @@ -47,26 +48,18 @@ public final class NotificationsViewer extends BaseLanguageActivity implements S @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { + notificationManager.cancel(1800000000); + if (Utils.isEmpty(cookie)) { + Toast.makeText(this, R.string.activity_notloggedin, Toast.LENGTH_SHORT).show(); + } super.onCreate(savedInstanceState); notificationsBinding = ActivityNotificationBinding.inflate(getLayoutInflater()); setContentView(notificationsBinding.getRoot()); notificationsBinding.swipeRefreshLayout.setOnRefreshListener(this); - - notificationsBinding.swipeRefreshLayout.setRefreshing(true); + resources = getResources(); setSupportActionBar(notificationsBinding.toolbar.toolbar); notificationsBinding.toolbar.toolbar.setTitle(R.string.action_notif); - - resources = getResources(); - - new NotificationsFetcher(new FetchListener() { - @Override - public void onResult(final NotificationModel[] notificationModels) { - notificationsAdapter = new NotificationsAdapter(notificationModels, clickListener, mentionClickListener); - - notificationsBinding.rvComments.setAdapter(notificationsAdapter); - notificationsBinding.swipeRefreshLayout.setRefreshing(false); - } - }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + onRefresh(); } @Override @@ -75,11 +68,9 @@ public final class NotificationsViewer extends BaseLanguageActivity implements S new NotificationsFetcher(new FetchListener() { @Override public void onResult(final NotificationModel[] notificationModels) { + notificationsBinding.rvComments.setAdapter(new NotificationsAdapter(notificationModels, clickListener, mentionClickListener)); notificationsBinding.swipeRefreshLayout.setRefreshing(false); - - notificationsAdapter = new NotificationsAdapter(notificationModels, clickListener, mentionClickListener); - - notificationsBinding.rvComments.setAdapter(notificationsAdapter); + new SeenAction().execute(); } }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } @@ -150,7 +141,8 @@ public final class NotificationsViewer extends BaseLanguageActivity implements S urlConnection.setUseCaches(false); urlConnection.setRequestProperty("User-Agent", Constants.USER_AGENT); urlConnection.setRequestProperty("x-csrftoken", - Utils.settingsHelper.getString(Constants.COOKIE).split("csrftoken=")[1].split(";")[0]);urlConnection.connect(); + Utils.settingsHelper.getString(Constants.COOKIE).split("csrftoken=")[1].split(";")[0]); + urlConnection.connect(); if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { ok = true; } @@ -169,4 +161,23 @@ public final class NotificationsViewer extends BaseLanguageActivity implements S else Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); } } + + class SeenAction extends AsyncTask { + protected Void doInBackground(Void... lmao) { + try { + final HttpURLConnection urlConnection = + (HttpURLConnection) new URL("https://www.instagram.com/web/activity/mark_checked/").openConnection(); + urlConnection.setRequestMethod("POST"); + urlConnection.setUseCaches(false); + urlConnection.setRequestProperty("User-Agent", Constants.USER_AGENT); + urlConnection.setRequestProperty("x-csrftoken", + Utils.settingsHelper.getString(Constants.COOKIE).split("csrftoken=")[1].split(";")[0]); + urlConnection.connect(); + urlConnection.disconnect(); + } catch (Throwable ex) { + Log.e("austin_debug", "seen: " + ex); + } + return null; + } + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/asyncs/NotificationsFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/NotificationsFetcher.java index 9790fa93..d1bdbd22 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/NotificationsFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/NotificationsFetcher.java @@ -19,6 +19,7 @@ import awais.instagrabber.utils.Utils; import awaisomereport.LogCollector; import static awais.instagrabber.utils.Utils.logCollector; +import static awais.instagrabber.utils.Utils.settingsHelper; public final class NotificationsFetcher extends AsyncTask { private final FetchListener fetchListener; @@ -31,6 +32,7 @@ public final class NotificationsFetcher extends AsyncTask list = listViewModel.getList().getValue(); final List newList = Arrays.asList(result.getItems()); list = list != null ? new LinkedList<>(list) : new LinkedList<>(); - if (hasSentSomething) { + if (hasSentSomething || hasDeletedSomething) { list = newList; - hasSentSomething = false; final Handler handler = new Handler(); - handler.postDelayed(() -> { + if (hasSentSomething) handler.postDelayed(() -> { if (messageList != null) { messageList.smoothScrollToPosition(0); } }, 200); + hasSentSomething = false; + hasDeletedSomething = false; } else { list.addAll(newList); } @@ -244,9 +248,7 @@ public class DirectMessageThreadFragment extends Fragment { sendText(null, directItemModel.getItemId(), directItemModel.isLiked()); } else if (w == 2) { - if (String.valueOf(directItemModel.getUserId()).equals(myId)) { - // unsend: https://www.instagram.com/direct_v2/web/threads/340282366841710300949128288687654467119/items/29473546990090204551245070881259520/delete/ - } + if (String.valueOf(directItemModel.getUserId()).equals(myId)) new Unsend().execute(); else searchUsername(getUser(directItemModel.getUserId()).getUsername()); } }; @@ -430,4 +432,41 @@ public class DirectMessageThreadFragment extends Fragment { return list; } } + + class Unsend extends AsyncTask { + protected Void doInBackground(Void... lmao) { + final String url = "https://i.instagram.com/api/v1/direct_v2/threads/"+threadId+"/items/"+directItemModel.getItemId()+"/delete/"; + try { + String urlParameters = "_csrftoken=" + cookie.split("csrftoken=")[1].split(";")[0] + +"&_uuid=" + Utils.settingsHelper.getString(Constants.DEVICE_UUID); + final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); + urlConnection.setRequestMethod("POST"); + urlConnection.setUseCaches(false); + urlConnection.setRequestProperty("User-Agent", Constants.I_USER_AGENT); + urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + urlConnection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length)); + urlConnection.setDoOutput(true); + DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream()); + wr.writeBytes(urlParameters); + wr.flush(); + wr.close(); + urlConnection.connect(); + if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { + hasDeletedSomething = true; + } + urlConnection.disconnect(); + } catch (Throwable ex) { + Log.e("austin_debug", "unsend: " + ex); + } + return null; + } + + @Override + protected void onPostExecute(Void result) { + if (hasDeletedSomething) { + directItemModel = null; + new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + } } diff --git a/app/src/main/java/awais/instagrabber/utils/UpdateChecker.java b/app/src/main/java/awais/instagrabber/utils/UpdateChecker.java index 0bde36bc..39cae1c4 100755 --- a/app/src/main/java/awais/instagrabber/utils/UpdateChecker.java +++ b/app/src/main/java/awais/instagrabber/utils/UpdateChecker.java @@ -33,9 +33,9 @@ public final class UpdateChecker extends AsyncTask { conn.connect(); final int responseCode = conn.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP) { + if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP && !BuildConfig.DEBUG) { version = conn.getHeaderField("Location").split("/v")[1]; - return Float.parseFloat(version) > Float.parseFloat(BuildConfig.VERSION_NAME); + return version != BuildConfig.VERSION_NAME; } conn.disconnect(); diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index a816192b..738531f4 100755 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -175,8 +175,8 @@ public final class Utils { clipString = clipString.substring((isHttps ? 22 : 21) + wwwDel); final char firstChar = clipString.charAt(0); - if (clipString.startsWith("p/") || clipString.startsWith("reel/")) { - clipString = clipString.substring(clipString.startsWith("p/") ? 2 : 5); + if (clipString.startsWith("p/") || clipString.startsWith("reel/") || clipString.startsWith("tv/")) { + clipString = clipString.substring(clipString.startsWith("p/") ? 2 : (clipString.startsWith("tv/") ? 3 : 5)); type = IntentModelType.POST; } else if (clipString.startsWith("explore/tags/")) { clipString = clipString.substring(13); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 287e6217..b3459430 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -222,4 +222,11 @@ InstaGrabber\nCopyright (C) 2019 AWAiS\nCopyright (C) 2020 Austin Huang, Ammar Githam\n\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See https://www.gnu.org/licenses/.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR \'\'AS IS\'\' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nSee project page for third-party attributions. Select Picture Uploading... + You have: + %s follows + %s comments + %s comment likes + %s usertags + %s likes + You logged out before clicking this notification?!