Compare commits
39 Commits
Author | SHA1 | Date |
---|---|---|
akaessens | ec681e45e1 | |
akaessens | a38bf1dcc1 | |
akaessens | 9d56911c04 | |
akaessens | 2ef6a35862 | |
akaessens | 43458af11c | |
akaessens | 2a67c74f57 | |
akaessens | 8f8d0d07a8 | |
akaessens | 7fdfd38cdc | |
akaessens | b4d37fbc3f | |
akaessens | 2b035b6975 | |
akaessens | 32da94c275 | |
akaessens | 866889db27 | |
akaessens | 6248e79021 | |
akaessens | e8893fd712 | |
akaessens | 6c00e63d1f | |
akaessens | b37fa14d96 | |
akaessens | c6e25fdcfb | |
akaessens | 1ec5c5ea41 | |
akaessens | e051e66188 | |
akaessens | 42882b7aa8 | |
akaessens | 62742fd1aa | |
akaessens | 3299001d9b | |
Diego | ad9bf21b68 | |
akaessens | 0a20102678 | |
akaessens | 85f420e15d | |
akaessens | c119a163c0 | |
akaessens | 08c1040679 | |
akaessens | 2efaafa38b | |
akaessens | 262c1c4377 | |
akaessens | 26021d540c | |
akaessens | 37627a43d0 | |
akaessens | b94fa6be60 | |
akaessens | 23f431f535 | |
akaessens | 404b8e1086 | |
akaessens | f067076752 | |
akaessens | 748cf3c074 | |
akaessens | 2479cd9c72 | |
akaessens | 9e81e3d74a | |
akaessens | ce790763fd |
|
@ -2,9 +2,7 @@ name: Android CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
@ -13,9 +11,9 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: set up JDK 1.8
|
- uses: actions/setup-java@v2
|
||||||
uses: actions/setup-java@v1
|
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
distribution: 'adopt'
|
||||||
|
java-version: '11'
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew build
|
run: ./gradlew build
|
||||||
|
|
24
CHANGELOG.md
|
@ -1,4 +1,28 @@
|
||||||
# Changelog
|
# 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
|
||||||
|
## v0.4.2 (12)
|
||||||
|
- Fix scraping not working when cookies need to be accepted
|
||||||
|
- Android 11 ready
|
||||||
|
## v0.4.1 (11)
|
||||||
|
- Fix events not displaying correctly after activity resume
|
||||||
|
- add share action on each event
|
||||||
|
- add URL shortener redirection for fb.me
|
||||||
|
## v0.4.0 (10)
|
||||||
|
- Support pages with upcoming events *beta*
|
||||||
|
- Display events in a scrollable card-based view
|
||||||
|
- Improve intent handling
|
||||||
|
- Add history for scraped events
|
||||||
|
- Tap image preview to open fullscreen
|
||||||
|
- Scrape name and image even if no event data found
|
||||||
## v0.3.3 (9)
|
## v0.3.3 (9)
|
||||||
- Update about section with download and changelog information.
|
- Update about section with download and changelog information.
|
||||||
- Improve high-res preview scraping.
|
- Improve high-res preview scraping.
|
||||||
|
|
|
@ -20,6 +20,10 @@ This source contains the information which is used to create a calendar entry.
|
||||||
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="75">
|
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="75">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
[CHANGELOG](CHANGELOG.md)
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
|
||||||
This Android app is written in Java and is using the Gradle build system. To compile it, i recommend using Android Studio.
|
This Android app is written in Java and is using the Gradle build system. To compile it, i recommend using Android Studio.
|
||||||
|
@ -29,9 +33,10 @@ This Android app is written in Java and is using the Gradle build system. To com
|
||||||
|
|
||||||
# Screenshots
|
# 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
|
# 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>.
|
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>.
|
||||||
|
|
||||||
<a title="PayPal" href="https://www.paypal.me/andreaskaessens"><img src="https://raw.githubusercontent.com/stefan-niedermann/paypal-donate-button/master/paypal-donate-button.png" height="75" /></a>
|
<a title="PayPal" href="https://www.paypal.me/andreaskaessens"><img src="https://raw.githubusercontent.com/stefan-niedermann/paypal-donate-button/master/paypal-donate-button.png" height="75" /></a>
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 30
|
||||||
buildToolsVersion "29.0.3"
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.akdev.nofbeventscraper"
|
applicationId "com.akdev.nofbeventscraper"
|
||||||
minSdkVersion 23
|
minSdkVersion 23
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
versionCode 10
|
versionCode 15
|
||||||
versionName "0.4.0"
|
versionName "0.5.0"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
@ -26,31 +25,31 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
// androidx
|
// androidx
|
||||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
|
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.navigation:navigation-fragment:2.3.0'
|
implementation 'androidx.navigation:navigation-fragment:2.3.5'
|
||||||
implementation 'androidx.navigation:navigation-ui:2.3.0'
|
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.preference:preference:1.1.1'
|
||||||
implementation "androidx.webkit:webkit:1.3.0"
|
implementation "androidx.webkit:webkit:1.4.0"
|
||||||
|
|
||||||
// JSON save/restore shared preference
|
// JSON save/restore shared preference
|
||||||
implementation 'com.google.code.gson:gson:2.8.5'
|
implementation 'com.google.code.gson:gson:2.8.6'
|
||||||
|
|
||||||
// Theme
|
// Theme
|
||||||
implementation 'com.google.android.material:material:1.2.1'
|
implementation 'com.google.android.material:material:1.4.0'
|
||||||
|
|
||||||
// Scraping
|
// Scraping
|
||||||
implementation 'org.jsoup:jsoup:1.13.1'
|
implementation 'org.jsoup:jsoup:1.14.1'
|
||||||
|
|
||||||
// Image loading and transforming
|
// Image loading and transforming
|
||||||
implementation 'com.squareup.picasso:picasso:2.71828'
|
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||||
|
|
||||||
// animations and transformations
|
// animations and transformations
|
||||||
implementation 'jp.wasabeef:picasso-transformations:2.2.1'
|
implementation 'jp.wasabeef:picasso-transformations:2.4.0'
|
||||||
implementation 'jp.wasabeef:recyclerview-animators:3.0.0'
|
implementation 'jp.wasabeef:recyclerview-animators:4.0.2'
|
||||||
// tests
|
// tests
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,12 @@
|
||||||
<data
|
<data
|
||||||
android:host="facebook.com"
|
android:host="facebook.com"
|
||||||
android:scheme="https" />
|
android:scheme="https" />
|
||||||
|
<data
|
||||||
|
android:host="*.fb.me"
|
||||||
|
android:scheme="https" />
|
||||||
|
<data
|
||||||
|
android:host="fb.me"
|
||||||
|
android:scheme="https" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<!-- Accepts share intents -->
|
<!-- Accepts share intents -->
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
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) 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 {
|
||||||
|
// 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) {
|
||||||
|
}
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package com.akdev.nofbeventscraper;
|
||||||
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
@ -14,6 +15,7 @@ import android.view.WindowManager;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
@ -60,6 +62,10 @@ public class EventAdapter extends
|
||||||
// Set item views based on your views and data model
|
// Set item views based on your views and data model
|
||||||
holder.text_view_event_name.setText(event.name);
|
holder.text_view_event_name.setText(event.name);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* initialize all text views with event information
|
||||||
|
* hide fields and image views if no information is available
|
||||||
|
*/
|
||||||
if (!event.location.equals("")) {
|
if (!event.location.equals("")) {
|
||||||
holder.text_view_event_location.setText(event.location);
|
holder.text_view_event_location.setText(event.location);
|
||||||
} else {
|
} else {
|
||||||
|
@ -86,7 +92,6 @@ public class EventAdapter extends
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (!event.description.equals("")) {
|
if (!event.description.equals("")) {
|
||||||
holder.text_view_event_description.setText(event.description);
|
holder.text_view_event_description.setText(event.description);
|
||||||
} else {
|
} else {
|
||||||
|
@ -113,20 +118,25 @@ public class EventAdapter extends
|
||||||
|
|
||||||
Uri intent_uri = Uri.parse(map_search);
|
Uri intent_uri = Uri.parse(map_search);
|
||||||
Intent map_intent = new Intent(Intent.ACTION_VIEW, intent_uri);
|
Intent map_intent = new Intent(Intent.ACTION_VIEW, intent_uri);
|
||||||
if (map_intent.resolveActivity(view.getContext().getPackageManager()) != null) {
|
|
||||||
|
try {
|
||||||
view.getContext().startActivity(map_intent);
|
view.getContext().startActivity(map_intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Toast toast=Toast.makeText(view.getContext(),"no App installed", Toast.LENGTH_SHORT);
|
||||||
|
toast.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
holder.image_view_event_location.setOnClickListener(location_click_listener);
|
holder.image_view_event_location.setOnClickListener(location_click_listener);
|
||||||
holder.text_view_event_location.setOnClickListener(location_click_listener);
|
holder.text_view_event_location.setOnClickListener(location_click_listener);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add to calendar button: launch calendar application with current event
|
* Add to calendar button: launch calendar application with current event
|
||||||
*/
|
*/
|
||||||
holder.button_add_to_calendar.setOnClickListener(new View.OnClickListener() {
|
holder.button_add_to_calendar.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
|
// calendar event intent expects epoch time format
|
||||||
Long start_epoch = dateTimeToEpoch(event.start_date);
|
Long start_epoch = dateTimeToEpoch(event.start_date);
|
||||||
Long end_epoch = dateTimeToEpoch(event.end_date);
|
Long end_epoch = dateTimeToEpoch(event.end_date);
|
||||||
|
|
||||||
|
@ -141,8 +151,11 @@ public class EventAdapter extends
|
||||||
String desc = event.url + "\n\n" + event.description;
|
String desc = event.url + "\n\n" + event.description;
|
||||||
intent.putExtra(CalendarContract.Events.DESCRIPTION, desc);
|
intent.putExtra(CalendarContract.Events.DESCRIPTION, desc);
|
||||||
|
|
||||||
if (intent.resolveActivity(view.getContext().getPackageManager()) != null) {
|
try {
|
||||||
view.getContext().startActivity(intent);
|
view.getContext().startActivity(intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Toast toast=Toast.makeText(view.getContext(),"no App installed", Toast.LENGTH_SHORT);
|
||||||
|
toast.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -163,7 +176,7 @@ public class EventAdapter extends
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Image dialog
|
* Image preview click creates fullscreen dialog
|
||||||
*/
|
*/
|
||||||
|
|
||||||
View.OnClickListener listener = new View.OnClickListener() {
|
View.OnClickListener listener = new View.OnClickListener() {
|
||||||
|
@ -198,11 +211,25 @@ public class EventAdapter extends
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
holder.image_view_event_image.setOnClickListener(listener);
|
holder.image_view_event_image.setOnClickListener(listener);
|
||||||
|
|
||||||
|
holder.image_view_share.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
Intent share_intent = new Intent(android.content.Intent.ACTION_SEND);
|
||||||
|
share_intent.setType("text/plain");
|
||||||
|
share_intent.putExtra(Intent.EXTRA_TEXT, event.url);
|
||||||
|
|
||||||
|
try {
|
||||||
|
view.getContext().startActivity(share_intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Toast toast=Toast.makeText(view.getContext(),"no App installed", Toast.LENGTH_SHORT);
|
||||||
|
toast.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -212,6 +239,9 @@ public class EventAdapter extends
|
||||||
return events.size();
|
return events.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* access item view elements via holder class
|
||||||
|
*/
|
||||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
protected TextView text_view_event_name;
|
protected TextView text_view_event_name;
|
||||||
|
@ -222,6 +252,7 @@ public class EventAdapter extends
|
||||||
protected ImageView image_view_event_image;
|
protected ImageView image_view_event_image;
|
||||||
protected ImageView image_view_event_location;
|
protected ImageView image_view_event_location;
|
||||||
protected ImageView image_view_event_time;
|
protected ImageView image_view_event_time;
|
||||||
|
protected ImageView image_view_share;
|
||||||
protected Button button_add_to_calendar;
|
protected Button button_add_to_calendar;
|
||||||
|
|
||||||
protected boolean description_collapsed = true;
|
protected boolean description_collapsed = true;
|
||||||
|
@ -237,6 +268,7 @@ public class EventAdapter extends
|
||||||
image_view_event_image = item_view.findViewById(R.id.image_view_event_image);
|
image_view_event_image = item_view.findViewById(R.id.image_view_event_image);
|
||||||
image_view_event_location = item_view.findViewById(R.id.image_view_event_location);
|
image_view_event_location = item_view.findViewById(R.id.image_view_event_location);
|
||||||
image_view_event_time = item_view.findViewById(R.id.image_view_event_time);
|
image_view_event_time = item_view.findViewById(R.id.image_view_event_time);
|
||||||
|
image_view_share = item_view.findViewById(R.id.image_view_share);
|
||||||
button_add_to_calendar = item_view.findViewById(R.id.button_add_to_calendar);
|
button_add_to_calendar = item_view.findViewById(R.id.button_add_to_calendar);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package com.akdev.nofbeventscraper;
|
package com.akdev.nofbeventscraper;
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.HttpStatusException;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -144,11 +145,10 @@ public class FbEventScraper extends AsyncTask<Void, Void, Void> {
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... voids) {
|
protected Void doInBackground(Void... voids) {
|
||||||
|
|
||||||
try {
|
Log.d("scraperLog", "doInBackground: "+url);
|
||||||
// use default android user agent
|
|
||||||
String user_agent = "Mozilla/5.0 (X11; Linux x86_64)";
|
|
||||||
Document document = Jsoup.connect(url).userAgent(user_agent).get();
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
Document document = DocumentReceiver.getDocument(url);
|
||||||
if (document == null) {
|
if (document == null) {
|
||||||
throw new IOException();
|
throw new IOException();
|
||||||
}
|
}
|
||||||
|
@ -163,6 +163,7 @@ public class FbEventScraper extends AsyncTask<Void, Void, Void> {
|
||||||
|
|
||||||
JSONObject reader = new JSONObject(json);
|
JSONObject reader = new JSONObject(json);
|
||||||
|
|
||||||
|
// get all fields from json event information
|
||||||
name = readFromJson(reader, "name");
|
name = readFromJson(reader, "name");
|
||||||
start_date = parseToDate(readFromJson(reader, "startDate"));
|
start_date = parseToDate(readFromJson(reader, "startDate"));
|
||||||
end_date = parseToDate(readFromJson(reader, "endDate"));
|
end_date = parseToDate(readFromJson(reader, "endDate"));
|
||||||
|
@ -170,6 +171,7 @@ public class FbEventScraper extends AsyncTask<Void, Void, Void> {
|
||||||
location = fixLocation(readFromJson(reader, "location"));
|
location = fixLocation(readFromJson(reader, "location"));
|
||||||
image_url = readFromJson(reader, "image");
|
image_url = readFromJson(reader, "image");
|
||||||
|
|
||||||
|
// try to find a high-res image
|
||||||
try {
|
try {
|
||||||
image_url = document.select("div[id=event_header_primary]")
|
image_url = document.select("div[id=event_header_primary]")
|
||||||
.select("img").first().attr("src");
|
.select("img").first().attr("src");
|
||||||
|
@ -177,6 +179,7 @@ public class FbEventScraper extends AsyncTask<Void, Void, Void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (JSONException | NullPointerException e) {
|
} catch (JSONException | NullPointerException e) {
|
||||||
|
// json event information mot found. get at least title and image
|
||||||
name = document.title();
|
name = document.title();
|
||||||
description = scraper.main.get().getString(R.string.error_scraping);
|
description = scraper.main.get().getString(R.string.error_scraping);
|
||||||
try {
|
try {
|
||||||
|
@ -186,12 +189,12 @@ public class FbEventScraper extends AsyncTask<Void, Void, Void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.event = new FbEvent(url, name, start_date, end_date, description, location, image_url);
|
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();
|
e.printStackTrace();
|
||||||
this.error = R.string.error_connection;
|
this.error = R.string.error_connection;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -212,6 +215,7 @@ public class FbEventScraper extends AsyncTask<Void, Void, Void> {
|
||||||
*
|
*
|
||||||
* @param aVoid
|
* @param aVoid
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
protected void onPostExecute(Void aVoid) {
|
protected void onPostExecute(Void aVoid) {
|
||||||
super.onPostExecute(aVoid);
|
super.onPostExecute(aVoid);
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import android.os.AsyncTask;
|
||||||
|
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.HttpStatusException;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -22,7 +22,7 @@ public class FbPageScraper extends AsyncTask<Void, Void, Void> {
|
||||||
private FbScraper scraper;
|
private FbScraper scraper;
|
||||||
private int error;
|
private int error;
|
||||||
private String url;
|
private String url;
|
||||||
private List<String> event_links = new ArrayList<String>();
|
private List<String> event_links = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor with reference to scraper to return results.
|
* Constructor with reference to scraper to return results.
|
||||||
|
@ -51,30 +51,36 @@ public class FbPageScraper extends AsyncTask<Void, Void, Void> {
|
||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
// use default android user agent
|
// use default android user agent
|
||||||
String user_agent = "Mozilla/5.0 (X11; Linux x86_64)";
|
|
||||||
Document document = Jsoup.connect(url).userAgent(user_agent).get();
|
Document document = DocumentReceiver.getDocument(url);
|
||||||
|
|
||||||
if (document == null) {
|
if (document == null) {
|
||||||
throw new IOException();
|
throw new IOException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get all event id's from current url and add to the list
|
||||||
|
*/
|
||||||
String regex = "(/events/[0-9]*)(/\\?event_time_id=[0-9]*)?";
|
String regex = "(/events/[0-9]*)(/\\?event_time_id=[0-9]*)?";
|
||||||
|
|
||||||
List<String> event_links_href = document
|
List<String> event_links_href = document
|
||||||
.getElementsByAttributeValueMatching("href", Pattern.compile(regex))
|
.getElementsByAttributeValueMatching("href", Pattern.compile(regex))
|
||||||
.eachAttr("href");
|
.eachAttr("href");
|
||||||
|
|
||||||
for (String link : event_links_href) {
|
for (String event_id : event_links_href) {
|
||||||
this.event_links.add("https://www.facebook.com" + link);
|
this.event_links.add("https://mbasic.facebook.com" + event_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* check if more events should be scraped
|
||||||
|
*/
|
||||||
SharedPreferences shared_prefs = PreferenceManager
|
SharedPreferences shared_prefs = PreferenceManager
|
||||||
.getDefaultSharedPreferences(scraper.main.get());
|
.getDefaultSharedPreferences(scraper.main.get());
|
||||||
|
|
||||||
int max = shared_prefs.getInt("page_event_max", 5);
|
int max = shared_prefs.getInt("page_event_max", 5);
|
||||||
|
|
||||||
if (event_links.size() < max) {
|
if (event_links.size() < max) {
|
||||||
|
// find next page
|
||||||
try {
|
try {
|
||||||
String next_url = document
|
String next_url = document
|
||||||
.getElementsByAttributeValueMatching("href", "has_more=1")
|
.getElementsByAttributeValueMatching("href", "has_more=1")
|
||||||
|
@ -83,7 +89,6 @@ public class FbPageScraper extends AsyncTask<Void, Void, Void> {
|
||||||
this.url = "https://mbasic.facebook.com" + next_url;
|
this.url = "https://mbasic.facebook.com" + next_url;
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
url = null;
|
url = null;
|
||||||
event_links = event_links.subList(0, max);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -91,16 +96,24 @@ public class FbPageScraper extends AsyncTask<Void, Void, Void> {
|
||||||
url = null;
|
url = null;
|
||||||
event_links = event_links.subList(0, max);
|
event_links = event_links.subList(0, max);
|
||||||
}
|
}
|
||||||
|
} catch (HttpStatusException e) {
|
||||||
|
this.error = R.string.error_url;
|
||||||
|
return null;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
this.error = R.string.error_connection;
|
this.error = R.string.error_connection;
|
||||||
|
return null;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
this.error = R.string.error_unknown;
|
this.error = R.string.error_unknown;
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
} while (url != null);
|
} while (url != null);
|
||||||
|
|
||||||
|
if (this.event_links.size() == 0) {
|
||||||
|
this.error = R.string.error_no_events;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +127,7 @@ public class FbPageScraper extends AsyncTask<Void, Void, Void> {
|
||||||
*
|
*
|
||||||
* @param aVoid
|
* @param aVoid
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
protected void onPostExecute(Void aVoid) {
|
protected void onPostExecute(Void aVoid) {
|
||||||
super.onPostExecute(aVoid);
|
super.onPostExecute(aVoid);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.akdev.nofbeventscraper;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public class FbRedirectionResolver extends AsyncTask<Void, Void, Void> {
|
||||||
|
|
||||||
|
private String input_url;
|
||||||
|
private FbScraper scraper;
|
||||||
|
private String redirected_url;
|
||||||
|
|
||||||
|
public FbRedirectionResolver (FbScraper scraper, String input_url) {
|
||||||
|
this.input_url = input_url;
|
||||||
|
this.scraper = scraper;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Void doInBackground(Void... voids) {
|
||||||
|
try {
|
||||||
|
HttpURLConnection con = (HttpURLConnection) new URL(input_url).openConnection();
|
||||||
|
|
||||||
|
con.setInstanceFollowRedirects(false);
|
||||||
|
|
||||||
|
con.connect();
|
||||||
|
|
||||||
|
redirected_url = con.getHeaderField("Location");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void aVoid) {
|
||||||
|
super.onPostExecute(aVoid);
|
||||||
|
|
||||||
|
scraper.redirectionResultCallback(redirected_url);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,27 +2,27 @@ package com.akdev.nofbeventscraper;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import static com.akdev.nofbeventscraper.FbEvent.createEventList;
|
|
||||||
|
|
||||||
public class FbScraper {
|
public class FbScraper {
|
||||||
|
|
||||||
protected List<FbEvent> events;
|
|
||||||
protected List<AsyncTask> tasks;
|
protected List<AsyncTask> tasks;
|
||||||
|
protected WeakReference<MainActivity> main; // no context leak with WeakReference
|
||||||
url_type_enum url_type = url_type_enum.EVENT;
|
url_type_enum url_type = url_type_enum.EVENT;
|
||||||
private String input_url;
|
private String input_url;
|
||||||
protected WeakReference<MainActivity> main; // no context leak with WeakReference
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor with WeakReference to the main activity, to add events.
|
* Constructor with WeakReference to the main activity, to add events.
|
||||||
|
@ -33,10 +33,40 @@ public class FbScraper {
|
||||||
FbScraper(WeakReference<MainActivity> main, String input_url) {
|
FbScraper(WeakReference<MainActivity> main, String input_url) {
|
||||||
this.main = main;
|
this.main = main;
|
||||||
this.input_url = input_url;
|
this.input_url = input_url;
|
||||||
this.events = createEventList();
|
|
||||||
this.tasks = new ArrayList<>();
|
this.tasks = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String getShortened(String url) throws IOException, URISyntaxException {
|
||||||
|
// check for url format
|
||||||
|
new URL(url).toURI();
|
||||||
|
|
||||||
|
String regex = "(fb.me/)(e/)?([^/?]*)|(facebook.com/event_invite/[a-zA-Z0-9]*)";
|
||||||
|
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(url);
|
||||||
|
|
||||||
|
if (matcher.find()) {
|
||||||
|
//only mbasic does have event ids displayed in HTML
|
||||||
|
String url_prefix = "https://mbasic.";
|
||||||
|
|
||||||
|
// create URL
|
||||||
|
return url_prefix + matcher.group();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new URISyntaxException(url, "Does not contain page.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if valid URL,
|
||||||
|
* strips the facebook page id from the input link and create an URL that can be scraped from.
|
||||||
|
*
|
||||||
|
* @param url input URL
|
||||||
|
* @return new mbasic url that can be scraped for event id's
|
||||||
|
* @throws URISyntaxException if page not found
|
||||||
|
* @throws MalformedURLException
|
||||||
|
*/
|
||||||
protected String getPageUrl(String url) throws URISyntaxException, MalformedURLException {
|
protected String getPageUrl(String url) throws URISyntaxException, MalformedURLException {
|
||||||
|
|
||||||
// check for url format
|
// check for url format
|
||||||
|
@ -48,10 +78,11 @@ public class FbScraper {
|
||||||
Matcher matcher = pattern.matcher(url);
|
Matcher matcher = pattern.matcher(url);
|
||||||
|
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
|
//only mbasic does have event ids displayed in HTML
|
||||||
String url_prefix = "https://mbasic.facebook.com/";
|
String url_prefix = "https://mbasic.facebook.com/";
|
||||||
String url_suffix = "?v=events";
|
String url_suffix = "?v=events";
|
||||||
|
|
||||||
|
// create URL
|
||||||
return url_prefix + matcher.group(3) + url_suffix;
|
return url_prefix + matcher.group(3) + url_suffix;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -60,7 +91,7 @@ public class FbScraper {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strips the facebook event link of the input url.
|
* Strips the facebook event link from the input event url.
|
||||||
*
|
*
|
||||||
* @param url input url
|
* @param url input url
|
||||||
* @return facebook event url String if one was found
|
* @return facebook event url String if one was found
|
||||||
|
@ -98,15 +129,46 @@ public class FbScraper {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void scrapeEvent(String event_url) {
|
/**
|
||||||
FbEventScraper scraper = new FbEventScraper(this, event_url);
|
* cancel vestigial async tasks
|
||||||
tasks.add(scraper);
|
*/
|
||||||
scraper.execute();
|
void killAllTasks() {
|
||||||
|
|
||||||
|
if (!tasks.isEmpty()) {
|
||||||
|
for (AsyncTask task : tasks) {
|
||||||
|
try {
|
||||||
|
task.cancel(true);
|
||||||
|
task = null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* start an EventScraper async task and add to tasks list
|
||||||
|
*
|
||||||
|
* @param event_url
|
||||||
|
*/
|
||||||
|
void scrapeEvent(String event_url) {
|
||||||
|
FbEventScraper scraper = new FbEventScraper(this, event_url);
|
||||||
|
|
||||||
|
Log.d("scraperLog", "scrapeEvent: "+event_url);
|
||||||
|
tasks.add(scraper);
|
||||||
|
scraper.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for finished EventSCraper async task
|
||||||
|
*
|
||||||
|
* @param event Contains event information if scraping successful
|
||||||
|
* @param error resId for error message
|
||||||
|
*/
|
||||||
void scrapeEventResultCallback(FbEvent event, int error) {
|
void scrapeEventResultCallback(FbEvent event, int error) {
|
||||||
|
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
|
Log.d("scraperLog", "scrapeEventResultCallback: "+event.url);
|
||||||
main.get().addEvent(event);
|
main.get().addEvent(event);
|
||||||
main.get().input_helper(main.get().getString(R.string.done), false);
|
main.get().input_helper(main.get().getString(R.string.done), false);
|
||||||
} else if (url_type == url_type_enum.EVENT) {
|
} else if (url_type == url_type_enum.EVENT) {
|
||||||
|
@ -115,34 +177,33 @@ public class FbScraper {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* cancel vestigial async tasks
|
* start a page scraper and add to list of tasks
|
||||||
|
*
|
||||||
|
* @param page_url
|
||||||
*/
|
*/
|
||||||
void killAllTasks() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (AsyncTask task : tasks) {
|
|
||||||
task.cancel(true);
|
|
||||||
task = null;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void scrapePage(String page_url) {
|
void scrapePage(String page_url) {
|
||||||
FbPageScraper scraper = new FbPageScraper(this, page_url);
|
FbPageScraper scraper = new FbPageScraper(this, page_url);
|
||||||
|
|
||||||
|
Log.d("scraperLog", "scrapePage: "+page_url);
|
||||||
|
|
||||||
tasks.add(scraper);
|
tasks.add(scraper);
|
||||||
scraper.execute();
|
scraper.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for page scraper async task
|
||||||
|
*
|
||||||
|
* @param event_urls List of event urls scraped from the event
|
||||||
|
* @param error resId of error message if task list is empty
|
||||||
|
*/
|
||||||
protected void scrapePageResultCallback(List<String> event_urls, int error) {
|
protected void scrapePageResultCallback(List<String> event_urls, int error) {
|
||||||
|
|
||||||
if (event_urls.size() > 0) {
|
if (event_urls.size() > 0) {
|
||||||
|
Log.d("scraperLog", "scrapePageResultCallback: "+event_urls.toString());
|
||||||
for (String event_url : event_urls) {
|
for (String event_url : event_urls) {
|
||||||
try {
|
try {
|
||||||
String url = getEventUrl(event_url);
|
String url = getEventUrl(event_url);
|
||||||
|
Log.d("scraperLog", "scrapePageResultCallback: "+url);
|
||||||
scrapeEvent(url);
|
scrapeEvent(url);
|
||||||
} catch (URISyntaxException | MalformedURLException e) {
|
} catch (URISyntaxException | MalformedURLException e) {
|
||||||
// ignore this event
|
// ignore this event
|
||||||
|
@ -153,8 +214,40 @@ public class FbScraper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void redirectUrl (String url) {
|
||||||
|
FbRedirectionResolver resolver = new FbRedirectionResolver(this, url);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start scraping input url
|
||||||
|
*/
|
||||||
void run() {
|
void run() {
|
||||||
|
|
||||||
|
// check if shortened url
|
||||||
|
try {
|
||||||
|
String shortened = getShortened(input_url);
|
||||||
|
url_type = url_type_enum.SHORT;
|
||||||
|
redirectUrl(shortened);
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
} catch (IOException | URISyntaxException e) {
|
||||||
|
url_type = url_type_enum.INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if input url is an event
|
||||||
try {
|
try {
|
||||||
String event_url = getEventUrl(input_url);
|
String event_url = getEventUrl(input_url);
|
||||||
url_type = url_type_enum.EVENT;
|
url_type = url_type_enum.EVENT;
|
||||||
|
@ -165,18 +258,29 @@ public class FbScraper {
|
||||||
} catch (URISyntaxException | MalformedURLException e) {
|
} catch (URISyntaxException | MalformedURLException e) {
|
||||||
url_type = url_type_enum.INVALID;
|
url_type = url_type_enum.INVALID;
|
||||||
}
|
}
|
||||||
|
// check if input url is a page
|
||||||
try {
|
try {
|
||||||
String page_url = getPageUrl(input_url);
|
String page_url = getPageUrl(input_url);
|
||||||
url_type = url_type_enum.PAGE;
|
url_type = url_type_enum.PAGE;
|
||||||
scrapePage(page_url);
|
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) {
|
} catch (URISyntaxException | MalformedURLException e) {
|
||||||
url_type = url_type_enum.INVALID;
|
url_type = url_type_enum.INVALID;
|
||||||
main.get().input_helper(main.get().getString(R.string.error_url), true);
|
main.get().input_helper(main.get().getString(R.string.error_url), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enum for storing url type in this class
|
||||||
enum url_type_enum {EVENT, PAGE, INVALID}
|
enum url_type_enum {SHORT, EVENT, PAGE, INVALID}
|
||||||
}
|
}
|
|
@ -1,11 +1,10 @@
|
||||||
package com.akdev.nofbeventscraper;
|
package com.akdev.nofbeventscraper;
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
public class IntentReceiver extends AppCompatActivity {
|
public class IntentReceiver extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
package com.akdev.nofbeventscraper;
|
package com.akdev.nofbeventscraper;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
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.app.AppCompatActivity;
|
||||||
import androidx.appcompat.view.menu.MenuBuilder;
|
import androidx.appcompat.view.menu.MenuBuilder;
|
||||||
|
@ -22,13 +25,13 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||||
import com.google.android.material.appbar.AppBarLayout;
|
import com.google.android.material.appbar.AppBarLayout;
|
||||||
import com.google.android.material.appbar.CollapsingToolbarLayout;
|
import com.google.android.material.appbar.CollapsingToolbarLayout;
|
||||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
|
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
|
||||||
import com.google.android.material.textfield.TextInputEditText;
|
|
||||||
import com.google.android.material.textfield.TextInputLayout;
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@ -39,7 +42,7 @@ import static com.akdev.nofbeventscraper.FbEvent.createEventList;
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
protected ExtendedFloatingActionButton paste_button;
|
protected ExtendedFloatingActionButton paste_button;
|
||||||
protected TextInputEditText edit_text_uri_input;
|
protected AutoCompleteTextView edit_text_uri_input;
|
||||||
protected TextInputLayout layout_uri_input;
|
protected TextInputLayout layout_uri_input;
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,6 +51,28 @@ public class MainActivity extends AppCompatActivity {
|
||||||
EventAdapter adapter;
|
EventAdapter adapter;
|
||||||
LinearLayoutManager linear_layout_manager;
|
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() {
|
private List<FbEvent> getSavedEvents() {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
|
||||||
|
@ -65,17 +90,30 @@ public class MainActivity extends AppCompatActivity {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Callback for Restoring data
|
* On resume from other activities, e.g. settings
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
events.clear();
|
/*
|
||||||
events.addAll(getSavedEvents());
|
* Clear events after saved events deleted from settings
|
||||||
adapter.notifyDataSetChanged();
|
*/
|
||||||
|
if (getSavedEvents().isEmpty()) {
|
||||||
|
events.clear();
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getHistory().isEmpty()) {
|
||||||
|
history.clear();
|
||||||
|
history_adapter.clear();
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Intent from IntentReceiver - read only once
|
||||||
|
*/
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
String data = intent.getStringExtra("InputLink");
|
String data = intent.getStringExtra("InputLink");
|
||||||
|
|
||||||
|
@ -98,6 +136,9 @@ public class MainActivity extends AppCompatActivity {
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
String json = gson.toJson(events);
|
String json = gson.toJson(events);
|
||||||
prefs_edit.putString("events", json);
|
prefs_edit.putString("events", json);
|
||||||
|
|
||||||
|
json = gson.toJson(history);
|
||||||
|
prefs_edit.putString("history", json);
|
||||||
prefs_edit.apply();
|
prefs_edit.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +165,10 @@ public class MainActivity extends AppCompatActivity {
|
||||||
linear_layout_manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
|
linear_layout_manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
|
||||||
recycler_view.setLayoutManager(linear_layout_manager);
|
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());
|
recycler_view.setItemAnimator(new FadeInAnimator());
|
||||||
|
|
||||||
|
|
||||||
|
@ -189,6 +234,14 @@ public class MainActivity extends AppCompatActivity {
|
||||||
};
|
};
|
||||||
layout_uri_input.setErrorIconOnClickListener(listener);
|
layout_uri_input.setErrorIconOnClickListener(listener);
|
||||||
layout_uri_input.setEndIconOnClickListener(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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -199,6 +252,13 @@ public class MainActivity extends AppCompatActivity {
|
||||||
//If the key event is a key-down event on the "enter" button
|
//If the key event is a key-down event on the "enter" button
|
||||||
if ((keyevent.getAction() == KeyEvent.ACTION_DOWN) && (keycode == KeyEvent.KEYCODE_ENTER)) {
|
if ((keyevent.getAction() == KeyEvent.ACTION_DOWN) && (keycode == KeyEvent.KEYCODE_ENTER)) {
|
||||||
startScraping();
|
startScraping();
|
||||||
|
|
||||||
|
// do not focus next view, just release it
|
||||||
|
edit_text_uri_input.clearFocus();
|
||||||
|
|
||||||
|
// close soft keyboard
|
||||||
|
InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||||
|
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -209,7 +269,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* launch the FbScraper asynchronous task with the current text in the input text field.
|
* launch the FbScraper with the current text in the input text field.
|
||||||
*/
|
*/
|
||||||
public void startScraping() {
|
public void startScraping() {
|
||||||
|
|
||||||
|
@ -220,8 +280,17 @@ public class MainActivity extends AppCompatActivity {
|
||||||
scraper = new FbScraper(new WeakReference<>(this), url);
|
scraper = new FbScraper(new WeakReference<>(this), url);
|
||||||
|
|
||||||
scraper.run();
|
scraper.run();
|
||||||
|
|
||||||
|
history_adapter.insert(url, 0);
|
||||||
|
history.add(0, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* manage Helper text on uri_input
|
||||||
|
*
|
||||||
|
* @param str What should be displayed
|
||||||
|
* @param error True if should be displayed as error
|
||||||
|
*/
|
||||||
public void input_helper(String str, boolean error) {
|
public void input_helper(String str, boolean error) {
|
||||||
|
|
||||||
if (str == null) {
|
if (str == null) {
|
||||||
|
@ -255,14 +324,22 @@ public class MainActivity extends AppCompatActivity {
|
||||||
// Inflate the menu; this adds items to the action bar if it is present.
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Display icons, restricted API, maybe find other solution?
|
||||||
|
*/
|
||||||
if (menu instanceof MenuBuilder) {
|
if (menu instanceof MenuBuilder) {
|
||||||
MenuBuilder m = (MenuBuilder) menu;
|
MenuBuilder m = (MenuBuilder) menu;
|
||||||
//noinspection RestrictedApi
|
|
||||||
m.setOptionalIconsVisible(true);
|
m.setOptionalIconsVisible(true);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch menu item to new activity
|
||||||
|
*
|
||||||
|
* @param item
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,15 @@
|
||||||
package com.akdev.nofbeventscraper;
|
package com.akdev.nofbeventscraper;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
import androidx.preference.PreferenceManager;
|
|
||||||
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.google.gson.Gson;
|
|
||||||
|
|
||||||
public class SettingsActivity extends AppCompatActivity {
|
public class SettingsActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@ -37,6 +32,9 @@ public class SettingsActivity extends AppCompatActivity {
|
||||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
setPreferencesFromResource(R.xml.root_preferences, rootKey);
|
setPreferencesFromResource(R.xml.root_preferences, rootKey);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* reset events click action: delete saved events and display snackbar
|
||||||
|
*/
|
||||||
Preference button = findPreference("event_reset");
|
Preference button = findPreference("event_reset");
|
||||||
if (button != null) {
|
if (button != null) {
|
||||||
button.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
button.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
|
@ -45,18 +43,22 @@ public class SettingsActivity extends AppCompatActivity {
|
||||||
|
|
||||||
final SharedPreferences prefs = preference.getSharedPreferences();
|
final SharedPreferences prefs = preference.getSharedPreferences();
|
||||||
|
|
||||||
final String undo = prefs.getString("events", "");
|
final String events = prefs.getString("events", "");
|
||||||
prefs.edit().remove("events").apply();
|
prefs.edit().remove("events").apply();
|
||||||
|
|
||||||
|
final String history = prefs.getString("history", "");
|
||||||
|
prefs.edit().remove("history").apply();
|
||||||
|
|
||||||
Snackbar.make(getActivity().findViewById(android.R.id.content),
|
Snackbar.make(getActivity().findViewById(android.R.id.content),
|
||||||
getString(R.string.preferences_event_snackbar), Snackbar.LENGTH_SHORT)
|
getString(R.string.preferences_event_snackbar), Snackbar.LENGTH_SHORT)
|
||||||
.setAction(R.string.undo, new View.OnClickListener() {
|
.setAction(R.string.undo, new View.OnClickListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
prefs.edit().putString("events", undo).apply();
|
prefs.edit().putString("events", events).apply();
|
||||||
}
|
prefs.edit().putString("history", history).apply();
|
||||||
}).show();
|
}
|
||||||
|
}).show();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
|
@ -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="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
</vector>
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/layout_uri_input"
|
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_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
|
@ -26,9 +26,10 @@
|
||||||
app:endIconMode="clear_text"
|
app:endIconMode="clear_text"
|
||||||
app:errorIconDrawable="@drawable/ic_backspace_black"
|
app:errorIconDrawable="@drawable/ic_backspace_black"
|
||||||
app:helperText="@string/helper_add_link"
|
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:id="@+id/edit_text_uri_input"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
@ -51,9 +51,9 @@
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:src="@drawable/ic_map"
|
android:src="@drawable/ic_map"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
app:tint="@color/material_on_surface_emphasis_high_type" />
|
app:tint="@color/material_on_surface_emphasis_high_type" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -73,8 +73,8 @@
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
style="?android:attr/borderlessButtonStyle"
|
|
||||||
android:id="@+id/image_view_event_time"
|
android:id="@+id/image_view_event_time"
|
||||||
|
style="?android:attr/borderlessButtonStyle"
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
|
@ -128,14 +128,26 @@
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/button_add_to_calendar"
|
android:id="@+id/button_add_to_calendar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
android:text="@string/button_add"
|
android:text="@string/button_add"
|
||||||
android:textColor="@android:color/white"
|
android:textColor="@android:color/white"
|
||||||
app:icon="@drawable/ic_event_available"
|
app:icon="@drawable/ic_event_available"
|
||||||
app:iconGravity="textStart"
|
app:iconGravity="textStart"
|
||||||
app:iconTint="@android:color/white" />
|
app:iconTint="@android:color/white" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/image_view_share"
|
||||||
|
style="?android:attr/borderlessButtonStyle"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/ic_share"
|
||||||
|
app:tint="@color/material_on_surface_emphasis_high_type" />
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!doctype html>
|
||||||
|
<h3>Código abierto</h3>.
|
||||||
|
<p>El código fuente de esta aplicación está disponible en <a href=" https://github.com/akaessens/NoFbEventScraper">GitHub</a>.<strong><br /></strong></p>
|
||||||
|
<p>Si encuentras un problema, por favor, infórmame de forma anónima a través del <a href="https://gitreports.com/issue/akaessens/NoFbEventScraper">bug tracker</a> o directamente en <a href="https://github.com/akaessens/NoFbEventScraper/issues">GitHub</a>.</p>
|
||||||
|
<h3>Actualizaciones</h3>.
|
||||||
|
<p>Esta aplicación está disponible para su descarga en <a href="https://f-droid.org/de/packages/com.akdev.nofbeventscraper">F-Droid</a>. El registro de cambios está disponible en <a href="https://github.com/akaessens/NoFbEventScraper/blob/master/CHANGELOG.md">GitHub</a>.</p>.
|
||||||
|
<p><a href="https://f-droid.org/en/packages/com.akdev.nofbeventscraper"> <img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="75" /> </a></p>
|
||||||
|
<h3>Aviso legal</h3>.
|
||||||
|
<p>Esta aplicación está pensada para almacenar eventos individuales y públicos en un calendario personal. No lo utilices para la recopilación automática de datos y respeta las <a href="http://www.facebook.com/apps/site_scraping_tos_terms.php">Condiciones de recopilación automática de datos</a> de Facebook.
|
||||||
|
<h3>Donación</h3>
|
||||||
|
<p>Estoy desarrollando esta aplicación en mi tiempo libre. Si te gusta, puedes donar en <a href="https://www.paypal.me/andreaskaessens">PayPal</a>.</p>
|
||||||
|
<p><a title="PayPal" href="https://www.paypal.me/andreaskaessens"><img src="https://raw.githubusercontent.com/stefan-niedermann/paypal-donate-button/master/paypal-donate-button.png" height="75" /></a></p>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<!doctype html>
|
||||||
|
<h3>¿Qué enlaces se pueden utilizar con esta aplicación?</h3>
|
||||||
|
<p>Se admiten todos los subdominios de facebook, ya sean móviles (m.facebook.com) o específicos del idioma (de-de.facebook.com). El enlace debe contener un ID de evento.</p>
|
||||||
|
<h3>¿Cómo utilizar esta aplicación?</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Botón pegar</strong>: Basta con pegar un enlace copiado desde el portapapeles en la barra de URL.</li>
|
||||||
|
<li><strong>Compartir vía</strong>: La función de compartir incorporada en Android, por ejemplo, desde un navegador.</li>
|
||||||
|
<li><strong>Abrir con</strong>: La función integrada de Android de abrir con, por ejemplo, al hacer clic desde un servicio de mensajería.</li>
|
||||||
|
</ul>
|
||||||
|
<h3>¿Por qué no funciona el evento X?</h3>
|
||||||
|
<p>Esta app se basa en la información del evento que está disponible públicamente. Si el evento no ofrece, por ejemplo, la ubicación sin necesidad de iniciar sesión, no se encontrará disponible en esta aplicación. Además, algunos eventos simplemente no proporcionan la información en un formato legible por la máquina. Los eventos con múltiples instancias son problemáticos porque no proporcionan la fecha correcta de inicio y fin cuando se extraen de m.facebook.com.</p>
|
||||||
|
<p>Si encuentras problemas con un evento específico, por favor, házmelo saber a través de <a href="https://gitreports.com/issue/akaessens/NoFbEventScraper">bugtracker anónimo</a> o en la <a href="https://github.com/akaessens/NoFbEventScraper/issues/">página de incidentes de GitHub</a>.</p>
|
||||||
|
<h3>¿Esta app se integra con mi aplicación de calendario?</h3>
|
||||||
|
<p>Sí. Esta aplicación hace uso de funciones de calendario independientes de la aplicación, lo que la hace compatible con cualquier app de calendario. Sin embargo, en lo personal recomiendo <a href="https://play.google.com/store/apps/details?id=ws.xsoh.etar">Calendario de Etar</a> porque es Open Source.</p>
|
|
@ -9,7 +9,7 @@
|
||||||
<string name="button_add">Zum Kalender hinzufügen</string>
|
<string name="button_add">Zum Kalender hinzufügen</string>
|
||||||
<string name="tooltip_paste">Einfügen von Inhalten aus der Zwischenablage in das URL-Eingabefeld</string>
|
<string name="tooltip_paste">Einfügen von Inhalten aus der Zwischenablage in das URL-Eingabefeld</string>
|
||||||
<string name="preferences_url_setting">Welcher URL-Präfix ist zu verwenden?</string>
|
<string name="preferences_url_setting">Welcher URL-Präfix ist zu verwenden?</string>
|
||||||
<string name="preferences_url_setting_summary">Die Nutzung von m.facebook.com ist stabiler und schneller. Die Verwendung von www.facebook.com funktioniert besser bei Ereignissen mit mehreren Instanzen und zeigt eine hochauflösende Vorschau an, geht aber irgendwann kaputt, wenn Facebook das klassische Design deaktiviert.</string>
|
<string name="preferences_url_setting_summary">mbasic is am schnellsten, www lädt eine hochauflösende Vorschau und touch dient als zusätzliches Backup</string>
|
||||||
<string name="error_clipboard_empty">Fehler: Zwischenablage leer</string>
|
<string name="error_clipboard_empty">Fehler: Zwischenablage leer</string>
|
||||||
<string name="error_scraping">Fehler: Veranstaltungsdaten nicht gefunden</string>
|
<string name="error_scraping">Fehler: Veranstaltungsdaten nicht gefunden</string>
|
||||||
<string name="error_url">Fehler: URL ungültig</string>
|
<string name="error_url">Fehler: URL ungültig</string>
|
||||||
|
@ -22,4 +22,5 @@
|
||||||
<string name="undo">Rückgängig</string>
|
<string name="undo">Rückgängig</string>
|
||||||
<string name="preferences_page_event_max_summary">Maximale Anzahl Events, die von einer einzelnen Seite geladen werden sollen.</string>
|
<string name="preferences_page_event_max_summary">Maximale Anzahl Events, die von einer einzelnen Seite geladen werden sollen.</string>
|
||||||
<string name="preferences_page_event_max">Veranstaltungslimit für Seiten</string>
|
<string name="preferences_page_event_max">Veranstaltungslimit für Seiten</string>
|
||||||
|
<string name="error_no_events">Fehler: keine bevorstehenden Veranstaltungen</string>
|
||||||
</resources>
|
</resources>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">NoFb Event Scraper</string>
|
||||||
|
<string name="action_about">Acerca de</string>
|
||||||
|
<string name="action_help">Ayuda</string>
|
||||||
|
<string name="action_settings">Ajustes</string>
|
||||||
|
<string name="hint_add_link">Enlace del evento</string>
|
||||||
|
<string name="helper_add_link">Pegue el enlace del evento en Facebook</string>
|
||||||
|
<string name="button_add">Añadir al calendario</string>
|
||||||
|
<string name="tooltip_paste">Pegue el contenido del portapapeles en el cuadro de entrada de la URL</string>
|
||||||
|
<string name="preferences_url_setting">Prefijo de URL que debe utilizarse</string>
|
||||||
|
<string name="preferences_url_setting_summary">mbasic es el más rápida, www carga la vista previa de la imagen en alta resolución, touch es una herramienta de respaldo</string>
|
||||||
|
<string name="error_clipboard_empty">Error: Portapapeles vacío</string>
|
||||||
|
<string name="error_scraping">Error: No se han encontrado los datos del evento</string>
|
||||||
|
<string name="error_url">Error: URL no válida</string>
|
||||||
|
<string name="error_connection">Error: No es posible la conexión</string>
|
||||||
|
<string name="error_unknown">Error: Error desconocido</string>
|
||||||
|
<string name="preferences_events_header">Eventos</string>
|
||||||
|
<string name="preferences_event_setting">Limpiar lista de eventos</string>
|
||||||
|
<string name="preferences_event_snackbar">"Lista de eventos limpiada "</string>
|
||||||
|
<string name="done">Aceptar</string>
|
||||||
|
<string name="undo">Deshacer</string>
|
||||||
|
<string name="preferences_page_event_max_summary">Número máximo de eventos a cargar en una sola página.</string>
|
||||||
|
<string name="preferences_page_event_max">Límite de eventos por página</string>
|
||||||
|
<string name="error_no_events">Error: no hay próximos eventos</string>
|
||||||
|
</resources>
|
|
@ -1,12 +1,14 @@
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Reply Preference -->
|
<!-- Reply Preference -->
|
||||||
<string-array name="url_to_scrape">
|
<string-array name="url_to_scrape">
|
||||||
<item>m.facebook.com</item>
|
<item>mbasic.facebook.com</item>
|
||||||
|
<item>touch.facebook.com</item>
|
||||||
<item>www.facebook.com</item>
|
<item>www.facebook.com</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="url_prefix">
|
<string-array name="url_prefix">
|
||||||
<item>https://m.</item>
|
<item>https://mbasic.</item>
|
||||||
|
<item>https://touch.</item>
|
||||||
<item>https://www.</item>
|
<item>https://www.</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
|
|
@ -18,13 +18,13 @@
|
||||||
<string name="error_url">Error: URL invalid</string>
|
<string name="error_url">Error: URL invalid</string>
|
||||||
<string name="error_connection">Error: Unable to connect</string>
|
<string name="error_connection">Error: Unable to connect</string>
|
||||||
<string name="error_unknown">Error: Unknown Error</string>
|
<string name="error_unknown">Error: Unknown Error</string>
|
||||||
|
<string name="error_no_events">Error: No upcoming events</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
<string name="preferences_scraper_header" translatable="false">Scraper</string>
|
<string name="preferences_scraper_header" translatable="false">Scraper</string>
|
||||||
<string name="preferences_url_setting">Which URL prefix to use</string>
|
<string name="preferences_url_setting">Which URL prefix to use</string>
|
||||||
<string name="preferences_url_setting_summary">"Using m.facebook.com is more stable and faster. Using www.facebook.com works better with multiple instance events and will display a high resolution preview but will eventually break when Facebook disables the classic design. "</string>
|
<string name="preferences_url_setting_summary">"mbasic is the fastest, www loads high-res image preview, touch is an additional backup."</string>
|
||||||
|
|
||||||
<string name="preferences_events_header">Events</string>
|
<string name="preferences_events_header">Events</string>
|
||||||
<string name="preferences_event_setting">Clear event list</string>
|
<string name="preferences_event_setting">Clear event list</string>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:summary="@string/preferences_url_setting_summary"
|
android:summary="@string/preferences_url_setting_summary"
|
||||||
app:defaultValue="https://m."
|
app:defaultValue="https://mbasic."
|
||||||
app:entries="@array/url_to_scrape"
|
app:entries="@array/url_to_scrape"
|
||||||
app:entryValues="@array/url_prefix"
|
app:entryValues="@array/url_prefix"
|
||||||
app:key="url_preference"
|
app:key="url_preference"
|
||||||
|
@ -19,8 +19,8 @@
|
||||||
<SeekBarPreference
|
<SeekBarPreference
|
||||||
android:defaultValue="5"
|
android:defaultValue="5"
|
||||||
app:showSeekBarValue="true"
|
app:showSeekBarValue="true"
|
||||||
app:min="5"
|
app:min="1"
|
||||||
android:max="30"
|
android:max="100"
|
||||||
android:summary="@string/preferences_page_event_max_summary"
|
android:summary="@string/preferences_page_event_max_summary"
|
||||||
android:key="page_event_max"
|
android:key="page_event_max"
|
||||||
android:title="@string/preferences_page_event_max" />
|
android:title="@string/preferences_page_event_max" />
|
||||||
|
|
|
@ -4,11 +4,10 @@ buildscript {
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
|
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.0.0'
|
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
@ -18,8 +17,7 @@ buildscript {
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
- Fix events not displaying correctly after activity resume
|
||||||
|
- add share action on each event
|
||||||
|
- add URL shortener redirection for fb.me
|
|
@ -0,0 +1,2 @@
|
||||||
|
- Fix scraping not working when cookies need to be accepted
|
||||||
|
- Android 11 ready
|
|
@ -0,0 +1,2 @@
|
||||||
|
- Add spanish translation thanks to @sguinetti
|
||||||
|
- update dependencies
|
|
@ -0,0 +1 @@
|
||||||
|
- Fix Android 11 intents (to open Calendar or Maps)
|
|
@ -0,0 +1,4 @@
|
||||||
|
- parallelize multiple event scraping
|
||||||
|
- add scraping history
|
||||||
|
- allow entering page name without URL format
|
||||||
|
- updated dependencies
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 304 KiB |
Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 290 KiB |
Before Width: | Height: | Size: 283 KiB After Width: | Height: | Size: 321 KiB |
Before Width: | Height: | Size: 235 KiB After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 185 KiB |
|
@ -0,0 +1,12 @@
|
||||||
|
El propósito de esta aplicación es obtener acceso a los eventos de Facebook sin necesidad de una cuenta.
|
||||||
|
Por lo tanto, no recurre a la API de Facebook.
|
||||||
|
Como alternativa, abre el URI del evento de Facebook y descarga el código HTML del sitio web.
|
||||||
|
Esta fuente debe contener la información del evento en forma de datos estructurados.
|
||||||
|
Esos datos al extraerse se crean eventos en Android.
|
||||||
|
|
||||||
|
Características:
|
||||||
|
* No recurre a la API de Facebook
|
||||||
|
* Soporta "abrir con" y "compartir via"
|
||||||
|
* Funciona con todas las URLs de los subdominios regionales de Facebook
|
||||||
|
* Guarda el historial de eventos extraídos
|
||||||
|
* Maneja los próximos eventos de las páginas
|
|
@ -0,0 +1 @@
|
||||||
|
Importa los eventos de Facebook al calendario
|
|
@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
|
||||||
distributionSha256Sum=23e7d37e9bb4f8dabb8a3ea7fdee9dd0428b9b1a71d298aefd65b11dccea220f
|
distributionSha256Sum=bf8b869948901d422e9bb7d1fa61da6a6e19411baa7ad6ee929073df85d6365d
|
||||||
|
|