0
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Google Homeに声をかけずに天気とGoogleカレンダーの予定を言わせる

Last updated at Posted at 2018-07-14

Google Homeに声をかけずに天気を言わせるを拡張して
Google Homeに声をかけずに天気とGoogleカレンダーの予定を言わせるようにしてみました。

やりたいこと

  • 声をかけずに天気とGoogleカレンダーの予定を言わせたい
  • 会社の予定と個人の予定をまとめて教えて欲しい
  • 朝が早い予定がある場合は警告して欲しい

Google APIを使用するためのClient ID&Client Secretを取得する

こちら等を参考にGoogle Developer ConsoleにアクセスしてGoogle APIを使用するための
Client IDとClient Secretを作成する。
作成したついでにリダイレクトURLに http://localhost を登録しておく。
この作業を会社のアカウントと個人のアカウント両方で行う。

Google APIのリフレッシュトークンを取得する

こちら等を参考にGoogle APIで使用するアクセストークンを取得するためにリフレッシュトークンを取得する。
こちらも会社のアカウントと個人のアカウント両方のリフレッシュトークンを用意。

天気&スケジュールつぶやきスクリプト作成

必要なパッケージのインストール

package.json
{
  "name": "google-home",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "google-home-notifier": "^1.2.0",
    "googleapis": "^28.1.0",
    "sync-request": "^6.0.0"
  }
}
$ npm install

OAuthで必要な情報を記述したjson

google-api-info.json
{
  "own": {
    "client_id": "${個人のアカウントのClient ID}",
    "client_secret": "${個人のアカウントのClient Secret}",
    "redirect_url": "http://localhost",
    "refresh_token": "${個人のアカウントのリフレッシュトークン}"
  },
  "company": {
    "client_id": "${会社のアカウントのClient ID}",
    "client_secret": "${会社のアカウントのClient Secret}",
    "redirect_url": "http://localhost",
    "refresh_token": "${会社のアカウントのリフレッシュトークン}"
  }
}

天気情報取得スクリプト

weather.js
'use strict';

const request = require('sync-request');
const darkSkyUrl = 'https://api.darksky.net/forecast/${Secert Key}/${緯度},${経度}?lang=ja&units=si';

const options = {
  headers: {'Content-Type': 'application/json'}
};

const getDate = (unixTime) => {
    const date = unixTime ? new Date(unixTime*1000) : new Date();
    return `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()}`;
};

const getTodayData = hourly => {
  const today = getDate();
  const todayData = hourly.filter(hourlyData => {
    const date = getDate(hourlyData.time);
    return today === date;
  });
  const temps = todayData.map(data => { return data.temperature; });
  const precipProbabilities = todayData.map(data => { return data.precipProbability; });
  const maxTemp = Math.max.apply(null, temps);
  const minTemp = Math.min.apply(null, temps);
  const precipProbability = Math.max.apply(null, precipProbabilities);
  return {
    maxTemp: Math.round(maxTemp),
    minTemp: Math.round(minTemp),
    precipProbability: Math.round(precipProbability * 100)
  };
};

const createMessage = body => {
  const currently = body.currently;
  const weather = currently.summary;

  const todayData = getTodayData(body.hourly.data);
  const maxTemp = todayData.maxTemp;
  const minTemp = todayData.minTemp;
  const precipProbability = todayData.precipProbability;

  const message = `今日の天気は${weather}、最高気温${maxTemp}度、最低気温${minTemp}度、降水確率${precipProbability}%です。`;
  return message;
};

exports.fetchWeatherInfo = () => {
  const response = request('GET', darkSkyUrl, options);
  const body = JSON.parse(response.getBody('utf-8'));
  return createMessage(body);
};

カレンダー情報取得スクリプト

google-calendar.js
'use strict';

const API_INFO = require("./google-api-info.json")
const { google } = require('googleapis');
const OAuth2 = google.auth.OAuth2;

const today = () => {
  const date = new Date();
  return `${date.getFullYear()}-${('0'+(date.getMonth()+1)).slice(-2)}-${('0'+date.getDate()).slice(-2)}`;
};

const isToday = (date) => {
  const regexp = new RegExp(`^${today()}`);
  return regexp.test(date);
};

const time = (dateString) => {
  const date = new Date(dateString);
  return `${date.getHours()}:${('0'+date.getMinutes()).slice(-2)}`;
};

const getTodaySchedules = (scheduleData) => {
  const schedules = scheduleData.filter((schedule) => {
    return schedule.start.date && isToday(schedule.start.date);
  }).map((schedule) => {
    return schedule.summary;
  });
  return schedules;
};

