Skip to content

Instantly share code, notes, and snippets.

@ochilab
Created August 5, 2026 02:22
Show Gist options
  • Select an option

  • Save ochilab/7c38d40a7206cff9d5aa187f1a2b3ecc to your computer and use it in GitHub Desktop.

Select an option

Save ochilab/7c38d40a7206cff9d5aa187f1a2b3ecc to your computer and use it in GitHub Desktop.
フォームで収集したスライドと動画の親フォルダを取得する
/**
* 現在開いているGoogleシートのB列にある対象シートURLを順に処理し、
* C列にスライド親フォルダURL、
* D列に動画親フォルダURLを書き込む。
*
* 一覧シート:
* A列: 任意
* B列: 対象GoogleシートURL
* C列: スライド親フォルダURL
* D列: 動画親フォルダURL
*/
function writeParentFolderUrls() {
const config = {
startRow: 2,
// 対象Googleシート内の設定
targetSheetName: '', // 空欄なら先頭シートを使用
headerRow: 1,
// 対象シート内の見出し名
slideUrlHeader: 'スライド',
videoUrlHeader: '動画'
};
const activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const listSheet = activeSpreadsheet.getActiveSheet();
const lastRow = listSheet.getLastRow();
if (lastRow < config.startRow) {
return;
}
const numberOfRows = lastRow - config.startRow + 1;
// 一覧シートB列のGoogleシートURL
const spreadsheetUrls = listSheet
.getRange(config.startRow, 2, numberOfRows, 1)
.getDisplayValues();
const results = spreadsheetUrls.map(([spreadsheetUrl]) => {
if (!spreadsheetUrl.trim()) {
return ['', ''];
}
try {
const result = getFolderUrlsFromSpreadsheet_(
spreadsheetUrl,
config
);
return [
result.slideParentFolderUrl,
result.videoParentFolderUrl
];
} catch (error) {
console.error(
`処理失敗: ${spreadsheetUrl}: ${error.message}`
);
return [
`エラー: ${error.message}`,
`エラー: ${error.message}`
];
}
});
// C列・D列へ一括出力
listSheet
.getRange(config.startRow, 3, results.length, 2)
.setValues(results);
// 見出しを必要に応じて設定
if (!listSheet.getRange(1, 3).getValue()) {
listSheet.getRange(1, 3).setValue('スライド親フォルダURL');
}
if (!listSheet.getRange(1, 4).getValue()) {
listSheet.getRange(1, 4).setValue('動画親フォルダURL');
}
}
/**
* 指定されたGoogleシートからスライドURL・動画URLを探し、
* それぞれの親フォルダURLを取得する。
*/
function getFolderUrlsFromSpreadsheet_(spreadsheetUrl, config) {
const spreadsheet = SpreadsheetApp.openByUrl(spreadsheetUrl);
const sheet = config.targetSheetName
? spreadsheet.getSheetByName(config.targetSheetName)
: spreadsheet.getSheets()[0];
if (!sheet) {
throw new Error('対象シートが見つかりません');
}
const lastRow = sheet.getLastRow();
const lastColumn = sheet.getLastColumn();
if (lastRow <= config.headerRow) {
throw new Error('対象シートにデータがありません');
}
const headers = sheet
.getRange(config.headerRow, 1, 1, lastColumn)
.getDisplayValues()[0]
.map(value => value.trim());
const slideColumn = findColumnByHeader_(
headers,
config.slideUrlHeader
);
const videoColumn = findColumnByHeader_(
headers,
config.videoUrlHeader
);
const slideUrl = getFirstUrlFromColumn_(
sheet,
slideColumn,
config.headerRow + 1,
lastRow
);
const videoUrl = getFirstUrlFromColumn_(
sheet,
videoColumn,
config.headerRow + 1,
lastRow
);
return {
slideParentFolderUrl: slideUrl
? getParentFolderUrl_(slideUrl)
: '',
videoParentFolderUrl: videoUrl
? getParentFolderUrl_(videoUrl)
: ''
};
}
/**
* 指定列から最初の有効なURLを取得する。
*
* Googleフォームのファイルアップロード回答では、
* セルの表示値ではなくリッチテキストのリンクとして
* URLが格納される場合もあるため、両方に対応する。
*/
function getFirstUrlFromColumn_(
sheet,
column,
startRow,
lastRow
) {
const numberOfRows = lastRow - startRow + 1;
const range = sheet.getRange(
startRow,
column,
numberOfRows,
1
);
const displayValues = range.getDisplayValues();
const richTextValues = range.getRichTextValues();
for (let i = 0; i < numberOfRows; i++) {
const richText = richTextValues[i][0];
if (richText) {
const directLink = richText.getLinkUrl();
if (directLink) {
return directLink;
}
const runs = richText.getRuns();
for (const run of runs) {
const link = run.getLinkUrl();
if (link) {
return link;
}
}
}
const text = displayValues[i][0].trim();
if (text) {
const url = extractFirstUrlFromText_(text);
if (url) {
return url;
}
}
}
return '';
}
/**
* ファイルURLから親フォルダURLを取得する。
*/
function getParentFolderUrl_(fileUrl) {
const fileId = extractDriveId_(fileUrl);
if (!fileId) {
throw new Error(`Drive IDを抽出できません: ${fileUrl}`);
}
const file = DriveApp.getFileById(fileId);
const parents = file.getParents();
if (!parents.hasNext()) {
throw new Error(`親フォルダがありません: ${fileUrl}`);
}
return parents.next().getUrl();
}
/**
* 見出し名から列番号を取得する。
*/
function findColumnByHeader_(headers, headerName) {
const normalizedTarget = normalizeHeader_(headerName);
const index = headers.findIndex(header => {
return normalizeHeader_(header) === normalizedTarget;
});
if (index === -1) {
throw new Error(
`見出し「${headerName}」が見つかりません`
);
}
return index + 1;
}
/**
* 見出し比較用の正規化。
*/
function normalizeHeader_(text) {
return String(text)
.trim()
.replace(/\s+/g, '')
.toLowerCase();
}
/**
* 文字列に含まれる最初のURLを取得する。
*/
function extractFirstUrlFromText_(text) {
const match = String(text).match(/https?:\/\/[^\s,]+/);
return match ? match[0] : '';
}
/**
* Google Drive関連URLからファイルIDを抽出する。
*/
function extractDriveId_(url) {
const text = String(url).trim();
const patterns = [
/\/d\/([a-zA-Z0-9_-]+)/,
/\/file\/d\/([a-zA-Z0-9_-]+)/,
/[?&]id=([a-zA-Z0-9_-]+)/,
/^([a-zA-Z0-9_-]{20,})$/
];
for (const pattern of patterns) {
const match = text.match(pattern);
if (match) {
return match[1];
}
}
return '';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment