175 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			175 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Go
		
	
	
	
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
 | 
						|
}
 |