PHPでSlackへメッセージ送信

PHPでSlackへメッセージ送信するサンプル。

Slackの該当チャンネルで、チャンネル詳細〜インテグレーション〜アプリを追加する、でIncomming Webhookを追加。
その際に、チャンネルを指定(ここで新規作成も可)&投稿者名を設定し、Webhook URLを拾う。
あとはChatWorkとほぼ一緒。(要php-curl)

<?php

class SlackComponent
{
    /**
     * Slackへメッセージ送信
     * @param $url : Incomming WebhookのWebhook URL
     * @param $channel : #チャンネル名
     * @param $bodyText : 内容
     * @return  string | false
     *      error : ?
     *      success: "ok"
     */
    public static function send(string $url, string $channel, string $bodyText) : string | false
    {
        $ch = curl_init();
        $options = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query([
                'payload' => json_encode([
                    'channel' => $channel, // #チャンネル名
                    'text' => $bodyText, //本文
                ]),
            ])
        ];
        curl_setopt_array($ch, $options);
        $res = curl_exec($ch);
        curl_close($ch);

        if ($res === false) {
            return false;
        }
        return $res;
    }
}

呼び出し側は、

$url = 'https://hooks.slack.com/services/XXXXXXXXX/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
$channel = '#test';

$res = SlackComponent::send($url, $channel, 'this is test');
if ($res !== 'ok') {
    //fail
}
else {
    //success
}

こんな感じでするっと’ok’が返ってくる。

PHPでChatWorkへメッセージ送信

Line-Notifyが2025年3月末で終了するとのことで、代替を探してみたがどれもパッとせず。

LINE Official AccountでのメッセージAPIも200通/月以上は有料(5,000円/月)となるので小規模では使えず。

とりあえずの用途として顧客へ通知するわけではなく、申込みフォームからの申込みを管理者側へ通知できれば良いので、SlackかChatWorkへの投稿ができればスマホにアプリ入れれば通知くるのでまぁいいかと。

で、ChatWorkへのPOSTをPHPで簡単に。(要php-curl : >sudo apt install php-curl)

<?php

class ChatWorkComponent
{
    public static function send(string $roomId, string $token, string $bodyText) : array | false
    {
        $options = array(
            CURLOPT_URL => 'https://api.chatwork.com/v2/rooms/'.$roomId.'/messages',
            CURLOPT_HTTPHEADER => [
                'X-ChatWorkToken: '. $token,
                'Content-Type: application/x-www-form-urlencoded'
            ],
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS =>
                http_build_query(['body' => $bodyText, 'self_unread=0'])
        );

        $ch = curl_init();
        curl_setopt_array($ch, $options);
        $res = curl_exec($ch);
        curl_close($ch);

        if ($res === false) {
            return false;
        }
        return json_decode($res, true);
    }
}

こんな感じのを

$roomId = 'XXXXXXXXX';
$token = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';

$res = ChatworkComponent::send($roomId, $token, 'this is test');
if ($res !== false) {
    if (array_key_exists('message_id', $res)) {
        //success
    }
    else {
        //fail
    }
}
else {
    //fail
}

こんな感じで呼び出してやる。

マルチ電圧アダプタ

ちと必要で、マルチ電圧アダプタを購入。

1つ目は手動切り替えタイプ。
3/4.5/6/7.5/9/12 Vの切り替えダイヤルで、変換プラグ6種類付き、正負極性変換可能なやつ。
家庭用で12Vまでなのだけれど、まぁ大抵のものには間に合う。1.78Kyen。
アダプタの刺す向きで極性を変える。

2つ目は、スイッチ切り替えの、
5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20 V対応の、変換プラグ6種類+USB-Cとか3つ付きで、同じく正負極性変換可能なもの。

ボタン長押しして電圧切り替えて使用するタイプ。極性リバース用のケーブルが別途ついてる。2.5Kyen。

まぁどちらも恒久的に使うものではないのだけれど、後者はノートパソコン用電源にも使えるので便利。

logi M240Grd

普通のBluetooth専用マウス。amazon.co.jp限定版のタイムセールで1760円也。

https://www.logicool.co.jp/ja-jp/products/mice/m240-silent-bluetooth-mouse.html

スクロールがぬるぬるは良い感じ。
小さめで手の大きい人には不満が出るかも。

単三電池1本は高ポイント。裏ブタ開くとドングル置き場がついているのはまぁご愛敬。

TESMEN TM-510

古いテスターがめっからないのでデジタルのやっすいやつをポチ。

https://tesmen.com/products/digital-multimeter-4000-counts-ncv-test-ac-dc-voltage-resistance-continuity

ちゃちいカバーが着いた全オートで家の中で使う分には導通/直流電圧/交流電圧が測れれば良いので十分なもの。

単4電池ってのもまぁポイント高く。(006Pのやつが多いんだよねぇ)

ケースも着いて目立たずコンパクトで、しばらく持ってくれればいいな。

(今更)double braceでのCollection初期化

以前より取り沙汰されてはいる、Java-Collectionのdouble-braceによる初期化について。

Map<String, String> mp = new HashMap<String, String>() {{
    put("abc", "ABC");
    put("def", "DEF");
   put("ghi", "GHI");
}};

のようにインスタンス生成と初期化を行うコードはJava8以前にはちょいちょい見られ。
今もまぁちょいちょい使うのだけれど。
このdouble-braceによる初期化当初よりメモリリークが指摘され、注意が必要とされている。
匿名内部クラス(annonymous inner class)が生成され”強い外部参照”が作成されるためガーベージコレクションされることなく残り続けるというもので、繰り返し処理されるロジックでは避けるべきとされている。
(https://blog.p-y.wtf/avoid-java-double-brace-initialization で詳しく解説されている)

でもって、じゃぁ、というのはJava9以降なら簡単で、

Map<String, String> mp = Map.of("abc", "ABC", "def", "DEF", "ghi", "GHI"};

とするだけ。もちろんMap.ofの場合はImmutable Collection(不変コレクション)を返すので、putなどの編集・追加・削除操作はできないが、ごそっと初期化したい時ってぇのはImmutableで構わないケースがほとんどなので問題はないだろう。

ただ”ガーベージコレクションされない”が致命的でない場合は、double-braceも便利な書き方ではあるので、例えば単発Java Applicationのループ外での初期設定とかでは十分有効だといえる。

つーか、コレクションの初期化は面倒なのはわかるのだけれど他の言語では割と簡単にできるものであるし、ささっと初期化できないってのはなんだかなーとは思うわけだがまぁそういうものだししょうがない。

Teamsのフォント

必要があってUbuntuに青柳衡山毛筆フォントをインストール。
ところがteams-for-linuxの日本語が全部毛筆になってしもうた。

いろいろ調べるがteamsのフォントは変更ができない模様。なんじゃこりゃ。

fontconfigの登録順序とかデバッガで調べてとかやればなんとかなりそうな気もするが、
諸々面倒なので、さっくり再インストール。

$>sudo snap remove teams-for-linux

ややしばらくで完了。

$>sudo snap install teams-for-linux

なんか以前のフォント(noto-serif-cjk)とは違う気がするが、まぁ毛筆フォントからは開放されたので良し。

Javaの非同期処理

Javaの非同期も随分簡単になって、Thread管理クラスを作ってThreadを起こして終了を拾って〜などとやっていた頃が夢のように。

基本的にはコレクションのstreamをparallelで処理、という具合だが業務系では大抵の処理は
・多重度の設定
・全終了の待機
が必要になる。

で、そんな場合のために、ForkJoinPoolクラスが存在していて、だいたい次のように。

private void foo() throws Exception
{
        Map<String, String> mp = new HashMap<String, String>(){
            {{ put("a", "A"); }}
            {{ put("b", "B"); }}
            {{ put("c", "C"); }}
            {{ put("d", "D"); }}
            {{ put("e", "E"); }}
        };

        var pool = new ForkJoinPool(20);    //多重度をセット
        pool.submit(()->mp.entrySet().parallelStream().forEach((x)->{
            System.out.println(x.getKey() + " : " + x.getValue());
        })).get();    //全終了を待機

        System.out.println("parallel-end");
}

非同期処理が込み入ったものであれば、ラムダ内から処理用の関数を呼ぶようにすれば良く、更に同期処理が必要なのであれば呼ばれる関数にsynchronized修飾子をつけておけば良い。

ま、常駐型のスレッドが必要な場合はやっぱりそれなりに管理用クラスを用意して上げたほうがいぢり易くはあるんだが。

ssl_client.php

httpsで某サイトの情報を取得する必要があり、phpで単純なssl_clientを作成。

Githubのどこかを参考にしたのだが忘れてしまった。(誰か気がついたら教えて)

で、PHPがOpenSSLサポートが有効なようにコンパイルされていることが前提で、fsockopenでstreamを開き、stream_get_meta_dataで読み込む。

<?php
/**
 * ssl_client.php
 */

class ssl_client
{
    protected $options = [];
    public function __construct()
    {
        ini_set("allow_url_fopen", 1);
        $this->options = [
            'http' => [
                'method' => 'GET',
                'timeout' => 10, //タイムアウト秒
                'ignore_errors' => true, //ステータスコードが4xxや5xxなど失敗の場合でもコンテンツを取得
            ],
        ];
    }
    public function wget($url, $param = array())
    {
        $timeout = 30;
        $method = count($param) ? 'POST' : 'GET'; // $param要素あればPOST
        if (!$purl = parse_url($url)) {
            return array('', '');
        }
        //print_r($purl);
        // URL分解
        if (empty($purl['port'])) {
            $purl['port'] = 80;
        }

        $ssl = '';
        if ($purl['scheme'] == 'https') {
            $ssl = 'ssl://';
            if ($purl['port'] == 80) {
                $purl['port'] = 443;
            }
        }
        // HTTPリクエスト
        $fp = fsockopen($ssl . $purl['host'], $purl['port'], $errno, $errstr, $timeout);
        if (!$fp) {
            die("$errstr ($errno)<br />\n");
        }
        if (!empty($purl['query'])) {
            $purl['path'] .= "?{$purl['query']}";
        }

        if (substr($purl['path'], 0, 1) !== '/') {
            $purl['path'] = '/' . $purl['path'];
        }

        $data = $method === 'POST' ? http_build_query($param, '', '&') : '';
        $cl = empty($data) ? '' : "Content-Length: " . strlen($data) . "\r\n";
        $out = "$method {$purl['path']} HTTP/1.1\r\n"
            . "Host: {$purl['host']}\r\n"
            . "User-Agent: PHP-Script/1.0\r\n"
            . "Content-Type: application/x-www-form-urlencoded\r\n"
            . $cl
            . "Connection: Close\r\n\r\n"
            . $data;
        fwrite($fp, $out);

        // HTTPレスポンス取得
        stream_set_timeout($fp, $timeout);
        $header = $html = '';
        $html_arr = [];
        while (!feof($fp)) {
            $info = stream_get_meta_data($fp);
            if ($info['timed_out']) {
                die("timeout!");
            }

            if (empty($html)
                && substr($header, -4) !== "\x0d\x0a\x0d\x0a"
                && $header .= fgets($fp)) {
                continue;
            }
            $line = fgets($fp);
            if (preg_match("/^0000[0-9A-F]{4}[\r\n]*$/", $line)) {
                continue;
            }
            $html .= $line;
            $html_arr[] = $line;
        }
        fclose($fp);

        return array($header, $html, $html_arr);
    }
}

ちょいちょい 0000ABCDのような行が紛れ込むのでそいつを削除してる、ってのと、HTML全文とは別に行ごとの配列も返してるあたりがいぢってあるところ。

別にたいしたことをしているわけでもなく、元があったのをいぢっただけなのだが、時々忘れてまた探すのが面倒なので書いておく。