@@ -146,8 +152,7 @@ from plexpy import helpers
$('#osxnotifyregister').click(function () {
var osx_notify_app = $("#osx_notify_app").val();
- $.get("/osxnotifyregister", { 'app': osx_notify_app }, function (data) { $('#ajaxMsg').html("
" + data); });
- $('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
+ $.get("/osxnotifyregister", { 'app': osx_notify_app }, function (data) { showMsg("
" + data, false, true, 3000); });
})
$('#save-notification-item').click(function () {
@@ -157,15 +162,37 @@ from plexpy import helpers
return false;
});
+ function disableTwitterVerify() {
+ if ($('#twitter_key').val() != '') { $('#twitterStep2').prop('disabled', false); }
+ else { $('#twitterStep2').prop('disabled', true); }
+ }
+ disableTwitterVerify();
+ $('#twitter_key').on('change', function () {
+ disableTwitterVerify()
+ });
+
$('#twitterStep1').click(function () {
$.get("/twitterStep1", function (data) {window.open(data); })
- .done(function () { $('#ajaxMsg').html("
Confirm Authorization. Check pop-up blocker if no response."); });
- $('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
+ .done(function () { showMsg("
Confirm Authorization. Check pop-up blocker if no response.", false, true, 3000); });
});
$('#twitterStep2').click(function () {
var twitter_key = $("#twitter_key").val();
- $.get("/twitterStep2", { 'key': twitter_key }, function (data) { $('#ajaxMsg').html("
" + data); });
- $('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
+ $.get("/twitterStep2", { 'key': twitter_key }, function (data) { showMsg("
" + data, false, true, 3000); });
+ });
+
+ function disableFacebookRequest() {
+ if ($('#facebook_app_id').val() != '' && $('#facebook_app_secret').val() != '') { $('#facebookStep1').prop('disabled', false); }
+ else { $('#facebookStep1').prop('disabled', true); }
+ }
+ disableFacebookRequest();
+ $('#facebook_app_id, #facebook_app_secret').on('change', function () {
+ disableFacebookRequest()
+ });
+
+ $('#facebookStep1').click(function () {
+ doAjaxCall('set_notification_config', $(this), 'tabs', true);
+ $.get("/facebookStep1", function (data) {window.open(data); })
+ .done(function () { showMsg("
Confirm Authorization. Check pop-up blocker if no response.", false, true, 3000); });
});
$('#test_notifier').click(function () {
diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html
index c4dbe142..adcdd590 100644
--- a/data/interfaces/default/settings.html
+++ b/data/interfaces/default/settings.html
@@ -1563,8 +1563,7 @@ $(document).ready(function() {
$('#osxnotifyregister').click(function () {
var osx_notify_app = $("#osx_notify_reg").val();
- $.get("/osxnotifyregister", {'app': osx_notify_app}, function (data) { $('#ajaxMsg').html("
"+data+"
"); });
- $('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut()
+ $.get("/osxnotifyregister", { 'app': osx_notify_app }, function (data) { showMsg("
" + data + "
", false, true, 3000); });
})
$.ajax({
diff --git a/lib/pythonfacebook/__init__.py b/lib/pythonfacebook/__init__.py
new file mode 100644
index 00000000..cfadcbbb
--- /dev/null
+++ b/lib/pythonfacebook/__init__.py
@@ -0,0 +1,457 @@
+#!/usr/bin/env python
+#
+# Copyright 2010 Facebook
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Python client library for the Facebook Platform.
+
+This client library is designed to support the Graph API and the
+official Facebook JavaScript SDK, which is the canonical way to
+implement Facebook authentication. Read more about the Graph API at
+http://developers.facebook.com/docs/api. You can download the Facebook
+JavaScript SDK at http://github.com/facebook/connect-js/.
+
+"""
+
+import hashlib
+import hmac
+import binascii
+import base64
+import requests
+import json
+import re
+
+try:
+ from urllib.parse import parse_qs, urlencode
+except ImportError:
+ from urlparse import parse_qs
+ from urllib import urlencode
+
+from . import version
+
+
+__version__ = version.__version__
+
+
+VALID_API_VERSIONS = ["2.0", "2.1", "2.2", "2.3", "2.4", "2.5"]
+
+
+class GraphAPI(object):
+ """A client for the Facebook Graph API.
+
+ See http://developers.facebook.com/docs/api for complete
+ documentation for the API.
+
+ The Graph API is made up of the objects in Facebook (e.g., people,
+ pages, events, photos) and the connections between them (e.g.,
+ friends, photo tags, and event RSVPs). This client provides access
+ to those primitive types in a generic way. For example, given an
+ OAuth access token, this will fetch the profile of the active user
+ and the list of the user's friends:
+
+ graph = facebook.GraphAPI(access_token)
+ user = graph.get_object("me")
+ friends = graph.get_connections(user["id"], "friends")
+
+ You can see a list of all of the objects and connections supported
+ by the API at http://developers.facebook.com/docs/reference/api/.
+
+ You can obtain an access token via OAuth or by using the Facebook
+ JavaScript SDK. See
+ http://developers.facebook.com/docs/authentication/ for details.
+
+ If you are using the JavaScript SDK, you can use the
+ get_user_from_cookie() method below to get the OAuth access token
+ for the active user from the cookie saved by the SDK.
+
+ """
+
+ def __init__(self, access_token=None, timeout=None, version=None,
+ proxies=None):
+ # The default version is only used if the version kwarg does not exist.
+ default_version = "2.0"
+
+ self.access_token = access_token
+ self.timeout = timeout
+ self.proxies = proxies
+
+ if version:
+ version_regex = re.compile("^\d\.\d$")
+ match = version_regex.search(str(version))
+ if match is not None:
+ if str(version) not in VALID_API_VERSIONS:
+ raise GraphAPIError("Valid API versions are " +
+ str(VALID_API_VERSIONS).strip('[]'))
+ else:
+ self.version = "v" + str(version)
+ else:
+ raise GraphAPIError("Version number should be in the"
+ " following format: #.# (e.g. 2.0).")
+ else:
+ self.version = "v" + default_version
+
+ def get_object(self, id, **args):
+ """Fetches the given object from the graph."""
+ return self.request(self.version + "/" + id, args)
+
+ def get_objects(self, ids, **args):
+ """Fetches all of the given object from the graph.
+
+ We return a map from ID to object. If any of the IDs are
+ invalid, we raise an exception.
+ """
+ args["ids"] = ",".join(ids)
+ return self.request(self.version + "/", args)
+
+ def get_connections(self, id, connection_name, **args):
+ """Fetches the connections for given object."""
+ return self.request(
+ "%s/%s/%s" % (self.version, id, connection_name), args)
+
+ def put_object(self, parent_object, connection_name, **data):
+ """Writes the given object to the graph, connected to the given parent.
+
+ For example,
+
+ graph.put_object("me", "feed", message="Hello, world")
+
+ writes "Hello, world" to the active user's wall. Likewise, this
+ will comment on the first post of the active user's feed:
+
+ feed = graph.get_connections("me", "feed")
+ post = feed["data"][0]
+ graph.put_object(post["id"], "comments", message="First!")
+
+ See http://developers.facebook.com/docs/api#publishing for all
+ of the supported writeable objects.
+
+ Certain write operations require extended permissions. For
+ example, publishing to a user's feed requires the
+ "publish_actions" permission. See
+ http://developers.facebook.com/docs/publishing/ for details
+ about publishing permissions.
+
+ """
+ assert self.access_token, "Write operations require an access token"
+ return self.request(
+ self.version + "/" + parent_object + "/" + connection_name,
+ post_args=data,
+ method="POST")
+
+ def put_wall_post(self, message, attachment={}, profile_id="me"):
+ """Writes a wall post to the given profile's wall.
+
+ We default to writing to the authenticated user's wall if no
+ profile_id is specified.
+
+ attachment adds a structured attachment to the status message
+ being posted to the Wall. It should be a dictionary of the form:
+
+ {"name": "Link name"
+ "link": "http://www.example.com/",
+ "caption": "{*actor*} posted a new review",
+ "description": "This is a longer description of the attachment",
+ "picture": "http://www.example.com/thumbnail.jpg"}
+
+ """
+ return self.put_object(profile_id, "feed", message=message,
+ **attachment)
+
+ def put_comment(self, object_id, message):
+ """Writes the given comment on the given post."""
+ return self.put_object(object_id, "comments", message=message)
+
+ def put_like(self, object_id):
+ """Likes the given post."""
+ return self.put_object(object_id, "likes")
+
+ def delete_object(self, id):
+ """Deletes the object with the given ID from the graph."""
+ self.request(self.version + "/" + id, method="DELETE")
+
+ def delete_request(self, user_id, request_id):
+ """Deletes the Request with the given ID for the given user."""
+ self.request("%s_%s" % (request_id, user_id), method="DELETE")
+
+ def put_photo(self, image, album_path="me/photos", **kwargs):
+ """
+ Upload an image using multipart/form-data.
+
+ image - A file object representing the image to be uploaded.
+ album_path - A path representing where the image should be uploaded.
+
+ """
+ return self.request(
+ self.version + "/" + album_path,
+ post_args=kwargs,
+ files={"source": image},
+ method="POST")
+
+ def get_version(self):
+ """Fetches the current version number of the Graph API being used."""
+ args = {"access_token": self.access_token}
+ try:
+ response = requests.request("GET",
+ "https://graph.facebook.com/" +
+ self.version + "/me",
+ params=args,
+ timeout=self.timeout,
+ proxies=self.proxies)
+ except requests.HTTPError as e:
+ response = json.loads(e.read())
+ raise GraphAPIError(response)
+
+ try:
+ headers = response.headers
+ version = headers["facebook-api-version"].replace("v", "")
+ return float(version)
+ except Exception:
+ raise GraphAPIError("API version number not available")
+
+ def request(
+ self, path, args=None, post_args=None, files=None, method=None):
+ """Fetches the given path in the Graph API.
+
+ We translate args to a valid query string. If post_args is
+ given, we send a POST request to the given path with the given
+ arguments.
+
+ """
+ args = args or {}
+
+ if post_args is not None:
+ method = "POST"
+
+ if self.access_token:
+ if post_args is not None:
+ post_args["access_token"] = self.access_token
+ else:
+ args["access_token"] = self.access_token
+
+ try:
+ response = requests.request(method or "GET",
+ "https://graph.facebook.com/" +
+ path,
+ timeout=self.timeout,
+ params=args,
+ data=post_args,
+ proxies=self.proxies,
+ files=files)
+ except requests.HTTPError as e:
+ response = json.loads(e.read())
+ raise GraphAPIError(response)
+
+ headers = response.headers
+ if 'json' in headers['content-type']:
+ result = response.json()
+ elif 'image/' in headers['content-type']:
+ mimetype = headers['content-type']
+ result = {"data": response.content,
+ "mime-type": mimetype,
+ "url": response.url}
+ elif "access_token" in parse_qs(response.text):
+ query_str = parse_qs(response.text)
+ if "access_token" in query_str:
+ result = {"access_token": query_str["access_token"][0]}
+ if "expires" in query_str:
+ result["expires"] = query_str["expires"][0]
+ else:
+ raise GraphAPIError(response.json())
+ else:
+ raise GraphAPIError('Maintype was not text, image, or querystring')
+
+ if result and isinstance(result, dict) and result.get("error"):
+ raise GraphAPIError(result)
+ return result
+
+ def fql(self, query):
+ """FQL query.
+
+ Example query: "SELECT affiliations FROM user WHERE uid = me()"
+
+ """
+ return self.request(self.version + "/" + "fql", {"q": query})
+
+ def get_app_access_token(self, app_id, app_secret):
+ """Get the application's access token as a string."""
+ args = {'grant_type': 'client_credentials',
+ 'client_id': app_id,
+ 'client_secret': app_secret}
+
+ return self.request("oauth/access_token", args=args)["access_token"]
+
+ def get_access_token_from_code(
+ self, code, redirect_uri, app_id, app_secret):
+ """Get an access token from the "code" returned from an OAuth dialog.
+
+ Returns a dict containing the user-specific access token and its
+ expiration date (if applicable).
+
+ """
+ args = {
+ "code": code,
+ "redirect_uri": redirect_uri,
+ "client_id": app_id,
+ "client_secret": app_secret}
+
+ return self.request("oauth/access_token", args)
+
+ def extend_access_token(self, app_id, app_secret):
+ """
+ Extends the expiration time of a valid OAuth access token. See
+
+
+ """
+ args = {
+ "client_id": app_id,
+ "client_secret": app_secret,
+ "grant_type": "fb_exchange_token",
+ "fb_exchange_token": self.access_token,
+ }
+
+ return self.request("oauth/access_token", args=args)
+
+ def debug_access_token(self, token, app_id, app_secret):
+ """
+ Gets information about a user access token issued by an app. See
+
+
+ We can generate the app access token by concatenating the app
+ id and secret:
+
+ """
+ args = {
+ "input_token": token,
+ "access_token": "%s|%s" % (app_id, app_secret)
+ }
+ return self.request("/debug_token", args=args)
+
+
+class GraphAPIError(Exception):
+ def __init__(self, result):
+ self.result = result
+ self.code = None
+ try:
+ self.type = result["error_code"]
+ except:
+ self.type = ""
+
+ # OAuth 2.0 Draft 10
+ try:
+ self.message = result["error_description"]
+ except:
+ # OAuth 2.0 Draft 00
+ try:
+ self.message = result["error"]["message"]
+ self.code = result["error"].get("code")
+ if not self.type:
+ self.type = result["error"].get("type", "")
+ except:
+ # REST server style
+ try:
+ self.message = result["error_msg"]
+ except:
+ self.message = result
+
+ Exception.__init__(self, self.message)
+
+
+def get_user_from_cookie(cookies, app_id, app_secret):
+ """Parses the cookie set by the official Facebook JavaScript SDK.
+
+ cookies should be a dictionary-like object mapping cookie names to
+ cookie values.
+
+ If the user is logged in via Facebook, we return a dictionary with
+ the keys "uid" and "access_token". The former is the user's
+ Facebook ID, and the latter can be used to make authenticated
+ requests to the Graph API. If the user is not logged in, we
+ return None.
+
+ Download the official Facebook JavaScript SDK at
+ http://github.com/facebook/connect-js/. Read more about Facebook
+ authentication at
+ http://developers.facebook.com/docs/authentication/.
+
+ """
+ cookie = cookies.get("fbsr_" + app_id, "")
+ if not cookie:
+ return None
+ parsed_request = parse_signed_request(cookie, app_secret)
+ if not parsed_request:
+ return None
+ try:
+ result = GraphAPI().get_access_token_from_code(
+ parsed_request["code"], "", app_id, app_secret)
+ except GraphAPIError:
+ return None
+ result["uid"] = parsed_request["user_id"]
+ return result
+
+
+def parse_signed_request(signed_request, app_secret):
+ """ Return dictionary with signed request data.
+
+ We return a dictionary containing the information in the
+ signed_request. This includes a user_id if the user has authorised
+ your application, as well as any information requested.
+
+ If the signed_request is malformed or corrupted, False is returned.
+
+ """
+ try:
+ encoded_sig, payload = map(str, signed_request.split('.', 1))
+
+ sig = base64.urlsafe_b64decode(encoded_sig + "=" *
+ ((4 - len(encoded_sig) % 4) % 4))
+ data = base64.urlsafe_b64decode(payload + "=" *
+ ((4 - len(payload) % 4) % 4))
+ except IndexError:
+ # Signed request was malformed.
+ return False
+ except TypeError:
+ # Signed request had a corrupted payload.
+ return False
+ except binascii.Error:
+ # Signed request had a corrupted payload.
+ return False
+
+ data = json.loads(data.decode('ascii'))
+ if data.get('algorithm', '').upper() != 'HMAC-SHA256':
+ return False
+
+ # HMAC can only handle ascii (byte) strings
+ # http://bugs.python.org/issue5285
+ app_secret = app_secret.encode('ascii')
+ payload = payload.encode('ascii')
+
+ expected_sig = hmac.new(app_secret,
+ msg=payload,
+ digestmod=hashlib.sha256).digest()
+ if sig != expected_sig:
+ return False
+
+ return data
+
+
+def auth_url(app_id, canvas_url, perms=None, **kwargs):
+ url = "https://www.facebook.com/dialog/oauth?"
+ kvps = {'client_id': app_id, 'redirect_uri': canvas_url}
+ if perms:
+ kvps['scope'] = ",".join(perms)
+ kvps.update(kwargs)
+ return url + urlencode(kvps)
diff --git a/lib/pythonfacebook/version.py b/lib/pythonfacebook/version.py
new file mode 100644
index 00000000..1ff2e07a
--- /dev/null
+++ b/lib/pythonfacebook/version.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 Martey Dodoo
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+__version__ = "1.0.0-alpha"
diff --git a/plexpy/config.py b/plexpy/config.py
index d899f583..f5afd041 100644
--- a/plexpy/config.py
+++ b/plexpy/config.py
@@ -79,6 +79,22 @@ _CONFIG_DEFINITIONS = {
'EMAIL_ON_EXTUP': (int, 'Email', 0),
'EMAIL_ON_INTUP': (int, 'Email', 0),
'ENABLE_HTTPS': (int, 'General', 0),
+ 'FACEBOOK_ENABLED': (int, 'Facebook', 0),
+ 'FACEBOOK_APP_ID': (str, 'Facebook', ''),
+ 'FACEBOOK_APP_SECRET': (str, 'Facebook', ''),
+ 'FACEBOOK_TOKEN': (str, 'Facebook', ''),
+ 'FACEBOOK_GROUP': (str, 'Facebook', ''),
+ 'FACEBOOK_ON_PLAY': (int, 'Facebook', 0),
+ 'FACEBOOK_ON_STOP': (int, 'Facebook', 0),
+ 'FACEBOOK_ON_PAUSE': (int, 'Facebook', 0),
+ 'FACEBOOK_ON_RESUME': (int, 'Facebook', 0),
+ 'FACEBOOK_ON_BUFFER': (int, 'Facebook', 0),
+ 'FACEBOOK_ON_WATCHED': (int, 'Facebook', 0),
+ 'FACEBOOK_ON_CREATED': (int, 'Facebook', 0),
+ 'FACEBOOK_ON_EXTDOWN': (int, 'Facebook', 0),
+ 'FACEBOOK_ON_INTDOWN': (int, 'Facebook', 0),
+ 'FACEBOOK_ON_EXTUP': (int, 'Facebook', 0),
+ 'FACEBOOK_ON_INTUP': (int, 'Facebook', 0),
'FIRST_RUN_COMPLETE': (int, 'General', 0),
'FREEZE_DB': (int, 'General', 0),
'GIT_BRANCH': (str, 'General', 'master'),
diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py
index 221e11f2..1cbf2a35 100644
--- a/plexpy/notifiers.py
+++ b/plexpy/notifiers.py
@@ -34,6 +34,7 @@ from pynma import pynma
import gntp.notifier
import oauth2 as oauth
import pythontwitter as twitter
+import pythonfacebook as facebook
import plexpy
from plexpy import logger, helpers, request
@@ -54,9 +55,10 @@ AGENT_IDS = {"Growl": 0,
"IFTTT": 12,
"Telegram": 13,
"Slack": 14,
- "Scripts": 15}
-
+ "Scripts": 15,
+ "Facebook": 16}
+
def available_notification_agents():
agents = [{'name': 'Growl',
'id': AGENT_IDS['Growl'],
@@ -312,8 +314,24 @@ def available_notification_agents():
'on_extup': plexpy.CONFIG.SCRIPTS_ON_EXTUP,
'on_intdown': plexpy.CONFIG.SCRIPTS_ON_INTDOWN,
'on_intup': plexpy.CONFIG.SCRIPTS_ON_INTUP
- }
-
+ },
+ {'name': 'Facebook',
+ 'id': AGENT_IDS['Facebook'],
+ 'config_prefix': 'facebook',
+ 'has_config': True,
+ 'state': checked(plexpy.CONFIG.FACEBOOK_ENABLED),
+ 'on_play': plexpy.CONFIG.FACEBOOK_ON_PLAY,
+ 'on_stop': plexpy.CONFIG.FACEBOOK_ON_STOP,
+ 'on_pause': plexpy.CONFIG.FACEBOOK_ON_PAUSE,
+ 'on_resume': plexpy.CONFIG.FACEBOOK_ON_RESUME,
+ 'on_buffer': plexpy.CONFIG.FACEBOOK_ON_BUFFER,
+ 'on_watched': plexpy.CONFIG.FACEBOOK_ON_WATCHED,
+ 'on_created': plexpy.CONFIG.FACEBOOK_ON_CREATED,
+ 'on_extdown': plexpy.CONFIG.FACEBOOK_ON_EXTDOWN,
+ 'on_intdown': plexpy.CONFIG.FACEBOOK_ON_INTDOWN,
+ 'on_extup': plexpy.CONFIG.FACEBOOK_ON_EXTUP,
+ 'on_intup': plexpy.CONFIG.FACEBOOK_ON_INTUP
+ }
]
# OSX Notifications should only be visible if it can be used
@@ -341,7 +359,7 @@ def available_notification_agents():
def get_notification_agent_config(config_id):
- if config_id:
+ if str(config_id).isdigit():
config_id = int(config_id)
if config_id == 0:
@@ -392,6 +410,9 @@ def get_notification_agent_config(config_id):
elif config_id == 15:
script = Scripts()
return script.return_config_options()
+ elif config_id == 16:
+ facebook = FacebookNotifier()
+ return facebook.return_config_options()
else:
return []
else:
@@ -450,11 +471,15 @@ def send_notification(config_id, subject, body, **kwargs):
elif config_id == 15:
scripts = Scripts()
scripts.notify(message=body, subject=subject, **kwargs)
+ elif config_id == 16:
+ facebook = FacebookNotifier()
+ facebook.notify(subject=subject, message=body)
else:
logger.debug(u"PlexPy Notifier :: Unknown agent id received.")
else:
logger.debug(u"PlexPy Notifier :: Notification requested but no agent id received.")
+
class GROWL(object):
"""
Growl notifications, for OS X.
@@ -1181,14 +1206,14 @@ class TwitterNotifier(object):
oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
# logger.debug('oauth_consumer: ' + str(oauth_consumer))
oauth_client = oauth.Client(oauth_consumer, token)
- logger.info('oauth_client: ' + str(oauth_client))
+ # logger.info('oauth_client: ' + str(oauth_client))
resp, content = oauth_client.request(self.ACCESS_TOKEN_URL, method='POST', body='oauth_verifier=%s' % key)
- logger.info('resp, content: ' + str(resp) + ',' + str(content))
+ # logger.info('resp, content: ' + str(resp) + ',' + str(content))
access_token = dict(parse_qsl(content))
- logger.info('access_token: ' + str(access_token))
+ # logger.info('access_token: ' + str(access_token))
- logger.info('resp[status] = ' + str(resp['status']))
+ # logger.info('resp[status] = ' + str(resp['status']))
if resp['status'] != '200':
logger.info('The request for a token with did not succeed: ' + str(resp['status']), logger.ERROR)
return False
@@ -1197,6 +1222,7 @@ class TwitterNotifier(object):
logger.info('Access Token secret: %s' % access_token['oauth_token_secret'])
plexpy.CONFIG.TWITTER_USERNAME = access_token['oauth_token']
plexpy.CONFIG.TWITTER_PASSWORD = access_token['oauth_token_secret']
+ plexpy.CONFIG.write()
return True
def _send_tweet(self, message=None):
@@ -1205,35 +1231,42 @@ class TwitterNotifier(object):
access_token_key = plexpy.CONFIG.TWITTER_USERNAME
access_token_secret = plexpy.CONFIG.TWITTER_PASSWORD
- logger.info(u"Sending tweet: " + message)
+ # logger.info(u"Sending tweet: " + message)
api = twitter.Api(username, password, access_token_key, access_token_secret)
try:
api.PostUpdate(message)
+ logger.info(u"Twitter notifications sent.")
except Exception as e:
- logger.info(u"Error Sending Tweet: %s" % e)
+ logger.info(u"Error sending Tweet: %s" % e)
return False
return True
def return_config_options(self):
- config_option = [{'label': 'Request Authorisation',
- 'value': 'Request Authorisation',
+ config_option = [{'label': 'Instructions',
+ 'description': 'Step 1: Click the Request Authorization button below.
\
+ Step 2: Input the Authorization Key you received from Step 1 below.
\
+ Step 3: Click the Verify Key button below.',
+ 'input_type': 'help'
+ },
+ {'label': 'Request Authorization',
+ 'value': 'Request Authorization',
'name': 'twitterStep1',
- 'description': 'Step 1: Click Request button above. (Ensure you allow the browser pop-up).',
+ 'description': 'Request Twitter authorization. (Ensure you allow the browser pop-up).',
'input_type': 'button'
},
- {'label': 'Authorisation Key',
+ {'label': 'Authorization Key',
'value': '',
'name': 'twitter_key',
- 'description': 'Step 2: Input the authorisation key you received from Step 1.',
+ 'description': 'Your Twitter authorization key.',
'input_type': 'text'
},
{'label': 'Verify Key',
'value': 'Verify Key',
'name': 'twitterStep2',
- 'description': 'Step 3: Verify the key.',
+ 'description': 'Verify your Twitter authorization key.',
'input_type': 'button'
},
{'input_type': 'nosave'
@@ -1635,6 +1668,7 @@ class TELEGRAM(object):
return config_option
+
class SLACK(object):
"""
Slack Notifications
@@ -1958,3 +1992,120 @@ class Scripts(object):
]
return config_option
+
+
+class FacebookNotifier(object):
+
+ def __init__(self):
+ self.app_id = plexpy.CONFIG.FACEBOOK_APP_ID
+ self.app_secret = plexpy.CONFIG.FACEBOOK_APP_SECRET
+ self.group_id = plexpy.CONFIG.FACEBOOK_GROUP
+
+ if plexpy.CONFIG.ENABLE_HTTPS:
+ protocol = 'https'
+ else:
+ protocol = 'http'
+
+ if plexpy.CONFIG.HTTP_HOST == '0.0.0.0':
+ host = 'localhost'
+ else:
+ host = plexpy.CONFIG.HTTP_HOST
+
+ self.redirect_url = '%s://%s:%i/facebookStep2' % (protocol, host, plexpy.CONFIG.HTTP_PORT)
+
+
+ def notify(self, subject, message):
+ if not subject or not message:
+ return
+ else:
+ self._post_facebook(subject + ': ' + message)
+
+ def test_notify(self):
+ return self._post_facebook("This is a test notification from PlexPy at " + helpers.now())
+
+ def _get_authorization(self):
+ return facebook.auth_url(app_id=self.app_id,
+ canvas_url=self.redirect_url,
+ perms=['user_managed_groups','publish_actions'])
+
+ def _get_credentials(self, code):
+ logger.info('Requesting access token from Facebook')
+
+ try:
+ # Request user access token
+ api = facebook.GraphAPI(version='2.5')
+ response = api.get_access_token_from_code(code=code,
+ redirect_uri=self.redirect_url,
+ app_id=self.app_id,
+ app_secret=self.app_secret)
+ access_token = response['access_token']
+
+ # Request extended user access token
+ api = facebook.GraphAPI(access_token=access_token, version='2.5')
+ response = api.extend_access_token(app_id=self.app_id,
+ app_secret=self.app_secret)
+ access_token = response['access_token']
+
+ plexpy.CONFIG.FACEBOOK_TOKEN = access_token
+ plexpy.CONFIG.write()
+ except Exception as e:
+ logger.info(u"Error requesting Facebook access token: %s" % e)
+ return False
+
+ return True
+
+ def _post_facebook(self, message=None):
+ access_token = plexpy.CONFIG.FACEBOOK_TOKEN
+ group_id = plexpy.CONFIG.FACEBOOK_GROUP
+
+ if group_id:
+ api = facebook.GraphAPI(access_token=access_token, version='2.5')
+
+ try:
+ api.put_wall_post(profile_id=group_id, message=message)
+ logger.info(u"Facebook notifications sent.")
+ except Exception as e:
+ logger.info(u"Error sending Facebook post: %s" % e)
+ return False
+
+ return True
+ else:
+ logger.info('Error sending Facebook post: No Facebook Group ID provided.')
+ return False
+
+ def return_config_options(self):
+ config_option = [{'label': 'Instructions',
+ 'description': 'Facebook notifications are experimental!
\
+ Step 1: Visit Facebook Developers to create a new app using advanced setup.
\
+ Step 2: Go to Settings > Advanced and fill in Valid OAuth redirect URIs with your PlexPy URL (i.e. http://localhost:8181).
\
+ Step 3: Fill in the App ID and App Secret below.
\
+ Step 4: Click the Request Authorization button below.',
+ 'input_type': 'help'
+ },
+ {'label': 'Facebook App ID',
+ 'value': self.app_id,
+ 'name': 'facebook_app_id',
+ 'description': 'Your Facebook app ID.',
+ 'input_type': 'text'
+ },
+ {'label': 'Facebook App Secret',
+ 'value': self.app_secret,
+ 'name': 'facebook_app_secret',
+ 'description': 'Your Facebook app secret.',
+ 'input_type': 'text'
+ },
+ {'label': 'Request Authorization',
+ 'value': 'Request Authorization',
+ 'name': 'facebookStep1',
+ 'description': 'Request Facebook authorization. (Ensure you allow the browser pop-up).',
+ 'input_type': 'button'
+ },
+ {'label': 'Facebook Group ID',
+ 'value': self.group_id,
+ 'name': 'facebook_group',
+ 'description': 'Your Facebook Group ID.',
+ 'input_type': 'text'
+ }
+ ]
+
+ return config_option
diff --git a/plexpy/webserve.py b/plexpy/webserve.py
index 412f2767..79ba325a 100644
--- a/plexpy/webserve.py
+++ b/plexpy/webserve.py
@@ -695,12 +695,29 @@ class WebInterface(object):
cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
tweet = notifiers.TwitterNotifier()
result = tweet._get_credentials(key)
- logger.info(u"result: " + str(result))
+ # logger.info(u"result: " + str(result))
if result:
return "Key verification successful"
else:
return "Unable to verify key"
+ @cherrypy.expose
+ def facebookStep1(self):
+ cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
+ facebook = notifiers.FacebookNotifier()
+ return facebook._get_authorization()
+
+ @cherrypy.expose
+ def facebookStep2(self, code):
+ cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"
+ facebook = notifiers.FacebookNotifier()
+ result = facebook._get_credentials(code)
+ # logger.info(u"result: " + str(result))
+ if result:
+ return "Key verification successful, you may close this page now."
+ else:
+ return "Unable to verify key"
+
@cherrypy.expose
def osxnotifyregister(self, app):
cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store"