613 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			613 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package main
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"html/template"
 | |
| 	"io/ioutil"
 | |
| 	"math"
 | |
| 	"math/rand"
 | |
| 	"net/http"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/dustin/go-humanize"
 | |
| 	"github.com/gin-gonic/gin"
 | |
| 	"github.com/jmoiron/sqlx"
 | |
| 	"github.com/russross/blackfriday"
 | |
| 	"github.com/thehowl/qsql"
 | |
| 	"golang.org/x/oauth2"
 | |
| 	"zxq.co/ripple/go-discord-oauth"
 | |
| 	"github.com/osuYozora/hanayo/modules/bbcode"
 | |
| 	"github.com/osuYozora/hanayo/modules/btcaddress"
 | |
| 	"github.com/osuYozora/hanayo/modules/doc"
 | |
| 	"github.com/osuYozora/hanayo/modules/fa-semantic-mappings"
 | |
| 	"zxq.co/ripple/playstyle"
 | |
| 	"zxq.co/ripple/rippleapi/common"
 | |
| )
 | |
| 
 | |
| // funcMap contains useful functions for the various templates.
 | |
| var funcMap = template.FuncMap{
 | |
| 	// html disables HTML escaping on the values it is given.
 | |
| 	"html": func(value interface{}) template.HTML {
 | |
| 		return template.HTML(fmt.Sprint(value))
 | |
| 	},
 | |
| 	// avatars is a function returning the configuration constant AvatarURL
 | |
| 	"config": func(key string) interface{} {
 | |
| 		return configMap[key]
 | |
| 	},
 | |
| 	// navbarItem is a function to generate an item in the navbar.
 | |
| 	// The reason why this exists is that I wanted to have the currently
 | |
| 	// selected element in the navbar having the "active" class.
 | |
| 	"navbarItem": func(currentPath, name, path string) template.HTML {
 | |
| 		var act string
 | |
| 		if path == currentPath {
 | |
| 			act = "active "
 | |
| 		}
 | |
| 		return template.HTML(fmt.Sprintf(`<a class="%sitem" href="%s">%s</a>`, act, path, name))
 | |
| 	},
 | |
| 	// curryear returns the current year.
 | |
| 	"curryear": func() int {
 | |
| 		return time.Now().Year()
 | |
| 	},
 | |
| 	// hasAdmin returns, based on the user's privileges, whether they should be
 | |
| 	// able to see the RAP button (aka AdminPrivilegeAccessRAP).
 | |
| 	"hasAdmin": func(privs common.UserPrivileges) bool {
 | |
| 		return privs&common.AdminPrivilegeAccessRAP > 0
 | |
| 	},
 | |
| 	// isRAP returns whether the current page is in RAP.
 | |
| 	"isRAP": func(p string) bool {
 | |
| 		parts := strings.Split(p, "/")
 | |
| 		return len(parts) > 1 && parts[1] == "admin"
 | |
| 	},
 | |
| 	// favMode is just a helper function for user profiles. Basically checks
 | |
| 	// whether a float and an int are ==, and if they are it will return "active ",
 | |
| 	// so that the element in the mode menu of a user profile can be marked as
 | |
| 	// the current active element.
 | |
| 	"favMode": func(favMode float64, current int) string {
 | |
| 		if int(favMode) == current {
 | |
| 			return "active "
 | |
| 		}
 | |
| 		return ""
 | |
| 	},
 | |
| 	// slice generates a []interface{} with the elements it is given.
 | |
| 	// useful to iterate over some elements, like this:
 | |
| 	//  {{ range slice 1 2 3 }}{{ . }}{{ end }}
 | |
| 	"slice": func(els ...interface{}) []interface{} {
 | |
| 		return els
 | |
| 	},
 | |
| 	// int converts a float/int to an int.
 | |
| 	"int": func(f interface{}) int {
 | |
| 		if f == nil {
 | |
| 			return 0
 | |
| 		}
 | |
| 		switch f := f.(type) {
 | |
| 		case int:
 | |
| 			return f
 | |
| 		case float64:
 | |
| 			return int(f)
 | |
| 		case float32:
 | |
| 			return int(f)
 | |
| 		}
 | |
| 		return 0
 | |
| 	},
 | |
| 	// float converts an int to a float.
 | |
| 	"float": func(i int) float64 {
 | |
| 		return float64(i)
 | |
| 	},
 | |
| 	// atoi converts a string to an int and then a float64.
 | |
| 	// If s is not an actual int, it returns nil.
 | |
| 	"atoi": func(s string) interface{} {
 | |
| 		i, err := strconv.Atoi(s)
 | |
| 		if err != nil {
 | |
| 			return nil
 | |
| 		}
 | |
| 		return float64(i)
 | |
| 	},
 | |
| 	// atoint is like atoi but returns always an int.
 | |
| 	"atoint": func(s string) int {
 | |
| 		i, _ := strconv.Atoi(s)
 | |
| 		return i
 | |
| 	},
 | |
| 	// parseUserpage compiles BBCode to HTML.
 | |
| 	"parseUserpage": func(s string) template.HTML {
 | |
| 		return template.HTML(bbcode.Compile(s))
 | |
| 	},
 | |
| 	// time converts a RFC3339 timestamp to the HTML element <time>.
 | |
| 	"time": func(s string) template.HTML {
 | |
| 		t, _ := time.Parse(time.RFC3339, s)
 | |
| 		return _time(s, t)
 | |
| 	},
 | |
| 	// time generates a time from a native Go time.Time
 | |
| 	"timeFromTime": func(t time.Time) template.HTML {
 | |
| 		return _time(t.Format(time.RFC3339), t)
 | |
| 	},
 | |
| 	// timeAddDay is basically time but adds a day.
 | |
| 	"timeAddDay": func(s string) template.HTML {
 | |
| 		t, _ := time.Parse(time.RFC3339, s)
 | |
| 		t = t.Add(time.Hour * 24)
 | |
| 		return _time(t.Format(time.RFC3339), t)
 | |
| 	},
 | |
| 	// nativeTime creates a native Go time.Time from a RFC3339 timestamp.
 | |
| 	"nativeTime": func(s string) time.Time {
 | |
| 		t, _ := time.Parse(time.RFC3339, s)
 | |
| 		return t
 | |
| 	},
 | |
| 	// band is a bitwise AND.
 | |
| 	"band": func(i1 int, i ...int) int {
 | |
| 		for _, el := range i {
 | |
| 			i1 &= el
 | |
| 		}
 | |
| 		return i1
 | |
| 	},
 | |
| 	// countryReadable converts a country's ISO name to its full name.
 | |
| 	"countryReadable": countryReadable,
 | |
| 	"country": func(s string, name bool) template.HTML {
 | |
| 		var c string
 | |
| 		if name {
 | |
| 			c = countryReadable(s)
 | |
| 			if c == "" {
 | |
| 				return ""
 | |
| 			}
 | |
| 		}
 | |
| 		return template.HTML(fmt.Sprintf(`<i class="%s flag"></i>%s`, strings.ToLower(s), c))
 | |
| 	},
 | |
| 	// humanize pretty-prints a float, e.g.
 | |
| 	//     humanize(1000) == "1,000"
 | |
| 	"humanize": func(f float64) string {
 | |
| 		return humanize.Commaf(f)
 | |
| 	},
 | |
| 	// levelPercent basically does this:
 | |
| 	//     levelPercent(56.23215) == "23"
 | |
| 	"levelPercent": func(l float64) string {
 | |
| 		_, f := math.Modf(l)
 | |
| 		f *= 100
 | |
| 		return fmt.Sprintf("%.0f", f)
 | |
| 	},
 | |
| 	// level removes the decimal part from a float.
 | |
| 	"level": func(l float64) string {
 | |
| 		i, _ := math.Modf(l)
 | |
| 		return fmt.Sprintf("%.0f", i)
 | |
| 	},
 | |
| 	// faIcon converts a fontawesome icon to a semantic ui icon.
 | |
| 	"faIcon": func(i string) string {
 | |
| 		classes := strings.Split(i, " ")
 | |
| 		for i, class := range classes {
 | |
| 			if v, ok := fasuimappings.Mappings[class]; ok {
 | |
| 				classes[i] = v
 | |
| 			}
 | |
| 		}
 | |
| 		return strings.Join(classes, " ")
 | |
| 	},
 | |
| 	// log fmt.Printf's something
 | |
| 	"log": fmt.Printf,
 | |
| 	// has returns whether priv1 has all 1 bits of priv2, aka priv1 & priv2 == priv2
 | |
| 	"has": func(priv1 interface{}, priv2 float64) bool {
 | |
| 		var p1 uint64
 | |
| 		switch priv1 := priv1.(type) {
 | |
| 		case common.UserPrivileges:
 | |
| 			p1 = uint64(priv1)
 | |
| 		case float64:
 | |
| 			p1 = uint64(priv1)
 | |
| 		case int:
 | |
| 			p1 = uint64(priv1)
 | |
| 		}
 | |
| 		return p1&uint64(priv2) == uint64(priv2)
 | |
| 	},
 | |
| 	// _range is like python range's.
 | |
| 	// If it is given 1 argument, it returns a []int containing numbers from 0
 | |
| 	// to x.
 | |
| 	// If it is given 2 arguments, it returns a []int containing numers from x
 | |
| 	// to y if x < y, from y to x if y < x.
 | |
| 	"_range": func(x int, y ...int) ([]int, error) {
 | |
| 		switch len(y) {
 | |
| 		case 0:
 | |
| 			r := make([]int, x)
 | |
| 			for i := range r {
 | |
| 				r[i] = i
 | |
| 			}
 | |
| 			return r, nil
 | |
| 		case 1:
 | |
| 			nums, up := pos(y[0] - x)
 | |
| 			r := make([]int, nums)
 | |
| 			for i := range r {
 | |
| 				if up {
 | |
| 					r[i] = i + x + 1
 | |
| 				} else {
 | |
| 					r[i] = i + y[0]
 | |
| 				}
 | |
| 			}
 | |
| 			if !up {
 | |
| 				// reverse r
 | |
| 				sort.Sort(sort.Reverse(sort.IntSlice(r)))
 | |
| 			}
 | |
| 			return r, nil
 | |
| 		}
 | |
| 		return nil, errors.New("y must be at maximum 1 parameter")
 | |
| 	},
 | |
| 	// blackfriday passes some markdown through blackfriday.
 | |
| 	"blackfriday": func(m string) template.HTML {
 | |
| 		// The reason of m[strings.Index...] is to remove the "header", where
 | |
| 		// there is the information about the file (namely, title, old_id and
 | |
| 		// reference_version)
 | |
| 		return template.HTML(
 | |
| 			blackfriday.Run(
 | |
| 				[]byte(
 | |
| 					m[strings.Index(m, "\n---\n")+5:],
 | |
| 				),
 | |
| 				blackfriday.WithExtensions(blackfriday.CommonExtensions),
 | |
| 			),
 | |
| 		)
 | |
| 	},
 | |
| 	// i is an inline if.
 | |
| 	// i (cond) (true) (false)
 | |
| 	"i": func(a bool, x, y interface{}) interface{} {
 | |
| 		if a {
 | |
| 			return x
 | |
| 		}
 | |
| 		return y
 | |
| 	},
 | |
| 	// modes returns an array containing all the modes (in their string representation).
 | |
| 	"modes": func() []string {
 | |
| 		return []string{
 | |
| 			"osu! standard",
 | |
| 			"Taiko",
 | |
| 			"Catch the Beat",
 | |
| 			"osu!mania",
 | |
| 		}
 | |
| 	},
 | |
| 	// _or is like or, but has only false and nil as its "falsey" values
 | |
| 	"_or": func(args ...interface{}) interface{} {
 | |
| 		for _, a := range args {
 | |
| 			if a != nil && a != false {
 | |
| 				return a
 | |
| 			}
 | |
| 		}
 | |
| 		return nil
 | |
| 	},
 | |
| 	// unixNano returns the UNIX timestamp of when hanayo was started in nanoseconds.
 | |
| 	"unixNano": func() string {
 | |
| 		return strconv.FormatInt(hanayoStarted, 10)
 | |
| 	},
 | |
| 	// playstyle returns the string representation of a playstyle.
 | |
| 	"playstyle": func(i float64, f *profileData) string {
 | |
| 		var parts []string
 | |
| 
 | |
| 		p := int(i)
 | |
| 		for k, v := range playstyle.Styles {
 | |
| 			if p&(1<<uint(k)) > 0 {
 | |
| 				parts = append(parts, f.T(v))
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return strings.Join(parts, ", ")
 | |
| 	},
 | |
| 	// arithmetic plus/minus
 | |
| 	"plus": func(i ...float64) float64 {
 | |
| 		var sum float64
 | |
| 		for _, i := range i {
 | |
| 			sum += i
 | |
| 		}
 | |
| 		return sum
 | |
| 	},
 | |
| 	"minus": func(i1 float64, i ...float64) float64 {
 | |
| 		for _, i := range i {
 | |
| 			i1 -= i
 | |
| 		}
 | |
| 		return i1
 | |
| 	},
 | |
| 	// rsin - Return Slice If Nil
 | |
| 	"rsin": func(i interface{}) interface{} {
 | |
| 		if i == nil {
 | |
| 			return []struct{}{}
 | |
| 		}
 | |
| 		return i
 | |
| 	},
 | |
| 	// loadjson loads a json file.
 | |
| 	"loadjson": func(jsonfile string) interface{} {
 | |
| 		f, err := ioutil.ReadFile(jsonfile)
 | |
| 		if err != nil {
 | |
| 			return nil
 | |
| 		}
 | |
| 		var x interface{}
 | |
| 		err = json.Unmarshal(f, &x)
 | |
| 		if err != nil {
 | |
| 			return nil
 | |
| 		}
 | |
| 		return x
 | |
| 	},
 | |
| 	// loadChangelog loads the changelog.
 | |
| 	"loadChangelog": loadChangelog,
 | |
| 	// teamJSON returns the data of team.json
 | |
| 	"teamJSON": func() map[string]interface{} {
 | |
| 		f, err := ioutil.ReadFile("team.json")
 | |
| 		if err != nil {
 | |
| 			return nil
 | |
| 		}
 | |
| 		var m map[string]interface{}
 | |
| 		json.Unmarshal(f, &m)
 | |
| 		return m
 | |
| 	},
 | |
| 	// in returns whether the first argument is in one of the following
 | |
| 	"in": func(a1 interface{}, as ...interface{}) bool {
 | |
| 		for _, a := range as {
 | |
| 			if a == a1 {
 | |
| 				return true
 | |
| 			}
 | |
| 		}
 | |
| 		return false
 | |
| 	},
 | |
| 	"capitalise": strings.Title,
 | |
| 	// servicePrefix gets the prefix of a service, like github.
 | |
| 	"servicePrefix": func(s string) string { return servicePrefixes[s] },
 | |
| 	// randomLogoColour picks a "random" colour for ripple's logo.
 | |
| 	"randomLogoColour": func() string {
 | |
| 		if rand.Int()%4 == 0 {
 | |
| 			return logoColours[rand.Int()%len(logoColours)]
 | |
| 		}
 | |
| 		return "pink"
 | |
| 	},
 | |
| 	// after checks whether a certain time is after time.Now()
 | |
| 	"after": func(s string) bool {
 | |
| 		t, _ := time.Parse(time.RFC3339, s)
 | |
| 		return t.After(time.Now())
 | |
| 	},
 | |
| 
 | |
| 	// qsql functions
 | |
| 	"qb": func(q string, p ...interface{}) map[string]qsql.String {
 | |
| 		r, err := qb.QueryRow(q, p...)
 | |
| 		if err != nil {
 | |
| 			fmt.Println(err)
 | |
| 		}
 | |
| 		if r == nil {
 | |
| 			return make(map[string]qsql.String, 0)
 | |
| 		}
 | |
| 		return r
 | |
| 	},
 | |
| 	"qba": func(q string, p ...interface{}) []map[string]qsql.String {
 | |
| 		r, err := qb.Query(q, p...)
 | |
| 		if err != nil {
 | |
| 			fmt.Println(err)
 | |
| 		}
 | |
| 		return r
 | |
| 	},
 | |
| 	"qbe": func(q string, p ...interface{}) int {
 | |
| 		i, _, err := qb.Exec(q, p...)
 | |
| 		if err != nil {
 | |
| 			fmt.Println(err)
 | |
| 		}
 | |
| 		return i
 | |
| 	},
 | |
| 
 | |
| 	// bget makes a request to the bancho api
 | |
| 	// https://docs.ripple.moe/docs/banchoapi/v1
 | |
| 	"bget": func(ept string, qs ...interface{}) map[string]interface{} {
 | |
| 		d, err := http.Get(fmt.Sprintf(config.BanchoAPI+"/api/v1/"+ept, qs...))
 | |
| 		if err != nil {
 | |
| 			return nil
 | |
| 		}
 | |
| 		x := make(map[string]interface{})
 | |
| 		data, _ := ioutil.ReadAll(d.Body)
 | |
| 		json.Unmarshal(data, &x)
 | |
| 		return x
 | |
| 	},
 | |
| 	// styles returns playstyle.Styles
 | |
| 	"styles": func() []string {
 | |
| 		return playstyle.Styles[:]
 | |
| 	},
 | |
| 	// shift shifts n1 by n2
 | |
| 	"shift": func(n1, n2 int) int {
 | |
| 		return n1 << uint(n2)
 | |
| 	},
 | |
| 	// calculateDonorPrice calculates the price of x donor months in euros.
 | |
| 	"calculateDonorPrice": func(a float64) string {
 | |
| 		return fmt.Sprintf("%.2f", math.Pow(a*30*0.2, 0.7))
 | |
| 	},
 | |
| 	// is2faEnabled checks 2fa is enabled for an user
 | |
| 	"is2faEnabled": is2faEnabled,
 | |
| 	// get2faConfirmationToken retrieves the current confirmation token for a certain user.
 | |
| 	"get2faConfirmationToken": get2faConfirmationToken,
 | |
| 	// csrfGenerate creates a csrf token input
 | |
| 	"csrfGenerate": func(u int) template.HTML {
 | |
| 		return template.HTML(`<input type="hidden" name="csrf" value="` + mustCSRFGenerate(u) + `">`)
 | |
| 	},
 | |
| 	// csrfURL creates a CSRF token for GET requests.
 | |
| 	"csrfURL": func(u int) template.URL {
 | |
| 		return template.URL("csrf=" + mustCSRFGenerate(u))
 | |
| 	},
 | |
| 	// systemSetting retrieves some information from the table system_settings
 | |
| 	"systemSettings": systemSettings,
 | |
| 	// authCodeURL gets the auth code for discord
 | |
| 	"authCodeURL": func(u int) string {
 | |
| 		return getDiscord().AuthCodeURL(mustCSRFGenerate(u))
 | |
| 	},
 | |
| 	// perc returns a percentage
 | |
| 	"perc": func(i, total float64) string {
 | |
| 		return fmt.Sprintf("%.0f", i/total*100)
 | |
| 	},
 | |
| 	// atLeastOne returns 1 if i < 1, or i otherwise.
 | |
| 	"atLeastOne": func(i int) int {
 | |
| 		if i < 1 {
 | |
| 			i = 1
 | |
| 		}
 | |
| 		return i
 | |
| 	},
 | |
| 	// ieForm fixes forms in IE/Trident being immensely fucked up. I hate microsoft.
 | |
| 	"ieForm": func(c *gin.Context) template.HTML {
 | |
| 		if !isIE(c.Request.UserAgent()) {
 | |
| 			return ""
 | |
| 		}
 | |
| 		return ieUnfucker
 | |
| 	},
 | |
| 	// version gets what's the current Hanayo version.
 | |
| 	"version": func() string {
 | |
| 		return version
 | |
| 	},
 | |
| 	"generateKey": generateKey,
 | |
| 	// getKeys gets the recovery 2fa keys for an user
 | |
| 	"getKeys": func(id int) []string {
 | |
| 		var keyRaw string
 | |
| 		db.Get(&keyRaw, "SELECT recovery FROM 2fa_totp WHERE userid = ?", id)
 | |
| 		s := make([]string, 0, 8)
 | |
| 		json.Unmarshal([]byte(keyRaw), &s)
 | |
| 		return s
 | |
| 	},
 | |
| 	// rediget retrieves a value from redis.
 | |
| 	"rediget": func(k string) string {
 | |
| 		x := rd.Get(k)
 | |
| 		if x == nil {
 | |
| 			return ""
 | |
| 		}
 | |
| 		if err := x.Err(); err != nil {
 | |
| 			fmt.Println(err)
 | |
| 		}
 | |
| 		return x.Val()
 | |
| 	},
 | |
| 	"getBitcoinAddress": btcaddress.Get,
 | |
| 	"languageInformation": func() []langInfo {
 | |
| 		return languageInformation
 | |
| 	},
 | |
| 	"languageInformationByNameShort": func(s string) langInfo {
 | |
| 		for _, lang := range languageInformation {
 | |
| 			if lang.NameShort == s {
 | |
| 				return lang
 | |
| 			}
 | |
| 		}
 | |
| 		return langInfo{}
 | |
| 	},
 | |
| 	"countryList": func(n int64) []string {
 | |
| 		return rd.ZRevRange("hanayo:country_list", 0, n-1).Val()
 | |
| 	},
 | |
| 	"documentationFiles": doc.GetDocs,
 | |
| 	"documentationData": func(slug string, language string) doc.File {
 | |
| 		if i, err := strconv.Atoi(slug); err == nil {
 | |
| 			slug = doc.SlugFromOldID(i)
 | |
| 		}
 | |
| 		return doc.GetFile(slug, language)
 | |
| 	},
 | |
| 	"privilegesToString": func(privs float64) string {
 | |
| 		return common.Privileges(privs).String()
 | |
| 	},
 | |
| 	"htmlescaper": template.HTMLEscaper,
 | |
| }
 | |
| 
 | |
| var localeLanguages = []string{"de", "pl", "it", "es", "ru", "fr", "nl", "ro", "fi", "sv", "vi", "ko"}
 | |
| 
 | |
| var hanayoStarted = time.Now().UnixNano()
 | |
| 
 | |
| var servicePrefixes = map[string]string{
 | |
| 	"github":  "https://github.com/",
 | |
| 	"twitter": "https://twitter.com/",
 | |
| 	"mail":    "mailto:",
 | |
| }
 | |
| 
 | |
| var logoColours = [...]string{
 | |
| 	"blue",
 | |
| 	"green",
 | |
| 	"orange",
 | |
| 	"red",
 | |
| }
 | |
| 
 | |
| // we still haven't got jquery when the script is here, so well shit.
 | |
| const ieUnfucker = `<input type="submit" class="ie" name="submit" value="submit">
 | |
| <script>
 | |
| var deferredToPageLoad = function() {
 | |
| 	$("button[form]").click(function() {
 | |
| 		$("form#" + $(this).attr("form") + " input.ie").click();
 | |
| 	});
 | |
| };
 | |
| </script>`
 | |
| 
 | |
| func pos(x int) (int, bool) {
 | |
| 	if x > 0 {
 | |
| 		return x, true
 | |
| 	}
 | |
| 	return x * -1, false
 | |
| }
 | |
| func _time(s string, t time.Time) template.HTML {
 | |
| 	return template.HTML(fmt.Sprintf(`<time class="timeago" datetime="%s">%v</time>`, s, t))
 | |
| }
 | |
| 
 | |
| // Fantastic IEs And Where To Find Them
 | |
| var ieUserAgentsContain = []string{
 | |
| 	"MSIE ",
 | |
| 	"Trident/",
 | |
| 	"Edge/",
 | |
| }
 | |
| 
 | |
| func isIE(s string) bool {
 | |
| 	for _, v := range ieUserAgentsContain {
 | |
| 		if strings.Contains(s, v) {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| type systemSetting struct {
 | |
| 	Name   string
 | |
| 	Int    int
 | |
| 	String string
 | |
| }
 | |
| 
 | |
| func systemSettings(names ...string) map[string]systemSetting {
 | |
| 	var settingsRaw []systemSetting
 | |
| 	q, p, _ := sqlx.In("SELECT name, value_int as `int`, value_string as `string` FROM system_settings WHERE name IN (?)", names)
 | |
| 	err := db.Select(&settingsRaw, q, p...)
 | |
| 	if err != nil {
 | |
| 		fmt.Println(err)
 | |
| 		return nil
 | |
| 	}
 | |
| 	settings := make(map[string]systemSetting, len(names))
 | |
| 	for _, s := range settingsRaw {
 | |
| 		settings[s.Name] = s
 | |
| 	}
 | |
| 	return settings
 | |
| }
 | |
| 
 | |
| func getDiscord() *oauth2.Config {
 | |
| 	return &oauth2.Config{
 | |
| 		ClientID:     config.DiscordOAuthID,
 | |
| 		ClientSecret: config.DiscordOAuthSecret,
 | |
| 		RedirectURL:  config.BaseURL + "/settings/discord/finish",
 | |
| 		Endpoint:     discordoauth.Endpoint,
 | |
| 		Scopes:       []string{"identify"},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func getLanguageFromGin(c *gin.Context) string {
 | |
| 	for _, l := range getLang(c) {
 | |
| 		if in(l, localeLanguages) {
 | |
| 			return l
 | |
| 		}
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| func init() {
 | |
| 	rand.Seed(time.Now().UnixNano())
 | |
| }
 | |
| 
 | |
| type langInfo struct {
 | |
| 	Name, CountryShort, NameShort string
 | |
| }
 | |
| 
 | |
| var languageInformation = []langInfo{
 | |
| 	{"Deutsch", "de", "de"},
 | |
| 	{"English (UK)", "gb", "en"},
 | |
| 	{"Español", "es", "es"},
 | |
| 	{"Français", "fr", "fr"},
 | |
| 	{"Italiano", "it", "it"},
 | |
| 	{"Nederlands", "nl", "nl"},
 | |
| 	{"Polski", "pl", "pl"},
 | |
| 	{"Русский", "ru", "ru"},
 | |
| 	{"Română", "ro", "ro"},
 | |
| 	{"Suomi", "fi", "fi"},
 | |
| 	{"Svenska", "se", "sv"},
 | |
| 	{"Tiếng Việt Nam", "vn", "vi"},
 | |
| 	{"한국어", "kr", "ko"},
 | |
| }
 |