20210213のGoに関する記事は5件です。

絵文字生成する slack コマンド作成

先日、絵文字生成を行なうコマンドとサーバを作成したため、それに合わせて slack のコマンドを作成してみました。軽いツールの説明と導入手順を書いていきます。

ツール説明

絵文字作成ツール

まず、絵文字作成ツールです。
https://github.com/komem3/wordimg

入力したテキストから絵文字を作成してくれるコマンドラインツールが欲しいと思い作成しました。 Go で作ったのでコマンドとサーバのどちらも作成してみました。サーバの URL は https://wordimg-otho5yxlgq-an.a.run.app/wordimg です。絵文字ジェネレーター のコマンドライン版みたいのを作りたいなというモチベーションで作成しましたが、あちらの完成度が高いのでまだまだ改良の余地があるなと感じております。

ちなみに、https://wordimg-otho5yxlgq-an.a.run.app/wordimg?text=helloworld というURLを見ると以下のようになります。更新するたびに違う画像が表示されるはずです。

hellworld

Slackbot

こちは先程の slackbot 版です。こちらも Go で書かれています。
https://github.com/komem3/wordimg/tree/main/slackbot

slack のコマンドを受けとり、その引数の絵文字を生成してくれる slackbot です。イメージ的には以下のような感じ。

/wordimg hello word

image

やっていることは文字をクエリとしてた URL をslack に返しているだけです。コード見ると slack の認証関係でぐちゃぐちゃやっていますが、基本的にはそれだけです。

Slackbot 導入手順

せっかく作ったのでこの bot の導入手順を記していきます。この子は cloud function で動かすことを想定して作成したため、必要なものは以下になります。

  • GCP プロジェクト
  • gcloud sdk

0. コードのダウンロード

コードをクローンなどでローカルにダウンロードを行ない、slackbot ディレクトリに入ってください。

1. コードのデプロイ

スクリプトを実行し、自身の GCP プロジェクトにコードをデプロイして下さい。

./deploy.sh $your_projectID

2. 環境変数の設定

デプロイした関数の環境変数の「URL」に https://wordimg-otho5yxlgq-an.a.run.app/wordimg を設定してください。自身の環境に絵文字作成ツールをデプロイしている場合は、そちらの URL を指定してください。(ないと思うけど)

image.png

3. Slack アプリを新規作成

https://api.slack.com/apps こちらから、Create New App で新規作成ができます。

4. コマンドを登録

以下4つのコマンドを登録していきます。URLは先程デプロイした Cloud Function の URL になります。Cloud Function の URL は Cloud Function の詳細のトリガータブから見ることができます。

  • /wordimg
  • /wordimg1
  • /wordimg2
  • /wordimg3

image.png

5. Slack への権限追加

ボットにチャットへの書き込み権限を与えます。こいつを与えないと slack に書きこめません。

image.png

6. Slack にインストール

その他、Bot の名前を入力したりアイコン付けたりする必要がありますが、 Bot 作成に必要な項目を全て入力すると Install App によってワークスペースにアプリがインストールできます。

7. シークレットマネージャーに追加

インストールを行なうと、API トークンが発行されるため、これと、Basic Information から見れる Signing Secret をシークレットマネージャーに追加します。
以下の名前でそれぞれ追加してください。

  • SLACK_SECRET : Signing Secret の値
  • SLACK_APIKEY : 発行された API トークンの値

image.png

8. Cloud Function のサービスアカウントに権限付与

デフォルトの Cloud Function のサービスアカウントはシークレットへのアクセス権を持っていないため、権限を付与してシークレットにアクセスできるようにする必要があります。
サービスアカウントの名前は プロジェクトID@appspot.gserviceaccount.com のような名前のはずです。こいつに Secret Manager Secret Accessor 権限(日本語だとシークレットアクセッサーだったはず)を与えてください。

image.png

9. 好きなチャンネルに追加

適当なチャンネルに追加してください。なかなか画像がでかくていうざいので、まずは Bot に DM で色々試して雰囲気を掴んでもらうといいかもです。

総括

  • Go は絵文字も作れてすごい。(めっちゃ作りにくかったけど)
  • 512*512 の画像はでかくてうざい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Regular Contest 112のメモ

前置き

Atcoderをやってみたので、自分用のメモです。
あとから加筆・修正する予定です。

問題

https://atcoder.jp/contests/arc112

A

Q_A.go
package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
)

var sc = bufio.NewScanner(os.Stdin)

func nextInt() int {
    sc.Scan()
    i, e := strconv.ParseInt(sc.Text(),10,32)
    if e != nil {
        panic(e)
    }
    return int(i)
}


func main() {
    sc.Split(bufio.ScanWords)

    var T int
    fmt.Scanf("%d", &T)

    var L, R int 
    var count int

    counts := make([]int, T)

    for i:=0; i<T; i++{
        count = 0
        L = nextInt()
        R = nextInt()

        if (L == 0) && (R == 0){
            count = 1
        } else {
            d := R - L
            if (d<=R) && (L<=d){
                count = (d-L+1) * (d-L+2) /2
            }
        }
        counts[i] = count    
    }

    for i:=0; i<T; i++{
        fmt.Printf("%d\n", counts[i])
    }    
}

B

Q_B.go
package main

import (
  "fmt"
)

func main() {
  var B, C int64
  fmt.Scanf("%d %d", &B, &C)

  var b_0, b_1, b_2, b_3 int64
  var A0, A1, A2  int64

  b_0 = B - C/2 // 下がり続ける
  b_1 = - B - (C-1)/2 // 一周回って下がり続ける
  b_2 = B + (C-2)/2 // 一周回って下がり続け、最後に戻る
  b_3 = - B + (C-1)/2

  if B == 0{
    A0 = - b_0 + 1
    A1 = 0
    A2 = b_2 - B
  } else if B > 0{
    if (b_0 <= 0){
      A0 = B + B
    }  else {
      A0 = B - b_0 + 1
      A0 += b_3 + B
    }
    A1 = - B - b_1 + 1
    A2 = b_2 - B
  } else {
    if (b_1 <= 0){
      A1 = -B - B
    }  else {
      A1 = -B - b_1 + 1
      A1 += b_2
    } 
    A0 = B - b_0 + 1
    A2 = b_3 + B
  }

  fmt.Printf("%d\n", A0+A1+A2)
}

C

覚えてたら後で書きます。

D

覚えてたら後で書きます。

E

覚えてたら後で書きます。

F

覚えてたら後で書きます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Paizaでも好きなエディタ使いたい!

Qiita初投稿です。
今回は自作のCLIツールを紹介します。

TL;DR

  • Paizaのスキルチェックを始めたが、Webページ上のエディタは使いづらいので、ローカルのエディタでコーディング&テストできるようなCLIツールを作ってみた

制作物

pz.png
(↑クリックするとYouTubeへジャンプします。)

リポジトリはこちら → reeve0930/pz

なぜ作ったか

ものすごく個人的な話なので、興味のない人は読み飛ばして下さい…

最近、AtCoderで競技プログラミングを始めました。
AtCoderで過去問を解いて色々と勉強していたのですが、
Paizaのスキルチェックで似た感じの問題が解けるということを友人に教えてもらったため、
こちらの問題も練習として解いてみることにしました。
Paizaも同様にレーティングがあり、Aランク問題より上に行くと現状の自分では即答できない問題が出てくるようになり、
確かに練習問題として解くにはちょうどいいと感じました。

ただ、一点不満な点がありました。
それはWebページ上のエディタでコーディングをしなければならないことです。
以下の点でコーディングしづらいと感じています (一部個人的な理由を含んでいます)。

  • エディタは問題ページ下部に存在し、問題文を読みながらコーディングできない
  • 今まで作成した関数を使うためには、それを毎回コピペする必要がある。
  • Vimキーバインドを使用できない (個人的理由)

