Skip to content

Commit

Permalink
Add Bridgy support for Meetup.com
Browse files Browse the repository at this point in the history
As part of snarfed#873, we want to add support for RSVPing to events on
Meetup.com via Bridgy.

Meetup.com is the first silo that doesn't support Listen, so we need to
handle it a little differently to other silos, ignoring anything Listen
related, and when performing the initial Authorization flow from the
home page, we use the Publish OAuth2 scopes, not Listen.

As noted in [0], we can only have one `redirect_uri` for Meetup.com's
OAuth2 clients, so we can use `https://brid.gy/meetup/` as the prefix,
and then it will allow any matches under that path.

As we don't support listen on Meetup, we need to make sure that
`CAN_LISTEN` is advertised as such.

Closes snarfed#873.

[0]: https://www.jvt.me/posts/2020/02/15/meetup-api-multiple-redirect-uris/
  • Loading branch information
jamietanna committed Feb 16, 2020
1 parent 260c36c commit 8d9fe02
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -20,6 +20,7 @@ instagram_scrape_*
instagram_sessionid_cookie
linkedin_client_*
medium_client_*
meetup_client_*
private_notes
superfeedr_token
superfeedr_username
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -132,7 +132,7 @@ I occasionally generate [stats and graphs of usage and growth](https://snarfed.o
bq load --replace --nosync --source_format=DATASTORE_BACKUP datastore.$kind gs://brid-gy.appspot.com/stats/all_namespaces/kind_$kind/all_namespaces_kind_$kind.export_metadata
done
for kind in Blogger FacebookPage Flickr GitHub GooglePlusPage Instagram Medium Tumblr Twitter WordPress; do
for kind in Blogger FacebookPage Flickr GitHub GooglePlusPage Instagram Medium Meetup Tumblr Twitter WordPress; do
bq load --replace --nosync --source_format=DATASTORE_BACKUP sources.$kind gs://brid-gy.appspot.com/stats/all_namespaces/kind_$kind/all_namespaces_kind_$kind.export_metadata
done
```
Expand Down
3 changes: 2 additions & 1 deletion app.py
Expand Up @@ -38,6 +38,7 @@
'instagram',
'mastodon',
'medium',
'meetup',
'publish',
'tumblr',
'twitter',
Expand Down Expand Up @@ -732,7 +733,7 @@ def get(self):
routes += [
('/?', FrontPageHandler),
('/users/?', UsersHandler),
('/(blogger|fake|fake_blog|flickr|github|instagram|mastodon|medium|tumblr|twitter|wordpress)/([^/]+)/?',
('/(blogger|fake|fake_blog|flickr|github|instagram|mastodon|medium|meetup|tumblr|twitter|wordpress)/([^/]+)/?',
UserHandler),
('/facebook/.*', FacebookIsDeadHandler),
('/googleplus/.*', GooglePlusIsDeadHandler),
Expand Down
4 changes: 2 additions & 2 deletions docs/index.rst
Expand Up @@ -233,7 +233,7 @@ dataset <https://console.cloud.google.com/bigquery?p=brid-gy&d=datastore&page=da

::

gcloud datastore export --async gs://brid-gy.appspot.com/stats/ --kinds Blogger,BlogPost,BlogWebmention,FacebookPage,Flickr,GitHub,GooglePlusPage,Instagram,Medium,Publish,PublishedPage,Response,SyndicatedPost,Tumblr,Twitter,WordPress
gcloud datastore export --async gs://brid-gy.appspot.com/stats/ --kinds Blogger,BlogPost,BlogWebmention,FacebookPage,Flickr,GitHub,GooglePlusPage,Instagram,Medium,Meetup,Publish,PublishedPage,Response,SyndicatedPost,Tumblr,Twitter,WordPress

Note that ``--kinds`` is required. `From the export
docs <https://cloud.google.com/datastore/docs/export-import-entities#limitations>`__,
Expand All @@ -250,7 +250,7 @@ dataset <https://console.cloud.google.com/bigquery?p=brid-gy&d=datastore&page=da
bq load --replace --nosync --source_format=DATASTORE_BACKUP datastore.$kind gs://brid-gy.appspot.com/stats/all_namespaces/kind_$kind/all_namespaces_kind_$kind.export_metadata
done

for kind in Blogger FacebookPage Flickr GitHub GooglePlusPage Instagram Medium Tumblr Twitter WordPress; do
for kind in Blogger FacebookPage Flickr GitHub GooglePlusPage Instagram Medium Meetup Tumblr Twitter WordPress; do
bq load --replace --nosync --source_format=DATASTORE_BACKUP sources.$kind gs://brid-gy.appspot.com/stats/all_namespaces/kind_$kind/all_namespaces_kind_$kind.export_metadata
done

Expand Down
71 changes: 71 additions & 0 deletions meetup.py
@@ -0,0 +1,71 @@
"""Meetup API code and datastore model classes.
"""
import logging

from granary import meetup as gr_meetup
from oauth_dropins import meetup as oauth_meetup
from oauth_dropins.webutil.util import json_dumps, json_loads
import webapp2

from models import Source
import util

# We don't support listen
LISTEN_SCOPES = []
PUBLISH_SCOPES = [
'rsvp',
]


class Meetup(Source):
GR_CLASS = gr_meetup.Meetup
OAUTH_START_HANDLER = oauth_meetup.StartHandler
SHORT_NAME = 'meetup'
BACKFEED_REQUIRES_SYNDICATION_LINK = True
CAN_LISTEN = False
CAN_PUBLISH = True
URL_CANONICALIZER = util.UrlCanonicalizer(
domain=GR_CLASS.DOMAIN,
headers=util.REQUEST_HEADERS)

@staticmethod
def new(handler, auth_entity=None, **kwargs):
"""Creates and returns a :class:`Meetup` for the logged in user.
Args:
handler: the current :class:`webapp2.RequestHandler`
auth_entity: :class:`oauth_dropins.meetup.MeetupAuth`
kwargs: property values
"""
user = json_loads(auth_entity.user_json)
gr_source = gr_meetup.Meetup(access_token=auth_entity.access_token())
actor = gr_source.user_to_actor(user)
return Meetup(id=auth_entity.key.id(),
auth_entity=auth_entity.key,
name=actor.get('displayName'),
picture=actor.get('image', {}).get('url'),
url=actor.get('url'),
**kwargs)

def silo_url(self):
"""Returns the Meetup account URL, e.g. https://meetup.com/members/...."""
return self.gr_source.user_url(self.key.id())

def label_name(self):
"""Returns the username."""
return self.name


class AddMeetup(oauth_meetup.CallbackHandler, util.Handler):
def finish(self, auth_entity, state=None):
logging.debug('finish with %s, %s', auth_entity, state)
self.maybe_add_or_delete_source(Meetup, auth_entity, state)

ROUTES = [
('/meetup/start', util.oauth_starter(oauth_meetup.StartHandler).to(
'/meetup/add', scopes=LISTEN_SCOPES)),
('/meetup/add', AddMeetup),
('/meetup/delete/finish', oauth_meetup.CallbackHandler.to('/delete/finish')),
('/meetup/publish/start', oauth_meetup.StartHandler.to(
'/meetup/publish/finish', scopes=PUBLISH_SCOPES)),
]
13 changes: 10 additions & 3 deletions publish.py
Expand Up @@ -18,6 +18,7 @@
flickr as oauth_flickr,
github as oauth_github,
mastodon as oauth_mastodon,
meetup as oauth_meetup,
twitter as oauth_twitter,
)
from oauth_dropins.webutil import appengine_info
Expand All @@ -29,14 +30,15 @@
from github import GitHub
from instagram import Instagram
from mastodon import Mastodon
from meetup import Meetup
from models import Publish, PublishedPage
from twitter import Twitter
import models
import util
import webmention


