tmp.php

// TODO: 後で修正する

VenturaにアプデしたらXcodeが動かなくなった

Venturaにアップデートしてから$ react-native iosがエラーを吐くようになった

TOC

やったこと

コマンドラインツールの再インストール

エラー

xcodebuild: error: An error occurred establishing a connection to the installation service.

対策

$ sudo rm -rf /Library/Developer/CommandLineTools
$ xcode-select --install

Xcodeのversionの切り替え

マイナーバージョンを切り替えて複数の13.xのXcodeをrenameして使ってて、アプデのタイミングで13系を削除したから出た

(Venturaへのアプデには無関係で、アプリをrenameしたり消してない場合は出ないと思う)

エラー

xcrun: error: active developer path ("/Applications/Xcode_13.2.1.app/Contents/Developer") does not exist
Use `sudo xcode-select --switch path/to/Xcode.app` to specify the Xcode that you wish to use for command line developer tools, or use `xcode-select --install` to install the standalone command line developer tools.
See `man xcode-select` for more details.

対策

エラーメッセージの通りにアプリを切り替える

$ sudo xcode-select --switch /Applications/Xcode.app

ライセンスへの同意

コマンドラインツールの再インストールのときに同意したはずだけど、なぜか2回聞かれた

エラー

You have not agreed to the Xcode license agreements, please run 'sudo xcodebuild -license' from within a Terminal window to review and agree to the Xcode license agreements.

対策

$ sudo xcodebuild -license
Password:

パスワードを入れたら同意文が表示されるので脳死agreeする

By typing 'agree' you are agreeing to the terms of the software license agreements. Type 'print' to print them or anything else to cancel, [agree, print, cancel] agree

You can view the license agreements in Xcode's About Box, or at /Applications/Xcode.app/Contents/Resources/en.lproj/License.rtf

エラーは出てないけどやったこと

キャッシュ系

$ rm -rf $HOME/Library/Caches/com.apple.dt.Xcode/
$ rm -rf ~/Library/Developer/Xcode/DerivedData/*

シミュレーター系

$ sudo xcrun simctl shutdown all
$ sudo xcrun simctl erase all

最後に

未だにbashを使っていることがバレる

Dockerの開発環境を自己証明書でSSL対応する

前提

  • Dockerイメージにはphp-apacheを使用

ディレクトリ構成

.
├── docker/
│   ├── Dockerfile
│   ├── apache2/
│   │   └── sites-available/
│   └── ssl/
└── docker-compose.yml

証明書の作成

作成前に秘密鍵たちを格納しておくディレクトリを作っておく

$ mkdir docker/ssl
$ cd docker/ssl

1. 秘密鍵の作成

$ openssl genrsa -out server.key 2048

2. CSRファイルの作成

Common Namelocalhostを指定、それ以外はEnterでOK

$ openssl req -new -sha256 -key server.key -out server.csr

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:
State or Province Name (full name) []:
Locality Name (eg, city) []:
Organization Name (eg, company) []:
Organizational Unit Name (eg, section) []:
Common Name (eg, fully qualified host name) []:localhost
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:

3. 証明書の作成1

$ echo "subjectAltName = DNS:localhost" > server.txt
$ openssl x509 -req -sha256 -days 3650 -signkey server.key -in server.csr -out server.crt -extfile server.txt

証明書作成後はserver.txtは削除してOK

4. default-ssl.confファイルのコピーとマウント

dockerのコンテナに入ってdefault-ssl.confをコピーする

$ docker exec -it container-name bash
$ cp /etc/apache2/sites-available/default-ssl.conf docker/apache2/sites-available/

5. default-ssl.confの修正

- SSLCertificateFile  /etc/ssl/certs/ssl-cert-snakeoil.pem
- SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
+ SSLCertificateFile    /etc/ssl/certs/server.crt
+ SSLCertificateKeyFile /etc/ssl/private/server.key

6. docker-comose.ymlの修正

  # 省略
  web:
    # 省略
    volumes:
      # 省略
      - ./docker/apache2/sites-available/default-ssl.conf:/etc/apache2/sites-available/default-ssl.conf # 追加
      - ./docker/ssl/server.crt:/etc/ssl/certs/server.crt # 追加
      - ./docker/ssl/server.key:/etc/ssl/private/server.key # 追加
    ports:
      - {http通信で使いたいport番号}:80
      - {https通信で使いたいport番号}:443 # 追加
# 省略    

7. Dockerfileでsslを有効にする

# 省略
RUN a2enmod ssl && a2ensite default-ssl
# 省略

buildし直してhttpsで接続できればOK

$ docker-compose build --no-cache && docker-compose up -d

参考

qiita.com


  1. 10年で作成しています。

TypeScript x axios で配列を非同期で処理する方法

やりたいこと

  • 会員情報を複数取得したい
  • 会員情報を複数取得できるAPIが存在しない

実装

APIへアクセスするファイル

export default class Sample {
  public async fetchUsers(): Promise<{username: string, /* ... 適当な型定義 */}[]> {
    return Promise.all(
      [1, 2, 3].map(async row =>
        axios.get(`http://example.jp/api/member/${row}`),
      ),
    ).then((results: AxiosResponse<{ username: string, /* ...適当な型定義 */ }>[]) =>
      results.map(res => ({
        username: res.data.username,
        // ... レスポンスの整形
      })),
    );
  }
}

