├── API.gs ├── GithubAuth.gs ├── LICENSE ├── README.md ├── UpdateDocs.gs ├── appsscript.json ├── img └── header.png ├── rollGacha.gs ├── test.gs └── uploadIcon.gs /API.gs: -------------------------------------------------------------------------------- 1 | //Driveのファイルid@ https://drive.google.com/drive/folders/104VHEklUV0qQvjTiCamRenYeVC1pSYB5?usp=sharing 2 | const fileIds = { 3 | userInformations: '1-UfUHB2VxgTm05BjQgxxClq9Xd5YxdcZ', 4 | events: '1NbDVZ1K2UYoZx4C3ZalUtbt4a1bYtWY-', 5 | stamps: '1HAGk-9YOjZ2c_w76I1vugEzeDO5OqjvC', 6 | musics: '1xsU6mTQkvgEGqSsXUaC0nE0pK4c0LGX-', 7 | tips: '1BeAACayVN2GCFdxO2JOESkUbKoRz9V4E', 8 | characterProfiles: '1H_VuQvlg_AehL_qAXEsGo_oBDxAYhU5Y', 9 | unitProfiles: '1zm5qieChyN_2qy47eXW3Yab7NGfAQW1K', 10 | cards: '1tO3_-Ae8sj8JQWgue_D_C4OwCLnpSA8n', 11 | skils: '1gMUhx4jbvIaw-vsXqx_sUYANoV2AVgo6', 12 | honors: '1LvOKXSF23_Kf7X5Ya0W5b_i9XXHuvFRZ', 13 | gachas: '1jBAHOMW9YM8p0cS4Y-daBT4lo_2oCPVw', 14 | versions: '141WTGhgct8DHzBQXDlIZDjK0r9-HXDrx' 15 | } 16 | 17 | 18 | /** 19 | * レスポンスを作成して返します 20 | * @param {*} content 21 | * @returns {TextOutput} 22 | */ 23 | function response(content) { 24 | const res = ContentService.createTextOutput() 25 | res.setMimeType(ContentService.MimeType.JSON) 26 | res.setContent(JSON.stringify(content)) 27 | return res 28 | } 29 | 30 | /** 31 | * アプリにPOSTリクエストが送信されたとき実行されます 32 | * @param {Event} e 33 | * @returns {TextOutput} 34 | */ 35 | function doGet(e) { 36 | let contents = e.parameter 37 | 38 | if (contents.id !== undefined && typeof contents.id !== 'number') { 39 | if (contents.id === 'null') contents.id = null 40 | else contents.id = parseFloat(contents.id) 41 | } 42 | 43 | if (contents.spinCount !== undefined && typeof contents.spinCount !== 'number') { 44 | if (contents.id === 'null') contents.id = null 45 | else contents.id = parseFloat(contents.id) 46 | } 47 | 48 | const authToken = PropertiesService.getScriptProperties().getProperty('authToken') || '' 49 | 50 | if (contents.authToken !== authToken) { 51 | return response({ error: '認証に失敗しました' }) 52 | } 53 | 54 | let result 55 | try { 56 | result = onGet(contents) 57 | if (!result.length && result instanceof Array) result = { error: '各当のデータは見つかりませんでした' } 58 | console.log(result) 59 | } 60 | catch (e) { 61 | result = { error: e } 62 | console.error(result) 63 | } 64 | 65 | return response(result) 66 | } 67 | 68 | 69 | function isValid(option) { 70 | const keys = ['data'] 71 | for (let key of keys) { 72 | if (option[key] === undefined) return false 73 | } 74 | if (option.id !== undefined && typeof option.id !== 'number') { 75 | if (option.id === null) return true 76 | else return false 77 | } 78 | return true 79 | } 80 | 81 | function onGet(option) { 82 | if (!isValid(option)) { 83 | return { 84 | error: '正しい形式で入力してください' 85 | } 86 | } 87 | 88 | /**検索用*/ 89 | if (option.search !== undefined) { 90 | //カナの場合ひらに変換 91 | const searchData = kanaToHira(option.search) 92 | //スペースでsplit 93 | option.search = searchData.replace(' ', ' ').split(' ') 94 | } 95 | 96 | let fileId 97 | switch (option.data) { 98 | case 'info': 99 | fileId = fileIds.userInformations 100 | return getText(fileId, option) 101 | case 'event': 102 | fileId = fileIds.events 103 | return getText(fileId, option) 104 | case 'stamp': 105 | fileId = fileIds.stamps 106 | return getText(fileId, option) 107 | case 'music': 108 | fileId = fileIds.musics 109 | return getText(fileId, option) 110 | case 'tip': 111 | fileId = fileIds.tips 112 | return getText(fileId, option) 113 | case 'character': 114 | fileId = fileIds.characterProfiles 115 | return getText(fileId, option) 116 | case 'unit': 117 | fileId = fileIds.unitProfiles 118 | return getText(fileId, option) 119 | case 'card': 120 | fileId = fileIds.cards 121 | return getText(fileId, option) 122 | case 'mission': 123 | fileId = fileIds.honors 124 | return getText(fileId, option) 125 | case 'gacha': 126 | fileId = fileIds.gachas 127 | return getText(fileId, option) 128 | case 'roll': 129 | return rollGacha(option) 130 | case 'version': 131 | fileId = fileIds.versions 132 | return getText(fileId, option) 133 | default: 134 | return { 135 | error: 'dataを指定してください(GitHub:https://github.com/Kadoyu/Project-SEKAI-API)' 136 | } 137 | } 138 | } 139 | 140 | /**GoogleDriveからテキストを取得 @ https://drive.google.com/drive/folders/104VHEklUV0qQvjTiCamRenYeVC1pSYB5?usp=sharing*/ 141 | function getText(fileId, data) { 142 | const content = DriveApp.getFileById(fileId) 143 | .getBlob() 144 | .getDataAsString('utf-8') 145 | //.replace(/\r?\n/g, '') 146 | const obj = JSON.parse(content) 147 | return search(obj, data) 148 | } 149 | 150 | 151 | /**検索 */ 152 | function search(obj, data) { 153 | if (data.search === undefined || data.search[0] === 'null' || data.search === null) { 154 | if (data.id === undefined || data.id === 'null' || data.id === null) return sort(obj, data) 155 | else return sort(obj.filter(elem => elem.id === data.id), data) 156 | } else { 157 | let result = [] 158 | for (let value in obj) { 159 | for (let i in data.search) { 160 | const arr = Object.values(obj[value]).join(' ').toLowerCase() 161 | if (kanaToHira(arr).match(data.search[i].toLowerCase())) result.push(obj[value]) 162 | } 163 | } 164 | result = Array.from(new Set(result)) 165 | if (data.id === undefined || data.id === 'null' || data.id === null) { 166 | return sort(result, data) 167 | } else { 168 | return sort(result.filter(elem => elem.id === data.id), data) 169 | } 170 | } 171 | } 172 | 173 | 174 | function sort(obj, data) { 175 | switch (data.sort) { 176 | case 'ascending': 177 | case undefined: 178 | return amount(obj, data) 179 | case 'descending': 180 | return amount(obj.reverse(), data) 181 | case 'random': 182 | return amount(arrayShuffle(obj), data) 183 | } 184 | } 185 | 186 | function amount(obj, data) { 187 | if (!Array.isArray(obj)) return obj 188 | switch (data.amount) { 189 | case 'all': 190 | return obj 191 | case undefined: 192 | return obj.slice(0, 5) 193 | default: 194 | const slicedObj = obj.slice(0, data.amount) 195 | return slicedObj 196 | } 197 | } 198 | 199 | /**配列をランダム化 */ 200 | function arrayShuffle(array) { 201 | for (var i = (array.length - 1); 0 < i; i--) { 202 | 203 | // 0〜(i+1)の範囲で値を取得 204 | var r = Math.floor(Math.random() * (i + 1)); 205 | 206 | // 要素の並び替えを実行 207 | var tmp = array[i]; 208 | array[i] = array[r]; 209 | array[r] = tmp; 210 | } 211 | return array; 212 | } 213 | 214 | /**カタカナをひらがなに変換 */ 215 | function kanaToHira(str) { 216 | return str.replace(/[\u30a1-\u30f6]/g, function (match) { 217 | var chr = match.charCodeAt(0) - 0x60; 218 | return String.fromCharCode(chr); 219 | }); 220 | } 221 | 222 | /**ひらがなをカタカナに変換 */ 223 | function hiraToKana(str) { 224 | return str.replace(/[\u3041-\u3096]/g, function (match) { 225 | var chr = match.charCodeAt(0) + 0x60; 226 | return String.fromCharCode(chr); 227 | }); 228 | } -------------------------------------------------------------------------------- /GithubAuth.gs: -------------------------------------------------------------------------------- 1 | function openAuthDialog() { 2 | var gitHubService = getGitHubService(); 3 | if (gitHubService.hasAccess()) { 4 | Browser.msgBox('Already autherized'); 5 | } else { 6 | var authorizationUrl = gitHubService.getAuthorizationUrl(); 7 | var template = HtmlService.createTemplate( 8 | 'Authorize'); 9 | template.authorizationUrl = authorizationUrl; 10 | var page = template.evaluate(); 11 | 12 | SpreadsheetApp.getUi() 13 | .showModalDialog(page, 'Authorize'); 14 | } 15 | } 16 | 17 | function getGitHubService() { 18 | const CLIENT_ID = PropertiesService.getScriptProperties().getProperty('github_client_id') 19 | const CLIENT_SECRET = PropertiesService.getScriptProperties().getProperty('github_client_secret') 20 | // Create a new service with the given name. The name will be used when 21 | // persisting the authorized token, so ensure it is unique within the 22 | // scope of the property store. 23 | return OAuth2.createService('GitHub') 24 | // Set the endpoint URLs, which are the same for all GitHub services. 25 | .setAuthorizationBaseUrl('https://github.com/login/oauth/authorize') 26 | .setTokenUrl('https://github.com/login/oauth/access_token') 27 | 28 | // Set the client ID and secret, from 29 | // GitHub Settings > Applications > Developer applications 30 | .setClientId(CLIENT_ID) 31 | .setClientSecret(CLIENT_SECRET) 32 | 33 | // Set the name of the callback function in the script referenced 34 | // above that should be invoked to complete the OAuth flow. 35 | .setCallbackFunction('authCallback') 36 | 37 | // Set the property store where authorized tokens should be persisted. 38 | .setPropertyStore(PropertiesService.getUserProperties()) 39 | } 40 | 41 | function authCallback(request) { 42 | var gitHubService = getGitHubService(); 43 | var isAuthorized = gitHubService.handleCallback(request); 44 | if (isAuthorized) { 45 | return HtmlService.createHtmlOutput('Success! You can close this tab.'); 46 | } else { 47 | return HtmlService.createHtmlOutput('Denied. You can close this tab'); 48 | } 49 | } 50 | 51 | function clearService() { 52 | OAuth2.createService('GitHub') 53 | .setPropertyStore(PropertiesService.getUserProperties()) 54 | .reset(); 55 | } 56 | 57 | // Reusable function to generate a callback URL, assuming the script has been published as a 58 | // web app (necessary to obtain the URL programmatically). If the script has not been published 59 | // as a web app, set `var url` in the first line to the URL of your script project (which 60 | // cannot be obtained programmatically). 61 | function getCallbackURL(callbackFunction) { 62 | var url = ScriptApp.getService().getUrl(); // Ends in /exec (for a web app) 63 | url = url.slice(0, -4) + 'usercallback?state='; // Change /exec to /usercallback 64 | var stateToken = ScriptApp.newStateToken() 65 | .withMethod(callbackFunction) 66 | .withTimeout(120) 67 | .createToken(); 68 | Logger.log(url + stateToken); 69 | } 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Kadoyu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /UpdateDocs.gs: -------------------------------------------------------------------------------- 1 | const sekaiRes_asset = 'https://sekai-res.dnaroma.eu/file/sekai-assets/' 2 | const minio_asset = 'https://minio.dnaroma.eu/sekai-assets/' 3 | 4 | const getRepoContent = (fileName) => { 5 | const url = `https://raw.githubusercontent.com/Sekai-World/sekai-master-db-diff/main/${fileName}` 6 | const gitHubService = getGitHubService() 7 | const options = { 8 | headers: { Authorization: 'Bearer ' + gitHubService.getAccessToken() } 9 | } 10 | // UrlFetchでデータを取得して、JSONデータをパースする 11 | const response = JSON.parse(UrlFetchApp.fetch(url, options)) 12 | return response 13 | } 14 | 15 | class GetData { 16 | static info() { 17 | const obj = getRepoContent('userInformations.json') 18 | /**各々の追加情報 */ 19 | for (let i in obj) { 20 | if (obj[i].browseType === 'internal') { 21 | obj[i].path = 'https://production-web.sekai.colorfulpalette.org/' + obj[i].path 22 | } 23 | } 24 | return obj 25 | } 26 | 27 | static event() { 28 | const obj = getRepoContent('events.json') 29 | const cardObj = getRepoContent('eventCards.json') 30 | /**各々の追加情報 */ 31 | const assetUrl = sekaiRes_asset + 'event/' 32 | const homeAssetUrl = sekaiRes_asset + 'home/banner/' 33 | obj.filter(elem => { 34 | delete elem.eventRankingRewardRanges 35 | elem.eventCard = [] 36 | cardObj.filter(value => { 37 | if (elem.id == value.eventId) elem.eventCard.push(value.cardId) 38 | }) 39 | elem.logoImg = assetUrl + elem.assetbundleName + '/logo_rip/logo.webp' 40 | elem.bannerImg = homeAssetUrl + elem.assetbundleName + '_rip/' + elem.assetbundleName + '.webp' 41 | elem.bgImg = assetUrl + elem.assetbundleName + '/screen_rip/bg.webp' 42 | elem.characterImg = assetUrl + elem.assetbundleName + '/screen_rip/character.webp' 43 | }) 44 | return obj 45 | } 46 | 47 | static stamp() { 48 | const obj = getRepoContent('stamps.json') 49 | /**各々の追加情報 */ 50 | const assetUrl = sekaiRes_asset + 'stamp/' 51 | obj.filter(elem => { 52 | elem.stampImg = assetUrl + elem.assetbundleName + '_rip/' + elem.assetbundleName + '/' + elem.assetbundleName + '.png' 53 | }) 54 | return obj 55 | } 56 | 57 | static music() { 58 | const obj = getRepoContent('musics.json') 59 | const vocalObj = getRepoContent('musicVocals.json') 60 | const difficultiesObj = getRepoContent('musicDifficulties.json') 61 | /** ゼロ埋め NUM=値 LEN=桁数 */ 62 | const zeroPadding = (NUM, LEN) => { 63 | return (Array(LEN).join('0') + NUM).slice(-LEN); 64 | } 65 | 66 | /**各々の追加情報 */ 67 | const assetUrl = sekaiRes_asset + 'music/' 68 | const minioAssetUrl = minio_asset + 'music/' 69 | obj.filter(elem => { 70 | elem.jacketImg = assetUrl + 'jacket/' + elem.assetbundleName + '_rip/' + elem.assetbundleName + '.webp' 71 | elem.difficulties = difficultiesObj.filter(value => { 72 | value.chart = minioAssetUrl + 'charts/' + zeroPadding(value.musicId, 4) + '/' + value.musicDifficulty + '.svg' 73 | if (elem.id == value.musicId) return value 74 | }) 75 | elem.vocal = vocalObj.filter(value => { 76 | value.mp3 = assetUrl + 'long/' + value.assetbundleName + '_rip/' + value.assetbundleName + '.mp3' 77 | value.flac = assetUrl + 'long/' + value.assetbundleName + '_rip/' + value.assetbundleName + '.flac' 78 | value.shortMp3 = assetUrl + 'short/' + value.assetbundleName + '_rip/' + value.assetbundleName + '_short.mp3' 79 | value.shortFlac = assetUrl + 'short/' + value.assetbundleName + '_rip/' + value.assetbundleName + '_short.flac' 80 | if (elem.id == value.musicId) { 81 | return value 82 | } 83 | }) 84 | }) 85 | return obj 86 | } 87 | 88 | static tip() { 89 | const obj = getRepoContent('tips.json') 90 | 91 | /**各々の追加情報 */ 92 | const minioAssetUrl = minio_asset + 'comic/' 93 | obj.filter(elem => { 94 | if (elem.assetbundleName !== undefined) elem.comicImg = minioAssetUrl + 'one_frame_rip/' + elem.assetbundleName + '.webp' 95 | }) 96 | return obj 97 | } 98 | 99 | static character() { 100 | const obj = getRepoContent('gameCharacters.json') 101 | const colorObj = getRepoContent('gameCharacterUnits.json') 102 | const profilesObj = getRepoContent('characterProfiles.json') 103 | 104 | const characterVoices = [ 105 | { id: 21, cv: '藤田咲' }, 106 | { id: 22, cv: '下田麻美' }, 107 | { id: 23, cv: '下田麻美' }, 108 | { id: 24, cv: '浅川悠' }, 109 | { id: 25, cv: '拝郷メイコ' }, 110 | { id: 26, cv: '風雅なおと' } 111 | ] 112 | 113 | /**各々の追加情報 */ 114 | const assetUrl = sekaiRes_asset + 'character/' 115 | obj.filter(elem => { 116 | characterVoices.filter(value => { 117 | if (value.id == elem.id) elem.characterVoice = value.cv 118 | }) 119 | colorObj.slice(0, 26).filter(value => { 120 | if (elem.id == value.gameCharacterId) { 121 | delete value.gameCharacterId 122 | return Object.assign(elem, value) 123 | } 124 | }) 125 | 126 | profilesObj.filter(value => { 127 | if (elem.id == value.characterId) { 128 | delete value.characterId 129 | return Object.assign(elem, value) 130 | } 131 | }) 132 | elem.characterImg = assetUrl + 'trim_rip/chr_trim_' + elem.id + '.webp' 133 | elem.horizNameImg = assetUrl + 'label_rip/chr_h_lb_' + elem.id + '.webp' 134 | elem.vertNameImg = assetUrl + 'label_vertical_rip/chr_v_lb_' + elem.id + '.webp' 135 | elem.characterSetImg = assetUrl + 'character_select_rip/chr_tl_' + elem.id + '.webp' 136 | }) 137 | return obj 138 | } 139 | 140 | static unit() { 141 | const obj = getRepoContent('unitProfiles.json') 142 | const colorObj = getRepoContent('gameCharacterUnits.json') 143 | 144 | /**各々の追加情報 */ 145 | const assetUrl = 'https://raw.githubusercontent.com/Sekai-World/sekai-viewer/main/public/images/jp/' 146 | const minioAssetUrl = minio_asset + 'unit/set_image_rip/' 147 | obj.filter(elem => { 148 | elem.membersId = [] 149 | colorObj.slice(0, 26).filter(value => { 150 | if (elem.unit == value.unit) { 151 | elem.membersId.push(value.gameCharacterId) 152 | } 153 | }) 154 | 155 | elem.logoImg = assetUrl + 'logol/logo_' + elem.unit + '.png' 156 | elem.logoOutlineImg = assetUrl + 'logol_outline/logo_' + elem.unit + '.png' 157 | elem.smallLogoOutlineImg = assetUrl + 'logos_outline/logo_' + elem.unit + '.png' 158 | elem.img = minioAssetUrl + elem.unit + '.webp' 159 | elem.shadowImg = minioAssetUrl + elem.unit + '_shadow.webp' 160 | }) 161 | return obj 162 | } 163 | 164 | static card() { 165 | const obj = getRepoContent('cards.json') 166 | const charaObj = getRepoContent('gameCharacters.json') 167 | 168 | /**各々の追加情報 */ 169 | const assetUrl = sekaiRes_asset + 'character/' 170 | const thumbnailUrl = sekaiRes_asset + 'thumbnail/chara_rip/' 171 | obj.filter(elem => { 172 | const chara = charaObj.filter(value => value.id === elem.characterId)[0] 173 | //MEIKO KAITO 174 | if (elem.characterId == 25 || elem.characterId == 26) elem.character = { id: chara.id, name: chara.givenName, ruby: chara.givenNameRuby } 175 | else elem.character = { id: chara.id, name: chara.firstName + chara.givenName, ruby: chara.firstNameRuby + chara.givenNameRuby } 176 | delete elem.cardParameters 177 | elem.iconImg = thumbnailUrl + elem.assetbundleName + '_normal.webp' 178 | elem.cardImg = assetUrl + 'member/' + elem.assetbundleName + '_rip/card_normal.webp' 179 | elem.cardTrimImg = assetUrl + 'member_cutout_trm/' + elem.assetbundleName + '_rip/normal.webp' 180 | 181 | if (elem.cardRarityType == 'rarity_3' || elem.cardRarityType == 'rarity_4') { 182 | elem.iconATImg = thumbnailUrl + elem.assetbundleName + '_after_training.webp' 183 | elem.cardATImg = assetUrl + 'member/' + elem.assetbundleName + '_rip/card_after_training.webp' 184 | elem.cardATTrimImg = assetUrl + 'member_cutout_trm/' + elem.assetbundleName + '_rip/after_training.webp' 185 | } 186 | }) 187 | 188 | /**ドライブからアイコン名とIDをとってくる */ 189 | const getDriveFilesId = folderId => { 190 | let data = [] 191 | const folder = DriveApp.getFolderById(folderId) 192 | const files = folder.getFiles() 193 | while (files.hasNext()) { 194 | const file = files.next() 195 | const fileName = file.getName() 196 | const fileId = file.getId() 197 | let fileData = { 198 | name: fileName, 199 | id: fileId 200 | } 201 | data.push(fileData) 202 | } 203 | return data 204 | } 205 | 206 | const folderId = '1EHtgKybN5OuVtkOM9QFPI93t4siY_BWJ' 207 | const iconData = getDriveFilesId(folderId) 208 | obj.filter(elem => { 209 | for (let i in iconData) { 210 | if (iconData[i].name == elem.assetbundleName + '_normal.png') elem.fullIconImg = 'https://drive.google.com/uc?id=' + iconData[i].id + '&usp=sharing' 211 | if (iconData[i].name == elem.assetbundleName + '_after_training.png') elem.fullATIconImg = 'https://drive.google.com/uc?id=' + iconData[i].id + '&usp=sharing' 212 | } 213 | }) 214 | return obj 215 | } 216 | 217 | static mission() { 218 | const obj = getRepoContent('honors.json') 219 | 220 | /**各々の追加情報 */ 221 | const assetUrl = sekaiRes_asset + 'honor/' 222 | obj.filter(elem => { 223 | elem.honorImg = assetUrl + elem.assetbundleName + '_rip/degree_main.webp' 224 | }) 225 | return obj 226 | } 227 | 228 | static gachaInfo() { 229 | const obj = getRepoContent('gachas.json') 230 | 231 | /**各々の追加情報 */ 232 | const assetUrl = sekaiRes_asset + 'gacha/' 233 | const homeAssetUrl = sekaiRes_asset + 'home/banner/' 234 | 235 | obj.filter(elem => { 236 | elem.logoImg = assetUrl + elem.assetbundleName + '/logo_rip/logo.webp' 237 | elem.bannerImg = homeAssetUrl + 'banner_gacha' + elem.gachaInformation.gachaId + '_rip/banner_gacha' + elem.gachaInformation.gachaId + '.webp' 238 | elem.bgImg = assetUrl + elem.assetbundleName + '/screen_rip/texture/bg_gacha' + elem.gachaInformation.gachaId + '.webp' 239 | elem.futureImg = assetUrl + elem.assetbundleName + '/screen_rip/texture/img_gacha' + elem.gachaInformation.gachaId + '.webp' 240 | }) 241 | return obj 242 | } 243 | 244 | static version() { 245 | const obj = getRepoContent('versions.json') 246 | obj.prskAPIVersion = '0.1.1 β' 247 | return obj 248 | } 249 | } 250 | 251 | const updateFile = (fileName) => { 252 | 253 | const setDriveFile = (fileId, content) => { 254 | const file = DriveApp.getFileById(fileId) 255 | const json = JSON.stringify(content) 256 | file.setContent(json) 257 | } 258 | 259 | let data 260 | switch (fileName) { 261 | case 'userInformations.json': 262 | data = GetData.info() 263 | setDriveFile(fileIds.userInformations, data) 264 | console.log(`[userInformations]をアップデートしました。`) 265 | break 266 | case 'events.json': 267 | case 'eventCards.json': 268 | data = GetData.event() 269 | setDriveFile(fileIds.events, data) 270 | console.log(`[events]をアップデートしました。`) 271 | break 272 | case 'stamps.json': 273 | data = GetData.stamp() 274 | setDriveFile(fileIds.stamps, data) 275 | console.log(`[stamps]をアップデートしました。`) 276 | break 277 | case 'musics.json': 278 | data = GetData.music() 279 | setDriveFile(fileIds.musics, data) 280 | console.log(`[musics]をアップデートしました。`) 281 | break 282 | case 'tips.json': 283 | data = GetData.tip() 284 | setDriveFile(fileIds.tips, data) 285 | console.log(`[tips]をアップデートしました。`) 286 | break 287 | case 'gameCharacters.json': 288 | case 'gameCharacterUnits.json': 289 | case 'characterProfiles.json': 290 | data = GetData.character() 291 | setDriveFile(fileIds.characterProfiles, data) 292 | console.log(`[characterProfiles]をアップデートしました。`) 293 | break 294 | case 'unitProfiles.json': 295 | case 'gameCharacterUnits.json': 296 | data = GetData.unit() 297 | setDriveFile(fileIds.unitProfiles, data) 298 | console.log(`[unitProfiles]をアップデートしました。`) 299 | break 300 | case 'cards.json': 301 | case 'gameCharacters.json': 302 | iconUpdate() 303 | data = GetData.card() 304 | setDriveFile(fileIds.cards, data) 305 | console.log(`[cards]をアップデートしました。`) 306 | break 307 | case 'honors.json': 308 | data = GetData.mission() 309 | setDriveFile(fileIds.honors, data) 310 | console.log(`[honors]をアップデートしました。`) 311 | break 312 | case 'gachas.json': 313 | data = GetData.gachaInfo() 314 | setDriveFile(fileIds.gachas, data) 315 | console.log(`[gachas]をアップデートしました。`) 316 | break 317 | case 'versions.json': 318 | data = GetData.version() 319 | setDriveFile(fileIds.versions, data) 320 | console.log(`[versions]をアップデートしました。`) 321 | break 322 | case 'all': 323 | data = GetData.info() 324 | setDriveFile(fileIds.userInformations, data) 325 | console.log(`[userInformations]をアップデートしました。`) 326 | data = GetData.event() 327 | setDriveFile(fileIds.events, data) 328 | console.log(`[events]をアップデートしました。`) 329 | data = GetData.stamp() 330 | setDriveFile(fileIds.stamps, data) 331 | console.log(`[stamps]をアップデートしました。`) 332 | data = GetData.music() 333 | setDriveFile(fileIds.musics, data) 334 | console.log(`[musics]をアップデートしました。`) 335 | data = GetData.tip() 336 | setDriveFile(fileIds.tips, data) 337 | console.log(`[tips]をアップデートしました。`) 338 | data = GetData.character() 339 | setDriveFile(fileIds.characterProfiles, data) 340 | console.log(`[characterProfiles]をアップデートしました。`) 341 | data = GetData.unit() 342 | setDriveFile(fileIds.unitProfiles, data) 343 | console.log(`[unitProfiles]をアップデートしました。`) 344 | data = GetData.card() 345 | setDriveFile(fileIds.cards, data) 346 | console.log(`[cards]をアップデートしました。`) 347 | data = GetData.mission() 348 | setDriveFile(fileIds.honors, data) 349 | console.log(`[honors]をアップデートしました。`) 350 | data = GetData.gachaInfo() 351 | setDriveFile(fileIds.gachas, data) 352 | console.log(`[gachas]をアップデートしました。`) 353 | data = GetData.version() 354 | setDriveFile(fileIds.versions, data) 355 | console.log(`[versions]をアップデートしました。`) 356 | iconUpdate() 357 | } 358 | } 359 | 360 | const getCommitEvent = () => { 361 | const url = "https://api.github.com/repos/Sekai-World/sekai-master-db-diff/events"; 362 | const gitHubService = getGitHubService() 363 | const options = { 364 | headers: { Authorization: 'Bearer ' + gitHubService.getAccessToken() } 365 | } 366 | const cache_key = 'sekai_db_latest_commit_id' 367 | const cache = CacheService.getScriptCache() 368 | // UrlFetchでデータを取得して、JSONデータをパースする 369 | const response = JSON.parse(UrlFetchApp.fetch(url, options)) 370 | 371 | //前回のコミットID 372 | let commited_id = cache.get(cache_key) 373 | 374 | //もしキャッシュがなかったら最新のコミットIDを設定 375 | if (commited_id === null) { 376 | cache.put(cache_key, response[0].id) 377 | commited_id = response[0].id 378 | } 379 | //コミットIDの比較 380 | const commited = response.findIndex(elem => elem.id === commited_id) 381 | 382 | if (commited !== 0) { 383 | const events = response.slice(0, commited).filter(event => event.type === "PushEvent") 384 | let commitedFiles = [] 385 | 386 | for (let i in events) { 387 | const commitUrl = events[i].payload.commits[0].url 388 | const commit = JSON.parse(UrlFetchApp.fetch(commitUrl, options)) 389 | //console.log(commit) 390 | commitedFiles.push(commit.files[0].filename) 391 | let patch = commit.files[0].patch 392 | if (patch !== undefined) { 393 | patch = patch.replace(/\n/g, '').match(/\+ {.*?\}/g) //追加されたコンテンツのみにマッチ 394 | if (patch !== null) { 395 | const patch_add = patch.map(elem => elem.match(/\".*?\": .*?\,/g).map(elem2 => { 396 | const arr = elem2.slice(0, -1) 397 | .split(': ') 398 | .map(obj => obj.replace(/^"|"$/g, '')) 399 | const num = Number(arr[1]) 400 | if (!Number.isNaN(num)) arr[1] = num 401 | return arr 402 | })) 403 | 404 | const added = { 405 | file_name: commit.files[0].filename, 406 | created_at: events[0].created_at, 407 | url: commit.url, 408 | raw_url: commit.files[0].raw_url, 409 | content_url: commit.files[0].contents_url, 410 | contents: Object.fromEntries(patch_add[0]) 411 | } 412 | console.log(added) 413 | 414 | //post to Twitter_prskBOT 415 | const prskBot = 'https://script.google.com/macros/s/AKfycbxZemVFuAyXLUStVYXTFabJ3qt9_upebNreatLPRPffdxAY9hHWXTYzIySnj7LEXaXKZQ/exec' 416 | const headers = { 417 | "Content-Type": "application/json" 418 | } 419 | const options = { 420 | 'headers': headers, 421 | 'method': 'post', 422 | 'payload': JSON.stringify(added) 423 | } 424 | const response = UrlFetchApp.fetch(prskBot, options).getContentText() 425 | } 426 | } 427 | } 428 | cache.put(cache_key, response[0].id) 429 | new Set(commitedFiles).forEach(elem => updateFile(elem)) 430 | } 431 | else { console.log('新しいコミットはありません') } 432 | } 433 | 434 | function updateAll() { 435 | updateFile('all') 436 | } 437 | 438 | function setCache() { 439 | const cache_key = 'sekai_db_latest_commit_id' 440 | CacheService.getScriptCache().put(cache_key, '22140345152') 441 | } 442 | -------------------------------------------------------------------------------- /appsscript.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeZone": "Asia/Tokyo", 3 | "exceptionLogging": "STACKDRIVER", 4 | "runtimeVersion": "V8", 5 | "dependencies": { 6 | "libraries": [ 7 | { 8 | "userSymbol": "OAuth2", 9 | "version": "41", 10 | "libraryId": "1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF" 11 | } 12 | ] 13 | }, 14 | "webapp": { 15 | "executeAs": "USER_DEPLOYING", 16 | "access": "ANYONE_ANONYMOUS" 17 | } 18 | } -------------------------------------------------------------------------------- /img/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kadoyu/Project-SEKAI-API/619c4b0c22023a87d6caa748e1239d95ef78bf9d/img/header.png -------------------------------------------------------------------------------- /rollGacha.gs: -------------------------------------------------------------------------------- 1 | function rollGacha(data) { 2 | if (data.id === undefined) return { error: '[roll]に対するid指定は必須です' } 3 | if (data.spinCount === undefined || data.spinCount === null) data.spinCount = 10 4 | data.spinCount = Number(data.spinCount) 5 | 6 | const gachaContent = DriveApp.getFileById(fileIds.gachas) 7 | .getBlob() 8 | .getDataAsString('utf-8') 9 | const cardContent = DriveApp.getFileById(fileIds.cards) 10 | .getBlob() 11 | .getDataAsString('utf-8') 12 | const json = JSON.parse(gachaContent) 13 | const cards = JSON.parse(cardContent) 14 | const gacha = json.filter(elem => elem.id === data.id)[0] 15 | //console.log(gacha.gachaBehaviors) 16 | //const behavior = gacha.gachaBehaviors[0] 17 | const behavior = gacha.gachaBehaviors.filter(elem => elem.spinCount === data.spinCount)[0] 18 | const gachaRarityRates = gacha.gachaCardRarityRates 19 | const normalRates = gachaRarityRates.map(rate => rate['rate']) 20 | var guaranteedRates = [0, 100 - normalRates[2], normalRates[2]] 21 | //console.log(normalRates, guaranteedRates) 22 | const cardRarityTypeToRarity = { rarity_1: 1, rarity_2: 2, rarity_3: 3, rarity_4: 4, rarity_birthday: 0 } 23 | 24 | const rollTimes = behavior.spinCount; 25 | const rollResult = gachaRarityRates.map(() => 0); 26 | const normalSum = normalRates.reduce( 27 | (sum, curr) => [...sum, curr + (sum.slice(-1)[0] || 0)], 28 | [] 29 | ); 30 | const guaranteeSum = guaranteedRates.reduce( 31 | (sum, curr) => [...sum, curr + (sum.slice(-1)[0] || 0)], 32 | [] 33 | ); 34 | const rollableCards = gachaRarityRates.map((rate) => 35 | gacha.gachaDetails 36 | .filter((gd) => 37 | rate.cardRarityType 38 | ? cards.find((card) => card.id === gd.cardId)?.cardRarityType === 39 | rate.cardRarityType 40 | : cards.find((card) => card.id === gd.cardId)?.rarity === 41 | rate.rarity 42 | ) 43 | .sort((a, b) => a.weight - b.weight) 44 | ); 45 | const rollWeights = rollableCards.map((elem) => 46 | elem?.map((_elem) => _elem.weight) 47 | ); 48 | const weights = [rollWeights[0].length, rollWeights[1].length, -rollWeights[0].length - rollWeights[1].length] 49 | for (let i in gacha.gachaDetails) { 50 | weights[2] += gacha.gachaDetails[i].weight 51 | } 52 | const tmpGachaResult = []; 53 | const isOverRarity = behavior.gachaBehaviorType.startsWith("over_rarity"); 54 | // const overRarity = isOverRarity 55 | // ? behavior.gachaBehaviorType === "over_rarity_3_once" 56 | // ? 3 57 | // : 4 58 | // : 0; 59 | let noOverRarityCount = 0; 60 | for (let i = 0; i < rollTimes; i++) { 61 | // console.log(i, rollTimes); 62 | if (i % 10 === 9 && isOverRarity && noOverRarityCount === 9) { 63 | // only roll 3* or 4* 64 | const roll = Math.random() * 100; 65 | const idx = guaranteeSum.findIndex((rate) => roll < rate); 66 | rollResult[idx] += 1; 67 | const weightArr = rollWeights[idx].reduce( 68 | (sum, curr) => [...sum, curr + (sum.slice(-1)[0] || 0)], 69 | [] 70 | ); 71 | const weightSum = weights[idx]; 72 | const rand = Math.floor(Math.random() * weightSum); 73 | tmpGachaResult.push( 74 | rollableCards[idx][ 75 | weightArr.filter((weight) => weight <= rand).length 76 | ] 77 | ); 78 | noOverRarityCount = 0; 79 | continue; 80 | } else if (i % 10 === 0) { 81 | noOverRarityCount = 0; 82 | } 83 | const roll = Math.random() * 100; 84 | const idx = normalSum.findIndex((rate) => roll < rate); 85 | rollResult[idx] += 1; 86 | const weightArr = rollWeights[idx].reduce( 87 | (sum, curr) => [...sum, curr + (sum.slice(-1)[0] || 0)], 88 | [] 89 | ); 90 | const weightSum = weights[idx]; 91 | const rand = Math.floor(Math.random() * weightSum); 92 | tmpGachaResult.push( 93 | rollableCards[idx][ 94 | weightArr.filter((weight) => weight <= rand).length 95 | ] 96 | ); 97 | 98 | if (isOverRarity && 99 | (gachaRarityRates[idx].rarity || 100 | cardRarityTypeToRarity[gachaRarityRates[idx].cardRarityType]) < 3 101 | ) noOverRarityCount += 1 102 | } 103 | return tmpGachaResult 104 | } 105 | -------------------------------------------------------------------------------- /test.gs: -------------------------------------------------------------------------------- 1 | function testttt() { 2 | const contents = { 3 | parameter: { 4 | authToken: 'HoshinoIchika', 5 | data: 'event', 6 | sort: 'random', 7 | id: '3', 8 | search: 'null' 9 | } 10 | } 11 | console.log(contents.parameter) 12 | const response = doGet(contents) 13 | console.log(response) 14 | } 15 | 16 | const getCommitEventTEST = () => { 17 | const url = "https://api.github.com/repos/Sekai-World/sekai-master-db-diff/events"; 18 | const gitHubService = getGitHubService() 19 | const options = { 20 | headers: { Authorization: 'Bearer ' + gitHubService.getAccessToken() } 21 | } 22 | const cache_key = 'sekai_db_latest_commit_id' 23 | const cache = CacheService.getScriptCache() 24 | // UrlFetchでデータを取得して、JSONデータをパースする 25 | const response = JSON.parse(UrlFetchApp.fetch(url, options)) 26 | 27 | //前回のコミットID 28 | let commited_id = cache.get(cache_key) 29 | 30 | //もしキャッシュがなかったら最新のコミットIDを設定 31 | if (commited_id === null) { 32 | cache.put(cache_key, response[0].id) 33 | commited_id = response[0].id 34 | } 35 | //コミットIDの比較 36 | const commited = response.findIndex(elem => elem.id === commited_id) 37 | 38 | if (commited !== 0) { 39 | const events = response.slice(0, commited).filter(event => event.type === "PushEvent") 40 | let commitedFiles = [] 41 | 42 | for (let i in events) { 43 | const commitUrl = events[i].payload.commits[0].url 44 | const commit = JSON.parse(UrlFetchApp.fetch(commitUrl, options)) 45 | //console.log(commit) 46 | commitedFiles.push(commit.files[0].filename) 47 | let patch = commit.files[0].patch 48 | if (patch !== undefined) { 49 | patch = patch.replace(/\n/g, '').match(/\+ {.*?\}/g) //追加されたコンテンツのみにマッチ 50 | if (patch !== null) { 51 | const patch_add = patch.map(elem => elem.match(/\".*?\": .*?\,/g).map(elem2 => { 52 | const arr = elem2.slice(0, -1) 53 | .split(': ') 54 | .map(obj => obj.replace(/^"|"$/g, '')) 55 | const num = Number(arr[1]) 56 | if (!Number.isNaN(num)) arr[1] = num 57 | return arr 58 | })) 59 | 60 | const added = { 61 | file_name: commit.files[0].filename, 62 | created_at: events[0].created_at, 63 | url: commit.url, 64 | raw_url: commit.files[0].raw_url, 65 | content_url: commit.files[0].contents_url, 66 | contents: Object.fromEntries(patch_add[0]) 67 | } 68 | console.log(added) 69 | } 70 | } 71 | } 72 | cache.put(cache_key, response[0].id) 73 | 74 | new Set(commitedFiles).forEach(elem => console.log(elem)) 75 | } 76 | else { console.log('新しいコミットはありません') } 77 | } 78 | 79 | function setCacheTEST() { 80 | const cache_key = 'sekai_db_latest_commit_id' 81 | CacheService.getScriptCache().put(cache_key, '22115836116') 82 | } -------------------------------------------------------------------------------- /uploadIcon.gs: -------------------------------------------------------------------------------- 1 | 2 | /**アイコンの定期的なアップデート */ 3 | function iconUpdate() { 4 | const gitHubService = getGitHubService() 5 | const url = 'https://raw.githubusercontent.com/Sekai-World/sekai-master-db-diff/main/cards.json' 6 | const options = { 7 | headers: { Authorization: 'Bearer ' + gitHubService.getAccessToken() } 8 | } 9 | const obj = JSON.parse(UrlFetchApp.fetch(url, options).getContentText()) 10 | 11 | const folderId = '1EHtgKybN5OuVtkOM9QFPI93t4siY_BWJ' 12 | const files = DriveApp.getFolderById(folderId).getFiles() 13 | 14 | const driveFiles = [] 15 | 16 | while (files.hasNext()) { 17 | const file = files.next() 18 | const fileName = file.getName() 19 | driveFiles.push(fileName.slice(0, -4)) 20 | } 21 | 22 | const result = [] 23 | obj.filter(elem => { 24 | delete elem.cardParameters 25 | const indexElem = driveFiles.indexOf(elem.assetbundleName + '_normal') 26 | if (indexElem === -1) result.push(elem.id) 27 | 28 | if (elem.cardRarityType == 'rarity_3' || elem.cardRarityType == 'rarity_4') { 29 | const indexElemAT = driveFiles.indexOf(elem.assetbundleName + '_after_training') 30 | if (indexElemAT === -1) result.push(elem.id) 31 | } 32 | }) 33 | 34 | console.log(result) 35 | 36 | const minio_asset = 'https://sekai-res.dnaroma.eu' 37 | new Set(result).forEach(id => { 38 | const target = obj.filter(elem => elem.id === id)[0] 39 | console.log(target) 40 | let fileName = target.assetbundleName + '_normal' 41 | let value = { 42 | icon: `${minio_asset}/sekai-assets/thumbnail/chara_rip/${fileName}.png`, 43 | attr: target.attr, 44 | rarity: null, //0=birthday 45 | at: false //After Training 46 | } 47 | switch (target.cardRarityType) { 48 | case 'rarity_1': 49 | value.rarity = 1 50 | break 51 | case 'rarity_2': 52 | value.rarity = 2 53 | break 54 | case 'rarity_3': 55 | value.rarity = 3 56 | break 57 | case 'rarity_4': 58 | value.rarity = 4 59 | break 60 | case 'rarity_birthday': 61 | value.rarity = 0 62 | break 63 | } 64 | const presentaionId = '1Ruvay7C4NcyCBae_KFzAbgZpcaDgky4yYY9QuoRhwfI' 65 | 66 | const response = insertIcon(value,presentaionId) 67 | const fileUrl = convertPresentation_(presentaionId, response, 'png', 1, fileName) 68 | console.log(fileUrl.getUrl()) 69 | 70 | if (target.cardRarityType === 'rarity_3' || target.cardRarityType === 'rarity_4') { 71 | fileName = target.assetbundleName + '_after_training' 72 | value.icon = `${minio_asset}/sekai-assets/thumbnail/chara_rip/${fileName}.png` 73 | value.at = true 74 | const response = insertIcon(value,presentaionId) 75 | const fileUrl = convertPresentation_(presentaionId, response, 'png', 1, fileName) 76 | console.log(fileUrl.getUrl()) 77 | } 78 | }) 79 | console.log(result.length+'コのカードアイコンを追加しました') 80 | } 81 | 82 | function maketest() { 83 | const value = { 84 | icon: `https://minio.dnaroma.eu/sekai-assets/thumbnail/chara_rip/res018_no018_normal.png`, 85 | attr: 'cool', 86 | rarity: 4, //0=birthday 87 | at: false //After Training 88 | } 89 | 90 | const id = '1Ruvay7C4NcyCBae_KFzAbgZpcaDgky4yYY9QuoRhwfI' 91 | const response = insertIcon(value, id) 92 | console.log(response) 93 | 94 | const fileUrl = convertPresentation_(id, response, 'png', 1) 95 | console.log(fileUrl.getUrl()) 96 | } 97 | 98 | 99 | function insertIcon({ icon: icon, attr: attr, rarity: rarity, at: at }, presentaion_id = '1Ruvay7C4NcyCBae_KFzAbgZpcaDgky4yYY9QuoRhwfI') { 100 | let attrId 101 | switch (attr) { 102 | case 'cool': 103 | attrId = '1T_J5IOGIMviqtclP9RSx622wNf_4zVmm' 104 | break 105 | case 'cute': 106 | attrId = '184FAqwiBMnvqV6pVa5ySdgFnaWmnLfb-' 107 | break 108 | case 'happy': 109 | attrId = '1Ivodaeqb1cnC3Huq8YmB50Il7834nxmw' 110 | break 111 | case 'mysterious': 112 | attrId = '1BDiZUbqzeQgFs-ZzDv9EbbmtkBdZh7rE' 113 | break 114 | case 'pure': 115 | attrId = '1OXOkatcDonZJT7k5LmFoFAhrzQkgNcAS' 116 | break 117 | default: 118 | console.error(attr + ' is undefined.') 119 | } 120 | 121 | let frameId 122 | let raritySum 123 | switch (rarity) { 124 | case 1: 125 | frameId = '16DNCMGi1i007RdhVDCvyxSzDLs7duaMN' 126 | raritySum = 1 127 | break 128 | case 2: 129 | frameId = '1yLGEWB1f5wFrhQHzlIR4MF12lIKbsLl2' 130 | raritySum = 2 131 | break 132 | case 3: 133 | frameId = '1_jlmXJlKjglARXKhD03d7cTPRIB-DlXp' 134 | raritySum = 3 135 | break 136 | case 4: 137 | frameId = '10t-4bGw6nog6A_BnS7lqCZcwLGB518YE' 138 | raritySum = 4 139 | break 140 | case 0: 141 | frameId = '1cx82yK1-PISrS62sebCnyCbKmP-1KdZf' 142 | raritySum = 1 143 | break 144 | default: 145 | console.error('rarity is 0-4') 146 | } 147 | 148 | let starId 149 | switch (at) { 150 | case true: 151 | starId = '1UDKQsfBLNS4ptuV9flE11Y6yeri5ZLt8' 152 | break 153 | case false: 154 | starId = '1L97ceSqw3Mh2-ON_j3fEQqhsaqgTAiK4' 155 | break 156 | default: 157 | starId = '1L97ceSqw3Mh2-ON_j3fEQqhsaqgTAiK4' 158 | } 159 | if (rarity === 0) starId = '1vOnBQYNM4H2hsxFh0D0T-tUp42ztV8U8' 160 | 161 | const iconImg = { 162 | img: UrlFetchApp.fetch(icon).getBlob(), 163 | width: 280, 164 | height: 280, 165 | x: 16, 166 | y: 16 167 | } 168 | const frameImg = { 169 | img: DriveApp.getFileById(frameId).getBlob(), 170 | width: 312, 171 | height: 312, 172 | x: 0, 173 | y: 0 174 | } 175 | const attrImg = { 176 | img: DriveApp.getFileById(attrId).getBlob(), 177 | width: 70, 178 | height: 70, 179 | x: 2, 180 | y: 2 181 | } 182 | const starImg = { 183 | img: DriveApp.getFileById(starId).getBlob(), 184 | width: 56, 185 | height: 56, 186 | x: 20, 187 | y: 236 188 | } 189 | 190 | const imgArr = [iconImg, frameImg, attrImg] 191 | 192 | const presentation = SlidesApp.openById(presentaion_id) 193 | const slide = presentation.getSlides()[0] 194 | //前のオブジェクトをすべて削除 195 | for (let elem of slide.getPageElements()) elem.remove() 196 | 197 | for (let value of imgArr) { 198 | slide.insertImage(value.img) 199 | .setWidth(value.width * 0.75)//0.75=>ポイントからピクセルへ変換 200 | .setHeight(value.height * 0.75) 201 | .setLeft(value.x * 0.75) 202 | .setTop(value.y * 0.75) 203 | } 204 | 205 | //星の数 206 | for (let i = 0; i < raritySum; i++) { 207 | slide.insertImage(starImg.img) 208 | .setWidth(starImg.width * 0.75) 209 | .setHeight(starImg.height * 0.75) 210 | .setLeft((starImg.x + i * 55) * 0.75) //def=52 211 | .setTop(starImg.y * 0.75) 212 | } 213 | presentation.saveAndClose() 214 | 215 | return slide.getObjectId() 216 | } 217 | 218 | function convertPresentation_(presentation_id, page_id, format, slidesNumber, file_name = 'undefined') { 219 | format = format.toLowerCase(); 220 | let ext = format;//ファイル名の拡張子 221 | switch (format) { 222 | case "pptx": 223 | case "odp": 224 | case "pdf": 225 | case "txt": 226 | case "png": 227 | case "svg": 228 | break; 229 | 230 | case "jpg": 231 | case "jpeg": 232 | format = "jpeg"; 233 | ext = "jpg"; 234 | break; 235 | 236 | default: 237 | format = "pptx"; 238 | ext = "pptx" 239 | break; 240 | } 241 | 242 | const url = "https://docs.google.com/presentation/d/" + presentation_id + "/export/" + format + "?id=" + presentation_id + "&pageid=" + page_id; 243 | const options = { 244 | method: "get", 245 | headers: { "Authorization": "Bearer " + ScriptApp.getOAuthToken() }, 246 | muteHttpExceptions: true 247 | }; 248 | 249 | const response = UrlFetchApp.fetch(url, options) 250 | if (response.getResponseCode() === 200) { 251 | 252 | //フォルダIDを入れる https://drive.google.com/drive/folders/この部分がフォルダID 253 | 254 | const folder = DriveApp.getFolderById("1EHtgKybN5OuVtkOM9QFPI93t4siY_BWJ"); 255 | const presentaion = SlidesApp.openById(presentation_id); 256 | return folder.createFile(response.getBlob()).setName(file_name + '.' + ext); 257 | 258 | }//if 259 | }//end --------------------------------------------------------------------------------