SOURCE CODE
Apps Scriptへ登録するコード
次の3ファイルを、それぞれ対応するApps Scriptファイルへ貼り付けます。各「コードをコピー」ボタンは、表示を開かなくても全文をコピーできます。
公開用の初期コードです。個人の地点やカレンダーの情報は含まれていません。設定後のコードやデプロイしたURLを、公開の場へ転載しないでください。
1. サーバーコード
Apps Scriptのスクリプトファイルへ貼り付けます。表示名は Code.gs または コード.gs で構いません。冒頭の CONFIG で地域などを設定します。
コード全文を表示
/**
* 電子ペーパー風ダッシュボード(時計・天気・週間予定・ニュース)
*
* GASのWebアプリとして公開し、iPadなどのSafariで開きっぱなしにして使います。
*
* ■ はじめに設定するところ
* 下の CONFIG のうち「★」の付いた項目だけ、お住まいの地域に合わせて書き換えてください。
* それ以外は初期値のままで動きます。
*
* ■ 初回の準備
* 1. CONFIG を書き換えて保存
* 2. 上部の関数選択で「setup」を選び「実行」→ 権限の確認画面で許可
* (「Googleで確認されていません」と出ますが、作者はご自身なので
* 「詳細」→「(安全ではないページ)に移動」で進めて問題ありません)
* 3. 「デプロイ」→「新しいデプロイ」→ 種類「ウェブアプリ」
* 次のユーザーとして実行:自分 / アクセスできるユーザー:自分のみ
* ※「全員」にすると、URLを知っている人にあなたの予定が見えてしまいます
* 4. 表示されたURLをiPadのSafariで開く
*
* ■ データの出典
* 天気:MET Norway / Yr(https://www.met.no/ ・CC BY 4.0・商用利用も可)
* 予定:あなたのGoogleカレンダー(読み取りのみ)
* ニュース:NEWS_RSS に設定したRSS(初期値はNHKニュース)
*/
var CONFIG = {
// ★ 天気の地域。緯度・経度は Googleマップで地点を長押しすると表示されます
PLACE: '東京都千代田区', // 画面に出す地名(表示用なので自由に)
LAT: 35.69, // 緯度(小数2桁程度で十分)
LON: 139.75, // 経度
// ★ 天気欄をタップしたときに開くページ。空欄ならタップしても何も開きません
// 例:tenki.jp でお住まいの市区町村のページを開き、そのURLを貼る
WEATHER_URL: '',
// ニュースのRSS。初期値はNHKニュース(主要)。他のRSSのURLにも差し替え可
// NHKの分野別:cat1 社会/cat3 科学・医療/cat4 政治/cat5 経済/cat6 国際
NEWS_RSS: 'https://news.web.nhk/n-data/conf/na/rss/cat0.xml',
NEWS_COUNT: 8, // ニュースの表示件数
DAYS: 7, // 予定を表示する日数
REFRESH_MIN: 10, // 画面の自動更新間隔(分)。短くしすぎないでください
CALENDAR_EXCLUDE: [], // 表示しないカレンダー名(例: ['誕生日', '仕事'])
TZ: 'Asia/Tokyo'
};
function doGet() {
var t = HtmlService.createTemplateFromFile('index');
t.refreshMin = CONFIG.REFRESH_MIN;
t.place = CONFIG.PLACE;
return t.evaluate()
.setTitle('ダッシュボード')
.addMetaTag('viewport', 'width=device-width, initial-scale=1, maximum-scale=1');
}
/**
* 初回に一度エディタから実行して、権限の許可と動作確認を行う。
* 実行ログに各パーツの取得結果が出ます。
*/
function setup() {
var d = getData();
Logger.log('天気: ' + (d.weather.ok ? 'OK(' + d.weather.data.temp + '°)' : 'NG ' + d.weather.error));
Logger.log('予定: ' + (d.events.ok ? 'OK(' + d.events.data.length + '日分)' : 'NG ' + d.events.error));
Logger.log('ニュース: ' + (d.news.ok ? 'OK(' + d.news.data.length + '件)' : 'NG ' + d.news.error));
}
/** 画面から呼ばれる。各パーツが失敗しても他は表示できるよう個別に包む */
function getData() {
var weather = safe_(getWeather_);
if (weather.ok) weather.data.url = CONFIG.WEATHER_URL;
return {
weather: weather,
events: safe_(getEvents_),
news: safe_(getNews_),
updated: fmt_(new Date(), 'H:mm')
};
}
function safe_(fn) {
try {
return { ok: true, data: fn() };
} catch (e) {
return { ok: false, error: String((e && e.message) || e) };
}
}
function fmt_(d, pattern) {
return Utilities.formatDate(d, CONFIG.TZ, pattern);
}
function cached_(key, seconds, fn) {
var cache = CacheService.getScriptCache();
var hit = cache.get(key);
if (hit) return JSON.parse(hit);
var val = fn();
cache.put(key, JSON.stringify(val), seconds);
return val;
}
/* ---------- 天気(MET Norway / Yr、APIキー不要) ----------
* MET Norwayの利用条件では、連絡先の分かるUser-Agentの送信が必須です。
* 再配布して多くの方が使う場合は、下の連絡先をご自身のものに変えてください。 */
var WEATHER_UA = 'PNY-Dashboard/1.0 (+https://www.pny.llc/; support@pny.llc)';
function getWeather_() {
// 地点を変えたらすぐ反映されるよう、キャッシュのキーに座標を含める
return cached_('weather_metno_v1_' + CONFIG.LAT + '_' + CONFIG.LON, 1800, function () {
var url = 'https://api.met.no/weatherapi/locationforecast/2.0/complete'
+ '?lat=' + CONFIG.LAT + '&lon=' + CONFIG.LON;
var res = UrlFetchApp.fetch(url, {
headers: { 'User-Agent': WEATHER_UA },
muteHttpExceptions: true
});
var code = res.getResponseCode();
if (code !== 200) throw new Error('met.no ' + code);
var ts = JSON.parse(res.getContentText()).properties.timeseries;
// 現在(先頭の時刻)
var cur = ts[0].data;
var nowSym = (cur.next_1_hours && cur.next_1_hours.summary.symbol_code)
|| (cur.next_6_hours && cur.next_6_hours.summary.symbol_code) || '';
var nowW = sym_(nowSym);
// 日ごとに集約(最高・最低・代表天気・降水確率)
var byDay = {}, order = [];
for (var i = 0; i < ts.length; i++) {
var e = ts[i], dt = new Date(e.time), key = fmt_(dt, 'yyyy-MM-dd');
if (!byDay[key]) { byDay[key] = { temps: [], pops: [], sym: '', noon: '' }; order.push(key); }
var b = byDay[key];
var inst = e.data.instant && e.data.instant.details;
if (inst && inst.air_temperature != null) b.temps.push(inst.air_temperature);
var nx = e.data.next_6_hours || e.data.next_1_hours;
if (nx) {
if (nx.summary && nx.summary.symbol_code) {
if (!b.sym) b.sym = nx.summary.symbol_code;
var h = +fmt_(dt, 'H');
if (h >= 12 && h <= 15 && !b.noon) b.noon = nx.summary.symbol_code;
}
if (nx.details && nx.details.probability_of_precipitation != null) {
b.pops.push(nx.details.probability_of_precipitation);
}
}
}
var days = [];
for (var k = 0; k < order.length && days.length < 7; k++) {
var b2 = byDay[order[k]];
if (!b2.temps.length) continue;
var p = order[k].split('-');
var dow = '日月火水木金土'.charAt(new Date(Date.UTC(+p[0], +p[1] - 1, +p[2])).getUTCDay());
var w = sym_(b2.noon || b2.sym);
days.push({
label: (+p[1]) + '/' + (+p[2]),
dow: dow,
icon: w[0],
text: w[1],
max: Math.round(Math.max.apply(null, b2.temps)),
min: Math.round(Math.min.apply(null, b2.temps)),
pop: b2.pops.length ? Math.round(Math.max.apply(null, b2.pops)) : null
});
}
return { temp: Math.round(ts[0].data.instant.details.air_temperature), icon: nowW[0], text: nowW[1], days: days };
});
}
/** MET Norwayの symbol_code → [一文字表記, 日本語]。_day/_night等は無視し、キーワードで判定 */
function sym_(code) {
if (!code) return ['—', '—'];
var c = String(code).replace(/_(day|night|polartwilight)$/, '');
if (/thunder/.test(c)) return ['雷', '雷雨'];
if (/snow/.test(c)) return ['雪', /heavy/.test(c) ? '大雪' : '雪'];
if (/sleet/.test(c)) return ['雪', 'みぞれ'];
if (/rain/.test(c)) return ['雨', /heavy/.test(c) ? '強い雨' : (/shower/.test(c) ? 'にわか雨' : '雨')];
if (/fog/.test(c)) return ['霧', '霧'];
if (c === 'cloudy') return ['曇', '曇り'];
if (c === 'partlycloudy') return ['晴', '晴れ時々曇り'];
if (c === 'fair') return ['晴', '晴れ'];
if (c === 'clearsky') return ['晴', '快晴'];
return ['—', '—'];
}
/* ---------- 週間予定(Googleカレンダー・読み取りのみ) ---------- */
function getEvents_() {
var DAY = 86400000;
var now = new Date();
var sinceMidnight = ((+fmt_(now, 'H') * 60 + +fmt_(now, 'm')) * 60 + +fmt_(now, 's')) * 1000 + now.getMilliseconds();
var start = new Date(now.getTime() - sinceMidnight); // 今日0時(日本時間)
var end = new Date(start.getTime() + CONFIG.DAYS * DAY);
var days = [];
var index = {};
for (var i = 0; i < CONFIG.DAYS; i++) {
var d = new Date(start.getTime() + i * DAY);
var key = fmt_(d, 'yyyy-MM-dd');
index[key] = days.length;
days.push({
key: key,
url: 'https://calendar.google.com/calendar/r/day/' + fmt_(d, 'yyyy/M/d'),
label: fmt_(d, 'M/d'),
dow: '日月火水木金土'.charAt(Number(fmt_(d, 'u')) % 7),
holiday: '',
items: []
});
}
CalendarApp.getAllCalendars().forEach(function (cal) {
var name = cal.getName();
if (CONFIG.CALENDAR_EXCLUDE.indexOf(name) >= 0) return;
var isHoliday = /祝日|holiday/i.test(name);
var color = cal.getColor();
var calId = cal.getId();
cal.getEvents(start, end).forEach(function (ev) {
var url = eventUrl_(ev, calId);
if (ev.isAllDayEvent()) {
var s = ev.getAllDayStartDate().getTime();
var e = ev.getAllDayEndDate().getTime();
for (var t = Math.max(s, start.getTime()); t < e; t += DAY) {
var k = fmt_(new Date(t), 'yyyy-MM-dd');
if (!(k in index)) continue;
if (isHoliday) {
days[index[k]].holiday = ev.getTitle();
} else {
days[index[k]].items.push({ time: '終日', sort: '', title: ev.getTitle(), color: color, url: url });
}
}
} else {
var st = ev.getStartTime();
var k2 = fmt_(st.getTime() < start.getTime() ? start : st, 'yyyy-MM-dd');
if (!(k2 in index)) return;
days[index[k2]].items.push({
time: fmt_(st, 'H:mm'),
sort: fmt_(st, 'HH:mm'),
title: ev.getTitle(),
color: color,
url: url
});
}
});
});
days.forEach(function (day) {
day.items.sort(function (a, b) { return a.sort < b.sort ? -1 : a.sort > b.sort ? 1 : 0; });
});
return days;
}
/** 予定の詳細画面へのURL(Googleカレンダーの eid 形式)
* ※繰り返し予定では、その回ではなく繰り返しの本体が開くことがあります */
function eventUrl_(ev, calId) {
var eid = Utilities.base64Encode(ev.getId().split('@')[0] + ' ' + calId).replace(/=+$/, '');
return 'https://www.google.com/calendar/event?eid=' + encodeURIComponent(eid);
}
/* ---------- ニュース(RSS) ---------- */
function getNews_() {
return cached_('news_v3_' + CONFIG.NEWS_RSS, 900, function () {
var xml = UrlFetchApp.fetch(CONFIG.NEWS_RSS).getContentText();
var channel = XmlService.parse(xml).getRootElement().getChild('channel');
return channel.getChildren('item').slice(0, CONFIG.NEWS_COUNT).map(function (it) {
var pd = it.getChildText('pubDate');
var t = pd ? new Date(pd) : null;
return {
title: it.getChildText('title'),
url: it.getChildText('link') || '',
time: t && !isNaN(t.getTime()) ? fmt_(t, 'H:mm') : ''
};
});
});
}
SHA-256: cc520dab662099b26daa3156b9f66d097a14e1416813e1c3aa102c3611760c16
2. 画面
Apps ScriptでHTMLファイルを追加し、名前を必ず index にしてから貼り付けます。
コード全文を表示
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<meta charset="utf-8">
<style>
/* 電子ペーパー風:紙色の地に墨一色。色はこの2つだけ */
html, body { margin: 0; padding: 0; }
body {
background: #e6e4de; color: #111;
font-family: -apple-system, "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Noto Sans JP", sans-serif;
-webkit-text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
}
body.night { background: #111; color: #d8d6d0; }
.wrap {
display: -webkit-box; display: -webkit-flex; display: flex;
box-sizing: border-box; height: 100vh; padding: 28px 32px;
}
.col-left { -webkit-flex: 0 0 42%; flex: 0 0 42%; margin-right: 40px;
display: -webkit-flex; display: flex; -webkit-flex-direction: column; flex-direction: column; }
.col-right { -webkit-flex: 1; flex: 1; min-width: 0;
display: -webkit-flex; display: flex; -webkit-flex-direction: column; flex-direction: column; }
.sub { color: #6b6963; }
body.night .sub { color: #7d7b76; }
/* 見出し:太字+下に一本線 */
h2 {
font-size: 15px; font-weight: 700; letter-spacing: .12em;
margin: 0 0 12px; padding-bottom: 8px; border-bottom: 2px solid #111;
}
body.night h2 { border-color: #d8d6d0; }
section { margin-bottom: 26px; }
/* 日付と時計 */
.head { margin-bottom: 26px; }
#date { font-size: 18px; font-weight: 600; letter-spacing: .04em; }
#clock { font-size: 76px; font-weight: 300; line-height: 1; letter-spacing: -2px; margin-top: 6px; }
/* 天気 */
.now { display: -webkit-flex; display: flex; -webkit-align-items: center; align-items: center;
margin-bottom: 14px; }
.now-icon { width: 56px; height: 56px; line-height: 52px; text-align: center; font-size: 28px;
border: 2px solid #111; margin-right: 16px; box-sizing: border-box; }
body.night .now-icon { border-color: #d8d6d0; }
.now-temp { font-size: 44px; font-weight: 300; margin-right: 14px; }
.now-text { font-size: 16px; }
.week { display: -webkit-flex; display: flex; border-top: 1px solid #111; border-left: 1px solid #111; }
body.night .week, body.night .wd { border-color: #555; }
.wd { -webkit-flex: 1; flex: 1; text-align: center; font-size: 12px; padding: 6px 0;
border-right: 1px solid #111; border-bottom: 1px solid #111; }
.wd .ic { font-size: 18px; font-weight: 600; margin: 3px 0; }
.wd .hi { font-weight: 700; }
.wd.today { background: #111; color: #e6e4de; }
.wd.today .sub { color: #b5b3ad; }
body.night .wd.today { background: #d8d6d0; color: #111; }
body.night .wd.today .sub { color: #55534e; }
/* ニュース */
.news { -webkit-flex: 1; flex: 1; overflow: hidden; margin-bottom: 0; }
.news ul { list-style: none; margin: 0; padding: 0; }
.news li { font-size: 15px; line-height: 1.5; padding: 7px 0; border-bottom: 1px solid #c4c2bc;
display: -webkit-flex; display: flex; }
body.night .news li { border-color: #333; }
.news .nt { width: 46px; -webkit-flex-shrink: 0; flex-shrink: 0; font-size: 12px; padding-top: 2px;
font-variant-numeric: tabular-nums; }
/* 予定 */
/* 見出しは固定し、予定の中身だけをスクロールさせる */
.sched { -webkit-flex: 1; flex: 1; min-height: 0; margin-bottom: 0;
display: -webkit-flex; display: flex; -webkit-flex-direction: column; flex-direction: column; }
.sched-body { -webkit-flex: 1; flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; }
/* 下に続きがあるときだけ出す印。いちばん下までスクロールすると消える */
#more { display: none; -webkit-flex-shrink: 0; flex-shrink: 0; font-size: 12px; letter-spacing: .08em;
text-align: center; padding-top: 6px; border-top: 1px solid #111; }
body.night #more { border-color: #d8d6d0; }
.day { display: -webkit-flex; display: flex; padding: 10px 0; border-bottom: 1px solid #c4c2bc; }
body.night .day { border-color: #333; }
.day:last-child { border-bottom: 0; }
.day-head { width: 64px; -webkit-flex-shrink: 0; flex-shrink: 0; }
.day-date { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; }
.day-dow { font-size: 12px; }
.day-body { -webkit-flex: 1; flex: 1; min-width: 0; padding-top: 1px; }
.hol { font-size: 12px; margin-bottom: 2px; }
.today .day-head { margin: -10px 14px -10px 0; padding: 10px 0 10px 10px; width: 54px;
background: #111; color: #e6e4de; }
.today .day-head .sub { color: #b5b3ad; }
body.night .today .day-head { background: #d8d6d0; color: #111; }
body.night .today .day-head .sub { color: #55534e; }
.item { display: -webkit-flex; display: flex; font-size: 16px; line-height: 1.55; }
.item-time { width: 52px; -webkit-flex-shrink: 0; flex-shrink: 0; font-size: 13px; padding-top: 2px;
font-variant-numeric: tabular-nums; }
.allday { font-size: 11px; border: 1px solid currentColor; padding: 0 4px; margin-right: 8px;
position: relative; top: -1px; }
.empty { font-size: 13px; padding-top: 2px; }
/* タップできる行:見た目は変えず、押した瞬間だけ反転 */
a.tap { color: inherit; text-decoration: none; display: -webkit-flex; display: flex;
-webkit-tap-highlight-color: transparent; }
a.tap:active { background: #111; color: #e6e4de; }
body.night a.tap:active { background: #d8d6d0; color: #111; }
.news li { padding: 0; }
.news li a.tap { padding: 7px 0; width: 100%; }
a.day-head, a.weather-link { display: block; }
.credit { font-size: 10px; margin-top: 6px; text-align: right; }
.credit a { color: inherit; }
#status { position: fixed; right: 32px; bottom: 10px; font-size: 11px; }
@media (orientation: portrait) {
.wrap { display: block; height: auto; }
.col-left { margin: 0 0 8px; }
.sched { display: block; }
.sched-body { overflow: visible; }
}
</style>
</head>
<body>
<div class="wrap">
<div class="col-left">
<div class="head">
<div id="date"></div>
<div id="clock">--:--</div>
</div>
<section>
<h2>天気 <span class="sub" style="font-weight:400;letter-spacing:0"><?= place ?></span></h2>
<div id="weather" class="sub">読み込み中…</div>
<!-- MET Norwayの利用条件(CC BY 4.0)により出典の表示が必要。消さないでください -->
<div class="credit sub">Weather data from <a href="https://www.met.no/" target="_blank" rel="noopener">MET Norway</a> (CC BY 4.0)</div>
</section>
<section class="news">
<h2>ニュース</h2>
<div id="news" class="sub">読み込み中…</div>
</section>
</div>
<div class="col-right">
<section class="sched">
<h2>今週の予定</h2>
<div id="sched-body" class="sched-body">
<div id="events" class="sub">読み込み中…</div>
</div>
<div id="more">▼ 続きあり</div>
</section>
</div>
</div>
<div id="status" class="sub"></div>
<script>
// 古いiPadのSafariでも動くよう、ES5のみで書いている
var REFRESH_MIN = Number('<?= refreshMin ?>') || 10;
var DOW = '日月火水木金土';
function $(id) { return document.getElementById(id); }
function esc(s) {
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
});
}
function fail(r) { return '<span class="sub">取得できませんでした(' + esc(r.error) + ')</span>'; }
// http(s) のURLだけリンクにし、新しいタブで開く(ダッシュボード自体は残す)
function link(url, cls, inner) {
if (!/^https?:\/\//.test(url || '')) return '<div class="' + cls.replace('tap', '') + '">' + inner + '</div>';
return '<a class="' + cls + '" href="' + esc(url) + '" target="_blank" rel="noopener">' + inner + '</a>';
}
function done(id, html) { $(id).className = ''; $(id).innerHTML = html; }
function tick() {
var d = new Date();
var m = d.getMinutes();
$('clock').innerHTML = d.getHours() + ':' + (m < 10 ? '0' : '') + m;
$('date').innerHTML = (d.getMonth() + 1) + '月' + d.getDate() + '日(' + DOW.charAt(d.getDay()) + ')';
var h = d.getHours();
document.body.className = (h >= 22 || h < 6) ? 'night' : '';
}
function renderWeather(r) {
if (!r.ok) { $('weather').innerHTML = fail(r); return; }
var w = r.data, h = '';
h += '<div class="now"><span class="now-icon">' + esc(w.icon) + '</span>'
+ '<span class="now-temp">' + w.temp + '°</span><span class="now-text">' + esc(w.text) + '</span></div>';
h += '<div class="week">';
for (var i = 0; i < w.days.length; i++) {
var d = w.days[i];
h += '<div class="wd' + (i === 0 ? ' today' : '') + '">'
+ '<div>' + (i === 0 ? '今日' : esc(d.dow)) + '</div>'
+ '<div class="ic">' + esc(d.icon) + '</div>'
+ '<div><span class="hi">' + d.max + '</span><span class="sub">/' + d.min + '</span></div>'
+ '<div class="sub">' + (d.pop == null ? '' : d.pop + '%') + '</div></div>';
}
h += '</div>';
done('weather', link(w.url, 'weather-link tap', h));
}
function renderEvents(r) {
if (!r.ok) { $('events').innerHTML = fail(r); return; }
var h = '';
for (var i = 0; i < r.data.length; i++) {
var day = r.data[i];
h += '<div class="day' + (i === 0 ? ' today' : '') + '">';
h += link(day.url, 'day-head tap', '<div class="day-date">' + esc(day.label) + '</div>'
+ '<div class="day-dow sub">' + (i === 0 ? '今日・' : '') + esc(day.dow) + '</div>');
h += '<div class="day-body">';
if (day.holiday) h += '<div class="hol sub">' + esc(day.holiday) + '</div>';
if (!day.items.length) {
h += '<div class="empty sub">予定なし</div>';
} else {
for (var j = 0; j < day.items.length; j++) {
var it = day.items[j];
if (it.time === '終日') {
h += link(it.url, 'item tap', '<span><span class="allday">終日</span>' + esc(it.title) + '</span>');
} else {
h += link(it.url, 'item tap', '<span class="item-time sub">' + esc(it.time) + '</span><span>' + esc(it.title) + '</span>');
}
}
}
h += '</div></div>';
}
done('events', h);
updateMore();
}
// 予定の欄の下に続きがあるときだけ「▼ 続きあり」を出す(縦向きでは欄がスクロールしないので出ない)
function updateMore() {
var b = $('sched-body');
var rest = b.scrollHeight - b.scrollTop - b.clientHeight;
$('more').style.display = rest > 4 ? 'block' : 'none';
}
function renderNews(r) {
if (!r.ok) { $('news').innerHTML = fail(r); return; }
var h = '<ul>';
for (var i = 0; i < r.data.length; i++) {
h += '<li>' + link(r.data[i].url, 'tap', '<span class="nt sub">' + esc(r.data[i].time) + '</span><span>' + esc(r.data[i].title) + '</span>') + '</li>';
}
h += '</ul>';
done('news', h);
}
function load() {
google.script.run
.withSuccessHandler(function (data) {
renderWeather(data.weather);
renderEvents(data.events);
renderNews(data.news);
$('status').innerHTML = '更新 ' + data.updated;
})
.withFailureHandler(function () {
$('status').innerHTML = '更新に失敗しました(次回再試行)';
})
.getData();
}
$('sched-body').addEventListener('scroll', updateMore, false);
window.addEventListener('resize', updateMore, false);
window.addEventListener('orientationchange', function () { setTimeout(updateMore, 300); }, false);
tick();
load();
setInterval(tick, 20000);
setInterval(load, REFRESH_MIN * 60000);
</script>
</body>
</html>
SHA-256: 6e489a94d9314e5566f899b4bdcbed455adbc95769dfbae20648c75c964facf4
3. マニフェスト
[プロジェクトの設定]で「『appsscript.json』マニフェスト ファイルをエディタで表示する」をオンにし、表示された appsscript.json の中身と置き換えます。
コード全文を表示
{
"timeZone": "Asia/Tokyo",
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/calendar.readonly",
"https://www.googleapis.com/auth/script.external_request"
],
"webapp": {
"executeAs": "USER_DEPLOYING",
"access": "MYSELF"
}
}
SHA-256: b69fd8c52fbfdd915694ba096152d769bdd8f8b09b8bb1c89fa0fbd52d187e12