mirror of
https://github.com/sigmasternchen/wikitil
synced 2025-03-14 23:58:59 +00:00
initial commit
This commit is contained in:
commit
a22502f299
11 changed files with 415 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
config.json
|
||||
access.json
|
||||
|
||||
.idea
|
64
cmd/main.go
Normal file
64
cmd/main.go
Normal 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
4
config.json.templ
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"consumer_key": "",
|
||||
"consumer_secret": ""
|
||||
}
|
8
go.mod
Normal file
8
go.mod
Normal 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
24
go.sum
Normal 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
55
internal/config/config.go
Normal 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
17
internal/twitter/init.go
Normal 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
47
internal/twitter/login.go
Normal 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
|
||||
}
|
6
internal/twitter/tweet.go
Normal file
6
internal/twitter/tweet.go
Normal 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
133
internal/wikipedia/api.go
Normal 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
|
||||
}
|
53
internal/wikipedia/wikipedia.go
Normal file
53
internal/wikipedia/wikipedia.go
Normal 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()
|
||||
}
|
Loading…
Reference in a new issue