/*
* Overchan Android (Meta Imageboard Client)
* Copyright (C) 2014-2016 miku-nyan <https://github.com/miku-nyan>
*
* 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 nya.miku.wishmaster.chans.sevenchan;
import java.io.InputStream;
import java.util.regex.Pattern;
import nya.miku.wishmaster.R;
import nya.miku.wishmaster.api.AbstractKusabaModule;
import nya.miku.wishmaster.api.interfaces.CancellableTask;
import nya.miku.wishmaster.api.interfaces.ProgressListener;
import nya.miku.wishmaster.api.models.BoardModel;
import nya.miku.wishmaster.api.models.CaptchaModel;
import nya.miku.wishmaster.api.models.PostModel;
import nya.miku.wishmaster.api.models.SendPostModel;
import nya.miku.wishmaster.api.models.SimpleBoardModel;
import nya.miku.wishmaster.api.models.ThreadModel;
import nya.miku.wishmaster.api.models.UrlPageModel;
import nya.miku.wishmaster.api.util.ChanModels;
import nya.miku.wishmaster.api.util.RegexUtils;
import nya.miku.wishmaster.api.util.WakabaReader;
import nya.miku.wishmaster.http.ExtendedMultipartBuilder;
import nya.miku.wishmaster.http.recaptcha.Recaptcha;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.preference.PreferenceGroup;
import android.support.v4.content.res.ResourcesCompat;
public class SevenchanModule extends AbstractKusabaModule {
private static final String CHAN_NAME = "7chan.org";
private static final String RECAPTCHA_KEY = "6LdVg8YSAAAAAOhqx0eFT1Pi49fOavnYgy7e-lTO";
static final String TIMEZONE = "GMT+4"; // ?
private static final Pattern THREAD_REFERENCE_ALT = Pattern.compile("read.php\\?b=([^&]+)&t=(\\d+)(?:&p=)?(\\d*).*");
private static final SimpleBoardModel[] BOARDS = new SimpleBoardModel[] {
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "7ch", "Site Discussion", "7chan & Related Services", false),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "ch7", "Channel7 & Radio7", "7chan & Related Services", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "irc", "Internet Relay Circlejerk", "7chan & Related Services", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "777", "/selfhelp/", "Premium Content", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "b", "Random", "Premium Content", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "banner", "Banners", "Premium Content", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "fl", "Flash", "Premium Content", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "gfx", "Graphics Manipulation", "Premium Content", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "class", "The Finer Things", "SFW", false),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "co", "Comics and Cartoons", "SFW", false),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "eh", "Particularly uninteresting conversation", "SFW", false),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "fit", "Fitness & Health", "SFW", false),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "halp", "Technical Support", "SFW", false),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "jew", "Thrifty Living", "SFW", false),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "lit", "Literature", "SFW", false),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "phi", "Philosophy", "SFW", false),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "pr", "Programming", "SFW", false),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "rnb", "Rage and Baww", "SFW", false),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "sci", "Science, Technology, Engineering, and Mathematics", "SFW", false),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "tg", "Tabletop Games", "SFW", false),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "w", "Weapons", "SFW", false),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "zom", "Zombies", "SFW", false),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "a", "Anime & Manga", "General", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "grim", "Cold, Grim & Miserable", "General", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "hi", "History and Culture", "General", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "me", "Film, Music & Television", "General", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "rx", "Drugs", "General", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "vg", "", "General", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "wp", "Wallpapers", "General", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "x", "Paranormal & Conspiracy", "General", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "cake", "Delicious", "Porn", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "cd", "Crossdressing", "Porn", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "d", "Alternative Hentai", "Porn", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "di", "Sexy Beautiful Traps", "Porn", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "elit", "Erotic Literature", "Porn", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "fag", "Men Discussion", "Porn", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "fur", "Furry", "Porn", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "gif", "Animated GIFs", "Porn", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "h", "Hentai", "Porn", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "men", "Sexy Beautiful Men", "Porn", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "pco", "Porn Comics", "Porn", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "s", "Sexy Beautiful Women", "Porn", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "sm", "Shotacon", "Porn", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "ss", "Straight Shotacon", "Porn", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "unf", "Uniforms", "Porn", true),
ChanModels.obtainSimpleBoardModel(CHAN_NAME, "v", "The Vineyard", "Porn", true)
};
private Recaptcha lastCaptcha = null;
public SevenchanModule(SharedPreferences preferences, Resources resources) {
super(preferences, resources);
}
@Override
public String getChanName() {
return CHAN_NAME;
}
@Override
public String getDisplayingName() {
return "7chan";
}
@Override
public Drawable getChanFavicon() {
return ResourcesCompat.getDrawable(resources, R.drawable.favicon_7chan, null);
}
@Override
protected String getUsingDomain() {
return "7chan.org";
}
@Override
protected boolean canHttps() {
return true;
}
@Override
protected boolean canCloudflare() {
return true;
}
private boolean loadOnlyNewPosts() {
return loadOnlyNewPosts(true);
}
@Override
public void addPreferencesOnScreen(PreferenceGroup preferenceGroup) {
addOnlyNewPostsPreference(preferenceGroup, true);
super.addPreferencesOnScreen(preferenceGroup);
}
@Override
protected WakabaReader getKusabaReader(InputStream stream, UrlPageModel urlModel) {
return new SevenchanReader(stream);
}
@Override
protected SimpleBoardModel[] getBoardsList() {
return BOARDS;
}
@Override
public BoardModel getBoard(String shortName, ProgressListener listener, CancellableTask task) throws Exception {
BoardModel board = super.getBoard(shortName, listener, task);
board.timeZoneId = TIMEZONE;
board.defaultUserName = "";
board.requiredFileForNewThread = !shortName.equals("halp") && !shortName.equals("7ch");
board.allowNames = !shortName.equals("b");
board.attachmentsFormatFilters = shortName.equals("fl") ? new String[] { "swf" } : null;
board.markType = BoardModel.MARK_BBCODE;
return board;
}
@Override
public PostModel[] getPostsList(String boardName, String threadNumber, ProgressListener listener, CancellableTask task, PostModel[] oldList)
throws Exception {
if (loadOnlyNewPosts() && oldList != null && oldList.length > 0) {
String url = getUsingUrl() +
"ajax.php?act=spy&board=" + boardName + "&thread=" + threadNumber + "&pastid=" + oldList[oldList.length-1].number;
ThreadModel[] page = readWakabaPage(url, listener, task, true, null);
if (page != null && page.length > 0) {
PostModel[] posts = new PostModel[oldList.length + page[0].posts.length];
for (int i=0; i<oldList.length; ++i) posts[i] = oldList[i];
for (int i=0; i<page[0].posts.length; ++i) posts[oldList.length + i] = page[0].posts[i];
return posts;
} else {
return oldList;
}
}
PostModel[] result = super.getPostsList(boardName, threadNumber, listener, task, oldList);
if (result.length >= 1000 && loadOnlyNewPosts()) return getPostsList(boardName, threadNumber, listener, task, result);
return result;
}
@Override
public CaptchaModel getNewCaptcha(String boardName, String threadNumber, ProgressListener listener, CancellableTask task) throws Exception {
if (threadNumber != null) return null;
Recaptcha recaptcha = Recaptcha.obtain(RECAPTCHA_KEY, task, httpClient, useHttps() ? "https" : "http");
CaptchaModel model = new CaptchaModel();
model.type = CaptchaModel.TYPE_NORMAL;
model.bitmap = recaptcha.bitmap;
lastCaptcha = recaptcha;
return model;
}
@Override
protected void setSendPostEntityMain(SendPostModel model, ExtendedMultipartBuilder postEntityBuilder) throws Exception {
super.setSendPostEntityMain(model, postEntityBuilder);
if (model.threadNumber == null) {
if (lastCaptcha == null) throw new Exception("Invalid captcha");
postEntityBuilder.
addString("recaptcha_challenge_field", lastCaptcha.challenge).
addString("recaptcha_response_field", model.captchaAnswer);
lastCaptcha = null;
postEntityBuilder.addString("embed", "");
}
}
@Override
protected void setSendPostEntityAttachments(SendPostModel model, ExtendedMultipartBuilder postEntityBuilder) throws Exception {
if (model.attachments != null && model.attachments.length > 0)
postEntityBuilder.addFile("imagefile[]", model.attachments[0], model.randomHash);
else if (model.threadNumber == null) postEntityBuilder.addString("nofile", "on");
}
@Override
public UrlPageModel parseUrl(String url) throws IllegalArgumentException {
return super.parseUrl(RegexUtils.replaceAll(url, THREAD_REFERENCE_ALT, "$1/res/$2.html#$3"));
}
@Override
public String fixRelativeUrl(String url) {
if (url.startsWith("//")) return (useHttps() ? "https:" : "http:") + url;
return super.fixRelativeUrl(url);
}
}