Compare commits
14 Commits
4572550f77
...
master
Author | SHA1 | Date | |
---|---|---|---|
956a979204
|
|||
cfd9c7292c
|
|||
7f14289760
|
|||
091f6a455e | |||
d061860fa2 | |||
afc1cee5d7 | |||
749758fca4 | |||
607f063abc | |||
8be9ddc40a | |||
f188873c40 | |||
651f3683d2 | |||
7b4c6cf3fa | |||
80cabdd365 | |||
79d7d64e98 |
@@ -4,7 +4,7 @@ WORKDIR /app/
|
|||||||
ADD go.mod go.sum ./
|
ADD go.mod go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
ADD . .
|
ADD . .
|
||||||
RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-w -s" -o tweetsvg main.go
|
RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-w -s" -o tweetsvg .
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
4
Makefile
4
Makefile
@@ -39,7 +39,7 @@ docker-publish:
|
|||||||
|
|
||||||
.PHONY: run # Run the application
|
.PHONY: run # Run the application
|
||||||
run:
|
run:
|
||||||
@go run main.go
|
@go run .
|
||||||
|
|
||||||
.PHONY: ci # Perform CI specific tasks to perform on a pull request
|
.PHONY: ci # Perform CI specific tasks to perform on a pull request
|
||||||
ci:
|
ci:
|
||||||
@@ -47,7 +47,7 @@ ci:
|
|||||||
|
|
||||||
.PHONY: release # Release the latest version of the application
|
.PHONY: release # Release the latest version of the application
|
||||||
release:
|
release:
|
||||||
@kubectl --namespace tweetsvg set image deployment tweetsvg web=docker.cluster.fun/averagemarcus/tweetsvg:$(SHA)
|
@kubectl --namespace tweetsvg rollout restart deployment tweetsvg
|
||||||
|
|
||||||
.PHONY: help # Show this list of commands
|
.PHONY: help # Show this list of commands
|
||||||
help:
|
help:
|
||||||
|
@@ -1,3 +1,8 @@
|
|||||||
|
# ⚠️ DEPRECATED
|
||||||
|
# Twitter has killed off its free API so this no longer works
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Generate an SVG for a given Tweet ID
|
Generate an SVG for a given Tweet ID
|
||||||
|
111
chars.go
Normal file
111
chars.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import emoji "github.com/tmdvs/Go-Emoji-Utils"
|
||||||
|
|
||||||
|
var charWidths = map[string]float64{
|
||||||
|
" ": 6,
|
||||||
|
"0": 14.333328247070312,
|
||||||
|
"1": 10.583328247070312,
|
||||||
|
"2": 13.433334350585938,
|
||||||
|
"3": 14.01666259765625,
|
||||||
|
"4": 14.333328247070312,
|
||||||
|
"5": 13.866668701171875,
|
||||||
|
"6": 14.199996948242188,
|
||||||
|
"7": 12.966659545898438,
|
||||||
|
"8": 14.199996948242188,
|
||||||
|
"9": 14.199996948242188,
|
||||||
|
"A": 14.949996948242188,
|
||||||
|
"B": 14.366668701171875,
|
||||||
|
"C": 16.183334350585938,
|
||||||
|
"D": 16.050003051757812,
|
||||||
|
"E": 13.133331298828125,
|
||||||
|
"F": 12.566665649414062,
|
||||||
|
"G": 16.666671752929688,
|
||||||
|
"H": 16.51666259765625,
|
||||||
|
"I": 5.600006103515625,
|
||||||
|
"J": 11.816665649414062,
|
||||||
|
"K": 14.583328247070312,
|
||||||
|
"L": 12.5,
|
||||||
|
"M": 19.550003051757812,
|
||||||
|
"N": 16.51666259765625,
|
||||||
|
"O": 17.25,
|
||||||
|
"P": 13.75,
|
||||||
|
"Q": 17.25,
|
||||||
|
"R": 14.23333740234375,
|
||||||
|
"S": 14.083328247070312,
|
||||||
|
"T": 13.816665649414062,
|
||||||
|
"U": 16.433334350585938,
|
||||||
|
"V": 14.949996948242188,
|
||||||
|
"W": 21.683334350585938,
|
||||||
|
"X": 15.033340454101562,
|
||||||
|
"Y": 14.51666259765625,
|
||||||
|
"Z": 14.616668701171875,
|
||||||
|
"a": 11.98333740234375,
|
||||||
|
"b": 13.216659545898438,
|
||||||
|
"c": 11.98333740234375,
|
||||||
|
"d": 13.199996948242188,
|
||||||
|
"e": 12.25,
|
||||||
|
"f": 7.4499969482421875,
|
||||||
|
"g": 13.100006103515625,
|
||||||
|
"h": 12.850006103515625,
|
||||||
|
"i": 5.133331298828125,
|
||||||
|
"j": 5.133331298828125,
|
||||||
|
"k": 11.633331298828125,
|
||||||
|
"l": 5.133331298828125,
|
||||||
|
"m": 18.966659545898438,
|
||||||
|
"n": 12.583328247070312,
|
||||||
|
"o": 12.683334350585938,
|
||||||
|
"p": 13.100006103515625,
|
||||||
|
"q": 13.100006103515625,
|
||||||
|
"r": 7.5833282470703125,
|
||||||
|
"s": 11.166671752929688,
|
||||||
|
"t": 7.4166717529296875,
|
||||||
|
"u": 12.583328247070312,
|
||||||
|
"v": 11.583328247070312,
|
||||||
|
"w": 16.916671752929688,
|
||||||
|
"x": 11.25,
|
||||||
|
"y": 11.649993896484375,
|
||||||
|
"z": 11.26666259765625,
|
||||||
|
"!": 6.5666656494140625,
|
||||||
|
"@": 20.550003051757812,
|
||||||
|
"£": 14.333328247070312,
|
||||||
|
"#": 14.300003051757812,
|
||||||
|
"$": 14.333328247070312,
|
||||||
|
"%": 18.416671752929688,
|
||||||
|
"^": 9.683334350585938,
|
||||||
|
"*": 9.683334350585938,
|
||||||
|
"(": 7.8,
|
||||||
|
")": 7.8,
|
||||||
|
"-": 10.300003051757812,
|
||||||
|
"—": 10.300003051757812,
|
||||||
|
"_": 9.73333740234375,
|
||||||
|
"=": 14.333328247070312,
|
||||||
|
"+": 14.333328247070312,
|
||||||
|
"[": 7.883331298828125,
|
||||||
|
"]": 7.883331298828125,
|
||||||
|
"{": 7.883331298828125,
|
||||||
|
"}": 7.883331298828125,
|
||||||
|
";": 6.26666259765625,
|
||||||
|
":": 6.26666259765625,
|
||||||
|
"'": 5.26666259765625,
|
||||||
|
"\"": 9.5,
|
||||||
|
",": 6.26666259765625,
|
||||||
|
".": 5,
|
||||||
|
"/": 7.76666259765625,
|
||||||
|
"?": 11.649993896484375,
|
||||||
|
"`": 11.833328247070312,
|
||||||
|
"”": 9,
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCharWidth(char string) float64 {
|
||||||
|
width, ok := charWidths[char]
|
||||||
|
if !ok {
|
||||||
|
if len(emoji.FindAll(char)) > 0 {
|
||||||
|
width = 24
|
||||||
|
} else {
|
||||||
|
width = 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return width
|
||||||
|
}
|
3
go.mod
3
go.mod
@@ -11,5 +11,8 @@ require (
|
|||||||
github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17 // indirect
|
github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17 // indirect
|
||||||
github.com/grokify/html-strip-tags-go v0.0.1
|
github.com/grokify/html-strip-tags-go v0.0.1
|
||||||
github.com/joho/godotenv v1.3.0
|
github.com/joho/godotenv v1.3.0
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
|
github.com/rivo/uniseg v0.2.0
|
||||||
|
github.com/tmdvs/Go-Emoji-Utils v1.1.0
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
|
||||||
)
|
)
|
||||||
|
6
go.sum
6
go.sum
@@ -14,6 +14,12 @@ github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q
|
|||||||
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
|
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
|
||||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/tmdvs/Go-Emoji-Utils v1.1.0 h1:gtPix7HZPrd49+MNDcuRLvv4xVNxCE5wgjqyuvmbyYg=
|
||||||
|
github.com/tmdvs/Go-Emoji-Utils v1.1.0/go.mod h1:J82i2WeGn+Kz+T3s5v9+i/OJlvevIVfGZ6qXgqiNWBc=
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
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/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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
75
index.html
75
index.html
@@ -6,14 +6,17 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>TweetSVG</title>
|
<title>TweetSVG</title>
|
||||||
|
|
||||||
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 512 512' style='height: 50px;'%3E%3Cg%3E%3Cpath d='M61 140c-8 0-14-6-14-14V61c0-8 6-14 14-14h65a14 14 0 110 28H75v51c0 8-6 14-14 14z' fill='%23ef71a8'/%3E%3Cpath d='M451 465h-65a14 14 0 110-28h51v-51a14 14 0 1128 0v65c0 8-6 14-14 14z' fill='%23ef71a8'/%3E%3Cpath d='M498 512H14c-8 0-14-6-14-14V14C0 6 6 0 14 0h484c8 0 14 6 14 14v484c0 8-6 14-14 14zM28 484h456V28H28v456z' fill='%23ef71a8'/%3E%3C/g%3E%3Cpath d='M369 266c8-21 12-44 12-65v-1c0-4 2-7 5-9 10-9 26-30 26-30l-40-6c-2 0-12-11-14-12a68 68 0 00-66-14c-22 7-39 25-44 47-2 8-3 17-2 25v2a3 3 0 01-2 1 178 178 0 01-122-65c-2-2-5-2-6 0a68 68 0 0011 82c-5-1-11-3-15-6-3-1-6 1-6 3 0 28 16 52 40 63a70 70 0 01-13-1c-3-1-5 2-4 4 8 24 29 42 53 46-20 14-44 21-69 21h-8c-3 0-5 2-5 4-1 3 0 5 2 6 29 17 61 25 94 25a176 176 0 00138-61' fill='%2371cad1'/%3E%3Cpath d='M196 401c-36 0-70-10-101-28-7-4-11-13-9-21 2-9 10-15 19-15h8c12 0 24-2 35-5a82 82 0 01-33-44c-1-3-1-8 1-11a82 82 0 01-24-59c0-5 3-10 7-14a82 82 0 015-72 18 18 0 0129-2 164 164 0 0099 58l2-15a80 80 0 0179-62 82 82 0 0157 23l7 7 38 6a14 14 0 019 23c-2 2-17 21-29 31 0 23-4 47-13 70a14 14 0 11-26-10c7-20 11-40 11-60v-1c0-8 4-15 10-20l9-9-15-2c-6-1-11-5-21-14l-1-2a53 53 0 00-53-11c-17 6-30 20-34 37-2 7-2 14-2 20a17 17 0 01-18 19 191 191 0 01-120-57 54 54 0 0015 50 14 14 0 01-14 24c4 14 15 26 29 33a14 14 0 01-2 26c8 12 21 20 35 22a14 14 0 015 26c-15 10-32 17-50 21a174 174 0 00129-6c21-10 39-23 54-41a14 14 0 1121 19 190 190 0 01-148 66zm49-211zm127-21zm-261-21zm262-8z' fill='%23ef71a8'/%3E%3C/svg%3E">
|
||||||
|
|
||||||
<meta property="og:title" content="TweetSVG">
|
<meta property="og:title" content="TweetSVG">
|
||||||
<meta property="og:site_name" content="TweetSVG">
|
<meta property="og:site_name" content="TweetSVG">
|
||||||
<meta property="og:url" content="https://tweet.cluster.fun">
|
<meta property="og:url" content="https://tweet.cluster.fun">
|
||||||
<meta property="og:description" content="Generate an SVG for a given Tweet ID">
|
<meta property="og:description" content="Generate an SVG for a given Tweet ID">
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
<meta property="og:image" content="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSItNTAwIC0xMDAgMTUxMiA3MTIiPgogIDxnPgogICAgPHBhdGggZD0iTTYxIDE0MGMtOCAwLTE0LTYtMTQtMTRWNjFjMC04IDYtMTQgMTQtMTRoNjVhMTQgMTQgMCAxMTAgMjhINzV2NTFjMCA4LTYgMTQtMTQgMTR6IiBmaWxsPSIjZWY3MWE4Ii8+CiAgICA8cGF0aCBkPSJNNDUxIDQ2NWgtNjVhMTQgMTQgMCAxMTAtMjhoNTF2LTUxYTE0IDE0IDAgMTEyOCAwdjY1YzAgOC02IDE0LTE0IDE0eiIgZmlsbD0iI2VmNzFhOCIvPgogICAgPHBhdGggZD0iTTQ5OCA1MTJIMTRjLTggMC0xNC02LTE0LTE0VjE0QzAgNiA2IDAgMTQgMGg0ODRjOCAwIDE0IDYgMTQgMTR2NDg0YzAgOC02IDE0LTE0IDE0ek0yOCA0ODRoNDU2VjI4SDI4djQ1NnoiIGZpbGw9IiNlZjcxYTgiLz4KICA8L2c+CiAgPHBhdGggZD0iTTM2OSAyNjZjOC0yMSAxMi00NCAxMi02NXYtMWMwLTQgMi03IDUtOSAxMC05IDI2LTMwIDI2LTMwbC00MC02Yy0yIDAtMTItMTEtMTQtMTJhNjggNjggMCAwMC02Ni0xNGMtMjIgNy0zOSAyNS00NCA0Ny0yIDgtMyAxNy0yIDI1djJhMyAzIDAgMDEtMiAxIDE3OCAxNzggMCAwMS0xMjItNjVjLTItMi01LTItNiAwYTY4IDY4IDAgMDAxMSA4MmMtNS0xLTExLTMtMTUtNi0zLTEtNiAxLTYgMyAwIDI4IDE2IDUyIDQwIDYzYTcwIDcwIDAgMDEtMTMtMWMtMy0xLTUgMi00IDQgOCAyNCAyOSA0MiA1MyA0Ni0yMCAxNC00NCAyMS02OSAyMWgtOGMtMyAwLTUgMi01IDQtMSAzIDAgNSAyIDYgMjkgMTcgNjEgMjUgOTQgMjVhMTc2IDE3NiAwIDAwMTM4LTYxIiBmaWxsPSIjNzFjYWQxIi8+CiAgPHBhdGggZD0iTTE5NiA0MDFjLTM2IDAtNzAtMTAtMTAxLTI4LTctNC0xMS0xMy05LTIxIDItOSAxMC0xNSAxOS0xNWg4YzEyIDAgMjQtMiAzNS01YTgyIDgyIDAgMDEtMzMtNDRjLTEtMy0xLTggMS0xMWE4MiA4MiAwIDAxLTI0LTU5YzAtNSAzLTEwIDctMTRhODIgODIgMCAwMTUtNzIgMTggMTggMCAwMTI5LTIgMTY0IDE2NCAwIDAwOTkgNThsMi0xNWE4MCA4MCAwIDAxNzktNjIgODIgODIgMCAwMTU3IDIzbDcgNyAzOCA2YTE0IDE0IDAgMDE5IDIzYy0yIDItMTcgMjEtMjkgMzEgMCAyMy00IDQ3LTEzIDcwYTE0IDE0IDAgMTEtMjYtMTBjNy0yMCAxMS00MCAxMS02MHYtMWMwLTggNC0xNSAxMC0yMGw5LTktMTUtMmMtNi0xLTExLTUtMjEtMTRsLTEtMmE1MyA1MyAwIDAwLTUzLTExYy0xNyA2LTMwIDIwLTM0IDM3LTIgNy0yIDE0LTIgMjBhMTcgMTcgMCAwMS0xOCAxOSAxOTEgMTkxIDAgMDEtMTIwLTU3IDU0IDU0IDAgMDAxNSA1MCAxNCAxNCAwIDAxLTE0IDI0YzQgMTQgMTUgMjYgMjkgMzNhMTQgMTQgMCAwMS0yIDI2YzggMTIgMjEgMjAgMzUgMjJhMTQgMTQgMCAwMTUgMjZjLTE1IDEwLTMyIDE3LTUwIDIxYTE3NCAxNzQgMCAwMDEyOS02YzIxLTEwIDM5LTIzIDU0LTQxYTE0IDE0IDAgMTEyMSAxOSAxOTAgMTkwIDAgMDEtMTQ4IDY2em00OS0yMTF6bTEyNy0yMXptLTI2MS0yMXptMjYyLTh6IiBmaWxsPSIjZWY3MWE4Ii8+Cjwvc3ZnPgo=">
|
<meta property="og:image" content="https://opengraph.cluster.fun/opengraph/?siteTitle=&title=&tags=&image=https%3A%2F%2Fcdn.githubraw.com%2FAverageMarcus%2Ftweetsvg%2Fmaster%2Flogo.png&twitter=Marcus_Noble_&github=AverageMarcus%2Ftweetsvg&website=https%3A%2F%2Ftweet.cluster.fun&bgColor=%23ffffff&fgColor=%23263943">
|
||||||
<meta name="twitter:card" content="summary" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta name="twitter:creator" content="@Marcus_Noble_" />
|
<meta name="twitter:creator" content="@Marcus_Noble_" />
|
||||||
|
<meta name="twitter:image" content="https://opengraph.cluster.fun/opengraph/?siteTitle=&title=&tags=&image=https%3A%2F%2Fcdn.githubraw.com%2FAverageMarcus%2Ftweetsvg%2Fmaster%2Flogo.png&twitter=Marcus_Noble_&github=AverageMarcus%2Ftweetsvg&website=https%3A%2F%2Ftweet.cluster.fun&bgColor=%23ffffff&fgColor=%23263943">
|
||||||
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
|
||||||
@@ -32,6 +35,24 @@
|
|||||||
textarea {
|
textarea {
|
||||||
height: 200px;
|
height: 200px;
|
||||||
}
|
}
|
||||||
|
#examples {
|
||||||
|
align-items: start;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
#examples figure {
|
||||||
|
margin: 2px;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
figcaption {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.announcement {
|
||||||
|
font-size: 3em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -45,6 +66,10 @@
|
|||||||
Generate an SVG for a given Tweet ID
|
Generate an SVG for a given Tweet ID
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
|
||||||
|
<p class="announcement">
|
||||||
|
⚠️ DEPRECATED - Twitter has killed off its free API so this no longer works
|
||||||
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Enter the URL or ID of a tweet to have an SVG generated for it, no JavaScript required!
|
Enter the URL or ID of a tweet to have an SVG generated for it, no JavaScript required!
|
||||||
</p>
|
</p>
|
||||||
@@ -75,6 +100,44 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3>Example Tweets</h3>
|
||||||
|
<div id="examples">
|
||||||
|
<figure>
|
||||||
|
<img src="/1285788484280545280" />
|
||||||
|
<figcaption>Text with Emoji</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure>
|
||||||
|
<img src="/1417089885647761409" />
|
||||||
|
<figcaption>Tweet with 1 image</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure>
|
||||||
|
<img src="/1400358030869372928" />
|
||||||
|
<figcaption>Tweet with 2 images</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure>
|
||||||
|
<img src="/1401103668267556868" />
|
||||||
|
<figcaption>Tweet with 3 images</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure>
|
||||||
|
<img src="/1396802436052836360" />
|
||||||
|
<figcaption>Tweet with 4 images</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure>
|
||||||
|
<img src="/1370521139684970496" />
|
||||||
|
<figcaption>Tweet with quoted Tweet</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure>
|
||||||
|
<img src="/1281166762394791937" />
|
||||||
|
<figcaption>Tweet with link</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure>
|
||||||
|
<img src="/1396979104944201741" />
|
||||||
|
<figcaption>Reply Tweet</figcaption>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
Source code available on <a href="https://github.com/AverageMarcus/tweetsvg" target="_blank" rel="noopener noreferrer">GitHub</a>, <a href="https://gitlab.com/AverageMarcus/tweetsvg" target="_blank" rel="noopener noreferrer">GitLab</a>, <a href="https://bitbucket.org/AverageMarcus/tweetsvg/" target="_blank" rel="noopener noreferrer">Bitbucket</a> & <a href="https://git.cluster.fun/AverageMarcus/tweetsvg" target="_blank" rel="noopener noreferrer">my own Gitea server</a>.
|
Source code available on <a href="https://github.com/AverageMarcus/tweetsvg" target="_blank" rel="noopener noreferrer">GitHub</a>, <a href="https://gitlab.com/AverageMarcus/tweetsvg" target="_blank" rel="noopener noreferrer">GitLab</a>, <a href="https://bitbucket.org/AverageMarcus/tweetsvg/" target="_blank" rel="noopener noreferrer">Bitbucket</a> & <a href="https://git.cluster.fun/AverageMarcus/tweetsvg" target="_blank" rel="noopener noreferrer">my own Gitea server</a>.
|
||||||
</div>
|
</div>
|
||||||
@@ -104,9 +167,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('tweetURL').addEventListener('change', function(e) {
|
document.getElementById('tweetURL').addEventListener('change', function(e) {
|
||||||
let parts = e.target.value.split("/");
|
if (e.target.value.trim() != "") {
|
||||||
let tweetID = parts[parts.length-1];
|
let parts = e.target.value.split("/");
|
||||||
loadTweet(tweetID);
|
let tweetID = parts[parts.length-1];
|
||||||
|
loadTweet(tweetID);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('imageTagExample').addEventListener('click', function(e) {
|
document.getElementById('imageTagExample').addEventListener('click', function(e) {
|
||||||
|
202
main.go
202
main.go
@@ -18,9 +18,12 @@ import (
|
|||||||
"github.com/ChimeraCoder/anaconda"
|
"github.com/ChimeraCoder/anaconda"
|
||||||
strip "github.com/grokify/html-strip-tags-go"
|
strip "github.com/grokify/html-strip-tags-go"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
"github.com/rivo/uniseg"
|
||||||
|
emoji "github.com/tmdvs/Go-Emoji-Utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed index.html tweet.svg.tmpl
|
//go:embed index.html tweet.svg.tmpl suspendedTweet.svg
|
||||||
|
|
||||||
var content embed.FS
|
var content embed.FS
|
||||||
|
|
||||||
@@ -34,6 +37,8 @@ var (
|
|||||||
accessTokenSecret string
|
accessTokenSecret string
|
||||||
consumerKey string
|
consumerKey string
|
||||||
consumerSecret string
|
consumerSecret string
|
||||||
|
|
||||||
|
ch *cache.Cache
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -44,6 +49,8 @@ func init() {
|
|||||||
accessTokenSecret = os.Getenv("ACCESS_TOKEN_SECRET")
|
accessTokenSecret = os.Getenv("ACCESS_TOKEN_SECRET")
|
||||||
consumerKey = os.Getenv("CONSUMER_KEY")
|
consumerKey = os.Getenv("CONSUMER_KEY")
|
||||||
consumerSecret = os.Getenv("CONSUMER_SECRET")
|
consumerSecret = os.Getenv("CONSUMER_SECRET")
|
||||||
|
|
||||||
|
ch = cache.New(24*time.Hour, 48*time.Hour)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -76,26 +83,58 @@ func getTweet(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(400)
|
w.WriteHeader(400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tweet, err := api.GetTweet(i, nil)
|
|
||||||
|
result, found := ch.Get(id)
|
||||||
|
if !found {
|
||||||
|
fmt.Println("No cached tweet found, generating new...")
|
||||||
|
tweet, err := api.GetTweet(i, nil)
|
||||||
|
if err != nil {
|
||||||
|
switch err := err.(type) {
|
||||||
|
case *anaconda.ApiError:
|
||||||
|
switch err.Decoded.Errors[0].Code {
|
||||||
|
case 63:
|
||||||
|
fmt.Printf("Generating suspended tweet image for %s\n", id)
|
||||||
|
suspendedTweet(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println(err)
|
||||||
|
w.WriteHeader(404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
processTweet(&tweet)
|
||||||
|
|
||||||
|
result = renderTemplate(tweet, false)
|
||||||
|
ch.Set(id, result, cache.DefaultExpiration)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-type", "image/svg+xml")
|
||||||
|
_, err = w.Write(result.([]byte))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(404)
|
fmt.Println(err)
|
||||||
|
w.WriteHeader(500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
re := regexp.MustCompile(`[\x{1F300}-\x{1F6FF}]`)
|
func processTweet(tweet *anaconda.Tweet) {
|
||||||
emojis := re.FindAllString(tweet.FullText, -1)
|
gr := uniseg.NewGraphemes(tweet.FullText)
|
||||||
|
count := 0
|
||||||
emojiCount := 0
|
displayText := ""
|
||||||
for _, emoji := range emojis {
|
for gr.Next() {
|
||||||
emojiCount += len([]byte(emoji)) - 1
|
if count >= tweet.DisplayTextRange[0] && count < tweet.DisplayTextRange[1] {
|
||||||
|
displayText += gr.Str()
|
||||||
|
}
|
||||||
|
count += 1
|
||||||
}
|
}
|
||||||
tweet.FullText = tweet.FullText[tweet.DisplayTextRange[0] : tweet.DisplayTextRange[1]+emojiCount]
|
tweet.FullText = displayText
|
||||||
|
|
||||||
for _, user := range tweet.Entities.User_mentions {
|
for _, user := range tweet.Entities.User_mentions {
|
||||||
tweet.FullText = strings.ReplaceAll(tweet.FullText, "@"+user.Screen_name, fmt.Sprintf("<a rel=\"noopener\" target=\"_blank\" href=\"https://twitter.com/%s/\">@%s</a>", user.Screen_name, user.Screen_name))
|
tweet.FullText = strings.ReplaceAll(tweet.FullText, "@"+user.Screen_name, fmt.Sprintf("<a rel=\"noopener\" target=\"_blank\" href=\"https://twitter.com/%s/\">@%s</a>", user.Screen_name, user.Screen_name))
|
||||||
}
|
}
|
||||||
for _, url := range tweet.Entities.Urls {
|
for _, url := range tweet.Entities.Urls {
|
||||||
tweet.FullText = strings.ReplaceAll(tweet.FullText, url.Url, fmt.Sprintf("<a rel=\"noopener\" target=\"_blank\" href=\"https://twitter.com/%s/\">%s</a>", url.Expanded_url, url.Display_url))
|
tweet.FullText = strings.ReplaceAll(tweet.FullText, url.Url, fmt.Sprintf("<a rel=\"noopener\" target=\"_blank\" href=\"%s\">%s</a>", url.Expanded_url, url.Display_url))
|
||||||
}
|
}
|
||||||
for _, hashtag := range tweet.Entities.Hashtags {
|
for _, hashtag := range tweet.Entities.Hashtags {
|
||||||
tweet.FullText = strings.ReplaceAll(tweet.FullText, "#"+hashtag.Text, fmt.Sprintf("<a rel=\"noopener\" target=\"_blank\" href=\"https://twitter.com/hashtag/%s\">#%s</a>", hashtag.Text, hashtag.Text))
|
tweet.FullText = strings.ReplaceAll(tweet.FullText, "#"+hashtag.Text, fmt.Sprintf("<a rel=\"noopener\" target=\"_blank\" href=\"https://twitter.com/hashtag/%s\">#%s</a>", hashtag.Text, hashtag.Text))
|
||||||
@@ -103,6 +142,13 @@ func getTweet(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
tweet.FullText = strings.ReplaceAll(tweet.FullText, "\n", "<br />")
|
tweet.FullText = strings.ReplaceAll(tweet.FullText, "\n", "<br />")
|
||||||
|
|
||||||
|
if tweet.QuotedStatus != nil {
|
||||||
|
processTweet(tweet.QuotedStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderTemplate(tweet anaconda.Tweet, isQuoted bool) []byte {
|
||||||
templateFuncs := template.FuncMap{
|
templateFuncs := template.FuncMap{
|
||||||
"base64": func(url string) string {
|
"base64": func(url string) string {
|
||||||
res, err := http.Get(url)
|
res, err := http.Get(url)
|
||||||
@@ -125,23 +171,22 @@ func getTweet(w http.ResponseWriter, r *http.Request) {
|
|||||||
return template.HTML(in)
|
return template.HTML(in)
|
||||||
},
|
},
|
||||||
"calculateHeight": func(tweet anaconda.Tweet) string {
|
"calculateHeight": func(tweet anaconda.Tweet) string {
|
||||||
height := 205.0
|
return fmt.Sprintf("%dpx", calculateHeight(tweet))
|
||||||
|
},
|
||||||
lines := math.Floor(float64(len(strip.StripTags(tweet.FullText))) / 40)
|
"renderTweet": func(tweet anaconda.Tweet) template.HTML {
|
||||||
height += lines * 20
|
return template.HTML(string(renderTemplate(tweet, true)))
|
||||||
|
},
|
||||||
if tweet.InReplyToScreenName != "" {
|
"tweetWidth": func() string {
|
||||||
height += 45
|
if isQuoted {
|
||||||
|
return "450px"
|
||||||
}
|
}
|
||||||
|
return "499px"
|
||||||
height += float64(strings.Count(tweet.FullText, "<br />") * 20)
|
},
|
||||||
|
"className": func() string {
|
||||||
for _, pic := range tweet.ExtendedEntities.Media {
|
if isQuoted {
|
||||||
ratio := float64(pic.Sizes.Small.W) / 464
|
return "subtweet"
|
||||||
height += float64(pic.Sizes.Small.H) / ratio
|
|
||||||
}
|
}
|
||||||
|
return "tweetsvg"
|
||||||
return fmt.Sprintf("%dpx", int64(height))
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,11 +195,102 @@ func getTweet(w http.ResponseWriter, r *http.Request) {
|
|||||||
Funcs(templateFuncs).
|
Funcs(templateFuncs).
|
||||||
ParseFS(content, "tweet.svg.tmpl"))
|
ParseFS(content, "tweet.svg.tmpl"))
|
||||||
|
|
||||||
w.Header().Set("Content-type", "image/svg+xml")
|
var buf bytes.Buffer
|
||||||
err = t.Execute(w, tweet)
|
t.Execute(&buf, tweet)
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
return buf.Bytes()
|
||||||
w.WriteHeader(500)
|
}
|
||||||
return
|
|
||||||
}
|
func calculateHeight(tweet anaconda.Tweet) int64 {
|
||||||
|
height := 64.0 /* Avatar */ + 20 /* footer */ + 46 /* text margin */ + 22 /* margin */
|
||||||
|
|
||||||
|
lineWidth := 0.0
|
||||||
|
lineHeight := 28.0
|
||||||
|
tweetText := strings.ReplaceAll(tweet.FullText, "<br />", " \n")
|
||||||
|
tweetText = strip.StripTags(tweetText)
|
||||||
|
tweetText = strings.ReplaceAll(tweetText, "--", "——")
|
||||||
|
words := regexp.MustCompile(`[ |-]`).Split(tweetText, -1)
|
||||||
|
for _, word := range words {
|
||||||
|
word = strings.ReplaceAll(word, "——", "--")
|
||||||
|
if len(emoji.FindAll(word)) > 0 {
|
||||||
|
lineHeight = 32.0
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(word, "\n") {
|
||||||
|
height += lineHeight
|
||||||
|
lineWidth = 0
|
||||||
|
word = strings.TrimPrefix(word, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(word, "\n") {
|
||||||
|
height += lineHeight
|
||||||
|
lineHeight = 28.0
|
||||||
|
lineWidth = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
chars := strings.Split(word, "")
|
||||||
|
wordWidth := 0.0
|
||||||
|
for _, char := range chars {
|
||||||
|
wordWidth += getCharWidth(char)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wordWidth > 435 {
|
||||||
|
height += (lineHeight * (math.Ceil(wordWidth/435) + 1))
|
||||||
|
lineHeight = 28.0
|
||||||
|
lineWidth = 0
|
||||||
|
} else if lineWidth+getCharWidth(" ")+wordWidth > 435 {
|
||||||
|
height += lineHeight
|
||||||
|
lineHeight = 28.0
|
||||||
|
lineWidth = wordWidth
|
||||||
|
} else {
|
||||||
|
lineWidth += wordWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lineWidth > 0 {
|
||||||
|
height += lineHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
if tweet.InReplyToScreenName != "" {
|
||||||
|
height += 42
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, img := range tweet.ExtendedEntities.Media {
|
||||||
|
ratio := float64(img.Sizes.Small.W) / 468
|
||||||
|
tweet.ExtendedEntities.Media[i].Sizes.Small.W = 468
|
||||||
|
tweet.ExtendedEntities.Media[i].Sizes.Small.H = int((float64(img.Sizes.Small.H) / ratio) + 5.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tweet.ExtendedEntities.Media) > 1 {
|
||||||
|
for i, img := range tweet.ExtendedEntities.Media {
|
||||||
|
tweet.ExtendedEntities.Media[i].Sizes.Small.W = (img.Sizes.Small.W / 2) - 20
|
||||||
|
tweet.ExtendedEntities.Media[i].Sizes.Small.H = (img.Sizes.Small.H / 2) - 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(tweet.ExtendedEntities.Media) {
|
||||||
|
case 1:
|
||||||
|
height += float64(tweet.ExtendedEntities.Media[0].Sizes.Small.H)
|
||||||
|
case 2:
|
||||||
|
height += math.Max(float64(tweet.ExtendedEntities.Media[0].Sizes.Small.H), float64(tweet.ExtendedEntities.Media[1].Sizes.Small.H)) + 5
|
||||||
|
case 3:
|
||||||
|
height += math.Max(float64(tweet.ExtendedEntities.Media[0].Sizes.Small.H), float64(tweet.ExtendedEntities.Media[1].Sizes.Small.H)) + 5
|
||||||
|
height += float64(tweet.ExtendedEntities.Media[2].Sizes.Small.H) + 35
|
||||||
|
case 4:
|
||||||
|
height += math.Max(float64(tweet.ExtendedEntities.Media[0].Sizes.Small.H), float64(tweet.ExtendedEntities.Media[1].Sizes.Small.H)) + 10
|
||||||
|
height += math.Max(float64(tweet.ExtendedEntities.Media[2].Sizes.Small.H), float64(tweet.ExtendedEntities.Media[3].Sizes.Small.H)) + 10
|
||||||
|
height += 7
|
||||||
|
}
|
||||||
|
|
||||||
|
if tweet.QuotedStatus != nil {
|
||||||
|
height += float64(calculateHeight(*tweet.QuotedStatus)) + 9
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func suspendedTweet(w http.ResponseWriter) {
|
||||||
|
w.Header().Set("Content-type", "image/svg+xml")
|
||||||
|
tweet, _ := content.ReadFile("suspendedTweet.svg")
|
||||||
|
w.Write(tweet)
|
||||||
}
|
}
|
||||||
|
1
suspendedTweet.svg
Normal file
1
suspendedTweet.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="566" height="46" viewBox="0 0 566 46"><rect width="100%" height="100%" fill="rgba(0, 0, 0, 0)"/><path vector-effect="non-scaling-stroke" stroke="#f7f9f9" stroke-width="20" stroke-linejoin="round" fill="#f7f9f9" d="M-50-50V50H50V-50z" transform="matrix(5.46 0 0 .26 283 23.17)"/><g><text font-family="'Open Sans', sans-serif" font-size="15" style="white-space:pre" stroke-width="0" fill="#536471" transform="translate(221.5 21.91)"><tspan x="-202.5" y="5.34" style="white-space:pre" font-size="17">This Tweet is from a suspended account.</tspan></text></g></svg>
|
After Width: | Height: | Size: 652 B |
@@ -1,43 +1,49 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="499px" height="{{ calculateHeight . }}">
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{{ tweetWidth }}" height="{{ calculateHeight . }}">
|
||||||
<foreignObject x="0" y="0" width="499px" height="100%" fill="#eade52">
|
<foreignObject x="0" y="0" width="{{ tweetWidth }}" height="100%" fill="#eade52">
|
||||||
<style>
|
<style>
|
||||||
.tweetsvg{clear:none;font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;}
|
.{{ className }}{clear:none;font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;}
|
||||||
.tweetsvg.text{font-size: 23px;}
|
.{{ className }}.text{font-size: 23px;}
|
||||||
a.tweetsvg{color: rgb(27, 149, 224); text-decoration:none;}
|
a.{{ className }}{color: rgb(27, 149, 224); text-decoration:none;}
|
||||||
.tweetsvg a { color: #1da1f2; }
|
.{{ className }} a { color: #1da1f2; }
|
||||||
blockquote.tweetsvg{margin:1px; background-color:#fefefe; border-radius:2%; border-style:solid; border-width:.1em; border-color:#ddd; padding:1em; font-family:sans; width:29rem}
|
blockquote.{{ className }}{margin:1px; background-color:#fefefe; border-radius:2%; border-style:solid; border-width:.1em; border-color:#ddd; padding:1em; font-family:sans; width:29rem}
|
||||||
.avatar-tweetsvg{float:left; width:4rem; height:4rem; border-radius:50%;margin-right:.5rem;;margin-bottom:.5rem;border-style: solid; border-width:.1em; border-color:#ddd;}
|
blockquote.subtweet{width:26rem; padding:0.8em;}
|
||||||
h1.tweetsvg{margin:0;font-size:15px;text-decoration:none;color:#000;}
|
.avatar-{{ className }}{float:left; width:4rem; height:4rem; border-radius:50%;margin-right:.5rem;;margin-bottom:.5rem;border-style: solid; border-width:.1em; border-color:#ddd;}
|
||||||
h2.tweetsvg{margin:0;font-size:15px;font-weight:normal;text-decoration:none;color:rgb(101, 119, 134);}
|
h1.{{ className }}{margin:0;font-size:15px;text-decoration:none;color:#000;}
|
||||||
p.tweetsvg{font-size:1rem; clear:both;}
|
h2.{{ className }}{margin:0;font-size:15px;font-weight:normal;text-decoration:none;color:rgb(101, 119, 134);}
|
||||||
hr.tweetsvg{color:#ddd;}
|
p.{{ className }}{font-size:1rem; clear:both;}
|
||||||
.media-tweetsvg{border-radius:2%; max-width:100%;border-radius: 2%; border-style: solid; border-width: .1em; border-color: #ddd;}
|
hr.{{ className }}{color:#ddd;}
|
||||||
time.tweetsvg{font-size:15px;margin:0;margin-left: 2px;padding-bottom:1rem;color:rgb(101, 119, 134);text-decoration:none;}
|
.media-{{ className }}{border-radius:2%; max-width:100%;border-radius: 2%; border-style: solid; border-width: .1em; border-color: #ddd;}
|
||||||
.tweetsvg.reply{font-size:15px;color:rgb(110, 118, 125);}
|
time.{{ className }}{font-size:15px;margin:0;margin-left: 2px;padding-bottom:1rem;color:rgb(101, 119, 134);text-decoration:none;}
|
||||||
|
.{{ className }}.reply{font-size:15px;color:rgb(110, 118, 125);}
|
||||||
|
.{{ className }}.footer{display:block;}
|
||||||
</style>
|
</style>
|
||||||
<blockquote class="tweetsvg" xmlns="http://www.w3.org/1999/xhtml">
|
<blockquote class="{{ className }}" xmlns="http://www.w3.org/1999/xhtml">
|
||||||
<a rel="noopener" target="_blank" class="tweetsvg" href="https://twitter.com/{{ .User.ScreenName }}/"><img class="avatar-tweetsvg" alt="" src="data:image/jpeg;base64,{{ base64 .User.ProfileImageUrlHttps }}" /></a>
|
<a rel="noopener" target="_blank" class="{{ className }}" href="https://twitter.com/{{ .User.ScreenName }}/"><img class="avatar-{{ className }}" alt="" src="data:image/jpeg;base64,{{ base64 .User.ProfileImageUrlHttps }}" /></a>
|
||||||
|
|
||||||
<a rel="noopener" target="_blank" class="tweetsvg" href="https://twitter.com/{{ .User.ScreenName }}/"><h1 class="tweetsvg">{{ .User.Name }}</h1></a>
|
<a rel="noopener" target="_blank" class="{{ className }}" href="https://twitter.com/{{ .User.ScreenName }}/"><h1 class="{{ className }}">{{ .User.Name }}</h1></a>
|
||||||
|
|
||||||
<a rel="noopener" target="_blank" class="tweetsvg" href="https://twitter.com/{{ .User.ScreenName }}/"><h2 class="tweetsvg">@{{ .User.ScreenName }}</h2></a>
|
<a rel="noopener" target="_blank" class="{{ className }}" href="https://twitter.com/{{ .User.ScreenName }}/"><h2 class="{{ className }}">@{{ .User.ScreenName }}</h2></a>
|
||||||
|
|
||||||
{{ if .InReplyToScreenName }}
|
{{ if .InReplyToScreenName }}
|
||||||
<p class="tweetsvg reply">Replying to <a rel="noopener" target="_blank" href="https://twitter.com/{{ .InReplyToScreenName }}/">@{{ .InReplyToScreenName }}</a></p>
|
<p class="{{ className }} reply">Replying to <a rel="noopener" target="_blank" href="https://twitter.com/{{ .InReplyToScreenName }}/">@{{ .InReplyToScreenName }}</a></p>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
<p class="tweetsvg text">{{ html .FullText }}</p>
|
<p class="{{ className }} text">{{ html .FullText }}</p>
|
||||||
|
|
||||||
|
{{ if .QuotedStatus }}
|
||||||
|
{{ renderTweet .QuotedStatus }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
{{ if .ExtendedEntities }}
|
{{ if .ExtendedEntities }}
|
||||||
{{ range .ExtendedEntities.Media }}
|
{{ range .ExtendedEntities.Media }}
|
||||||
|
|
||||||
<a rel="noopener" target="_blank" href="{{ .Media_url_https }}"><img class="media-tweetsvg" width="{{ .Sizes.Small.W }}" src="data:image/jpeg;base64,{{ base64 .Media_url }}" alt="{{ .ExtAltText }}"/></a>
|
<a rel="noopener" target="_blank" href="{{ .Media_url_https }}"><img class="media-{{ className }}" width="{{ .Sizes.Small.W }}" src="data:image/jpeg;base64,{{ base64 .Media_url }}" alt="{{ .ExtAltText }}"/></a>
|
||||||
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
<a rel="noopener" target="_blank" class="tweetsvg" href="https://twitter.com/{{ .User.ScreenName }}/status/{{ .Id }}">
|
<a rel="noopener" target="_blank" class="{{ className }} footer" href="https://twitter.com/{{ .User.ScreenName }}/status/{{ .Id }}">
|
||||||
<time class="tweetsvg" datetime="{{ isoDate .CreatedAt }}">{{ humanDate .CreatedAt }}</time>
|
<time class="{{ className }}" datetime="{{ isoDate .CreatedAt }}">{{ humanDate .CreatedAt }}</time>
|
||||||
</a>
|
</a>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
</foreignObject>
|
</foreignObject>
|
||||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.1 KiB |
Reference in New Issue
Block a user