ググっても意外と出てこないレア情報な気がする
replace()
'foo bar'.replace(/(?<first>.+?) (?<last>.+)/, '$<first>-$<last>');
match()
const mat = 'foo bar'.match(/(?<first>.+?) (?<last>.+)/);
console.log(mat.groups.first, mat.groups.last);
参考
- グループと後方参照
- 引数としての文字列の指定
replace()
側はこっちに書いてる
割とハマってだるいので今回はサンプル程度にPRの一覧を取得して個別にSlackに投げるものを作ってみます
PRが2個ある場合、出力イメージはこんな感じ。2投稿に分けて投稿します
API トークンの入手
まずはSlack APIを叩くためのトークンをゲットします
- Create an appからアプリを作成
- 左のメニューからFeatures -> OAuth & Permissions
- Scopesを設定
- 今回はBot Token Scopesを
chat:write
とします
- 今回はBot Token Scopesを
- 左のメニューからSettings -> Install App to Your Teamでアプリをインストール
- トークンが吐き出されるのでメモする
GitHub Actions Workflowsの作成
SECRETの設定
- Slack APIトークンをリポジトリのSecretsに突っ込んでおきます
- 名前は一旦
SLACK_TOKEN
とします
Workflowsの作成
前提
- PR一覧の取得には actions/github-script を利用します
- GitHub内部の情報を抜いたり、JSで処理を組みたいときに重宝します
- APIリファレンスが読みやすいので、使うのにはあんま苦労しないと思います
- Slack APIを叩くのにはcurlを利用します
- actions/github-scriptから叩くのは多分難しいです
ベースの作成
これに肉付けをしていきます
name: Post to slack example
on:
workflow_dispatch:
jobs:
post-slack:
runs-on: ubuntu-latest
steps:
PR一覧の取得
List pull requestsにある通りに進めていきます
- uses: actions/github-script@v6
id: set-result
with:
result-encoding: string
script: |
const { data: respPulls } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
});
console.log(respPulls);
PR一覧の加工
こんなデータを取る感じで組んでいきます
API仕様は List pull requests を参照
type PullRequest = {
id: number;
reviewers: string[];
};
先ほど取得したrespPulls
を上記の型付けになるように加工します
const getReviewersName = (requested_reviewers) => {
return requested_reviewers.map((reviewers) => {
return reviewers.login;
});
};
const getPullRequests = (pulls) => {
return pulls.map((pull) => {
return {
id: pull.number,
reviewers: getReviewersName(pull.requested_reviewers),
};
});
};
const pulls = getPullRequests(respPulls);
Slackに投げるメッセージの作成
こんなメッセージをPRの数分組んでいきます
なお実際にSlackでメンションを作る場合はGitHubのスクリーン名とSlackのユーザー IDの突き合わせ処理が別途必要です。やり方は別途後述します
@foo @bar
https://example.com/pulls/1
やっていること
- 上記のフォーマットでメッセージを作成
- シェルスクリプトで配列として扱うためにBase64にエンコード
- 改行コードが混ざっていると扱いづらいので
- エンコードした文字列をスペース区切り文字列として連結
AAA BBB CCC ...
みたいな
- 最後にWorkflowsの戻り値として設定しています
const encodedMessages = pulls.reduce((messages, pull) => {
const reviewersBuff = pull.reviewers
.reduce((acc, cur) => {
return `${acc}${cur} `;
}, '')
.replace(/ $/, '');
const reviewers = reviewersBuff === '' ? 'レビュアー未設定' : reviewersBuff;
const message = `${reviewers}\\nhttps://example.com/pulls/${pull.id}`;
const encodedMessage = Buffer.from(message).toString('Base64');
return `${messages}${encodedMessage} `;
}, '');
return encodedMessages;
curlを利用してSlack APIを叩く
やっていること
encodedMessages=(${{steps.set-result.outputs.result}})
- 前項で作った文字列を配列として取得しています
for message in ${encodedMessages[@]}
- foreach的なやつです
- 改行コードがこの時点で存在すると上手くいきません
decoded_mes=$(echo ${message} | base64 -di)
- ここでBase64エンコードをデコードします
postSlack "$decoded_mes"
- 別引数にならないように
""
で固めます
- 別引数にならないように
- curl叩いてるところ
-d
の中をヒアドキュメントで展開するのが味噌です- 単純に文字列として扱うと変数展開が起きてJSONが壊れます
- run: |
postSlack() {
local mes=$1
curl -sS https://slack.com/api/chat.postMessage \
-H 'Authorization: Bearer ${{ secrets.SLACK_TOKEN }}' \
-H 'Content-Type: application/json; charset=UTF-8' \
-d @- <<EOF
{
token: "${{ secrets.SLACK_TOKEN }}",
channel: "#api-test",
text: "$mes"
}
EOF
}
encodedMessages=(${{steps.set-result.outputs.result}})
for message in ${encodedMessages[@]}
do
decoded_mes=$(echo ${message} | base64 -di)
postSlack "$decoded_mes"
done
コード全体
name: Post to slack example
on:
workflow_dispatch:
jobs:
post-slack:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6
id: set-result
with:
result-encoding: string
script: |
const getReviewersName = (requested_reviewers) => {
return requested_reviewers.map((reviewers) => {
return reviewers.login;
});
};
const getPullRequests = (pulls) => {
return pulls.map((pull) => {
return {
id: pull.number,
reviewers: getReviewersName(pull.requested_reviewers),
}
});
}
const { data: respPulls } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
});
const pulls = getPullRequests(respPulls);
const encodedMessages = pulls.reduce((messages, pull) => {
const reviewersBuff = pull.reviewers.reduce((acc, cur) => {
return `${acc}${cur} `
}, '').replace(/ $/, '');
const reviewers = reviewersBuff === '' ? 'レビュアー未設定' : reviewersBuff;
const message = `${reviewers}\\nhttps://example.com/pulls/${pull.id}`;
const encodedMessage = Buffer.from(message).toString('Base64');
return `${messages}${encodedMessage} `
}, '');
return encodedMessages;
- run: |
postSlack() {
local mes=$1
curl -sS https://slack.com/api/chat.postMessage \
-H 'Authorization: Bearer ${{ secrets.SLACK_TOKEN }}' \
-H 'Content-Type: application/json; charset=UTF-8' \
-d @- <<EOF
{
token: "${{ secrets.SLACK_TOKEN }}",
channel: "#api-test",
text: "$mes"
}
EOF
}
encodedMessages=(${{steps.set-result.outputs.result}})
for message in ${encodedMessages[@]}
do
decoded_mes=$(echo ${message} | base64 -di)
postSlack "$decoded_mes"
done
Appendix:Slackにメンションを投げる方法
Slackへ実際にメンションを投げるのはユーザーIDを指定する必要があります
参考:Formatting text for app surfaces
"text": "<@U024BE7LH> Hello"
のようにすることでメンションを投げられます
ユーザーIDはSlackアプリから相手のプロフィールを開き、そこにあるハンバーガーメニューみたいなやつから取れます。一応取得用のAPIもあります
- 素のHTMLにJSを埋め込んでイベントコールバックの引数を取る方法
- イベントコールバックの引数に
event
を指定する- 例:
onclick="test(event)"
- 例:
- イベントコールバックの引数に
サンプルコード
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Example of DOM Event callback arguments</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
const test = (ev) => {
console.log(ev);
};
</script>
</head>
<body>
<p onclick="test(event)">aaa</p>
</body>
</html>
iOS Safari向けのinputやtextareaを実装するときにmaxlengthが効かない問題のメモ
maxlength
- 古いiOS Safariでは効かない模様
- iOS 18.6.2のSafari 18.6では効いていることを確認
onInput()
でstring.slice(0, maxlength)
するとIMEの挙動が可笑しくなるtype="tel"
など日本語が入力できない場合であれば有効
- オートコンプリートやコピペ入力での字切れなどもあるため、根本的に使わないことが望ましい
type="number"
- iOS Safariでは期待した動作にはならない
- IMEが有効になり、全角入力が発生する
- iOS 18.6.2, Safari 18.6でも挙動は変わらず
- 使うなら
type="tel"
を使い、JSで数字以外の入力を弾くのが無難 - 恐らく普及ブラウザの全てで半角入力を強制出来、スマホなどではNumPadが出てくる
- アルファベットやハイフンなどの記号も打てるので必要に応じた入力制御が必要
あとがき
iOS SafariのバージョンはiOS側に一定の依存があるらしく調べるのが手間だった。とりあえずUserAgentから抜いた値で確認している。
参考
"maxlength" | Can I use... Support tables for HTML5, CSS3, etc
JavaScriptで添付ファイルを拾うのと、ファイルを落とす処理のサンプルコード
<html>
<head>
<meta charset='utf-8'>
<title>js file up/dl example</title>
<script>
window.onload = () => {
const fr = new FileReader();
const el = document.getElementById("test");
fr.onload = (ev) => {
const dl = document.createElement('a');
dl.setAttribute('href', ev.target.result);
dl.setAttribute('download', el.files[0].name);
dl.style.display = 'none';
document.body.appendChild(dl);
dl.click();
document.body.removeChild(dl);
};
el.onchange = () => {
fr.readAsDataURL(el.files[0]);
};
};
</script>
</head>
<body>
<input type="file" id="test"></input>
</body>
</html>