SOURCES = (Flickr, GitHub, Mastodon, Twitter)
SOURCES = (Flickr, GitHub, Mastodon, Meetup, Twitter)
SOURCE_NAMES = {cls.SHORT_NAME: cls for cls in SOURCES}
SOURCE_DOMAINS = {cls.GR_CLASS.DOMAIN: cls for cls in SOURCES}
# image URLs matching this regexp should be ignored.
Expand Down Expand Up @@ -145,7 +147,7 @@ def _run(self):
if (domain not in util.DOMAINS or
len(path_parts) != 2 or path_parts[0] != '/publish' or not source_cls):
return self.error(
'Target must be brid.gy/publish/{flickr,github,mastodon,twitter}')
'Target must be brid.gy/publish/{flickr,github,mastodon,meetup,twitter}')
elif source_cls == Instagram:
return self.error('Sorry, %s is not supported.' %
source_cls.GR_CLASS.NAME)
Expand Down Expand Up @@ -696,6 +698,10 @@ class MastodonSendHandler(oauth_mastodon.CallbackHandler, SendHandler):
finish = SendHandler.finish


class MeetupSendHandler(oauth_meetup.CallbackHandler, SendHandler):
finish = SendHandler.finish


class TwitterSendHandler(oauth_twitter.CallbackHandler, SendHandler):
finish = SendHandler.finish