※適当な型定義の部分は*.d.tsからimportするのを想定

配列で行うことがaxiosじゃなかったりして、レスポンスの整形とかが必要なければそのままawait Promise.all()で返却してOK

会員情報を取得するファイル

const users = await new Sample().fetchUsers();
console.log(users); // [{username: "レスポンス1(res.data.username)", /* 整形した戻り値 */}, {username: "レスポンス2", /* 整形した戻り値 */}]

TSで書いたコードをブログに書くときって型情報とか消すから、これくらいならJSで書いたほうがスッキリしていいな...タイトル詐欺ごめんなさい...

canvasで特定の幅で文字を折り返して表示する

やりたいこと

canvasを使った文字の描画で、特定の幅で文字を折り返して表示したかった。
何も処理しないと↓みたいに文字が詰まって表示される

canvasで表示される文字列

実装

①文字を任意の文字数ごとに分割して配列に入れていく

// 表示したいテキスト
const src = "あ".repeat(30);
const results = [];
let tmp = "";
src.split("").map((row) => {
  tmp = tmp + row;
  // 16文字の場合に配列に入れる
  if (tmp.length === 16) {
    results.push(tmp);
    // 文字カウント用の変数をクリア
    tmp = "";
  }
});
// 16文字に満たなかった残りの文字を配列に追加する
const length = results.join("").length;
results.push(src.slice(length));

②配列の文字列をfillText()で描画していく

ctx.fillStyle = "black";
ctx.font = "16px serif";
results.map((result, index) => {
  // 16px*インデックス でY軸を指定する
  ctx.fillText(result, 0, 30 + (16 * index));
});

canvasで表示される文字列(おり返しあり)

コード全文

See the Pen Untitled by miyata (@m1y474) on CodePen.

canvasに表示した画像を丸で囲って表示する

やりたいこと

canvasにdrawImage()で貼り付けた画像を正円で表示させたい(丸型にトリミングされたように表示させたい)

やるまでは「quadraticCurveTo()ベジェ曲線使ってやらなかん感じ〜!?!?!?!数学できないよ〜!!」(数学関係あるかもわからない)って思ってたけど全然使わなかった

実装

一部抜粋

const image = new Image();
image.src = "表示したい画像";
image.onload = () => {
  ctx.beginPath();
  // 半径40pxの正円
  ctx.arc(50, 50, 40, 0, 2 * Math.PI);
  ctx.fill();  // 枠線表示したい場合はctx.stroke();にする
 
  // パスで切り抜く
  ctx.clip();

  // 80x80の画像を表示する
  ctx.drawImage(image, 10, 10, 80, 80);

  // 他の描画に影響が出ないようにrestoreする
  ctx.restore();
};

