BlenderKit: Oauth browser login. No more hassle with API keys for the users. Needs testing.
renamed get_bkit_url to get_api_url task queue is a new simple module to do tasks, more timed tasks should come here instead of being in assetbar as of now. (download, search checks and more)
This commit is contained in:
parent
d2808959bb
commit
e16c55a110
|
@ -41,9 +41,11 @@ if "bpy" in locals():
|
|||
importlib.reload(bg_blender)
|
||||
importlib.reload(paths)
|
||||
importlib.reload(utils)
|
||||
importlib.reload(oauth)
|
||||
importlib.reload(tasks_queue)
|
||||
else:
|
||||
from blenderkit import asset_inspector, search, download, upload, ratings, autothumb, ui, bg_blender, paths, utils, \
|
||||
overrides, ui_panels, categories
|
||||
overrides, ui_panels, categories, oauth, tasks_queue
|
||||
|
||||
import os
|
||||
import math
|
||||
|
@ -71,7 +73,6 @@ from bpy.types import (
|
|||
PropertyGroup,
|
||||
)
|
||||
|
||||
|
||||
# logging.basicConfig(filename = 'blenderkit.log', level = logging.INFO,
|
||||
# format = ' %(asctime)s:%(filename)s:%(funcName)s:%(lineno)d:%(message)s')
|
||||
|
||||
|
@ -1208,9 +1209,8 @@ class BlenderKitAddonPreferences(AddonPreferences):
|
|||
# this must match the addon name, use '__package__'
|
||||
# when defining this in a submodule of a python package.
|
||||
bl_idname = __name__
|
||||
from os.path import expanduser
|
||||
home = expanduser("~")
|
||||
default_global_dict = home + os.sep + 'blenderkit_data'
|
||||
|
||||
default_global_dict = paths.default_global_dict()
|
||||
|
||||
api_key: StringProperty(
|
||||
name="BlenderKit API Key",
|
||||
|
@ -1220,6 +1220,20 @@ class BlenderKitAddonPreferences(AddonPreferences):
|
|||
update=utils.save_prefs
|
||||
)
|
||||
|
||||
api_key_refresh: StringProperty(
|
||||
name="BlenderKit refresh API Key",
|
||||
description="API key used to refresh the token regularly.",
|
||||
default="",
|
||||
subtype="PASSWORD",
|
||||
update=utils.save_prefs
|
||||
)
|
||||
|
||||
login_attempt: BoolProperty(
|
||||
name="Login/Signup attempt",
|
||||
description="When this is on, BlenderKit is trying to connect and login.",
|
||||
default=False
|
||||
)
|
||||
|
||||
global_dir: StringProperty(
|
||||
name="Global Files Directory",
|
||||
description="Global storage for your assets, will use subdirectories for the contents",
|
||||
|
@ -1389,6 +1403,8 @@ def register():
|
|||
bpy.app.handlers.load_post.append(scene_load)
|
||||
utils.load_prefs()
|
||||
overrides.register_overrides()
|
||||
oauth.register()
|
||||
tasks_queue.register()
|
||||
|
||||
|
||||
def unregister():
|
||||
|
@ -1403,6 +1419,8 @@ def unregister():
|
|||
ui_panels.unregister_ui_panels()
|
||||
bg_blender.unregister()
|
||||
overrides.unregister_overrides()
|
||||
oauth.unregister()
|
||||
tasks_queue.unregister()
|
||||
|
||||
del bpy.types.Scene.blenderkit_models
|
||||
del bpy.types.Scene.blenderkit_scene
|
||||
|
|
|
@ -60,7 +60,7 @@ def copy_categories():
|
|||
def fetch_categories(API_key):
|
||||
BLENDERKIT_API_MAIN = "https://www.blenderkit.com/api/v1/"
|
||||
|
||||
url = paths.get_bkit_url() + 'categories/'
|
||||
url = paths.get_api_url() + 'categories/'
|
||||
|
||||
headers = utils.get_headers(API_key)
|
||||
|
||||
|
|
|
@ -174,7 +174,7 @@ def report_usages():
|
|||
api_key = user_preferences.api_key
|
||||
sid = get_scene_id()
|
||||
headers = utils.get_headers(api_key)
|
||||
url = paths.get_bkit_url() + paths.BLENDERKIT_REPORT_URL
|
||||
url = paths.get_api_url() + paths.BLENDERKIT_REPORT_URL
|
||||
|
||||
assets = {}
|
||||
asset_obs = []
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
import bpy
|
||||
|
||||
import json
|
||||
import webbrowser
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
import requests
|
||||
import threading
|
||||
from blenderkit import tasks_queue, utils, paths
|
||||
|
||||
CLIENT_ID = "IdFRwa3SGA8eMpzhRVFMg5Ts8sPK93xBjif93x0F"
|
||||
PORTS = [62485, 1234]
|
||||
|
||||
|
||||
class SimpleOAuthAuthenticator(object):
|
||||
def __init__(self, server_url, client_id, ports):
|
||||
self.server_url = server_url
|
||||
self.client_id = client_id
|
||||
self.ports = ports
|
||||
|
||||
def _get_tokens(self, authorization_code=None, refresh_token=None, grant_type="authorization_code"):
|
||||
data = {
|
||||
"grant_type": grant_type,
|
||||
"state": "random_state_string",
|
||||
"client_id": self.client_id,
|
||||
"scopes": "read write",
|
||||
}
|
||||
if authorization_code:
|
||||
data['code'] = authorization_code
|
||||
if refresh_token:
|
||||
data['refresh_token'] = refresh_token
|
||||
|
||||
response = requests.post(
|
||||
'%s/o/token/' % self.server_url,
|
||||
data=data
|
||||
)
|
||||
if response.status_code != 200:
|
||||
return None, None
|
||||
refresh_token = json.loads(response.content)['refresh_token']
|
||||
access_token = json.loads(response.content)['access_token']
|
||||
return access_token, refresh_token
|
||||
|
||||
def get_new_token(self):
|
||||
class HTTPServerHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
if 'code' in self.path:
|
||||
self.auth_code = self.path.split('=')[1]
|
||||
# Display to the user that they no longer need the browser window
|
||||
self.wfile.write(bytes('<html><h1>You may now close this window.</h1></html>', 'utf-8'))
|
||||
qs = parse_qs(urlparse(self.path).query)
|
||||
self.server.authorization_code = qs['code'][0]
|
||||
|
||||
for port in self.ports:
|
||||
try:
|
||||
httpServer = HTTPServer(('localhost', port), HTTPServerHandler)
|
||||
except OSError:
|
||||
continue
|
||||
break
|
||||
webbrowser.open_new(
|
||||
"%s/o/authorize?client_id=%s&state=random_state_string&response_type=code&"
|
||||
"redirect_uri=http://localhost:%s/consumer/exchange/" % (self.server_url, self.client_id, port),
|
||||
)
|
||||
|
||||
httpServer.handle_request()
|
||||
authorization_code = httpServer.authorization_code
|
||||
return self._get_tokens(authorization_code=authorization_code)
|
||||
|
||||
def get_refreshed_token(self, refresh_token):
|
||||
return self._get_tokens(refresh_token=refresh_token, grant_type="refresh_token")
|
||||
|
||||
|
||||
def login_thread():
|
||||
thread = threading.Thread(target=login, args=([]), daemon=True)
|
||||
thread.start()
|
||||
|
||||
|
||||
def login():
|
||||
authenticator = SimpleOAuthAuthenticator(server_url=paths.get_bkit_url(), client_id=CLIENT_ID, ports=PORTS)
|
||||
auth_token, refresh_token = authenticator.get_new_token()
|
||||
print('tokens retrieved')
|
||||
tasks_queue.tasks_queue.put('blenderkit.oauth.write_tokens("%s", "%s")' % (auth_token, refresh_token))
|
||||
|
||||
|
||||
def refresh_token_thread():
|
||||
preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
||||
if len(preferences.api_key_refresh) > 0:
|
||||
thread = threading.Thread(target=refresh_token, args=([preferences.api_key_refresh]), daemon=True)
|
||||
thread.start()
|
||||
|
||||
|
||||
def refresh_token(api_key_refresh):
|
||||
authenticator = SimpleOAuthAuthenticator(server_url=paths.get_bkit_url(), client_id=CLIENT_ID, ports=PORTS)
|
||||
auth_token, refresh_token = authenticator.get_refreshed_token(api_key_refresh)
|
||||
tasks_queue.tasks_queue.put('blenderkit.oauth.write_tokens("%s", "%s")' % (auth_token, refresh_token))
|
||||
|
||||
|
||||
def write_tokens(auth_token, refresh_token):
|
||||
print('writing tokens?')
|
||||
preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
||||
preferences.api_key = auth_token
|
||||
preferences.api_key_refresh = refresh_token
|
||||
preferences.login_attempt = False
|
||||
props = utils.get_search_props()
|
||||
props.report = 'Login success!'
|
||||
|
||||
|
||||
class RegisterLoginOnline(bpy.types.Operator):
|
||||
"""Bring linked object hierarchy to scene and make it editable."""
|
||||
|
||||
bl_idname = "wm.blenderkit_login"
|
||||
bl_label = "BlenderKit login or signup"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
||||
preferences.login_attempt = True
|
||||
login_thread()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class CancelLoginOnline(bpy.types.Operator):
|
||||
"""Cancel login attempt."""
|
||||
|
||||
bl_idname = "wm.blenderkit_login_cancel"
|
||||
bl_label = "BlenderKit login cancel"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
||||
preferences.login_attempt = False
|
||||
return {'FINISHED'}
|
||||
|
||||
classess = (
|
||||
RegisterLoginOnline,
|
||||
CancelLoginOnline,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
for c in classess:
|
||||
bpy.utils.register_class(c)
|
||||
|
||||
|
||||
def unregister():
|
||||
for c in classess:
|
||||
bpy.utils.unregister_class(c)
|
|
@ -18,9 +18,10 @@
|
|||
|
||||
import bpy, os, sys
|
||||
|
||||
BLENDERKIT_API_LOCAL = "http://localhost:8001/api/v1/"
|
||||
BLENDERKIT_API_MAIN = "https://www.blenderkit.com/api/v1/"
|
||||
BLENDERKIT_API_DEVEL = "https://devel.blenderkit.com/api/v1/"
|
||||
BLENDERKIT_LOCAL = "http://localhost:8001"
|
||||
BLENDERKIT_MAIN = "https://www.blenderkit.com"
|
||||
BLENDERKIT_DEVEL = "https://devel.blenderkit.com"
|
||||
BLENDERKIT_API = "/api/v1/"
|
||||
BLENDERKIT_REPORT_URL = "usage_report/"
|
||||
BLENDERKIT_USER_ASSETS = "https://www.blenderkit.com/my-assets"
|
||||
BLENDERKIT_PLANS = "https://www.blenderkit.com/plans/pricing/"
|
||||
|
@ -38,14 +39,24 @@ def get_bkit_url():
|
|||
d = bpy.app.debug_value
|
||||
# d = 2
|
||||
if d == 1:
|
||||
url = BLENDERKIT_API_LOCAL
|
||||
url = BLENDERKIT_LOCAL
|
||||
elif d == 2:
|
||||
url = BLENDERKIT_API_DEVEL
|
||||
url = BLENDERKIT_DEVEL
|
||||
else:
|
||||
url = BLENDERKIT_API_MAIN
|
||||
url = BLENDERKIT_MAIN
|
||||
return url
|
||||
|
||||
|
||||
def get_api_url():
|
||||
return get_bkit_url() + BLENDERKIT_API
|
||||
|
||||
|
||||
def default_global_dict():
|
||||
from os.path import expanduser
|
||||
home = expanduser("~")
|
||||
return home + os.sep + 'blenderkit_data'
|
||||
|
||||
|
||||
def get_categories_filepath():
|
||||
tempdir = get_temp_dir()
|
||||
return os.path.join(tempdir, 'categories.json')
|
||||
|
|
|
@ -90,7 +90,7 @@ def upload_rating(asset):
|
|||
|
||||
bkit_ratings = asset.bkit_ratings
|
||||
# print('rating asset', asset_data['name'], asset_data['asset_base_id'])
|
||||
url = paths.get_bkit_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
|
||||
url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
|
||||
|
||||
ratings = [
|
||||
]
|
||||
|
@ -103,7 +103,7 @@ def upload_rating(asset):
|
|||
thread = threading.Thread(target=uplaod_rating_thread, args=(url, ratings, headers))
|
||||
thread.start()
|
||||
|
||||
url = paths.get_bkit_url() + 'assets/' + asset['asset_data']['id'] + '/review'
|
||||
url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/review'
|
||||
|
||||
reviews = {
|
||||
'reviewText': bkit_ratings.rating_compliments,
|
||||
|
|
|
@ -24,8 +24,9 @@ if "bpy" in locals():
|
|||
imp.reload(categories)
|
||||
imp.reload(ui)
|
||||
imp.reload(version_checker)
|
||||
imp.reload(oauth)
|
||||
else:
|
||||
from blenderkit import paths, utils, categories, ui, version_checker
|
||||
from blenderkit import paths, utils, categories, ui, oauth, version_checker
|
||||
|
||||
import blenderkit
|
||||
from bpy.app.handlers import persistent
|
||||
|
@ -79,7 +80,7 @@ def scene_load(context):
|
|||
wm = bpy.context.window_manager
|
||||
fetch_server_data()
|
||||
# following doesn't necessarilly happen if version isn't checked yet or similar, first run.
|
||||
wm['bkit_update'] = version_checker.compare_versions(blenderkit)
|
||||
# wm['bkit_update'] = version_checker.compare_versions(blenderkit)
|
||||
utils.load_categories()
|
||||
|
||||
|
||||
|
@ -89,6 +90,7 @@ def fetch_server_data():
|
|||
url = paths.BLENDERKIT_ADDON_URL
|
||||
api_key = user_preferences.api_key
|
||||
# version_checker.check_version_thread(url, api_key, blenderkit)
|
||||
oauth.refresh_token_thread()
|
||||
categories.fetch_categories_thread(api_key)
|
||||
|
||||
|
||||
|
@ -534,7 +536,7 @@ class Searcher(threading.Thread):
|
|||
params['get_next'] = False
|
||||
if not params['get_next']:
|
||||
# build a new request
|
||||
url = paths.get_bkit_url() + 'search/'
|
||||
url = paths.get_api_url() + 'search/'
|
||||
|
||||
nquery = {
|
||||
# 'tags': query['keywords'],
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import bpy
|
||||
|
||||
import queue
|
||||
|
||||
import blenderkit
|
||||
|
||||
tasks_queue = queue.Queue()
|
||||
|
||||
def every_2_seconds():
|
||||
while not tasks_queue.empty():
|
||||
print('as a task: ')
|
||||
fstring = tasks_queue.get()
|
||||
eval(fstring)
|
||||
return 2.0
|
||||
|
||||
def register():
|
||||
bpy.app.timers.register(every_2_seconds)
|
||||
|
||||
def unregister():
|
||||
bpy.app.timers.unregister(every_2_seconds)
|
|
@ -523,13 +523,17 @@ class VIEW3D_PT_blenderkit_unified(Panel):
|
|||
row.prop(ui_props, 'asset_type', expand=True, icon_only=True)
|
||||
|
||||
w = context.region.width
|
||||
if user_preferences.login_attempt:
|
||||
layout.label(text = 'Login through browser')
|
||||
layout.label(text = 'in progress.')
|
||||
layout.operator("wm.blenderkit_login_cancel", text = "Cancel", icon = 'CANCEL')
|
||||
return
|
||||
|
||||
if len(user_preferences.api_key) < 35 and user_preferences.asset_counter >25:
|
||||
op = layout.operator("wm.url_open", text="Register online",
|
||||
icon='QUESTION')
|
||||
op.url = paths.BLENDERKIT_SIGNUP_URL
|
||||
layout.label(text='Paste your API Key:')
|
||||
layout.prop(user_preferences, 'api_key', text='')
|
||||
if len(user_preferences.api_key) < 20 and user_preferences.asset_counter >-10:
|
||||
layout.operator("wm.blenderkit_login", text="Login/ Sign up",
|
||||
icon='URL')
|
||||
# layout.label(text='Paste your API Key:')
|
||||
# layout.prop(user_preferences, 'api_key', text='')
|
||||
layout.separator()
|
||||
elif bpy.data.filepath == '':
|
||||
|
||||
|
|
|
@ -465,7 +465,7 @@ def mark_for_validation(self, context, asset_type):
|
|||
"verificationStatus": "ready"
|
||||
}
|
||||
|
||||
url = paths.get_bkit_url() + 'assets/'
|
||||
url = paths.get_api_url() + 'assets/'
|
||||
|
||||
headers = utils.get_headers(user_preferences.api_key)
|
||||
|
||||
|
@ -559,7 +559,7 @@ def start_upload(self, context, asset_type, as_new, metadata_only):
|
|||
return {'CANCELLED'}
|
||||
|
||||
# first upload metadata to server, so it can be saved inside the current file
|
||||
url = paths.get_bkit_url() + 'assets/'
|
||||
url = paths.get_api_url() + 'assets/'
|
||||
|
||||
headers = utils.get_headers(upload_data['token'])
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ def upload_files(filepath, upload_data, files):
|
|||
'fileIndex': f['index'],
|
||||
'originalFilename': os.path.basename(f['file_path'])
|
||||
}
|
||||
upload_create_url = paths.get_bkit_url() + 'uploads/'
|
||||
upload_create_url = paths.get_api_url() + 'uploads/'
|
||||
upload = requests.post(upload_create_url, json=upload_info, headers=headers, verify=True)
|
||||
upload = upload.json()
|
||||
|
||||
|
@ -108,7 +108,7 @@ def upload_files(filepath, upload_data, files):
|
|||
time.sleep(1)
|
||||
|
||||
# confirm single file upload to bkit server
|
||||
upload_done_url = paths.get_bkit_url() + 'uploads_s3/' + upload['id'] + '/upload-file/'
|
||||
upload_done_url = paths.get_api_url() + 'uploads_s3/' + upload['id'] + '/upload-file/'
|
||||
upload_response = requests.post(upload_done_url, headers=headers, verify=True)
|
||||
|
||||
bg_blender.progress('finished uploading')
|
||||
|
@ -187,7 +187,7 @@ if __name__ == "__main__":
|
|||
"verificationStatus": "uploaded"
|
||||
}
|
||||
|
||||
url = paths.get_bkit_url() + 'assets/'
|
||||
url = paths.get_api_url() + 'assets/'
|
||||
|
||||
headers = utils.get_headers(upload_data['token'])
|
||||
|
||||
|
|
|
@ -171,17 +171,22 @@ def load_prefs():
|
|||
if os.path.exists(fpath):
|
||||
with open(fpath, 'r') as s:
|
||||
prefs = json.load(s)
|
||||
user_preferences.api_key = prefs['API_key']
|
||||
user_preferences.global_dir = prefs['global_dir']
|
||||
user_preferences.api_key = prefs.get('API_key','')
|
||||
user_preferences.global_dir = prefs.get('global_dir', paths.default_global_dict())
|
||||
|
||||
user_preferences.api_key_refresh = prefs.get('API_key_refresh','')
|
||||
|
||||
def save_prefs(self, context):
|
||||
# print(type(context),type(bpy.context))
|
||||
if not bpy.app.background and hasattr(bpy.context, 'view_layer'):
|
||||
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
||||
if user_preferences.api_key != '':
|
||||
if len(user_preferences.api_key)>35:
|
||||
#we test the api key for lenght, so not a random accidentaly typed sequence gets saved.
|
||||
if len(user_preferences.api_key)>25:
|
||||
|
||||
prefs = {
|
||||
'API_key': user_preferences.api_key,
|
||||
'API_key_refresh': user_preferences.api_key_refresh,
|
||||
'global_dir': user_preferences.global_dir,
|
||||
}
|
||||
# user_preferences.api_key = user_preferences.api_key.strip()
|
||||
|
@ -194,7 +199,7 @@ def save_prefs(self, context):
|
|||
# reset the api key in case the user writes some nonsense, e.g. a search string instead of the Key
|
||||
user_preferences.api_key = ''
|
||||
props = get_search_props()
|
||||
props.report = 'Please paste a correct API Key.'
|
||||
props.report = 'Login failed. Please paste a correct API Key.'
|
||||
|
||||
def load_categories():
|
||||
categories.copy_categories()
|
||||
|
|
Loading…
Reference in New Issue