diff --git a/README.md b/README.md index 3b67268..beba684 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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). @@ -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. -
Section Key Valid Values Description
AUTHfb_emailYour Facebook login email
fb_passwordYour Facebook login password
DRIVEupload_to_driveTrue, FalseIf tool should automatically upload ICS file to Google Drive
drive_file_idThe file id of file to write to on Google Drive. Leave blank to create a new file for the first time.
ics_file_nameThe name of the file to be stored/updated on Google Drive.
FILESYSTEMsave_to_fileTrue, FalseIf tool should save ICS file to the local file system
ics_file_pathPath to save ICS file to (including file name)
LOGGINGlevelDEBUG, INFO, WARNING, ERROR, CRITICALLogging level to use. Default: INFO
+
Section Key Valid Values Description
AUTHfb_emailYour Facebook login email
fb_passwordYour Facebook login password
FILESYSTEMsave_to_fileTrue, FalseIf tool should save ICS file to the local file system
ics_file_pathPath to save ICS file to (including file name)
LOGGINGlevelDEBUG, INFO, WARNING, ERROR, CRITICALLogging level to use. Default: INFO
## 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. diff --git a/config/config-template.ini b/config/config-template.ini index 150c0b7..60d0465 100644 --- a/config/config-template.ini +++ b/config/config-template.ini @@ -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 diff --git a/requirements.txt b/requirements.txt index 8b87050..ab8d645 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,8 @@ -MechanicalSoup -ics>=0.6 -babel -pytz -httplib2 -requests -beautifulsoup4 -lxml -python_dateutil -google_api_python_client -google-auth-oauthlib -oauth2client \ No newline at end of file +MechanicalSoup +ics>=0.6 +babel +pytz +requests +beautifulsoup4 +lxml +python_dateutil \ No newline at end of file diff --git a/src/fb2cal.py b/src/fb2cal.py index 0bf44dd..70ee510 100644 --- a/src/fb2cal.py +++ b/src/fb2cal.py @@ -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): @@ -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) @@ -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: @@ -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 """