コード全文

See the Pen Untitled by miyata (@m1y474) on CodePen.

canvasで正円を描画したいのに楕円になってしまう

Reactでcanvas扱ってたときに正円を描画したいのに楕円になったときの備忘録

canvas描画部分

const ctx = this.canvas.current?.getContext('2d');
if (!ctx) {
  return;
}
ctx.fillStyle = "black";
ctx.fillRect(30, 0, 290, 450);
ctx.fillStyle = "orange";
ctx.beginPath();
ctx.arc(100, 50, 15, 0, 2 * Math.PI);
ctx.fill();

修正前

<canvas style={{ width: "320px", height: "450px" }} ref={this.canvas}></canvas>

修正前のcanvasで描画された楕円

修正後

<canvas width="320px" height="450px" ref={this.canvas}></canvas>

修正後のcanvasで描画した正円

原因はstyle属性にwidth,heightを書いていたこと
canvaswidth, height属性として記述すればOK

意図的に楕円を描きたいときはelipseを使う

参考

developer.mozilla.org

developer.mozilla.org

API Blueprintでパラメータにハイフン(-)が含まれる場合にハイフン以降が出力されない

はじめに

API Blueprint でクエリパラメータをつけるエンドポイントを定義したかったけど、出力されたフォーマットが想定していたものと違っていた時の備忘録

修正前

http://example.com/api/example?date=2000-01-10 みたいなURIを想定して、以下のように書いた(一部抜粋)

## サンプル [/api/example{?date}]

### 取得 [GET]

+ Parameters
    + date: 2000-01-10 (string) - 対象年月日

aglio を使って出力されたもの aglioで出力したHTML

2000で切れてる....!!!!

修正後

値をバッククォートで囲ってあげればいい

## サンプル [/api/example{?date}]

### 取得 [GET]

+ Parameters
    + date: `2000-01-10` (string) - 対象年月日

aglioで出力したHTML

ハイフン以降も表示されました


ドキュメントにもちゃんと書いてありました

NOTE: It's important to note, that if a key or value contains reserved characters such as :, (,), <, >, {, }, [, ], _, *, -, +, ` then the value must be wrapped in a code-block using back-ticks.

React Nativeでフォアグラウンドを検知する

はじめに

アプリがフォアグラウンドにあるタイミングで特定の処理を実施したかった

検証環境

$ react-native -v
react-native-cli: 2.0.1
react-native: 0.67.3

コード

import React from 'react';
import { AppState } from 'react-native';

class App extends React.Component {
  componentDidMount() {
    AppState.addEventListener('change', () => {
      if (AppState.currentState === 'active') {
        // ...フォアグラウントで実施したい処理
      }
    });
}

ちゃんとReactNative側に用意されててよかった...

状態

AppStateStatus トリガー
active アプリがフォアグラウンドに入ったとき。
background アプリがバックグラウンドに入ったとき。
inactive iOSのみ。アプリを開いた状態で通知センターを表示したとき。タスクキルする手前のタスク一覧画面のとき。

着信のときもinactiveになるらしいけど検証してないからわからん。。。
unknownextension はどこで使うねん

参考

reactnative.dev

Rect Nativeでダークモードを無効にする

検証環境

$ react-native -v
react-native-cli: 2.0.1
react-native: 0.67.3

実装

iOS

ios/{project}/AppDelegate.mを以下のように修正する

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

  // 省略

  // ↓追加
  if (@available(iOS 13, *)) {
    self.window.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
  }
  return YES;
}

※iOS13以下を切るならif文要らないかも

Android

android/app/src/main/java/path/to/project/MainApplication.javaを以下のように修正する

// ↓追加
import androidx.appcompat.app.AppCompatDelegate;

// 省略

public class MainApplication extends Application implements ReactApplication {

   // 省略

  @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, /* native exopackage */ false);
    initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
    // 追加
    AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
  }

最後に

ダークモード対応してるアプリすごい。

