浜田市の防災情報をLINEで通知してくれるBotを作った。

はじめに

浜田市の若者会議のメンバーの方から「浜田市の防災情報を通知してくれるアプリとかあるとうれしい」という話をいただいたので、実際に作ってみた話です。

 

作ったもの

github.com

 

実際にLINEで防災情報を通知してくれるBotはこちらのQRコードを読み込むか「@689nsohm」で友達追加していただければOKです。

f:id:gamelinks007:20210723154237p:plain

やったこと

基本的な構成としてはAWS Lambda/Event Bridge/Ruby 2.7/LINE Messaging APIを使用して作りました。

 

肝としては10分に一回RSSを取得しに行くようにしたことと、その取得時間とRSSの作成時間を比較して直近のものだけをLINEに通知するようにしたところですね(防災情報などはできるだけ最新の情報がラグなくユーザーに届ける必要があるので)

 

今後

とりあえず、防災情報に関しては通知できるようになっているので実用性は高そう。

あとは実際に利用してくれるユーザーをいかに増やすかが課題になりそう

typeprofのインスタンス変数が未初期化のまま使われている警告を解消した

以下のPRをRubyに投げたところ、typeprof周りのテストで落ちているようだった。

github.com

で、どの辺が原因なのかtypeprofのテストを回してみたら以下の警告が表示されていた。

/home/sh/rubydev/typeprof/lib/typeprof/analyzer.rb:496: warning: instance variable @namespace not initialized

調べてみると、Ruby 3.0.0からは未初期化のインスタンス変数を使っていても警告が表示されなくなっており、今回テストで使用したRuby 2.7系列では警告が表示されているようだった。

bugs.ruby-lang.org

というわけで、Ruby 2.7系列でテストを実行した際に警告が表示されないようにPRを作った。

github.com

無事マージされたようなので何より。

浜田市の若者会議に選任された

この度、住んでいる浜田市の若者会議の委員に選任されました。

 

やることとしては、委員各々が持つスキルとかを使って若者が過ごしやすい街づくりを進める感じです

 

より詳しいことは下記のPDFを読んでいただければわかります。

https://www.city.hamada.shimane.jp/www/contents/1446178852885/files/senryakupu.pdf

 

今後に関しては僕の守備範囲であるIT系の話とかしつつ、住みやすい街づくりに協力できればと思います

雑にBacklogのチケットを一括で作成できるスクリプト書いた

はじめに

仕事の関係で一括でBacklogに複数のチケットを登録する必要があり、作成したものになります。 手作業なんて面倒くさいことしたくない人は是非使ってみてください。

作ったもの

github.com

使い方

使い方は簡単。 上記のコードをクローンした後、必要なAPIキーなどを.envに追加

SPACE_ID=<スペースID>
API_KEY=<APIキー>
PROJECT_ID=<プロジェクトID>
ISSUE_TYPE_ID=<チケットのタイプ>
PRIORITY_ID=<優先度>

その後、tickets.mdをクローンしたディレクトリ内に以下のように作成。

### ○○の修正対応

○○は××だったのでそのように修正

TICKET_END

### △△の修正対応

△△は××だったのでそのように修正

TICKET_END

TICKET_ENDで各チケット毎に区切るようにしています。

あとはbundle installなどをした後にbundle exec ruby main.rbを実行するだけです。

bundle exec ruby main.rb

参考

github.com

developer.nulab.com

高校生にプログラマーってどういう仕事か話してきた

経緯

Hamada.rbとかCoderDojo浜田とかでアレコレ作ったりしてたのがきっかけで知り合った方から「授業(という体のインタビュー)をしてみませんか?」とお誘い頂いたのがきっかけですね。

 

で、話を聞いてみると浜田高校では以下のような取り組みをしていて「地域の頑張っている人に話を聞く」ということをしているとのこと。

 

www.hamakou.ed.jp

 

