Skip to content

Instantly share code, notes, and snippets.

@esnya
Last active August 14, 2020 05:10
Show Gist options
  • Save esnya/0f021e7656e9cafdba6ff0ff4e63845d to your computer and use it in GitHub Desktop.
Save esnya/0f021e7656e9cafdba6ff0ff4e63845d to your computer and use it in GitHub Desktop.
Google Drive のフォルダかファイルの共有URLを指定すると VRChat の VRC_Panorama を自動的に設定するツール
#if UNITY_EDITOR
using System.Linq;
using UnityEngine;
using UnityEditor;
using System;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Drive.v3;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using VRCSDK2;
namespace EsnyaFactory {
public class GoogleDrivePanoramaUtility : EditorWindow {
[MenuItem("EsnyaFactory/Google Drive Panorama Utility")]
private static void ShowWindow() {
var window = GetWindow<GoogleDrivePanoramaUtility>();
window.Show();
}
private string fileIdPatternString = @"(file/d|folders)/(?<fileId>[a-zA-Z0-9_-]+)";
private Vector2 _scrollPosition = Vector2.zero;
private string clientId;
private string clientSecret;
private long fileSizeLimit = 2 * 1024 * 1024;
private UserCredential credential;
private MeshRenderer target;
private string url;
private Google.Apis.Drive.v3.Data.File file;
private Google.Apis.Drive.v3.Data.File[] files;
private bool isRunning = false;
private DriveService drive {
get {
return new DriveService(new BaseClientService.Initializer() {
HttpClientInitializer = credential,
ApplicationName = "EsnyaFactory.GoogleDrivePanoramaUtility",
});
}
}
private Regex fileIdPattern {
get {
return new Regex(fileIdPatternString);
}
}
private string fileId {
get {
if (url == null) {
return null;
}
var match = fileIdPattern.Match(url);
return match.Groups["fileId"].Value;
}
}
private bool isFolder {
get {
var b = file?.Capabilities?.CanListChildren;
return b == true;
}
}
private MonoBehaviour panorama {
get {
if (target == null) return null;
return target.GetComponents<MonoBehaviour>().FirstOrDefault(c => c.GetType().Name.EndsWith("VRC_Panorama"));
}
}
private bool isReady {
get {
if (target == null) return false;
if (file == null) return false;
if (isFolder) {
if (files == null || files.Length == 0) return false;
} else {
if (file.WebContentLink == null) return false;
}
if (panorama == null) return false;
return true;
}
}
private void ReadonlyTextField(string label, string value)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(label, GUILayout.Width(EditorGUIUtility.labelWidth - 4));
EditorGUILayout.SelectableLabel(value, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight));
EditorGUILayout.EndHorizontal();
}
public GoogleDrivePanoramaUtility()
{
Initialize();
}
private async void AsyncAction(Func<Task> action)
{
isRunning = true;
try {
await action();
} finally {
isRunning = false;
}
}
private void OnGUI() {
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
EditorGUI.BeginDisabledGroup(isRunning);
var headerStyle = new GUIStyle() { fontStyle = FontStyle.Bold };
titleContent = new GUIContent("Google Drive Panorama Utility");
EditorGUILayout.LabelField("Google Drive SDK", headerStyle);
if (credential == null) {
clientId = EditorGUILayout.TextField("Client Id", clientId);
clientSecret = EditorGUILayout.PasswordField("Client Secret", clientSecret);
EditorGUI.BeginDisabledGroup(clientId == null || clientSecret == null);
if (GUILayout.Button("Authorize")) {
AsyncAction(Authorize);
}
EditorGUI.EndDisabledGroup();
} else {
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Authorized");
if (GUILayout.Button("Clear Credential")) {
AsyncAction(ClearCredential);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.LabelField("File(s)", headerStyle);
url = EditorGUILayout.TextField("URL", url);
fileIdPatternString = EditorGUILayout.TextField("File Id Pattern", fileIdPatternString);
EditorGUI.BeginDisabledGroup(file == null);
EditorGUILayout.LabelField("Name", file?.Name ?? "");
EditorGUILayout.LabelField("Type", $"{file?.MimeType}");
EditorGUILayout.LabelField("Size", $"{file?.Size}");
EditorGUILayout.LabelField("Shared", $"{file?.Shared == true}");
EditorGUILayout.LabelField("Web Content Link", $"{file?.WebContentLink}");
EditorGUILayout.LabelField("Is Folder", $"{file?.Capabilities?.CanListChildren}");
if (files != null) {
EditorGUILayout.LabelField("Children");
files.ToList().ForEach(childFile => {
EditorGUILayout.BeginVertical(GUI.skin.box);
EditorGUILayout.LabelField("Name", childFile?.Name ?? "");
EditorGUILayout.LabelField("Type", $"{childFile?.MimeType}");
EditorGUILayout.LabelField("Size", $"{childFile?.Size}");
EditorGUILayout.LabelField("Web Content Link", $"{childFile?.WebContentLink}");
EditorGUILayout.EndVertical();
});
}
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup(url == null);
if (GUILayout.Button("Update")) {
AsyncAction(UpdateFileList);
}
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space();
EditorGUILayout.LabelField("Panorama", headerStyle);
target = EditorGUILayout.ObjectField("Target Mesh Renderer", target, typeof(MeshRenderer), true) as MeshRenderer;
EditorGUILayout.Space();
if (panorama == null) {
EditorGUILayout.LabelField("Add VRC_Panorama into Target Mesh Renderer");
}
fileSizeLimit = EditorGUILayout.LongField("File Size Limit (Bytes)", fileSizeLimit);
EditorGUI.BeginDisabledGroup(!isReady);
if (GUILayout.Button("Generate VRCPanorama")) {
GeneratePanorama();
}
EditorGUI.EndDisabledGroup();
}
EditorGUI.EndDisabledGroup();
EditorGUILayout.EndScrollView();
}
private async Task Authorize()
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = clientId,
ClientSecret = clientSecret,
},
new[] { DriveService.Scope.DriveReadonly },
"user",
CancellationToken.None,
new FileDataStore("google.token")
);
if (credential == null || fileId == null) return;
await UpdateFileList();
}
private async Task ClearCredential()
{
await credential.RevokeTokenAsync(CancellationToken.None);
credential = null;
}
private async Task UpdateFileList()
{
var fields = "name,shared,capabilities,mimeType,webContentLink,size";
var getReq = drive.Files.Get(fileId);
getReq.Fields = fields;
file = await getReq.ExecuteAsync();
if (file?.Capabilities?.CanListChildren != true) {
files = null;
} else {
var listReq = drive.Files.List();
listReq.Q = $"'{fileId}' in parents";
var list = await listReq.ExecuteAsync() ;
var allFiles = await Task.WhenAll(list.Files.Select(async f => {
var req = drive.Files.Get(f.Id);
req.Fields = fields;
return await req.ExecuteAsync();
}));
files = allFiles.Where(f => f.Shared == true && f.MimeType.StartsWith("image/") && f.WebContentLink != null).ToArray();
}
}
private void GeneratePanorama()
{
var dataStorage = target.GetComponent<VRC_DataStorage>();
var display = dataStorage.data.FirstOrDefault(d => d.name == "display");
if (display == null) {
dataStorage.data = dataStorage.data.Append(new VRC_DataStorage.VrcDataElement() {
name = "display",
type = VRC_DataStorage.VrcDataType.Int,
valueInt = 0,
}).ToArray();
} else {
display.valueInt = 0;
}
var urls = (isFolder ? files : new [] { file }).Where(f => f.Size < fileSizeLimit).Select(f => f.WebContentLink.Replace("export=download", "export=view").Replace("https://", "http://"));
var panoramas = urls.Select(url => new VRC.SDKBase.VRC_Panorama.PanoSpec() { url = url }).ToList();
panorama.GetType().GetField("panoramas").SetValue(panorama, panoramas);
}
private async void Initialize() {
if (clientId == null || clientSecret == null) return;
await Authorize();
}
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment