From 8d3621dbe7eb25ba904385e0bcfd06d158511e41 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 4 Mar 2025 15:19:51 +0100 Subject: [PATCH] Fix Nitter instances --- .../client/entities/nitter/Nitter.java | 85 ++++++++++- .../android/mastodon/helper/Helper.java | 5 +- .../mastodon/ui/drawer/StatusAdapter.java | 38 +++-- .../timeline/FragmentMastodonTimeline.java | 58 ++++---- .../viewmodel/mastodon/TimelinesVM.java | 134 +++++++----------- .../layouts/mastodon/layout/drawer_status.xml | 9 +- 6 files changed, 197 insertions(+), 132 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/mastodon/client/entities/nitter/Nitter.java b/app/src/main/java/app/fedilab/android/mastodon/client/entities/nitter/Nitter.java index 015e0c7f..75d77719 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/client/entities/nitter/Nitter.java +++ b/app/src/main/java/app/fedilab/android/mastodon/client/entities/nitter/Nitter.java @@ -14,10 +14,12 @@ package app.fedilab.android.mastodon.client.entities.nitter; * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see . */ + import android.content.Context; import androidx.annotation.NonNull; +import org.jsoup.select.Elements; import org.simpleframework.xml.Element; import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Namespace; @@ -29,6 +31,7 @@ import java.net.IDN; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.concurrent.ThreadLocalRandom; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -82,7 +85,8 @@ public class Nitter implements Serializable { status.text = feedItem.title; status.content = status.content.replaceAll("]*src=\"[^\"]+\"[^>]*>", ""); status.visibility = "public"; - status.created_at = Helper.stringToDateWithFormat(context, feedItem.pubDate, "EEE, dd MMM yyyy HH:mm:ss zzz"); + String dateFormat = "E', 'dd' 'MMM' 'yyyy' 'hh:m:s' GMT'"; + status.created_at = Helper.stringToDateWithFormat(context, feedItem.pubDate, dateFormat); status.uri = feedItem.guid; status.url = feedItem.link; if (!accounts.containsKey(feedItem.creator)) { @@ -177,4 +181,83 @@ public class Nitter implements Serializable { } + public static Status nitterHTMLParser(Context context, org.jsoup.nodes.Element timelineItem, String nitterInstance) { + + if(timelineItem == null) { + return null; + } + Status status = new Status(); + Account account = new Account(); + String fedilabInstance = "nitter.fedilab.app"; + + + org.jsoup.nodes.Element messageLink; + if(timelineItem.select(".quote-text").html().isEmpty()) { + status.content = timelineItem.select(".tweet-content").html(); + status.text = timelineItem.select(".tweet-content").text(); + status.url = "https://"+ nitterInstance +timelineItem.select(".tweet-link").attr("href"); + messageLink = timelineItem.select(".tweet-link").first(); + } else { + status.content = timelineItem.select(".quote-text").html(); + status.text = timelineItem.select(".quote-text").text(); + status.url = "https://"+ nitterInstance +timelineItem.select(".quote-link").attr("href"); + messageLink = timelineItem.select(".quote-link").first(); + } + status.uri = status.url; + + String status_id = String.valueOf(ThreadLocalRandom.current().nextLong(10,10000000));; + if(messageLink != null){ + String[] splitLink = messageLink.attr("href").split("/"); + status_id = splitLink[splitLink.length-1]; + } + String pubDate = timelineItem.select(".tweet-date").select("a").attr("title"); + org.jsoup.nodes.Element nameElement = timelineItem.select(".fullname").first(); + String name = nameElement!= null?nameElement.text():""; + org.jsoup.nodes.Element userNameElement = timelineItem.select(".username").first(); + String userName = userNameElement!= null?userNameElement.text().replace("@",""):""; + String avatar = "https://" + fedilabInstance + timelineItem.select(".avatar").attr("src"); + account.id = userName; + account.acct = userName; + if(timelineItem.select(".replying-to").html().isEmpty()) { + account.username = userName; + account.display_name = name; + } else { + account.display_name = timelineItem.select(".fullname").text() +" " +timelineItem.select(".replying-to").text(); + } + + account.avatar = avatar; + account.avatar_static = avatar; + account.url = "https://"+ nitterInstance +"/" + userName; + status.id = status_id; + status.account = account; + + + + Elements imageElements = timelineItem.select(".attachments").select("img"); + Elements videoElements = timelineItem.select(".attachments").select("video"); + ArrayList attachmentList = new ArrayList<>(); + for(org.jsoup.nodes.Element imageElement: imageElements) { + Attachment attachment = new Attachment(); + attachment.type = "image"; + attachment.url = "https://"+fedilabInstance+imageElement.attr("src"); + attachment.preview_url = "https://"+fedilabInstance+imageElement.attr("src"); + attachment.id = imageElement.attr("src"); + attachmentList.add(attachment); + } + for(org.jsoup.nodes.Element videoElement: videoElements) { + Attachment attachment = new Attachment(); + attachment.type = "video"; + attachment.url = "https://"+fedilabInstance+videoElement.child(0).attr("src"); + attachment.preview_url = "https://"+fedilabInstance+videoElement.attr("poster"); + attachment.id = videoElement.attr("poster"); + attachmentList.add(attachment); + } + status.visibility = "public"; + status.media_attachments = attachmentList; + String dateFormat = "MMM d', 'yyyy' · 'h:m a' UTC'"; + status.created_at = Helper.stringToDateWithFormat(context, pubDate, dateFormat); + return status; + } + + } diff --git a/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java b/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java index 70ce652f..d9c0a002 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java @@ -61,7 +61,6 @@ import android.provider.OpenableColumns; import android.text.Html; import android.text.TextUtils; import android.util.DisplayMetrics; -import android.util.Log; import android.util.TypedValue; import android.view.Menu; import android.view.MenuItem; @@ -647,9 +646,9 @@ public class Helper { */ public static Date stringToDateWithFormat(Context context, String stringDate, String format) { if (stringDate == null) - return null; + return new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat(format, Locale.US); - Date date = null; + Date date = new Date(); try { date = dateFormat.parse(stringDate); } catch (java.text.ParseException ignored) { diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java index dd58df77..2c2101d6 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java @@ -58,7 +58,6 @@ import android.os.Looper; import android.text.Html; import android.text.SpannableString; import android.text.TextUtils; -import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -567,15 +566,19 @@ public class StatusAdapter extends RecyclerView.Adapter } return; } - Intent intent = new Intent(context, ContextActivity.class); - Bundle args = new Bundle(); - args.putSerializable(Helper.ARG_STATUS, statusToDeal.quote); - new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> { - Bundle bundle = new Bundle(); - bundle.putLong(Helper.ARG_INTENT_ID, bundleId); - intent.putExtras(bundle); - context.startActivity(intent); - }); + if (!remote) { + Intent intent = new Intent(context, ContextActivity.class); + Bundle args = new Bundle(); + args.putSerializable(Helper.ARG_STATUS, statusToDeal.quote); + new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> { + Bundle bundle = new Bundle(); + bundle.putLong(Helper.ARG_INTENT_ID, bundleId); + intent.putExtras(bundle); + context.startActivity(intent); + }); + } else { + Helper.openBrowser(context,statusToDeal.quote.url); + } }); holder.binding.quotedMessage.cardviewContainer.setStrokeColor(ThemeHelper.getAttColor(context, R.attr.colorPrimary)); holder.binding.quotedMessage.statusContent.setText( @@ -1453,15 +1456,24 @@ public class StatusAdapter extends RecyclerView.Adapter //--- BOOSTER INFO --- if (status.reblog != null) { - MastodonHelper.loadPPMastodon(holder.binding.statusBoosterAvatar, status.account); - + if(status.account.avatar != null) { + MastodonHelper.loadPPMastodon(holder.binding.statusBoosterAvatar, status.account); + holder.binding.statusBoosterAvatar.setVisibility(View.VISIBLE); + } else { + holder.binding.statusBoosterAvatar.setVisibility(View.GONE); + } holder.binding.statusBoosterDisplayName.setText( status.account.getSpanDisplayName(context, new WeakReference<>(holder.binding.statusBoosterDisplayName)), TextView.BufferType.SPANNABLE); holder.binding.statusBoosterInfo.setVisibility(View.VISIBLE); - holder.binding.statusBoosterUsername.setText(String.format("@%s", status.account.acct)); + if(status.account.acct != null) { + holder.binding.statusBoosterUsername.setText(String.format("@%s", status.account.acct)); + holder.binding.statusBoosterUsername.setVisibility(View.VISIBLE); + } else { + holder.binding.statusBoosterUsername.setVisibility(View.GONE); + } } else { holder.binding.statusBoosterInfo.setVisibility(View.GONE); } diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java b/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java index 00020bee..156d85b0 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java @@ -17,6 +17,7 @@ package app.fedilab.android.mastodon.ui.fragment.timeline; import static app.fedilab.android.BaseMainActivity.currentInstance; import static app.fedilab.android.BaseMainActivity.networkAvailable; +import static app.fedilab.android.mastodon.helper.Helper.TAG; import android.content.BroadcastReceiver; import android.content.Context; @@ -418,7 +419,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. search = bundle.getString(Helper.ARG_SEARCH_KEYWORD, null); searchCache = bundle.getString(Helper.ARG_SEARCH_KEYWORD_CACHE, null); pinnedTimeline = (PinnedTimeline) bundle.getSerializable(Helper.ARG_REMOTE_INSTANCE); - + canBeFederated = true; if (pinnedTimeline != null && pinnedTimeline.remoteInstance != null) { if (pinnedTimeline.remoteInstance.type != RemoteInstance.InstanceType.NITTER) { remoteInstance = pinnedTimeline.remoteInstance.host; @@ -490,7 +491,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. initialStatuses = null; lockForResumeCall = 0; - canBeFederated = true; + retry_for_home_done = false; SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); boolean displayScrollBar = sharedpreferences.getBoolean(getString(R.string.SET_TIMELINE_SCROLLBAR), false); @@ -562,7 +563,12 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } //Update the timeline with new statuses int insertedStatus; - if (timelineType != Timeline.TimeLineEnum.TREND_MESSAGE_PUBLIC && timelineType != Timeline.TimeLineEnum.TREND_MESSAGE ) { + if(pinnedTimeline!= null && pinnedTimeline.remoteInstance != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER) { + insertedStatus = fetched_statuses.statuses.size(); + int fromPosition = timelineStatuses.size(); + timelineStatuses.addAll(fetched_statuses.statuses); + statusAdapter.notifyItemRangeInserted(fromPosition, insertedStatus); + } else if (timelineType != Timeline.TimeLineEnum.TREND_MESSAGE_PUBLIC && timelineType != Timeline.TimeLineEnum.TREND_MESSAGE ) { insertedStatus = updateStatusListWith(fetched_statuses.statuses); } else { //Trends cannot be ordered by id insertedStatus = fetched_statuses.statuses.size(); @@ -679,8 +685,8 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. if (max_id == null || (statuses.pagination.max_id != null && Helper.compareTo(statuses.pagination.max_id, max_id) < 0) || timelineType.getValue().startsWith("TREND_")) { max_id = statuses.pagination.max_id; } - //For Lemmy pagination - if (pinnedTimeline != null && pinnedTimeline.remoteInstance != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.LEMMY) { + //For Lemmy and Nitter pagination + if (pinnedTimeline != null && pinnedTimeline.remoteInstance != null && (pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.LEMMY || pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER)) { max_id = statuses.pagination.max_id; } if (min_id == null || (statuses.pagination.min_id != null && Helper.compareTo(statuses.pagination.min_id, min_id) > 0)) { @@ -1066,6 +1072,26 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } }); } + }//LEMMY TIMELINES + else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.LEMMY) { + if (direction == null) { + timelinesVM.getLemmy(remoteInstance, lemmy_post_id, null, MastodonHelper.statusesPerCall(requireActivity())) + .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); + } else if (direction == DIRECTION.BOTTOM) { + timelinesVM.getLemmy(remoteInstance, lemmy_post_id, max_id, MastodonHelper.statusesPerCall(requireActivity())) + .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false, true, fetchStatus)); + } else if (direction == DIRECTION.TOP) { + flagLoading = false; + } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { + timelinesVM.getLemmy(remoteInstance, lemmy_post_id, null, MastodonHelper.statusesPerCall(requireActivity())) + .observe(getViewLifecycleOwner(), statusesRefresh -> { + if (statusAdapter != null) { + dealWithPagination(statusesRefresh, direction, true, true, fetchStatus); + } else { + initializeStatusesCommonView(statusesRefresh); + } + }); + } }//MISSKEY TIMELINES else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.MISSKEY) { if (direction == null) { @@ -1087,27 +1113,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } }); } - } //LEMMY TIMELINES - else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.LEMMY) { - if (direction == null) { - timelinesVM.getLemmy(remoteInstance, lemmy_post_id, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); - } else if (direction == DIRECTION.BOTTOM) { - timelinesVM.getLemmy(remoteInstance, lemmy_post_id, max_id, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false, true, fetchStatus)); - } else if (direction == DIRECTION.TOP) { - flagLoading = false; - } else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) { - timelinesVM.getLemmy(remoteInstance, lemmy_post_id, null, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), statusesRefresh -> { - if (statusAdapter != null) { - dealWithPagination(statusesRefresh, direction, true, true, fetchStatus); - } else { - initializeStatusesCommonView(statusesRefresh); - } - }); - } - }//PEERTUBE TIMELINES + } //PEERTUBE TIMELINES else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.PEERTUBE) { if (direction == null) { timelinesVM.getPeertube(remoteInstance, null, MastodonHelper.statusesPerCall(requireActivity())) diff --git a/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/TimelinesVM.java b/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/TimelinesVM.java index 75f8578e..ff4df4e2 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/TimelinesVM.java +++ b/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/TimelinesVM.java @@ -41,15 +41,12 @@ import java.net.IDN; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import app.fedilab.android.BuildConfig; import app.fedilab.android.R; import app.fedilab.android.activities.MainActivity; import app.fedilab.android.mastodon.client.endpoints.MastodonTimelinesService; import app.fedilab.android.mastodon.client.entities.api.Account; -import app.fedilab.android.mastodon.client.entities.api.Attachment; import app.fedilab.android.mastodon.client.entities.api.Conversation; import app.fedilab.android.mastodon.client.entities.api.Conversations; import app.fedilab.android.mastodon.client.entities.api.Marker; @@ -255,19 +252,12 @@ public class TimelinesVM extends AndroidViewModel { public LiveData getNitterRSS( String accountsStr, String max_position) { - Context context = getApplication().getApplicationContext(); - SharedPreferences sharedpreferences = PreferenceManager - .getDefaultSharedPreferences(context); - String instance = sharedpreferences.getString(context.getString(R.string.SET_NITTER_HOST), context.getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase(); - if (instance.trim().isEmpty()) { - instance = context.getString(R.string.DEFAULT_NITTER_HOST); - } - MastodonTimelinesService mastodonTimelinesService = initInstanceXMLOnly(instance); + MastodonTimelinesService mastodonTimelinesService = initInstanceXMLOnly("nitter.fedilab.app"); accountsStr = accountsStr.replaceAll("\\s", ","); statusesMutableLiveData = new MutableLiveData<>(); String finalAccountsStr = accountsStr; - String finalInstance = instance; + new Thread(() -> { Call publicTlCall = mastodonTimelinesService.getNitter(finalAccountsStr, max_position); Statuses statuses = new Statuses(); @@ -280,7 +270,7 @@ public class TimelinesVM extends AndroidViewModel { if (rssResponse != null && rssResponse.mFeedItems != null) { for (Nitter.FeedItem feedItem : rssResponse.mFeedItems) { if (!feedItem.title.startsWith("RT by")) { - Status status = Nitter.convert(getApplication(), finalInstance, feedItem); + Status status = Nitter.convert(getApplication(), "nitter.fedilab.app", feedItem); statusList.add(status); } } @@ -314,99 +304,75 @@ public class TimelinesVM extends AndroidViewModel { String max_position) { statusesMutableLiveData = new MutableLiveData<>(); Context context = getApplication().getApplicationContext(); - SharedPreferences sharedpreferences = PreferenceManager - .getDefaultSharedPreferences(context); - String instance = sharedpreferences.getString(context.getString(R.string.SET_NITTER_HOST), context.getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase(); - if (instance.trim().isEmpty()) { - instance = context.getString(R.string.DEFAULT_NITTER_HOST); - } - //TODO: remove after tests - instance = "nitter.privacydev.net"; - + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); + final String nitterInstance = sharedpreferences.getString(context.getString(R.string.SET_NITTER_HOST), context.getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase(); + final String fedilabInstance = "nitter.fedilab.app"; accountsStr = accountsStr.replaceAll("\\s", ",").replaceAll(",,",","); - String maxposition = max_position == null ? "" : "?max_position="+max_position; - String url = "https://" + instance + "/" + accountsStr + "/with_replies"+maxposition; - OkHttpClient client = new OkHttpClient.Builder() - .connectTimeout(10, TimeUnit.SECONDS) - - .writeTimeout(10, TimeUnit.SECONDS) - .readTimeout(10, TimeUnit.SECONDS).build(); + String cursor = max_position == null ? "" : max_position; + String url = "https://"+fedilabInstance+"/" + accountsStr + "/with_replies" +cursor; Request request = new Request.Builder() + .header("User-Agent", context.getString(R.string.app_name) + "/" + BuildConfig.VERSION_NAME + "/" + BuildConfig.VERSION_CODE) .url(url) - .header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7") - .header("accept-language","en-US;q=0.6") - .header("dnt","1") - .header("user-agent","Mozilla/5.0 (X11; Linux i686; rv:135.0) Gecko/20100101 Firefox/135.0") - .get() .build(); - String finalInstance = instance; - String finalInstance1 = instance; - client.newCall(request).enqueue(new Callback() { + + okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(@NonNull okhttp3.Call call, @NonNull IOException e) { } @Override public void onResponse(@NonNull okhttp3.Call call, @NonNull okhttp3.Response response) throws IOException { - Statuses statuses = new Statuses(); + if (response.isSuccessful()) { try { String data = response.body().string(); - Document doc = Jsoup.parse(data); Elements timelineItems = doc.select(".timeline-item"); List statusList = new ArrayList<>(); for(Element timelineItem: timelineItems) { - - //Not a RT - if(timelineItem.select(".icon-retweet").html().trim().isEmpty()) { - Status status = new Status(); - Account account = new Account(); - - String[] splitLink = timelineItem.select(".tweet-link").text().split("/"); - String status_id = splitLink[splitLink.length-1]; - String pubDate = timelineItem.select(".tweet-date").select("a").attr("title"); - String name = timelineItem.select(".fullname").text(); - String userName = timelineItem.select(".username").text(); - String avatar = "https://"+ finalInstance + timelineItem.select(".avatar").attr("src"); - account.id = userName; - account.acct = userName; - account.username = userName; - account.display_name = name; - account.avatar = avatar; - account.avatar_static = avatar; - account.url = "https://"+ finalInstance +"/" + userName; - - status.id = status_id; - status.account = account; - status.url = "https://"+ finalInstance +timelineItem.select(".tweet-link").attr("href"); - status.content = timelineItem.select(".tweet-content").text(); - Pattern imgPattern = Pattern.compile("]*src=\"([^\"]+)\"[^>]*>"); - Matcher matcher = imgPattern.matcher(status.content); - String description = status.content; - ArrayList attachmentList = new ArrayList<>(); - while (matcher.find()) { - description = description.replaceAll(Pattern.quote(matcher.group()), ""); - Attachment attachment = new Attachment(); - attachment.type = "image"; - attachment.url = matcher.group(1); - attachment.preview_url = matcher.group(1); - attachment.id = matcher.group(1); - attachmentList.add(attachment); - } - status.visibility = "public"; - status.media_attachments = attachmentList; - String dateformat = "MMM d', 'yyyy' · 'h:m a' UTC'"; - status.created_at = Helper.stringToDateWithFormat(context, pubDate, dateformat); - statusList.add(status); + if(!timelineItem.select(".unavailable").html().isEmpty() || timelineItem.select(".tweet-link").attr("href").isEmpty()) { + continue; } + //RT + boolean isBoosted = !timelineItem.select(".retweet-header").select(".icon-container").isEmpty(); + Status status = Nitter.nitterHTMLParser(context, timelineItem, nitterInstance); + + //Quoted message + + if(!timelineItem.select(".quote").html().isEmpty()) { + status.quote = Nitter.nitterHTMLParser(context, timelineItem.select(".quote").first(), nitterInstance); + } + + Status finalStatus; + if(isBoosted) { + finalStatus = new Status(); + finalStatus.reblog = status; + finalStatus.id = status.id; + finalStatus.visibility = "public"; + finalStatus.url = "https://"+ nitterInstance +timelineItem.select(".tweet-link").attr("href"); + finalStatus.uri = finalStatus.url; + Account acccountOriginal = new Account(); + acccountOriginal.display_name = timelineItem.select(".retweet-header").select(".icon-container").text(); + finalStatus.account = acccountOriginal; + } else { + finalStatus = status; + } + statusList.add(finalStatus); } + statuses.statuses = statusList; - String max_id = response.headers().get("min-id"); + Elements elementsShow = doc.select(".show-more a"); + Element showMore = null; + if(elementsShow.size() > 1) { + showMore = elementsShow.get(elementsShow.size()-1); + } else { + showMore = elementsShow.get(0); + } + String cursor = showMore.attr("href"); statuses.pagination = new Pagination(); - statuses.pagination.max_id = max_id; + statuses.pagination.max_id = cursor; } catch (Exception e) { e.printStackTrace(); } diff --git a/app/src/main/res/layouts/mastodon/layout/drawer_status.xml b/app/src/main/res/layouts/mastodon/layout/drawer_status.xml index cb178842..2d0a3c8a 100644 --- a/app/src/main/res/layouts/mastodon/layout/drawer_status.xml +++ b/app/src/main/res/layouts/mastodon/layout/drawer_status.xml @@ -612,7 +612,7 @@