Goの書籍を読んでいたら簡単なスクリプトぐらい書いてみたいと思って、めちゃくちゃシンプルなSlack通知のスクリプトを書いてみました

Slack Block Kit
SlackのUIフレームワークでブロックを積み重ねてレイアウトができるコンポーネントになります。
https://api.slack.com/block-kit
色々できることもありますが、滅茶苦茶シンプルにWebhookを使ってメッセージを送信することも可能なので今回はそれだけやってみます。
GoでSlack通知のスクリプト
今回はせっかくなので、良く忘れてしまう数ヶ月~年単位でしかやらない作業を思い出すためにリマインド通知をするものをつくりました
メンテナンス情報を格納した下記のようなjsonファイルを用意しておきます。
{
"targets" : [
{
"name" : "サマータイヤの車の空気圧",
"category" : "自動車",
"description" : "車の空気圧を半年に1回入れ替える",
"maintenance_interval" : 180,
"last_updated": "2025-04-05T00:00:00Z"
},
{
"name" : "リビングエアコンのフィルタ掃除",
"category" : "掃除",
"description" : "リビングエアコンのフィルタを定期的に掃除する",
"maintenance_interval" : 180,
"last_updated": "2025-04-05T00:00:00Z"
}
]
}
これを読み込んで期限の近づいたものと過ぎたものでグルーピングしてメッセージを表示するというものです。
メッセージのためのBlockを動的に生成してから最後にまとめてメッセージとして発信するという流れです。
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
)
type Maintenance struct {
Name string `json:"name"`
Category string `json:"category"`
Description string `json:"description"`
MaintenanceInterval int `json:"maintenance_interval"`
LastUpdated time.Time `json:"last_updated"`
}
type Config struct {
Maintenances []Maintenance `json:"targets"`
}
type SlackMessage struct {
Blocks []Block `json:"blocks"`
}
type Block struct {
Type string `json:"type"`
Text *TextObject `json:"text,omitempty"`
Fields []TextObject `json:"fields,omitempty"`
}
type TextObject struct {
Type string `json:"type"`
Text string `json:"text"`
}
func (m Maintenance) IsWarningDue() bool {
next := time.Time(m.LastUpdated).AddDate(0, 0, m.MaintenanceInterval-7)
return time.Now().After(next)
}
func (m Maintenance) IsCriticalDue() bool {
next := time.Time(m.LastUpdated).AddDate(0, 0, m.MaintenanceInterval)
return time.Now().After(next)
}
func formatDate(t time.Time) string {
return t.Format("2006-01-02")
}
func NewSlackMessage(title string, m []Maintenance) SlackMessage {
var dynBlocks []Block
for _, task := range m {
block := Block{
Type: "section",
Fields: []TextObject{
{Type: "mrkdwn", Text: fmt.Sprintf("*Title:*\n%s", task.Name)},
{Type: "mrkdwn", Text: fmt.Sprintf("*Category:*\n%s", task.Category)},
{Type: "mrkdwn", Text: fmt.Sprintf("*Description:*\n%s", task.Description)},
{Type: "mrkdwn", Text: fmt.Sprintf("*LastUpdated:*\n%s", formatDate(task.LastUpdated))},
},
}
dynBlocks = append(dynBlocks, block)
}
firstBlock := Block{
Type: "section",
Text: &TextObject{
Type: "mrkdwn",
Text: fmt.Sprintf("*%s* <!channel>\n下記のタスクを確認してください", title),
},
}
allBlocks := append([]Block{firstBlock}, dynBlocks...)
return SlackMessage{
Blocks: allBlocks,
}
}
func SendSlackMessage(webhookURL string, msg SlackMessage) error {
payload, err := json.Marshal(msg)
if err != nil {
return fmt.Errorf("failed to marshal message: %w", err)
}
resp, err := http.Post(webhookURL, "application/json", bytes.NewBuffer(payload))
if err != nil {
return fmt.Errorf("failed to post to Slack: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
return fmt.Errorf("received non-2xx response from Slack: %s", resp.Status)
}
return nil
}
func main() {
webhookURL, exists := os.LookupEnv("SLACK_WEBHOOK")
if !exists {
fmt.Println("SLACK_WEBHOOK is not set")
return
}
file, err := os.Open("data.json")
if err != nil {
fmt.Println("ファイルを開けません:", err)
return
}
defer file.Close()
var config Config
decoder := json.NewDecoder(file)
if err := decoder.Decode(&config); err != nil {
fmt.Println("デコードエラー:", err)
return
}
var warnings, criticals []Maintenance
// 重要度に応じてタスクを分類する
for _, task := range config.Maintenances {
if task.IsCriticalDue() {
criticals = append(criticals, task)
} else if task.IsWarningDue() {
warnings = append(warnings, task)
}
}
if len(criticals) != 0 {
SendSlackMessage(webhookURL, NewSlackMessage("Critical", criticals))
}
if len(warnings) != 0 {
SendSlackMessage(webhookURL, NewSlackMessage("Warning", warnings))
}
}
今回はサクッとやるためにIncoming Webhook を使っています
環境変数に定義する形で用意しているので、定期実行するならgithub actionsなどでsecretsにwebhookのURLだけ設定すれば動かせるはずです