Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Google Drive export functionality #77

Merged
merged 1 commit into from
Oct 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 2 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ This change was unannounced and no reason was ever released.

fb2cal is a tool which restores this functionality.
It works by calling various async endpoints that power the https://www.facebook.com/events/birthdays/ page.
After gathering a list of birthdays for all the users friends for a full year, it creates a ICS calendar file which is then stored on Google Drive as a publically shared file. This ICS file can then be imported into third party tools (such as Google Calendar).
The ICS file can also be stored on the local file system.
After gathering a list of birthdays for all the users friends for a full year, it creates a ICS calendar file. This ICS file can then be imported into third party tools (such as Google Calendar).

This tool **does not** use the Facebook API.

Expand All @@ -30,10 +29,8 @@ This tool **does not** use the Facebook API.
* Python 3.6+
* pipenv
* Scheduler tool to automatically run script periodically (optional)
* Google Drive API access (optional)

## Instructions
### Option 1: Save ICS file to filesystem
1. Clone repo
`git clone git@github.com:mobeigi/fb2cal.git`
2. Rename `config/config-template.ini` to `config/config.ini` and enter your Facebook email and password (no quotes).
Expand All @@ -42,32 +39,11 @@ This tool **does not** use the Facebook API.
4. Run the script manually:
`pipenv run python src/fb2cal.py`
5. Import the created `birthdays.ics` file into Calendar applications (i.e. Google Calendar)
### Option 2: Automatically Upload ICS file to Google Drive
1. Clone repo
`git clone git@github.com:mobeigi/fb2cal.git`
2. Create a Google Drive API credentials
1. Visit the Google Drive APIs page: https://console.developers.google.com/apis/api/drive.googleapis.com/overview
2. Create a new project (if you don't already have one)
3. Enable API (if not already enabled)
4. Select **API & Services** > **Credentials** from left pane.
5. Select **Create Credentials** > **OAuth client ID**. Make sure to **Configure content screen** if you are prompted to do so. For the application type select **Other** and then enter any name you like (i.e. fb2cal)
6. Click **Create** to create your OAuth client ID credentials
7. Download credentials JSON file
3. Rename credentials JSON file to **credentials.json** and put it in the `src` folder
4. Rename `config/config-template.ini` to `config/config.ini` and enter your Facebook email and password as well as a name for your calender to be saved on Google Drive. Change `upload_to_drive` to `True`. Initially, the value for the **drive_file_id** field should be empty.
5. Install required python modules
`pipenv install`
1. Run script manually once for testing purposes:
`pipenv run python ./fb2cal.py`
7. Check Google Drive to ensure your ICS file was made.
8. Setup Cron Jobs/Task Scheduler/Automator to repeatedly run the script to periodically generate an updated ICS file. See **Scheduled Task Frequency** section for more info.
9. Use the following link to import your ICS file into Calendar applications (i.e. Google Calendar):
`http://drive.google.com/uc?export=download&id=DRIVE_FILE_ID`. Replace **DRIVE_FILE_ID** with the autopopulated value found in your `config/config.ini` file.

## Configuration
This tool can be configured by editing the `config/config.ini` configuration file.

<table> <thead> <tr style="background-color: inherit"> <th>Section</th> <th>Key</th> <th>Valid Values</th> <th>Description</th> </tr></thead> <tbody> <tr style="background-color: inherit"> <td rowspan=2>AUTH</td><td>fb_email</td><td></td><td>Your Facebook login email</td></tr><tr style="background-color: inherit"> <td>fb_password</td><td></td><td>Your Facebook login password</td></tr><tr style="background-color: inherit"> <td rowspan=3>DRIVE</td><td>upload_to_drive</td><td>True, False</td><td>If tool should automatically upload ICS file to Google Drive</td></tr><tr style="background-color: inherit"> <td>drive_file_id</td><td></td><td>The file id of file to write to on Google Drive. Leave blank to create a new file for the first time.</td></tr><tr style="background-color: inherit"> <td>ics_file_name</td><td></td><td>The name of the file to be stored/updated on Google Drive.</td></tr><tr style="background-color: inherit"> <td rowspan=2>FILESYSTEM</td><td>save_to_file</td><td>True, False</td><td>If tool should save ICS file to the local file system</td></tr><tr style="background-color: inherit"> <td>ics_file_path</td><td></td><td>Path to save ICS file to (including file name)</td></tr><tr style="background-color: inherit"> <td>LOGGING</td><td>level</td><td>DEBUG, INFO, WARNING, ERROR, CRITICAL</td><td>Logging level to use. Default: INFO</td></tr></tbody></table>
<table> <thead> <tr> <th>Section</th> <th>Key</th> <th>Valid Values</th> <th>Description</th> </tr></thead> <tbody> <tr> <td rowspan=2>AUTH</td><td>fb_email</td><td></td><td>Your Facebook login email</td></tr><tr> <td>fb_password</td><td></td><td>Your Facebook login password</td></tr><tr> <td rowspan=2>FILESYSTEM</td><td>save_to_file</td><td>True, False</td><td>If tool should save ICS file to the local file system</td></tr><tr> <td>ics_file_path</td><td></td><td>Path to save ICS file to (including file name)</td></tr><tr> <td>LOGGING</td><td>level</td><td>DEBUG, INFO, WARNING, ERROR, CRITICAL</td><td>Logging level to use. Default: INFO</td></tr></tbody></table>

## Troubleshooting
If you encounter any issues, please open the `config/config.ini` configuration file and set the `LOGGING` `level` to `DEBUG` (it is `INFO` by default). Include these logs when asking for help.
Expand Down
27 changes: 11 additions & 16 deletions config/config-template.ini
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
; Rename this file to config.ini and fill in required information below
[AUTH]
fb_email =
fb_pass =

[DRIVE]
upload_to_drive = False
drive_file_id =
ics_file_name = birthdays.ics

[FILESYSTEM]
save_to_file = True
ics_file_path = ./birthdays.ics

[LOGGING]
level = INFO
; Rename this file to config.ini and fill in required information below
[AUTH]
fb_email =
fb_pass =

[FILESYSTEM]
save_to_file = True
ics_file_path = ./birthdays.ics

[LOGGING]
level = INFO
20 changes: 8 additions & 12 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
MechanicalSoup
ics>=0.6
babel
pytz
httplib2
requests
beautifulsoup4
lxml
python_dateutil
google_api_python_client
google-auth-oauthlib
oauth2client
MechanicalSoup
ics>=0.6
babel
pytz
requests
beautifulsoup4
lxml
python_dateutil
79 changes: 0 additions & 79 deletions src/fb2cal.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,6 @@
from distutils import util
import calendar

from oauth2client import file, client, tools
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseUpload
from googleapiclient.errors import HttpError
from httplib2 import Http
from io import BytesIO

# Classes
class Birthday:
def __init__(self, uid, name, day, month):
Expand Down Expand Up @@ -98,12 +91,6 @@ def main():

logger.info(f'Logging level set to: {logging.getLevelName(logger.level)}')

# Authenticate with Google API early
if util.strtobool(config['DRIVE']['UPLOAD_TO_DRIVE']):
logger.info('Authenticating with Google Drive API...')
service = google_drive_api_authenticate()
logger.info('Successfully authenticated with Google Drive API.')

# Init browser
browser = mechanicalsoup.StatefulBrowser()
init_browser(browser)
Expand Down Expand Up @@ -143,37 +130,6 @@ def main():
ics_file.write(ics_str)
logger.info(f'Successfully saved ICS file to {os.path.abspath(config["FILESYSTEM"]["ICS_FILE_PATH"])}')

# Upload to drive
if util.strtobool(config['DRIVE']['UPLOAD_TO_DRIVE']):
logger.info('Uploading ICS file to Google Drive...')
metadata = {'name': config['DRIVE']['ICS_FILE_NAME']}
UPLOAD_RETRY_ATTEMPTS = 3
uploaded_successfully = False

for attempt in range(UPLOAD_RETRY_ATTEMPTS):
try:
updated_file = upload_and_replace_file(service, config['DRIVE']['DRIVE_FILE_ID'], metadata, bytearray(ics_str, 'utf-8')) # Pass payload as bytes
config.set('DRIVE', 'DRIVE_FILE_ID', updated_file['id'])
uploaded_successfully = True
except HttpError as e:
if e.resp.status == 404: # file not found
if config['DRIVE']['DRIVE_FILE_ID']:
logger.warning(f'{e}. Resetting stored file id in config and trying again. Attempt: {attempt+1}')
config.set('DRIVE', 'DRIVE_FILE_ID', '') # reset stored file_id
continue
else:
logger.error(e)
raise SystemError
else:
logger.error(e)
raise SystemError

if uploaded_successfully:
logger.info(f'Successfully uploaded {config["DRIVE"]["ICS_FILE_NAME"]} to Google Drive with file id: {config["DRIVE"]["DRIVE_FILE_ID"]}\nDirect download link: http://drive.google.com/uc?export=download&id={config["DRIVE"]["DRIVE_FILE_ID"]}')
else:
logger.error(f'Failed to upload {config["DRIVE"]["ICS_FILE_NAME"]} to Google Drive after {UPLOAD_RETRY_ATTEMPTS} attempts.')
raise SystemError

# Update config file with updated file id for subsequent runs
logger.info('Saving changes to config file...')
with open(CONFIG_FILE_PATH, 'w') as configfile:
Expand Down Expand Up @@ -262,41 +218,6 @@ def facebook_authenticate(browser, email, password):
logger.error(f'Hit Facebook security checkpoint. Please login to Facebook manually and follow prompts to authorize this device.')
raise SystemError

def google_drive_api_authenticate():
""" Authenticate with Google Drive Api """

# Confirm credentials.json exists
if not os.path.isfile('credentials.json'):
logger.error(f'credentials.json file does not exist')
raise SystemExit

SCOPES = 'https://www.googleapis.com/auth/drive.file'
store = file.Storage('token.json')
creds = store.get()
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('credentials.json', SCOPES)
creds = tools.run_flow(flow, store)
service = build('drive', 'v3', http=creds.authorize(Http()), cache_discovery=False)
return service

def upload_and_replace_file(service, file_id, metadata, payload):
mine_type = 'text/calendar'
text_stream = BytesIO(payload)
media_body = MediaIoBaseUpload(text_stream, mimetype=mine_type, chunksize=1024*1024, resumable=True)

# If file id is provided, update the file, otherwise we'll create a new file
if file_id:
updated_file = service.files().update(fileId=file_id, body=metadata, media_body=media_body).execute()
else:
updated_file = service.files().create(body=metadata, media_body=media_body).execute()

# Need publically accessible ics file so third party tools can read from it publically
permission = { "role": 'reader',
"type": 'anyone'}
service.permissions().create(fileId=updated_file['id'], body=permission).execute()

return updated_file

__cached_async_token = None
def get_async_token(browser):
""" Get async authorization token (CSRF protection token) that must be included in all async requests """
Expand Down