예제 #1
0
def publish(site):
    type = request.form.get('h')
    new_post_url = API_NEW_POST_URL.format(site.site_id)

    data = {
        'title': request.form.get('name'),
        'content': util.get_complex_content(request.form),
        'excerpt': request.form.get('summary'),
        'slug': request.form.get('slug'),
    }

    files = None
    photo_file = request.files.get('photo')
    if photo_file:
        # TODO support multiple files
        data['format'] = 'image'
        files = {
            'media[0]': (os.path.basename(photo_file.filename), photo_file),
        }

    req = requests.Request('POST', new_post_url, data=util.trim_nulls(data),
                           files=files, headers={
                               'Authorization': 'Bearer ' + site.token,
    })

    req = req.prepare()
    s = requests.Session()
    r = s.send(req)

    if r.status_code // 100 != 2:
        return util.wrap_silo_error_response(r)

    r_data = r.json()
    return util.make_publish_success_response(r_data.get('URL'), data=r_data)
예제 #2
0
파일: twitter.py 프로젝트: kylewm/silo.pub
    def interpret_response(result):
        if result.status_code // 100 != 2:
            return util.wrap_silo_error_response(result)

        result_json = result.json()
        twitter_url = 'https://twitter.com/{}/status/{}'.format(
            result_json.get('user', {}).get('screen_name'),
            result_json.get('id_str'))
        return util.make_publish_success_response(twitter_url, result_json)
예제 #3
0
파일: twitter.py 프로젝트: kylewm/silo.pub
    def interpret_response(result):
        if result.status_code // 100 != 2:
            return util.wrap_silo_error_response(result)

        result_json = result.json()
        twitter_url = 'https://twitter.com/{}/status/{}'.format(
            result_json.get('user', {}).get('screen_name'),
            result_json.get('id_str'))
        return util.make_publish_success_response(twitter_url, result_json)
예제 #4
0
def publish(site):
    new_post_url = API_NEW_POST_URL.format(site.site_id)

    data = {
        'title': request.form.get('name'),
        'content': util.get_complex_content(request.form),
        'excerpt': request.form.get('summary'),
        'slug': request.form.get('slug'),
    }

    files = None
    photo_files = util.get_possible_array_value(request.files, 'photo')
    photo_urls = util.get_possible_array_value(request.form, 'photo')
    if photo_files or photo_urls:
        data['format'] = 'image'
        if photo_files:
            files = {
                'media[]': [(os.path.basename(photo_file.filename), photo_file)
                            for photo_file in photo_files],
            }
        if photo_urls:
            data['media_urls[]'] = photo_urls

    req = requests.Request('POST',
                           new_post_url,
                           data=util.trim_nulls(data),
                           files=files,
                           headers={'Authorization': 'Bearer ' + site.token})

    req = req.prepare()
    s = requests.Session()
    r = s.send(req)

    if r.status_code // 100 != 2:
        return util.wrap_silo_error_response(r)

    r_data = r.json()
    return util.make_publish_success_response(r_data.get('URL'), data=r_data)