面白い取り組みだったのと、こういう地方だとプログラマーに直接話を聞く機会とかもないだろうしと思ったので、快諾。

で、今日色々と仕事の話とかHamada.rbとかでやってるアプリ開発の話とか話してきました。

 

話した内容とか

Hamada.rbでやってるアプリ開発(避難先のGoogleMapアプリ)とかの話がメインで、それ以外に実際にプログラマーってどんな仕事かとかを話してきました。

アプリ開発の文脈で、ノーコードツールとかの話もしましたね。

 

意外と食いつきが良かったのは、実際に作ったアプリを見せた時ですね。

「どれぐらいでつくれるのか?」とか質問が来てました。

 

あと仕事の話も意外と興味をもって貰えたようでした。

浜田市とかだとプログラマーとして仕事している人はかなり少ないので、珍しかったのもありそう。

あと、リモートで働いているというのも面白かったみたいです。

 

今後とか

こういうのがきっかけでプログラミングに興味を持って貰える可能性はあるので、今後も参加できるようなら参加していきたいですねー。

 

あとは実際に高校生たちが考えたアプリとかを作ってみるのも面白いかもと思ったりしましたねー。

GoでZoomのMTGを作成してみた

はじめに

この記事は、仕事で触ることになったGo製Zoomライブラリを触った時の備忘録です。

実際に試せるサンプルコードもありますので、気になった人は以下のリポジトリをクローンして試してみてください。

github.com

やりかた

まず、Zoomの開発者登録とか済ませてAPIキーなどを取得します。

marketplace.zoom.us

APIキー取得後、.envに取得したAPIキーとシークレットキー、そしてZoomのMTGを作成するために必要なホストのユーザーIDを追加します。

ZOOM_API_KEY=<APIキー>
ZOOM_API_SECRET=<シークレットキー>
USER_ID=<ユーザーID>

次に、必要なライブラリを落としてきます。

go get -u github.com/himalayan-institute/zoom-lib-golang github.com/joho/godotenv

最後に、main.goを以下のように作成します。

package main

import (
    "log"
    "os"
    "github.com/joho/godotenv"
    "github.com/himalayan-institute/zoom-lib-golang"
)

func main() {
    err := godotenv.Load()
    if err != nil {
        log.Fatalf("Error loading .env file")
    }

    var (
        apiKey    = os.Getenv("ZOOM_API_KEY")
        apiSecret = os.Getenv("ZOOM_API_SECRET")
        userID    = os.Getenv("USER_ID")
    )

    zoom.APIKey = apiKey
    zoom.APISecret = apiSecret

    var createMeetingOpts zoom.CreateMeetingOptions
    createMeetingOpts.HostID = userID
    meeting, err := zoom.CreateMeeting(createMeetingOpts)
    if err != nil {
        log.Printf("Got err: %+v\n", err)
    }
    log.Printf("Get Meeting Info: %+v\n", meeting)
}

あとはgo run main.goを実行するとZoomのMTGが作成されます。

go run main.go

おわりに

仕事でも使っているライブラリなんですが、結構簡単にMTGが作成できるので良い感じです。 今後GoでZoomのAPIを使う必要があるかたは是非触ってみて欲しいですね。

Goのライブラリにはじめてパッチを投げた

はじめに

この記事は、仕事で必要なライブラリにパッチを投げてそれがマージされた時の備忘録です。 今後もパッチを投げる可能性があるのでメモ書きとしてまとめている感じです。

背景

最近、仕事ではもっぱらGoを書いているんですが、ライブラリにパッチを投げるとかそういうことはなく平穏な日々を過ごしてました。 が、数か月前から仕事で大き目な機能改修があり、「さすがにこれは使ってるライブラリを改修しなければならないな」という事態が起きたんですよね……。

まあ、こういう時によくあるのは「Forkしてそれを改修する」か「改修したパッチを投げてマージされるようにする」のどちらかだと思います。

で、今回は後者のパターンで行くことにしたんですね。

