initial commit

This commit is contained in:
overflowerror 2021-11-22 17:51:03 +01:00
commit a22502f299
11 changed files with 415 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
config.json
access.json
.idea

64
cmd/main.go Normal file
View file

@ -0,0 +1,64 @@
package main
import (
"log"
. "randomarticle/internal/config"
"randomarticle/internal/twitter"
"randomarticle/internal/wikipedia"
"time"
)
const configPath = "config.json"
const accessConfigPath = "access.json"
func getAccessConfig(config Config) AccessConfig {
accessConfig, err := ReadAccessConfig(accessConfigPath)
if err == nil {
return accessConfig
}
log.Println("no access config found")
accessConfig, err = twitter.Login(config)
if err != nil {
log.Fatalln(err)
}
err = WriteAccessConfig(accessConfigPath, accessConfig)
if err != nil {
log.Fatalln(err)
}
accessConfig, err = ReadAccessConfig(accessConfigPath)
if err != nil {
log.Fatalln(err)
}
return accessConfig
}
func main() {
config, err := ReadConfig(configPath)
if err != nil {
log.Fatalln(err)
}
access := getAccessConfig(config)
twitter.Init(config, access)
for range time.Tick(time.Minute * 1) {
log.Println("tick")
page, err := wikipedia.Get()
if err != nil {
log.Println(err)
continue
}
err = twitter.Tweet(wikipedia.Format(page))
if err != nil {
log.Println(err)
continue
}
}
}

4
config.json.templ Normal file
View file

@ -0,0 +1,4 @@
{
"consumer_key": "",
"consumer_secret": ""
}

8
go.mod Normal file
View file

@ -0,0 +1,8 @@
module randomarticle
go 1.16
require (
github.com/dghubble/go-twitter v0.0.0-20211115160449-93a8679adecb
github.com/dghubble/oauth1 v0.7.0
)

24
go.sum Normal file
View file

@ -0,0 +1,24 @@
github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dghubble/go-twitter v0.0.0-20211115160449-93a8679adecb h1:7ENzkH+O3juL+yj2undESLTaAeRllHwCs/b8z6aWSfc=
github.com/dghubble/go-twitter v0.0.0-20211115160449-93a8679adecb/go.mod h1:qhZBgV9e4WyB1JNjHpcXVkUe3knWUwYuAPB1hITdm50=
github.com/dghubble/oauth1 v0.7.0 h1:AlpZdbRiJM4XGHIlQ8BuJ/wlpGwFEJNnB4Mc+78tA/w=
github.com/dghubble/oauth1 v0.7.0/go.mod h1:8pFdfPkv/jr8mkChVbNVuJ0suiHe278BtWI4Tk1ujxk=
github.com/dghubble/sling v1.4.0 h1:/n8MRosVTthvMbwlNZgLx579OGVjUOy3GNEv5BIqAWY=
github.com/dghubble/sling v1.4.0/go.mod h1:0r40aNsU9EdDUVBNhfCstAtFgutjgJGYbO1oNzkMoM8=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

55
internal/config/config.go Normal file
View file

@ -0,0 +1,55 @@
package config
import (
"encoding/json"
"io/ioutil"
)
type Config struct {
ConsumerKey string `json:"consumer_key"`
ConsumerSecret string `json:"consumer_secret"`
}
type AccessConfig struct {
AccessToken string `json:"access_token"`
AccessSecret string `json:"access_secret"`
}
func ReadConfig(path string) (Config, error) {
content, err := ioutil.ReadFile(path)
if err != nil {
return Config{}, err
}
var config Config
err = json.Unmarshal(content, &config)
if err != nil {
return Config{}, err
}
return config, nil
}
func ReadAccessConfig(path string) (AccessConfig, error) {
content, err := ioutil.ReadFile(path)
if err != nil {
return AccessConfig{}, err
}
var config AccessConfig
err = json.Unmarshal(content, &config)
if err != nil {
return AccessConfig{}, err
}
return config, nil
}
func WriteAccessConfig(path string, config AccessConfig) error {
content, err := json.Marshal(config)
if err != nil {
return err
}
return ioutil.WriteFile(path, content, 0660)
}

17
internal/twitter/init.go Normal file
View file

@ -0,0 +1,17 @@
package twitter
import (
"github.com/dghubble/go-twitter/twitter"
appConfig "randomarticle/internal/config"
)
import "github.com/dghubble/oauth1"
var client *twitter.Client
func Init(appConfig appConfig.Config, access appConfig.AccessConfig) {
config := oauth1.NewConfig(appConfig.ConsumerKey, appConfig.ConsumerSecret)
token := oauth1.NewToken(access.AccessToken, access.AccessSecret)
httpClient := config.Client(oauth1.NoContext, token)
client = twitter.NewClient(httpClient)
}

47
internal/twitter/login.go Normal file
View file

