forked from snarfed/bridgy
/
instagram.py
159 lines (130 loc) · 5.56 KB
/
instagram.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""Instagram API code and datastore model classes.
Example post ID and links
id: 595990791004231349 or 595990791004231349_247678460
(suffix is user id)
Permalink: http://instagram.com/p/hFYnd7Nha1/
API URL: https://api.instagram.com/v1/media/595990791004231349
Local handler path: /post/instagram/212038/595990791004231349
Example comment ID and links
id: 595996024371549506
No direct API URL or permalink, as far as I can tell. :/
API URL for all comments on that picture:
https://api.instagram.com/v1/media/595990791004231349_247678460/comments
Local handler path:
/comment/instagram/212038/595990791004231349_247678460/595996024371549506
"""
__author__ = ['Ryan Barrett <bridgy@ryanb.org>']
import datetime
import json
import logging
import urlparse
import appengine_config
from granary import instagram as gr_instagram
from granary import microformats2
from granary import source as gr_source
from oauth_dropins import indieauth
# InstagramAuth entities are loaded here
from oauth_dropins import instagram as oauth_instagram
from oauth_dropins.webutil.handlers import TemplateHandler
import webapp2
import models
import util
class Instagram(models.Source):
"""An Instagram account.
The key name is the username.
"""
GR_CLASS = gr_instagram.Instagram
SHORT_NAME = 'instagram'
FAST_POLL = datetime.timedelta(minutes=60)
URL_CANONICALIZER = util.UrlCanonicalizer(
domain=GR_CLASS.DOMAIN,
subdomain='www',
approve=r'https://www.instagram.com/p/[^/?]+/$',
trailing_slash=True,
headers=util.USER_AGENT_HEADER)
# no reject regexp; non-private Instagram post URLs just 404
@staticmethod
def new(handler, auth_entity=None, actor=None, **kwargs):
"""Creates and returns a InstagramPage for the logged in user.
Args:
handler: the current RequestHandler
auth_entity: oauth_dropins.instagram.InstagramAuth
"""
user = json.loads(auth_entity.user_json)
user['actor'] = actor
auth_entity.user_json = json.dumps(user)
auth_entity.put()
username = actor['username']
if not kwargs.get('features'):
kwargs['features'] = ['listen']
urls = microformats2.object_urls(actor)
return Instagram(id=username,
auth_entity=auth_entity.key,
name=actor.get('displayName'),
picture=actor.get('image', {}).get('url'),
url=gr_instagram.Instagram.user_url(username),
domain_urls=urls,
domains=[util.domain_from_link(url) for url in urls],
**kwargs)
def silo_url(self):
"""Returns the Instagram account URL, e.g. https://instagram.com/foo."""
return self.url
def user_tag_id(self):
"""Returns the tag URI for this source, e.g. 'tag:instagram.com:123456'."""
user = json.loads(self.auth_entity.get().user_json)
return self.gr_source.tag_uri(user.get('id') or self.key.id())
def label_name(self):
"""Returns the username."""
return self.key.id()
def get_activities_response(self, *args, **kwargs):
"""Set user_id because scraping requires it."""
kwargs.setdefault('group_id', gr_source.SELF)
kwargs.setdefault('user_id', self.key.id())
return self.gr_source.get_activities_response(*args, **kwargs)
class StartHandler(TemplateHandler):
"""Serves the "Enter your username" form page."""
def template_file(self):
return 'templates/indieauth.html'
class CallbackHandler(indieauth.CallbackHandler, util.Handler):
def finish(self, auth_entity, state=None):
if auth_entity:
user_json = json.loads(auth_entity.user_json)
# find instagram profile URL
urls = user_json.get('rel-me', [])
logging.info('rel-mes: %s', urls)
for url in util.trim_nulls(urls):
if util.domain_from_link(url) == gr_instagram.Instagram.DOMAIN:
username = urlparse.urlparse(url).path.strip('/')
break
else:
self.messages.add(
'No Instagram profile found. Please <a href="https://indieauth.com/setup">'
'add an Instagram rel-me link</a>, then try again.')
return self.redirect_home_or_user_page(state)
# check that instagram profile links to web site
actor = gr_instagram.Instagram(scrape=True).get_actor(username)
if not actor:
self.messages.add(
"Couldn't find Instagram user '%s'. Please check your site's rel-me "
"link and your Instagram account." % username)
return self.redirect_home_or_user_page(state)
canonicalize = util.UrlCanonicalizer(redirects=False)
website = canonicalize(auth_entity.key.id())
urls = [canonicalize(u) for u in microformats2.object_urls(actor)]
logging.info('Looking for %s in %s', website, urls)
if website not in urls:
self.messages.add("Please add %s to your Instagram profile's website or "
'bio field and try again.' % website)
return self.redirect_home_or_user_page(state)
# check that the instagram account is public
if not gr_source.Source.is_public(actor):
self.messages.add('Your Instagram account is private. '
'Bridgy only supports public accounts.')
return self.redirect_home_or_user_page(state)
source = self.maybe_add_or_delete_source(Instagram, auth_entity, state,
actor=actor)
application = webapp2.WSGIApplication([
('/instagram/start', StartHandler),
('/instagram/indieauth', indieauth.StartHandler.to('/instagram/callback')),
('/instagram/callback', CallbackHandler),
], debug=appengine_config.DEBUG)