replace zxq.co/ripple/hanayo

This commit is contained in:
Alicia
2019-02-23 13:29:15 +00:00
commit c3d206c173
5871 changed files with 1353715 additions and 0 deletions

229
modules/bbcode/bbcode.go Normal file
View File

@@ -0,0 +1,229 @@
// Package bbcode implements BBCode compiling for Hanayo.
package bbcode
import (
"fmt"
"net/url"
"strconv"
"strings"
"github.com/frustra/bbcode"
"github.com/microcosm-cc/bluemonday"
)
var bbcodeCompiler = func() bbcode.Compiler {
compiler := bbcode.NewCompiler(true, true)
compiler.SetTag("list", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
out := bbcode.NewHTMLTag("")
out.Name = "ul"
style := node.GetOpeningTag().Value
switch style {
case "a":
out.Attrs["style"] = "list-style-type: lower-alpha;"
case "A":
out.Attrs["style"] = "list-style-type: upper-alpha;"
case "i":
out.Attrs["style"] = "list-style-type: lower-roman;"
case "I":
out.Attrs["style"] = "list-style-type: upper-roman;"
case "1":
out.Attrs["style"] = "list-style-type: decimal;"
default:
out.Attrs["style"] = "list-style-type: disc;"
}
if len(node.Children) == 0 {
out.AppendChild(bbcode.NewHTMLTag(""))
} else {
node.Info = []*bbcode.HTMLTag{out, out}
tags := node.Info.([]*bbcode.HTMLTag)
for _, child := range node.Children {
curr := tags[1]
curr.AppendChild(node.Compiler.CompileTree(child))
}
if len(tags[1].Children) > 0 {
last := tags[1].Children[len(tags[1].Children)-1]
if len(last.Children) > 0 && last.Children[len(last.Children)-1].Name == "br" {
last.Children[len(last.Children)-1] = bbcode.NewHTMLTag("")
}
} else {
tags[1].AppendChild(bbcode.NewHTMLTag(""))
}
}
return out, false
})
compiler.SetTag("*", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
parent := node.Parent
for parent != nil {
if parent.ID == bbcode.OPENING_TAG && parent.GetOpeningTag().Name == "list" {
out := bbcode.NewHTMLTag("")
out.Name = "li"
tags := parent.Info.([]*bbcode.HTMLTag)
if len(tags[1].Children) > 0 {
last := tags[1].Children[len(tags[1].Children)-1]
if len(last.Children) > 0 && last.Children[len(last.Children)-1].Name == "br" {
last.Children[len(last.Children)-1] = bbcode.NewHTMLTag("")
}
} else {
tags[1].AppendChild(bbcode.NewHTMLTag(""))
}
tags[1] = out
tags[0].AppendChild(out)
if len(parent.Children) == 0 {
out.AppendChild(bbcode.NewHTMLTag(""))
} else {
for _, child := range node.Children {
curr := tags[1]
curr.AppendChild(node.Compiler.CompileTree(child))
}
}
if node.ClosingTag != nil {
tag := bbcode.NewHTMLTag(node.ClosingTag.Raw)
bbcode.InsertNewlines(tag)
out.AppendChild(tag)
}
return nil, false
}
parent = parent.Parent
}
return bbcode.DefaultTagCompiler(node)
})
compiler.SetTag("youtube", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
var youtubeID string
content := bbcode.CompileText(node)
youtubeLink, err := url.Parse(content)
if err != nil {
youtubeID = content
} else {
youtubeID = youtubeLink.Query().Get("v")
if youtubeID == "" {
youtubeID = content
}
}
tag := bbcode.NewHTMLTag("")
tag.Name = "iframe"
tag.Attrs = map[string]string{
"style": "width: 100%; max-height: 100%;",
"src": "https://www.youtube.com/embed/" + youtubeID,
"frameborder": "0",
"allowfullscreen": "",
}
tag.AppendChild(nil)
container := bbcode.NewHTMLTag("")
container.Name = "div"
container.Attrs["class"] = "youtube video container"
container.AppendChild(tag)
return container, false
})
compiler.SetTag("left", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
out := bbcode.NewHTMLTag("")
out.Name = "div"
out.Attrs["style"] = "text-align: left;"
return out, true
})
compiler.SetTag("right", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
out := bbcode.NewHTMLTag("")
out.Name = "div"
out.Attrs["style"] = "text-align: right;"
return out, true
})
compiler.SetTag("container", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
args := node.GetOpeningTag().Args
out := bbcode.NewHTMLTag("")
out.Name = "div"
out.Attrs["style"] = ""
out.Attrs["class"] = ""
if _, err := strconv.Atoi(args["width"]); err == nil {
out.Attrs["style"] += "width: " + args["width"] + "px;"
}
if args["compact"] != "" {
out.Attrs["class"] += "compact-container "
}
if args["center"] != "" {
out.Attrs["style"] += "margin: 0 auto;"
}
return out, true
})
compiler.SetTag("hr", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
out := bbcode.NewHTMLTag("")
out.Name = "div"
out.Attrs["class"] = "ui divider"
out.AppendChild(nil)
return out, false
})
compiler.SetTag("email", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
out := bbcode.NewHTMLTag("")
out.Name = "a"
val := node.GetOpeningTag().Value
if val == "" {
out.Attrs["href"] = "mailto:" + bbcode.CompileText(node)
out.AppendChild(bbcode.NewHTMLTag(bbcode.CompileText(node)))
return out, false
}
out.Attrs["href"] = "mailto:" + val
return out, true
})
compiler.SetTag("size", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
out := bbcode.NewHTMLTag("")
out.Name = "span"
if size, err := strconv.Atoi(node.GetOpeningTag().Value); err == nil && size > 0 {
if size > 15 {
size = 15
}
out.Attrs["style"] = fmt.Sprintf("font-size: %dpt; line-height: %[1]dpt;", size*6)
}
return out, true
})
return compiler
}()
var emojis = []string{
"peppy",
"barney",
"akerino",
"foka",
"kappy",
"creepypeppy",
"peppyfiero",
"djpeppy",
"kappa",
}
var emojiReplacer = func() *strings.Replacer {
var list []string
for _, e := range emojis {
list = append(list, ":"+e+":", "[img=/static/emotes/"+e+".png]:"+e+":[/img]")
}
return strings.NewReplacer(list...)
}()
// Compile takes some BBCode and converts it into safe HTML output.
func Compile(s string) string {
s = emojiReplacer.Replace(s)
s = strings.TrimSpace(s)
return mondaySanitise(bbcodeCompiler.Compile(s))
}
var policy = func() *bluemonday.Policy {
p := bluemonday.UGCPolicy()
p.AllowAttrs("style", "class").Globally()
p.AllowElements("iframe")
p.AllowAttrs("style", "src", "frameborder", "allowfullscreen").OnElements("iframe")
return p
}()
func mondaySanitise(source string) string {
return policy.Sanitize(source)
}

