Compare commits

...

1 Commits

Author SHA1 Message Date
Derek ce9ec54a4e Better youtube chat integration via pytchat (KaitoCross's fork) 2023-07-19 13:21:17 -04:00
3 changed files with 149 additions and 213 deletions

View File

@ -29,6 +29,7 @@ torch = "==1.13.*"
simpleobsws = "*"
python-socketio = {extras = ["client"], version = "*"}
aioprocessing = "*"
pytchat = { git="https://github.com/KaitoCross/pytchat.git" }
[requires]
python_version = "3.10"

162
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "3b4cd1eb9c78699755944e2fe143b99de80c19de02770e35498b2ee7c781afa5"
"sha256": "804193102d3647eb91facddeec5be5af9702c6ffc9506762960daee131f00d44"
},
"pipfile-spec": 6,
"requires": {
@ -56,6 +56,14 @@
"markers": "python_version >= '3.3'",
"version": "==0.3.2"
},
"anyio": {
"hashes": [
"sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780",
"sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"
],
"markers": "python_version >= '3.7'",
"version": "==3.7.1"
},
"appdirs": {
"hashes": [
"sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
@ -271,11 +279,11 @@
},
"click": {
"hashes": [
"sha256:4be4b1af8d665c6d942909916d31a213a106800c47d0eeba73d34da3cbc11367",
"sha256:e576aa487d679441d7d30abb87e1b43d24fc53bffb8758443b1a9e1cee504548"
"sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd",
"sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"
],
"index": "pypi",
"version": "==8.1.5"
"version": "==8.1.6"
},
"contourpy": {
"hashes": [
@ -402,6 +410,14 @@
],
"version": "==0.6.2"
},
"exceptiongroup": {
"hashes": [
"sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5",
"sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"
],
"markers": "python_version < '3.11'",
"version": "==1.1.2"
},
"flask": {
"hashes": [
"sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0",
@ -575,7 +591,6 @@
"sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d",
"sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"
],
"markers": "python_full_version >= '3.6.1'",
"version": "==4.1.0"
},
"hpack": {
@ -586,6 +601,25 @@
"markers": "python_full_version >= '3.6.1'",
"version": "==4.0.0"
},
"httpcore": {
"hashes": [
"sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888",
"sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"
],
"markers": "python_version >= '3.7'",
"version": "==0.17.3"
},
"httpx": {
"extras": [
"http2"
],
"hashes": [
"sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd",
"sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"
],
"markers": "python_version >= '3.7'",
"version": "==0.24.1"
},
"humanize": {
"hashes": [
"sha256:7ca0e43e870981fa684acb5b062deb307218193bca1a01f2b2676479df849b3a",
@ -1296,11 +1330,11 @@
},
"platformdirs": {
"hashes": [
"sha256:cec7b889196b9144d088e4c57d9ceef7374f6c39694ad1577a0aab50d27ea28c",
"sha256:f87ca4fcff7d2b0f81c6a748a77973d7af0f4d526f98f308477c3c436c74d528"
"sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421",
"sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"
],
"markers": "python_version >= '3.7'",
"version": "==3.8.1"
"version": "==3.9.1"
},
"pooch": {
"hashes": [
@ -1426,6 +1460,10 @@
"markers": "python_version >= '3'",
"version": "==0.3.4"
},
"pytchat": {
"git": "https://github.com/KaitoCross/pytchat.git",
"ref": "d74b4d69c96b6bbc6ac553079485bcd2cbebced8"
},
"python-crfsuite": {
"hashes": [
"sha256:002fb06c68667653f64bc08b53f62fe89284841109f18814c87f06ebbde1f585",
@ -1551,49 +1589,49 @@
},
"pyyaml": {
"hashes": [
"sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
"sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
"sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
"sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
"sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
"sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
"sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
"sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
"sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
"sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
"sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
"sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
"sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782",
"sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
"sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
"sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
"sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
"sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
"sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1",
"sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
"sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
"sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
"sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
"sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
"sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
"sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d",
"sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
"sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
"sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7",
"sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
"sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
"sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
"sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358",
"sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
"sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
"sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
"sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
"sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f",
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
"sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc",
"sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741",
"sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206",
"sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27",
"sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595",
"sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62",
"sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98",
"sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696",
"sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d",
"sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867",
"sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47",
"sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486",
"sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6",
"sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3",
"sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007",
"sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938",
"sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c",
"sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735",
"sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d",
"sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba",
"sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8",
"sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5",
"sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd",
"sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3",
"sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0",
"sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515",
"sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c",
"sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c",
"sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924",
"sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34",
"sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43",
"sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859",
"sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673",
"sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a",
"sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab",
"sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa",
"sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c",
"sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585",
"sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d",
"sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"
],
"markers": "python_version >= '3.6'",
"version": "==6.0"
"version": "==6.0.1"
},
"quart": {
"hashes": [
@ -1810,6 +1848,14 @@
],
"version": "==0.2.4"
},
"sniffio": {
"hashes": [
"sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101",
"sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"
],
"markers": "python_version >= '3.7'",
"version": "==1.3.0"
},
"soundfile": {
"hashes": [
"sha256:074247b771a181859d2bc1f98b5ebf6d5153d2c397b86ee9e29ba602a8dfe2a6",
@ -1924,11 +1970,11 @@
},
"trainer": {
"hashes": [
"sha256:cdda6c9bd1c25d5471dbd187c79889a57be8da26bf46bcc3e998f9b2f4b539f5",
"sha256:e75cc8590800ae0ae09b8b9ef6f82c56c9713f227f92c53e7beffbc60a5f534e"
"sha256:5206320a4957b3278fcf3e6b7041480b0147d08a6c8fab7e666a74a010188f18",
"sha256:7e091d49d14c14d8c781af217b635a66eb16398aea550d1e8d2d2cb524a34fb7"
],
"markers": "python_version < '3.12' and python_full_version >= '3.6.0'",
"version": "==0.0.27"
"version": "==0.0.28"
},
"tts": {
"hashes": [
@ -2262,10 +2308,10 @@
},
"distlib": {
"hashes": [
"sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46",
"sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"
"sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057",
"sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"
],
"version": "==0.3.6"
"version": "==0.3.7"
},
"idna": {
"hashes": [
@ -2330,11 +2376,11 @@
},
"platformdirs": {
"hashes": [
"sha256:cec7b889196b9144d088e4c57d9ceef7374f6c39694ad1577a0aab50d27ea28c",
"sha256:f87ca4fcff7d2b0f81c6a748a77973d7af0f4d526f98f308477c3c436c74d528"
"sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421",
"sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"
],
"markers": "python_version >= '3.7'",
"version": "==3.8.1"
"version": "==3.9.1"
},
"plette": {
"extras": [

View File

@ -1,120 +1,48 @@
import os
import json
import webbrowser
import time
from enum import Enum, auto
import requests
from itertools import chain
from ovtk_audiencekit.chats import ChatProcess
from ovtk_audiencekit.events.Message import Message, SysMessage, USER_TYPE
from ovtk_audiencekit.events import Message, SysMessage, USER_TYPE
import pytchat
class STATES(Enum):
WAITING_FOR_BROADCAST = auto()
POLLING = auto()
REFRESH = auto()
UNAUTHORIZED = auto()
FAILURE = auto()
WAITING = auto()
READING = auto()
class YoutubeLivePollProcess(ChatProcess):
class CONTROL_MESSAGES(str, Enum):
RESTART = "restart"
class YoutubeProcess(ChatProcess):
def __init__(self, *args, video_id=None, **kwargs):
super().__init__(*args, **kwargs)
GOOGLE_OAUTH_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token'
YOUTUBE_API_URL = 'https://www.googleapis.com/youtube/v3'
def __init__(self, *args, client_secrets_path=None):
if client_secrets_path is None or not os.path.exists(client_secrets_path):
raise ValueError('Missing client secrets')
with open(client_secrets_path, 'r') as f:
client_secrets = json.load(f).get('installed')
if client_secrets is None:
raise ValueError('Malformed client secrets file - missing installed section')
super().__init__(*args)
self._client_secrets = client_secrets
self._state_machine = self.bind_to_states(STATES)
self._video_id = video_id
self._consent_code = None
self._refresh_token = None
self._access_token = None
self._stream_title = None
self._live_chat_id = None
self._page_token = None
self.state = STATES.CONNECTING
self._chat = pytchat.create(video_id=self._video_id)
def normalize_event(self, event):
user_type = USER_TYPE.USER
if event.author.isChatOwner:
user_type = USER_TYPE.OWNER
elif event.author.isChatModerator:
user_type = USER_TYPE.MODERATOR
elif event.author.isChatSponsor:
user_type = USER_TYPE.PATRON
msg = Message(self._name, event.message,
event.author.name, event.author.channelId, user_type,
id=event.id)
return msg
def loop(self, next_state):
if self.state is None:
if os.path.exists('refresh_token.secret'):
with open('refresh_token.secret', 'r') as f:
self._refresh_token = f.read()
return STATES.REFRESH, STATES.WAITING_FOR_BROADCAST
return STATES.UNAUTHORIZED, STATES.WAITING_FOR_BROADCAST
return self._state_machine(self.state, next_state)
def on_unauthorized(self, next_state):
self.request_oauth_consent()
response = self.safe_input('Allow access in your browser and paste response code here: ')
self.setup_oauth_consent(response['token'])
return next_state
def on_failure(self, next_state):
pass
def on_refresh(self, next_state):
self.get_fresh_access_token()
return next_state
def on_waiting_for_broadcast(self, next_state):
response = requests.get(f'{self.__class__.YOUTUBE_API_URL}/liveBroadcasts',
params={'part': 'snippet', 'broadcastStatus': 'active'},
headers={'Authorization': f'Bearer {self._access_token}'})
if (response.status_code == 401):
return STATES.REFRESH, self.state
items = response.json()['items']
if len(items) == 1:
stream_snippet = items[0]['snippet']
self._live_chat_id = stream_snippet['liveChatId']
self._stream_title = stream_snippet['title']
return STATES.POLLING
return 30
def on_polling(self, next_state):
response = requests.get(f'{self.__class__.YOUTUBE_API_URL}/liveChat/messages',
params={'liveChatId': self._live_chat_id,
'part': 'snippet,authorDetails',
'hl': 'en_US',
'pageToken': self._page_token},
headers={'Authorization': f'Bearer {self._access_token}'}
)
if (response.status_code == 401):
return STATES.REFRESH, self.state
if response.status_code == requests.codes.ok:
data = response.json()
self._page_token = data['nextPageToken']
if len(data['items']):
for message in data['items']:
normalized = self.normalize_message(message)
self.publish(normalized)
sleep_milis = max(data['pollingIntervalMillis'], 5000)
return sleep_milis / 1000
else:
print(response.json())
return STATES.FAILURE
def on_state_enter(self, new_state):
status_messages = {
STATES.WAITING_FOR_BROADCAST: 'Waiting for active broadcast...',
STATES.POLLING: f'''Tuning into "{self._stream_title}" - ready to rumble ~\n''',
STATES.UNAUTHORIZED: 'Unauthorized - see terminal',
STATES.FAILURE: 'YouTube API returned a bad status, polling stopped - see terminal',
STATES.READING: 'Connected to channel!',
}
message = status_messages.get(new_state)
@ -122,62 +50,23 @@ class YoutubeLivePollProcess(ChatProcess):
sys_msg = SysMessage(self._name, message)
self.publish(sys_msg)
def process_messages(self, message_type, args, next_state):
if message_type == self.__class__.CONTROL_MESSAGES.RESTART:
if self.state == STATES.POLLING:
self.publish(SysMessage(self._name, 'Restart requested, but service is in healthy state! Ignoring...'))
else:
self.publish(SysMessage(self._name, 'Restarting service...'))
return None, None
def on_waiting(self, next_state):
if self._chat.is_alive():
return STATES.READING
return 5
def normalize_message(self, message):
if message['authorDetails']['isChatOwner']:
author_type = USER_TYPE.OWNER
elif message['authorDetails']['isChatModerator']:
author_type = USER_TYPE.MODERATOR
elif message['authorDetails']['isChatSponsor']:
author_type = USER_TYPE.PATRON
else:
author_type = USER_TYPE.USER
def on_reading(self, next_state):
try:
if not self._chat.is_alive():
return STATES.TIMEOUT
text = message['snippet']['displayMessage']
author_name = message['authorDetails']['displayName']
author_id = message['authorDetails']['channelId']
return Message(self._name, text, author_name, author_id, author_type)
for raw_event in self._chat.get().sync_items():
normalized_event = self.normalize_event(raw_event)
self.publish(normalized_event)
def request_oauth_consent(self):
params = {
'client_id': self._client_secrets['client_id'],
'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob',
'scope': 'https://www.googleapis.com/auth/youtube',
'response_type': 'code',
}
param_str = '&'.join(f'{k}={v}' for (k, v) in params.items())
url = f"{self._client_secrets['auth_uri']}?{param_str}"
webbrowser.open(url)
return 0
except TimeoutError:
return STATES.TIMEOUT
def setup_oauth_consent(self, consent_code):
response = requests.post(self.__class__.GOOGLE_OAUTH_TOKEN_URL, data={
'code': consent_code,
'client_id': self._client_secrets['client_id'],
'client_secret': self._client_secrets['client_secret'],
'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob',
'grant_type': 'authorization_code',
})
response.raise_for_status()
auth = response.json()
with open('refresh_token.secret', 'w') as f:
f.write(auth['refresh_token'])
self._access_token = auth['access_token']
self._refresh_token = auth['refresh_token']
def get_fresh_access_token(self):
response = requests.post(self.__class__.GOOGLE_OAUTH_TOKEN_URL, data={
'client_id': self._client_secrets['client_id'],
'client_secret': self._client_secrets['client_secret'],
'refresh_token': self._refresh_token,
'grant_type': 'refresh_token'
})
response.raise_for_status()
auth = response.json()
self._access_token = auth['access_token']
def on_failure(self, next_state):
return None