With Nitter

This commit is contained in:
Thomas 2022-06-29 14:13:33 +02:00
parent 2caa24c79b
commit f47866660e
5 changed files with 107 additions and 88 deletions

View file

@ -23,7 +23,6 @@ import app.fedilab.android.client.entities.api.MastodonList;
import app.fedilab.android.client.entities.api.Status; import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.misskey.MisskeyNote; import app.fedilab.android.client.entities.misskey.MisskeyNote;
import app.fedilab.android.client.entities.nitter.Nitter; import app.fedilab.android.client.entities.nitter.Nitter;
import app.fedilab.android.client.entities.nitter.NitterAccount;
import app.fedilab.android.client.entities.peertube.PeertubeVideo; import app.fedilab.android.client.entities.peertube.PeertubeVideo;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.http.Body; import retrofit2.http.Body;
@ -233,7 +232,7 @@ public interface MastodonTimelinesService {
); );
@GET("{account}/rss") @GET("{account}/rss")
Call<NitterAccount> getNitterAccount( Call<Nitter> getNitterAccount(
@Path("account") String account @Path("account") String account
); );

View file

@ -1,18 +1,31 @@
package app.fedilab.android.client.entities.nitter; package app.fedilab.android.client.entities.nitter;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
*
* This 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.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.content.Context; import android.content.Context;
import com.google.gson.Gson; import androidx.annotation.NonNull;
import com.google.gson.GsonBuilder;
import org.simpleframework.xml.Element; import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementList; import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Namespace;
import org.simpleframework.xml.Path;
import org.simpleframework.xml.Root; import org.simpleframework.xml.Root;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -27,19 +40,26 @@ import okhttp3.OkHttpClient;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Response; import retrofit2.Response;
import retrofit2.Retrofit; import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.simplexml.SimpleXmlConverterFactory; import retrofit2.converter.simplexml.SimpleXmlConverterFactory;
@Root(name = "rss", strict = false) @Root(name = "rss", strict = false)
public class Nitter implements Serializable { public class Nitter implements Serializable {
public static HashMap<String, NitterAccount> accounts = new HashMap<>(); public static HashMap<String, Nitter> accounts = new HashMap<>();
@Element(name = "channel")
public Channel channel; @Element(name = "title")
@Path("channel")
public String title;
@Element(name = "image")
@Path("channel")
public Image image;
@ElementList(name = "item", inline = true)
@Path("channel")
public List<FeedItem> mFeedItems;
public static MastodonTimelinesService initInstanceXMLOnly(Context context, String instance) { public static MastodonTimelinesService initInstanceXMLOnly(Context context, String instance) {
Gson gson = new GsonBuilder().setDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz").create();
OkHttpClient okHttpClient = new OkHttpClient.Builder() OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS) .connectTimeout(60, TimeUnit.SECONDS)
@ -48,7 +68,6 @@ public class Nitter implements Serializable {
.build(); .build();
Retrofit retrofit = new Retrofit.Builder() Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance) .baseUrl("https://" + instance)
.addConverterFactory(GsonConverterFactory.create(gson))
.addConverterFactory(SimpleXmlConverterFactory.create()) .addConverterFactory(SimpleXmlConverterFactory.create())
.client(okHttpClient) .client(okHttpClient)
.build(); .build();
@ -57,21 +76,21 @@ public class Nitter implements Serializable {
public static Status convert(Context context, String instance, FeedItem feedItem) { public static Status convert(Context context, String instance, FeedItem feedItem) {
Status status = new Status(); Status status = new Status();
status.id = feedItem.pubDate.toString(); status.id = feedItem.pubDate;
status.content = feedItem.title; status.content = feedItem.title;
status.text = feedItem.title; status.text = feedItem.title;
status.visibility = "public"; status.visibility = "public";
status.created_at = feedItem.pubDate; status.created_at = Helper.stringToDateWithFormat(context, feedItem.pubDate, "EEE, dd MMM yyyy HH:mm:ss zzz");
status.uri = feedItem.guid; status.uri = feedItem.guid;
status.url = feedItem.link; status.url = feedItem.link;
if (feedItem.creator != null && !accounts.containsValue(feedItem.creator)) { if (!accounts.containsValue(feedItem.creator)) {
MastodonTimelinesService mastodonTimelinesService = initInstanceXMLOnly(context, instance); MastodonTimelinesService mastodonTimelinesService = initInstanceXMLOnly(context, instance);
Call<NitterAccount> accountCall = mastodonTimelinesService.getNitterAccount(instance); Call<Nitter> accountCall = mastodonTimelinesService.getNitterAccount(feedItem.creator.replace("@", ""));
if (accountCall != null) { if (accountCall != null) {
try { try {
Response<NitterAccount> publicTlResponse = accountCall.execute(); Response<Nitter> publicTlResponse = accountCall.execute();
if (publicTlResponse.isSuccessful()) { if (publicTlResponse.isSuccessful()) {
NitterAccount nitterAccount = publicTlResponse.body(); Nitter nitterAccount = publicTlResponse.body();
accounts.put(feedItem.creator, nitterAccount); accounts.put(feedItem.creator, nitterAccount);
} }
} catch (Exception e) { } catch (Exception e) {
@ -79,58 +98,73 @@ public class Nitter implements Serializable {
} }
} }
} }
NitterAccount nitterAccount = accounts.get(feedItem.creator); Nitter nitterAccount = accounts.get(feedItem.creator);
if (nitterAccount != null) { if (nitterAccount != null) {
app.fedilab.android.client.entities.api.Account account = new app.fedilab.android.client.entities.api.Account(); app.fedilab.android.client.entities.api.Account account = new app.fedilab.android.client.entities.api.Account();
String[] names = nitterAccount.channel.image.title.split("/"); String[] names = nitterAccount.image.title.split("/");
account.id = feedItem.guid; account.id = feedItem.guid;
account.acct = names[1]; account.acct = names[1];
account.username = names[1]; account.username = names[1];
account.display_name = names[0]; account.display_name = names[0];
account.avatar = nitterAccount.channel.image.url; account.avatar = nitterAccount.image.url;
account.avatar_static = nitterAccount.channel.image.url; account.avatar_static = nitterAccount.image.url;
account.url = nitterAccount.channel.image.link; account.url = nitterAccount.image.link;
status.account = account; status.account = account;
} }
Pattern imgPattern = Pattern.compile("<img [^>]*src=\"([^\"]+)\"[^>]*>"); if (feedItem.description != null) {
Matcher matcher = imgPattern.matcher(feedItem.description); Pattern imgPattern = Pattern.compile("<img [^>]*src=\"([^\"]+)\"[^>]*>");
String description = feedItem.description; Matcher matcher = imgPattern.matcher(feedItem.description);
ArrayList<Attachment> attachmentList = new ArrayList<>(); String description = feedItem.description;
while (matcher.find()) { ArrayList<Attachment> attachmentList = new ArrayList<>();
description = description.replaceAll(Pattern.quote(matcher.group()), ""); while (matcher.find()) {
Attachment attachment = new Attachment(); description = description.replaceAll(Pattern.quote(matcher.group()), "");
attachment.type = "image"; Attachment attachment = new Attachment();
attachment.url = matcher.group(1); attachment.type = "image";
attachment.preview_url = matcher.group(1); attachment.url = matcher.group(1);
attachment.id = matcher.group(1); attachment.preview_url = matcher.group(1);
attachmentList.add(attachment); attachment.id = matcher.group(1);
attachmentList.add(attachment);
}
status.media_attachments = attachmentList;
} }
status.media_attachments = attachmentList;
return status; return status;
} }
@Root(name = "channel", strict = false) @Root(name = "image", strict = false)
public static class Channel implements Serializable { public static class Image implements Serializable {
@ElementList(name = "item") @Element(name = "title")
public List<FeedItem> mFeedItems; public String title;
@Element(name = "url")
public String url;
@Element(name = "link")
public String link;
} }
@Root(name = "item", strict = false) @Root(name = "item", strict = false)
public static class FeedItem implements Serializable { public static class FeedItem implements Serializable {
@ElementList(name = "dc:creator", required = false) @Namespace(prefix = "dc")
@Element(name = "creator", required = false)
public String creator; public String creator;
@ElementList(name = "title") @Element(name = "title", required = false)
public String title; public String title;
@ElementList(name = "description", required = false) @Element(name = "description", required = false)
public String description; public String description;
@ElementList(name = "pubDate") @Element(name = "pubDate", required = false)
public Date pubDate; public String pubDate;
@ElementList(name = "guid", required = false) @Element(name = "guid", required = false)
public String guid; public String guid;
@ElementList(name = "link", required = false) @Element(name = "link", required = false)
public String link; public String link;
@NonNull
@Override
public String toString() {
return "creator: " + creator + "\r" + "title: " + title + "\r" + "description: "
+ description + "\r" + "pubDate: " + pubDate + "\r"
+ "guid: " + guid + "\r" + "link: " + link;
}
} }
} }

View file

@ -1,36 +0,0 @@
package app.fedilab.android.client.entities.nitter;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;
import java.io.Serializable;
import java.util.HashMap;
@Root(name = "rss", strict = false)
public class NitterAccount implements Serializable {
public static HashMap<String, NitterAccount> accounts = new HashMap<>();
@Element(name = "channel")
public Channel channel;
@Root(name = "channel", strict = false)
public static class Channel implements Serializable {
@Element(name = "image")
public Image image;
}
@Root(name = "image", strict = false)
public static class Image implements Serializable {
@Element(name = "title")
public String title;
@Element(name = "url")
public String url;
@Element(name = "link")
public String link;
}
}

View file

@ -590,6 +590,30 @@ public class Helper {
return date; return date;
} }
/**
* Convert String date from db to Date Object
*
* @param stringDate date to convert
* @return Date
*/
public static Date stringToDateWithFormat(Context context, String stringDate, String format) {
if (stringDate == null)
return null;
Locale userLocale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
userLocale = context.getResources().getConfiguration().getLocales().get(0);
} else {
userLocale = context.getResources().getConfiguration().locale;
}
SimpleDateFormat dateFormat = new SimpleDateFormat(format, userLocale);
Date date = null;
try {
date = dateFormat.parse(stringDate);
} catch (java.text.ParseException ignored) {
}
return date;
}
/** /**
* Converts dp to pixel * Converts dp to pixel

View file

@ -94,10 +94,8 @@ public class TimelinesVM extends AndroidViewModel {
} }
private MastodonTimelinesService initInstanceXMLOnly(String instance) { private MastodonTimelinesService initInstanceXMLOnly(String instance) {
Gson gson = new GsonBuilder().setDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz").create();
Retrofit retrofit = new Retrofit.Builder() Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance) .baseUrl("https://" + instance)
//.addConverterFactory(GsonConverterFactory.create(gson))
.addConverterFactory(SimpleXmlConverterFactory.create()) .addConverterFactory(SimpleXmlConverterFactory.create())
.client(okHttpClient) .client(okHttpClient)
.build(); .build();
@ -180,8 +178,8 @@ public class TimelinesVM extends AndroidViewModel {
if (publicTlResponse.isSuccessful()) { if (publicTlResponse.isSuccessful()) {
Nitter rssResponse = publicTlResponse.body(); Nitter rssResponse = publicTlResponse.body();
List<Status> statusList = new ArrayList<>(); List<Status> statusList = new ArrayList<>();
if (rssResponse != null && rssResponse.channel != null && rssResponse.channel.mFeedItems != null) { if (rssResponse != null && rssResponse.mFeedItems != null) {
for (Nitter.FeedItem feedItem : rssResponse.channel.mFeedItems) { for (Nitter.FeedItem feedItem : rssResponse.mFeedItems) {
Status status = Nitter.convert(getApplication(), instance, feedItem); Status status = Nitter.convert(getApplication(), instance, feedItem);
statusList.add(status); statusList.add(status);
} }