diff --git a/app.js b/app.js index 939121b..2cb7db5 100644 --- a/app.js +++ b/app.js @@ -3,58 +3,34 @@ const photoapi = require("./photoAPI"); const {capture} = require("./caputure"); const slack = require("./slack"); +const utils = require("./utils"); const main = async () => { - //認証鍵の設定 - const {client_id, client_secret} = process.env; - if (!client_id || !client_secret) { - console.log("READMEに従ってGoogle Photos APIsの認証鍵を設定してください"); - process.exit(1) - } + //環境設定 + const {client_id, client_secret, slack_token} = process.env; const oAuth2Client = await photoapi.getOAuthToken(client_id, client_secret); - const bot = new slack.Slack(); + const slackBot = new slack.Slack(slack_token); - //共有するためアルバムを指定 + //アルバム, 共有のの設定 const albumTitle = "bushitsuchan_album"; const albums = await photoapi.getAlbumList(oAuth2Client); - let album = albums.filter((album) => album.title === albumTitle)[0]; - - if (album === undefined) { + let album = albums.filter(album => album.title === albumTitle)[0]; + if (!album) { album = await photoapi.createAlbum(oAuth2Client, albumTitle); await photoapi.shareAlbum(oAuth2Client, album.id) } - bot.start(); - - //定期的に撮影した写真の共有リンクをslackbotで送信 - //https://developers.google.com/photos/library/guides/api-limits-quotas に抵触しないように!! - /** - * 何msに一回実行するか あまり小さくしすぎるとエラーが発生します - * @type {number} - */ - const interval = 10 * 1000; - if (60 * 60 * 24 * 1000 / 10000 * 3 > interval) { - console.log(`注意: 1日あたり${(60 * 60 * 24 * 3 / interval * 1000).toLocaleString()}回PhotoAPIを叩く設定で,1日の上限10,000回を越してしまいます`) - } - setInterval(async () => { - const url = await capture(oAuth2Client, album).catch(e => { - console.error(e.name); - if (e.name === "StatusCodeError") { - console.error(JSON.parse(e.error).error.message); - return - } - console.error(e.message) - // console.error(e) - }); - if (!url) { - return - } - const shortURL = await photoapi.getShortURL(url); - - // ここをカスタマイズしてください - bot.url = shortURL; - // slack.send(shortURL); - }, interval) + slackBot.getReplyText = async () => { + const photo = await capture(0, ".png"); + const uploadToken = await photoapi.uploadPhoto(oAuth2Client, photo, Date().toLocaleString()); + const {mediaItem} = await photoapi.createAlbumMediaItem(oAuth2Client, album.id, uploadToken, ""); + const {baseUrl} = await photoapi.getMediaItem(oAuth2Client, mediaItem.id); + const shortUrl = await utils.getShortURL(baseUrl); + return shortUrl; + }; + + //slackbotの開始 + slackBot.start(); }; diff --git a/caputure.js b/caputure.js index 406d0db..0400b67 100644 --- a/caputure.js +++ b/caputure.js @@ -1,45 +1,9 @@ "use strict"; -const photoapi = require("./photoAPI"); -const NodeWebcam = require('node-webcam'); -const slack = require("./slack"); +const cv = require('opencv4nodejs'); - -const Webcam = NodeWebcam.create({ - width: 1280, - height: 720, - quality: 100, - - delay: 0, - saveShots: true, - device: false, - callbackReturn: "buffer", - verbose: false -}); - -/** - * 写真を撮影しGooglePhotoへとアップロード,その共有リンクを取得します - * @param {oAuth2Client} oAuth2Client - photoapi.getOAuthToken関数で取得します - * @param {Object} album - * @returns {Promise} - */ -module.exports.capture = async (oAuth2Client, album) => { - const photo = await new Promise((resolve, reject) => { - Webcam.capture("capture", (err, photo) => { - if (err) { - reject(err) - } - resolve(photo) - }) - }).catch(e => { - console.error(e); - console.error( - "READMEに従ってカメラを使えるようにしてください\n" + - "また,OSやセキュリティソフトでカメラへのアクセスをブロックしている可能性もあります 解除してください\n"); - process.exit(1) - }); - const uploadToken = await photoapi.uploadPhoto(oAuth2Client, photo, Date().toLocaleString()); - const {mediaItem} = await photoapi.createAlbumMediaItem(oAuth2Client, album.id, uploadToken, ""); - const {baseUrl} = await photoapi.getMediaItem(oAuth2Client, mediaItem.id); - return baseUrl -}; \ No newline at end of file +module.exports.capture = async (devicePort = 0, ext = ".png") => { + const cap = new cv.VideoCapture(devicePort); + const frame = cap.read(); + return cv.imencode(ext, frame); +}; diff --git a/package.json b/package.json index 8fd0d5e..5860b13 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@slack/client": "^4.10.0", "googleapis": "^35.0.0", "node-webcam": "^0.4.6", + "opencv4nodejs": "^4.14.1", "path": "^0.12.7", "readline": "^1.3.0", "request": "^2.88.0", diff --git a/photoAPI.js b/photoAPI.js index fadb53f..b816da4 100644 --- a/photoAPI.js +++ b/photoAPI.js @@ -4,25 +4,11 @@ const fs = require("fs"); const {google} = require("googleapis"); const readline = require("readline"); const path = require("path"); -const rp = require("request-promise"); +const {rpap} = require("./utils"); // const assert = require("assert"); - -const rpap = rp.defaults({ - "transform": (body, response) => { - const constentType = response.headers["content-type"].split("")[0]; - if (constentType === "application/json") { - return JSON.parse(body) - } else if (constentType === "text/plain") { - return body - } else { - return body - } - } -}); - /** - * @typedef {Object} oAuth2Client + * @typedef {Object} OAuth2Client * @property {function: string} getAccessToken */ @@ -30,9 +16,14 @@ const rpap = rp.defaults({ * 認証鍵を取得します * @param {string} client_id - GCPで取得したクライアントID * @param {string} client_secret - GCPで取得したクライアントシークレット - * @returns {Promise} + * @returns {Promise} */ -module.exports.getOAuthToken = (client_id, client_secret) => { +module.exports.getOAuthToken = async (client_id, client_secret) => { + if (!client_id || !client_secret) { + throw new TypeError("Google Photos APIsの認証キーがロードできていません。" + + "READMEに従ってdirenvの設定をしてください。"); + } + const oAuth2Client = new google.auth.OAuth2( client_id, client_secret, @@ -46,13 +37,13 @@ module.exports.getOAuthToken = (client_id, client_secret) => { const tokenPath = path.join(__dirname, "token.json"); if (fs.existsSync(tokenPath)) { oAuth2Client.setCredentials(require(tokenPath)); - return oAuth2Client + return oAuth2Client; } + const authURL = oAuth2Client.generateAuthUrl({ access_type: "offline", scope: scopes }); - console.log(`以下のサイトを開き,認証したあと表示される文字列をここに貼り付けてください\n${authURL}`); const rl = readline.createInterface({ input: process.stdin, @@ -76,7 +67,7 @@ module.exports.getOAuthToken = (client_id, client_secret) => { /** * アルバム一覧の取得 - * @param {oAuth2Client} oAuth2Client - getOAuthToken関数で取得します + * @param {OAuth2Client} OAuth2Client - getOAuthToken関数で取得します * @returns {Promise>} */ module.exports.getAlbumList = async oAuth2Client => { @@ -90,13 +81,13 @@ module.exports.getAlbumList = async oAuth2Client => { method: "GET", headers: headers }) - .then(response => JSON.parse(response)["albums"]) + .then(response => response["albums"]) }; /** * 画像のバイナリデータを送信します - * @param {oAuth2Client} oAuth2Client - getOAuthToken関数で取得します + * @param {OAuth2Client} OAuth2Client - getOAuthToken関数で取得します * @param photo * @param {string} filename * @returns {Promise} uploadToken @@ -120,7 +111,7 @@ module.exports.uploadPhoto = async (oAuth2Client, photo, filename) => { /** * アップロードした画像を単なる写真として保存します - * @param {oAuth2Client} oAuth2Client - getOAuthToken関数で取得します + * @param {OAuth2Client} OAuth2Client - getOAuthToken関数で取得します * @param {string} uploadToken - uploadPhoto関数で取得します * @param {string} description * @returns {Promise>} @@ -153,7 +144,7 @@ module.exports.createMediaItem = async (oAuth2Client, uploadToken, description) /** * アップロードした画像をアルバムに追加します - * @param {oAuth2Client} oAuth2Client - getOAuthToken関数で取得します + * @param {OAuth2Client} OAuth2Client - getOAuthToken関数で取得します * @param {string} albumID * @param {string} uploadToken - uploadPhoto関数で取得します * @param {string} description @@ -182,14 +173,14 @@ module.exports.createAlbumMediaItem = async (oAuth2Client, albumID, uploadToken, headers: headers, body: JSON.stringify(body) }) - .then(response => JSON.parse(response)["newMediaItemResults"][0]) - .catch(e => console.error(e)) + .then(response => response["newMediaItemResults"][0]) + // .catch(e => console.error(e)) }; /** * アルバムを作成します - * @param {oAuth2Client} oAuth2Client - getOAuthToken関数で取得します + * @param {OAuth2Client} OAuth2Client - getOAuthToken関数で取得します * @param {string} title * @returns {Promise} */ @@ -214,7 +205,7 @@ module.exports.createAlbum = async (oAuth2Client, title) => { /** * アルバムを共有します - * @param {oAuth2Client} oAuth2Client - getOAuthToken関数で取得します + * @param {OAuth2Client} OAuth2Client - getOAuthToken関数で取得します * @param {string} albumID * @returns {Promise} */ @@ -241,7 +232,7 @@ module.exports.shareAlbum = async (oAuth2Client, albumID) => { /** * アップロード済みの写真に関する情報を取得します - * @param {oAuth2Client} oAuth2Client - getOAuthToken関数で取得します + * @param {OAuth2Client} OAuth2Client - getOAuthToken関数で取得します * @param {string} mediaItemID * @returns {Promise} */ @@ -256,17 +247,6 @@ module.exports.getMediaItem = async (oAuth2Client, mediaItemID) => { method: "GET", headers: headers, }) - .then(response => JSON.parse(response)) -}; - -/** - * 与えられたURLの短縮URLを取得します - * @param {string} url - * @returns {Promise} 短縮URL - */ -module.exports.getShortURL = url => { - return rpap.get(`http://is.gd/create.php?format=simple&format=json&url=${url}`) - .then(result => JSON.parse(result)["shorturl"]) }; async function main() { diff --git a/slack.js b/slack.js index 0a14c29..cc22708 100644 --- a/slack.js +++ b/slack.js @@ -1,26 +1,17 @@ "use strict"; -const {RTMClient, WebClient} = require('@slack/client'); -const rp = require("request-promise"); - -const rpap = rp.defaults({ - transform: (body, response) => { - const constentType = response.headers["content-type"].split(";")[0]; - if (constentType === "application/json") { - return JSON.parse(body); - } else if (constentType === "text/plain") { - return body; - } else { - return body; - } - } -}); - -const wait = time => new Promise(resolve => setTimeout(resolve, time)); +const {RTMClient, WebClient} = require('@slack/client'); +const utils = require("./utils"); module.exports.Slack = class Slack { - constructor() { - const {slack_token} = process.env; + /** + * @param {string} slack_token + */ + constructor(slack_token) { + if (!slack_token) { + throw new TypeError("Slcak botの認証キーがロードできていません。" + + "READMEに従ってdirenvの設定をしてください。"); + } this.rtm = new RTMClient(slack_token); this.web = new WebClient(slack_token); this.url = "not yet"; @@ -28,44 +19,49 @@ module.exports.Slack = class Slack { start() { this.rtm.start(); + this.rtm.on("ready", () => console.log("ready")); this.rtm.on("message", message => this.reply(message)); } - async reply(message) { - // console.debug(`message: ${JSON.stringify(message)}\n`); - const {user, text, channel, subtype, ts} = message; - if (subtype === "bot_message" || subtype === "channel_join" || subtype === "group_join") { - return - } else if (subtype === "message_changed") { - return - } else if (subtype === "message_deleted") { - return - } else if (subtype === "message_replied") { - return - } else if (subtype) { - return - } + async getReplyText() { + throw Error("NotImplementedError"); + } - console.log(user, this.rtm.activeUserId); - if (user === this.rtm.activeUserId) { - return - } else if (!text || text === "/") { - return + /** + * @param{Object} receiveMessage + */ + reply(receiveMessage) { + const {user, text, channel, subtype, ts} = receiveMessage; + if (subtype) { + // console.log(subtype); + return; + } else if (user === this.rtm.activeUserId) { + return; + } else if (!text) { + return; + } else if (!text.match(new RegExp(`<@${this.rtm.activeUserId}>`))) { + return; + } + if (text.match(/ip$/)) { + const ips = utils.getLocalIps(); + this.web.chat.postMessage({ + channel: channel, + text: ips.map(ip => `address: ${ip}`).join("\n"), + as_user: true, + thread_ts: ts + }); + return; } - const replyText = this.url; - - const response = await this.web.chat.postMessage({ - channel: channel, - text: replyText, - as_user: true, - thread_ts: message.thread_ts || message.event_ts - }); - console.info(`Message sent: ${response.message.text}`); + this.getReplyText() + .then(replyText => + this.web.chat.postMessage({ + channel: channel, + text: replyText, + as_user: true, + thread_ts: ts + }) + ) + // console.info(`Message sent: ${response.message.text}` } -}; - - -module.exports.send = value => { - console.log(`未実装ですが以下を投稿したことになりました ${value}`) }; \ No newline at end of file diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..e65788e --- /dev/null +++ b/utils.js @@ -0,0 +1,49 @@ +"use strict"; + +const rp = require("request-promise"); +const os = require("os"); + + +const rpap = rp.defaults({ + "transform": (body, response) => { + const constentType = response.headers["content-type"]; + if (constentType.match(/application\/json/)) { + return JSON.parse(body) + } else if (constentType.match(/"text\/plain/)) { + return body + } else { + return body + } + } +}); +module.exports.rpap = rpap; + +/** + * 与えられたURLの短縮URLを取得します + * @param {string} url + * @returns {Promise} 短縮URL + */ +module.exports.getShortURL = async url => { + const response = await rpap.get(`http://is.gd/create.php?format=simple&format=json&url=${url}`); + return JSON.parse(response)["shorturl"]; +}; + +/** + * @return {Array} + */ +module.exports.getLocalIps = () => { + const interfaces = os.networkInterfaces(); + const ipList = []; + for (const i in interfaces) { + for (const iface of interfaces[i]) { + if (iface.family !== 'IPv4' || iface.internal) { + continue; + } + ipList.push(iface.address); + } + } + if (ipList.length === 0) { + throw Error("IP Adressが見つかりません"); + } + return ipList; +}; \ No newline at end of file