Beispiel #1
0
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
Beispiel #2
0
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
Beispiel #3
0
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)
Beispiel #4
0
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))
Beispiel #5
0
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)))
Beispiel #6
0
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)
Beispiel #7
0
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)
Beispiel #8
0
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)
Beispiel #9
0
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))
Beispiel #10
0
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)))