nodeのversionを切り替えるときに `Use of uninitialized value $b1 in numeric comparison (<=>) at ...` と表示される

nodeのversionを切り替えたときに以下のメッセージが表示された

nodebrew use v16.8.0
Use of uninitialized value $b1 in numeric comparison (<=>) at /Users/XXX/.nodebrew/current/bin/nodebrew line 678.
Use of uninitialized value $a1 in numeric comparison (<=>) at /Users/XXX/.nodebrew/current/bin/nodebrew line 678.
use v16.8.0

ググった感じ、~/.nodebrew/node.DS_Storeがあるのが原因らしいので消しておく

$ ls -al ~/.nodebrew/node
# .DS_Store が表示される
$ rm ~/.nodebrew/node/.DS_Store

再度useでエラーが表示されなければOK

$ nodebrew use v16.8.0
use v16.8.0

俺はuseで発生したけど普通にlistとかでもなるっぽい

【GoogleAppsScript】SESAME API を使ってGASから施錠・解錠する

はじめに

この記事は過去に投稿したSlackからNatureRemoのAPIを叩く記事の延長編です。

iliiliiiliili.hatenablog.jp

外出時に電化製品の電源オフったあとに、そのまま鍵の解錠→再施錠を実施するためにSESAME APIを使用します:)

やること

  1. SESAME APIの準備
  2. GASの作成

過去記事の延長線なのでSlackとの連携部分は省略します。

1. SESAME APIの準備

https://partners.candyhouse.co/login から SESAME に登録済みのメールアドレスでログイン

SESAME APIログイン画面

ログインしたらメールが来るので認証などを済ませる

https://partners.candyhouse.co/ が閲覧可能になるので施錠・解錠に使用したい登録デバイスをクリック
API キー・SecretKey・UUID を UUID をコピる

SESAME APIキー画面

2. GASの作成

施錠・解錠には以下の API を使用

doc.candyhouse.co

RequestBody には以下が必要

①操作コマンド

command 操作内容
88 トグル(施錠状態 → 解錠する・解錠状態 → 施錠する)
82 施錠
83 解錠

②履歴に表示される識別子

アプリの操作履歴↓に表示される文字列を base64 形式で指定する

SESAME アプリ画面

// 履歴に表示される名前を"GoogleAppsScript"にしたい場合
Utilities.base64Encode("GoogleAppsScript"); // "R29vZ2xlQXBwc1NjcmlwdA==";

最初、Utilities.base64Encode()があるの知らなくて、ずっと btoa()使ってたよ...

③AES-CMAC 方式で暗号化したタイムスタンプ

暗号化において、以下の記事を参考にさせていただきました。

zmzlz.blogspot.com

AES-CMAC での暗号化には CryptoJS を使用

CryptoJS のうち、以下のファイルを使うので GAS のコード.gsにコピペ

(参考記事だと別ファイルでのスクリプトになってるけど、呼び出せなかったので全部コード.gsに書いた...)

SESAMEAPI 仕様書に書かれてる 1,2,3 の部分は以下だと思う(多分)

// 1. timestamp (SECONDS SINCE JAN 01 1970. (UTC)) // 1621854456905
// 2. timestamp to uint32 (little endian) //f888ab60
// 3. remove most-significant byte //0x88ab60

// 1. timestampのミリ秒を切り捨て
const ts = Math.floor(Date.now() / 1000);
// ビューの作成
const view = new DataView(new ArrayBuffer(4));
// 2. 32bitにしてリトルエンディアンで格納
view.setUint32(0, ts, true);
// 3. hexにして最上位ビットを削除
const src = view.getUint32(0).toString(16).slice(2, 8);
// 暗号化
const sign = CryptoJS.CMAC(
  CryptoJS.enc.Hex.parse(SECRET_KEY),
  CryptoJS.enc.Hex.parse(src)
).toString();

これを関数にして解錠 →40 秒後に再施錠をする

