diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml new file mode 100644 index 0000000..79cb74a --- /dev/null +++ b/.github/workflows/docker.yaml @@ -0,0 +1,34 @@ +name: Docker + +on: + push: + branches: + - master + + tags: + - v* + +env: + IMAGE_NAME: change-status-go + +jobs: + push: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Build an image with a tag + run: docker build -t app . + + - name: Log into the registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin + + - name: Push the image + run: | + IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME + VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') + [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') + [ "$VERSION" == "master" ] && VERSION=latest + docker tag app $IMAGE_ID:$VERSION + docker push $IMAGE_ID:$VERSION diff --git a/.gitignore b/.gitignore index 2ca5a1f..4bd8ebc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.exe + .idea -secret +vendor diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..00b52ae --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:1.14.4-alpine3.12 as build +RUN mkdir /src +COPY . /src +WORKDIR /src +RUN go build -a -tags netgo -installsuffix netgo -o change-status-go + + +FROM scratch +COPY --from=build /src/change-status-go /bot/ +WORKDIR /bot/ +ENTRYPOINT [ "/bot/change-status-go" ] diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7f093c1 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module change-status-go + +go 1.14 + +require ( + github.com/bwmarrin/discordgo v0.20.3 + github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect + golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect + golang.org/x/tools v0.0.0-20200501205727-542909fd9944 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8be8df3 --- /dev/null +++ b/go.sum @@ -0,0 +1,34 @@ +github.com/bwmarrin/discordgo v0.20.3 h1:AxjcHGbyBFSC0a3Zx5nDQwbOjU7xai5dXjRnZ0YB7nU= +github.com/bwmarrin/discordgo v0.20.3/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200501205727-542909fd9944 h1:H2fcUfqnOlhuExePgcxfMRf98XwAWOF2pqkJTFTc2z0= +golang.org/x/tools v0.0.0-20200501205727-542909fd9944/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/main.go b/main.go index 2d460cd..b1f1254 100644 --- a/main.go +++ b/main.go @@ -1,55 +1,43 @@ package main import ( + command "change-status-go/src" "fmt" + "os" + "os/signal" + "syscall" + "github.com/bwmarrin/discordgo" - "log" - "strings" ) -var ( - Token = "Bot Njk5OTM0MTQ5NzI0NzMzNTIw.XpcOmQ.4laodM1AugWukek0aPpl-glBfEU" - BotName = "699934149724733520" - StopBot = make(chan bool) - Hello = "!hello" -) func main() { - var discord, err = discordgo.New() - discord.Token = Token + discordBrain, err := discordgo.New() if err != nil { - fmt.Println("Error logged in") - fmt.Println(err) + panic(err) } - discord.AddHandler(onMessageCreate) - - err = discord.Open() - if err != nil { - fmt.Println(err) + discordToken := loadToken() + if discordToken == "" { + panic("no discord token exists.") } + discordBrain.Token = discordToken - fmt.Println("Listening...") - <-StopBot - return -} + discordBrain.AddHandler(command.MessageCreate) -func onMessageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { - c, err := s.State.Channel(m.ChannelID) + err = discordBrain.Open() if err != nil { - log.Println("Error getting channel: ", err) - return + panic(err) } + defer discordBrain.Close() - if strings.HasPrefix(m.Content, fmt.Sprintf("%s", Hello)) { - sendMessage(s, c, "Hello world!") - } + fmt.Println("Bot起動完了、命令待機中") + discordBrain.AddHandlerOnce(command.BootNotify) + sc := make(chan os.Signal, 1) + signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) + <-sc + return } -func sendMessage(s *discordgo.Session, c *discordgo.Channel, msg string) { - _, err := s.ChannelMessageSend(c.ID, msg) - - log.Println(">>> " + msg) - if err != nil { - log.Println("Error sending message: ", err) - } +func loadToken() string { + return os.Getenv("DISCORD_TOKEN") } diff --git a/src/decodeMorse.go b/src/decodeMorse.go new file mode 100644 index 0000000..fbb6f4a --- /dev/null +++ b/src/decodeMorse.go @@ -0,0 +1,127 @@ +package src + +import ( + "errors" + "fmt" + "log" + "regexp" + "strings" +) + +var alphabetTable = map[string]string{ + "a": ".-", + "b": "-...", + "c": "-.-.", + "d": "-..", + "e": ".", + "f": "..-.", + "g": "--.", + "h": "....", + "i": "..", + "j": ".---", + "k": "-.-", + "l": ".-..", + "m": "--", + "n": "-.", + "o": "---", + "p": ".--.", + "q": "--.-", + "r": ".-.", + "s": "...", + "t": "-", + "u": "..-", + "v": "...-", + "w": ".--", + "x": "-..-", + "y": "-.--", + "z": "--..", + "0": "-----", + "1": ".----", + "2": "..---", + "3": "...--", + "4": "....-", + "5": ".....", + "6": "-....", + "7": "--...", + "8": "---..", + "9": "----.", + ".": ".-.-.-", + ",": "--..--", + "?": "..--..", + "'": ".----.", + "!": "-.-.--", + "/": "-..-.", + "&": ".-...", + ":": "---...", + ";": "-.-.-.", + "=": "-...-", + "+": ".-.-.", + "-": "-....-", + "_": "..--.-", + "\"": ".-..-.", + "$": "...-..-", + "@": ".--.-.", + " ***amend*** ": "........", + " ": "*", +} + +func searchTable(morseInput string) (string, bool) { + for alphabet, morse := range alphabetTable { + if morseInput == morse { + return alphabet, true + } + } + + return "", false +} + +func decode(sentence string) (response string, Err error) { + reg := regexp.MustCompile(`\s+`) + sentence = reg.ReplaceAllString(sentence, " ") + log.Printf(sentence) + for _, part := range strings.Split(sentence, " ") { + + alphabet, found := searchTable(part) + + if !found { + Err = errors.New(fmt.Sprintf("Not found such code: %s", part)) + return + } + + response += alphabet + } + return +} + +func DecodeMorse(messageContent string) (decodeResult string, Err error) { + sentence := strings.TrimSpace(messageContent) + decodeResult, Err = decode(sentence) + if Err != nil { + log.Println("Failed to decode:", Err) + return + } + return +} + +func morseCodeOperation(mode string, content string) (answerSentence string, Err error) { + switch mode { + case "decode": + answerSentence, Err = DecodeMorse(content) + return + default: + return "", fmt.Errorf("Error at morseCodeOperation: No such operation.") + } +} + +func morseAction(command string, codeSentence string, context messageContext) { + contentText, Err := morseCodeOperation(command, codeSentence) + if Err != nil { + log.Println("failed decode morse: ", Err) + return + } + Err = context.messageSend(contentText) + if Err != nil { + log.Println("failed send message: ", Err) + return + } +} diff --git a/src/fecthMessage.go b/src/fecthMessage.go new file mode 100644 index 0000000..d0ba8ea --- /dev/null +++ b/src/fecthMessage.go @@ -0,0 +1,12 @@ +package src + +func fetchMessage(cmd string) string { + switch cmd { + case "help": + return helpMessage + case "ping": + return "Pong!" + default: + return "該当するコマンドがありません。`%help`を参照してください。" + } +} diff --git a/src/message.go b/src/message.go new file mode 100644 index 0000000..e4d620c --- /dev/null +++ b/src/message.go @@ -0,0 +1,24 @@ +package src + +var ( + helpMessage = "```asciidoc\n" + + `= Title = + something += Description = + 表示名に絵文字を付けることでステータスを表示するBotの予定 += Command = + (Prefix: "%") + help :: このBotの概要を説明します。 + ping :: Pong!と返します。かわいいですね。 + color :: 第二引数に以下の形式で入力: + (prefix: "#") + [HexRGB] :: 16進カラーコードの画像を返します。略記法は現在対応していません。 + morse :: モールス暗号に関する変換をします。第2引数で動作を指定します。 + decode :: 暗号文 → 平文 + [Tips] + 現在任意の長さの空白を暗号変換に際して圧縮しています。これにより本来長い空白を入れることによって + 平文での空白を表現していたところを、*を代替しています。 += Source = + github.com/brokenManager/change-status-go +` + "```\n" +) diff --git a/src/sendImage.go b/src/sendImage.go new file mode 100644 index 0000000..98cda85 --- /dev/null +++ b/src/sendImage.go @@ -0,0 +1,109 @@ +package src + +import ( + "bufio" + "bytes" + "fmt" + "image" + "image/color" + "image/jpeg" + "io" + "log" + "strconv" +) + +func ParseColorCode(colorCode string) (Result color.RGBA, Err error) { + red, Err := strconv.ParseInt(colorCode[0:2], 16, 32) + if Err != nil { + log.Println(fmt.Sprintf("Red --- strconv.ParseInt: %s", Err)) + return + } + green, Err := strconv.ParseInt(colorCode[2:4], 16, 32) + if Err != nil { + log.Println(fmt.Sprintf("Green --- strconv.ParseInt: %s", Err)) + return + } + blue, Err := strconv.ParseInt(colorCode[4:6], 16, 32) + if Err != nil { + log.Println(fmt.Sprintf("Blue --- strconv.ParseInt: %s", Err)) + return + } + log.Printf("\"%s\" parsed as %d, %d, %d,", colorCode, red, green, blue) + Result.R = byte(red) + Result.G = byte(green) + Result.B = byte(blue) + return + +} + +func genImage(colorInfo color.RGBA) *image.RGBA { + const ( + startX = 0 + startY = 0 + width = 40 + height = 30 + ) + + img := image.NewRGBA(image.Rect(startX, startY, width, height)) + + for y := img.Rect.Min.Y; y < img.Rect.Max.Y; y++ { + for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ { + img.Set(x, y, colorInfo) + } + } + return img +} + +func GenerateImage(colorCode string) (fileReader io.Reader, Err error) { + var ( + buffer bytes.Buffer + fileWriter = bufio.NewWriter(&buffer) + ) + + colorCodeHex := colorCode[len(colorPrefix):] + log.Println(colorCodeHex) + + if v, Err := strconv.ParseInt(colorCodeHex, 16, 32); Err != nil || v < 0 { + log.Println("strconv: invalid value; not Hex") + return nil, Err + } + + colorInfo, Err := ParseColorCode(colorCodeHex) + if Err != nil { + log.Println(Err) + return + } + + colorImage := genImage(colorInfo) + + fileReader = bufio.NewReader(&buffer) + + Err = jpeg.Encode(fileWriter, colorImage, &jpeg.Options{Quality: 60}) + if Err != nil { + text := fmt.Sprintf("Error at encoding jpeg: %s", Err) + log.Println(text) + return + } + Err = fileWriter.Flush() + if Err != nil { + text := fmt.Sprintf("Error at io.Writer flush: %s", Err) + log.Println(text) + return + } + + log.Println("generatedImage: process ended") + return +} + +func colorAction(command string, context messageContext) { + fileData, Err := GenerateImage(command) + if Err != nil { + log.Println("failed to genarateImage: ", Err) + return + } + Err = context.fileSend("unkonow.jpeg", fileData) + if Err != nil { + log.Println("failed file send: ", Err) + return + } +} diff --git a/src/tools.go b/src/tools.go new file mode 100644 index 0000000..aadeed9 --- /dev/null +++ b/src/tools.go @@ -0,0 +1,87 @@ +package src + +import ( + "io" + "log" + "strings" + + "github.com/bwmarrin/discordgo" +) + +const ( + prefix = "%" + colorPrefix = "#" +) + +type messageContext struct { + s *discordgo.Session + m *discordgo.Message +} + +func (cxt *messageContext) messageSend(message string) (Err error) { + _, Err = cxt.s.ChannelMessageSend(cxt.m.ChannelID, message) + if Err != nil { + log.Println("failed send message: ", Err) + } + return +} + +func (cxt *messageContext) fileSend(fileName string, data io.Reader) (Err error) { + _, Err = cxt.s.ChannelFileSend(cxt.m.ChannelID, fileName, data) + if Err != nil { + log.Println("failed send file: ", Err) + } + return +} + +func MessageCreate(session *discordgo.Session, message *discordgo.Message) { + Context := messageContext{ + session, + message, + } + + if message.Author.ID == session.State.User.ID { + return + } + if !strings.HasPrefix(message.Content, prefix) { + return + } + commandBody := strings.Split(message.Content, " ") + switch commandBody[0][len(prefix):] { + case "color": + if len(commandBody) != 2 { + Err := Context.messageSend("コマンドの形式が間違っています。`%help`を参照してください。") + if Err != nil { + log.Println("failed send message: ", Err) + return + } + return + } + colorAction(commandBody[1], Context) + case "morse": + if len(commandBody) != 2 { + Err := Context.messageSend("コマンドの形式が間違っています。`%help`を参照してください。") + if Err != nil { + log.Println("failed send message: ", Err) + return + } + return + } + morseAction(commandBody[1], Context.m.Content[len("%morse decode"):], Context) + default: + contentText := fetchMessage(commandBody[0][len(prefix):]) + Err := Context.messageSend(contentText) + if Err != nil { + log.Println("failed send message: ", Err) + } + } + +} + +func BootNotify(s *discordgo.Session, _ *discordgo.Ready) { + // BootNotify is sending message when this bot is booted. + _, Err := s.ChannelMessageSend("699941274484080660", "BootBot! <@!622077711309078529>") + if Err != nil { + log.Println("Boot failed: ", Err) + } +}