Expand Down Expand Up @@ -733,10 +739,11 @@ def authorize(self):
ROUTES = [
('/publish/preview', PreviewHandler),
('/publish/webmention', WebmentionHandler),
('/publish/(flickr|github|mastodon|twitter)',
('/publish/(flickr|github|mastodon|meetup|twitter)',
webmention.WebmentionGetHandler),
('/publish/flickr/finish', FlickrSendHandler),
('/publish/github/finish', GitHubSendHandler),
('/publish/mastodon/finish', MastodonSendHandler),
('/meetup/publish/finish', MeetupSendHandler), # because Meetup's `redirect_uri` handling is a little more restrictive
('/publish/twitter/finish', TwitterSendHandler),
]
Binary file added static/meetup_icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions templates/about.html
Expand Up @@ -105,6 +105,7 @@
<li><a href="#github-respond-to-comment">How do I respond to a GitHub comment in particular?</a></li>
<li><a href="#add-label">How do I add a label to a GitHub issue?</a></li>
<li><a href="#github-star">How do I star a GitHub repo?</a></li>
<li><a href="#meetup-rsvp">How do I RSVP to an event on Meetup?</a></li>
<li><a href="#omit-link">Can I disable the link back to my post?</a></li>
<li><a href="#ignore-formatting">Can I disable the plain text whitespace and
formatting?</a></li>
Expand Down Expand Up @@ -811,6 +812,7 @@ <h3 id="publish">Publishing</h3>
<li><code>https://brid.gy/publish/flickr</code></li>
<li><code>https://brid.gy/publish/github</code></li>
<li><code>https://brid.gy/publish/mastodon</code></li>
<li><code>https://brid.gy/publish/meetup</code></li>
<li><code>https://brid.gy/publish/twitter</code></li>
</ul>
Your post HTML must also include that same
Expand All @@ -823,6 +825,7 @@ <h3 id="publish">Publishing</h3>
<pre>&lt;a href="<a href='https://brid.gy/publish/flickr'>https://brid.gy/publish/flickr</a>"&gt;&lt;/a&gt;
&lt;a href="<a href='https://brid.gy/publish/github'>https://brid.gy/publish/github</a>"&gt;&lt;/a&gt;
&lt;a href="<a href='https://brid.gy/publish/mastodon'>https://brid.gy/publish/mastodon</a>"&gt;&lt;/a&gt;
&lt;a href="<a href='https://brid.gy/publish/meetup'>https://brid.gy/publish/meetup</a>"&gt;&lt;/a&gt;
&lt;a href="<a href='https://brid.gy/publish/twitter'>https://brid.gy/publish/twitter</a>"&gt;&lt;/a&gt;
</pre>
</p>
Expand Down Expand Up @@ -1023,6 +1026,19 @@ <h3 id="publish">Publishing</h3>
</pre>
</li>

<li id="meetup-rsvp" class="question">How do I RSVP to an event on Meetup?</li>
<li class="answer">
<p>Just post an RSVP to the event's URL! For example:</p>
<pre>
&lt;div class="<span class='keyword'>h-entry</span>"&gt;
&lt;p&gt;
&lt;data class="<span class='keyword'>p-rsvp</span>" value="no"&gt;I will not be attending&lt;/data&gt;
&lt;a class="<span class="keyword">u-in-reply-to</span>" href="<a href="https://www.meetup.com/PHPMiNDS-in-Nottingham/events/264008439/">https://www.meetup.com/PHPMiNDS-in-Nottingham/events/264008439/</a>"&gt;https://www.meetup.com/PHPMiNDS-in-Nottingham/events/264008439/&lt;/a&gt;
&lt;/p&gt;
&lt;/div&gt;
</pre>
</li>

<li id="omit-link" class="question">Can I disable the link back to my post?</li>
<li class="answer">
<p>Sure! By default, Bridgy includes a link back to your post. If you're using
Expand Down
1 change: 1 addition & 0 deletions templates/index.html
Expand Up @@ -15,6 +15,7 @@
{{ sources['flickr'].button_html('listen')|safe }}
{{ sources['github'].button_html('listen')|safe }}
{{ sources['mastodon'].button_html('listen')|safe }}
{{ sources['meetup'].button_html('publish')|safe }}
</div>

<hr />
Expand Down
1 change: 1 addition & 0 deletions templates/meetup_user.html
@@ -0,0 +1 @@
{% extends "social_user.html" %}
2 changes: 1 addition & 1 deletion tests/test_publish.py
Expand Up @@ -296,7 +296,7 @@ def test_bad_target_url(self):
'https://brid.gy/publish/instagram',
):
self.assert_error(
'Target must be brid.gy/publish/{flickr,github,mastodon,twitter}',
'Target must be brid.gy/publish/{flickr,github,mastodon,meetup,twitter}',
target=target)

def test_source_url_redirects(self):
Expand Down

0 comments on commit 8d9fe02

Please sign in to comment.