replace zxq.co/ripple/hanayo
This commit is contained in:
18
vendor/zxq.co/ripple/rippleapi/app/websockets/entry.go
vendored
Normal file
18
vendor/zxq.co/ripple/rippleapi/app/websockets/entry.go
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
package websockets
|
||||
|
||||
import (
|
||||
"github.com/leavengood/websocket"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
var upgrader = websocket.FastHTTPUpgrader{
|
||||
Handler: handler,
|
||||
CheckOrigin: func(ctx *fasthttp.RequestCtx) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// WebsocketV1Entry upgrades a connection to a websocket.
|
||||
func WebsocketV1Entry(ctx *fasthttp.RequestCtx) {
|
||||
upgrader.UpgradeHandler(ctx)
|
||||
}
|
101
vendor/zxq.co/ripple/rippleapi/app/websockets/identify.go
vendored
Normal file
101
vendor/zxq.co/ripple/rippleapi/app/websockets/identify.go
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
package websockets
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"database/sql"
|
||||
|
||||
"zxq.co/ripple/rippleapi/common"
|
||||
)
|
||||
|
||||
type websocketUser struct {
|
||||
ID int `json:"id"`
|
||||
Username string `json:"username"`
|
||||
UserPrivileges uint64 `json:"user_privileges"`
|
||||
TokenPrivileges uint64 `json:"token_privileges"`
|
||||
ApplicationID *string `json:"application_id"`
|
||||
}
|
||||
|
||||
type identifyMessage struct {
|
||||
Token string `json:"token"`
|
||||
IsBearer bool `json:"is_bearer"`
|
||||
}
|
||||
|
||||
// Identify sets the identity of the user.
|
||||
func Identify(c *conn, message incomingMessage) {
|
||||
var idMsg identifyMessage
|
||||
err := json.Unmarshal(message.Data, &idMsg)
|
||||
if err != nil {
|
||||
c.WriteJSON(TypeInvalidMessage, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var wsu websocketUser
|
||||
if idMsg.IsBearer {
|
||||
err = getBearerToken(idMsg.Token, &wsu)
|
||||
} else {
|
||||
err = db.Get(&wsu, `
|
||||
SELECT
|
||||
t.user as id, t.privileges as token_privileges,
|
||||
u.username, u.privileges as user_privileges
|
||||
FROM tokens t
|
||||
INNER JOIN users u ON t.user = u.id
|
||||
WHERE t.token = ?`, fmt.Sprintf("%x", md5.Sum([]byte(idMsg.Token))))
|
||||
}
|
||||
|
||||
switch err {
|
||||
case nil:
|
||||
break
|
||||
case sql.ErrNoRows:
|
||||
c.WriteJSON(TypeNotFound, nil)
|
||||
return
|
||||
default:
|
||||
common.WSErr(err)
|
||||
c.WriteJSON(TypeUnexpectedError, nil)
|
||||
return
|
||||
}
|
||||
|
||||
wsu.TokenPrivileges = uint64(
|
||||
common.Privileges(wsu.TokenPrivileges).CanOnly(
|
||||
common.UserPrivileges(wsu.UserPrivileges),
|
||||
),
|
||||
)
|
||||
|
||||
c.Mtx.Lock()
|
||||
c.User = &wsu
|
||||
c.Mtx.Unlock()
|
||||
|
||||
c.WriteJSON(TypeIdentified, wsu)
|
||||
}
|
||||
|
||||
func getBearerToken(token string, wsu *websocketUser) error {
|
||||
var x struct {
|
||||
Client string
|
||||
Scope string
|
||||
Extra int
|
||||
}
|
||||
err := db.Get(&x, "SELECT client, scope, extra FROM osin_access WHERE access_token = ? LIMIT 1", fmt.Sprintf("%x", sha256.Sum256([]byte(token))))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var userInfo struct {
|
||||
Username string
|
||||
Privileges uint64
|
||||
}
|
||||
err = db.Get(&userInfo, "SELECT username, privileges FROM users WHERE id = ? LIMIT 1", x.Extra)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wsu.ApplicationID = &x.Client
|
||||
wsu.ID = x.Extra
|
||||
wsu.Username = userInfo.Username
|
||||
wsu.UserPrivileges = userInfo.Privileges
|
||||
wsu.TokenPrivileges = uint64(common.OAuthPrivileges(x.Scope))
|
||||
|
||||
return nil
|
||||
}
|
125
vendor/zxq.co/ripple/rippleapi/app/websockets/main_handler.go
vendored
Normal file
125
vendor/zxq.co/ripple/rippleapi/app/websockets/main_handler.go
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
package websockets
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/leavengood/websocket"
|
||||
)
|
||||
|
||||
var stepNumber uint64
|
||||
|
||||
func handler(rawConn *websocket.Conn) {
|
||||
defer catchPanic()
|
||||
defer rawConn.Close()
|
||||
|
||||
step := atomic.AddUint64(&stepNumber, 1)
|
||||
|
||||
// 5 is a security margin in case
|
||||
if step == (1<<10 - 5) {
|
||||
atomic.StoreUint64(&stepNumber, 0)
|
||||
}
|
||||
|
||||
c := &conn{
|
||||
rawConn,
|
||||
sync.Mutex{},
|
||||
step | uint64(time.Now().UnixNano()<<10),
|
||||
false,
|
||||
nil,
|
||||
}
|
||||
|
||||
c.WriteJSON(TypeConnected, nil)
|
||||
|
||||
defer cleanup(c.ID)
|
||||
|
||||
for {
|
||||
var i incomingMessage
|
||||
err := c.Conn.ReadJSON(&i)
|
||||
if _, ok := err.(*websocket.CloseError); ok {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.WriteJSON(TypeInvalidMessage, err.Error())
|
||||
continue
|
||||
}
|
||||
f, ok := messageHandler[i.Type]
|
||||
if !ok {
|
||||
c.WriteJSON(TypeInvalidMessage, "invalid message type")
|
||||
continue
|
||||
}
|
||||
if f != nil {
|
||||
f(c, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
Conn *websocket.Conn
|
||||
Mtx sync.Mutex
|
||||
ID uint64
|
||||
RestrictedVisible bool
|
||||
User *websocketUser
|
||||
}
|
||||
|
||||
func (c *conn) WriteJSON(t string, data interface{}) error {
|
||||
c.Mtx.Lock()
|
||||
err := c.Conn.WriteJSON(newMessage(t, data))
|
||||
c.Mtx.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
var messageHandler = map[string]func(c *conn, message incomingMessage){
|
||||
TypeSubscribeScores: SubscribeScores,
|
||||
TypeSubscribeMultiMatches: SubscribeMultiMatches,
|
||||
TypeSetRestrictedVisibility: SetRestrictedVisibility,
|
||||
TypeIdentify: Identify,
|
||||
TypePing: pingHandler,
|
||||
}
|
||||
|
||||
// Server Message Types
|
||||
const (
|
||||
TypeConnected = "connected"
|
||||
TypeInvalidMessage = "invalid_message_type"
|
||||
TypeUnexpectedError = "unexpected_error"
|
||||
TypeNotFound = "not_found"
|
||||
TypeSubscribedToScores = "subscribed_to_scores"
|
||||
TypeNewScore = "new_score"
|
||||
TypeSubscribedToMultiMatches = "subscribed_mp_complete_match"
|
||||
TypeNewMatch = "new_completed_match"
|
||||
TypeIdentified = "identified"
|
||||
TypeRestrictedVisibilitySet = "restricted_visibility_set"
|
||||
TypePong = "pong"
|
||||
)
|
||||
|
||||
// Client Message Types
|
||||
const (
|
||||
TypeSubscribeScores = "subscribe_scores"
|
||||
TypeSubscribeMultiMatches = "subscribe_mp_complete_match"
|
||||
TypeIdentify = "identify"
|
||||
TypeSetRestrictedVisibility = "set_restricted_visibility"
|
||||
TypePing = "ping"
|
||||
)
|
||||
|
||||
func pingHandler(c *conn, message incomingMessage) {
|
||||
c.WriteJSON(TypePong, nil)
|
||||
}
|
||||
|
||||
// Message is the wrapped information for a message sent to the client.
|
||||
type Message struct {
|
||||
Type string `json:"type"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func newMessage(t string, data interface{}) Message {
|
||||
return Message{
|
||||
Type: t,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
type incomingMessage struct {
|
||||
Type string `json:"type"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
57
vendor/zxq.co/ripple/rippleapi/app/websockets/multi.go
vendored
Normal file
57
vendor/zxq.co/ripple/rippleapi/app/websockets/multi.go
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package websockets
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// SubscribeMultiMatches subscribes to receiving information from completed
|
||||
// games in multiplayer matches.
|
||||
func SubscribeMultiMatches(c *conn, message incomingMessage) {
|
||||
multiSubscriptionsMtx.Lock()
|
||||
var found bool
|
||||
for _, el := range multiSubscriptions {
|
||||
if el.ID == c.ID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// if it was not found, we need to add it
|
||||
if !found {
|
||||
multiSubscriptions = append(multiSubscriptions, c)
|
||||
}
|
||||
multiSubscriptionsMtx.Unlock()
|
||||
|
||||
c.WriteJSON(TypeSubscribedToMultiMatches, nil)
|
||||
}
|
||||
|
||||
var multiSubscriptions []*conn
|
||||
var multiSubscriptionsMtx = new(sync.RWMutex)
|
||||
|
||||
func matchRetriever() {
|
||||
ps, err := red.Subscribe("api:mp_complete_match")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
for {
|
||||
msg, err := ps.ReceiveMessage()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
go handleNewMultiGame(msg.Payload)
|
||||
}
|
||||
}
|
||||
|
||||
func handleNewMultiGame(payload string) {
|
||||
defer catchPanic()
|
||||
multiSubscriptionsMtx.RLock()
|
||||
cp := make([]*conn, len(multiSubscriptions))
|
||||
copy(cp, multiSubscriptions)
|
||||
multiSubscriptionsMtx.RUnlock()
|
||||
|
||||
for _, el := range cp {
|
||||
el.WriteJSON(TypeNewMatch, json.RawMessage(payload))
|
||||
}
|
||||
}
|
31
vendor/zxq.co/ripple/rippleapi/app/websockets/restricted_visibility.go
vendored
Normal file
31
vendor/zxq.co/ripple/rippleapi/app/websockets/restricted_visibility.go
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package websockets
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"zxq.co/ripple/rippleapi/common"
|
||||
)
|
||||
|
||||
// SetRestrictedVisibility sets whether the information of restricted users
|
||||
// can be seen.
|
||||
func SetRestrictedVisibility(c *conn, message incomingMessage) {
|
||||
var visibility bool
|
||||
|
||||
err := json.Unmarshal(message.Data, &visibility)
|
||||
if err != nil {
|
||||
c.WriteJSON(TypeInvalidMessage, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var userIsManager bool
|
||||
if c.User != nil && (c.User.UserPrivileges&uint64(common.AdminPrivilegeManageUsers) > 0) {
|
||||
userIsManager = true
|
||||
}
|
||||
|
||||
c.Mtx.Lock()
|
||||
visibility = visibility && userIsManager
|
||||
c.RestrictedVisible = visibility
|
||||
c.Mtx.Unlock()
|
||||
|
||||
c.WriteJSON(TypeRestrictedVisibilitySet, visibility)
|
||||
}
|
173
vendor/zxq.co/ripple/rippleapi/app/websockets/scores.go
vendored
Normal file
173
vendor/zxq.co/ripple/rippleapi/app/websockets/scores.go
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
package websockets
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gopkg.in/thehowl/go-osuapi.v1"
|
||||
"zxq.co/ripple/rippleapi/app/v1"
|
||||
"zxq.co/ripple/rippleapi/common"
|
||||
"zxq.co/x/getrank"
|
||||
)
|
||||
|
||||
type subscribeScoresUser struct {
|
||||
User int `json:"user"`
|
||||
Modes []int `json:"modes"`
|
||||
}
|
||||
|
||||
// SubscribeScores subscribes a connection to score updates.
|
||||
func SubscribeScores(c *conn, message incomingMessage) {
|
||||
var ssu []subscribeScoresUser
|
||||
err := json.Unmarshal(message.Data, &ssu)
|
||||
if err != nil {
|
||||
c.WriteJSON(TypeInvalidMessage, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
scoreSubscriptionsMtx.Lock()
|
||||
|
||||
var found bool
|
||||
for idx, el := range scoreSubscriptions {
|
||||
// already exists, change the users
|
||||
if el.Conn.ID == c.ID {
|
||||
found = true
|
||||
scoreSubscriptions[idx].Users = ssu
|
||||
}
|
||||
}
|
||||
|
||||
// if it was not found, we need to add it
|
||||
if !found {
|
||||
scoreSubscriptions = append(scoreSubscriptions, scoreSubscription{c, ssu})
|
||||
}
|
||||
|
||||
scoreSubscriptionsMtx.Unlock()
|
||||
|
||||
c.WriteJSON(TypeSubscribedToScores, ssu)
|
||||
}
|
||||
|
||||
type scoreSubscription struct {
|
||||
Conn *conn
|
||||
Users []subscribeScoresUser
|
||||
}
|
||||
|
||||
var scoreSubscriptions []scoreSubscription
|
||||
var scoreSubscriptionsMtx = new(sync.RWMutex)
|
||||
|
||||
func scoreRetriever() {
|
||||
ps, err := red.Subscribe("api:score_submission")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
for {
|
||||
msg, err := ps.ReceiveMessage()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
go handleNewScore(msg.Payload)
|
||||
}
|
||||
}
|
||||
|
||||
type scoreUser struct {
|
||||
UserID int `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Privileges uint64 `json:"privileges"`
|
||||
}
|
||||
|
||||
type score struct {
|
||||
v1.Score
|
||||
scoreUser
|
||||
}
|
||||
|
||||
type scoreJSON struct {
|
||||
v1.Score
|
||||
UserID int `json:"user_id"`
|
||||
User scoreUser `json:"user"`
|
||||
}
|
||||
|
||||
func handleNewScore(id string) {
|
||||
defer catchPanic()
|
||||
var s score
|
||||
err := db.Get(&s, `
|
||||
SELECT
|
||||
s.id, s.beatmap_md5, s.score, s.max_combo, s.full_combo, s.mods,
|
||||
s.300_count, s.100_count, s.50_count, s.gekis_count, s.katus_count, s.misses_count,
|
||||
s.time, s.play_mode, s.accuracy, s.pp, s.completed, s.userid AS user_id,
|
||||
u.username, u.privileges
|
||||
FROM scores s
|
||||
INNER JOIN users u ON s.userid = u.id
|
||||
WHERE s.id = ?`, id)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
s.Rank = strings.ToUpper(getrank.GetRank(
|
||||
osuapi.Mode(s.PlayMode),
|
||||
osuapi.Mods(s.Mods),
|
||||
s.Accuracy,
|
||||
s.Count300,
|
||||
s.Count100,
|
||||
s.Count50,
|
||||
s.CountMiss,
|
||||
))
|
||||
|
||||
sj := scoreJSON{
|
||||
Score: s.Score,
|
||||
UserID: s.UserID,
|
||||
User: s.scoreUser,
|
||||
}
|
||||
|
||||
scoreSubscriptionsMtx.RLock()
|
||||
cp := make([]scoreSubscription, len(scoreSubscriptions))
|
||||
copy(cp, scoreSubscriptions)
|
||||
scoreSubscriptionsMtx.RUnlock()
|
||||
|
||||
for _, el := range cp {
|
||||
if len(el.Users) > 0 && !scoreUserValid(el.Users, sj) {
|
||||
continue
|
||||
}
|
||||
|
||||
if sj.User.Privileges&3 != 3 && !el.Conn.RestrictedVisible {
|
||||
continue
|
||||
}
|
||||
|
||||
el.Conn.WriteJSON(TypeNewScore, sj)
|
||||
}
|
||||
}
|
||||
|
||||
func scoreUserValid(users []subscribeScoresUser, s scoreJSON) bool {
|
||||
for _, u := range users {
|
||||
if u.User == s.UserID {
|
||||
if len(u.Modes) > 0 {
|
||||
if !inModes(u.Modes, s.PlayMode) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func inModes(modes []int, i int) bool {
|
||||
for _, m := range modes {
|
||||
if m == i {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func catchPanic() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
switch r := r.(type) {
|
||||
case error:
|
||||
common.WSErr(r)
|
||||
default:
|
||||
fmt.Println("PANIC", r)
|
||||
}
|
||||
}
|
||||
}
|
42
vendor/zxq.co/ripple/rippleapi/app/websockets/websockets.go
vendored
Normal file
42
vendor/zxq.co/ripple/rippleapi/app/websockets/websockets.go
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// Package websockets implements functionality related to the API websockets.
|
||||
package websockets
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"gopkg.in/redis.v5"
|
||||
)
|
||||
|
||||
var (
|
||||
red *redis.Client
|
||||
db *sqlx.DB
|
||||
)
|
||||
|
||||
// Start begins websocket functionality
|
||||
func Start(r *redis.Client, _db *sqlx.DB) error {
|
||||
red = r
|
||||
db = _db
|
||||
go scoreRetriever()
|
||||
go matchRetriever()
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanup(connID uint64) {
|
||||
scoreSubscriptionsMtx.Lock()
|
||||
for idx, el := range scoreSubscriptions {
|
||||
if el.Conn.ID == connID {
|
||||
scoreSubscriptions[idx] = scoreSubscriptions[len(scoreSubscriptions)-1]
|
||||
scoreSubscriptions = scoreSubscriptions[:len(scoreSubscriptions)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
scoreSubscriptionsMtx.Unlock()
|
||||
multiSubscriptionsMtx.Lock()
|
||||
for idx, el := range multiSubscriptions {
|
||||
if el.ID == connID {
|
||||
multiSubscriptions[idx] = multiSubscriptions[len(multiSubscriptions)-1]
|
||||
multiSubscriptions = multiSubscriptions[:len(multiSubscriptions)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
multiSubscriptionsMtx.Unlock()
|
||||
}
|
Reference in New Issue
Block a user