commit
a8c21fabf1
@ -0,0 +1,170 @@ |
||||
### Git ### |
||||
# Created by git for backups. To disable backups in Git: |
||||
# $ git config --global mergetool.keepBackup false |
||||
*.orig |
||||
|
||||
# Created by git when using merge tools for conflicts |
||||
*.BACKUP.* |
||||
*.BASE.* |
||||
*.LOCAL.* |
||||
*.REMOTE.* |
||||
*_BACKUP_*.txt |
||||
*_BASE_*.txt |
||||
*_LOCAL_*.txt |
||||
*_REMOTE_*.txt |
||||
|
||||
### Go ### |
||||
# Binaries for programs and plugins |
||||
*.exe |
||||
*.exe~ |
||||
*.dll |
||||
*.so |
||||
*.dylib |
||||
|
||||
# Test binary, built with `go test -c` |
||||
*.test |
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE |
||||
*.out |
||||
|
||||
# Dependency directories (remove the comment below to include it) |
||||
# vendor/ |
||||
|
||||
### Go Patch ### |
||||
/vendor/ |
||||
/Godeps/ |
||||
|
||||
### Node ### |
||||
# Logs |
||||
logs |
||||
*.log |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
lerna-debug.log* |
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html) |
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json |
||||
|
||||
# Runtime data |
||||
pids |
||||
*.pid |
||||
*.seed |
||||
*.pid.lock |
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover |
||||
lib-cov |
||||
|
||||
# Coverage directory used by tools like istanbul |
||||
coverage |
||||
*.lcov |
||||
|
||||
# nyc test coverage |
||||
.nyc_output |
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) |
||||
.grunt |
||||
|
||||
# Bower dependency directory (https://bower.io/) |
||||
bower_components |
||||
|
||||
# node-waf configuration |
||||
.lock-wscript |
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html) |
||||
build/Release |
||||
|
||||
# Dependency directories |
||||
node_modules/ |
||||
jspm_packages/ |
||||
|
||||
# TypeScript v1 declaration files |
||||
typings/ |
||||
|
||||
# TypeScript cache |
||||
*.tsbuildinfo |
||||
|
||||
# Optional npm cache directory |
||||
.npm |
||||
|
||||
# Optional eslint cache |
||||
.eslintcache |
||||
|
||||
# Optional REPL history |
||||
.node_repl_history |
||||
|
||||
# Output of 'npm pack' |
||||
*.tgz |
||||
|
||||
# Yarn Integrity file |
||||
.yarn-integrity |
||||
|
||||
# dotenv environment variables file |
||||
.env |
||||
.env.test |
||||
|
||||
# parcel-bundler cache (https://parceljs.org/) |
||||
.cache |
||||
|
||||
# next.js build output |
||||
.next |
||||
|
||||
# nuxt.js build output |
||||
.nuxt |
||||
|
||||
# rollup.js default build output |
||||
dist/ |
||||
|
||||
# Storybook build outputs |
||||
.out |
||||
.storybook-out |
||||
|
||||
# vuepress build output |
||||
.vuepress/dist |
||||
|
||||
# Serverless directories |
||||
.serverless/ |
||||
|
||||
# FuseBox cache |
||||
.fusebox/ |
||||
|
||||
# DynamoDB Local files |
||||
.dynamodb/ |
||||
|
||||
# Temporary folders |
||||
tmp/ |
||||
temp/ |
||||
|
||||
# VSCode |
||||
.vscode/* |
||||
!.vscode/settings.json |
||||
!.vscode/tasks.json |
||||
!.vscode/launch.json |
||||
!.vscode/extensions.json |
||||
*.code-workspace |
||||
.history/ |
||||
|
||||
# MacOS |
||||
# General |
||||
.DS_Store |
||||
.AppleDouble |
||||
.LSOverride |
||||
|
||||
# Thumbnails |
||||
._* |
||||
|
||||
# Files that might appear in the root of a volume |
||||
.DocumentRevisions-V100 |
||||
.fseventsd |
||||
.Spotlight-V100 |
||||
.TemporaryItems |
||||
.Trashes |
||||
.VolumeIcon.icns |
||||
.com.apple.timemachine.donotpresent |
||||
|
||||
# Directories potentially created on remote AFP share |
||||
.AppleDB |
||||
.AppleDesktop |
||||
Network Trash Folder |
||||
Temporary Items |
||||
.apdisk |
@ -0,0 +1,22 @@ |
||||
FROM golang:alpine AS builder |
||||
RUN apk update && apk add --no-cache git && adduser -D -g '' gopher && apk add -U --no-cache ca-certificates |
||||
WORKDIR /app/ |
||||
ADD go.mod go.sum ./ |
||||
RUN go mod download |
||||
ADD . . |
||||
RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-w -s" -o tweetsvg main.go |
||||
|
||||
FROM scratch |
||||
|
||||
ENV ACCESS_TOKEN= |
||||
ENV ACCESS_TOKEN_SECRET= |
||||
ENV CONSUMER_KEY= |
||||
ENV CONSUMER_SECRET= |
||||
|
||||
WORKDIR /app/ |
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ |
||||
COPY --from=builder /etc/passwd /etc/passwd |
||||
COPY --from=builder /app/tweetsvg /app/tweetsvg |
||||
USER gopher |
||||
|
||||
ENTRYPOINT ["/app/tweetsvg"] |
@ -0,0 +1,39 @@ |
||||
# TweetSVG |
||||
|
||||
Generate an SVG for a given Tweet ID |
||||
|
||||
## Features |
||||
|
||||
## Install |
||||
|
||||
```sh |
||||
|
||||
``` |
||||
|
||||
## Building from source |
||||
|
||||
With Docker: |
||||
|
||||
```sh |
||||
make docker-build |
||||
``` |
||||
|
||||
Standalone: |
||||
|
||||
```sh |
||||
make build |
||||
``` |
||||
|
||||
## Resources |
||||
|
||||
## Contributing |
||||
|
||||
If you find a bug or have an idea for a new feature please [raise an issue](issues/new) to discuss it. |
||||
|
||||
Pull requests are welcomed but please try and follow similar code style as the rest of the project and ensure all tests and code checkers are passing. |
||||
|
||||
Thank you 💛 |
||||
|
||||
## License |
||||
|
||||
See [LICENSE](LICENSE) |
@ -0,0 +1,13 @@ |
||||
module tweetsvg |
||||
|
||||
go 1.15 |
||||
|
||||
require ( |
||||
github.com/ChimeraCoder/anaconda v2.0.0+incompatible |
||||
github.com/ChimeraCoder/tokenbucket v0.0.0-20131201223612-c5a927568de7 // indirect |
||||
github.com/azr/backoff v0.0.0-20160115115103-53511d3c7330 // indirect |
||||
github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc // indirect |
||||
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad // indirect |
||||
github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17 // indirect |
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect |
||||
) |
@ -0,0 +1,18 @@ |
||||
github.com/ChimeraCoder/anaconda v2.0.0+incompatible h1:F0eD7CHXieZ+VLboCD5UAqCeAzJZxcr90zSCcuJopJs= |
||||
github.com/ChimeraCoder/anaconda v2.0.0+incompatible/go.mod h1:TCt3MijIq3Qqo9SBtuW/rrM4x7rDfWqYWHj8T7hLcLg= |
||||
github.com/ChimeraCoder/tokenbucket v0.0.0-20131201223612-c5a927568de7 h1:r+EmXjfPosKO4wfiMLe1XQictsIlhErTufbWUsjOTZs= |
||||
github.com/ChimeraCoder/tokenbucket v0.0.0-20131201223612-c5a927568de7/go.mod h1:b2EuEMLSG9q3bZ95ql1+8oVqzzrTNSiOQqSXWFBzxeI= |
||||
github.com/azr/backoff v0.0.0-20160115115103-53511d3c7330 h1:ekDALXAVvY/Ub1UtNta3inKQwZ/jMB/zpOtD8rAYh78= |
||||
github.com/azr/backoff v0.0.0-20160115115103-53511d3c7330/go.mod h1:nH+k0SvAt3HeiYyOlJpLLv1HG1p7KWP7qU9QPp2/pCo= |
||||
github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc h1:tP7tkU+vIsEOKiK+l/NSLN4uUtkyuxc6hgYpQeCWAeI= |
||||
github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc/go.mod h1:ORH5Qp2bskd9NzSfKqAF7tKfONsEkCarTE5ESr/RVBw= |
||||
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad h1:Qk76DOWdOp+GlyDKBAG3Klr9cn7N+LcYc82AZ2S7+cA= |
||||
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad/go.mod h1:mPKfmRa823oBIgl2r20LeMSpTAteW5j7FLkc0vjmzyQ= |
||||
github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17 h1:GOfMz6cRgTJ9jWV0qAezv642OhPnKEG7gtUjJSdStHE= |
||||
github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17/go.mod h1:HfkOCN6fkKKaPSAeNq/er3xObxTW4VLeY6UUK895gLQ= |
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= |
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= |
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= |
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
@ -0,0 +1,92 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/base64" |
||||
"fmt" |
||||
"html/template" |
||||
"log" |
||||
"net/http" |
||||
"os" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/ChimeraCoder/anaconda" |
||||
) |
||||
|
||||
var ( |
||||
api *anaconda.TwitterApi |
||||
|
||||
port = os.Getenv("PORT") |
||||
|
||||
tweetDateLayout = "Mon Jan 2 15:04:05 -0700 2006" |
||||
|
||||
accessToken = os.Getenv("ACCESS_TOKEN") |
||||
accessTokenSecret = os.Getenv("ACCESS_TOKEN_SECRET") |
||||
consumerKey = os.Getenv("CONSUMER_KEY") |
||||
consumerSecret = os.Getenv("CONSUMER_SECRET") |
||||
) |
||||
|
||||
func main() { |
||||
if accessToken == "" || accessTokenSecret == "" || consumerKey == "" || consumerSecret == "" { |
||||
panic("Missing Twitter credentials") |
||||
} |
||||
|
||||
api = anaconda.NewTwitterApiWithCredentials(accessToken, accessTokenSecret, consumerKey, consumerSecret) |
||||
|
||||
if port == "" { |
||||
port = "8080" |
||||
} |
||||
|
||||
http.HandleFunc("/", getTweet) |
||||
fmt.Println("Server started at port " + port) |
||||
log.Fatal(http.ListenAndServe(":"+port, nil)) |
||||
} |
||||
|
||||
func getTweet(w http.ResponseWriter, r *http.Request) { |
||||
id := strings.ReplaceAll(r.URL.Path, "/", "") |
||||
i, err := strconv.ParseInt(id, 10, 64) |
||||
if err != nil { |
||||
w.WriteHeader(400) |
||||
return |
||||
} |
||||
tweet, err := api.GetTweet(i, nil) |
||||
if err != nil { |
||||
w.WriteHeader(404) |
||||
return |
||||
} |
||||
|
||||
templateFuncs := template.FuncMap{ |
||||
"base64": func(url string) string { |
||||
res, err := http.Get(url) |
||||
if err != nil { |
||||
return "" |
||||
} |
||||
buf := new(bytes.Buffer) |
||||
buf.ReadFrom(res.Body) |
||||
return base64.StdEncoding.EncodeToString(buf.Bytes()) |
||||
}, |
||||
"isoDate": func(date string) string { |
||||
t, _ := time.Parse(tweetDateLayout, date) |
||||
return t.Format(time.RFC3339) |
||||
}, |
||||
"humanDate": func(date string) string { |
||||
t, _ := time.Parse(tweetDateLayout, date) |
||||
return t.Format("3:04 PM · Jan 2, 2006") |
||||
}, |
||||
} |
||||
|
||||
t := template.Must( |
||||
template.New("tweet.svg.tmpl"). |
||||
Funcs(templateFuncs). |
||||
ParseFiles("tweet.svg.tmpl")) |
||||
|
||||
w.Header().Set("Content-type", "image/svg+xml") |
||||
err = t.Execute(w, tweet) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
w.WriteHeader(500) |
||||
return |
||||
} |
||||
} |
After Width: | Height: | Size: 2.3 KiB |
Loading…
Reference in new issue