예제 #5
0
파일: blogger.py 프로젝트: aaronpk/silo.pub
def publish(site):
    """
    Request:

    POST https://www.googleapis.com/blogger/v3/blogs/6561492933847572094/posts
    {
     "title": "This is a test, beautiful friend",
     "content": "This is some content with <i>html</i>!"
    }

    Response:

    200 OK
    {
     "kind": "blogger#post",
     "id": "8225907794810815386",
     "blog": {
      "id": "6561492933847572094"
     },
     "published": "2015-04-14T20:00:00-07:00",
     "updated": "2015-04-14T20:00:19-07:00",
     "etag": "\"Fgc6PVMaOxmEtPvQq0K7b_sZrRM/dGltZXN0YW1wOiAxNDI5MDY2ODE5MTYwCm9mZnNldDogLTI1MjAwMDAwCg\"",
     "url": "http://nofeathersnofur.blogspot.com/2015/04/this-is-test-beautiful-friend.html",
     "selfLink": "https://www.googleapis.com/blogger/v3/blogs/6561492933847572094/posts/8225907794810815386",
     "title": "This is a test, beautiful friend",
     "content": "This is some content with <i>html</i>!",
     "author": {
      "id": "01975554238474627641",
      "displayName": "Kyle",
      "url": "http://www.blogger.com/profile/01975554238474627641",
      "image": {
       "url": "http://img2.blogblog.com/img/b16-rounded.gif"
      }
     },
     "replies": {
      "totalItems": "0",
      "selfLink": "https://www.googleapis.com/blogger/v3/blogs/6561492933847572094/posts/8225907794810815386/comments"
     },
     "status": "LIVE",
     "readerComments": "ALLOW"
    }
    """
    maybe_refresh_access_token(site.account)

    type = request.form.get('h')
    create_post_url = API_CREATE_POST_URL.format(site.site_id)

    current_app.logger.info('posting to blogger %s', create_post_url)

    post_data = util.trim_nulls({
        'title': request.form.get('name'),
        'content': util.get_complex_content(request.form),
    })

    r = requests.post(create_post_url, headers={
        'Authorization': 'Bearer ' + site.account.token,
        'Content-Type': 'application/json',
    }, data=json.dumps(post_data))

    current_app.logger.info(
        'response from blogger %r, data=%r, headers=%r',
        r, r.content, r.headers)

    if r.status_code // 100 != 2:
        return util.wrap_silo_error_response(r)

    success_data = r.json()
    return util.make_publish_success_response(
        success_data.get('url'), data=success_data)
예제 #6
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)
예제 #7
0
파일: flickr.py 프로젝트: kylewm/silo.pub
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))
예제 #8
0
파일: github.py 프로젝트: kylewm/silo.pub
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)))
예제 #9
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)
예제 #10
0
파일: facebook.py 프로젝트: kylewm/silo.pub
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)
예제 #11
0
파일: tumblr.py 프로젝트: kylewm/silo.pub
def publish(site):
    auth = OAuth1(
        client_key=current_app.config['TUMBLR_CLIENT_KEY'],
        client_secret=current_app.config['TUMBLR_CLIENT_SECRET'],
        resource_owner_key=site.account.token,
        resource_owner_secret=site.account.token_secret)

    create_post_url = CREATE_POST_URL.format(site.domain)
    photo_url = util.get_first(util.get_possible_array_value(request.form, 'photo'))
    photo_file = util.get_first(util.get_possible_array_value(request.files, 'photo'))

    if photo_url:
        data = util.trim_nulls({
            'type': 'photo',
            'slug': request.form.get('slug'),
            'caption': request.form.get('content[html]') or
            request.form.get('content') or request.form.get('name') or
            request.form.get('summary'),
            'source': photo_url
        })
        r = requests.post(create_post_url, data=data, auth=auth)

    elif photo_file:
        # tumblr signs multipart in a weird way. first sign the request as if
        # it's application/x-www-form-urlencoded, then recreate the request as
        # multipart but use the signed headers from before. Mostly cribbed from
        # https://github.com/tumblr/pytumblr/blob/\
        # 20e7e38ba6f0734335deee64d4cae45fa8a2ce90/pytumblr/request.py#L101

        # The API documentation and some of the code samples gave me the
        # impression that you could also send files just as part of the
        # form-encoded data but I couldnit make it work
        # https://www.tumblr.com/docs/en/api/v2#pphoto-posts
        # https://gist.github.com/codingjester/1649885#file-upload-php-L56
        data = util.trim_nulls({
            'type': 'photo',
            'slug': request.form.get('slug'),
            'caption': request.form.get('content[html]') or
            request.form.get('content') or request.form.get('name') or
            request.form.get('summary'),
        })
        fake_req = requests.Request('POST', create_post_url, data=data)
        fake_req = fake_req.prepare()
        auth(fake_req)

        real_headers = dict(fake_req.headers)

        # manually strip these, requests will recalculate them for us
        del real_headers['Content-Type']
        del real_headers['Content-Length']

        current_app.logger.info(
            'uploading photo to tumblr %s, headers=%r',
            create_post_url, real_headers)
        r = requests.post(create_post_url, data=data, files={
            'data': photo_file,
        }, headers=real_headers)

    else:
        data = util.trim_nulls({
            # one of: text, photo, quote, link, chat, audio, video
            'type': 'text',
            'slug': request.form.get('slug'),
            'title': request.form.get('name'),
            'body': util.get_complex_content(request.form),
        })
        current_app.logger.info(
            'posting to tumblr %s, data=%r', create_post_url, data)
        r = requests.post(create_post_url, data=data, auth=auth)

    current_app.logger.info(
        'response from tumblr %r, data=%r, headers=%r',
        r, r.content, r.headers)

    if r.status_code // 100 != 2:
        current_app.logger.warn(
            'Tumblr publish failed with response %s', r.text)
        return util.wrap_silo_error_response(r)

    location = None
    if 'Location' in r.headers:
        location = r.headers['Location']
    else:
        # only get back the id, look up the url
        post_id = r.json().get('response').get('id')
        r = requests.get(FETCH_POST_URL.format(site.domain), params={
            'api_key': current_app.config['TUMBLR_CLIENT_KEY'],
            'id': post_id,
        })
        if r.status_code // 100 == 2:
            posts = r.json().get('response', {}).get('posts', [])
            if posts:
                location = posts[0].get('post_url')

    return util.make_publish_success_response(location)
