mirror of https://github.com/kurisufriend/Clover
a bunch of renaming of the site classes, because we know know more how we want to structure this. adds the commonsite, the easy way to implement a site. work on the comment parser, to properly recursively parse html, so we can later easily extend it for each site.refactor-toolbar
parent
e78d3e7a7f
commit
a14b407bf9
@ -1,12 +0,0 @@ |
||||
package org.floens.chan.core.site; |
||||
|
||||
|
||||
import okhttp3.HttpUrl; |
||||
|
||||
public interface Resolvable { |
||||
boolean matchesName(String value); |
||||
|
||||
boolean respondsTo(HttpUrl url); |
||||
|
||||
Class<? extends Site> getSiteClass(); |
||||
} |
@ -0,0 +1,83 @@ |
||||
/* |
||||
* 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 org.floens.chan.core.site.http.DeleteRequest; |
||||
import org.floens.chan.core.site.http.DeleteResponse; |
||||
import org.floens.chan.core.site.http.HttpCall; |
||||
import org.floens.chan.core.site.http.LoginRequest; |
||||
import org.floens.chan.core.site.http.LoginResponse; |
||||
import org.floens.chan.core.site.http.Reply; |
||||
import org.floens.chan.core.site.http.ReplyResponse; |
||||
|
||||
public interface SiteActions { |
||||
void boards(BoardsListener boardsListener); |
||||
|
||||
interface BoardsListener { |
||||
void onBoardsReceived(Boards boards); |
||||
} |
||||
|
||||
void post(Reply reply, PostListener postListener); |
||||
|
||||
interface PostListener { |
||||
|
||||
void onPostComplete(HttpCall httpCall, ReplyResponse replyResponse); |
||||
|
||||
void onPostError(HttpCall httpCall); |
||||
} |
||||
|
||||
boolean postRequiresAuthentication(); |
||||
|
||||
/** |
||||
* If {@link ReplyResponse#requireAuthentication} was {@code true}, or if |
||||
* {@link #postRequiresAuthentication()} is {@code true}, get the authentication |
||||
* required to post. |
||||
* <p> |
||||
* <p>Some sites know beforehand if you need to authenticate, some sites only report it |
||||
* after posting. That's why there are two methods.</p> |
||||
* |
||||
* @return an {@link SiteAuthentication} model that describes the way to authenticate. |
||||
*/ |
||||
SiteAuthentication postAuthenticate(); |
||||
|
||||
void delete(DeleteRequest deleteRequest, DeleteListener deleteListener); |
||||
|
||||
interface DeleteListener { |
||||
void onDeleteComplete(HttpCall httpCall, DeleteResponse deleteResponse); |
||||
|
||||
void onDeleteError(HttpCall httpCall); |
||||
} |
||||
|
||||
/* TODO(multi-site) this login mechanism is probably not generic enough right now, |
||||
* especially if we're thinking about what a login really is |
||||
* We'll expand this later when we have a better idea of what other sites require. |
||||
*/ |
||||
void login(LoginRequest loginRequest, LoginListener loginListener); |
||||
|
||||
void logout(); |
||||
|
||||
boolean isLoggedIn(); |
||||
|
||||
LoginRequest getLoginDetails(); |
||||
|
||||
interface LoginListener { |
||||
void onLoginComplete(HttpCall httpCall, LoginResponse loginResponse); |
||||
|
||||
void onLoginError(HttpCall httpCall); |
||||
} |
||||
} |
@ -0,0 +1,38 @@ |
||||
/* |
||||
* 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.support.annotation.Nullable; |
||||
|
||||
import org.floens.chan.core.model.Post; |
||||
import org.floens.chan.core.model.orm.Loadable; |
||||
|
||||
import okhttp3.HttpUrl; |
||||
|
||||
public interface SiteUrlHandler { |
||||
Class<? extends Site> getSiteClass(); |
||||
|
||||
boolean matchesName(String value); |
||||
|
||||
boolean respondsTo(HttpUrl url); |
||||
|
||||
String desktopUrl(Loadable loadable, @Nullable Post post); |
||||
|
||||
Loadable resolveLoadable(Site site, HttpUrl url); |
||||
} |
@ -1,14 +0,0 @@ |
||||
package org.floens.chan.core.site.common; |
||||
|
||||
|
||||
import android.util.JsonReader; |
||||
|
||||
public interface ChanReader { |
||||
ChanParser getParser(); |
||||
|
||||
void loadThread(JsonReader reader, ChanReaderProcessingQueue queue) throws Exception; |
||||
|
||||
void loadCatalog(JsonReader reader, ChanReaderProcessingQueue queue) throws Exception; |
||||
|
||||
void readPostObject(JsonReader reader, ChanReaderProcessingQueue queue) throws Exception; |
||||
} |
@ -0,0 +1,426 @@ |
||||
/* |
||||
* 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; |
||||
|
||||
import android.support.annotation.NonNull; |
||||
import android.support.annotation.Nullable; |
||||
import android.webkit.WebView; |
||||
|
||||
import org.floens.chan.core.model.Post; |
||||
import org.floens.chan.core.model.json.site.SiteConfig; |
||||
import org.floens.chan.core.model.orm.Board; |
||||
import org.floens.chan.core.model.orm.Loadable; |
||||
import org.floens.chan.core.settings.json.JsonSettings; |
||||
import org.floens.chan.core.site.Site; |
||||
import org.floens.chan.core.site.SiteActions; |
||||
import org.floens.chan.core.site.SiteAuthentication; |
||||
import org.floens.chan.core.site.SiteBase; |
||||
import org.floens.chan.core.site.SiteEndpoints; |
||||
import org.floens.chan.core.site.SiteIcon; |
||||
import org.floens.chan.core.site.SiteRequestModifier; |
||||
import org.floens.chan.core.site.SiteUrlHandler; |
||||
import org.floens.chan.core.site.http.DeleteRequest; |
||||
import org.floens.chan.core.site.http.HttpCall; |
||||
import org.floens.chan.core.site.http.LoginRequest; |
||||
import org.floens.chan.core.site.http.Reply; |
||||
import org.floens.chan.core.site.http.ReplyResponse; |
||||
import org.floens.chan.core.site.parser.ChanReader; |
||||
import org.floens.chan.core.site.parser.PostParser; |
||||
|
||||
import java.io.IOException; |
||||
import java.security.SecureRandom; |
||||
import java.util.Map; |
||||
import java.util.Random; |
||||
|
||||
import okhttp3.HttpUrl; |
||||
import okhttp3.Request; |
||||
import okhttp3.Response; |
||||
|
||||
public abstract class CommonSite extends SiteBase { |
||||
private final Random secureRandom = new SecureRandom(); |
||||
|
||||
private String name; |
||||
private SiteIcon icon; |
||||
private BoardsType boardsType; |
||||
private CommonConfig config; |
||||
private CommonSiteUrlHandler resolvable; |
||||
private CommonEndpoints endpoints; |
||||
private CommonActions actions; |
||||
private CommonApi api; |
||||
private CommonParser parser; |
||||
private CommonRequestModifier requestModifier; |
||||
|
||||
@Override |
||||
public void initialize(int id, SiteConfig config, JsonSettings userSettings) { |
||||
super.initialize(id, config, userSettings); |
||||
setup(); |
||||
|
||||
if (name == null) { |
||||
throw new NullPointerException("setName not called"); |
||||
} |
||||
|
||||
if (icon == null) { |
||||
throw new NullPointerException("setIcon not called"); |
||||
} |
||||
|
||||
if (boardsType == null) { |
||||
throw new NullPointerException("setBoardsType not called"); |
||||
} |
||||
|
||||
if (this.config == null) { |
||||
throw new NullPointerException("setConfig not called"); |
||||
} |
||||
|
||||
if (resolvable == null) { |
||||
throw new NullPointerException("setResolvable not called"); |
||||
} |
||||
|
||||
if (endpoints == null) { |
||||
throw new NullPointerException("setEndpoints not called"); |
||||
} |
||||
|
||||
if (actions == null) { |
||||
throw new NullPointerException("setActions not called"); |
||||
} |
||||
|
||||
if (api == null) { |
||||
throw new NullPointerException("setApi not called"); |
||||
} |
||||
|
||||
if (parser == null) { |
||||
throw new NullPointerException("setParser not called"); |
||||
} |
||||
|
||||
if (requestModifier == null) { |
||||
// No-op implementation.
|
||||
requestModifier = new CommonRequestModifier() { |
||||
}; |
||||
} |
||||
} |
||||
|
||||
public abstract void setup(); |
||||
|
||||
public void setName(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public void setIcon(SiteIcon icon) { |
||||
this.icon = icon; |
||||
} |
||||
|
||||
public void setBoardsType(BoardsType boardsType) { |
||||
this.boardsType = boardsType; |
||||
} |
||||
|
||||
public void setConfig(CommonConfig config) { |
||||
this.config = config; |
||||
} |
||||
|
||||
public void setResolvable(CommonSiteUrlHandler resolvable) { |
||||
this.resolvable = resolvable; |
||||
} |
||||
|
||||
public void setEndpoints(CommonEndpoints endpoints) { |
||||
this.endpoints = endpoints; |
||||
} |
||||
|
||||
public void setActions(CommonActions actions) { |
||||
this.actions = actions; |
||||
} |
||||
|
||||
public void setApi(CommonApi api) { |
||||
this.api = api; |
||||
} |
||||
|
||||
public void setParser(CommonParser parser) { |
||||
this.parser = parser; |
||||
} |
||||
|
||||
public void setRequestModifier(CommonRequestModifier requestModifier) { |
||||
this.requestModifier = requestModifier; |
||||
} |
||||
|
||||
/* |
||||
* Site implementation: |
||||
*/ |
||||
|
||||
@Override |
||||
public String name() { |
||||
return name; |
||||
} |
||||
|
||||
@Override |
||||
public SiteIcon icon() { |
||||
return icon; |
||||
} |
||||
|
||||
@Override |
||||
public BoardsType boardsType() { |
||||
return boardsType; |
||||
} |
||||
|
||||
@Override |
||||
public SiteUrlHandler resolvable() { |
||||
return resolvable; |
||||
} |
||||
|
||||
@Override |
||||
public boolean feature(Feature feature) { |
||||
return config.feature(feature); |
||||
} |
||||
|
||||
@Override |
||||
public boolean boardFeature(BoardFeature boardFeature, Board board) { |
||||
return config.boardFeature(boardFeature, board); |
||||
} |
||||
|
||||
@Override |
||||
public SiteEndpoints endpoints() { |
||||
return endpoints; |
||||
} |
||||
|
||||
@Override |
||||
public SiteActions actions() { |
||||
return actions; |
||||
} |
||||
|
||||
@Override |
||||
public SiteRequestModifier requestModifier() { |
||||
return requestModifier; |
||||
} |
||||
|
||||
@Override |
||||
public ChanReader chanReader() { |
||||
return api; |
||||
} |
||||
|
||||
public abstract class CommonConfig { |
||||
public boolean feature(Feature feature) { |
||||
return false; |
||||
} |
||||
|
||||
public boolean boardFeature(BoardFeature boardFeature, Board board) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public static abstract class CommonSiteUrlHandler implements SiteUrlHandler { |
||||
@Override |
||||
public boolean matchesName(String value) { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public boolean respondsTo(HttpUrl url) { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public String desktopUrl(Loadable loadable, @Nullable Post post) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public Loadable resolveLoadable(Site site, HttpUrl url) { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
public abstract class CommonEndpoints implements SiteEndpoints { |
||||
@NonNull |
||||
public SimpleHttpUrl from(String url) { |
||||
return new SimpleHttpUrl(url); |
||||
} |
||||
|
||||
@Override |
||||
public HttpUrl catalog(Board board) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public HttpUrl thread(Board board, Loadable loadable) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public HttpUrl imageUrl(Post.Builder post, Map<String, String> arg) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public HttpUrl thumbnailUrl(Post.Builder post, boolean spoiler, Map<String, String> arg) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public HttpUrl icon(Post.Builder post, String icon, Map<String, String> arg) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public HttpUrl boards() { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public HttpUrl reply(Loadable thread) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public HttpUrl delete(Post post) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public HttpUrl report(Post post) { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public HttpUrl login() { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
public class SimpleHttpUrl { |
||||
@NonNull |
||||
public HttpUrl.Builder url; |
||||
|
||||
public SimpleHttpUrl(String from) { |
||||
HttpUrl res = HttpUrl.parse(from); |
||||
if (res == null) { |
||||
throw new NullPointerException(); |
||||
} |
||||
url = res.newBuilder(); |
||||
} |
||||
|
||||
public SimpleHttpUrl(@NonNull HttpUrl.Builder from) { |
||||
url = from; |
||||
} |
||||
|
||||
public SimpleHttpUrl builder() { |
||||
return new SimpleHttpUrl(url.build().newBuilder()); |
||||
} |
||||
|
||||
public SimpleHttpUrl s(String segment) { |
||||
url.addPathSegment(segment); |
||||
return this; |
||||
} |
||||
|
||||
public HttpUrl url() { |
||||
return url.build(); |
||||
} |
||||
} |
||||
|
||||
public abstract class CommonActions implements SiteActions { |
||||
public void setupPost(Reply reply, MultipartHttpCall call) { |
||||
} |
||||
|
||||
public void handlePost(ReplyResponse response, Response httpResponse, String responseBody) { |
||||
} |
||||
|
||||
@Override |
||||
public void boards(BoardsListener boardsListener) { |
||||
} |
||||
|
||||
@Override |
||||
public void post(Reply reply, PostListener postListener) { |
||||
ReplyResponse replyResponse = new ReplyResponse(); |
||||
|
||||
reply.password = Long.toHexString(secureRandom.nextLong()); |
||||
replyResponse.password = reply.password; |
||||
|
||||
MultipartHttpCall call = new MultipartHttpCall(CommonSite.this) { |
||||
@Override |
||||
public void process(Response response, String result) throws IOException { |
||||
handlePost(replyResponse, response, result); |
||||
} |
||||
}; |
||||
|
||||
setupPost(reply, call); |
||||
|
||||
httpCallManager.makeHttpCall(call, new HttpCall.HttpCallback<HttpCall>() { |
||||
@Override |
||||
public void onHttpSuccess(HttpCall httpCall) { |
||||
postListener.onPostComplete(httpCall, replyResponse); |
||||
} |
||||
|
||||
@Override |
||||
public void onHttpFail(HttpCall httpCall, Exception e) { |
||||
postListener.onPostError(httpCall); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public boolean postRequiresAuthentication() { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public SiteAuthentication postAuthenticate() { |
||||
return SiteAuthentication.fromNone(); |
||||
} |
||||
|
||||
@Override |
||||
public void delete(DeleteRequest deleteRequest, DeleteListener deleteListener) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void login(LoginRequest loginRequest, LoginListener loginListener) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void logout() { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public boolean isLoggedIn() { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public LoginRequest getLoginDetails() { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
public abstract class CommonApi implements ChanReader { |
||||
@Override |
||||
public PostParser getParser() { |
||||
return parser; |
||||
} |
||||
} |
||||
|
||||
public abstract class CommonParser implements PostParser { |
||||
} |
||||
|
||||
public abstract class CommonRequestModifier implements SiteRequestModifier { |
||||
@Override |
||||
public void modifyHttpCall(HttpCall httpCall, Request.Builder requestBuilder) { |
||||
} |
||||
|
||||
@Override |
||||
public void modifyWebView(WebView webView) { |
||||
} |
||||
} |
||||
} |
@ -1,238 +0,0 @@ |
||||
/* |
||||
* 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; |
||||
|
||||
import android.graphics.Typeface; |
||||
import android.text.SpannableString; |
||||
import android.text.TextUtils; |
||||
import android.text.style.StrikethroughSpan; |
||||
import android.text.style.StyleSpan; |
||||
import android.text.style.TypefaceSpan; |
||||
import android.text.style.UnderlineSpan; |
||||
|
||||
import org.floens.chan.core.model.Post; |
||||
import org.floens.chan.core.model.PostLinkable; |
||||
import org.floens.chan.ui.span.AbsoluteSizeSpanHashed; |
||||
import org.floens.chan.ui.span.ForegroundColorSpanHashed; |
||||
import org.floens.chan.ui.theme.Theme; |
||||
import org.jsoup.nodes.Element; |
||||
import org.jsoup.select.Elements; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
|
||||
import static org.floens.chan.utils.AndroidUtils.sp; |
||||
|
||||
public class DefaultFutabaChanParserHandler implements FutabaChanParserHandler { |
||||
private static final Pattern COLOR_PATTERN = Pattern.compile("color:#([0-9a-fA-F]*)"); |
||||
|
||||
@Override |
||||
public CharSequence handleParagraph(FutabaChanParser parser, Theme theme, Post.Builder post, CharSequence text, Element element) { |
||||
return text; |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence handleSpan(FutabaChanParser parser, Theme theme, Post.Builder post, Element span) { |
||||
SpannableString quote; |
||||
|
||||
Set<String> classes = span.classNames(); |
||||
if (classes.contains("deadlink")) { |
||||
quote = new SpannableString(span.text()); |
||||
quote.setSpan(new ForegroundColorSpanHashed(theme.quoteColor), 0, quote.length(), 0); |
||||
quote.setSpan(new StrikethroughSpan(), 0, quote.length(), 0); |
||||
} else if (classes.contains("fortune")) { |
||||
// html looks like <span class="fortune" style="color:#0893e1"><br><br><b>Your fortune:</b>
|
||||
// manually add these <br>
|
||||
quote = new SpannableString("\n\n" + span.text()); |
||||
|
||||
String style = span.attr("style"); |
||||
if (!TextUtils.isEmpty(style)) { |
||||
style = style.replace(" ", ""); |
||||
|
||||
// private static final Pattern COLOR_PATTERN = Pattern.compile("color:#([0-9a-fA-F]*)");
|
||||
Matcher matcher = COLOR_PATTERN.matcher(style); |
||||
|
||||
int hexColor = 0xff0000; |
||||
if (matcher.find()) { |
||||
String group = matcher.group(1); |
||||
if (!TextUtils.isEmpty(group)) { |
||||
try { |
||||
hexColor = Integer.parseInt(group, 16); |
||||
} catch (NumberFormatException ignored) { |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (hexColor >= 0 && hexColor <= 0xffffff) { |
||||
quote.setSpan(new ForegroundColorSpanHashed(0xff000000 + hexColor), 0, quote.length(), 0); |
||||
quote.setSpan(new StyleSpan(Typeface.BOLD), 0, quote.length(), 0); |
||||
} |
||||
} |
||||
} else if (classes.contains("abbr")) { |
||||
return null; |
||||
} else { |
||||
quote = new SpannableString(span.text()); |
||||
quote.setSpan(new ForegroundColorSpanHashed(theme.inlineQuoteColor), 0, quote.length(), 0); |
||||
ChanParserHelper.detectLinks(theme, post, span.text(), quote); |
||||
} |
||||
|
||||
return quote; |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence handleTable(FutabaChanParser parser, Theme theme, Post.Builder post, Element table) { |
||||
List<CharSequence> parts = new ArrayList<>(); |
||||
Elements tableRows = table.getElementsByTag("tr"); |
||||
for (int i = 0; i < tableRows.size(); i++) { |
||||
Element tableRow = tableRows.get(i); |
||||
if (tableRow.text().length() > 0) { |
||||
Elements tableDatas = tableRow.getElementsByTag("td"); |
||||
for (int j = 0; j < tableDatas.size(); j++) { |
||||
Element tableData = tableDatas.get(j); |
||||
|
||||
SpannableString tableDataPart = new SpannableString(tableData.text()); |
||||
if (tableData.getElementsByTag("b").size() > 0) { |
||||
tableDataPart.setSpan(new StyleSpan(Typeface.BOLD), 0, tableDataPart.length(), 0); |
||||
tableDataPart.setSpan(new UnderlineSpan(), 0, tableDataPart.length(), 0); |
||||
} |
||||
|
||||
parts.add(tableDataPart); |
||||
|
||||
if (j < tableDatas.size() - 1) { |
||||
parts.add(": "); |
||||
} |
||||
} |
||||
|
||||
if (i < tableRows.size() - 1) { |
||||
parts.add("\n"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
SpannableString tableTotal = new SpannableString(TextUtils.concat(parts.toArray(new CharSequence[parts.size()]))); |
||||
tableTotal.setSpan(new ForegroundColorSpanHashed(theme.inlineQuoteColor), 0, tableTotal.length(), 0); |
||||
tableTotal.setSpan(new AbsoluteSizeSpanHashed(sp(12f)), 0, tableTotal.length(), 0); |
||||
|
||||
return tableTotal; |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence handleStrong(FutabaChanParser parser, Theme theme, Post.Builder post, Element strong) { |
||||
SpannableString red = new SpannableString(strong.text()); |
||||
red.setSpan(new ForegroundColorSpanHashed(theme.quoteColor), 0, red.length(), 0); |
||||
red.setSpan(new StyleSpan(Typeface.BOLD), 0, red.length(), 0); |
||||
return red; |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence handlePre(FutabaChanParser parser, Theme theme, Post.Builder post, Element pre) { |
||||
Set<String> classes = pre.classNames(); |
||||
if (classes.contains("prettyprint")) { |
||||
String text = ChanParserHelper.getNodeTextPreservingLineBreaks(pre); |
||||
SpannableString monospace = new SpannableString(text); |
||||
monospace.setSpan(new TypefaceSpan("monospace"), 0, monospace.length(), 0); |
||||
monospace.setSpan(new AbsoluteSizeSpanHashed(sp(12f)), 0, monospace.length(), 0); |
||||
return monospace; |
||||
} else { |
||||
return pre.text(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence handleStrike(FutabaChanParser parser, Theme theme, Post.Builder post, Element strike) { |
||||
SpannableString link = new SpannableString(strike.text()); |
||||
|
||||
PostLinkable pl = new PostLinkable(theme, strike.text(), strike.text(), PostLinkable.Type.SPOILER); |
||||
link.setSpan(pl, 0, link.length(), 0); |
||||
post.addLinkable(pl); |
||||
|
||||
return link; |
||||
} |
||||
|
||||
@Override |
||||
public Link handleAnchor(FutabaChanParser parser, Theme theme, Post.Builder post, Element anchor) { |
||||
String href = anchor.attr("href"); |
||||
Set<String> classes = anchor.classNames(); |
||||
|
||||
PostLinkable.Type t = null; |
||||
String key = null; |
||||
Object value = null; |
||||
if (classes.contains("quotelink")) { |
||||
if (href.contains("/thread/")) { |
||||
// link to another thread
|
||||
PostLinkable.ThreadLink threadLink = null; |
||||
|
||||
String[] slashSplit = href.split("/"); |
||||
if (slashSplit.length == 4) { |
||||
String board = slashSplit[1]; |
||||
String nums = slashSplit[3]; |
||||
String[] numsSplitted = nums.split("#p"); |
||||
if (numsSplitted.length == 2) { |
||||
try { |
||||
int tId = Integer.parseInt(numsSplitted[0]); |
||||
int pId = Integer.parseInt(numsSplitted[1]); |
||||
threadLink = new PostLinkable.ThreadLink(board, tId, pId); |
||||
} catch (NumberFormatException ignored) { |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (threadLink != null) { |
||||
t = PostLinkable.Type.THREAD; |
||||
key = anchor.text(); |
||||
value = threadLink; |
||||
} |
||||
} else { |
||||
// normal quote
|
||||
int id = -1; |
||||
|
||||
String[] splitted = href.split("#p"); |
||||
if (splitted.length == 2) { |
||||
try { |
||||
id = Integer.parseInt(splitted[1]); |
||||
} catch (NumberFormatException ignored) { |
||||
} |
||||
} |
||||
|
||||
if (id >= 0) { |
||||
t = PostLinkable.Type.QUOTE; |
||||
key = anchor.text(); |
||||
value = id; |
||||
} |
||||
} |
||||
} else { |
||||
// normal link
|
||||
t = PostLinkable.Type.LINK; |
||||
key = anchor.text(); |
||||
value = href; |
||||
} |
||||
|
||||
if (t != null && key != null && value != null) { |
||||
Link link = new Link(); |
||||
link.type = t; |
||||
link.key = key; |
||||
link.value = value; |
||||
return link; |
||||
} else { |
||||
return null; |
||||
} |
||||
} |
||||
} |
@ -1,44 +0,0 @@ |
||||
/* |
||||
* 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; |
||||
|
||||
import org.floens.chan.core.model.Post; |
||||
import org.floens.chan.core.model.PostLinkable; |
||||
import org.floens.chan.ui.theme.Theme; |
||||
import org.jsoup.nodes.Element; |
||||
|
||||
public interface FutabaChanParserHandler { |
||||
CharSequence handleParagraph(FutabaChanParser parser, Theme theme, Post.Builder post, CharSequence text, Element element); |
||||
|
||||
CharSequence handleSpan(FutabaChanParser parser, Theme theme, Post.Builder post, Element span); |
||||
|
||||
CharSequence handleTable(FutabaChanParser parser, Theme theme, Post.Builder post, Element table); |
||||
|
||||
CharSequence handleStrong(FutabaChanParser parser, Theme theme, Post.Builder post, Element strong); |
||||
|
||||
CharSequence handlePre(FutabaChanParser parser, Theme theme, Post.Builder post, Element pre); |
||||
|
||||
CharSequence handleStrike(FutabaChanParser parser, Theme theme, Post.Builder post, Element strike); |
||||
|
||||
Link handleAnchor(FutabaChanParser parser, Theme theme, Post.Builder post, Element anchor); |
||||
|
||||
class Link { |
||||
public PostLinkable.Type type; |
||||
public String key; |
||||
public Object value; |
||||
} |
||||
} |
@ -0,0 +1,66 @@ |
||||
/* |
||||
* 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; |
||||
|
||||
import org.floens.chan.core.site.Site; |
||||
import org.floens.chan.core.site.http.HttpCall; |
||||
|
||||
import java.io.File; |
||||
|
||||
import okhttp3.HttpUrl; |
||||
import okhttp3.MediaType; |
||||
import okhttp3.MultipartBody; |
||||
import okhttp3.Request; |
||||
import okhttp3.RequestBody; |
||||
|
||||
public abstract class MultipartHttpCall extends HttpCall { |
||||
private final MultipartBody.Builder formBuilder; |
||||
|
||||
private HttpUrl url; |
||||
|
||||
public MultipartHttpCall(Site site) { |
||||
super(site); |
||||
|
||||
formBuilder = new MultipartBody.Builder(); |
||||
formBuilder.setType(MultipartBody.FORM); |
||||
} |
||||
|
||||
public MultipartHttpCall url(HttpUrl url) { |
||||
this.url = url; |
||||
return this; |
||||
} |
||||
|
||||
public MultipartHttpCall parameter(String name, String value) { |
||||
formBuilder.addFormDataPart(name, value); |
||||
return this; |
||||
} |
||||
|
||||
public MultipartHttpCall fileParameter(String name, String filename, File file) { |
||||
formBuilder.addFormDataPart(name, filename, RequestBody.create( |
||||
MediaType.parse("application/octet-stream"), file |
||||
)); |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public void setup(Request.Builder requestBuilder) { |
||||
requestBuilder.url(url); |
||||
requestBuilder.addHeader("Referer", url.toString()); |
||||
requestBuilder.post(formBuilder.build()); |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
/* |
||||
* 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.parser; |
||||
|
||||
|
||||
import android.util.JsonReader; |
||||
|
||||
public interface ChanReader { |
||||
PostParser getParser(); |
||||
|
||||
void loadThread(JsonReader reader, ChanReaderProcessingQueue queue) throws Exception; |
||||
|
||||
void loadCatalog(JsonReader reader, ChanReaderProcessingQueue queue) throws Exception; |
||||
|
||||
void readPostObject(JsonReader reader, ChanReaderProcessingQueue queue) throws Exception; |
||||
} |
@ -0,0 +1,307 @@ |
||||
/* |
||||
* 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.parser; |
||||
|
||||
import android.graphics.Typeface; |
||||
import android.support.annotation.AnyThread; |
||||
import android.text.SpannableString; |
||||
import android.text.TextUtils; |
||||
import android.text.style.StrikethroughSpan; |
||||
import android.text.style.StyleSpan; |
||||
import android.text.style.TypefaceSpan; |
||||
import android.text.style.UnderlineSpan; |
||||
|
||||
import org.floens.chan.core.model.Post; |
||||
import org.floens.chan.core.model.PostLinkable; |
||||
import org.floens.chan.ui.span.AbsoluteSizeSpanHashed; |
||||
import org.floens.chan.ui.span.ForegroundColorSpanHashed; |
||||
import org.floens.chan.ui.theme.Theme; |
||||
import org.jsoup.nodes.Element; |
||||
import org.jsoup.select.Elements; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
|
||||
import static org.floens.chan.utils.AndroidUtils.sp; |
||||
|
||||
@AnyThread |
||||
public class CommentParser { |
||||
private static final String SAVED_REPLY_SUFFIX = " (You)"; |
||||
private static final String OP_REPLY_SUFFIX = " (OP)"; |
||||
private static final String EXTERN_THREAD_LINK_SUFFIX = " \u2192"; // arrow to the right
|
||||
|
||||
private Pattern fullQuotePattern = Pattern.compile("/(\\w+)/\\w+/(\\d+)#p(\\d+)"); |
||||
private Pattern quotePattern = Pattern.compile(".*#p(\\d+)"); |
||||
private Pattern colorPattern = Pattern.compile("color:#([0-9a-fA-F]*)"); |
||||
|
||||
public void setQuotePattern(Pattern quotePattern) { |
||||
this.quotePattern = quotePattern; |
||||
} |
||||
|
||||
public void setFullQuotePattern(Pattern fullQuotePattern) { |
||||
this.fullQuotePattern = fullQuotePattern; |
||||
} |
||||
|
||||
public CharSequence handleTag(PostParser.Callback callback, |
||||
Theme theme, |
||||
Post.Builder post, |
||||
String tag, |
||||
CharSequence text, |
||||
Element element) { |
||||
switch (tag) { |
||||
case "br": |
||||
return "\n"; |
||||
case "span": |
||||
return handleSpan(theme, post, text, element); |
||||
case "p": |
||||
return appendBreakIfNotLastSibling( |
||||
handleParagraph(theme, post, text, element), element); |
||||
case "table": |
||||
return handleTable(theme, post, text, element); |
||||
case "strong": |
||||
return handleStrong(theme, post, text, element); |
||||
case "a": |
||||
return handleAnchor(theme, post, text, element, callback); |
||||
case "s": |
||||
return handleStrike(theme, post, text, element); |
||||
case "pre": |
||||
return handlePre(theme, post, text, element); |
||||
default: |
||||
// Unknown tag, return the text;
|
||||
return text; |
||||
} |
||||
} |
||||
|
||||
private CharSequence appendBreakIfNotLastSibling(CharSequence text, Element element) { |
||||
if (element.nextSibling() != null) { |
||||
return TextUtils.concat(text, "\n"); |
||||
} else { |
||||
return text; |
||||
} |
||||
} |
||||
|
||||
private CharSequence handleAnchor(Theme theme, |
||||
Post.Builder post, |
||||
CharSequence text, |
||||
Element anchor, |
||||
PostParser.Callback callback) { |
||||
CommentParser.Link handlerLink = matchAnchor(post, text, anchor, callback); |
||||
|
||||
if (handlerLink != null) { |
||||
if (handlerLink.type == PostLinkable.Type.THREAD) { |
||||
handlerLink.key = TextUtils.concat(handlerLink.key, EXTERN_THREAD_LINK_SUFFIX); |
||||
} |
||||
|
||||
if (handlerLink.type == PostLinkable.Type.QUOTE) { |
||||
int postNo = (int) handlerLink.value; |
||||
post.addReplyTo(postNo); |
||||
|
||||
// Append (OP) when it's a reply to OP
|
||||
if (postNo == post.opId) { |
||||
handlerLink.key = TextUtils.concat(handlerLink.key, OP_REPLY_SUFFIX); |
||||
} |
||||
|
||||
// Append (You) when it's a reply to an saved reply
|
||||
if (callback.isSaved(postNo)) { |
||||
handlerLink.key = TextUtils.concat(handlerLink.key, SAVED_REPLY_SUFFIX); |
||||
} |
||||
} |
||||
|
||||
SpannableString res = new SpannableString(handlerLink.key); |
||||
PostLinkable pl = new PostLinkable(theme, handlerLink.key, handlerLink.value, handlerLink.type); |
||||
res.setSpan(pl, 0, res.length(), 0); |
||||
post.addLinkable(pl); |
||||
|
||||
return res; |
||||
} else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
public CharSequence handleSpan(Theme theme, Post.Builder post, CharSequence text, Element span) { |
||||
SpannableString quote; |
||||
|
||||
Set<String> classes = span.classNames(); |
||||
if (classes.contains("deadlink")) { |
||||
quote = new SpannableString(span.text()); |
||||
quote.setSpan(new ForegroundColorSpanHashed(theme.quoteColor), 0, quote.length(), 0); |
||||
quote.setSpan(new StrikethroughSpan(), 0, quote.length(), 0); |
||||
} else if (classes.contains("fortune")) { |
||||
// html looks like <span class="fortune" style="color:#0893e1"><br><br><b>Your fortune:</b>
|
||||
// manually add these <br>
|
||||
quote = new SpannableString("\n\n" + span.text()); |
||||
|
||||
String style = span.attr("style"); |
||||
if (!TextUtils.isEmpty(style)) { |
||||
style = style.replace(" ", ""); |
||||
|
||||
Matcher matcher = colorPattern.matcher(style); |
||||
|
||||
int hexColor = 0xff0000; |
||||
if (matcher.find()) { |
||||
String group = matcher.group(1); |
||||
if (!TextUtils.isEmpty(group)) { |
||||
try { |
||||
hexColor = Integer.parseInt(group, 16); |
||||
} catch (NumberFormatException ignored) { |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (hexColor >= 0 && hexColor <= 0xffffff) { |
||||
quote.setSpan(new ForegroundColorSpanHashed(0xff000000 + hexColor), 0, quote.length(), 0); |
||||
quote.setSpan(new StyleSpan(Typeface.BOLD), 0, quote.length(), 0); |
||||
} |
||||
} |
||||
} else if (classes.contains("spoiler")) { |
||||
PostLinkable pl = new PostLinkable(theme, span.text(), span.text(), PostLinkable.Type.SPOILER); |
||||
post.addLinkable(pl); |
||||
return span(span.text(), pl); |
||||
} else if (classes.contains("abbr")) { |
||||
return null; |
||||
} else { |
||||
quote = new SpannableString(span.text()); |
||||
quote.setSpan(new ForegroundColorSpanHashed(theme.inlineQuoteColor), 0, quote.length(), 0); |
||||
CommentParserHelper.detectLinks(theme, post, span.text(), quote); |
||||
} |
||||
|
||||
return quote; |
||||
} |
||||
|
||||
public CharSequence handleParagraph(Theme theme, Post.Builder post, CharSequence text, Element span) { |
||||
return text; |
||||
} |
||||
|
||||
public CharSequence handleTable(Theme theme, Post.Builder post, CharSequence text, Element table) { |
||||
List<CharSequence> parts = new ArrayList<>(); |
||||
Elements tableRows = table.getElementsByTag("tr"); |
||||
for (int i = 0; i < tableRows.size(); i++) { |
||||
Element tableRow = tableRows.get(i); |
||||
if (tableRow.text().length() > 0) { |
||||
Elements tableDatas = tableRow.getElementsByTag("td"); |
||||
for (int j = 0; j < tableDatas.size(); j++) { |
||||
Element tableData = tableDatas.get(j); |
||||
|
||||
SpannableString tableDataPart = new SpannableString(tableData.text()); |
||||
if (tableData.getElementsByTag("b").size() > 0) { |
||||
tableDataPart.setSpan(new StyleSpan(Typeface.BOLD), 0, tableDataPart.length(), 0); |
||||
tableDataPart.setSpan(new UnderlineSpan(), 0, tableDataPart.length(), 0); |
||||
} |
||||
|
||||
parts.add(tableDataPart); |
||||
|
||||
if (j < tableDatas.size() - 1) parts.add(": "); |
||||
} |
||||
|
||||
if (i < tableRows.size() - 1) parts.add("\n"); |
||||
} |
||||
} |
||||
|
||||
// Overrides the text (possibly) parsed by child nodes.
|
||||
return span(TextUtils.concat(parts.toArray(new CharSequence[parts.size()])), |
||||
new ForegroundColorSpanHashed(theme.inlineQuoteColor), |
||||
new AbsoluteSizeSpanHashed(sp(12f))); |
||||
} |
||||
|
||||
public CharSequence handleStrong(Theme theme, Post.Builder post, CharSequence text, Element strong) { |
||||
return span(text, |
||||
new ForegroundColorSpanHashed(theme.quoteColor), |
||||
new StyleSpan(Typeface.BOLD)); |
||||
} |
||||
|
||||
public CharSequence handlePre(Theme theme, Post.Builder post, CharSequence text, Element pre) { |
||||
Set<String> classes = pre.classNames(); |
||||
if (classes.contains("prettyprint")) { |
||||
// String linebreakText = CommentParserHelper.getNodeTextPreservingLineBreaks(pre);
|
||||
return span(text, |
||||
new TypefaceSpan("monospace"), |
||||
new AbsoluteSizeSpanHashed(sp(12f))); |
||||
} else { |
||||
return pre.text(); |
||||
} |
||||
} |
||||
|
||||
public CharSequence handleStrike(Theme theme, Post.Builder post, CharSequence text, Element strike) { |
||||
PostLinkable pl = new PostLinkable(theme, text.toString(), text, PostLinkable.Type.SPOILER); |
||||
post.addLinkable(pl); |
||||
|
||||
return span(text, pl); |
||||
} |
||||
|
||||
public Link matchAnchor(Post.Builder post, CharSequence text, Element anchor, PostParser.Callback callback) { |
||||
String href = anchor.attr("href"); |
||||
|
||||
PostLinkable.Type t; |
||||
Object value; |
||||
|
||||
Matcher externalMatcher = fullQuotePattern.matcher(href); |
||||
if (externalMatcher.matches()) { |
||||
String board = externalMatcher.group(1); |
||||
int threadId = Integer.parseInt(externalMatcher.group(2)); |
||||
int postId = Integer.parseInt(externalMatcher.group(3)); |
||||
|
||||
if (board.equals(post.board.code) && callback.isInternal(postId)) { |
||||
t = PostLinkable.Type.QUOTE; |
||||
value = postId; |
||||
} else { |
||||
t = PostLinkable.Type.THREAD; |
||||
value = new PostLinkable.ThreadLink(board, threadId, postId); |
||||
} |
||||
} else { |
||||
Matcher quoteMatcher = quotePattern.matcher(href); |
||||
if (quoteMatcher.matches()) { |
||||
t = PostLinkable.Type.QUOTE; |
||||
value = Integer.parseInt(quoteMatcher.group(1)); |
||||
} else { |
||||
// normal link
|
||||
t = PostLinkable.Type.LINK; |
||||
value = href; |
||||
} |
||||
} |
||||
|
||||
Link link = new Link(); |
||||
link.type = t; |
||||
link.key = text; |
||||
link.value = value; |
||||
return link; |
||||
} |
||||
|
||||
public SpannableString span(CharSequence text, Object... additionalSpans) { |
||||
SpannableString result = new SpannableString(text); |
||||
int l = result.length(); |
||||
|
||||
if (additionalSpans != null && additionalSpans.length > 0) { |
||||
for (Object additionalSpan : additionalSpans) { |
||||
if (additionalSpan != null) { |
||||
result.setSpan(additionalSpan, 0, l, 0); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
public class Link { |
||||
public PostLinkable.Type type; |
||||
public CharSequence key; |
||||
public Object value; |
||||
} |
||||
} |
@ -0,0 +1,47 @@ |
||||
/* |
||||
* 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.sites.vichan; |
||||
|
||||
import android.text.SpannableString; |
||||
|
||||
import org.floens.chan.core.model.Post; |
||||
import org.floens.chan.core.site.parser.CommentParser; |
||||
import org.floens.chan.core.site.parser.CommentParserHelper; |
||||
import org.floens.chan.ui.span.ForegroundColorSpanHashed; |
||||
import org.floens.chan.ui.theme.Theme; |
||||
import org.jsoup.nodes.Element; |
||||
|
||||
import java.util.regex.Pattern; |
||||
|
||||
public class ViChanCommentParser extends CommentParser { |
||||
public ViChanCommentParser() { |
||||
setQuotePattern(Pattern.compile(".*#(\\d+)")); |
||||
setFullQuotePattern(Pattern.compile("/(\\w+)/\\w+/(\\d+)\\.html#(\\d+)")); |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence handleParagraph(Theme theme, Post.Builder post, CharSequence text, Element element) { |
||||
if (element.hasClass("quote")) { |
||||
SpannableString res = span(text, new ForegroundColorSpanHashed(theme.inlineQuoteColor)); |
||||
CommentParserHelper.detectLinks(theme, post, res.toString(), res); |
||||
return res; |
||||
} else { |
||||
return text; |
||||
} |
||||
} |
||||
} |
@ -1,134 +0,0 @@ |
||||
/* |
||||
* 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.sites.vichan; |
||||
|
||||
import android.text.SpannableString; |
||||
|
||||
import org.floens.chan.core.model.Post; |
||||
import org.floens.chan.core.model.PostLinkable; |
||||
import org.floens.chan.core.site.common.ChanParserHelper; |
||||
import org.floens.chan.core.site.common.DefaultFutabaChanParserHandler; |
||||
import org.floens.chan.core.site.common.FutabaChanParser; |
||||
import org.floens.chan.ui.span.ForegroundColorSpanHashed; |
||||
import org.floens.chan.ui.theme.Theme; |
||||
import org.jsoup.nodes.Element; |
||||
|
||||
import java.util.Set; |
||||
|
||||
public class ViChanParserHandler extends DefaultFutabaChanParserHandler { |
||||
@Override |
||||
public CharSequence handleParagraph(FutabaChanParser parser, Theme theme, Post.Builder post, CharSequence text, Element element) { |
||||
if (element.hasClass("quote")) { |
||||
SpannableString quote = new SpannableString(text); |
||||
quote.setSpan(new ForegroundColorSpanHashed(theme.inlineQuoteColor), 0, quote.length(), 0); |
||||
ChanParserHelper.detectLinks(theme, post, quote.toString(), quote); |
||||
return quote; |
||||
} else { |
||||
return text; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence handleSpan(FutabaChanParser parser, Theme theme, Post.Builder post, Element span) { |
||||
SpannableString quote; |
||||
|
||||
Set<String> classes = span.classNames(); |
||||
if (classes.contains("abbr")) { |
||||
return null; |
||||
} else if (classes.contains("spoiler")) { |
||||
quote = new SpannableString(span.text()); |
||||
PostLinkable pl = new PostLinkable(theme, span.text(), span.text(), PostLinkable.Type.SPOILER); |
||||
quote.setSpan(pl, 0, quote.length(), 0); |
||||
post.addLinkable(pl); |
||||
} else { |
||||
quote = new SpannableString(span.text()); |
||||
quote.setSpan(new ForegroundColorSpanHashed(theme.inlineQuoteColor), 0, quote.length(), 0); |
||||
ChanParserHelper.detectLinks(theme, post, span.text(), quote); |
||||
} |
||||
|
||||
return quote; |
||||
} |
||||
|
||||
@Override |
||||
public Link handleAnchor(FutabaChanParser parser, Theme theme, Post.Builder post, Element anchor) { |
||||
String href = anchor.attr("href"); |
||||
|
||||
PostLinkable.Type t = null; |
||||
String key = null; |
||||
Object value = null; |
||||
if (href.startsWith("/")) { |
||||
if (!href.startsWith("/" + post.board.code + "/res/")) { |
||||
// link to another thread
|
||||
PostLinkable.ThreadLink threadLink = null; |
||||
|
||||
String[] slashSplit = href.split("/"); |
||||
if (slashSplit.length == 4) { |
||||
String board = slashSplit[1]; |
||||
String nums = slashSplit[3]; |
||||
String[] numsSplitted = nums.split("#"); |
||||
if (numsSplitted.length == 2) { |
||||
try { |
||||
int tId = Integer.parseInt(numsSplitted[0].replace(".html", "")); |
||||
int pId = Integer.parseInt(numsSplitted[1]); |
||||
threadLink = new PostLinkable.ThreadLink(board, tId, pId); |
||||
} catch (NumberFormatException ignored) { |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (threadLink != null) { |
||||
t = PostLinkable.Type.THREAD; |
||||
key = anchor.text(); |
||||
value = threadLink; |
||||
} |
||||
} else { |
||||
// normal quote
|
||||
int id = -1; |
||||
|
||||
String[] splitted = href.split("#"); |
||||
if (splitted.length == 2) { |
||||
try { |
||||
id = Integer.parseInt(splitted[1]); |
||||
} catch (NumberFormatException ignored) { |
||||
} |
||||
} |
||||
|
||||
if (id >= 0) { |
||||
t = PostLinkable.Type.QUOTE; |
||||
key = anchor.text(); |
||||
value = id; |
||||
} |
||||
} |
||||
} else { |
||||
// normal link
|
||||
t = PostLinkable.Type.LINK; |
||||
key = anchor.text(); |
||||
value = href; |
||||
} |
||||
|
||||
if (t != null && key != null && value != null) { |
||||
Link link = new Link(); |
||||
link.type = t; |
||||
link.key = key; |
||||
link.value = value; |
||||
return link; |
||||
} else { |
||||
return null; |
||||
} |
||||
} |
||||
} |
@ -1,129 +0,0 @@ |
||||
/* |
||||
* 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.sites.vichan; |
||||
|
||||
import android.text.TextUtils; |
||||
|
||||
import org.floens.chan.core.site.Site; |
||||
import org.floens.chan.core.site.common.CommonReplyHttpCall; |
||||
import org.floens.chan.core.site.http.Reply; |
||||
import org.floens.chan.utils.Logger; |
||||
import org.jsoup.Jsoup; |
||||
|
||||
import java.io.IOException; |
||||
import java.util.List; |
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
|
||||
import okhttp3.HttpUrl; |
||||
import okhttp3.MediaType; |
||||
import okhttp3.MultipartBody; |
||||
import okhttp3.RequestBody; |
||||
import okhttp3.Response; |
||||
|
||||
public class ViChanReplyHttpCall extends CommonReplyHttpCall { |
||||
private static final String TAG = "ViChanReplyHttpCall"; |
||||
|
||||
private static final Pattern REQUIRE_AUTHENTICATION = Pattern.compile(".*\"captcha\": ?true.*"); |
||||
private static final Pattern ERROR_MESSAGE = |
||||
Pattern.compile(".*<h1>Error</h1>.*<h2[^>]*>(.*?)<\\/h2>.*"); |
||||
|
||||
public ViChanReplyHttpCall(Site site, Reply reply) { |
||||
super(site, reply); |
||||
} |
||||
|
||||
@Override |
||||
public void addParameters(MultipartBody.Builder formBuilder) { |
||||
// formBuilder.addFormDataPart("pwd", replyResponse.password);
|
||||
|
||||
formBuilder.addFormDataPart("board", reply.loadable.board.code); |
||||
|
||||
if (reply.loadable.isThreadMode()) { |
||||
formBuilder.addFormDataPart("post", "New Reply"); |
||||
|
||||
formBuilder.addFormDataPart("thread", String.valueOf(reply.loadable.no)); |
||||
} else { |
||||
formBuilder.addFormDataPart("post", "New Thread"); |
||||
|
||||
formBuilder.addFormDataPart("page", "1"); |
||||
} |
||||
|
||||
formBuilder.addFormDataPart("name", reply.name); |
||||
formBuilder.addFormDataPart("email", reply.options); |
||||
|
||||
if (!reply.loadable.isThreadMode() && !TextUtils.isEmpty(reply.subject)) { |
||||
formBuilder.addFormDataPart("subject", reply.subject); |
||||
} |
||||
|
||||
formBuilder.addFormDataPart("body", reply.comment); |
||||
|
||||
if (reply.file != null) { |
||||
formBuilder.addFormDataPart("file", reply.fileName, RequestBody.create( |
||||
MediaType.parse("application/octet-stream"), reply.file |
||||
)); |
||||
} |
||||
|
||||
if (reply.spoilerImage) { |
||||
formBuilder.addFormDataPart("spoiler", "on"); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void process(Response response, String result) throws IOException { |
||||
Matcher authenticationMatcher = REQUIRE_AUTHENTICATION.matcher(result); |
||||
Matcher errorMessageMatcher = ERROR_MESSAGE.matcher(result); |
||||
if (authenticationMatcher.find()) { |
||||
replyResponse.requireAuthentication = true; |
||||
replyResponse.errorMessage = result; |
||||
} else if (errorMessageMatcher.find()) { |
||||
replyResponse.errorMessage = Jsoup.parse(errorMessageMatcher.group(1)).body().text(); |
||||
} else { |
||||
Logger.d(TAG, "url: " + response.request().url().toString()); |
||||
Logger.d(TAG, "body: " + response); |
||||
|
||||
// TODO(multisite): 8ch redirects us, but the result is a 404, and we need that
|
||||
// redirect url to figure out what we posted.
|
||||
HttpUrl url = response.request().url(); |
||||
List<String> segments = url.pathSegments(); |
||||
|
||||
String board = null; |
||||
int threadId = 0, postId = 0; |
||||
try { |
||||
if (segments.size() == 3) { |
||||
board = segments.get(0); |
||||
threadId = Integer.parseInt( |
||||
segments.get(2).replace(".html", "")); |
||||
postId = Integer.parseInt(url.encodedFragment()); |
||||
} |
||||
} catch (NumberFormatException ignored) { |
||||
} |
||||
|
||||
if (postId == 0) { |
||||
postId = threadId; |
||||
} |
||||
|
||||
if (board != null && threadId != 0) { |
||||
replyResponse.threadNo = threadId; |
||||
replyResponse.postNo = postId; |
||||
replyResponse.posted = true; |
||||
} else { |
||||
replyResponse.errorMessage = "Error posting: could not find posted thread."; |
||||
} |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue