Created
March 19, 2023 07:38
-
-
Save a2ikm/ffc6058660db6226193058e0b92b4c9d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bufio" | |
"bytes" | |
"context" | |
"encoding/json" | |
"fmt" | |
"io" | |
"log" | |
"net/http" | |
"net/url" | |
"os" | |
"os/exec" | |
"strings" | |
"time" | |
) | |
const state = "this-is-state" | |
const ( | |
authorizationSuccess = iota | |
authorizationFailure | |
) | |
type authorizationResult struct { | |
status int | |
token string | |
} | |
func newAuthorizationResultSuccess(token string) authorizationResult { | |
return authorizationResult{ | |
status: authorizationSuccess, | |
token: token, | |
} | |
} | |
func newAuthorizationResultFailure() authorizationResult { | |
return authorizationResult{ | |
status: authorizationFailure, | |
token: "", | |
} | |
} | |
type tokenResponse struct { | |
AccessToken string `json:"access_token"` | |
ExpiresIn int `json:"expires_in"` | |
TokenType string `json:"token_type"` | |
Scope string `json:"scope"` | |
RefreshToken string `json:"refresh_token"` | |
} | |
var consumerKey string | |
var consumerSecret string | |
var accessToken string | |
var blogIdentifier string | |
var beforeTimestamp int64 | |
var authorized chan authorizationResult | |
func main() { | |
consumerKey = readConsumerKey() | |
consumerSecret = readConsumerSecret() | |
authorized = make(chan authorizationResult) | |
server := bootServer(authorized) | |
requestAuthorization() | |
result := <-authorized | |
server.Shutdown(context.Background()) | |
switch result.status { | |
case authorizationSuccess: | |
log.Println("authorized") | |
accessToken = result.token | |
deleteOldPosts() | |
case authorizationFailure: | |
log.Fatalln("authorization failed") | |
} | |
} | |
func deleteOldPosts() { | |
blogIdentifier = readBlogIdentifier() | |
beforeTimestamp = readBeforeTimestamp() | |
for { | |
ids := getOldPostIds() | |
if len(ids) == 0 { | |
break | |
} | |
time.Sleep(1 * time.Second) | |
for _, id := range ids { | |
deletePost(id) | |
time.Sleep(1 * time.Second) | |
} | |
} | |
} | |
type getPostsResponse struct { | |
Response struct { | |
Posts []struct { | |
Id int64 `json:"id"` | |
} `json:"posts"` | |
TotalPosts int `json:"total_posts"` | |
} `json:"response"` | |
} | |
func getOldPostIds() []int64 { | |
u := fmt.Sprintf("https://api.tumblr.com/v2/blog/%s/posts?before=%d", blogIdentifier, beforeTimestamp) | |
req, err := http.NewRequest(http.MethodGet, u, nil) | |
if err != nil { | |
log.Fatalf("failed to create request to get ids: %v", err) | |
} | |
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken)) | |
resp, err := http.DefaultClient.Do(req) | |
if err != nil { | |
log.Fatalf("failed to send request to get ids: %v", err) | |
} | |
defer resp.Body.Close() | |
if resp.StatusCode != http.StatusOK { | |
msg, err := io.ReadAll(resp.Body) | |
if err != nil { | |
log.Fatalf("failed to read response of get ids: %v", err) | |
} | |
log.Fatalf("failed to get ids: %s", msg) | |
} | |
var res getPostsResponse | |
err = json.NewDecoder(resp.Body).Decode(&res) | |
if err != nil { | |
log.Fatalf("failed to decode get posts response: %v", err) | |
} | |
log.Printf("total posts is %d\n", res.Response.TotalPosts) | |
ids := make([]int64, 0, 10) | |
for _, post := range res.Response.Posts { | |
ids = append(ids, post.Id) | |
} | |
return ids | |
} | |
func deletePost(id int64) { | |
u := fmt.Sprintf("https://api.tumblr.com/v2/blog/%s/post/delete", blogIdentifier) | |
body := bytes.NewBufferString(fmt.Sprintf("id=%d", id)) | |
req, err := http.NewRequest(http.MethodPost, u, body) | |
if err != nil { | |
log.Fatalf("failed to create request to delete post: %v", err) | |
} | |
req.Header.Add("Content-Type", "application/x-www-form-urlencoded") | |
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken)) | |
resp, err := http.DefaultClient.Do(req) | |
if err != nil { | |
log.Fatalf("failed to send request to delete post: %v", err) | |
} | |
defer resp.Body.Close() | |
if resp.StatusCode != http.StatusOK { | |
msg, err := io.ReadAll(resp.Body) | |
if err != nil { | |
log.Fatalf("failed to read response of delete post: %v", err) | |
} | |
log.Fatalf("failed to delete post: %s", msg) | |
} | |
} | |
func readUserInput(label string) string { | |
reader := bufio.NewReader(os.Stdin) | |
fmt.Printf("Please input %s: ", label) | |
input, _ := reader.ReadString('\n') | |
return strings.TrimSpace(input) | |
} | |
func readConsumerKey() string { | |
return readUserInput("your consumer key") | |
} | |
func readConsumerSecret() string { | |
return readUserInput("your consumer secret") | |
} | |
func readBlogIdentifier() string { | |
return readUserInput("your blog identifier like your-blog-name.tumblr.com") | |
} | |
func readBeforeTimestamp() int64 { | |
input := readUserInput("before timestamp in yyyy-mm-dd form") | |
t, err := time.Parse("2006-01-02", input) | |
if err != nil { | |
log.Fatalf("failed to parse before timestamp: %s err: %v", input, err) | |
} | |
return t.Unix() | |
} | |
func requestAuthorization() { | |
params := url.Values{} | |
params.Set("client_id", consumerKey) | |
params.Set("response_type", "code") | |
params.Set("scope", "basic write") | |
params.Set("state", state) | |
aurl := "https://www.tumblr.com/oauth2/authorize?" + params.Encode() | |
exec.Command("open", aurl).Run() | |
} | |
func bootServer(authorized chan authorizationResult) *http.Server { | |
mux := http.NewServeMux() | |
mux.HandleFunc("/ping", pingHandler) | |
mux.HandleFunc("/callback", callbackHandler) | |
s := &http.Server{ | |
Addr: "0.0.0.0:8000", | |
Handler: mux, | |
} | |
go func() { | |
log.Println("booting server...") | |
err := s.ListenAndServe() | |
if err != nil && err != http.ErrServerClosed { | |
log.Fatalf("server error: %v", err) | |
} | |
}() | |
client := &http.Client{ | |
Timeout: 1 * time.Second, | |
} | |
for { | |
_, err := client.Get("http://localhost:8000/ping") | |
if err != nil { | |
log.Println("wait 1 second...") | |
time.Sleep(1 * time.Second) | |
continue | |
} | |
break | |
} | |
log.Println("booted") | |
return s | |
} | |
func pingHandler(resp http.ResponseWriter, req *http.Request) { | |
resp.WriteHeader(http.StatusOK) | |
} | |
func callbackHandler(resp http.ResponseWriter, req *http.Request) { | |
defer req.Body.Close() | |
u, err := url.ParseRequestURI(req.RequestURI) | |
if err != nil { | |
resp.WriteHeader(http.StatusInternalServerError) | |
fmt.Fprintf(resp, "failed to parse request url: %v", err) | |
authorized <- newAuthorizationResultFailure() | |
return | |
} | |
authres, err := url.ParseQuery(u.RawQuery) | |
if err != nil { | |
resp.WriteHeader(http.StatusInternalServerError) | |
fmt.Fprintf(resp, "failed to parse query: %v", err) | |
authorized <- newAuthorizationResultFailure() | |
return | |
} | |
if authres.Has("error") { | |
resp.WriteHeader(http.StatusUnauthorized) | |
fmt.Fprintf(resp, "authorization request error: %v", authres) | |
authorized <- newAuthorizationResultFailure() | |
return | |
} | |
params := url.Values{} | |
params.Set("grant_type", "authorization_code") | |
params.Set("code", authres.Get("code")) | |
params.Set("client_id", consumerKey) | |
params.Set("client_secret", consumerSecret) | |
res, err := http.PostForm("https://api.tumblr.com/v2/oauth2/token", params) | |
if err != nil { | |
resp.WriteHeader(http.StatusUnauthorized) | |
fmt.Fprintf(resp, "token request error: %v", err) | |
authorized <- newAuthorizationResultFailure() | |
return | |
} | |
tokres := tokenResponse{} | |
err = json.NewDecoder(res.Body).Decode(&tokres) | |
if err != nil { | |
resp.WriteHeader(http.StatusInternalServerError) | |
fmt.Fprintf(resp, "token response parse error: %v", err) | |
authorized <- newAuthorizationResultFailure() | |
return | |
} | |
res.Body.Close() | |
resp.WriteHeader(http.StatusOK) | |
fmt.Fprintf(resp, "Authorized :)\n") | |
authorized <- newAuthorizationResultSuccess(tokres.AccessToken) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment