# coding=utf-8
"""Meetup.com source class.
"""
from . import source
import datetime
import logging
from oauth_dropins import meetup
from oauth_dropins.webutil import util
import re
import urllib.parse, urllib.request
API_BASE = 'https://api.meetup.com'
API_RSVPS = '/%(urlname)s/events/%(event_id)s/rsvps'
# We don't want to be too strict here with what a valid urlname and event_id
# are because Meetup.com haven't documented it too well, and it may change
EVENT_URL_RE = re.compile(r'https://(www\.|)meetup.com/([^/]+)/events/([^/]+)/?$')
[docs]class Meetup(source.Source):
DOMAIN = 'meetup.com'
NAME = 'Meetup.com'
URL_CANONICALIZER = util.UrlCanonicalizer(
domain=DOMAIN,
approve=EVENT_URL_RE)
[docs] @classmethod
def embed_post(cls, obj):
"""Returns the HTML string for embedding an RSVP from Meetup.com.
Args:
obj:
obj: AS1 dict with at least url, and optionally also content.
Returns: string, HTML
"""
return '<span class="verb">RSVP %s</span> to <a href="%s">this event</a>.' % (
source.object_type(obj)[5:],
source.Source.base_object(cls, obj)['url']
)
[docs] def __init__(self, access_token):
self.access_token = access_token
pass
[docs] def create(self, obj, include_link=source.OMIT_LINK, ignore_formatting=False):
return self._create(obj, False, include_link, ignore_formatting)
[docs] def preview_create(self, obj, include_link=source.OMIT_LINK, ignore_formatting=False):
return self._create(obj, True, include_link, ignore_formatting)
def post_rsvp(self, urlname, event_id, response):
url = API_BASE + API_RSVPS % {
'urlname': urlname,
'event_id': event_id,
}
params = 'response=%(response)s' % {
'response': response,
}
logging.debug('Creating RSVP=%(rsvp)s for %(urlname)s %(event_id)s' % {
'rsvp': response,
'urlname': urlname,
'event_id': event_id,
})
return meetup.urlopen_bearer_token(url, self.access_token, data=params)
def _create(self, obj, preview=False, include_link=source.OMIT_LINK, ignore_formatting=False):
if not preview in (False, True):
return self.return_error('Invalid Preview parameter, must be True or False')
verb = source.object_type(obj)
response = None
if verb == 'rsvp-yes':
response = 'yes'
elif verb == 'rsvp-no':
response = 'no'
elif verb == 'rsvp-maybe' or verb == 'rsvp-interested':
return self.return_error('Meetup.com does not support %(verb)s' % {'verb': verb})
else:
return self.return_error('Meetup.com syndication does not support %(verb)s' % {'verb': verb})
# parse the in-reply-to out
url_containers = self.base_object(obj)
if not url_containers:
return self.return_error('RSVP not to Meetup.com or missing in-reply-to')
if not 'url' in url_containers:
return self.return_error('missing an in-reply-to')
event_url = url_containers['url']
if not event_url:
return self.return_error('missing an in-reply-to')
event_url = self.URL_CANONICALIZER(event_url)
if not event_url:
return self.return_error('Invalid Meetup.com event URL')
parsed_url_part = EVENT_URL_RE.match(event_url)
if not parsed_url_part:
return self.return_error('Invalid Meetup.com event URL')
urlname = parsed_url_part.group(2)
event_id = parsed_url_part.group(3)
if preview:
return source.creation_result(description=Meetup.embed_post(obj))
post_url = obj.get('url')
if not post_url:
return self.return_error('Missing the post\'s url')
create_resp = {
'url': '%(event_url)s#rsvp-by-%(url)s' % {
'event_url': event_url,
'url': urllib.parse.quote_plus(post_url),
},
'type': 'rsvp'
}
resp = self.post_rsvp(urlname, event_id, response)
logging.debug('Response: %s %s', resp.getcode(), resp.read())
return source.creation_result(create_resp)
def return_error(self, msg):
return source.creation_result(abort=True, error_plain=msg, error_html=msg)
[docs] def user_to_actor(self, user):
"""Converts a user to an actor.
Args:
user: dict, a decoded JSON Meetup user
Returns:
an ActivityStreams actor dict, ready to be JSON-encoded
"""
user_id = user.get('id')
user_id_str = str(user_id)
published_s = round(user.get('joined') / 1000)
published_dt = datetime.datetime.utcfromtimestamp(published_s)
photo = user.get('photo', {}).get('photo_link', 'https://secure.meetupstatic.com/img/noPhoto_80.png')
return util.trim_nulls({
'objectType': 'person',
'displayName': user.get('name'),
'image': {'url': photo},
'id': self.tag_uri(user_id_str),
# numeric_id is our own custom field that always has the source's numeric
# user id, if available.
'numeric_id': user_id,
'published': published_dt.isoformat(),
'url': self.user_url(user_id_str),
'urls': None,
'location': {'displayName': user.get('localized_country_name')},
'username': user_id_str,
'description': None,
})
[docs] def user_url(self, user_id):
"""Returns the URL for a user's profile."""
return 'https://www.meetup.com/members/%s/' % (user_id)