2023-11-17 09:23:57 +05:30
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
2024-09-22 18:58:03 +05:30
|
|
|
"encoding/json"
|
2023-11-17 09:23:57 +05:30
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2024-06-09 06:54:15 +05:30
|
|
|
"github.com/getsentry/sentry-go"
|
2023-11-17 09:23:57 +05:30
|
|
|
_ "github.com/go-sql-driver/mysql"
|
|
|
|
"github.com/julienschmidt/httprouter"
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
"github.com/urfave/negroni"
|
|
|
|
)
|
|
|
|
|
|
|
|
func main() {
|
2024-06-10 07:10:52 +05:30
|
|
|
err := sentry.Init(sentry.ClientOptions{
|
|
|
|
Integrations: func(integrations []sentry.Integration) []sentry.Integration {
|
|
|
|
nDeleted := 0
|
|
|
|
for i, integration := range integrations {
|
|
|
|
if integration.Name() == "Modules" {
|
|
|
|
integrations[i] = integrations[len(integrations)-(nDeleted+1)]
|
|
|
|
nDeleted++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return integrations[:len(integrations)-nDeleted]
|
|
|
|
},
|
|
|
|
})
|
2024-06-09 06:54:15 +05:30
|
|
|
if err != nil {
|
2024-06-10 07:10:52 +05:30
|
|
|
panic(fmt.Errorf("unable to initialize Sentry: %w", err))
|
2024-06-09 06:54:15 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
defer sentry.Flush(2 * time.Second)
|
|
|
|
|
2023-11-17 09:23:57 +05:30
|
|
|
viper.AutomaticEnv()
|
|
|
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
|
|
|
2023-11-17 10:09:45 +05:30
|
|
|
viper.SetDefault("http.host", "0.0.0.0")
|
2023-11-17 09:23:57 +05:30
|
|
|
viper.SetDefault("http.port", 8080)
|
|
|
|
|
|
|
|
viper.SetDefault("mysql.user", "root")
|
|
|
|
viper.SetDefault("mysql.password", "")
|
|
|
|
viper.SetDefault("mysql.host", "localhost")
|
|
|
|
viper.SetDefault("mysql.port", 3306)
|
|
|
|
viper.SetDefault("mysql.protocol", "tcp")
|
|
|
|
viper.SetDefault("mysql.db", "")
|
|
|
|
|
|
|
|
db, err := sql.Open("mysql", fmt.Sprintf(
|
|
|
|
"%s:%s@%s(%s:%d)/%s",
|
|
|
|
viper.GetString("mysql.user"),
|
|
|
|
viper.GetString("mysql.password"),
|
|
|
|
viper.GetString("mysql.protocol"),
|
|
|
|
viper.GetString("mysql.host"),
|
|
|
|
viper.GetInt("mysql.port"),
|
|
|
|
viper.GetString("mysql.db"),
|
|
|
|
))
|
|
|
|
if err != nil {
|
2024-06-10 07:10:52 +05:30
|
|
|
panic(fmt.Errorf("invalid MySQL connection params: %w", err))
|
2023-11-17 09:23:57 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
db.SetConnMaxLifetime(time.Minute * 3)
|
|
|
|
db.SetMaxOpenConns(10)
|
|
|
|
db.SetMaxIdleConns(10)
|
|
|
|
|
2024-09-22 18:58:03 +05:30
|
|
|
findUsernameByUuidStmt, err := db.Prepare("SELECT username FROM accounts WHERE uuid = ? LIMIT 1")
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Errorf("unable to prepare query: %w", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
findUuidAndCorrectUsernameByUsernameStmt, err := db.Prepare("SELECT uuid, username FROM accounts WHERE username = ? LIMIT 1")
|
2023-11-17 09:23:57 +05:30
|
|
|
if err != nil {
|
2024-06-10 07:10:52 +05:30
|
|
|
panic(fmt.Errorf("unable to prepare query: %w", err))
|
2023-11-17 09:23:57 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
router := httprouter.New()
|
|
|
|
router.GET("/api/minecraft/session/profile/:uuid", logRequestHandler(func(
|
|
|
|
response http.ResponseWriter,
|
|
|
|
request *http.Request,
|
|
|
|
params httprouter.Params,
|
|
|
|
) {
|
|
|
|
uuid, err := formatUuid(params.ByName("uuid"))
|
|
|
|
if err != nil {
|
|
|
|
response.WriteHeader(204)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var username string
|
2024-09-22 18:58:03 +05:30
|
|
|
err = findUsernameByUuidStmt.QueryRow(uuid).Scan(&username)
|
2023-11-17 09:23:57 +05:30
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
|
|
response.WriteHeader(204)
|
|
|
|
return
|
|
|
|
} else if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2024-06-11 06:12:53 +05:30
|
|
|
profileUrl := fmt.Sprintf("http://skinsystem.ely.by/profile/%s?onUnknownProfileRespondWithUuid=%s", username, uuid)
|
2023-11-17 09:23:57 +05:30
|
|
|
if request.FormValue("unsigned") == "false" {
|
2024-06-11 06:12:53 +05:30
|
|
|
profileUrl += "&unsigned=false"
|
2023-11-17 09:23:57 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
profileResp, err := http.Get(profileUrl)
|
|
|
|
if err != nil {
|
2024-06-09 06:54:15 +05:30
|
|
|
sentry.CaptureException(fmt.Errorf("received invalid response from Chrly service: %w", err))
|
2023-11-17 09:23:57 +05:30
|
|
|
response.WriteHeader(500)
|
2024-06-09 06:54:15 +05:30
|
|
|
|
2023-11-17 09:23:57 +05:30
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-06-11 06:12:53 +05:30
|
|
|
if profileResp.StatusCode != http.StatusOK {
|
2024-06-09 06:54:15 +05:30
|
|
|
sentry.CaptureException(fmt.Errorf("received unsuccessful response code from Chrly servicer: %d. error is %w", profileResp.StatusCode, err))
|
2023-12-21 23:35:26 +05:30
|
|
|
response.WriteHeader(500)
|
2024-06-09 06:54:15 +05:30
|
|
|
|
2023-12-21 23:35:26 +05:30
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-11-17 09:23:57 +05:30
|
|
|
response.Header().Set("Content-Type", "application/json")
|
2023-12-21 23:35:26 +05:30
|
|
|
response.WriteHeader(profileResp.StatusCode)
|
2023-11-17 09:23:57 +05:30
|
|
|
|
|
|
|
_, err = io.Copy(response, profileResp.Body)
|
|
|
|
if err != nil {
|
2024-06-09 06:54:15 +05:30
|
|
|
sentry.CaptureException(fmt.Errorf("unable to write response body: %w", err))
|
2023-11-17 09:23:57 +05:30
|
|
|
response.WriteHeader(500)
|
2024-06-09 06:54:15 +05:30
|
|
|
|
2023-11-17 09:23:57 +05:30
|
|
|
return
|
|
|
|
}
|
|
|
|
}))
|
2024-09-22 18:58:03 +05:30
|
|
|
router.GET("/api/mojang/profiles/:username", logRequestHandler(func(
|
|
|
|
response http.ResponseWriter,
|
|
|
|
request *http.Request,
|
|
|
|
params httprouter.Params,
|
|
|
|
) {
|
|
|
|
var username, uuid string
|
|
|
|
err = findUuidAndCorrectUsernameByUsernameStmt.QueryRow(params.ByName("username")).Scan(&uuid, &username)
|
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
|
|
response.WriteHeader(204)
|
|
|
|
return
|
|
|
|
} else if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
response.Header().Set("Content-Type", "application/json")
|
|
|
|
response.WriteHeader(http.StatusOK)
|
|
|
|
|
|
|
|
result, err := json.Marshal(map[string]string{
|
|
|
|
"id": strings.ReplaceAll(uuid, "-", ""),
|
|
|
|
"name": username,
|
|
|
|
})
|
|
|
|
|
|
|
|
_, err = response.Write(result)
|
|
|
|
if err != nil {
|
|
|
|
sentry.CaptureException(fmt.Errorf("unable to write response body: %w", err))
|
|
|
|
response.WriteHeader(500)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}))
|
2023-11-17 09:23:57 +05:30
|
|
|
|
|
|
|
err = http.ListenAndServe(fmt.Sprintf("%s:%d", viper.GetString("http.host"), viper.GetInt("http.port")), router)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-22 00:04:01 +05:30
|
|
|
var InvalidUuid = errors.New("invalid uuid")
|
|
|
|
|
2023-11-17 09:23:57 +05:30
|
|
|
func formatUuid(input string) (string, error) {
|
|
|
|
uuid := strings.ReplaceAll(input, "-", "")
|
|
|
|
if len(uuid) != 32 {
|
2023-12-22 00:04:01 +05:30
|
|
|
return "", InvalidUuid
|
2023-11-17 09:23:57 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
return uuid[0:8] + "-" + uuid[8:12] + "-" + uuid[12:16] + "-" + uuid[16:20] + "-" + uuid[20:], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func logRequestHandler(h httprouter.Handle) httprouter.Handle {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
|
|
|
lrw := negroni.NewResponseWriter(w)
|
|
|
|
|
|
|
|
h(lrw, r, p) // Call the original handler
|
|
|
|
log.Printf("\"%s %s\" %d", r.Method, r.URL.String(), lrw.Status())
|
|
|
|
}
|
|
|
|
}
|