예제 #12
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)
예제 #13
0
파일: tumblr.py 프로젝트: aaronpk/silo.pub
def publish(site):
    auth = OAuth1(
        client_key=current_app.config["TUMBLR_CLIENT_KEY"],
        client_secret=current_app.config["TUMBLR_CLIENT_SECRET"],
        resource_owner_key=site.account.token,
        resource_owner_secret=site.account.token_secret,
    )

    type = request.form.get("h")

    create_post_url = CREATE_POST_URL.format(site.domain)
    photo_file = request.files.get("photo")
    if photo_file:
        # tumblr signs multipart in a weird way. first sign the request as if
        # it's application/x-www-form-urlencoded, then recreate the request as
        # multipart but use the signed headers from before. Mostly cribbed from
        # https://github.com/tumblr/pytumblr/blob/\
        # 20e7e38ba6f0734335deee64d4cae45fa8a2ce90/pytumblr/request.py#L101

        # The API documentation and some of the code samples gave me the
        # impression that you could also send files just as part of the
        # form-encoded data but I couldnit make it work
        # https://www.tumblr.com/docs/en/api/v2#pphoto-posts
        # https://gist.github.com/codingjester/1649885#file-upload-php-L56
        data = util.trim_nulls(
            {
                "type": "photo",
                "slug": request.form.get("slug"),
                "caption": request.form.get("content[html]")
                or request.form.get("content")
                or request.form.get("name")
                or request.form.get("summary"),
            }
        )
        fake_req = requests.Request("POST", create_post_url, data=data)
        fake_req = fake_req.prepare()
        auth(fake_req)

        real_headers = dict(fake_req.headers)

        # manually strip these, requests will recalculate them for us
        del real_headers["Content-Type"]
        del real_headers["Content-Length"]

        current_app.logger.info("uploading photo to tumblr %s, headers=%r", create_post_url, real_headers)
        r = requests.post(create_post_url, data=data, files={"data": photo_file}, headers=real_headers)

    else:
        data = util.trim_nulls(
            {
                # one of: text, photo, quote, link, chat, audio, video
                "type": "text",
                "slug": request.form.get("slug"),
                "title": request.form.get("name"),
                "body": util.get_complex_content(request.form),
            }
        )
        current_app.logger.info("posting to tumblr %s, data=%r", create_post_url, data)
        r = requests.post(create_post_url, data=data, auth=auth)

    current_app.logger.info("response from tumblr %r, data=%r, headers=%r", r, r.content, r.headers)

    if r.status_code // 100 != 2:
        current_app.logger.warn("Tumblr publish failed with response %s", r.text)
        return util.wrap_silo_error_response(r)

    location = None
    if "Location" in r.headers:
        location = r.headers["Location"]
    else:
        # only get back the id, look up the url
        post_id = r.json().get("response").get("id")
        r = requests.get(
            FETCH_POST_URL.format(site.domain),
            params={"api_key": current_app.config["TUMBLR_CLIENT_KEY"], "id": post_id},
        )
        if r.status_code // 100 == 2:
            posts = r.json().get("response", {}).get("posts", [])
            if posts:
                location = posts[0].get("post_url")

    return util.make_publish_success_response(location)
