package main import ( "bufio" "encoding/json" "errors" "fmt" "io" "io/fs" "net/http" "os" "path/filepath" "strconv" "strings" "time" ) var ( configDir string config *Config data *Data ) func LoadConfigAndData() error { iHateBugs, err := os.UserConfigDir() if err != nil { return fmt.Errorf("Cannot determine config directory: %s", err) } configDir = filepath.Join(iHateBugs, "omordl") err = os.MkdirAll(configDir, 0o700) if err != nil { return fmt.Errorf("Cannot create config directory: %s", err) } configFile, err := os.OpenFile(filepath.Join(configDir, "config.json"), os.O_RDONLY, 0o600) if err == nil { contents, err := io.ReadAll(configFile) if err != nil { return fmt.Errorf("Failed to read config file: %s", err) } err = json.Unmarshal(contents, &config) if err != nil { return fmt.Errorf("Failed to parse config file: %s", err) } } else if errors.Is(err, fs.ErrNotExist) { configFile, err = os.OpenFile(filepath.Join(configDir, "config.json"), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600) if err != nil { return fmt.Errorf("Failed to open config file for writing default template: %s", err) } _, err = configFile.WriteString(EXAMPLE_CONFIG) if err != nil { return fmt.Errorf("Failed to write to config file: %s", err) } return fmt.Errorf("Please fill out the default template at %s", filepath.Join(configDir, "config.json")) } else { return fmt.Errorf("Failed to open config file: %s", err) } if config.ClientId == CONFIG_PLACEHOLDER || config.ClientSecret == CONFIG_PLACEHOLDER { return fmt.Errorf("Please fill out the default template at %s", filepath.Join(configDir, "config.json")) } dataFile, err := os.OpenFile(filepath.Join(configDir, "data.json"), os.O_RDONLY, 0o600) if err == nil { contents, err := io.ReadAll(dataFile) if err != nil { return fmt.Errorf("Failed to read data file: %s", err) } err = json.Unmarshal(contents, &data) if err != nil { return fmt.Errorf("Failed to parse data file: %s", err) } } else if !errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("Failed to open data file: %s", err) } return nil } func WriteData() error { contents, err := json.Marshal(data) if err != nil { return err } file, err := os.OpenFile(filepath.Join(configDir, "data.json"), os.O_WRONLY|os.O_CREATE, 0o600) if err != nil { return err } _, err = file.Write(contents) return err } func GetToken(client *http.Client) (string, error) { if data.AuthorizationHeader != "" && data.AuthorizationExpiry != 0 && data.AuthorizationExpiry > time.Now().Unix() { return data.AuthorizationHeader, nil } request, err := http.NewRequest("POST", "https://www.reddit.com/api/v1/access_token?grant_type=client_credentials", nil) if err != nil { return "", fmt.Errorf("Failed to create request: %s", err) } request.Header.Add("User-Agent", USER_AGENT) request.SetBasicAuth(config.ClientId, config.ClientSecret) response, err := client.Do(request) if err != nil { return "", fmt.Errorf("Failed to get response: %s", err) } contents, err := io.ReadAll(response.Body) response.Body.Close() if err != nil { return "", fmt.Errorf("Failed to read response body: %s", err) } if response.StatusCode != 200 { return "", fmt.Errorf("Response returned status code %d, body: %s", response.StatusCode, contents) } var tokenResponse TokenResponse err = json.Unmarshal(contents, &tokenResponse) if err != nil { return "", fmt.Errorf("Failed to parse response: %s", err) } s := []string{tokenResponse.TokenType, tokenResponse.AccessToken} data.AuthorizationHeader = strings.Join(s, " ") data.AuthorizationExpiry = time.Now().Unix() + tokenResponse.ExpiresIn - 5 err = WriteData() if err != nil { fmt.Fprintf(os.Stderr, "Warning: Failed to save token to disk: %s\n", err) } return data.AuthorizationHeader, nil } func GetSubmission(client *http.Client, token, submissionId string) (*Submission, error) { request, err := http.NewRequest("GET", "https://oauth.reddit.com/comments/"+submissionId+"/?raw_json=1&limit=1", nil) if err != nil { return nil, fmt.Errorf("Failed to create request: %s", err) } request.Header.Add("User-Agent", USER_AGENT) request.Header.Add("Authorization", data.AuthorizationHeader) response, err := client.Do(request) if err != nil { return nil, fmt.Errorf("Failed to get response: %s", err) } contents, err := io.ReadAll(response.Body) response.Body.Close() if err != nil { return nil, fmt.Errorf("Failed to read response body: %s", err) } if response.StatusCode != 200 { return nil, fmt.Errorf("Response returned status code %d, body: %s", response.StatusCode, contents) } var submission []SubmissionResponseItem err = json.Unmarshal(contents, &submission) if err != nil { return nil, fmt.Errorf("Failed to parse response: %s", err) } return submission[0].Data.Children[0].Data, nil } func InteractivelyAskIndex(stdin *bufio.Reader, items []string) (int, error) { if len(items) == 1 { return 0, nil } fmt.Printf("Select an item (from 1 to %d): ", len(items)) str, err := stdin.ReadString('\n') if err != nil { return 0, fmt.Errorf("Failed to read stdin: %s", err) } i, err := strconv.Atoi(strings.TrimSpace(str)) if err != nil { return 0, fmt.Errorf("Failed to parse stdin: %s", err) } i-- if i < 0 { return i, errors.New("Index is under or equal to 0") } if i >= len(items) { return i, errors.New("Index is bigger than items available") } return i, nil }