add first version of lainchan

refactor-toolbar
Floens 8 years ago
parent 26683237e5
commit 31678e104e
  1. 26
      Clover/app/src/main/java/org/floens/chan/core/site/SiteIcon.java
  2. 51
      Clover/app/src/main/java/org/floens/chan/core/site/SiteRegistry.java
  3. 2
      Clover/app/src/main/java/org/floens/chan/core/site/SiteResolver.java
  4. 6
      Clover/app/src/main/java/org/floens/chan/core/site/SiteService.java
  5. 24
      Clover/app/src/main/java/org/floens/chan/core/site/Sites.java
  6. 97
      Clover/app/src/main/java/org/floens/chan/core/site/common/CommonSite.java
  7. 2
      Clover/app/src/main/java/org/floens/chan/core/site/common/MultipartHttpCall.java
  8. 106
      Clover/app/src/main/java/org/floens/chan/core/site/common/vichan/VichanAntispam.java
  9. 318
      Clover/app/src/main/java/org/floens/chan/core/site/common/vichan/VichanApi.java
  10. 3
      Clover/app/src/main/java/org/floens/chan/core/site/http/HttpCall.java
  11. 4
      Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java
  12. 205
      Clover/app/src/main/java/org/floens/chan/core/site/sites/chan8/Chan8.java
  13. 204
      Clover/app/src/main/java/org/floens/chan/core/site/sites/lainchan/Lainchan.java
  14. 541
      Clover/app/src/main/java/org/floens/chan/core/site/sites/vichan/ViChan.java

@ -23,14 +23,23 @@ import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import org.floens.chan.utils.Logger;
import java.io.IOException;
import okhttp3.HttpUrl;
import static org.floens.chan.Chan.injector;
import static org.floens.chan.utils.AndroidUtils.getAppContext;
import static org.floens.chan.utils.AndroidUtils.getRes;
public class SiteIcon {
private static final String TAG = "SiteIcon";
private static final int FAVICON_SIZE = 64;
private String assetPath;
private HttpUrl url;
@ -40,7 +49,7 @@ public class SiteIcon {
return siteIcon;
}
public static SiteIcon fromUrl(HttpUrl url) {
public static SiteIcon fromFavicon(HttpUrl url) {
SiteIcon siteIcon = new SiteIcon();
siteIcon.url = url;
return siteIcon;
@ -65,7 +74,20 @@ public class SiteIcon {
drawable.getPaint().setFilterBitmap(false);
result.onSiteIcon(this, drawable);
} else if (url != null) {
// TODO
injector().instance(ImageLoader.class).get(url.toString(), new ImageLoader.ImageListener() {
@Override
public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null) {
Drawable drawable = new BitmapDrawable(response.getBitmap());
result.onSiteIcon(SiteIcon.this, drawable);
}
}
@Override
public void onErrorResponse(VolleyError error) {
Logger.e(TAG, "Error loading favicon", error);
}
}, FAVICON_SIZE, FAVICON_SIZE);
}
}

@ -0,0 +1,51 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.site;
import android.util.SparseArray;
import org.floens.chan.core.site.sites.chan4.Chan4;
import org.floens.chan.core.site.sites.lainchan.Lainchan;
import org.floens.chan.core.site.sites.vichan.ViChan;
import java.util.ArrayList;
import java.util.List;
/**
* Registry of all sites and url handler we have.
*/
public class SiteRegistry {
public static final List<SiteUrlHandler> URL_HANDLERS = new ArrayList<>();
public static final SparseArray<Class<? extends Site>> SITE_CLASSES = new SparseArray<>();
static {
URL_HANDLERS.add(Chan4.URL_HANDLER);
URL_HANDLERS.add(ViChan.URL_HANDLER);
URL_HANDLERS.add(Lainchan.URL_HANDLER);
}
static {
// This id-siteclass mapping is used to look up the correct site class at deserialization.
// This differs from the Site.id() id, that id is used for site instance linking, this is just to
// find the correct class to use.
SITE_CLASSES.put(0, Chan4.class);
SITE_CLASSES.put(1, ViChan.class);
SITE_CLASSES.put(2, Lainchan.class);
}
}

