Compare commits

...

15 Commits

Author SHA1 Message Date
akaessens ec681e45e1
Update README.md
add screenshot
2021-08-26 11:06:51 +02:00
akaessens a38bf1dcc1 Update changelog 2021-08-12 14:54:06 +00:00
akaessens 9d56911c04 prepare v0.5.0 2021-08-12 16:52:56 +02:00
akaessens 2ef6a35862 Add history for search
Gets deleted with all events from settings
2021-08-11 22:09:45 +02:00
akaessens 43458af11c update android CI workflow java version
enable on all branches
2021-08-10 21:02:41 +02:00
akaessens 2a67c74f57 Update gson dependency 2021-08-10 20:56:54 +02:00
akaessens 8f8d0d07a8 Upgrade gradle to 7.1.1
Repace jcenter with maven as jcenter is deprecated
update dependencies that are more up to date on maven

Gradle 7.1.1 working even though #14 didn't change.
Apparently incopatibility is now shifted to gradle 8, whatever.
2021-08-10 17:55:56 +02:00
akaessens 7fdfd38cdc Allow input of arbitraty page name
Document receiver will check for 404 error if page is invalid.
If page is valid just scrape it as if it was the full uri.

closes #34
2021-08-10 14:49:06 +02:00
akaessens b4d37fbc3f Correctly exit if error in scraping instead of endless loop 2021-08-10 14:25:43 +02:00
akaessens 2b035b6975 Update dependencies 2021-08-10 14:05:04 +02:00
akaessens 32da94c275 More logging
Signed-off-by: akaessens <24660231+akaessens@users.noreply.github.com>
2021-08-10 14:04:28 +02:00
akaessens 866889db27 Increase page max to 100 as it has turned out to be stable
Related #33
2021-08-10 12:48:17 +02:00
akaessens 6248e79021 Replace asyncTasks execute with actual parallel async execution
Using executeOnExecutor(asyncTask.THREAD_POOL_EXECUTOR) all pages
are scraped in parallel. Related: #33

Note: with Android 11 the whole Aync Task is deprecated, therefore
needs to be replaced in the future.
2021-08-10 12:43:43 +02:00
akaessens e8893fd712 Add some logging 2021-08-10 12:33:46 +02:00
akaessens 6c00e63d1f Update changelog 2021-04-27 18:13:36 +00:00
21 changed files with 177 additions and 68 deletions

View File

@ -2,9 +2,7 @@ name: Android CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
@ -13,9 +11,9 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: set up JDK 1.8
uses: actions/setup-java@v1
- uses: actions/setup-java@v2
with:
java-version: 1.8
distribution: 'adopt'
java-version: '11'
- name: Build with Gradle
run: ./gradlew build

View File

@ -1,4 +1,11 @@
# Changelog
## v0.5.0 (15)
- parallelize multiple event scraping
- add scraping history
- allow entering page name without URL format
- updated dependencies
## v0.4.4 (14)
- Fix Android 11 intents (to open Calendar or Maps)
## v0.4.3 (13)
- Add spanish translation thanks to @sguinetti
- update dependencies

View File

@ -33,7 +33,8 @@ This Android app is written in Java and is using the Gradle build system. To com
# Screenshots
<img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png" alt="Screenshot 1" width="200"> <img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png" alt="Screenshot 2" width="200"> <img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" alt="Screenshot 3" width="200"> <img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png" alt="Screenshot 4" width="200">
<img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png" alt="Screenshot 1" width="150"> <img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png" alt="Screenshot 2" width="150"> <img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" alt="Screenshot 3" width="150"> <img src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png" alt="Screenshot 4" width="150"> <img
src="https://github.com/akaessens/NoFbEventScraper/raw/master/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png" alt="Screenshot 5" width="150">
# Donations
I develop this application in my free time. If you like it, you can donate at <a href="https://www.paypal.me/andreaskaessens">PayPal</a>.

View File