const CMD = { lock: 82, unlock: 83 };
changeSesameCmd(CMD.unlock);
Utilities.sleep(40000);
// 40秒後に再施錠する
changeSesameCmd(CMD.lock);

コード全文

最後に

SESAME APIでRequestが間違ってたときのステータスコードが500エラーなのしんどい画期的ですごい

【GoogleAppsScript】朝に雨が降っていたら傘を持って帰るのを忘れないようにSlackに教えてもらう

(最近SlackとGASの記事ばっか書いてる気がする...)

はじめに

以下を解消するのが目的です

  • 朝に雨が降ってると退勤時に雨が降ってようが降ってまいが、会社の傘立てに傘を忘れる確率が100%(降ってるときは傘立てが事務所にあるから取りに戻る必要がある)
  • 傘を一つしか持ってないから2日連続で雨のときに無事死亡する

2つ目はずぶ濡れ出勤すれば済む問題なので1つ目を解消します:)

あと、スマホと傘を常時一緒に持っているわけではないのでスマートタグは使いません

どうしたか

弊社では退勤時、Slackの#timecardに「お先に失礼します。」という投稿をする慣習があります。
その慣習を使って、投稿された「お先に失礼します。」のメッセージのスレッドに傘を持ち帰る旨の投稿をして傘の置き忘れを防ぎます。

チャンネルではなくスレッドに書き込む理由は、投稿された自動メッセージがリモートワークの方など他の社員にとって邪魔な情報になる可能性があるからです。

「お先に失礼します。」に関しての自動化は過去記事に記載していますのでよければ以下もご覧ください :) iliiliiiliili.hatenablog.jp

手順

1. SlackBotの作成

1-1. Event Subscriptionsの設定

https://api.slack.com/apps/からSlackBotを作成します。
この辺りの作成手順はググれば出てくるので割愛します。

メッセージの投稿をトリガーとしてWebhookを使用するのでEvent Subscriptionsをオンにします。

Webhook先のURLをVerifiedにするために一旦GASを以下のコードでデプロイしておきます。

const params = JSON.parse(e.postData.getDataAsString());
if (params.challenge) {
  return ContentService.createTextOutput(params.challenge);
}

SlackのEvent Subscriptionsの設定画面

Request URLVerifiedになればOK

eventは以下を設定しました。(チャンネルのみで使用するのであればmessage.channelsだけでOK。多分。)

1-2. OAuth & Permissions の設定

次に、傘を忘れないようにするメッセージを投稿するためにOAuth & Permissionsを設定します

SlackのOAuth &amp; Permissionsの設定画面

今回はメッセージの書き込みとアプリBotの表示のカスタマイズをしたいのでScopesには以下を設定

アプリBotを使うのでBot User OAuth Tokenに表示されているトークンをメモっておく

2. GASの作成

2-1. パラメータの確認

doPost()でSlackからの投稿を受け取って処理します。

function doPost(e) {
  if (params.event.user !== SLACK_USER_ID) {
    return;
  }
  if (
    params.event.type === "message" &&
    params.event.text === "お先に失礼します。"
  ) {
    checkBringBackUmbrella(params.event.ts);
  }
}

やってること

  • 自分以外のSlackUserIdの場合は何もしない
  • テキストが「お先に失礼します。」の時にのみ天気予報のチェック(+Slackへの投稿)を行う

2-2. 天気予報のAPIを叩く

次に、天気予報のAPIを叩いて当日の雨量を取得します。
天気予報を取得するためのAPIにはOpen-Meteoを使用しました。

open-meteo.com

const date = new Date().toLocaleDateString();
// YYYY-MM-DDのformatに整形
const format = date.split("/").map((row) => (row.length === 1 ? "0" + row : row));
const formattedDate = [format[2], format[0], format[1]].join("-");

const LONGTIDE = 雨量を取得したい地域の経度;
const LATITUDE = 雨量を取得したい地域の緯度;
const res = UrlFetchApp.fetch(
  "https://api.open-meteo.com/v1/forecast?timezone=Asia/Tokyo&latitude=" +
    LATITUDE +
    "&longitude=" +
    LONGTIDE +
    "&hourly=rain&start_date=" +
    formattedDate +
    "&end_date=" +
    formattedDate
);