理由としては二つありまして

  • ⓵ Goでなんかしらのパッチ投げたことがこれまでなかった
  • ⓶ Forkしてメンテナンスし続けるのは辛いのでしたくなかった

こんな感じですね。

Forkするのは最初は良いんですが、だんだんメンテナンスするのが辛くなってくるのでやりたくなかったんですよねぇ……。 それに、Goのライブラリにパッチ投げたこともなかったので「いい機会だしやってみるか」となり、パッチを書くことにしました。

やったこと

実際に作ったPRとしては以下です。

github.com

Zoomではグループというものを設定することができ、グループ単位でMTGとかの設定をすることができるようになっています。

今までは手動でグループにユーザーを追加していたんですが、ユーザー数が多くなると面倒くさかったりします。 で、今回の機能改修ではその辺を自動的にできるようにしたかったので上記のPRを投げた感じですね。

やってることとしては、group_member_post.goというファイルを追加して、ついでにサンプルコードを追加しました。

実際に追加しているコードとしては以下通りです。

package zoom

import "fmt"

// AddMenbersPath - v2 path for add group members
const AddMenbersPath = "/groups/%s/members"

// AddMemberOptions are details about add group members
type AddMemberOptions struct {
    GroupID string   `json:"-"`
    Members []Member `json:"members"`
}

// Member represents an group member
type Member struct {
    ID    string `json:"id"`
    Email string `json:"email"`
}

// ResopnseAddGroupMembers represents response for added member to group
type ResopnseAddGroupMembers struct {
    // IDs has comma-delimited, like 'xxxxxxxxxx,xxxxxxxxxx'
    IDs     string `json:"ids"`
    AddedAt string `json:"added_at"`
}

// AddMembers calls POST /groups/{groupId}/members
func AddMembers(opts AddMemberOptions) (ResopnseAddGroupMembers, error) {
    return defaultClient.AddMembers(opts)
}

// AddMembers calls POST /groups/{groupId}/members
// https://marketplace.zoom.us/docs/api-reference/zoom-api/groups/groupmemberscreate
func (c *Client) AddMembers(opts AddMemberOptions) (ResopnseAddGroupMembers, error) {
    var ret = ResopnseAddGroupMembers{}
    return ret, c.requestV2(requestV2Opts{
        Method:         Post,
        Path:           fmt.Sprintf(AddMenbersPath, opts.GroupID),
        DataParameters: &opts,
        Ret:            &ret,
    })
}

基本的に他の部分と同じような実装にして、かつZoomのAPIに書かれているパラメータを構造体として追加しています。

あと実際に使う場合のサンプルコードは以下の通りです。

package main

import (
    "log"
    "os"

    "github.com/himalayan-institute/zoom-lib-golang"
)

func main() {
    var (
        apiKey    = os.Getenv("ZOOM_API_KEY")
        apiSecret = os.Getenv("ZOOM_API_SECRET")
        userID    = os.Getenv("USER_ID")
        groupID   = os.Getenv("GROUP_ID")
    )

    zoom.APIKey = apiKey
    zoom.APISecret = apiSecret

    addMemberopts := zoom.AddMemberOptions{
        GroupID: groupID,
        Members: []zoom.Member{
            {
                ID: userID,
            },
        },
    }

    member, err := zoom.AddMembers(addMemberopts)
    if err != nil {
        log.Printf("Got add members: %+v\n", err)
    }
    log.Printf("Resopnse add members: %+v\n", member)
}

サンプルの追加と実装の追加はそんなに難しくはなかったですね(まあ、実際には色々フィードバックをもらったので直してたりしましたが……)

あと、CIの設定を弄る必要があるのに早い段階で気づけなかったのはよくなかったなぁと……。

おわりに

とりあえず、仕事の改修にも間に合って無事マージされたので良かったです。 とはいえ、まだ改修しなきゃいけないとことかあるので必要に応じて今後もパッチを投げていきたいですねー。