@ -0,0 +1,47 @@
package twitter
import (
"fmt"
"github.com/dghubble/oauth1"
twauth "github.com/dghubble/oauth1/twitter"
. "randomarticle/internal/config"
)
const outOfBand = "oob"
func Login(config Config) (AccessConfig, error) {
oauthConfig := oauth1.Config{
ConsumerKey: config.ConsumerKey,
ConsumerSecret: config.ConsumerSecret,
CallbackURL: outOfBand,
Endpoint: twauth.AuthorizeEndpoint,
}
requestToken, _, err := oauthConfig.RequestToken()
if err != nil {
return AccessConfig{}, fmt.Errorf("could not get request token: %w", err)
}
authorizationURL, err := oauthConfig.AuthorizationURL(requestToken)
if err != nil {
return AccessConfig{}, fmt.Errorf("could not create authorization url: %w", err)
}
fmt.Printf("Open this URL in your browser:\n%s\n", authorizationURL.String())
fmt.Printf("Paste your PIN here: ")
var verifier string
_, err = fmt.Scanf("%s", &verifier)
if err != nil {
return AccessConfig{}, err
}
accessToken, accessSecret, err := oauthConfig.AccessToken(requestToken, "secret does not matter", verifier)
if err != nil {
return AccessConfig{}, err
}
return AccessConfig{
AccessToken: accessToken,
AccessSecret: accessSecret,
}, nil
}

View file

@ -0,0 +1,6 @@
package twitter
func Tweet(content string) error {
_, _, err := client.Statuses.Update(content, nil)
return err
}

133
internal/wikipedia/api.go Normal file
View file

@ -0,0 +1,133 @@
package wikipedia
import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"strconv"
"strings"
)
type PageInfo struct {
Title string
Description string
URL string
}
type infoResponse struct {
Query struct {
Pages map[string]struct{
Title string `json:"title"`
FullURL string `json:"fullurl"`
Terms struct {
Description []string `json:"description"`
} `json:"terms"`
} `json:"pages"`
} `json:"query"`
}
type randomReponse struct {
Query struct {
Random []struct {
ID int64 `json:"id"`
} `json:"random"`
} `json:"query"`
}
const baseURL = "https://de.wikipedia.org"
var noDescription = errors.New("no description found")
var noPageInfo = errors.New("no page info found")
func request(params map[string]string) ([]byte, error) {
builder := strings.Builder{}
builder.WriteString(baseURL)
builder.WriteString("/w/api.php?")
for key, value := range params {
builder.WriteString(key)
builder.WriteString("=")
builder.WriteString(value)
builder.WriteString("&")
}
builder.WriteString("format=json")
response, err := http.Get(builder.String())
if err != nil {
return nil, err
}
defer response.Body.Close()
return ioutil.ReadAll(response.Body)
}
func responseToPageInfo(response infoResponse) (PageInfo, error) {
for _, page := range response.Query.Pages {
if len(page.Terms.Description) < 1 {
return PageInfo{}, noDescription
}
return PageInfo{
Title: page.Title,
Description: page.Terms.Description[0],
URL: page.FullURL,
}, nil
}
return PageInfo{}, noPageInfo
}
func queryInfo(id int64) (PageInfo, error) {
params := map[string]string {
"action": "query",
"pageids": strconv.FormatInt(id, 10),
"prop": "info|pageterms",
"inprop": "url",
}
content, err := request(params)
if err != nil {
return PageInfo{}, err
}
var response infoResponse
err = json.Unmarshal(content, &response)
if err != nil {
return PageInfo{}, err
}
return responseToPageInfo(response)
}
func queryRandom() (int64, error) {
params := map[string]string {
"action": "query",
"list": "random",
"rnnamespace": "0",
}
content, err := request(params)
if err != nil {
return 0, err
}
var response randomReponse
err = json.Unmarshal(content, &response)
if err != nil {
return 0, err
}
if len(response.Query.Random) < 1 {
return 0, errors.New("could not get random result")
}
id := response.Query.Random[0].ID
if id == 0 {
return 0, errors.New("no page id in result")
}
return id, nil
}

View file

@ -0,0 +1,53 @@
package wikipedia
import (
"errors"
"fmt"
"strings"
)
const retries = 5
var illegalDescriptionParts = []string{
"Begriffsklärungsseite",
}
func Get() (PageInfo, error){
retryLoop:
for i := 0; i < retries; i++ {
id, err := queryRandom()
if err != nil {
return PageInfo{}, err
}
info, err := queryInfo(id)
if err != nil {
fmt.Println(err)
fmt.Println("Retrying...")
fmt.Println()
continue
}
for _, part := range illegalDescriptionParts {
if strings.Contains(info.Description, part) {
fmt.Println("illegal description: " + info.Description)
i-- // illegal descriptions don't count towards retry limit
continue retryLoop
}
}
return info, err
}
return PageInfo{}, errors.New("retries exceeded")
}
func Format(info PageInfo) string {
var builder strings.Builder
builder.WriteString(info.Title)
builder.WriteString(":\n")
builder.WriteString(info.Description)
builder.WriteString("\n\n")
builder.WriteString(info.URL)
return builder.String()
}