const getHourlySchedules = (scheduleData) => {
  const regexp = new RegExp(`^${today()}`);
  const schedules = scheduleData.filter((schedule) => {
    return schedule.start.dateTime && isToday(schedule.start.dateTime);
  }).map((schedule) => {
    return { time: time(schedule.start.dateTime), summary: schedule.summary };
  });
  return schedules;
};

const mergeSchedules = (mySchedules, companySchedules) => {
  const todaySchedule = mySchedules.todaySchedule.concat(companySchedules.todaySchedule);
  const hourlySchedule = mySchedules.hourlySchedule.concat(companySchedules.hourlySchedule).sort((a,b) => {
    if (Number(a.time) < Number(b.time)) return - 1;
    if (Number(a.time) > Number(b.time)) return 1;
    return 0;
  });
  return { todaySchedule: todaySchedule, hourlySchedule: hourlySchedule };
};

const createMessage = (scheduleInfo) => {
  let message = '今日の予定は、';
  let isEarly = false;
  const todaySchedule = scheduleInfo.todaySchedule;
  const hourlySchedule = scheduleInfo.hourlySchedule;
  if (!todaySchedule.length && !hourlySchedule.length) {
    return '本日登録されている予定はありません。';
  }
  if (todaySchedule.length) {
    message += `${todaySchedule.join('と、')}、`;
  }
  if (hourlySchedule.length) {
    const hourly = hourlySchedule.map(schedule => {
      return `${schedule.time}から${schedule.summary}`;
    }).join('');
    message += `${hourly}、`;
    isEarly = !!(hourlySchedule.filter(schedule => {
      return Number(schedule.time.replace(':', '')) < 1030;
    }).length);
  }
  message += 'です。';
  if (isEarly) message += '朝一の予定があるので、早めに起きてください。';
  return message;
};

const fetchTokens = async (apiInfo) => {
  const oauth2Client = new OAuth2(
    apiInfo.client_id, apiInfo.client_secret, apiInfo.redirect_url
  );
  oauth2Client.setCredentials({ refresh_token: apiInfo.refresh_token });

  const oauthPromise = new Promise((resolve, reject) => {
    oauth2Client.refreshAccessToken((err, response) => {
      if (err) return reject(err);
      resolve(response);
    });
  });
  const tokens = await oauthPromise;
  oauth2Client.credentials = tokens;
  return oauth2Client;
};

const fetchEvents = async (apiInfo) => {
  const auth = await fetchTokens(apiInfo);
  const today = new Date(new Date().setHours(0, 0, 0, 0));
  const timeMin = today.toISOString();
  const calendar = google.calendar({ version: 'v3', auth: auth});
  const calendarPromise = new Promise((resolve, reject) => {
    calendar.events.list({
      calendarId: 'primary',
      timeMin: timeMin,
      maxResults: 20,
      singleEvents: true,
      orderBy: 'startTime'
    }, (err, res) => {
      if (err) return reject(err);
      resolve(res);
    });
  });
  const response = await calendarPromise;
  const scheduleData = response.data.items;
  return {
    todaySchedule: getTodaySchedules(scheduleData),
    hourlySchedule: getHourlySchedules(scheduleData)
  };
};

const fetchSchedules = async () => {
  const mySchedules = await fetchEvents(API_INFO.own);
  const companySchedules = await fetchEvents(API_INFO.company);
  const schedule = mergeSchedules(mySchedules, companySchedules);
  return createMessage(schedule);
};

exports.fetchMySchedules = () => {
  return fetchSchedules();
};

Google Homeつぶやきスクリプト

notifier.js
'use strict';

const googlehome = require('google-home-notifier');
const googleCalendar = require('./google-calendar.js');
const weather = require('./weather.js');

const createMessage = async () => {
  const weatherInfo = weather.fetchWeatherInfo();
  const scheduleInfo = await googleCalendar.fetchMySchedules();
  const message = `おはようございます。${weatherInfo}${scheduleInfo}`;
  return message;
};

const notify = async (message) => {
  const text = await message;
  googlehome.device("Google-Home", 'ja');
  googlehome.notify(text, res => {
    console.log(text);
  });
};

notify(createMessage());

実行

$ node notifier.js

補足

文字数が多いとGoogleHomeがつぶやいてくれなかった。。
あと突然スクリプトが動かなくなって調べてみたら原因はこれだったので
node_modules/google_tts を最新にして解決した。

0
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?