View File

@@ -0,0 +1,177 @@
// Package btcaddress makes sure the Bitcoin address for the Ripple donations
// account on Keybase is up-to-date.
// This shows a default key if the system is not set up, or otherwise refreshes
// the address every 30 minutes (using redis to hold the cache).
// The wallet with the name "Ripple" will be used for getting the address.
package btcaddress
import (
"crypto/hmac"
"crypto/sha256"
"encoding/json"
"fmt"
"net/http"
"time"
"strings"
"gopkg.in/redis.v5"
)
// DefaultAddress is the address used if the package is not set up to refresh
// from coinbase.
const DefaultAddress = "1CKGzZqrVwKoXwHEWpTobWqqtkYuqoFNro"
// AccountName is the name the account on coinbase must have to be considered as
// the actual account.
const AccountName = "Ripple"
// Configuration variables.
var (
Redis *redis.Client
APIKey string
APISecret string
)
// Get retrieves the Bitcoin address, using the following methods:
//
// First the address is requested from Redis, and if the key is available from
// redis then that is used.
//
// After that, the key is requested from coinbase, and the account ID is found
// if not already saved in Redis.
func Get() string {
v := Redis.Get("hanayo:btcaddress").Val()
if v != "" {
return v
}
if APIKey == "" || APISecret == "" {
return DefaultAddress
}
a, err := getFromCoinbase()
if err != nil {
fmt.Println(err)
return DefaultAddress
}
Redis.Set("hanayo:btcaddress", a, time.Minute*30)
return a
}
type account struct {
ID string `json:"id"`
Name string `json:"name"`
}
type accountsResp struct {
Data []account `json:"data"`
}
type address struct {
ID string `json:"id"`
Name string `json:"name"`
Address string `json:"address"`
}
type createAddressResp struct {
Data address `json:"data"`
}
type errors struct {
Errors []struct {
ID string `json:"id"`
Message string `json:"message"`
} `json:"errors"`
}
const coinbaseAPIDate = "2017-01-23"
const coinbaseAPIBase = "https://api.coinbase.com/v2/"
var accountID string
func getFromCoinbase() (string, error) {
if accountID == "" {
var accs accountsResp
err := req("accounts", nil, &accs)
if err != nil {
return "", err
}
for _, el := range accs.Data {
if el.Name == AccountName {
accountID = el.ID
break
}
}
}
var addrResp createAddressResp
err := req("accounts/"+accountID+"/addresses", struct{}{}, &addrResp)
if err != nil {
return "", err
}
return addrResp.Data.Address, nil
}
func req(endpoint string, data interface{}, out interface{}) error {
var dataJSON string
method := "GET"
if data != nil {
method = "POST"
// sort of a dirty hack, but oh well
djr, err := json.Marshal(data)
if err != nil {
return err
}
dataJSON = string(djr)
}
req, err := http.NewRequest(method, coinbaseAPIBase+endpoint, strings.NewReader(dataJSON))
if err != nil {
return err
}
if data != nil {
req.Header.Add("Content-Type", "application/json")
}
// Not usind .Header.Add because we want to make sure the names are kept
// uppercase.
ts := time.Now().Unix()
req.Header["CB-VERSION"] = []string{coinbaseAPIDate}
req.Header["CB-ACCESS-KEY"] = []string{APIKey}
req.Header["CB-ACCESS-TIMESTAMP"] = []string{fmt.Sprintf("%d", ts)}
sig := fmt.Sprintf(
"%d%s/v2/%s%s",
ts,
method,
endpoint,
string(dataJSON),
)
hm := hmac.New(
sha256.New,
[]byte(APISecret),
)
hm.Write([]byte(sig))
req.Header["CB-ACCESS-SIGN"] = []string{
fmt.Sprintf("%x", hm.Sum(nil)),
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode != 200 && resp.StatusCode != 201 {
var errs errors
json.NewDecoder(resp.Body).Decode(&errs)
return fmt.Errorf("btcaddress: response status code is %d - %v", resp.StatusCode, errs)
}
err = json.NewDecoder(resp.Body).Decode(&out)
return err
}

View File

@@ -0,0 +1,54 @@
package btcconversions
import (
"fmt"
"net/http"
"time"
"encoding/json"
"github.com/gin-gonic/gin"
)
var rates = struct {
EUR float64
USD float64
}{
// 15m values fetched from blockchain ticker on 2017-02-25
1118.33,
1180.4,
}
// GetRates returns the bitcoin rates as JSON.
func GetRates(c *gin.Context) {
c.JSON(200, rates)
}
func init() {
go func() {
for {
updateRates()
time.Sleep(time.Hour)
}
}()
}
type blockchainCurrency struct {
// The X is necessary a) to make this exported b) because an identifier
// can't start with a number.
X15m float64 `json:"15m"`
}
func updateRates() {
resp, err := http.Get("https://blockchain.info/ticker")
if err != nil {
fmt.Println(err)
return
}
m := make(map[string]blockchainCurrency)
json.NewDecoder(resp.Body).Decode(&m)
rates.EUR = m["EUR"].X15m
rates.USD = m["USD"].X15m
}

View File

@@ -0,0 +1,134 @@
package doc
import (
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strings"
"time"
)
const referenceLanguage = "en"
var docFiles []Document
// File represents the single documentation file of a determined language.
type File struct {
IsUpdated bool
Title string
referencesFile string
}
// Data retrieves data from file's actual file on disk.
func (f File) Data() (string, error) {
data, err := ioutil.ReadFile(f.referencesFile)
updateIPs()
res := strings.NewReplacer(
"{ipmain}", ipMain,
"{ipmirror}", ipMirror,
).Replace(string(data))
return res, err
}
// Document represents a documentation file, providing its old ID, its slug,
// and all its variations in the various languages.
type Document struct {
Slug string
OldID int
Languages map[string]File
}
// File retrieves a Document's File based on the passed language, and returns
// the values for the referenceLanguage (en) if in the passed language they are
// not available
func (d Document) File(lang string) File {
if vals, ok := d.Languages[lang]; ok {
return vals
}
return d.Languages[referenceLanguage]
}
// LanguageDoc has the only purpose to be returned by GetDocs.
type LanguageDoc struct {
Title string
Slug string
}
// GetDocs retrieves a list of documents in a certain language, with titles and
// slugs.
func GetDocs(lang string) []LanguageDoc {
var docs []LanguageDoc
for _, file := range docFiles {
docs = append(docs, LanguageDoc{
Slug: file.Slug,
Title: file.File(lang).Title,
})
}
return docs
}
// SlugFromOldID gets a doc file's slug from its old ID
func SlugFromOldID(i int) string {
for _, d := range docFiles {
if d.OldID == i {
return d.Slug
}
}
return ""
}
// GetFile retrieves a file, given a slug and a language.
func GetFile(slug, language string) File {
for _, f := range docFiles {
if f.Slug != slug {
continue
}
if val, ok := f.Languages[language]; ok {
return val
}
return f.Languages[referenceLanguage]
}
return File{}
}
var (
ipMain = "163.172.71.251"
ipMirror = "51.15.222.176"
ipLastUpdated = time.Date(2018, 5, 13, 11, 45, 0, 0, time.UTC)
ipRegex = regexp.MustCompile(`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$`)
)
func updateIPs() {
if time.Now().Sub(ipLastUpdated) < time.Hour*24*14 {
return
}
ipLastUpdated = time.Now()
resp, err := http.Get("https://ip.ripple.moe")
if err != nil {
fmt.Println("error updating IPs", err)
return
}
data, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
fmt.Println("error updating IPs", err)
return
}
ips := strings.SplitN(string(data), "\n", 3)
if len(ips) < 2 || !ipRegex.MatchString(ips[0]) || !ipRegex.MatchString(ips[1]) {
return
}
ipMain = ips[0]
ipMirror = ips[1]
}
func init() {
go updateIPs()
}

