Skip to content
This repository has been archived by the owner on Sep 25, 2021. It is now read-only.

Commit

Permalink
add 4chan archive support
Browse files Browse the repository at this point in the history
reads the html because there is no api endpoint for it.
adds a new `archive` boolean to the Board model.
  • Loading branch information
floens committed Feb 13, 2018
1 parent 162a0b2 commit 9c4f433
Show file tree
Hide file tree
Showing 19 changed files with 723 additions and 6 deletions.
Expand Up @@ -45,7 +45,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
private static final String TAG = "DatabaseHelper";

private static final String DATABASE_NAME = "ChanDB";
private static final int DATABASE_VERSION = 22;
private static final int DATABASE_VERSION = 23;

public Dao<Pin, Integer> pinDao;
public Dao<Loadable, Integer> loadableDao;
Expand Down Expand Up @@ -230,6 +230,14 @@ public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource

SiteService.addSiteForLegacy();
}

if (oldVersion < 23) {
try {
pinDao.executeRawNoArgs("ALTER TABLE board ADD COLUMN \"archive\" INTEGER;");
} catch (SQLException e) {
Logger.e(TAG, "Error upgrading to version 14", e);
}
}
}

public void reset() {
Expand Down
47 changes: 47 additions & 0 deletions Clover/app/src/main/java/org/floens/chan/core/model/Archive.java
@@ -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.model;

import java.util.List;

public class Archive {
public final List<ArchiveItem> items;

public static Archive fromItems(List<ArchiveItem> items) {
return new Archive(items);
}

private Archive(List<ArchiveItem> items) {
this.items = items;
}

public static class ArchiveItem {
public final String description;

public final int id;

public static ArchiveItem fromDescriptionId(String description, int id) {
return new ArchiveItem(description, id);
}

private ArchiveItem(String description, int id) {
this.description = description;
this.id = id;
}
}
}
Expand Up @@ -131,6 +131,9 @@ public class Board implements SiteReference {
@DatabaseField
public String description;

@DatabaseField
public boolean archive = false;

@Deprecated // public, at least
public Board() {
}
Expand Down Expand Up @@ -200,6 +203,7 @@ public void updateExcudingUserFields(Board o) {
trollFlags = o.trollFlags;
mathTags = o.mathTags;
description = o.description;
archive = o.archive;
}

/**
Expand Down Expand Up @@ -238,6 +242,7 @@ public Board copy() {
b.trollFlags = trollFlags;
b.mathTags = mathTags;
b.description = description;
b.archive = archive;
return b;
}
}
@@ -0,0 +1,62 @@
/*
* 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.net;

import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.Response.Listener;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HttpHeaderParser;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import java.io.ByteArrayInputStream;
import java.io.IOException;

public abstract class HtmlReaderRequest<T> extends Request<T> {
protected final Listener<T> listener;

public HtmlReaderRequest(String url, Listener<T> listener, Response.ErrorListener errorListener) {
super(Request.Method.GET, url, errorListener);

this.listener = listener;
}

@Override
protected void deliverResponse(T response) {
listener.onResponse(response);
}

@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
try {
Document document = Jsoup.parse(new ByteArrayInputStream(response.data),
HttpHeaderParser.parseCharset(response.headers), getUrl());

T result = readDocument(document);

return Response.success(result, HttpHeaderParser.parseCacheHeaders(response));
} catch (IOException e) {
return Response.error(new VolleyError(e));
}
}

public abstract T readDocument(Document document) throws IOException;
}
@@ -0,0 +1,138 @@
/*
* 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.presenter;

import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.model.Archive;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.site.SiteActions;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import javax.inject.Inject;

import static android.text.TextUtils.isEmpty;

public class ArchivePresenter implements SiteActions.ArchiveListener {
private DatabaseManager databaseManager;

private Callback callback;
private Board board;

private boolean inRequest = false;

private String filter;
private List<Archive.ArchiveItem> items = new ArrayList<>();
private List<Archive.ArchiveItem> filteredItems = new ArrayList<>();

@Inject
public ArchivePresenter(DatabaseManager databaseManager) {
this.databaseManager = databaseManager;
}

public void create(Callback callback, Board board) {
this.callback = callback;
this.board = board;

loadArchive();
}

public void onRefresh() {
if (!inRequest) {
loadArchive();
}
}

private void loadArchive() {
inRequest = true;
callback.showError(false);
board.site.actions().archive(board, this);
}

public void onSearchEntered(String query) {
filterArchive(query);
}

public void onSearchVisibility(boolean visible) {
if (!visible) {
filterArchive(null);
}
}

public void onItemClicked(Archive.ArchiveItem item) {
Loadable loadable = databaseManager.getDatabaseLoadableManager()
.get(Loadable.forThread(board.site, board, item.id));

callback.openThread(loadable);
}

@Override
public void onArchive(Archive archive) {
inRequest = false;
callback.hideRefreshing();
callback.showList();
items = archive.items;
updateWithFilter();
}

@Override
public void onArchiveError() {
inRequest = false;
callback.hideRefreshing();
callback.showError(true);
}

private void filterArchive(String query) {
filter = query;
updateWithFilter();
}

private void updateWithFilter() {
filteredItems.clear();
if (isEmpty(filter)) {
filteredItems.addAll(items);
} else {
for (Archive.ArchiveItem item : items) {
if (filterApplies(item, filter)) {
filteredItems.add(item);
}
}
}

callback.setArchiveItems(filteredItems);
}

private boolean filterApplies(Archive.ArchiveItem item, String filter) {
return item.description.toLowerCase(Locale.ENGLISH).contains(filter.toLowerCase());
}

public interface Callback {
void setArchiveItems(List<Archive.ArchiveItem> items);

void hideRefreshing();

void showList();

void showError(boolean show);

void openThread(Loadable loadable);
}
}
Expand Up @@ -118,11 +118,14 @@ private Loadable getLoadableForBoard(Board board) {
private void loadBoard(Board board) {
currentBoard = board;
callback.loadBoard(getLoadableForBoard(board));
callback.showArchiveOption(board.site.boardFeature(Site.BoardFeature.ARCHIVE, board));
}

public interface Callback {
void loadBoard(Loadable loadable);

void loadSiteSetup(Site site);

void showArchiveOption(boolean show);
}
}
5 changes: 5 additions & 0 deletions Clover/app/src/main/java/org/floens/chan/core/site/Site.java
Expand Up @@ -79,6 +79,11 @@ enum BoardFeature {
*/
// TODO(multisite) use this
POSTING_SPOILER,

/**
* This board support loading the archive, a list of threads that are locked after expiring.
*/
ARCHIVE,
}

/**
Expand Down
Expand Up @@ -17,6 +17,8 @@
*/
package org.floens.chan.core.site;

import org.floens.chan.core.model.Archive;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.site.http.DeleteRequest;
import org.floens.chan.core.site.http.DeleteResponse;
import org.floens.chan.core.site.http.HttpCall;
Expand Down Expand Up @@ -63,6 +65,14 @@ interface DeleteListener {
void onDeleteError(HttpCall httpCall);
}

void archive(Board board, ArchiveListener archiveListener);

interface ArchiveListener {
void onArchive(Archive archive);

void onArchiveError();
}

/* 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.
Expand Down
Expand Up @@ -43,6 +43,8 @@ public interface SiteEndpoints {

HttpUrl boards();

HttpUrl archive(Board board);

HttpUrl reply(Loadable thread);

HttpUrl delete(Post post);
Expand Down
Expand Up @@ -357,6 +357,11 @@ public HttpUrl boards() {
return null;
}

@Override
public HttpUrl archive(Board board) {
return null;
}

@Override
public HttpUrl reply(Loadable thread) {
return null;
Expand Down Expand Up @@ -522,6 +527,10 @@ public void boards(BoardsListener boardsListener) {
}
}

@Override
public void archive(Board board, ArchiveListener archiveListener) {
}

@Override
public void login(LoginRequest loginRequest, LoginListener loginListener) {
}
Expand Down

3 comments on commit 9c4f433

@Bitti09
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this api endpoint not the one you are looking for?

"A list of archived thread IDs can be found here:

http(s)://a.4cdn.org/board/archive.json"

@floens
Copy link
Member Author

@floens floens commented on 9c4f433 Feb 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Bitti09 that endpoint only contains the thread nos, and we need more information for the archive to be useful. See the related issue #173

@Bitti09
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its complicate cause:

  1. step would be: parse the archive.json for every board.
  2. step would be: fetch every thread json form the numbers in archive.json
    Example:
    https://a.4cdn.org/c/archive.json
    first thread from it must be fetched via
    https://a.4cdn.org/c/thread/3034542.json
    to get informations about it without using http scraper.

and yes they arent included in catalog.json&threads.json

Please sign in to comment.