概要
ブラウザ操作をやってみたかったので、
毎日勤怠管理で使用しているAKASHIのボタンを自動ブラウザで押せるようなアプリケーション作ってみました。
Goを使用した背景は、Goで書きたかったからです笑
実際のコード見たい方がいらっしゃいましたら、別途GitHubのリポジトリ公開しようと思います。
実装環境
- macOS Monterey(12.2.1)
- Goland
- go version go1.20 darwin/arm64
使用フレームワーク
1. agouti
Golangでブラウザ操作してくれるやつ。
% go get https://pkg.go.dev/github.com/sclevine/agouti
今回はChromeを使用するので、事前にChrome Driverもインストールしておきます。
% brew install chromedriver
2. godotenv
Golangで環境変数が使用できるようになるやつ
% go get github.com/joho/godotenv
ツリー構造
.
├── .gitignore
├── .env
├── akashi.go
├── go.mod
├── go.sum
└── main.go
コード
main.go
package main
import (
"fmt"
"github.com/sclevine/agouti"
"log"
)
func main() {
// オプションの選択
option, err := selectOption()
if err != nil {
fmt.Printf("select option error: %s\n", err)
return
}
// chromeドライバを使って、Google Chromeを使用できるようにする。
agoutiDriver := agouti.ChromeDriver()
if err := agoutiDriver.Start(); err != nil {
fmt.Printf("Failed to start driver. %s\n", err)
return
}
defer agoutiDriver.Stop()
// agoutiを使用して新規ブラウザを立ち上げる。開いたブラウザを使って色々操作します。
page, err := agoutiDriver.NewPage()
if err != nil {
fmt.Printf("Failed to open a new page. %s\n", err)
return
}
// オプションに応じて勤怠処理の実行
if err := executeAkashiTimeClock(page, option); err != nil {
log.Fatalln(err)
}
fmt.Println("success")
}
akashi.go
package main
import (
"fmt"
"github.com/joho/godotenv"
"github.com/sclevine/agouti"
"os"
)
const (
attendanceOptionNumber = 1
leavingOptionNumber = 2
)
func selectOption() (int, error) {
var optionMap = map[int]string{
attendanceOptionNumber: "出勤",
leavingOptionNumber: "退勤",
}
// 実行したいオプションを入力してもらう
var option int
fmt.Printf("select options \n")
for k, v := range optionMap {
fmt.Printf("%v: %v\n", k, v)
}
fmt.Printf(">")
fmt.Scan(&option)
// 入力されたオプションが正しいか判定
if _, ok := optionMap[option]; !ok {
return 0, fmt.Errorf("not the correct option value")
}
return option, nil
}
func executeAkashiTimeClock(page *agouti.Page, option int) error {
// AKASHIログインページ移動
if err := openAkashiLoginPage(page); err != nil {
return err
}
// AKASHIログイン
if err := loginAkashi(page); err != nil {
return err
}
// 音声ミュート
if err := muteAkashi(page); err != nil {
return err
}
// オプションに応じて勤怠処理の変更
var xPath string
switch option {
case attendanceOptionNumber:
xPath = "/html/body/div[1]/div/section/form/div[2]/div/div[2]/ul[1]/li[1]/a[@data-punch-type='attendance']"
case leavingOptionNumber:
xPath = "/html/body/div[1]/div/section/form/div[2]/div/div[2]/ul[1]/li[2]/a[@data-punch-type='leaving']"
}
button := page.FindByXPath(xPath)
if err := button.Click(); err != nil {
return fmt.Errorf("Failed to click button. %s\n", err)
}
return nil
}
func openAkashiLoginPage(page *agouti.Page) error {
// 次に勤怠ページを指定したAKASHIのページを開きます。
if err := page.Navigate("https://atnd.ak4.jp/login?next=%2Fmypage%2Fpunch"); err != nil {
return fmt.Errorf("Failed to navigate to AKASHI login page. %s\n", err)
}
return nil
}
func loginAkashi(page *agouti.Page) error {
// フォーム情報を取得
formCompanyId := page.FindByID("form_company_id")
formLoginId := page.FindByID("form_login_id")
formPassword := page.FindByID("form_password")
// 環境変数セット
if err := godotenv.Load(); err != nil {
return fmt.Errorf("Failed loading .env file: %s\n", err)
}
companyId := os.Getenv("FORM_COMPANY_ID")
loginId := os.Getenv("FORM_LOGIN_ID")
password := os.Getenv("FORM_PASSWORD")
// フォームにログイン情報を入力
formCompanyId.Fill(companyId)
formLoginId.Fill(loginId)
formPassword.Fill(password)
// submit
formSubmitButton := page.FindByID("submit-button")
if err := formSubmitButton.Click(); err != nil {
return fmt.Errorf("Failed to click form button. %s\n", err)
}
return nil
}
func muteAkashi(page *agouti.Page) error {
// muteボタンのXPathを指定して音声ミュート
muteButton := page.FindByXPath("/html/body/div[1]/div/section/form/div[1]/div[2]/ul/li[2]")
if err := muteButton.Click(); err != nil {
return fmt.Errorf("Failed to click mute button. %s\n", err)
}
return nil
}
.gitignore
.env
.idea
.env
# AKASHIログイン情報
FORM_COMPANY_ID=""
FORM_LOGIN_ID=""
FORM_PASSWORD=""
使用方法
% go build FILE_NAME
% go run BUILD_FILE_NAME
select options
2: 退勤
1: 出勤
> // 実行したいオプションを入力 → chromeが起動してオプションに応じたブラウザ操作実行
success
参考