new Date().toLocaleDateString()の戻りが2022/9/22じゃなくて9/22/2022になるのなんでなん...?
ちなGASのタイムゾーンは東京にしてるし、appsscript.jsontimeZoneAsia/Tokyoになってる)

2-3. Slackにメッセージを投稿する

午前7~9時のいずれかの雨量が0.1以上だったら会社に傘を持ってきているとみなして「お先に失礼します。」のメッセージのスレッドに対して投稿する。
(なお、会社用の鞄に常時折りたたみ傘が入ってるので、10時以降に雨が降った場合は傘立てには傘は入っていないものとしています。)

const src = JSON.parse(res);
// 7,8,9時の雨量
const morningRains = src.hourly.rain.slice(7, 10);
// すべて0.0の場合は雨が降っていなかった
if (morningRains.every((rain) => rain === 0.0)) {
  return;
}
// 雨が降っている場合はSlackのスレッドに書き込む
const SLACK_TOKEN = "SlackのAppBotのトークン";
const SLACK_CHANNEL_NAME = "チャンネル名";
UrlFetchApp.fetch("https://api.slack.com/api/chat.postMessage", {
  method: "post",
  payload: {
    token: SLACK_TOKEN,
    channel: SLACK_CHANNEL_NAME,
    text: "<@"+SLACK_USER_ID+">今日は午前中の雨量が0.1以上でした。傘があれば持って帰りましょう。",
    icon_emoji: ":umbrella:",
    username: "放置傘警察",
    thread_ts: ts,
    reply_broadcast: false,
  },
});

これで雨のときにはSlackが教えてくれるようになりました:)

Slackのスレッド画面

コード全文

最後に

会社以外の傘立てに忘れたらおしまいだね!!

参考

open-meteo.com

【GoogleAppsScript】Slackに投稿したらNature RemoのAPIを使って照明とエアコンを消す

はじめに

外出時にいつもパートナーに外出する旨のメッセージを送ってるんだけど↓の3つをやらなくちゃいけないのがめんどい

  1. Nature Remoで照明を消す
  2. Nature Remoでエアコンを消す
  3. Slackでメッセージを送る

「Slackにメッセージを送るのが前提としてあるんだったら他のフローは省略できるのでは...?」って思ったのがきっかけです

(過干渉すぎん?っていうツッコミはなしでお願いします!)

Nature Remoとは?

nature.global

やること

  1. Slackにメッセージを投稿する
  2. SlackからGASにイベントを投げてもらう
  3. GASからNature RemoのAPIを叩く

1は人間が実行して2,3を自動化します:)

使うもの

  • Nature Remo API
  • GoogleAppsScript
  • Slack Bot

(いつも GoogleAppsScript なのか GoogleAppScripts なのかが覚えられずググってまう)

手順

1. Slackでアプリbotの作成

https://api.slack.com/apps からアプリBotを作成して、Event Subscriptionsからトリガーとなるイベントを設定する
今回はチャンネルでもDMでも使いたかったから以下を設定

Request URL で検証するために以下の内容でGASを作成して一旦Webアプリケーションとしてデプロイする

function doPost(e) {
  const params = JSON.parse(e.postData.getDataAsString());
  return ContentService.createTextOutput(params.challenge);
}

実行URLをSlack側のRequest URLに入力
↓みたいにVerified になったらSave Changesボタンを押せるようになるので保存

Slack Apiの画面

2. GASからNature RemoのAPIを叩く

https://home.nature.global/ からNature RemoのBearerトークンの発行

電化製品を操作するためのIDの取得には https://api.nature.global/1/appliances/ へGETリクエストを送って確認する

①電気を消す

UrlFetchApp.fetch(
  "https://api.nature.global/1/appliances/" + LIGHT_ID + "/light",
  {
    method: "post",
    headers: { Authorization: "Bearer " + NATURE_REMO_TOKEN },
    payload: {
      appliance: LIGHT_ID,
      button: "off",
    },
  }
);