私は普段VSCodeVim拡張を入れてコーディングしています。
カーソルキーを使わずにカーソルを移動できるため、キーボードのみでコーディングできるのが魅力です。
また、便利なキーバインドも多数あり、これがないとコーディングする気が起きなくなってしまいました (中毒)。

AtCoderでは以下のようなツールがあり、自分の好きなエディタでコーディングできる環境が整っています。

  • atcoder-cli
    • 問題用のディレクトリの作成からテストケースのダウンロードなどを自動化
  • online-judge-tools
    • テストケースのチェックやコードの提出を自動化

AtCoderはコンテストサイトのため、競技性が高く、それ故このようなツールを制作する人も多いです。
しかし、Paizaの場合はあくまでも個人のスキルを評価することを目的としているため、
これらの便利なツールが今まで開発されて来なかったのだと思います (私が見つけられてないだけで、既にあったらごめんなさい)。

Paiza用CLIツール: pz

前置きが長くなりましたが、以上の背景からPaiza用CLIツール pzを作成してみました。
このツールでできることは以下の通りです。

  • Paizaへの自動ログイン
  • 問題用のディレクトリの作成
  • 問題のテストケースのダウンロード
  • 事前に準備しているテンプレートのコピー
  • テストケースのチェック
  • 特定のテストケースに対するデバッグ
  • コーディングした内容をクリップボードにコピー
  • 提出用フォームのプルダウンメニューを事前に設定したプログラミング言語に設定

このツールのインストール方法や詳細な使い方についてはリポジトリのREADMEに記載しております。
使ってみたい方はこちらからご確認下さい。
reeve0930/pz

開発に用いたもの

マルチプラットフォームのCLIツールにしたかったため、Goを用いました。
異なるOS用のバイナリもコンパイルできるため(クロスコンパイル)、多様な動作環境で動作させるプログラムを作成できます。
(とは言え、M1 Macに対応させようとして、GOOS=darwin GOARCH=arm64でビルドしたら失敗しました… Goがまだ対応していない…? 誰か教えて下さいw)

また、Goのライブラリとしては以下を用いました。

CLIツールの開発を劇的に楽にするライブラリです。
Cobra自体もCLIツールとなっており、これを使うことでテンプレートとなるコードを自動で生成してくれます。

chromedriverなどでブラウザを操作するライブラリです。
seleniumのGo言語用のライブラリとなります。
chromedriverでブラウザと連動させることで、
問題番号などを指定せずとも現在のページの状態に応じて処理を行えるようにしました。

今回実装できなかったこと

実は当初考えていたけど現状実装できていない機能もあります。
submitコマンドで当初はWebページの提出用のテキストフォームに自動でコピペするように作りたかったのですが、
テキストフォームの作りが少し特殊でうまくできなかったのでクリップボードにコピーすることにし、
ペーストは手動で行うようにしました。
Paizaのエディタでも一応シンタックスハイライトや補完が効くので、
そのあたりの処理の関係でうまくいかないのだと思います。
時間があるときにそのあたりをちゃんと解析し、実装したいと思います。
うまくいく方法がわかる方がいましたら教えていただけると大変助かります。
何ならプルリクでも… (強欲)

まとめ

今回はPaizaのスキルチェック用の自作CLIツール pzを紹介しました。
興味がある方がいましたら是非使っていただけるとありがたいです。
また、バグや機能改善要望などがございましたら、issueに書き込んでいただければ、随時対応したいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

15rep - 独自Errorの作成 ~ 備忘録

書いた理由

  • API作成しているときにエラーって返すと思います。その際に独自でErrorを定義し返すというよくある内容をメモ化(作り込まず簡易)

独自エラーの定義

  • 独自のエラー型を定義するやり方
  • Goの組み込みのエラー型(error)は以下のような型です。 (errorインタフェース)
type error interface {
    Error() string
}
  • func Error() string を実装した型は全てerrorインターフェースが実装されているものとみなす。(便利)

実際に書いてみた

package main

import (
    "fmt"
    "net/http"
    "encoding/json"
)

type ErrorMessage struct {
    Code    int
    Message string
}