137
modules/doc/loader.go Normal file
View File

@@ -0,0 +1,137 @@
// Package doc handles documentation.
package doc
import (
"bufio"
"bytes"
"crypto/md5"
"fmt"
"io/ioutil"
"os"
"strings"
"gopkg.in/yaml.v2"
)
func init() {
// When we start the program, we should load the documentation files.
err := loadDocFiles()
if err != nil {
fmt.Println("error while loading documentation:", err)
}
}
// rawFile represents the data that may be provided at the top of files.
type rawFile struct {
Title string `yaml:"title"`
OldID int `yaml:"old_id"`
ReferenceVersion string `yaml:"reference_version"`
}
func loadDocFiles() error {
langs, err := loadLanguagesAvailable()
if err != nil {
return err
}
files, err := ioutil.ReadDir("website-docs/" + referenceLanguage)
if err != nil {
return err
}
for _, file := range files {
data, err := ioutil.ReadFile("website-docs/" + referenceLanguage + "/" + file.Name())
if err != nil {
return err
}
header := loadHeader(data)
md5sum := fmt.Sprintf("%x", md5.Sum(data))
doc := Document{
OldID: header.OldID,
Slug: strings.TrimSuffix(file.Name(), ".md"),
}
doc.Languages, err = loadLanguages(langs, file.Name(), md5sum)
if err != nil {
return err
}
docFiles = append(docFiles, doc)
}
return nil
}
func loadHeader(b []byte) rawFile {
s := bufio.NewScanner(bytes.NewReader(b))
var (
isConf bool
conf string
)
for s.Scan() {
line := s.Text()
if !isConf {
if line == "---" {
isConf = true
}
continue
}
if line == "---" {
break
}
conf += line + "\n"
}
var f rawFile
err := yaml.Unmarshal([]byte(conf), &f)
if err != nil {
fmt.Println("Error unmarshaling yaml:", err)
return rawFile{}
}
return f
}
func loadLanguagesAvailable() ([]string, error) {
files, err := ioutil.ReadDir("website-docs")
if err != nil {
return nil, err
}
langs := make([]string, 0, len(files))
for _, f := range files {
if !f.IsDir() {
continue
}
langs = append(langs, f.Name())
}
return langs, nil
}
func loadLanguages(langs []string, fname string, referenceMD5 string) (map[string]File, error) {
m := make(map[string]File, len(langs))
for _, lang := range langs {
data, err := ioutil.ReadFile("website-docs/" + lang + "/" + fname)
if err != nil {
if os.IsNotExist(err) {
continue
}
return nil, err
}
header := loadHeader(data)
m[lang] = File{
IsUpdated: lang == referenceLanguage || header.ReferenceVersion == referenceMD5,
Title: header.Title,
referencesFile: "website-docs/" + lang + "/" + fname,
}
}
return m, nil
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,62 @@
package locale
import (
"fmt"
"io/ioutil"
"strings"
)
var languageMap = make(map[string]*po, 20)
func loadLanguages() {
files, err := ioutil.ReadDir("./data/locales")
if err != nil {
fmt.Println("loadLanguages", err)
return
}
for _, file := range files {
if file.Name() == "templates.pot" || file.Name() == "." || file.Name() == ".." {
continue
}
p, err := parseFile("./data/locales/" + file.Name())
if err != nil {
fmt.Println(file.Name(), ":", err)
continue
}
if p == nil {
fmt.Println(file.Name(), ":", "p is nil")
}
langName := strings.TrimPrefix(strings.TrimSuffix(file.Name(), ".po"), "templates-")
languageMap[langName] = p
}
}
func init() {
loadLanguages()
}
// Get retrieves a string from a language
func Get(langs []string, str string, vars ...interface{}) string {
for _, lang := range langs {
l := languageMap[lang]
if l == nil {
continue
}
if el := l.Translations[str]; el != "" {
if len(vars) > 0 {
return fmt.Sprintf(el, vars...)
}
return el
}
}
if len(vars) > 0 {
return fmt.Sprintf(str, vars...)
}
return str
}

View File

@@ -0,0 +1,38 @@
package locale
import (
"sort"
"strconv"
"strings"
)
// ParseHeader parses an Accept-Language header, and sorts the values.
func ParseHeader(header string) []string {
if header == "" {
return nil
}
parts := strings.Split(header, ",")
sort.Slice(parts, func(i, j int) bool {
return getQuality(parts[i]) > getQuality(parts[j])
})
for idx, val := range parts {
parts[idx] = strings.Replace(strings.SplitN(val, ";q=", 2)[0], "-", "_", 1)
}
return parts
}
func getQuality(s string) float32 {
idx := strings.Index(s, ";q=")
if idx == -1 {
return 1
}
f, err := strconv.ParseFloat(s[idx+3:], 32)
if err != nil {
return 1
}
return float32(f)
}

View File

@@ -0,0 +1,45 @@
package locale
import (
"reflect"
"testing"
)
func TestParseHeader(t *testing.T) {
tt := []struct {
In string
Out []string
}{
{
"en",
[]string{"en"},
},
{
"en-GB",
[]string{"en_GB"},
},
{
"en-GB;q=0.5,it",
[]string{"it", "en_GB"},
},
{
"en-GB;q=0.5,it,pl;q=0.2",
[]string{"it", "en_GB", "pl"},
},
{
"en-GB;q=0.5,pl;q=xd",
[]string{"pl", "en_GB"},
},
{
"",
nil,
},
}
for _, el := range tt {
got := ParseHeader(el.In)
if !reflect.DeepEqual(got, el.Out) {
t.Errorf("got %v want %v", got, el.Out)
}
}
}

112
modules/locale/parser.go Normal file
View File

@@ -0,0 +1,112 @@
package locale
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
type po struct {
Headers map[string]string
Translations map[string]string
}
// A minimal .po file parser.
func parse(s *bufio.Scanner) *po {
p := &po{
Headers: make(map[string]string, 20),
Translations: make(map[string]string, 500),
}
const (
currentNothing = iota
currentMsgID
currentMsgString
)
var current byte
var currentID string
var currentString string
for s.Scan() {
line := strings.TrimSpace(s.Text())
if line == "" || line[0] == '#' {
continue
}
switch {
case strings.HasPrefix(line, "msgid "):
unq, err := strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(line, "msgid")))
if err != nil {
fmt.Println(line)
fmt.Println(err)
return nil
}
if current != currentNothing && currentID == "" && currentString != "" {
for _, h := range strings.Split(currentString, "\n") {
if h == "" {
continue
}
parts := strings.SplitN(h, ": ", 2)
if len(parts) != 2 {
continue
}
p.Headers[parts[0]] = parts[1]
}
} else {
p.Translations[currentID] = currentString
}
currentID = unq
current = currentMsgID
case strings.HasPrefix(line, "msgstr "):
unq, err := strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(line, "msgstr")))
if err != nil {
fmt.Println(line)
fmt.Println(err)
return nil
}
currentString = unq
current = currentMsgString
case strings.HasPrefix(line, "msgid_plural "), strings.HasPrefix(line, "msgstr["):
// We currently don't support plural in the Go parser, at least hold it off 'til it's needed.
current = currentNothing
default:
if current == currentNothing {
continue
}
unq, err := strconv.Unquote(strings.TrimSpace(line))
if err != nil {
fmt.Println(line)
fmt.Println(err)
return nil
}
switch current {
case currentMsgID:
currentID += unq
case currentMsgString:
currentString += unq
}
}
}
if current == currentMsgString {
p.Translations[currentID] = currentString
}
return p
}
func parseFile(fName string) (*po, error) {
f, err := os.Open(fName)
if err != nil {
return nil, err
}
defer f.Close()
s := bufio.NewScanner(f)
p := parse(s)
return p, nil
}

File diff suppressed because it is too large Load Diff