@ -7,8 +7,8 @@ android {
applicationId "com.akdev.nofbeventscraper"
minSdkVersion 23
targetSdkVersion 30
versionCode 14
versionName "0.4.4"
versionCode 15
versionName "0.5.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -25,31 +25,31 @@ android {
dependencies {
// androidx
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.navigation:navigation-fragment:2.3.5'
implementation 'androidx.navigation:navigation-ui:2.3.5'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.preference:preference:1.1.1'
implementation "androidx.webkit:webkit:1.4.0"
// JSON save/restore shared preference
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.google.code.gson:gson:2.8.6'
// Theme
implementation 'com.google.android.material:material:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
// Scraping
implementation 'org.jsoup:jsoup:1.13.1'
implementation 'org.jsoup:jsoup:1.14.1'
// Image loading and transforming
implementation 'com.squareup.picasso:picasso:2.71828'
// animations and transformations
implementation 'jp.wasabeef:picasso-transformations:2.2.1'
implementation 'jp.wasabeef:recyclerview-animators:3.0.0'
implementation 'jp.wasabeef:picasso-transformations:2.4.0'
implementation 'jp.wasabeef:recyclerview-animators:4.0.2'
// tests
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

View File

@ -1,52 +1,58 @@
package com.akdev.nofbeventscraper;
import android.util.Log;
import org.jsoup.Connection;
import org.jsoup.HttpStatusException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DocumentReceiver {
public static org.jsoup.nodes.Document getDocument(String url) {
public static org.jsoup.nodes.Document getDocument(String url) throws HttpStatusException, IOException {
org.jsoup.nodes.Document document;
// use default android user agent
String user_agent = "Mozilla/5.0 (X11; Linux x86_64)";
Log.d("scraperLog", "DocumentReceiver: " + url);
Connection connection = Jsoup.connect(url).userAgent(user_agent).followRedirects(true);
Connection.Response response = connection.execute();
document = response.parse();
Log.d("scraperLog", "Document title: " + document.title());
try {
// use default android user agent
String user_agent = "Mozilla/5.0 (X11; Linux x86_64)";
// accept cookies needed?
Element form = document.select("form[method=post]").first();
String action = form.attr("action");
Connection connection = Jsoup.connect(url).userAgent(user_agent).followRedirects(true);
List<String> names = form.select("input").eachAttr("name");
List<String> values = form.select("input").eachAttr("value");
Connection.Response response = connection.execute();
Map<String, String> data = new HashMap<String, String>();
document = response.parse();
try {
// accept cookies needed?
Element form = document.select("form[method=post]").first();
String action = form.attr("action");
List<String> names = form.select("input").eachAttr("name");
List<String> values = form.select("input").eachAttr("value");
Map<String, String> data = new HashMap<String, String>();
for (int i = 0; i < names.size(); i++) {
data.put(names.get(i), values.get(i));
}
document = connection.url("https://mbasic.facebook.com" + action)
.cookies(response.cookies())
.method(Connection.Method.POST)
.data(data)
.post();
} catch (Exception ignore) {
for (int i = 0; i < names.size(); i++) {
data.put(names.get(i), values.get(i));
}
} catch (Exception e) {
return null;
document = connection.url("https://mbasic.facebook.com" + action)
.cookies(response.cookies())
.method(Connection.Method.POST)
.data(data)
.post();
} catch (Exception ignore) {
}
return document;
}

View File

@ -1,9 +1,11 @@
package com.akdev.nofbeventscraper;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.HttpStatusException;
import org.jsoup.nodes.Document;
import java.io.IOException;
@ -143,9 +145,10 @@ public class FbEventScraper extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
Document document = DocumentReceiver.getDocument(url);
Log.d("scraperLog", "doInBackground: "+url);
try {
Document document = DocumentReceiver.getDocument(url);
if (document == null) {
throw new IOException();
}
@ -188,7 +191,10 @@ public class FbEventScraper extends AsyncTask<Void, Void, Void> {
this.event = new FbEvent(url, name, start_date, end_date, description, location, image_url);
} catch (IOException e) {
} catch (HttpStatusException e) {
this.error = R.string.error_url;
}
catch (IOException e) {
e.printStackTrace();
this.error = R.string.error_connection;
} catch (Exception e) {

View File

@ -5,6 +5,7 @@ import android.os.AsyncTask;
import androidx.preference.PreferenceManager;
import org.jsoup.HttpStatusException;
import org.jsoup.nodes.Document;
import java.io.IOException;
@ -95,13 +96,17 @@ public class FbPageScraper extends AsyncTask<Void, Void, Void> {
url = null;
event_links = event_links.subList(0, max);
}
} catch (HttpStatusException e) {
this.error = R.string.error_url;
return null;
} catch (IOException e) {
e.printStackTrace();
this.error = R.string.error_connection;
return null;
} catch (Exception e) {
e.printStackTrace();
this.error = R.string.error_unknown;
return null;
}
} while (url != null);

View File

@ -2,6 +2,7 @@ package com.akdev.nofbeventscraper;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.util.Log;
import androidx.preference.PreferenceManager;
@ -152,8 +153,10 @@ public class FbScraper {
*/
void scrapeEvent(String event_url) {
FbEventScraper scraper = new FbEventScraper(this, event_url);
Log.d("scraperLog", "scrapeEvent: "+event_url);
tasks.add(scraper);
scraper.execute();
scraper.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
/**
@ -165,6 +168,7 @@ public class FbScraper {
void scrapeEventResultCallback(FbEvent event, int error) {
if (event != null) {
Log.d("scraperLog", "scrapeEventResultCallback: "+event.url);
main.get().addEvent(event);
main.get().input_helper(main.get().getString(R.string.done), false);
} else if (url_type == url_type_enum.EVENT) {
@ -180,8 +184,10 @@ public class FbScraper {
void scrapePage(String page_url) {
FbPageScraper scraper = new FbPageScraper(this, page_url);
Log.d("scraperLog", "scrapePage: "+page_url);
tasks.add(scraper);
scraper.execute();
scraper.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
/**
@ -193,10 +199,11 @@ public class FbScraper {
protected void scrapePageResultCallback(List<String> event_urls, int error) {
if (event_urls.size() > 0) {
Log.d("scraperLog", "scrapePageResultCallback: "+event_urls.toString());
for (String event_url : event_urls) {
try {
String url = getEventUrl(event_url);
Log.d("scraperLog", "scrapePageResultCallback: "+url);
scrapeEvent(url);
} catch (URISyntaxException | MalformedURLException e) {
// ignore this event
@ -210,11 +217,15 @@ public class FbScraper {
protected void redirectUrl (String url) {
FbRedirectionResolver resolver = new FbRedirectionResolver(this, url);
resolver.execute();
Log.d("scraperLog", "redirectUrl: "+url);
resolver.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
protected void redirectionResultCallback(String url) {
this.input_url = url;
Log.d("scraperLog", "redirectUrlCb: "+url);
// now try again with expanded url
this.run();
}
@ -253,6 +264,17 @@ public class FbScraper {
url_type = url_type_enum.PAGE;
scrapePage(page_url);
return;
} catch (URISyntaxException | MalformedURLException e) {
url_type = url_type_enum.INVALID;
}
// check if only page name without prefix
try {
String page_url = getPageUrl("https://mbasic.facebook.com/"+input_url);
url_type = url_type_enum.PAGE;
scrapePage(page_url);
} catch (URISyntaxException | MalformedURLException e) {
url_type = url_type_enum.INVALID;
main.get().input_helper(main.get().getString(R.string.error_url), true);

View File

@ -12,6 +12,8 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.menu.MenuBuilder;
@ -23,13 +25,13 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.ref.WeakReference;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@ -40,7 +42,7 @@ import static com.akdev.nofbeventscraper.FbEvent.createEventList;
public class MainActivity extends AppCompatActivity {
protected ExtendedFloatingActionButton paste_button;
protected TextInputEditText edit_text_uri_input;
protected AutoCompleteTextView edit_text_uri_input;
protected TextInputLayout layout_uri_input;
@ -49,6 +51,28 @@ public class MainActivity extends AppCompatActivity {
EventAdapter adapter;
LinearLayoutManager linear_layout_manager;
List<String> history;
ArrayAdapter<String> history_adapter;
private List<String> getHistory() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
Gson gson = new Gson();
String json = prefs.getString("history", "");
Type history_type = new TypeToken<List<String>>() {
}.getType();
List<String> list = gson.fromJson(json, history_type);
if (list == null) {
list = new ArrayList<>();
}
return list;
}
private List<FbEvent> getSavedEvents() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
@ -81,6 +105,12 @@ public class MainActivity extends AppCompatActivity {
adapter.notifyDataSetChanged();
}
if (getHistory().isEmpty()) {
history.clear();
history_adapter.clear();
adapter.notifyDataSetChanged();
}
/*
* Intent from IntentReceiver - read only once
*/
@ -106,6 +136,9 @@ public class MainActivity extends AppCompatActivity {
Gson gson = new Gson();
String json = gson.toJson(events);
prefs_edit.putString("events", json);
json = gson.toJson(history);
prefs_edit.putString("history", json);
prefs_edit.apply();
}
@ -132,6 +165,10 @@ public class MainActivity extends AppCompatActivity {
linear_layout_manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
recycler_view.setLayoutManager(linear_layout_manager);
// restore history
this.history = getHistory();
history_adapter = new ArrayAdapter<>(this, android.R.layout.simple_expandable_list_item_1, history);
recycler_view.setItemAnimator(new FadeInAnimator());
@ -197,6 +234,14 @@ public class MainActivity extends AppCompatActivity {
};
layout_uri_input.setErrorIconOnClickListener(listener);
layout_uri_input.setEndIconOnClickListener(listener);
edit_text_uri_input.setAdapter(history_adapter);
layout_uri_input.setStartIconOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
edit_text_uri_input.showDropDown();
}
});
/*
@ -235,6 +280,9 @@ public class MainActivity extends AppCompatActivity {
scraper = new FbScraper(new WeakReference<>(this), url);
scraper.run();
history_adapter.insert(url, 0);
history.add(0, url);
}
/**

View File

@ -43,16 +43,20 @@ public class SettingsActivity extends AppCompatActivity {
final SharedPreferences prefs = preference.getSharedPreferences();
final String undo = prefs.getString("events", "");
final String events = prefs.getString("events", "");
prefs.edit().remove("events").apply();
final String history = prefs.getString("history", "");
prefs.edit().remove("history").apply();
Snackbar.make(getActivity().findViewById(android.R.id.content),
getString(R.string.preferences_event_snackbar), Snackbar.LENGTH_SHORT)
.setAction(R.string.undo, new View.OnClickListener() {
@Override
public void onClick(View v) {
prefs.edit().putString("events", undo).apply();
prefs.edit().putString("events", events).apply();
prefs.edit().putString("history", history).apply();
}
}).show();

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.25,2.52 0.77,-1.28 -3.52,-2.09L13.5,8z"
android:fillColor="#000000"/>
</vector>

View File

@ -17,7 +17,7 @@
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layout_uri_input"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
@ -26,9 +26,10 @@
app:endIconMode="clear_text"
app:errorIconDrawable="@drawable/ic_backspace_black"
app:helperText="@string/helper_add_link"
app:helperTextEnabled="true">
app:helperTextEnabled="true"
app:startIconDrawable="@drawable/ic_history_black">
<com.google.android.material.textfield.TextInputEditText
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/edit_text_uri_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -20,7 +20,7 @@
android:defaultValue="5"
app:showSeekBarValue="true"
app:min="1"
android:max="30"
android:max="100"
android:summary="@string/preferences_page_event_max_summary"
android:key="page_event_max"
android:title="@string/preferences_page_event_max" />

View File

@ -4,8 +4,7 @@ buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.3'
@ -18,8 +17,7 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}

View File

@ -0,0 +1,4 @@
- parallelize multiple event scraping
- add scraping history
- allow entering page name without URL format
- updated dependencies

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 KiB

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

View File

@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
distributionSha256Sum=23e7d37e9bb4f8dabb8a3ea7fdee9dd0428b9b1a71d298aefd65b11dccea220f
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
distributionSha256Sum=bf8b869948901d422e9bb7d1fa61da6a6e19411baa7ad6ee929073df85d6365d