def micropub_endpoint(): current_app.logger.info( "received micropub request %s, args=%s, form=%s, headers=%s", request, request.args, request.form, request.headers) if request.method == 'GET': return 'This is the micropub endpoint.' bearer_prefix = 'Bearer ' header_token = request.headers.get('authorization') if header_token and header_token.startswith(bearer_prefix): token = header_token[len(bearer_prefix):] else: token = request.form.get('access_token') if not token: err_msg = 'Micropub request is missing access_token' current_app.logger.warn(err_msg) return err_msg, 401 # indieauth has confirmed that me is who they say they are, so if we have # an access token for their api, then we should be good to go # new short tokens token_data = Token.query.get(token) if token_data: site = token_data.site # old JWT tokens (TODO how do I get rid of these??) else: try: token_data = util.jwt_decode(token) except: current_app.logger.debug('could not find or decode token: %s', token) if not token_data: err_msg = 'Unrecognized token: {}' .format(token) current_app.logger.warn(err_msg) return err_msg, 401 site_id = token_data.get('site') site = Site.query.get(site_id) if not site: err_msg = 'Could not find a site for site id {}.'.format(site_id) current_app.logger.warn(err_msg) return err_msg, 401 current_app.logger.info('Success! Publishing to %s', site) resp = SERVICES[site.service].publish(site) if not resp: return util.make_publish_error_response( 'Unspecified failure. Maybe an invalid or unsupported request?') return resp
def publish(site): auth = OAuth1( client_key=current_app.config['GOODREADS_CLIENT_KEY'], client_secret=current_app.config['GOODREADS_CLIENT_SECRET'], resource_owner_key=site.account.token, resource_owner_secret=site.account.token_secret) # publish a review # book_id (goodreads internal id) # review[review] (text of the review) # review[rating] (0-5) ... 0 = not given, 1-5 maps directly to h-review p-rating # review[read_at] dt-reviewed in YYYY-MM-DD # shelf -- check p-category for any that match existing goodreads shelves, # TODO consider creating shelves for categories? # item might be an ISBN, a Goodreads URL, or just a title item = request.form.get('item') if not item: item_name = request.form.get('item[name]') item_author = request.form.get('item[author]') if item_name and item_author: item = item_name + ' ' + item_author rating = request.form.get('rating') review = next((request.form.get(key) for key in ( 'description[value]', 'description', 'content[value]', 'content', 'summary', 'name')), None) categories = util.get_possible_array_value(request.form, 'category') if not item: return util.make_publish_error_response( 'Expected "item": a URL, ISBN, or book title to review') m = item and BOOK_URL_RE.match(item) if m: book_id = m.group(1) else: # try searching for item r = requests.get(SEARCH_URL, params={ 'q': item, 'key': current_app.config['GOODREADS_CLIENT_KEY'], }) if r.status_code // 100 != 2: return util.wrap_silo_error_response(r) root = ETree.fromstring(r.content) book = root.find('search/results/work/best_book') if not book: return { 'error': 'Goodreads found no results for query: ' + item, 'upstream-data': r.text } book_id = book.findtext('id') # add book to shelves all_shelves = set() if categories: r = requests.get(SHELVES_LIST_URL, params={ 'key': current_app.config['GOODREADS_CLIENT_KEY'], 'user_id': site.account.user_id, }) if r.status_code // 100 != 2: return util.wrap_silo_error_response(r) root = ETree.fromstring(r.content) for shelf in root.find('shelves'): all_shelves.add(shelf.findtext('name')) matched_categories = [c for c in categories if c in all_shelves] permalink = 'https://www.goodreads.com/book/show/' + book_id resp_data = {} # publish a review of the book if rating or review: current_app.logger.debug('creating a review: book=%s, rating=%s, review=%s', book_id, rating, review) r = requests.post(REVIEW_CREATE_URL, data=util.trim_nulls({ 'book_id': book_id, 'review[rating]': rating, 'review[review]': review, # first shelf that matches 'shelf': matched_categories.pop(0) if matched_categories else None, }), auth=auth) if r.status_code // 100 != 2: return util.wrap_silo_error_response(r) # example response """<?xml version="1.0" encoding="UTF-8"?> <review> <id type="integer">1484927007</id> <user-id type="integer">4544167</user-id> <book-id type="integer">9361589</book-id> <rating type="integer">2</rating> <read-status>read</read-status> <sell-flag type="boolean">false</sell-flag> <review></review> <recommendation nil="true"/> <read-at type="datetime" nil="true"/> <updated-at type="datetime">2015-12-29T21:25:34+00:00</updated-at> <created-at type="datetime">2015-12-29T21:25:34+00:00</created-at> <comments-count type="integer">0</comments-count> <weight type="integer">0</weight> <ratings-sum type="integer">0</ratings-sum> <ratings-count type="integer">0</ratings-count> <notes nil="true"/> <spoiler-flag type="boolean">false</spoiler-flag> <recommender-user-id1 type="integer">0</recommender-user-id1> <recommender-user-name1 nil="true"/> <work-id type="integer">14245059</work-id> <read-count nil="true"/> <last-comment-at type="datetime" nil="true"/> <started-at type="datetime" nil="true"/> <hidden-flag type="boolean">false</hidden-flag> <language-code type="integer" nil="true"/> <last-revision-at type="datetime">2015-12-29T21:25:34+00:00</last-revision-at> <non-friends-rating-count type="integer">0</non-friends-rating-count> </review>""" root = ETree.fromstring(r.content) review_id = root.findtext('id') permalink = 'https://www.goodreads.com/review/show/' + review_id resp_data['review-response'] = r.text if matched_categories: r = requests.post(ADD_BOOKS_TO_SHELVES_URL, data={ 'bookids': book_id, 'shelves': ','.join(matched_categories), }, auth=auth) if r.status_code // 100 != 2: current_app.logger.error( 'Failed to add book %s to additional shelves %r. Status: %s, Response: %r', book_id, matched_categories, r.status_code, r.text) resp_data['shelves-response'] = r.text return util.make_publish_success_response(permalink, data=resp_data)
def publish(site): def get_photo_id(original): """Based on an original URL, find the Flickr syndicated URL and extract the photo ID Returns a tuple with (photo_id, photo_url) """ flickr_url = util.posse_post_discovery(original, FLICKR_PHOTO_RE) if flickr_url: m = FLICKR_PHOTO_RE.match(flickr_url) if m: return m.group(2), flickr_url return None, None def get_path_alias(): return (site.account.user_info.get('person', {}).get('path_alias') or site.account.user_id) in_reply_to = request.form.get('in-reply-to') like_of = request.form.get('like-of') title = request.form.get('name') desc = request.form.get('content[value]') or request.form.get('content') # try to comment on a photo if in_reply_to: photo_id, flickr_url = get_photo_id(in_reply_to) if not photo_id: return util.make_publish_error_response( 'Could not find Flickr photo to comment on based on URL {}' .format(in_reply_to)) r = call_api_method('POST', 'flickr.photos.comments.addComment', { 'photo_id': photo_id, 'comment_text': desc or title, }, site=site) result = r.json() if result.get('stat') == 'fail': return util.wrap_silo_error_response(r) return util.make_publish_success_response( result.get('comment', {}).get('permalink'), result) # try to like a photo if like_of: photo_id, flickr_url = get_photo_id(like_of) if not photo_id: return util.make_publish_error_response( 'Could not find Flickr photo to like based on original URL {}' .format(like_of)) r = call_api_method('POST', 'flickr.favorites.add', { 'photo_id': photo_id, }, site=site) result = r.json() if result.get('stat') == 'fail': return util.wrap_silo_error_response(r) return util.make_publish_success_response( flickr_url + '#liked-by-' + get_path_alias(), result) # otherwise we're uploading a photo photo_file = util.get_first(util.get_files_or_urls_as_file_storage(request.files, request.form, 'photo')) if not photo_file: photo_file = util.get_first(util.get_files_or_urls_as_file_storage(request.files, request.form, 'video')) if not photo_file: return util.make_publish_error_response('Missing "photo" attachment') r = upload({'title': title, 'description': desc}, photo_file, site=site) if r.status_code // 100 != 2: return util.wrap_silo_error_response(r) photo_id, error = interpret_upload_response(r) if error: return util.make_publish_error_response(error) # maybe add some tags or people cats = util.get_possible_array_value(request.form, 'category') tags = [] user_ids = [] for cat in cats: if util.looks_like_a_url(cat): resp = call_api_method( 'GET', 'flickr.urls.lookupUser', {'url': cat}, site=site) if resp.status_code // 100 != 2: current_app.logger.error( 'Error looking up user by url %s. Response: %r, %s', cat, resp, resp.text) result = resp.json() if result.get('stat') == 'fail': current_app.logger.debug( 'User not found for url %s', cat) else: user_id = result.get('user', {}).get('id') if user_id: user_ids.append(user_id) else: tags.append('"' + cat + '"') if tags: current_app.logger.debug('Adding tags: %s', ','.join(tags)) resp = call_api_method('POST', 'flickr.photos.addTags', { 'photo_id': photo_id, 'tags': ','.join(tags), }, site=site) current_app.logger.debug('Added tags: %r, %s', resp, resp.text) for user_id in user_ids: current_app.logger.debug('Tagging user id: %s', user_id) resp = call_api_method('POST', 'flickr.photos.people.add', { 'photo_id': photo_id, 'user_id': user_id, }, site=site) current_app.logger.debug('Tagged person: %r, %s', resp, resp.text) lat, lng = util.parse_geo_uri(request.form.get('location')) if lat and lng: current_app.logger.debug('setting location: %s, %s', lat, lng) resp = call_api_method('POST', 'flickr.photos.geo.setLocation', { 'photo_id': photo_id, 'lat': lat, 'lon': lng, }, site=site) current_app.logger.debug('set location: %r, %s', resp, resp.text) return util.make_publish_success_response( 'https://www.flickr.com/photos/{}/{}/'.format( get_path_alias(), photo_id))
def publish(site): auth_headers = {'Authorization': 'token ' + site.account.token} in_reply_to = request.form.get('in-reply-to') if in_reply_to: in_reply_to = util.posse_post_discovery(in_reply_to, BASE_PATTERN) repo_match = (re.match(REPO_PATTERN, in_reply_to) or re.match(ISSUES_PATTERN, in_reply_to)) issue_match = (re.match(ISSUE_PATTERN, in_reply_to) or re.match(PULL_PATTERN, in_reply_to)) # reply to a repository -- post a new issue if repo_match: endpoint = 'https://api.github.com/repos/{}/{}/issues'.format( repo_match.group(1), repo_match.group(2)) title = request.form.get('name') body = (request.form.get('content[markdown]') or request.form.get('content[value]') or request.form.get('content') or '') if not title and body: title = body[:49] + '\u2026' data = { 'title': title, 'body': body, 'labels': util.get_possible_array_value(request.form, 'category'), } # reply to an issue -- post a new comment elif issue_match: endpoint = 'https://api.github.com/repos/{}/{}/issues/{}/comments'.format( issue_match.group(1), issue_match.group(2), issue_match.group(3)) body = (request.form.get('content[markdown]') or request.form.get('content[value]') or request.form.get('content') or '') data = {'body': body} else: return util.make_publish_error_response( 'Reply URL does look like a repo or issue: ' + in_reply_to) current_app.logger.debug('sending POST to %s with data %s', endpoint, data) r = requests.post(endpoint, json=data, headers=auth_headers) if r.status_code // 100 != 2: return util.wrap_silo_error_response(r) resp_json = r.json() return util.make_publish_success_response(resp_json.get('html_url'), resp_json) # like a repository -- star the repository like_of = request.form.get('like-of') if like_of: like_of = util.posse_post_discovery(like_of, BASE_PATTERN) repo_match = re.match(REPO_PATTERN, like_of) if repo_match: endpoint = 'https://api.github.com/user/starred/{}/{}'.format( repo_match.group(1), repo_match.group(2)) current_app.logger.debug('sending PUT to %s', endpoint) r = requests.put(endpoint, headers=auth_headers) if r.status_code // 100 != 2: return util.wrap_silo_error_response(r) return util.make_publish_success_response(like_of + '#starred-by-' + site.account.username) else: return util.make_publish_error_response( 'Like-of URL must be a repo: ' + like_of) return util.make_publish_error_response( 'See {} for details publishing to GitHub.'.format( url_for('views.developers', _external=True)))
def publish(site): title = request.form.get('name') content = request.form.get('content[value]') or request.form.get('content') permalink = request.form.get('url') photo_file = util.get_first( util.get_possible_array_value(request.files, 'photo')) photo_url = util.get_first( util.get_possible_array_value(request.form, 'photo')) video_file = util.get_first( util.get_possible_array_value(request.files, 'video')) video_url = util.get_first( util.get_possible_array_value(request.form, 'video')) location = request.form.get('location') post_data = {'access_token': site.account.token} post_files = None api_endpoint = 'https://graph.facebook.com/v2.5/me/feed' fburl_separator = 'posts' message = (content if not permalink else '({})'.format(permalink) if not content else '{} ({})'.format(content, permalink)) if video_file or video_url: if video_file: post_files = { 'source': (video_file.filename, video_file.stream, video_file.content_type or 'video/mp4') } elif video_url: post_data['url'] = video_url post_data['title'] = title post_data['description'] = message api_endpoint = 'https://graph-video.facebook.com/v2.5/me/videos' fburl_separator = 'videos' elif photo_file or photo_url: if photo_file: post_files = {'source': photo_file} elif photo_url: post_data['url'] = photo_url post_data['caption'] = message # TODO support album id as alternative to 'me' # TODO upload to "Timeline photos" album by default api_endpoint = 'https://graph.facebook.com/v2.5/me/photos' fburl_separator = 'photos' elif title and content: # looks like an article -- include the permalink as a 'link' # instead of inline post_data['message'] = '{}\n\n{}'.format(title, content) post_data['link'] = permalink post_data['name'] = title elif content: post_data['message'] = message tokens = brevity.tokenize(content) # linkify the first url in the message linktok = next((tok for tok in tokens if tok.tag == 'link'), None) if linktok: post_data['link'] = linktok.content else: return util.make_publish_error_response( 'Request must contain a photo, video, or content') # posting Location to Facebook is disabled for now -- just # searching lat/long does not get us close enough to assume we # have the correct place. if False and location: if location.isnumeric(): post_data['place'] = location else: place_name = (request.form.get('place-name') or request.form.get('place_name')) lat, lng = util.parse_geo_uri(location) if lat and lng: current_app.logger.debug('Search FB for a place, %s at %s, %s', place_name, lat, lng) r = requests.get('https://graph.facebook.com/v2.5/search', params=util.trim_nulls({ 'type': 'place', 'center': '%s,%s' % (lat, lng), 'distance': '500', 'q': place_name, 'access_token': site.account.token, })) if r.status_code != 200: current_app.logger.warning( 'FB place search failed with response %r: %r', r, r.text) else: places = r.json().get('data', []) if not places: # TODO consider searching without a place name? current_app.logger.warning( 'FB no resuts for place %s at %s, %s ', place_name, lat, lng) else: current_app.logger.debug('Found FB place: %s (%s)', places[0].get('name'), places[0].get('id')) post_data['place'] = places[0].get('id') post_data = util.trim_nulls(post_data) current_app.logger.debug('Publishing to facebook %s: data=%s, files=%s', api_endpoint, post_data, post_files) r = requests.post(api_endpoint, data=post_data, files=post_files) # need Web Canvas permissions to do this, which I am too lazy to apply for # if r.status_code == 400: # error_data = r.json().get('error', {}) # code = error_data.get('code') # subcode = error_data.get('subcode') # # token is expired or otherwise invalid # if code == 190: # send_token_expired_notification( # site.account.user_id, # "silo.pub's Facebook access token has expired. Click the " # "Facebook button on silo.pub's homepage to renew.", # 'https://silo.pub/') if r.status_code // 100 != 2: return util.wrap_silo_error_response(r) resp_data = r.json() userid = '' fbid = resp_data.get('id') or resp_data.get('post_id') split = fbid.split('_') if len(split) == 2: userid, fbid = split return util.make_publish_success_response( 'https://www.facebook.com/{}/{}/{}'.format( site.account.username or userid, fburl_separator, fbid), data=resp_data)
def publish(site): title = request.form.get('name') content = request.form.get('content[value]') or request.form.get('content') permalink = request.form.get('url') photo_file = util.get_first(util.get_possible_array_value(request.files, 'photo')) photo_url = util.get_first(util.get_possible_array_value(request.form, 'photo')) video_file = util.get_first(util.get_possible_array_value(request.files, 'video')) video_url = util.get_first(util.get_possible_array_value(request.form, 'video')) location = request.form.get('location') post_data = {'access_token': site.account.token} post_files = None api_endpoint = 'https://graph.facebook.com/v2.5/me/feed' fburl_separator = 'posts' message = ( content if not permalink else '({})'.format(permalink) if not content else '{} ({})'.format(content, permalink)) if video_file or video_url: if video_file: post_files = {'source': (video_file.filename, video_file.stream, video_file.content_type or 'video/mp4')} elif video_url: post_data['url'] = video_url post_data['title'] = title post_data['description'] = message api_endpoint = 'https://graph-video.facebook.com/v2.5/me/videos' fburl_separator = 'videos' elif photo_file or photo_url: if photo_file: post_files = {'source': photo_file} elif photo_url: post_data['url'] = photo_url post_data['caption'] = message # TODO support album id as alternative to 'me' # TODO upload to "Timeline photos" album by default api_endpoint = 'https://graph.facebook.com/v2.5/me/photos' fburl_separator = 'photos' elif title and content: # looks like an article -- include the permalink as a 'link' # instead of inline post_data['message'] = '{}\n\n{}'.format(title, content) post_data['link'] = permalink post_data['name'] = title elif content: post_data['message'] = message tokens = brevity.tokenize(content) # linkify the first url in the message linktok = next((tok for tok in tokens if tok.tag == 'link'), None) if linktok: post_data['link'] = linktok.content else: return util.make_publish_error_response( 'Request must contain a photo, video, or content') # posting Location to Facebook is disabled for now -- just # searching lat/long does not get us close enough to assume we # have the correct place. if False and location: if location.isnumeric(): post_data['place'] = location else: place_name = (request.form.get('place-name') or request.form.get('place_name')) lat, lng = util.parse_geo_uri(location) if lat and lng: current_app.logger.debug('Search FB for a place, %s at %s, %s', place_name, lat, lng) r = requests.get( 'https://graph.facebook.com/v2.5/search', params=util.trim_nulls({ 'type': 'place', 'center': '%s,%s' % (lat, lng), 'distance': '500', 'q': place_name, 'access_token': site.account.token, })) if r.status_code != 200: current_app.logger.warning( 'FB place search failed with response %r: %r', r, r.text) else: places = r.json().get('data', []) if not places: # TODO consider searching without a place name? current_app.logger.warning( 'FB no resuts for place %s at %s, %s ', place_name, lat, lng) else: current_app.logger.debug( 'Found FB place: %s (%s)', places[0].get('name'), places[0].get('id')) post_data['place'] = places[0].get('id') post_data = util.trim_nulls(post_data) current_app.logger.debug( 'Publishing to facebook %s: data=%s, files=%s', api_endpoint, post_data, post_files) r = requests.post(api_endpoint, data=post_data, files=post_files) # need Web Canvas permissions to do this, which I am too lazy to apply for # if r.status_code == 400: # error_data = r.json().get('error', {}) # code = error_data.get('code') # subcode = error_data.get('subcode') # # token is expired or otherwise invalid # if code == 190: # send_token_expired_notification( # site.account.user_id, # "silo.pub's Facebook access token has expired. Click the " # "Facebook button on silo.pub's homepage to renew.", # 'https://silo.pub/') if r.status_code // 100 != 2: return util.wrap_silo_error_response(r) resp_data = r.json() userid = '' fbid = resp_data.get('id') or resp_data.get('post_id') split = fbid.split('_') if len(split) == 2: userid, fbid = split return util.make_publish_success_response( 'https://www.facebook.com/{}/{}/{}'.format( site.account.username or userid, fburl_separator, fbid), data=resp_data)
def publish(site): auth = OAuth1(client_key=current_app.config['GOODREADS_CLIENT_KEY'], client_secret=current_app.config['GOODREADS_CLIENT_SECRET'], resource_owner_key=site.account.token, resource_owner_secret=site.account.token_secret) # publish a review # book_id (goodreads internal id) # review[review] (text of the review) # review[rating] (0-5) ... 0 = not given, 1-5 maps directly to h-review p-rating # review[read_at] dt-reviewed in YYYY-MM-DD # shelf -- check p-category for any that match existing goodreads shelves, # TODO consider creating shelves for categories? # item might be an ISBN, a Goodreads URL, or just a title item = request.form.get('item') if not item: item_name = request.form.get('item[name]') item_author = request.form.get('item[author]') if item_name and item_author: item = item_name + ' ' + item_author rating = request.form.get('rating') review = next( (request.form.get(key) for key in ('description[value]', 'description', 'content[value]', 'content', 'summary', 'name')), None) categories = util.get_possible_array_value(request.form, 'category') if not item: return util.make_publish_error_response( 'Expected "item": a URL, ISBN, or book title to review') m = item and BOOK_URL_RE.match(item) if m: book_id = m.group(1) else: # try searching for item r = requests.get(SEARCH_URL, params={ 'q': item, 'key': current_app.config['GOODREADS_CLIENT_KEY'], }) if r.status_code // 100 != 2: return util.wrap_silo_error_response(r) root = ETree.fromstring(r.content) book = root.find('search/results/work/best_book') if not book: return { 'error': 'Goodreads found no results for query: ' + item, 'upstream-data': r.text } book_id = book.findtext('id') # add book to shelves all_shelves = set() if categories: r = requests.get(SHELVES_LIST_URL, params={ 'key': current_app.config['GOODREADS_CLIENT_KEY'], 'user_id': site.account.user_id, }) if r.status_code // 100 != 2: return util.wrap_silo_error_response(r) root = ETree.fromstring(r.content) for shelf in root.find('shelves'): all_shelves.add(shelf.findtext('name')) matched_categories = [c for c in categories if c in all_shelves] permalink = 'https://www.goodreads.com/book/show/' + book_id resp_data = {} # publish a review of the book if rating or review: current_app.logger.debug( 'creating a review: book=%s, rating=%s, review=%s', book_id, rating, review) r = requests.post( REVIEW_CREATE_URL, data=util.trim_nulls({ 'book_id': book_id, 'review[rating]': rating, 'review[review]': review, # first shelf that matches 'shelf': matched_categories.pop(0) if matched_categories else None, }), auth=auth) if r.status_code // 100 != 2: return util.wrap_silo_error_response(r) # example response """<?xml version="1.0" encoding="UTF-8"?> <review> <id type="integer">1484927007</id> <user-id type="integer">4544167</user-id> <book-id type="integer">9361589</book-id> <rating type="integer">2</rating> <read-status>read</read-status> <sell-flag type="boolean">false</sell-flag> <review></review> <recommendation nil="true"/> <read-at type="datetime" nil="true"/> <updated-at type="datetime">2015-12-29T21:25:34+00:00</updated-at> <created-at type="datetime">2015-12-29T21:25:34+00:00</created-at> <comments-count type="integer">0</comments-count> <weight type="integer">0</weight> <ratings-sum type="integer">0</ratings-sum> <ratings-count type="integer">0</ratings-count> <notes nil="true"/> <spoiler-flag type="boolean">false</spoiler-flag> <recommender-user-id1 type="integer">0</recommender-user-id1> <recommender-user-name1 nil="true"/> <work-id type="integer">14245059</work-id> <read-count nil="true"/> <last-comment-at type="datetime" nil="true"/> <started-at type="datetime" nil="true"/> <hidden-flag type="boolean">false</hidden-flag> <language-code type="integer" nil="true"/> <last-revision-at type="datetime">2015-12-29T21:25:34+00:00</last-revision-at> <non-friends-rating-count type="integer">0</non-friends-rating-count> </review>""" root = ETree.fromstring(r.content) review_id = root.findtext('id') permalink = 'https://www.goodreads.com/review/show/' + review_id resp_data['review-response'] = r.text if matched_categories: r = requests.post(ADD_BOOKS_TO_SHELVES_URL, data={ 'bookids': book_id, 'shelves': ','.join(matched_categories), }, auth=auth) if r.status_code // 100 != 2: current_app.logger.error( 'Failed to add book %s to additional shelves %r. Status: %s, Response: %r', book_id, matched_categories, r.status_code, r.text) resp_data['shelves-response'] = r.text return util.make_publish_success_response(permalink, data=resp_data)
def publish(site): def get_photo_id(original): """Based on an original URL, find the Flickr syndicated URL and extract the photo ID Returns a tuple with (photo_id, photo_url) """ flickr_url = util.posse_post_discovery(original, FLICKR_PHOTO_RE) if flickr_url: m = FLICKR_PHOTO_RE.match(flickr_url) if m: return m.group(2), flickr_url return None, None def get_path_alias(): return (site.account.user_info.get('person', {}).get('path_alias') or site.account.user_id) in_reply_to = request.form.get('in-reply-to') like_of = request.form.get('like-of') title = request.form.get('name') desc = request.form.get('content[value]') or request.form.get('content') # try to comment on a photo if in_reply_to: photo_id, flickr_url = get_photo_id(in_reply_to) if not photo_id: return util.make_publish_error_response( 'Could not find Flickr photo to comment on based on URL {}'. format(in_reply_to)) r = call_api_method('POST', 'flickr.photos.comments.addComment', { 'photo_id': photo_id, 'comment_text': desc or title, }, site=site) result = r.json() if result.get('stat') == 'fail': return util.wrap_silo_error_response(r) return util.make_publish_success_response( result.get('comment', {}).get('permalink'), result) # try to like a photo if like_of: photo_id, flickr_url = get_photo_id(like_of) if not photo_id: return util.make_publish_error_response( 'Could not find Flickr photo to like based on original URL {}'. format(like_of)) r = call_api_method('POST', 'flickr.favorites.add', { 'photo_id': photo_id, }, site=site) result = r.json() if result.get('stat') == 'fail': return util.wrap_silo_error_response(r) return util.make_publish_success_response( flickr_url + '#liked-by-' + get_path_alias(), result) # otherwise we're uploading a photo photo_file = util.get_first( util.get_files_or_urls_as_file_storage(request.files, request.form, 'photo')) if not photo_file: photo_file = util.get_first( util.get_files_or_urls_as_file_storage(request.files, request.form, 'video')) if not photo_file: return util.make_publish_error_response('Missing "photo" attachment') r = upload({'title': title, 'description': desc}, photo_file, site=site) if r.status_code // 100 != 2: return util.wrap_silo_error_response(r) photo_id, error = interpret_upload_response(r) if error: return util.make_publish_error_response(error) # maybe add some tags or people cats = util.get_possible_array_value(request.form, 'category') tags = [] user_ids = [] for cat in cats: if util.looks_like_a_url(cat): resp = call_api_method('GET', 'flickr.urls.lookupUser', {'url': cat}, site=site) if resp.status_code // 100 != 2: current_app.logger.error( 'Error looking up user by url %s. Response: %r, %s', cat, resp, resp.text) result = resp.json() if result.get('stat') == 'fail': current_app.logger.debug('User not found for url %s', cat) else: user_id = result.get('user', {}).get('id') if user_id: user_ids.append(user_id) else: tags.append('"' + cat + '"') if tags: current_app.logger.debug('Adding tags: %s', ','.join(tags)) resp = call_api_method('POST', 'flickr.photos.addTags', { 'photo_id': photo_id, 'tags': ','.join(tags), }, site=site) current_app.logger.debug('Added tags: %r, %s', resp, resp.text) for user_id in user_ids: current_app.logger.debug('Tagging user id: %s', user_id) resp = call_api_method('POST', 'flickr.photos.people.add', { 'photo_id': photo_id, 'user_id': user_id, }, site=site) current_app.logger.debug('Tagged person: %r, %s', resp, resp.text) lat, lng = util.parse_geo_uri(request.form.get('location')) if lat and lng: current_app.logger.debug('setting location: %s, %s', lat, lng) resp = call_api_method('POST', 'flickr.photos.geo.setLocation', { 'photo_id': photo_id, 'lat': lat, 'lon': lng, }, site=site) current_app.logger.debug('set location: %r, %s', resp, resp.text) return util.make_publish_success_response( 'https://www.flickr.com/photos/{}/{}/'.format(get_path_alias(), photo_id))
def publish(site): auth_headers = {'Authorization': 'token ' + site.account.token} in_reply_to = request.form.get('in-reply-to') if in_reply_to: in_reply_to = util.posse_post_discovery(in_reply_to, BASE_PATTERN) repo_match = (re.match(REPO_PATTERN, in_reply_to) or re.match(ISSUES_PATTERN, in_reply_to)) issue_match = (re.match(ISSUE_PATTERN, in_reply_to) or re.match(PULL_PATTERN, in_reply_to)) # reply to a repository -- post a new issue if repo_match: endpoint = 'https://api.github.com/repos/{}/{}/issues'.format( repo_match.group(1), repo_match.group(2)) title = request.form.get('name') body = (request.form.get('content[markdown]') or request.form.get('content[value]') or request.form.get('content') or '') if not title and body: title = body[:49] + '\u2026' data = { 'title': title, 'body': body, 'labels': util.get_possible_array_value(request.form, 'category'), } # reply to an issue -- post a new comment elif issue_match: endpoint = 'https://api.github.com/repos/{}/{}/issues/{}/comments'.format( issue_match.group(1), issue_match.group(2), issue_match.group(3)) body = (request.form.get('content[markdown]') or request.form.get('content[value]') or request.form.get('content') or '') data = {'body': body} else: return util.make_publish_error_response( 'Reply URL does look like a repo or issue: ' + in_reply_to) current_app.logger.debug('sending POST to %s with data %s', endpoint, data) r = requests.post(endpoint, json=data, headers=auth_headers) if r.status_code // 100 != 2: return util.wrap_silo_error_response(r) resp_json = r.json() return util.make_publish_success_response( resp_json.get('html_url'), resp_json) # like a repository -- star the repository like_of = request.form.get('like-of') if like_of: like_of = util.posse_post_discovery(like_of, BASE_PATTERN) repo_match = re.match(REPO_PATTERN, like_of) if repo_match: endpoint = 'https://api.github.com/user/starred/{}/{}'.format( repo_match.group(1), repo_match.group(2)) current_app.logger.debug('sending PUT to %s', endpoint) r = requests.put(endpoint, headers=auth_headers) if r.status_code // 100 != 2: return util.wrap_silo_error_response(r) return util.make_publish_success_response( like_of + '#starred-by-' + site.account.username) else: return util.make_publish_error_response( 'Like-of URL must be a repo: ' + like_of) return util.make_publish_error_response( 'See {} for details publishing to GitHub.' .format(url_for('views.developers', _external=True)))