@ -67,7 +67,7 @@ public class SiteResolver {
}
public SiteResolverResult resolveSiteForUrl(String url) {
List<SiteUrlHandler> siteUrlHandlers = Sites.URL_HANDLERS;
List<SiteUrlHandler> siteUrlHandlers = SiteRegistry.URL_HANDLERS;
HttpUrl httpUrl = sanitizeUrl(url);

@ -104,7 +104,7 @@ public class SiteService {
Site site = new Chan4();
SiteConfig config = new SiteConfig();
config.classId = Sites.SITE_CLASSES.indexOfValue(site.getClass());
config.classId = SiteRegistry.SITE_CLASSES.indexOfValue(site.getClass());
config.external = false;
SiteModel model = siteRepository.create(config, new JsonSettings());
@ -138,7 +138,7 @@ public class SiteService {
SiteConfig config = new SiteConfig();
JsonSettings settings = new JsonSettings();
config.classId = Sites.SITE_CLASSES.indexOfValue(site.getClass());
config.classId = SiteRegistry.SITE_CLASSES.indexOfValue(site.getClass());
config.external = false;
siteRepository.create(config, settings);
@ -155,7 +155,7 @@ public class SiteService {
}
private Site instantiateSiteClass(int classId) {
Class<? extends Site> clazz = Sites.SITE_CLASSES.get(classId);
Class<? extends Site> clazz = SiteRegistry.SITE_CLASSES.get(classId);
if (clazz == null) {
throw new IllegalArgumentException("Unknown class id");
}

@ -1,39 +1,17 @@
package org.floens.chan.core.site;
import android.util.SparseArray;
import org.floens.chan.core.site.sites.vichan.ViChan;
import org.floens.chan.core.site.sites.chan4.Chan4;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Sites {
public static final SparseArray<Class<? extends Site>> SITE_CLASSES = new SparseArray<>();
static {
// This id-siteclass mapping is used to look up the correct site class at deserialization.
// This differs from the Site.id() id, that id is used for site instance linking, this is just to
// find the correct class to use.
SITE_CLASSES.put(0, Chan4.class);
SITE_CLASSES.put(1, ViChan.class);
}
public static final List<SiteUrlHandler> URL_HANDLERS = new ArrayList<>();
static {
URL_HANDLERS.add(Chan4.SITE_URL_HANDLER);
URL_HANDLERS.add(ViChan.RESOLVABLE);
}
private static List<Site> ALL_SITES = Collections.unmodifiableList(new ArrayList<Site>());
/**
* Return all sites known in the system.
* <p>This list is immutable. Changes to the known sites cause this function to return a new immutable list
* with the site changes.
*
* @return list of sites known in the system.
*/
public static List<Site> allSites() {

@ -17,6 +17,8 @@
*/
package org.floens.chan.core.site.common;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.webkit.WebView;
@ -47,11 +49,15 @@ import java.io.IOException;
import java.security.SecureRandom;
import java.util.Map;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
import static android.text.TextUtils.isEmpty;
public abstract class CommonSite extends SiteBase {
private final Random secureRandom = new SecureRandom();
@ -222,25 +228,79 @@ public abstract class CommonSite extends SiteBase {
}
public static abstract class CommonSiteUrlHandler implements SiteUrlHandler {
public abstract HttpUrl getUrl();
public abstract String[] getNames();
@Override
public boolean matchesName(String value) {
for (String s : getNames()) {
if (value.equals(s)) {
return true;
}
}
return false;
}
@Override
public boolean respondsTo(HttpUrl url) {
return false;
return getUrl().host().equals(url.host());
}
@Override
public String desktopUrl(Loadable loadable, @Nullable Post post) {
return null;
if (loadable.isCatalogMode()) {
return getUrl().newBuilder().addPathSegment(loadable.boardCode).toString();
} else if (loadable.isThreadMode()) {
return getUrl().newBuilder()
.addPathSegment(loadable.boardCode).addPathSegment("res")
.addPathSegment(String.valueOf(loadable.no))
.toString();
} else {
return getUrl().toString();
}
}
@Override
public Loadable resolveLoadable(Site site, HttpUrl url) {
Matcher board = boardPattern().matcher(url.encodedPath());
Matcher thread = threadPattern().matcher(url.encodedPath());
try {
if (thread.find()) {
Board b = site.board(thread.group(1));
if (b == null) {
return null;
}
Loadable l = Loadable.forThread(site, b, Integer.parseInt(thread.group(3)));
if (isEmpty(url.fragment())) {
l.markedNo = Integer.parseInt(url.fragment());
}
return l;
} else if (board.find()) {
Board b = site.board(board.group(1));
if (b == null) {
return null;
}
return Loadable.forCatalog(b);
}
} catch (NumberFormatException ignored) {
}
return null;
}
public Pattern boardPattern() {
return Pattern.compile("/(\\w+)");
}
public Pattern threadPattern() {
return Pattern.compile("/(\\w+)/\\w+/(\\d+).*");
}
}
public abstract class CommonEndpoints implements SiteEndpoints {
@ -357,8 +417,22 @@ public abstract class CommonSite extends SiteBase {
call.url(endpoints().reply(reply.loadable));
setupPost(reply, call);
if (requirePrepare()) {
Handler handler = new Handler(Looper.getMainLooper());
new Thread(() -> {
prepare(call, reply, replyResponse);
handler.post(() -> {
setupPost(reply, call);
makePostCall(call, replyResponse, postListener);
});
}).start();
} else {
setupPost(reply, call);
makePostCall(call, replyResponse, postListener);
}
}
private void makePostCall(HttpCall call, ReplyResponse replyResponse, PostListener postListener) {
httpCallManager.makeHttpCall(call, new HttpCall.HttpCallback<HttpCall>() {
@Override
public void onHttpSuccess(HttpCall httpCall) {
@ -372,6 +446,13 @@ public abstract class CommonSite extends SiteBase {
});
}
public boolean requirePrepare() {
return false;
}
public void prepare(MultipartHttpCall call, Reply reply, ReplyResponse replyResponse) {
}
@Override
public boolean postRequiresAuthentication() {
return false;
@ -408,10 +489,16 @@ public abstract class CommonSite extends SiteBase {
}
}
public abstract class CommonApi implements ChanReader {
public static abstract class CommonApi implements ChanReader {
private CommonSite commonSite;
public CommonApi(CommonSite commonSite) {
this.commonSite = commonSite;
}
@Override
public PostParser getParser() {
return postParser;
return commonSite.postParser;
}
}

@ -60,7 +60,7 @@ public abstract class MultipartHttpCall extends HttpCall {
@Override
public void setup(Request.Builder requestBuilder) {
requestBuilder.url(url);
requestBuilder.addHeader("Referer", url.toString());
requestBuilder.addHeader("Referer", url.scheme() + "://" + url.host());
requestBuilder.post(formBuilder.build());
}
}

@ -0,0 +1,106 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.site.common.vichan;
import org.floens.chan.utils.Logger;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
/**
* Vichan applies garbage looking fields to the post form, to combat bots.
* Load up the normal html, parse the form, and get these fields for our post.
* <p>
* {@link #get()} blocks, run it off the main thread.
*/
public class VichanAntispam {
private static final String TAG = "Antispam";
private HttpUrl url;
private OkHttpClient okHttpClient = new OkHttpClient();
private List<String> fieldsToIgnore = new ArrayList<>();
public VichanAntispam(HttpUrl url) {
this.url = url;
}
public void addDefaultIgnoreFields() {
fieldsToIgnore.addAll(Arrays.asList("board", "thread", "name", "email",
"subject", "body", "password", "file", "spoiler", "json_response",
"file_url1", "file_url2", "file_url3"));
}
public void ignoreField(String name) {
fieldsToIgnore.add(name);
}
public Map<String, String> get() {
Map<String, String> res = new HashMap<>();
Request request = new Request.Builder()
.url(url)
.build();
try {
Response response = okHttpClient.newCall(request).execute();
ResponseBody body = response.body();
if (body != null) {
Document document = Jsoup.parse(body.string());
Elements form = document.body().getElementsByTag("form");
for (Element element : form) {
if (element.attr("name").equals("post")) {
// Add all <input> and <textarea> elements.
Elements inputs = element.getElementsByTag("input");
inputs.addAll(element.getElementsByTag("textarea"));
for (Element input : inputs) {
String name = input.attr("name");
String value = input.val();
if (!fieldsToIgnore.contains(name)) {
res.put(name, value);
}
}
break;
}
}
}
} catch (IOException e) {
Logger.e(TAG, "IOException parsing vichan bot fields", e);
} catch (NullPointerException e) {
Logger.e(TAG, "NullPointerException parsing vichan bot fields", e);
}
return res;
}
}

@ -0,0 +1,318 @@
package org.floens.chan.core.site.common.vichan;
import android.util.JsonReader;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostHttpIcon;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.site.SiteEndpoints;
import org.floens.chan.core.site.common.CommonSite;
import org.floens.chan.core.site.parser.ChanReaderProcessingQueue;
import org.jsoup.parser.Parser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import okhttp3.HttpUrl;
import static org.floens.chan.core.site.SiteEndpoints.makeArgument;
public class VichanApi extends CommonSite.CommonApi {
public VichanApi(CommonSite commonSite) {
super(commonSite);
}
@Override
public void loadThread(JsonReader reader, ChanReaderProcessingQueue queue) throws Exception {
reader.beginObject();
// Page object
while (reader.hasNext()) {
String key = reader.nextName();
if (key.equals("posts")) {
reader.beginArray();
// Thread array
while (reader.hasNext()) {
// Thread object
readPostObject(reader, queue);
}
reader.endArray();
} else {
reader.skipValue();
}
}
reader.endObject();
}
@Override
public void loadCatalog(JsonReader reader, ChanReaderProcessingQueue queue) throws Exception {
reader.beginArray(); // Array of pages
while (reader.hasNext()) {
reader.beginObject(); // Page object
while (reader.hasNext()) {
if (reader.nextName().equals("threads")) {
reader.beginArray(); // Threads array
while (reader.hasNext()) {
readPostObject(reader, queue);
}
reader.endArray();
} else {
reader.skipValue();
}
}
reader.endObject();
}
reader.endArray();
}
@Override
public void readPostObject(JsonReader reader, ChanReaderProcessingQueue queue) throws Exception {
Post.Builder builder = new Post.Builder();
builder.board(queue.getLoadable().board);
SiteEndpoints endpoints = queue.getLoadable().getSite().endpoints();
// File
String fileId = null;
String fileExt = null;
int fileWidth = 0;
int fileHeight = 0;
long fileSize = 0;
boolean fileSpoiler = false;
String fileName = null;
List<PostImage> files = new ArrayList<>();
// Country flag
String countryCode = null;
String trollCountryCode = null;
String countryName = null;
reader.beginObject();
while (reader.hasNext()) {
String key = reader.nextName();
switch (key) {
case "no":
builder.id(reader.nextInt());
break;
case "sub":
builder.subject(reader.nextString());
break;
case "name":
builder.name(reader.nextString());
break;
case "com":
builder.comment(reader.nextString());
break;
case "tim":
fileId = reader.nextString();
break;
case "time":
builder.setUnixTimestampSeconds(reader.nextLong());
break;
case "ext":
fileExt = reader.nextString().replace(".", "");
break;
case "w":
fileWidth = reader.nextInt();
break;
case "h":
fileHeight = reader.nextInt();
break;
case "fsize":
fileSize = reader.nextLong();
break;
case "filename":
fileName = reader.nextString();
break;
case "trip":
builder.tripcode(reader.nextString());
break;
case "country":
countryCode = reader.nextString();
break;
case "troll_country":
trollCountryCode = reader.nextString();
break;
case "country_name":
countryName = reader.nextString();
break;
case "spoiler":
fileSpoiler = reader.nextInt() == 1;
break;
case "resto":
int opId = reader.nextInt();
builder.op(opId == 0);
builder.opId(opId);
break;
case "sticky":
builder.sticky(reader.nextInt() == 1);
break;
case "closed":
builder.closed(reader.nextInt() == 1);
break;
case "archived":
builder.archived(reader.nextInt() == 1);
break;
case "replies":
builder.replies(reader.nextInt());
break;
case "images":
builder.images(reader.nextInt());
break;
case "unique_ips":
builder.uniqueIps(reader.nextInt());
break;
case "id":
builder.posterId(reader.nextString());
break;
case "capcode":
builder.moderatorCapcode(reader.nextString());
break;
case "extra_files":
reader.beginArray();
while (reader.hasNext()) {
PostImage postImage = readPostImage(reader, builder, endpoints);
if (postImage != null) {
files.add(postImage);
}
}
reader.endArray();
break;
default:
// Unknown/ignored key
reader.skipValue();
break;
}
}
reader.endObject();
// The file from between the other values.
if (fileId != null && fileName != null && fileExt != null) {
Map<String, String> args = makeArgument("tim", fileId,
"ext", fileExt);
PostImage image = new PostImage.Builder()
.originalName(String.valueOf(fileId))
.thumbnailUrl(endpoints.thumbnailUrl(builder, false, args))
.spoilerThumbnailUrl(endpoints.thumbnailUrl(builder, true, args))
.imageUrl(endpoints.imageUrl(builder, args))
.filename(Parser.unescapeEntities(fileName, false))
.extension(fileExt)
.imageWidth(fileWidth)
.imageHeight(fileHeight)
.spoiler(fileSpoiler)
.size(fileSize)
.build();
// Insert it at the beginning.
files.add(0, image);
}
builder.images(files);
if (builder.op) {
// Update OP fields later on the main thread
Post.Builder op = new Post.Builder();
op.closed(builder.closed);
op.archived(builder.archived);
op.sticky(builder.sticky);
op.replies(builder.replies);
op.images(builder.imagesCount);
op.uniqueIps(builder.uniqueIps);
queue.setOp(op);
}
Post cached = queue.getCachedPost(builder.id);
if (cached != null) {
// Id is known, use the cached post object.
queue.addForReuse(cached);
return;
}
if (countryCode != null && countryName != null) {
HttpUrl countryUrl = endpoints.icon(builder, "country",
makeArgument("country_code", countryCode));
builder.addHttpIcon(new PostHttpIcon(countryUrl, countryName));
}
if (trollCountryCode != null && countryName != null) {
HttpUrl countryUrl = endpoints.icon(builder, "troll_country",
makeArgument("troll_country_code", trollCountryCode));
builder.addHttpIcon(new PostHttpIcon(countryUrl, countryName));
}
queue.addForParse(builder);
}
private PostImage readPostImage(JsonReader reader, Post.Builder builder,
SiteEndpoints endpoints) throws IOException {
reader.beginObject();
String fileId = null;
long fileSize = 0;
String fileExt = null;
int fileWidth = 0;
int fileHeight = 0;
boolean fileSpoiler = false;
String fileName = null;
while (reader.hasNext()) {
switch (reader.nextName()) {
case "tim":
fileId = reader.nextString();
break;
case "fsize":
fileSize = reader.nextLong();
break;
case "w":
fileWidth = reader.nextInt();
break;
case "h":
fileHeight = reader.nextInt();
break;
case "spoiler":
fileSpoiler = reader.nextInt() == 1;
break;
case "ext":
fileExt = reader.nextString().replace(".", "");
break;
case "filename":
fileName = reader.nextString();
break;
default:
reader.skipValue();
break;
}
}
reader.endObject();
if (fileId != null && fileName != null && fileExt != null) {
Map<String, String> args = makeArgument("tim", fileId,
"ext", fileExt);
return new PostImage.Builder()
.originalName(String.valueOf(fileId))
.thumbnailUrl(endpoints.thumbnailUrl(builder, false, args))
.spoilerThumbnailUrl(endpoints.thumbnailUrl(builder, true, args))
.imageUrl(endpoints.imageUrl(builder, args))
.filename(Parser.unescapeEntities(fileName, false))
.extension(fileExt)
.imageWidth(fileWidth)
.imageHeight(fileHeight)
.spoiler(fileSpoiler)
.size(fileSize)
.build();
}
return null;
}
}

@ -22,6 +22,7 @@ import android.os.Looper;
import org.floens.chan.core.site.Site;
import org.floens.chan.utils.IOUtils;
import org.floens.chan.utils.Logger;
import java.io.IOException;
@ -72,6 +73,7 @@ public abstract class HttpCall implements Callback {
}
if (exception != null) {
Logger.e(TAG, "onResponse", exception);
callFail(exception);
} else {
callSuccess();
@ -80,6 +82,7 @@ public abstract class HttpCall implements Callback {
@Override
public void onFailure(Call call, IOException e) {
Logger.e(TAG, "onFailure", e);
callFail(e);
}

@ -62,7 +62,7 @@ import okhttp3.HttpUrl;
import okhttp3.Request;
public class Chan4 extends SiteBase {
public static final SiteUrlHandler SITE_URL_HANDLER = new SiteUrlHandler() {
public static final SiteUrlHandler URL_HANDLER = new SiteUrlHandler() {
@Override
public Class<? extends Site> getSiteClass() {
return Chan4.class;
@ -481,7 +481,7 @@ public class Chan4 extends SiteBase {
@Override
public SiteUrlHandler resolvable() {
return SITE_URL_HANDLER;
return URL_HANDLER;
}
@Override

@ -0,0 +1,205 @@
package org.floens.chan.core.site.sites.vichan;
import android.support.annotation.Nullable;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.site.Site;
import org.floens.chan.core.site.SiteAuthentication;
import org.floens.chan.core.site.SiteIcon;
import org.floens.chan.core.site.common.CommonSite;
import org.floens.chan.core.site.common.MultipartHttpCall;
import org.floens.chan.core.site.common.vichan.VichanApi;
import org.floens.chan.core.site.http.Reply;
import org.floens.chan.core.site.http.ReplyResponse;
import org.floens.chan.core.site.parser.CommentParser;
import org.floens.chan.core.site.parser.StyleRule;
import org.jsoup.Jsoup;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import okhttp3.HttpUrl;
import okhttp3.Response;
import static android.text.TextUtils.isEmpty;
public class ViChan extends CommonSite {
public static final CommonSiteUrlHandler URL_HANDLER = new CommonSiteUrlHandler() {
@Override
public Class<? extends Site> getSiteClass() {
return ViChan.class;
}
@Override
public HttpUrl getUrl() {
return HttpUrl.parse("https://8ch.net/");
}
@Override
public String[] getNames() {
return new String[]{"8chan", "8ch"};
}
@Override
public String desktopUrl(Loadable loadable, @Nullable Post post) {
if (loadable.isCatalogMode()) {
return getUrl().newBuilder().addPathSegment(loadable.boardCode).toString();
} else if (loadable.isThreadMode()) {
return getUrl().newBuilder()
.addPathSegment(loadable.boardCode).addPathSegment("res")
.addPathSegment(String.valueOf(loadable.no) + ".html")
.toString();
} else {
return getUrl().toString();
}
}
};
@Override
public void setup() {
setName("8chan");
setIcon(SiteIcon.fromAssets("icons/8chan.png"));
setBoardsType(BoardsType.INFINITE);
setResolvable(URL_HANDLER);
setConfig(new CommonConfig() {
@Override
public boolean feature(Feature feature) {
return feature == Feature.POSTING;
}
});
setEndpoints(new CommonEndpoints() {
private final SimpleHttpUrl root = from("https://8ch.net");
private final SimpleHttpUrl sys = from("https://sys.8ch.net");
@Override
public HttpUrl catalog(Board board) {
return root.builder().s(board.code).s("catalog.json").url();
}
@Override
public HttpUrl thread(Board board, Loadable loadable) {
return root.builder().s(board.code).s("res").s(loadable.no + ".json").url();
}
@Override
public HttpUrl imageUrl(Post.Builder post, Map<String, String> arg) {
return root.builder().s("file_store").s(arg.get("tim") + "." + arg.get("ext")).url();
}
@Override
public HttpUrl thumbnailUrl(Post.Builder post, boolean spoiler, Map<String, String> arg) {
String ext;
switch (arg.get("ext")) {
case "jpeg":
case "jpg":
case "png":
case "gif":
ext = arg.get("ext");
break;
default:
ext = "jpg";
break;
}
return root.builder().s("file_store").s("thumb").s(arg.get("tim") + "." + ext).url();
}
@Override
public HttpUrl icon(Post.Builder post, String icon, Map<String, String> arg) {
SimpleHttpUrl stat = root.builder().s("static");
if (icon.equals("country")) {
stat.s("flags").s(arg.get("country_code").toLowerCase(Locale.ENGLISH) + ".png");
}
return stat.url();
}
@Override
public HttpUrl reply(Loadable loadable) {
return sys.builder().s("post.php").url();
}
});
setActions(new CommonActions() {
@Override
public void setupPost(Reply reply, MultipartHttpCall call) {
call.parameter("board", reply.loadable.board.code);
if (reply.loadable.isThreadMode()) {
call.parameter("post", "New Reply");
call.parameter("thread", String.valueOf(reply.loadable.no));
} else {
call.parameter("post", "New Thread");
call.parameter("page", "1");
}
call.parameter("pwd", reply.password);
call.parameter("name", reply.name);
call.parameter("email", reply.options);
if (!reply.loadable.isThreadMode() && !isEmpty(reply.subject)) {
call.parameter("subject", reply.subject);
}
call.parameter("body", reply.comment);
if (reply.file != null) {
call.fileParameter("file", reply.fileName, reply.file);
}
if (reply.spoilerImage) {
call.parameter("spoiler", "on");
}
}
@Override
public void handlePost(ReplyResponse replyResponse, Response response, String result) {
Matcher auth = Pattern.compile(".*\"captcha\": ?true.*").matcher(result);
Matcher err = Pattern.compile(".*<h1>Error</h1>.*<h2[^>]*>(.*?)</h2>.*").matcher(result);
if (auth.find()) {
replyResponse.requireAuthentication = true;
replyResponse.errorMessage = result;
} else if (err.find()) {
replyResponse.errorMessage = Jsoup.parse(err.group(1)).body().text();
} else {
HttpUrl url = response.request().url();
Matcher m = Pattern.compile("/\\w+/\\w+/(\\d+).html").matcher(url.encodedPath());
try {
if (m.find()) {
replyResponse.threadNo = Integer.parseInt(m.group(1));
replyResponse.postNo = Integer.parseInt(url.encodedFragment());
replyResponse.posted = true;
}
} catch (NumberFormatException ignored) {
replyResponse.errorMessage = "Error posting: could not find posted thread.";
}
}
}
@Override
public SiteAuthentication postAuthenticate() {
return SiteAuthentication.fromUrl("https://8ch.net/dnsbls_bypass.php",
"You failed the CAPTCHA",
"You may now go back and make your post");
}
});
setApi(new VichanApi(this));
CommentParser commentParser = new CommentParser();
commentParser.addDefaultRules();
commentParser.setQuotePattern(Pattern.compile(".*#(\\d+)"));
commentParser.setFullQuotePattern(Pattern.compile("/(\\w+)/\\w+/(\\d+)\\.html#(\\d+)"));
commentParser.rule(StyleRule.tagRule("p").cssClass("quote").color(StyleRule.Color.INLINE_QUOTE).linkify());
setParser(commentParser);
}
}

@ -0,0 +1,204 @@
package org.floens.chan.core.site.sites.lainchan;
import android.support.annotation.Nullable;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.site.Site;
import org.floens.chan.core.site.SiteAuthentication;
import org.floens.chan.core.site.SiteIcon;
import org.floens.chan.core.site.common.CommonSite;
import org.floens.chan.core.site.common.MultipartHttpCall;
import org.floens.chan.core.site.common.vichan.VichanAntispam;
import org.floens.chan.core.site.common.vichan.VichanApi;
import org.floens.chan.core.site.http.Reply;
import org.floens.chan.core.site.http.ReplyResponse;
import org.floens.chan.core.site.parser.CommentParser;
import org.floens.chan.core.site.parser.StyleRule;
import org.jsoup.Jsoup;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import okhttp3.HttpUrl;
import okhttp3.Response;
import static android.text.TextUtils.isEmpty;
public class Lainchan extends CommonSite {
public static final CommonSiteUrlHandler URL_HANDLER = new CommonSiteUrlHandler() {
@Override
public Class<? extends Site> getSiteClass() {
return Lainchan.class;
}
@Override
public HttpUrl getUrl() {
return HttpUrl.parse("https://lainchan.org/");
}
@Override
public String[] getNames() {
return new String[]{"lainchan"};
}
@Override
public String desktopUrl(Loadable loadable, @Nullable Post post) {
if (loadable.isCatalogMode()) {
return getUrl().newBuilder().addPathSegment(loadable.boardCode).toString();
} else if (loadable.isThreadMode()) {
return getUrl().newBuilder()
.addPathSegment(loadable.boardCode).addPathSegment("res")
.addPathSegment(String.valueOf(loadable.no) + ".html")
.toString();
} else {
return getUrl().toString();
}
}
};
@Override
public void setup() {
setName("Lainchan");
setIcon(SiteIcon.fromFavicon(HttpUrl.parse("https://lainchan.org/favicon.ico")));
setBoardsType(BoardsType.INFINITE);
setResolvable(URL_HANDLER);
setConfig(new CommonConfig() {
@Override
public boolean feature(Feature feature) {
return feature == Feature.POSTING;
}
});
setEndpoints(new CommonEndpoints() {
private final SimpleHttpUrl root = from("https://lainchan.org");
private final SimpleHttpUrl sys = from("https://lainchan.org");
@Override
public HttpUrl catalog(Board board) {
return root.builder().s(board.code).s("catalog.json").url();
}
@Override
public HttpUrl thread(Board board, Loadable loadable) {
return root.builder().s(board.code).s("res").s(loadable.no + ".json").url();
}
@Override
public HttpUrl thumbnailUrl(Post.Builder post, boolean spoiler, Map<String, String> arg) {
return root.builder().s(post.board.code).s("thumb").s(arg.get("tim") + ".png").url();
}
@Override
public HttpUrl imageUrl(Post.Builder post, Map<String, String> arg) {
return root.builder().s(post.board.code).s("src").s(arg.get("tim") + "." + arg.get("ext")).url();
}
@Override
public HttpUrl icon(Post.Builder post, String icon, Map<String, String> arg) {
SimpleHttpUrl stat = root.builder().s("static");
if (icon.equals("country")) {
stat.s("flags").s(arg.get("country_code").toLowerCase(Locale.ENGLISH) + ".png");
}
return stat.url();
}
@Override
public HttpUrl reply(Loadable loadable) {
return sys.builder().s("post.php").url();
}
});
setActions(new CommonActions() {
@Override
public void setupPost(Reply reply, MultipartHttpCall call) {
call.parameter("board", reply.loadable.board.code);
if (reply.loadable.isThreadMode()) {
call.parameter("thread", String.valueOf(reply.loadable.no));
} else {
// call.parameter("page", "1");
}
call.parameter("password", reply.password);
call.parameter("name", reply.name);
call.parameter("email", reply.options);
if (!reply.loadable.isThreadMode() && !isEmpty(reply.subject)) {
call.parameter("subject", reply.subject);
}
call.parameter("body", reply.comment);
if (reply.file != null) {
call.fileParameter("file", reply.fileName, reply.file);
}
if (reply.spoilerImage) {
call.parameter("spoiler", "on");
}
}
@Override
public boolean requirePrepare() {
return true;
}
@Override
public void prepare(MultipartHttpCall call, Reply reply, ReplyResponse replyResponse) {
VichanAntispam antispam = new VichanAntispam(
HttpUrl.parse(resolvable().desktopUrl(reply.loadable, null)));
antispam.addDefaultIgnoreFields();
for (Map.Entry<String, String> e : antispam.get().entrySet()) {
call.parameter(e.getKey(), e.getValue());
}
}
@Override
public void handlePost(ReplyResponse replyResponse, Response response, String result) {
Matcher auth = Pattern.compile(".*\"captcha\": ?true.*").matcher(result);
Matcher err = Pattern.compile(".*<h1[^>]*>Error</h1>.*<h2[^>]*>(.*?)</h2>.*").matcher(result);
if (auth.find()) {
replyResponse.requireAuthentication = true;
replyResponse.errorMessage = result;
} else if (err.find()) {
replyResponse.errorMessage = Jsoup.parse(err.group(1)).body().text();
} else {
HttpUrl url = response.request().url();
Matcher m = Pattern.compile("/\\w+/\\w+/(\\d+).html").matcher(url.encodedPath());
try {
if (m.find()) {
replyResponse.threadNo = Integer.parseInt(m.group(1));
replyResponse.postNo = Integer.parseInt(url.encodedFragment());
replyResponse.posted = true;
}
} catch (NumberFormatException ignored) {
replyResponse.errorMessage = "Error posting: could not find posted thread.";
}
}
}
@Override
public SiteAuthentication postAuthenticate() {
return SiteAuthentication.fromNone();
}
});
setApi(new VichanApi(this));
CommentParser commentParser = new CommentParser();
commentParser.addDefaultRules();
commentParser.setQuotePattern(Pattern.compile(".*#(\\d+)"));
commentParser.setFullQuotePattern(Pattern.compile("/(\\w+)/\\w+/(\\d+)\\.html#(\\d+)"));
commentParser.rule(StyleRule.tagRule("p").cssClass("quote").color(StyleRule.Color.INLINE_QUOTE).linkify());
setParser(commentParser);
}
}

@ -1,541 +0,0 @@
package org.floens.chan.core.site.sites.vichan;
import android.support.annotation.Nullable;
import android.util.JsonReader;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostHttpIcon;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.site.Site;
import org.floens.chan.core.site.SiteAuthentication;
import org.floens.chan.core.site.SiteEndpoints;
import org.floens.chan.core.site.SiteIcon;
import org.floens.chan.core.site.common.CommonSite;
import org.floens.chan.core.site.common.MultipartHttpCall;
import org.floens.chan.core.site.http.Reply;
import org.floens.chan.core.site.http.ReplyResponse;
import org.floens.chan.core.site.parser.ChanReaderProcessingQueue;
import org.floens.chan.core.site.parser.CommentParser;
import org.floens.chan.core.site.parser.StyleRule;
import org.jsoup.Jsoup;
import org.jsoup.parser.Parser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import okhttp3.HttpUrl;
import okhttp3.Response;
import static android.text.TextUtils.isEmpty;
import static org.floens.chan.core.site.SiteEndpoints.makeArgument;
public class ViChan extends CommonSite {
public static final CommonSiteUrlHandler RESOLVABLE = new CommonSiteUrlHandler() {
@Override
public Class<? extends Site> getSiteClass() {
return ViChan.class;
}
@Override
public boolean matchesName(String value) {
return value.equals("8chan") || value.equals("8ch");
}
@Override
public boolean respondsTo(HttpUrl url) {
return url.host().equals("8ch.net");
}
@Override
public Loadable resolveLoadable(Site site, HttpUrl url) {
Matcher board = Pattern.compile("/(\\w+)")
.matcher(url.encodedPath());
Matcher thread = Pattern.compile("/(\\w+)/res/(\\d+).html")
.matcher(url.encodedPath());
try {
if (thread.find()) {
Board b = site.board(thread.group(1));
if (b == null) {
return null;
}
Loadable l = Loadable.forThread(site, b, Integer.parseInt(thread.group(3)));
if (isEmpty(url.fragment())) {
l.markedNo = Integer.parseInt(url.fragment());
}
return l;
} else if (board.find()) {
Board b = site.board(board.group(1));
if (b == null) {
return null;
}
return Loadable.forCatalog(b);
}
} catch (NumberFormatException ignored) {
}
return null;
}
@Override
public String desktopUrl(Loadable loadable, @Nullable Post post) {
if (loadable.isCatalogMode()) {
return "https://8ch.net/" + loadable.boardCode;
} else if (loadable.isThreadMode()) {
return "https://8ch.net/" + loadable.boardCode + "/res/" + loadable.no + ".html";
} else {
return "https://8ch.net/";
}
}
};
private static final String TAG = "ViChan";
@Override
public void setup() {
setName("8chan");
setIcon(SiteIcon.fromAssets("icons/8chan.png"));
setBoardsType(BoardsType.INFINITE);
setResolvable(RESOLVABLE);
setConfig(new CommonConfig() {
@Override
public boolean feature(Feature feature) {
return feature == Feature.POSTING;
}
});
setEndpoints(new CommonEndpoints() {
private final SimpleHttpUrl root = from("https://8ch.net");
private final SimpleHttpUrl sys = from("https://sys.8ch.net");
@Override
public HttpUrl catalog(Board board) {
return root.builder().s(board.code).s("catalog.json").url();
}
@Override
public HttpUrl thread(Board board, Loadable loadable) {
return root.builder().s(board.code).s("res").s(loadable.no + ".json").url();
}
@Override
public HttpUrl imageUrl(Post.Builder post, Map<String, String> arg) {
return root.builder().s("file_store").s(arg.get("tim") + "." + arg.get("ext")).url();
}
@Override
public HttpUrl thumbnailUrl(Post.Builder post, boolean spoiler, Map<String, String> arg) {
String ext;
switch (arg.get("ext")) {
case "jpeg":
case "jpg":
case "png":
case "gif":
ext = arg.get("ext");
break;
default:
ext = "jpg";
break;
}
return root.builder().s("file_store").s("thumb").s(arg.get("tim") + "." + ext).url();
}
@Override
public HttpUrl icon(Post.Builder post, String icon, Map<String, String> arg) {
SimpleHttpUrl stat = root.builder().s("static");
if (icon.equals("country")) {
stat.s("flags").s(arg.get("country_code").toLowerCase(Locale.ENGLISH) + ".png");
}
return stat.url();
}
@Override
public HttpUrl reply(Loadable loadable) {
return sys.builder().s("post.php").url();
}
});
setActions(new CommonActions() {
@Override
public void setupPost(Reply reply, MultipartHttpCall call) {
call.parameter("board", reply.loadable.board.code);
if (reply.loadable.isThreadMode()) {
call.parameter("post", "New Reply");
call.parameter("thread", String.valueOf(reply.loadable.no));
} else {
call.parameter("post", "New Thread");
call.parameter("page", "1");
}
call.parameter("pwd", reply.password);
call.parameter("name", reply.name);
call.parameter("email", reply.options);
if (!reply.loadable.isThreadMode() && !isEmpty(reply.subject)) {
call.parameter("subject", reply.subject);
}
call.parameter("body", reply.comment);
if (reply.file != null) {
call.fileParameter("file", reply.fileName, reply.file);
}
if (reply.spoilerImage) {
call.parameter("spoiler", "on");
}
}
@Override
public void handlePost(ReplyResponse replyResponse, Response response, String result) {
Matcher auth = Pattern.compile(".*\"captcha\": ?true.*").matcher(result);
Matcher err = Pattern.compile(".*<h1>Error</h1>.*<h2[^>]*>(.*?)</h2>.*").matcher(result);
if (auth.find()) {
replyResponse.requireAuthentication = true;
replyResponse.errorMessage = result;
} else if (err.find()) {
replyResponse.errorMessage = Jsoup.parse(err.group(1)).body().text();
} else {
HttpUrl url = response.request().url();
Matcher m = Pattern.compile("/\\w+/\\w+/(\\d+).html").matcher(url.encodedPath());
try {
if (m.find()) {
replyResponse.threadNo = Integer.parseInt(m.group(1));
replyResponse.postNo = Integer.parseInt(url.encodedFragment());
replyResponse.posted = true;
}
} catch (NumberFormatException ignored) {
replyResponse.errorMessage = "Error posting: could not find posted thread.";
}
}
}
@Override
public SiteAuthentication postAuthenticate() {
return SiteAuthentication.fromUrl("https://8ch.net/dnsbls_bypass.php",
"You failed the CAPTCHA",
"You may now go back and make your post");
}
});
setApi(new ViChanApi());
CommentParser commentParser = new CommentParser();
commentParser.addDefaultRules();
commentParser.setQuotePattern(Pattern.compile(".*#(\\d+)"));
commentParser.setFullQuotePattern(Pattern.compile("/(\\w+)/\\w+/(\\d+)\\.html#(\\d+)"));
commentParser.rule(StyleRule.tagRule("p").cssClass("quote").color(StyleRule.Color.INLINE_QUOTE).linkify());
setParser(commentParser);
}
private class ViChanApi extends CommonApi {
@Override
public void loadThread(JsonReader reader, ChanReaderProcessingQueue queue) throws Exception {
reader.beginObject();
// Page object
while (reader.hasNext()) {
String key = reader.nextName();
if (key.equals("posts")) {
reader.beginArray();
// Thread array
while (reader.hasNext()) {
// Thread object
readPostObject(reader, queue);
}
reader.endArray();
} else {
reader.skipValue();
}
}
reader.endObject();
}
@Override
public void loadCatalog(JsonReader reader, ChanReaderProcessingQueue queue) throws Exception {
reader.beginArray(); // Array of pages
while (reader.hasNext()) {
reader.beginObject(); // Page object
while (reader.hasNext()) {
if (reader.nextName().equals("threads")) {
reader.beginArray(); // Threads array
while (reader.hasNext()) {
readPostObject(reader, queue);
}
reader.endArray();
} else {
reader.skipValue();
}
}
reader.endObject();
}
reader.endArray();
}
@Override
public void readPostObject(JsonReader reader, ChanReaderProcessingQueue queue) throws Exception {
Post.Builder builder = new Post.Builder();
builder.board(queue.getLoadable().board);
SiteEndpoints endpoints = queue.getLoadable().getSite().endpoints();
// File
String fileId = null;
String fileExt = null;
int fileWidth = 0;
int fileHeight = 0;
long fileSize = 0;
boolean fileSpoiler = false;
String fileName = null;
List<PostImage> files = new ArrayList<>();
// Country flag
String countryCode = null;
String trollCountryCode = null;
String countryName = null;
reader.beginObject();
while (reader.hasNext()) {
String key = reader.nextName();
switch (key) {
case "no":
builder.id(reader.nextInt());
break;
case "sub":
builder.subject(reader.nextString());
break;
case "name":
builder.name(reader.nextString());
break;
case "com":
builder.comment(reader.nextString());
break;
case "tim":
fileId = reader.nextString();
break;
case "time":
builder.setUnixTimestampSeconds(reader.nextLong());
break;
case "ext":
fileExt = reader.nextString().replace(".", "");
break;
case "w":
fileWidth = reader.nextInt();
break;
case "h":
fileHeight = reader.nextInt();
break;
case "fsize":
fileSize = reader.nextLong();
break;
case "filename":
fileName = reader.nextString();
break;
case "trip":
builder.tripcode(reader.nextString());
break;
case "country":
countryCode = reader.nextString();
break;
case "troll_country":
trollCountryCode = reader.nextString();
break;
case "country_name":
countryName = reader.nextString();
break;
case "spoiler":
fileSpoiler = reader.nextInt() == 1;
break;
case "resto":
int opId = reader.nextInt();
builder.op(opId == 0);
builder.opId(opId);
break;
case "sticky":
builder.sticky(reader.nextInt() == 1);
break;
case "closed":
builder.closed(reader.nextInt() == 1);
break;
case "archived":
builder.archived(reader.nextInt() == 1);
break;
case "replies":
builder.replies(reader.nextInt());
break;
case "images":
builder.images(reader.nextInt());
break;
case "unique_ips":
builder.uniqueIps(reader.nextInt());
break;
case "id":
builder.posterId(reader.nextString());
break;
case "capcode":
builder.moderatorCapcode(reader.nextString());
break;
case "extra_files":
reader.beginArray();
while (reader.hasNext()) {
PostImage postImage = readPostImage(reader, builder, endpoints);
if (postImage != null) {
files.add(postImage);
}
}
reader.endArray();
break;
default:
// Unknown/ignored key
reader.skipValue();
break;
}
}
reader.endObject();
// The file from between the other values.
if (fileId != null && fileName != null && fileExt != null) {
Map<String, String> args = makeArgument("tim", fileId,
"ext", fileExt);
PostImage image = new PostImage.Builder()
.originalName(String.valueOf(fileId))
.thumbnailUrl(endpoints.thumbnailUrl(builder, false, args))
.spoilerThumbnailUrl(endpoints.thumbnailUrl(builder, true, args))
.imageUrl(endpoints.imageUrl(builder, args))
.filename(Parser.unescapeEntities(fileName, false))
.extension(fileExt)
.imageWidth(fileWidth)
.imageHeight(fileHeight)
.spoiler(fileSpoiler)
.size(fileSize)
.build();
// Insert it at the beginning.
files.add(0, image);
}
builder.images(files);
if (builder.op) {
// Update OP fields later on the main thread
Post.Builder op = new Post.Builder();
op.closed(builder.closed);
op.archived(builder.archived);
op.sticky(builder.sticky);
op.replies(builder.replies);
op.images(builder.imagesCount);
op.uniqueIps(builder.uniqueIps);
queue.setOp(op);
}
Post cached = queue.getCachedPost(builder.id);
if (cached != null) {
// Id is known, use the cached post object.
queue.addForReuse(cached);
return;
}
if (countryCode != null && countryName != null) {
HttpUrl countryUrl = endpoints.icon(builder, "country",
makeArgument("country_code", countryCode));
builder.addHttpIcon(new PostHttpIcon(countryUrl, countryName));
}
if (trollCountryCode != null && countryName != null) {
HttpUrl countryUrl = endpoints.icon(builder, "troll_country",
makeArgument("troll_country_code", trollCountryCode));
builder.addHttpIcon(new PostHttpIcon(countryUrl, countryName));
}
queue.addForParse(builder);
}
private PostImage readPostImage(JsonReader reader, Post.Builder builder,
SiteEndpoints endpoints) throws IOException {
reader.beginObject();
String fileId = null;
long fileSize = 0;
String fileExt = null;
int fileWidth = 0;
int fileHeight = 0;
boolean fileSpoiler = false;
String fileName = null;
while (reader.hasNext()) {
switch (reader.nextName()) {
case "tim":
fileId = reader.nextString();
break;
case "fsize":
fileSize = reader.nextLong();
break;
case "w":
fileWidth = reader.nextInt();
break;
case "h":
fileHeight = reader.nextInt();
break;
case "spoiler":
fileSpoiler = reader.nextInt() == 1;
break;
case "ext":
fileExt = reader.nextString().replace(".", "");
break;
case "filename":
fileName = reader.nextString();
break;
default:
reader.skipValue();
break;
}
}
reader.endObject();
if (fileId != null && fileName != null && fileExt != null) {
Map<String, String> args = makeArgument("tim", fileId,
"ext", fileExt);
return new PostImage.Builder()
.originalName(String.valueOf(fileId))
.thumbnailUrl(endpoints.thumbnailUrl(builder, false, args))
.spoilerThumbnailUrl(endpoints.thumbnailUrl(builder, true, args))
.imageUrl(endpoints.imageUrl(builder, args))
.filename(Parser.unescapeEntities(fileName, false))
.extension(fileExt)
.imageWidth(fileWidth)
.imageHeight(fileHeight)
.spoiler(fileSpoiler)
.size(fileSize)
.build();
}
return null;
}
}
}
Loading…
Cancel
Save