// Errorメソッドさえ定義されていれば良い
func (e *ErrorMessage) Error() string {
    return e.Message
}

func errHandlerSample() error {
    return &ErrorMessage{
        Code: http.StatusBadRequest,
        Message: "sample error!!!",
    }
}

func main() {
    if err := errHandlerSample(); err != nil {
        switch err := err.(type) {
        case *ErrorMessage:
        // ErrorMessageの処理
        // switch文の頭で再定義したerr変数は*ErrorMessage型なので、
        // ErrorMessage型の各フィールドにアクセスできる。
          stJson := ErrorMessage{Code: err.Code, Message: err.Message}
          out, _ := json.Marshal(stJson)
          fmt.Println(string(out))
        default:
            fmt.Println("Hello, playground")
        }
    }
}

結果: {"Code":400,"Message":"sample error!!!"}
  • 型キャストしSwitchで判定して出力しています。
  • 他にも判別する内容があればStructで定義しSwitchの判定に入れてください。
  • jsonで出力する部分は別途関数化して返す内容にしてもいいと思います。
func ResponseJSON(w http.ResponseWriter, obj interface{}, httpStatus int) error {
    b, err := json.Marshal(obj)
    if err != nil {
        return err.Error()
    }
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("Content-Length", fmt.Sprintf("%d", len(b)))
    w.WriteHeader(httpStatus)
    w.Write(b)
    return nil
}

終わりに

  • 「func Error() string を実装した型は全てerrorインターフェースが実装されているものとみなす」 ので気軽に独自にError作成を作成できより状態に合わせて作成できます。

参考記事

Go言語のエラーハンドリングについて
WebAPIでエラーをどう表現すべき?15のサービスを調査してみた

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】一体いつからappendの引数に与えた変数が不変だと錯覚していた?

結論

slice =  append(slice[:idx], slice[idx+1:]...)

上記のようにできない場合で、どうしても元のスライスを保持したいときはmake&copyしましょう

slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
newSlice := make([]int, len(slice))
copy(newSlice, slice)

https://play.golang.org/p/T9su3lB5xi5

はじめに

スライスを使うときに無意識でappendを使うかと思います。
単純に追加するだけであれば、至って普通の挙動を示しますが、とある条件の時に
想定しない挙動になることがあります。

実験内容

配列の中から特定のインデックスにある値を削除する処理です。
例えば以下のようなコードがあった場合はどうでしょうか?

main.go
package main

import "fmt"

func main() {
    slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Println(slice)
    idx := 5
    for k, v := range slice {
        if k == idx {
            fmt.Println("before:", slice[k], v) //5 5
            fmt.Println("append", append(slice[:idx], slice[idx+1:]...)) //特定の配列を削除する
            fmt.Println("original slice", slice)
            fmt.Println("after", slice[k], v) // この出力は?
        }
    }
}

https://play.golang.org/p/v3gXZ9bFb-A

普通は

slice = append(slice[:idx], slice[idx+1:]...)

のようにするところですが、例えば以下のようなケースが想定されます。
1. 共通化するべく、別メソッドで作る
2. 削除する前と後を両方保持しておきたい

感覚的にはbeforeとafterは全く同じになると思うかもしれません。

実験結果

実際に実行してみます

[0 1 2 3 4 5 6 7 8 9]
before: 5 5
append [0 1 2 3 4 6 7 8 9]
original slice [0 1 2 3 4 6 7 8 9 9]
after 6 5

afterの値が「5 5」ではなく「6 5」となります。元のスライスもおかしくなっていることがわかります。

なぜ普遍だと錯覚していた?

A Tour of Go より

もし、元の配列 s が、変数群を追加する際に容量が小さい場合は、より大きいサイズの配列を割り当て直します。 その場合、戻り値となるスライスは、新しい割当先を示すようになります。

つまり言い換えるとappendの引数にあるスライスの容量が新たに割り当てるまでもなく大きい場合は割り当て直さない。

append にで指定した元の配列が変更されてしまい、
感覚と反する結果になっています

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む