Spaces:
Running
Running
Update src/addons/addons/save-to-google/userscript.js
Browse files
src/addons/addons/save-to-google/userscript.js
CHANGED
@@ -1,50 +1,106 @@
|
|
1 |
export default async ({ addon, console, msg }) => {
|
2 |
-
// 永続的に対象要素が出現するのを待つループ
|
3 |
while (true) {
|
4 |
-
// 対象のメニュー要素を待機
|
5 |
const targetElem = await addon.tab.waitForElement(
|
6 |
'div[class*="menu-bar_file-group"] > div:last-child:not(.sa-record)',
|
7 |
{ markAsSeen: true }
|
8 |
);
|
9 |
|
10 |
-
// 同じボタンが複数作られないようにチェック
|
11 |
if (!document.querySelector('.sa-custom-modal-button')) {
|
12 |
-
// ボタン要素を作成
|
13 |
const button = document.createElement("div");
|
14 |
button.className = "sa-custom-modal-button " + targetElem.className;
|
15 |
-
button.textContent =
|
16 |
button.style.cursor = "pointer";
|
17 |
|
18 |
-
// ボタンがクリックされたときにモーダルを表示するイベントリスナー
|
19 |
button.addEventListener("click", () => {
|
20 |
-
// モーダルを作成(addon.tab.createModalを利用)
|
21 |
const { backdrop, container, content, closeButton, remove } = addon.tab.createModal(
|
22 |
-
|
23 |
{ isOpen: true, useEditorClasses: true }
|
24 |
);
|
25 |
|
26 |
-
// モーダル内に任意のHTMLを設定
|
27 |
content.innerHTML = `
|
28 |
<div>
|
29 |
-
<h1
|
30 |
-
<p
|
31 |
-
<button id="
|
32 |
</div>
|
33 |
`;
|
34 |
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
}
|
40 |
|
41 |
-
// バックドロップやクローズボタンをクリックした場合もモーダルを閉じる
|
42 |
backdrop.addEventListener("click", () => remove());
|
43 |
closeButton.addEventListener("click", () => remove());
|
44 |
});
|
45 |
|
46 |
-
// 対象の親要素にボタンを追加
|
47 |
targetElem.parentElement.appendChild(button);
|
48 |
}
|
49 |
}
|
50 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
export default async ({ addon, console, msg }) => {
|
|
|
2 |
while (true) {
|
|
|
3 |
const targetElem = await addon.tab.waitForElement(
|
4 |
'div[class*="menu-bar_file-group"] > div:last-child:not(.sa-record)',
|
5 |
{ markAsSeen: true }
|
6 |
);
|
7 |
|
|
|
8 |
if (!document.querySelector('.sa-custom-modal-button')) {
|
|
|
9 |
const button = document.createElement("div");
|
10 |
button.className = "sa-custom-modal-button " + targetElem.className;
|
11 |
+
button.textContent = "保存";
|
12 |
button.style.cursor = "pointer";
|
13 |
|
|
|
14 |
button.addEventListener("click", () => {
|
|
|
15 |
const { backdrop, container, content, closeButton, remove } = addon.tab.createModal(
|
16 |
+
"保存",
|
17 |
{ isOpen: true, useEditorClasses: true }
|
18 |
);
|
19 |
|
|
|
20 |
content.innerHTML = `
|
21 |
<div>
|
22 |
+
<h1>Googleドライブに接続</h1>
|
23 |
+
<p>Googleでログインして、プロジェクトを保存します。</p>
|
24 |
+
<button id="google-login-button">Googleでログイン</button>
|
25 |
</div>
|
26 |
`;
|
27 |
|
28 |
+
const loginButton = content.querySelector("#google-login-button");
|
29 |
+
if (loginButton) {
|
30 |
+
loginButton.addEventListener("click", () => {
|
31 |
+
const authUrl = `https://accounts.google.com/o/oauth2/auth?client_id=1033286471224-n9mv8l869fqikubj2e8q92n8ige3qr6r.apps.googleusercontent.com&redirect_uri=${encodeURIComponent("close.php")}&response_type=token&scope=https://www.googleapis.com/auth/drive.file`;
|
32 |
+
|
33 |
+
const authWindow = window.open(authUrl, "_blank", "width=500,height=600");
|
34 |
+
|
35 |
+
const checkAuth = setInterval(() => {
|
36 |
+
if (authWindow.closed) {
|
37 |
+
clearInterval(checkAuth);
|
38 |
+
saveToGoogleDrive();
|
39 |
+
}
|
40 |
+
}, 1000);
|
41 |
+
});
|
42 |
}
|
43 |
|
|
|
44 |
backdrop.addEventListener("click", () => remove());
|
45 |
closeButton.addEventListener("click", () => remove());
|
46 |
});
|
47 |
|
|
|
48 |
targetElem.parentElement.appendChild(button);
|
49 |
}
|
50 |
}
|
51 |
};
|
52 |
+
|
53 |
+
async function saveToGoogleDrive() {
|
54 |
+
const dataURL = await new Promise((resolve) => {
|
55 |
+
SB3Downloader.saveProjectSb3().then((blob) => {
|
56 |
+
const fr = new FileReader();
|
57 |
+
fr.onload = () => resolve(fr.result);
|
58 |
+
fr.onabort = () => {
|
59 |
+
throw new Error("Read aborted");
|
60 |
+
};
|
61 |
+
fr.readAsDataURL(blob);
|
62 |
+
});
|
63 |
+
});
|
64 |
+
|
65 |
+
const accessToken = getAccessTokenFromUrl();
|
66 |
+
if (!accessToken) {
|
67 |
+
console.error("Google認証トークンが取得できませんでした。");
|
68 |
+
return;
|
69 |
+
}
|
70 |
+
|
71 |
+
const metadata = {
|
72 |
+
name: "project.sb3",
|
73 |
+
mimeType: "application/x-scratch",
|
74 |
+
};
|
75 |
+
|
76 |
+
const form = new FormData();
|
77 |
+
form.append("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" }));
|
78 |
+
form.append("file", dataURLToBlob(dataURL));
|
79 |
+
|
80 |
+
fetch("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart", {
|
81 |
+
method: "POST",
|
82 |
+
headers: {
|
83 |
+
Authorization: `Bearer ${accessToken}`,
|
84 |
+
},
|
85 |
+
body: form,
|
86 |
+
})
|
87 |
+
.then(response => response.json())
|
88 |
+
.then(data => console.log("ファイルがGoogleドライブに保存されました: ", data))
|
89 |
+
.catch(error => console.error("Googleドライブへの保存中にエラーが発生しました: ", error));
|
90 |
+
}
|
91 |
+
|
92 |
+
function getAccessTokenFromUrl() {
|
93 |
+
const params = new URLSearchParams(window.location.hash.substring(1));
|
94 |
+
return params.get("access_token");
|
95 |
+
}
|
96 |
+
|
97 |
+
function dataURLToBlob(dataURL) {
|
98 |
+
const byteString = atob(dataURL.split(",")[1]);
|
99 |
+
const mimeString = dataURL.split(",")[0].split(":")[1].split(";")[0];
|
100 |
+
const ab = new ArrayBuffer(byteString.length);
|
101 |
+
const ia = new Uint8Array(ab);
|
102 |
+
for (let i = 0; i < byteString.length; i++) {
|
103 |
+
ia[i] = byteString.charCodeAt(i);
|
104 |
+
}
|
105 |
+
return new Blob([ab], { type: mimeString });
|
106 |
+
}
|