예제 #14
0
def publish(site):
    auth = OAuth1(client_key=current_app.config['TUMBLR_CLIENT_KEY'],
                  client_secret=current_app.config['TUMBLR_CLIENT_SECRET'],
                  resource_owner_key=site.account.token,
                  resource_owner_secret=site.account.token_secret)

    create_post_url = CREATE_POST_URL.format(site.domain)
    photo_url = util.get_first(
        util.get_possible_array_value(request.form, 'photo'))
    photo_file = util.get_first(
        util.get_possible_array_value(request.files, 'photo'))

    if photo_url:
        data = util.trim_nulls({
            'type':
            'photo',
            'slug':
            request.form.get('slug'),
            'caption':
            request.form.get('content[html]') or request.form.get('content')
            or request.form.get('name') or request.form.get('summary'),
            'source':
            photo_url
        })
        r = requests.post(create_post_url, data=data, auth=auth)

    elif photo_file:
        # tumblr signs multipart in a weird way. first sign the request as if
        # it's application/x-www-form-urlencoded, then recreate the request as
        # multipart but use the signed headers from before. Mostly cribbed from
        # https://github.com/tumblr/pytumblr/blob/\
        # 20e7e38ba6f0734335deee64d4cae45fa8a2ce90/pytumblr/request.py#L101

        # The API documentation and some of the code samples gave me the
        # impression that you could also send files just as part of the
        # form-encoded data but I couldnit make it work
        # https://www.tumblr.com/docs/en/api/v2#pphoto-posts
        # https://gist.github.com/codingjester/1649885#file-upload-php-L56
        data = util.trim_nulls({
            'type':
            'photo',
            'slug':
            request.form.get('slug'),
            'caption':
            request.form.get('content[html]') or request.form.get('content')
            or request.form.get('name') or request.form.get('summary'),
        })
        fake_req = requests.Request('POST', create_post_url, data=data)
        fake_req = fake_req.prepare()
        auth(fake_req)

        real_headers = dict(fake_req.headers)

        # manually strip these, requests will recalculate them for us
        del real_headers['Content-Type']
        del real_headers['Content-Length']

        current_app.logger.info('uploading photo to tumblr %s, headers=%r',
                                create_post_url, real_headers)
        r = requests.post(create_post_url,
                          data=data,
                          files={
                              'data': photo_file,
                          },
                          headers=real_headers)

    else:
        data = util.trim_nulls({
            # one of: text, photo, quote, link, chat, audio, video
            'type': 'text',
            'slug': request.form.get('slug'),
            'title': request.form.get('name'),
            'body': util.get_complex_content(request.form),
        })
        current_app.logger.info('posting to tumblr %s, data=%r',
                                create_post_url, data)
        r = requests.post(create_post_url, data=data, auth=auth)

    current_app.logger.info('response from tumblr %r, data=%r, headers=%r', r,
                            r.content, r.headers)

    if r.status_code // 100 != 2:
        current_app.logger.warn('Tumblr publish failed with response %s',
                                r.text)
        return util.wrap_silo_error_response(r)

    location = None
    if 'Location' in r.headers:
        location = r.headers['Location']
    else:
        # only get back the id, look up the url
        post_id = r.json().get('response').get('id')
        r = requests.get(FETCH_POST_URL.format(site.domain),
                         params={
                             'api_key':
                             current_app.config['TUMBLR_CLIENT_KEY'],
                             'id': post_id,
                         })
        if r.status_code // 100 == 2:
            posts = r.json().get('response', {}).get('posts', [])
            if posts:
                location = posts[0].get('post_url')

    return util.make_publish_success_response(location)
예제 #15
0
파일: flickr.py 프로젝트: kylewm/silo.pub
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))
예제 #16
0
파일: github.py 프로젝트: kylewm/silo.pub
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)))