2023幎12æ1æ¥ã«Qiitaã§å ¬éããŠããèšäºã®ã¢ãŒã«ã€ãã§ãã
å¬ã§ãããéåžžã«å¯ããŠè¶³ã®æå ãå€ãªè²ããŠããŸãã æè¿APIã䜿ã£ãŠäœããã®åŠçãããŠããã®ã«ããã£ãŠããŸãã
åã¯åœåPythonã§Twitterã®APIã䜿ã£ãŠæå ±ãæçš¿ã®äžæ¬æ¶å»ãè¡ã£ãŠã¿ããããŠæ¥œããã§ãããã§ããã©ãæ®å¿µãªããšã«2022幎ã®12æã«ãã¹ãŠã®ã¢ã«ãŠã³ããèšããããªãééåŠçã«éã£ãŠããŸããã¢ã«ãŠã³ããäœãçŽããã®ã«ããããããAPIææåéšåãåºãŠããŸããæµç³ã«ãããŸããã
ããããAPIã®å¿«æ¥œãå³ãã£ãŠããŸã£ãã®ã§ã©ããã§æåããããšããåæ©ã¯åãŸããïŒçŠæçç¶ïŒåãããšãMisskeyã§ãåºæ¥ãªãããšæã£ãŠææŠããŠã¿ãŸããã
è²ã æ©ãã çµæ11æã®éã«3ã€åºæ¥ããã§ããã©ããã®äžã®äžã€ã§ãããMisskeyã§æçš¿ããã³ã³ãã³ããæ¹æ¶ããAPIãããã¹ããããåœããåã®ããã«å šéšæçš¿ãæ¶ãã¡ãã£ãã®ã§ãè²ã èŠæŠããèšé²ãæ¶ãé£ãã§ããŸããŸããïŒèŠç¬ïŒ
ä»åã¯ãŸã èšæ¶ãæ®ã£ãŠããã¡ã«MisskeyãSpotifyã®APIã«é¢ããèªèšŒéšåãæçš¿éšåã§è©°ãŸã£ãããšãæ©ãã ããšãæŽçããŠãããŠãä»åŸãŸãäœããšãã«ç¢ºèªããããŒãã·ãŒãã«ããŠãããããšæããŸãã
ä»åãäŸã®åŠãGoã§æžããŸãããïŸïŸïœ·ïŸïŸïŸïœºïŸ
èªèšŒ
Misskey
https://misskey-hub.net/docs/api/
Misskeyã§APIã䜿ãããã«ã¯å¯Ÿå¿ãããµãŒããŒã®ã¢ã¯ã»ã¹ããŒã¯ã³ã®ååŸãå¿ é ã§ãã ã¢ã¯ã»ã¹ããŒã¯ã³ã®çºè¡æ段ã¯Misskeyã®èªã¢ã«ãŠã³ãã®èšå®æ¬ããæåã§ååŸããæ¹æ³ãšOauth2.0ã䜿ã£ãŠã¢ã¯ã»ã¹ããææ³ã®äºçš®é¡ãååšããŸãã ä»åã¯èªåã§å©çšããã ããšããç¹ãããæ¯èŒç楜ãªæåçºè¡æ¹åŒãæ¡çšããŸããã
åæã®å®è£ ã¯æ¯åå ¥åãããã¿ã€ãã§æžããŠã¿ãŸããã
func oauth() string { var token, host string fmt.Println("ããŒã¯ã³ãèšå®ããŠãã ããã") token = readInput("Token: ") fmt.Println("ãµãŒããŒã®ãã¹ããèšå®ããŠãã ããã") host = readInput("Host: ") endpoint = "https://" + host + "/api/" return token }
â ã¿ãŒããã«äžã«å ¥åãä¿ã衚瀺ãåºãããŠãŒã¶ãŒã«å ¥åãããã â¡å ¥åãããæ å ±ããšã³ããã€ã³ãã®åœ¢ã«ããŠæ圢ãã â¢POSTåŠçã«æž¡ããŠå®è¡ãã
ãšãã圢ã§ãã
åºæ¬çã«ã¯ããã§åé¡ãªããã§ããã©ãæ¯ååŠçãèµ°ããããã³ã«ããŒã¯ã³ãšãã¹ãåãå ¥åããŠãããšéåžžã«é¢åã ãªãšæã£ãã®ã§ããããã¡ã€ã«ã®äžã«ä¿æããŠããŸããããšããèãã«è³ããŸããã
ããããªãããã¡ã€ã«å ã«çŽæ¥ããŒã¯ã³ã®ãããªæ©å¯æ å ±ãä¿æãããŠããŸããšããã©ãªãºã ã©ããã®è©±ã§ã¯ãªãé»åé²åºçã«ãªã£ãŠããŸãæžå¿µããã£ãã®ã§ãã³ãŒãæ¬äœã«åã蟌ãã®ã§ã¯ãªããç°å¢å€æ°ã䜿ãããã«æ¹å転æããŸããã
.env
ãã¡ã€ã«ã䜿çšããå Žåãgitçã®ãªããžããªã«å
¬éããéã«.gitignore
ã§è©²åœãããã¡ã€ã«ãé€å€ã§ããç¹ããä»åŸDevãã©ã³ããTestãã©ã³ããäœã£ãŠç°ãªãç°å¢å€æ°ãç»å Žããéã«äžæ¬ããŠç®¡çããããšãåºæ¥ãã®ã§ããã¹ãããããã°ã«ãæé©ã§ãã
äœãããæ¯åå
¥åããå¿
èŠãç¡ãã®ãéåžžã«å©ãããŸããã
ä»åã¯Goåãã®å€éšããã±ãŒãžã§.env
ãã¡ã€ã«ããç°å¢å€æ°ãèªã¿èŸŒãããã®ããŒã«ãšããŠæ€çŽ¢ã«åŒã£ããã£ãgodotenv
ã䜿çšãããŠããã ããŸããã
godotenvãã€ã³ããŒããããšãããªæãã§ãã¡ã€ã«çµç±ã§ç°å¢å€æ°ãèªã¿åããããã«ãªãã®ã§éåžžã«äŸ¿å©ã§ãã
package main import ( "log" "os" "github.com/joho/godotenv" ) func main() { err := godotenv.Load() if err != nil { log.Fatal(".envãã¡ã€ã«ãèŠã€ãããªãã") } loginID := os.Getenv("YAH_YAH_YAH") logginSECRET := os.Getenv("DO_YA_DO") }
Misskeyã®å Žåã¯ãã¹ãåãšããŒã¯ã³ãå ¥åããããšã«ãªãã®ã§ããã®ããã«æ¹é ããŸããã
func oauth() string { err := godotenv.Load() if err != nil { fmt.Println(".envãã¡ã€ã«ãèŠã€ãããªããèªã¿èŸŒããŸãã") return "" } token := os.Getenv("TOKEN") host := os.Getenv("HOST") if token == "" || host == "" { fmt.Println("ããŒã¯ã³ãšãã¹ããæ£ããèšå®ããŠãã ããã") return "" } endpoint = "https://" + host + "/api/" fmt.Println("Endpoint:", endpoint) return token }
ãã¡ã€ã«ãèŠã€ãããªãããšã«å ããã©ã¡ãããæ¬ èœãããŠããŸãå¯èœæ§ããããããããªãã®ã§è²ã èŠåæãåºããŠããŸããããšã³ããã€ã³ãã«åŒãæž¡ãåŠçã¯ååãšåæ§ã®ãŸãŸã§åé¡ãããŸããã
ããšã¯mainé¢æ°ã®éšåã§Misskeyãæå®ããi
ã®ãã©ã¡ãŒã¿ãŒã«oauth()ã§å
¥åãããããŒã¯ã³ãæž¡ããšãèªèšŒãéãããã«ãªããŸãã
func main() { token := oauth() me, err := FetchUser("i", map[string]interface{}{"i": token}) if err != nil { fmt.Println("ç §äŒäžã«ãšã©ãŒãçºçããŸãã:", err) return }
Spotify
Spotifyã®APIã䜿çšããå ŽåãOAuth 2.0èªèšŒããã»ã¹ãéããŠã¢ã¯ã»ã¹ããŒã¯ã³access_token
ãšãªãã¬ãã·ã¥ããŒã¯ã³refresh_token
ãååŸããŸãã(ããããªããªãã«ãããã£ã...)
Spotifyã®ã¢ã¯ã»ã¹ããŒã¯ã³ã¯äžæéã§æ»ãã§ããŸãããããªãã¬ãã·ã¥ããŒã¯ã³ã䜿ã£ãŠå®æçã«èçããããšã«ãªããŸãããªãã¬ãã·ã¥ããŒã¯ã³ããããšãã¡ãã¡ãŠãŒã¶ãŒã«å床蚱å¯ãæ±ããªããŠãã¢ã¯ã»ã¹ããŒã¯ã³ãçºè¡ã§ããã®ã§éåžžã«äŸ¿å©ã§ãã
ããããSpotifyã®Devããã·ã¥ããŒãã§ç¢ºèªã§ããã®ã¯ClientIDãšClientSecretã ãã§ãã ãã®ããã ClientIDãšClientSecretã䜿çšããŠâãªãã¬ãã·ã¥ããŒã¯ã³çºè¡ã³ãŒããååŸâãªãã¬ãã·ã¥ããŒã¯ã³ãååŸâã¢ã¯ã»ã¹ããŒã¯ã³ã®ååŸãšããããã»ã¹ãèžãããšã«ãªããŸãã
https://github.com/yude/np2mast
ããã®åŠçã«é¢ããŠã¯Yudeãããšããéçºè ã®æ¹ã1幎ã»ã©åã«Mastodonã§åçäžã®æ²ãéä¿¡ããåŠçãæžãããŠããã®ã倧ãã«åèã«ããŸããã
åæã®æ®µéã§ã¯ã€ã³ããŒãããŠå¿ èŠãªæ å ±ãæžã蟌ãã ã®ã«äœæ ã401ãšã©ãŒãåºãŠããŸã£ãã®ã§æ©ãã§ãããšããããªãã¬ãã·ã¥ããŒã¯ã³çºè¡ã³ãŒããååŸããããã»ã¹ã§ã¹ã³ãŒããæå®ããéšåãåããŠããŸã£ãŠããã®ã§ããã¡ããè¿œå ããŠMisskeyåãã«æ¹é ããŸããã
values.Add("scope", "user-read-playback-state user-read-currently-playing")
//envãã¡ã€ã«ããèªã¿åãéšå values.Add("client_id", os.Getenv("SPOTIFY_CLIENT_ID")) values.Add("response_type", "code") values.Add("redirect_uri", "http://localhost:3000/callback") values.Add("scope", "user-read-playback-state user-read-currently-playing") fmt.Println("https://accounts.spotify.com/authorize?" + values.Encode())
//URLã«ã¢ã¯ã»ã¹ããŠã³ãŒããååŸããéšå func spotify_login(w http.ResponseWriter, req *http.Request) { values := url.Values{} values.Add("client_id", os.Getenv("SPOTIFY_CLIENT_ID")) values.Add("response_type", "code") values.Add("redirect_uri", "http://localhost:3000/callback") values.Add("scope", "user-read-playback-state user-read-currently-playing") http.Redirect(w, req, "https://accounts.spotify.com/authorize?"+values.Encode(), http.StatusFound) }
éä¿¡ãæ¶å»ã®åŠç
Misskeyã§æçš¿ãéä¿¡ãæ¶å»ããå Žåã¯ããšã³ããã€ã³ãã«é©åãªãã©ã¡ãŒã¿ã§json圢åŒã®ããŒã¿ãéä¿¡ããŸãã ãšã³ããã€ã³ãã¯åæäœããšã«æ確ã«åãããŠããŠããªã¯ãšã¹ãåŠçãPOSTãGETããšããç¹ã«é¢ããŠã匷ãæèããå¿ èŠããããŸãã
https://misskey-hub.net/docs/api/endpoints/notes/create.html
詳现ãªç¹ã¯ãšã³ããã€ã³ãäžèŠ§ããåç §ãã ããã
äŸãã°ãéä¿¡åŠçã®å Žåã¯
func postToMisskey(message string) error { misskeyURL := os.Getenv("MISSKEY_ENDPOINT_URL") + "/api/notes/create" //ããã§ãšã³ããã€ã³ããæå®ã"/api/notes/create"ãçŸè¡ã®æçš¿äœæã®ãšã³ããã€ã³ã requestData := map[string]string{ "i": os.Getenv("MISSKEY_ACCESS_TOKEN"), "text": message, "visibility": "home", } //ããã§ãšã³ããã€ã³ããèŠæ±ããé©åãªãã©ã¡ãŒã¿ãèšå®ããŠããªããã°Unauthorizedãšã©ãŒ(401)ãåºãŠããŸããŸãã jsonData, err := json.Marshal(requestData) if err != nil { return fmt.Errorf("ãªã¯ãšã¹ãããŒã¿ã®JSONãšã³ã³ãŒãã«å€±æããŸããã: %v", err) } resp, err := http.Post(misskeyURL, "application/json", bytes.NewBuffer(jsonData)) if err != nil { return fmt.Errorf("ããŒãã«å€±æããŸããã: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) log.Printf("Failed to post to Misskey. Status: %d. Response: %s", resp.StatusCode, string(body)) return errors.New("ããŒãã«å€±æããŸãã") } return nil }
åã®å ŽåãããŒã¯ã³ããã£ãŠãã®ã«èªèšŒãããªãã£...ïŒäœæ ïŒããšããç¹ã§éåžžã«è©°ãŸã£ãŠããŸã£ãŠããŸããé©åãªãã©ã¡ãŒã¿ãèšå®ãããŠããªãããšã«æéãããã£ãŠããŸããŸããã èŠããã«ããã®å®è£ ãæããŠããŠãçŽæ¥ãšã³ããã€ã³ãã«æååãéãããšããããã§ããã
requestData := map[string]string{ "i": os.Getenv("MISSKEY_ACCESS_TOKEN"), "text": message, "visibility": "public", }
jsonData, err := json.Marshal(requestData) if err != nil { return fmt.Errorf("ãªã¯ãšã¹ãããŒã¿ã®JSONãšã³ã³ãŒãã«å€±æããŸããã: %v", err) } resp, err := http.Post(misskeyURL, "application/json", bytes.NewBuffer(jsonData)) if err != nil { return fmt.Errorf("ããŒãã«å€±æããŸããã: %v", err) } defer resp.Body.Close()
éä¿¡å ã®Misskeyã¯å³æ Œã«ããŒã¿ã®æ±ãã決ããŠãããããåçŽãªæååã®æ¿å ¥ã§ã¯éä¿¡ã§ããªãããã«ãªã£ãŠããŸããéã«ãããããããå€ãªããŒã¿ãéãä»ããããªããšããç¹ãããéåžžã«å©ãããŸããã
æçš¿ã®ååŸãããŒãã®æ¶å»ãåæ§ã§ãã
func GetUsersNotes(userId, untilId, token string) ([]Note, error) { args := map[string]interface{}{} if untilId == "" { args = map[string]interface{}{ "userId": userId, "limit": 100, "i": token, "withChannelNotes": true, } } else { args = map[string]interface{}{ "userId": userId, "untilId": untilId, "limit": 100, "i": token, "withChannelNotes": true, } } notes, err := FetchNotes("users/notes", args) return notes, err }
func DeleteNote(noteId, token string) error { args := map[string]interface{}{ "noteId": noteId, "i": token, } return Post("notes/delete", args, nil) }
å°ããã¡ãã¡Json圢åŒã«çŽãã®ã¯éåžžã«åå«ã ãšæãã®ã§ãGoã§ã¯ãŠãŒã¶ãŒãããŒãããšã©ãŒæ¯ã«æ§é äœãæãããŠäžå 管çããæ¹æ³ã䟿å©ã§ãã
type User struct { Name string `json:"name"` Username string `json:"username"` NotesCount int `json:"notesCount"` Id string `json:"id"` PinnedNotes []Note `json:"pinnedNotes"` } type Note struct { Id string `json:"id"` CreatedAt time.Time `json:"createdAt"` } type Error struct { Message string `json:"message"` Code string `json:"code"` Id string `json:"id"` Kind string `json:"kind"` }
ãã®ããã«ç®¡çããŠããã°ãåæ§ã®åŠçãäœåºŠãèšè¿°ããå¿ èŠããããŸããã
è£è¶³
API䜿çšæã®äœæ³ãšããŠãMisskeyãMastodonã¯å人ã§éå¶ããŠãããµãŒããŒãå€ãããšãããAPIãäžåºŠã«å€§éã«ãªã¯ãšã¹ãããŠããŸããšéå¶è ã®ãµãŒããŒã«è² è·ããããŠããŸããŸãã
äžå¿ã©ã¡ãã«ããªã¯ãšã¹ãå¶éãèšããããŠããŠãå¹³åçã«ã¯300ãªã¯ãšã¹ãããããããš1æéã®ã€ã³ã¿ãŒãã«ãèšããããã«èšå®ããŠããã®ã§ãããäœåºŠãåãå Žæãããªã¯ãšã¹ãããããšãè¡åãæªãã®ãšãã³ã¹ãã®ç¡é§ãèããããŸãã
ã¶ã£ã¡ããåäžã®éä¿¡å ããäœåºŠããªã¯ãšã¹ãããŠãã®ãèŠããããšãããã€DOSãã¡ã ã ãª...ããšè§£éããŠIPBANãããããŸããã
ãã®ããããªã¯ãšã¹ãå¶éãããã£ããäœåºŠããªã¯ãšã¹ãããªãããã«ãã©ã³ã¯ã¿ã€ã ãèšããŠããã®ãæå¹ã§ãã
äŸãã°Goã®æšæºã©ã€ãã©ãªã«ããtime
ãã€ã³ããŒãããŠãããŠ
time.Sleep(30 * time.Minute)
ã®ããã«èšå®ããŠããã°30åééä¿¡åŠçãäžæããããšãåºæ¥ãŸãã
ææ³
ä»ãŸã§ã¯ããŒã«ã«ç°å¢ã§åŠçãè¡ããã®ã ããæžããŠããã®ããã£ãŠãéä¿¡å ã®äºãé æ ®ããå®è£ ã«é¢ããŠã¯æèã§ããŠããªãã£ãã®ã§ããã1ãæãéããŠAPIã®å®è£ ãåŠãã ããšã§ãè¡åã®ããéä¿¡ãšã¯äœããæèããããã«ãªããŸããã
ä»åŸããã£ã³ãã«ã®ããŒããæ¶ãå®è£ ã ã£ããããŽã«ãŒãã³ããããæã«åŸ©åž°ãããåŠçãè¿œå ããçãããããããšããã£ã±ãããã®ã§ãæ¥ã 粟é²ããŠåããŸãã
ããGoã§æžããšãšã©ãŒãã³ããªã³ã°ãã©ãããŠãé¢åããããšããæèŠãé ãã®ã§ãããåã¯è«žåŠè ãªã®ã§ã©ããã£ããšã©ãŒãªã®ããèšèã§èª¬æã§ããã¢ãŠããããã®å Žã«ãªãã®ã§ãããå©ãã£ãŠããŸãã
ä»åŸããŽãŒãã¡ãŒåãæã§ã€ã€2024幎ãè¿ããŠãããããšæããŸããããã幎ãïŒïŒæ©ãïŒ