②エアコンを消す

UrlFetchApp.fetch(
  "https://api.nature.global/1/appliances/" + AIRCON_ID + "/aircon_settings",
  {
    method: "post",
    headers: { Authorization: "Bearer " + NATURE_REMO_TOKEN },
    payload: {
      appliance: AIRCON_ID,
      button: "power-off",
    },
  }
);

クエリパラメータでもbodyでも動作するのなんでなん...?

Slackからのイベントでメッセージが特定の言葉だったときにこれらを実行する

function doPost(e) {
  const SLACK_USER_ID = "自分のSlackUserId";
  const params = JSON.parse(e.postData.getDataAsString());
  if (
    params.event.type === "message" &&
    ["トリガーにしたいメッセージの配列"].includes(params.event.text) &&
    params.event.user === SLACK_USER_ID
  ) {
    turnOffAppliances();
    return;
  }
  return;
}

コード全文

最後に

スマートスピーカー持ってません

参考

developer.nature.global swagger.nature.global

api.slack.com

はてブロの記事が投稿から1年以上経過している場合にアラートを表示する

経緯

ありがたいことに投稿日から数年経過しても、検索からアクセスされてる記事がいくつかあるんだけど、情報が古いから○iitaみたいに この記事は公開日からn年以上が経過しています。情報が古い可能性がありますので十分ご注意ください。 っていう注意書きを画面上に表示させたかった

注意

カスタマイズ

1. CSSを追加

管理画面からデザインカスタマイズ画面を開く
はてブロ管理画面 デザインCSS

デザインCSSにalert用のスタイルを追加 (Bootstrapのwarning alertと同じ)

.warning-alert {
    font-size: 12px;
    background-color: #ebd1b6;
    padding: 0.75rem 1.25rem;
    color: #856404;
    background-color: #fff3cd;
    border: 1px solid #fae39f;
    border-radius: 0.25rem;
}

2. JavaScriptの追加

設定詳細設定画面を開く

設定>詳細設定>&lt;head&gt;要素にメタデータを追加w:100

<head>要素にメタデータを追加に以下のJSを追加する

document.addEventListener("DOMContentLoaded", function () {
  const dtElements = document.querySelectorAll(".date.entry-date.first time");
  if (dtElements.length === 0) {
    return;
  }
  const nowTs = new Date().getTime();
  dtElements.forEach((element) => {
    if (!element) {
      return;
    }
    const dt = element.getAttribute("datetime");
    const postedTs = new Date(dt).getTime();
    const diffYears = ((nowTs - postedTs) / (1000 * 60 * 60 * 24) / 365).toFixed();

    // 記事公開日から1年以上経過している場合にalertを表示する
    if (diffYears >= 1) {
      const div = document.createElement("div");
      div.innerText = `この記事は公開日から${diffYears}年以上が経過しています。情報が古い可能性がありますので十分ご注意ください。`;
      div.classList.add("warning-alert");
      element.closest(".entry-header").prepend(div);
    }
  });
});

上記の設定が完了後に記事一覧・詳細画面で以下のように表示されます:)

記事に表示されるalert


MySQLのDELETE文をEXPLAINで確認する

MySQL5.5でDELETE文をEXPLAINで見ようとしたらエラーになった

mysql> EXPLAIN DELETE FROM samples WHERE title = "title";
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DELETE FROM samples WHERE title = "title"' at line 1

EXPLAINでDELETE文が使えるのは5.6.3からで5.5系では実行できないみたい(泣)

dev.mysql.com

MySQL 5.6.3 現在、EXPLAIN に使用できる説明可能なステートメントは、SELECT、DELETE、INSERT、REPLACE、および UPDATE です。MySQL 5.6.3 より前では、SELECT が唯一の説明可能なステートメントです。

5.5で確認したいときは代替として以下で対応する

EXPLAIN SELECT 1 FROM samples WHERE title = "title";