This commit is contained in:
Iliyan Angelov
2025-09-14 23:24:25 +03:00
commit c67067a2a4
71311 changed files with 6800714 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
import requests
from allauth.socialaccount import app_settings
from allauth.socialaccount.providers.oauth2.client import (
OAuth2Client,
OAuth2Error,
)
from .provider import UntappdProvider
class UntappdOAuth2Client(OAuth2Client):
"""
Custom client because Untappd:
* uses redirect_url instead of redirect_uri
* nests access_token inside an extra 'response' object
"""
def get_access_token(self, code, pkce_code_verifier=None):
data = {
"client_id": self.consumer_key,
"redirect_url": self.callback_url,
"grant_type": "authorization_code",
"response_type": "code",
"client_secret": self.consumer_secret,
"code": code,
}
params = None
self._strip_empty_keys(data)
url = self.access_token_url
if self.access_token_method == "GET":
params = data
data = None
if data and pkce_code_verifier:
data["code_verifier"] = pkce_code_verifier
# Allow custom User Agent to comply with Untappd API
settings = app_settings.PROVIDERS.get(UntappdProvider.id, {})
headers = {"User-Agent": settings.get("USER_AGENT", "django-allauth")}
# TODO: Proper exception handling
resp = requests.request(
self.access_token_method,
url,
params=params,
data=data,
headers=headers,
)
access_token = None
if resp.status_code == 200:
access_token = resp.json()["response"]
if not access_token or "access_token" not in access_token:
raise OAuth2Error("Error retrieving access token: %s" % resp.content)
return access_token

View File

@@ -0,0 +1,54 @@
from django.urls import reverse
from allauth.account.models import EmailAddress
from allauth.socialaccount.providers.base import ProviderAccount
from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider
class UntappdAccount(ProviderAccount):
def get_profile_url(self):
return self.account.extra_data.get("untappd_url")
def get_avatar_url(self):
return self.account.extra_data.get("user_avatar")
def to_str(self):
dflt = super(UntappdAccount, self).to_str()
return self.account.extra_data.get("user_name", dflt)
class UntappdProvider(OAuth2Provider):
id = "untappd"
name = "Untappd"
account_class = UntappdAccount
def get_auth_params(self, request, action):
params = super(UntappdProvider, self).get_auth_params(request, action)
# Untappd uses redirect_url instead of redirect_uri
params["redirect_url"] = request.build_absolute_uri(
reverse(self.id + "_callback")
)
return params
def extract_uid(self, data):
return str(data["response"]["user"]["uid"])
def extract_common_fields(self, data):
user = data["response"]["user"]
return dict(
username=user["user_name"],
name=user["first_name"] + " " + user["last_name"],
)
def extract_email_addresses(self, data):
ret = [
EmailAddress(
email=data["response"]["user"]["settings"]["email_address"],
verified=True,
primary=True,
)
]
return ret
provider_classes = [UntappdProvider]

View File

@@ -0,0 +1,99 @@
from allauth.socialaccount.tests import OAuth2TestsMixin
from allauth.tests import MockedResponse, TestCase
from .provider import UntappdProvider
class UntappdTests(OAuth2TestsMixin, TestCase):
provider_id = UntappdProvider.id
def get_login_response_json(self, with_refresh_token=True):
return """
{
"meta": {
"http_code": 200
},
"response": {
"access_token": "testac"
}
}"""
def get_mocked_response(self):
return MockedResponse(
200,
"""
{
"meta":{
"code":200,
"response_time":{
"time":0.29,
"measure":"seconds"
},
"init_time":{
"time":0.011,
"measure":"seconds"
}
},
"notifications":{
"type":"notifications",
"unread_count":{
"comments":0,
"toasts":0,
"friends":0,
"messages":0,
"news":0
}
},
"response":{
"user":{
"uid":123456,
"id":123456,
"user_name":"groovecoder",
"first_name":"",
"last_name":"",
"user_avatar":"https:\\/\\/gravatar.com\\/avatar\\/ec25d046746de3be33779256f6957d8f?size=100&d=https%3A%2F%2Funtappd.akamaized.net%2Fsite%2Fassets%2Fimages%2Fdefault_avatar_v2.jpg%3Fv%3D1",
"user_avatar_hd":"https:\\/\\/gravatar.com\\/avatar\\/ec25d046746de3be33779256f6957d8f?size=125&d=https%3A%2F%2Funtappd.akamaized.net%2Fsite%2Fassets%2Fimages%2Fdefault_avatar_v2.jpg%3Fv%3D1",
"user_cover_photo":"https:\\/\\/untappd.akamaized.net\\/site\\/assets\\/v3\\/images\\/cover_default.jpg",
"user_cover_photo_offset":0,
"is_private":0,
"location":"Testville",
"url":"",
"bio":"",
"is_supporter":0,
"relationship":"self",
"untappd_url":"http:\\/\\/untappd.com\\/user\\/testuser",
"account_type":"user",
"stats":{
"total_badges":43,
"total_friends":43,
"total_checkins":73,
"total_beers":61,
"total_created_beers":1,
"total_followings":9,
"total_photos":31
},
"recent_brews":{},
"checkins":{},
"media":{},
"contact":{},
"date_joined":"Tue, 11 Dec 2012 14:27:53 +0000",
"settings":{
"badge":{
"badges_to_facebook":1,
"badges_to_twitter":1
},
"checkin":{
"checkin_to_facebook":0,
"checkin_to_twitter":0,
"checkin_to_foursquare":0
},
"navigation":{
"default_to_checkin":0
},
"email_address":"test@example.com"
}
}
}
}
""",
)

View File

@@ -0,0 +1,6 @@
from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
from .provider import UntappdProvider
urlpatterns = default_urlpatterns(UntappdProvider)

View File

@@ -0,0 +1,30 @@
import requests
from allauth.socialaccount.providers.oauth2.views import (
OAuth2Adapter,
OAuth2CallbackView,
OAuth2LoginView,
)
from .client import UntappdOAuth2Client
from .provider import UntappdProvider
class UntappdOAuth2Adapter(OAuth2Adapter):
client_class = UntappdOAuth2Client
provider_id = UntappdProvider.id
access_token_url = "https://untappd.com/oauth/authorize/"
access_token_method = "GET"
authorize_url = "https://untappd.com/oauth/authenticate/"
user_info_url = "https://api.untappd.com/v4/user/info/"
supports_state = False
def complete_login(self, request, app, token, **kwargs):
resp = requests.get(self.user_info_url, params={"access_token": token.token})
extra_data = resp.json()
# TODO: get and store the email from the user info json
return self.get_provider().sociallogin_from_response(request, extra_data)
oauth2_login = OAuth2LoginView.adapter_view(UntappdOAuth2Adapter)
oauth2_callback = OAuth2CallbackView.adapter_view(UntappdOAuth2Adapter)