mirror of https://github.com/synctv-org/synctv
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
617 lines
16 KiB
Go
617 lines
16 KiB
Go
package op
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"hash/crc32"
|
|
"sync/atomic"
|
|
|
|
"github.com/gorilla/websocket"
|
|
"github.com/synctv-org/synctv/internal/db"
|
|
"github.com/synctv-org/synctv/internal/model"
|
|
"github.com/synctv-org/synctv/internal/settings"
|
|
"github.com/synctv-org/synctv/utils"
|
|
"github.com/zijiren233/gencontainer/rwmap"
|
|
rtmps "github.com/zijiren233/livelib/server"
|
|
"github.com/zijiren233/stream"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
type Room struct {
|
|
model.Room
|
|
version uint32
|
|
current *current
|
|
initOnce utils.Once
|
|
hub *Hub
|
|
movies movies
|
|
members rwmap.RWMap[string, *model.RoomMember]
|
|
}
|
|
|
|
func (r *Room) lazyInitHub() {
|
|
r.initOnce.Do(func() {
|
|
r.hub = newHub(r.ID)
|
|
})
|
|
}
|
|
|
|
func (r *Room) PeopleNum() int64 {
|
|
if r.hub == nil {
|
|
return 0
|
|
}
|
|
return r.hub.PeopleNum()
|
|
}
|
|
|
|
func (r *Room) KickUser(userID string) error {
|
|
if r.hub == nil {
|
|
return nil
|
|
}
|
|
return r.hub.KickUser(userID)
|
|
}
|
|
|
|
func (r *Room) Broadcast(data Message, conf ...BroadcastConf) error {
|
|
if r.hub == nil {
|
|
return nil
|
|
}
|
|
return r.hub.Broadcast(data, conf...)
|
|
}
|
|
|
|
func (r *Room) SendToUser(user *User, data Message) error {
|
|
if r.hub == nil {
|
|
return nil
|
|
}
|
|
return r.hub.SendToUser(user.ID, data)
|
|
}
|
|
|
|
func (r *Room) GetChannel(channelName string) (*rtmps.Channel, error) {
|
|
return r.movies.GetChannel(channelName)
|
|
}
|
|
|
|
func (r *Room) close() {
|
|
if r.initOnce.Done() {
|
|
r.hub.Close()
|
|
r.movies.Close()
|
|
}
|
|
}
|
|
|
|
func (r *Room) Version() uint32 {
|
|
return atomic.LoadUint32(&r.version)
|
|
}
|
|
|
|
func (r *Room) CheckVersion(version uint32) bool {
|
|
return atomic.LoadUint32(&r.version) == version
|
|
}
|
|
|
|
func (r *Room) UpdateMovie(movieId string, movie *model.BaseMovie) error {
|
|
if r.current.current.MovieID == movieId {
|
|
return errors.New("cannot update current movie")
|
|
}
|
|
return r.movies.Update(movieId, movie)
|
|
}
|
|
|
|
func (r *Room) AddMovie(m *model.Movie) error {
|
|
m.RoomID = r.ID
|
|
return r.movies.AddMovie(m)
|
|
}
|
|
|
|
func (r *Room) AddMovies(movies []*model.Movie) error {
|
|
for _, m := range movies {
|
|
m.RoomID = r.ID
|
|
}
|
|
return r.movies.AddMovies(movies)
|
|
}
|
|
|
|
func (r *Room) UserRole(userID string) (model.RoomMemberRole, error) {
|
|
if r.IsCreator(userID) {
|
|
return model.RoomMemberRoleCreator, nil
|
|
}
|
|
rur, err := r.LoadRoomMember(userID)
|
|
if err != nil {
|
|
return model.RoomMemberRoleUnknown, err
|
|
}
|
|
return rur.Role, nil
|
|
}
|
|
|
|
// do not use this value for permission determination
|
|
func (r *Room) IsAdmin(userID string) bool {
|
|
role, err := r.UserRole(userID)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return role.IsAdmin()
|
|
}
|
|
|
|
func (r *Room) IsCreator(userID string) bool {
|
|
return r.CreatorID == userID
|
|
}
|
|
|
|
func (r *Room) IsGuest(userID string) bool {
|
|
return userID == db.GuestUserID
|
|
}
|
|
|
|
func (r *Room) HasPermission(userID string, permission model.RoomMemberPermission) bool {
|
|
if r.IsCreator(userID) {
|
|
return true
|
|
}
|
|
|
|
rur, err := r.LoadOrCreateRoomMember(userID)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if rur.Role.IsAdmin() {
|
|
return true
|
|
}
|
|
|
|
switch {
|
|
case permission.Has(model.PermissionGetMovieList) && !r.Settings.CanGetMovieList,
|
|
permission.Has(model.PermissionAddMovie) && !r.Settings.CanAddMovie,
|
|
permission.Has(model.PermissionDeleteMovie) && !r.Settings.CanDeleteMovie,
|
|
permission.Has(model.PermissionEditMovie) && !r.Settings.CanEditMovie,
|
|
permission.Has(model.PermissionSetCurrentMovie) && !r.Settings.CanSetCurrentMovie,
|
|
permission.Has(model.PermissionSetCurrentStatus) && !r.Settings.CanSetCurrentStatus,
|
|
permission.Has(model.PermissionSendChatMessage) && !r.Settings.CanSendChatMessage:
|
|
return false
|
|
default:
|
|
return rur.Permissions.Has(permission)
|
|
}
|
|
}
|
|
|
|
func (r *Room) HasAdminPermission(userID string, permission model.RoomAdminPermission) bool {
|
|
if r.IsCreator(userID) {
|
|
return true
|
|
}
|
|
|
|
rur, err := r.LoadOrCreateRoomMember(userID)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return rur.HasAdminPermission(permission)
|
|
}
|
|
|
|
func (r *Room) LoadOrCreateMemberStatus(userID string) (model.RoomMemberStatus, error) {
|
|
if r.IsCreator(userID) {
|
|
return model.RoomMemberStatusActive, nil
|
|
}
|
|
rur, err := r.LoadOrCreateRoomMember(userID)
|
|
if err != nil {
|
|
return model.RoomMemberStatusUnknown, err
|
|
}
|
|
return rur.Status, nil
|
|
}
|
|
|
|
func (r *Room) LoadMemberStatus(userID string) (model.RoomMemberStatus, error) {
|
|
if r.IsCreator(userID) {
|
|
return model.RoomMemberStatusActive, nil
|
|
}
|
|
rur, err := r.LoadRoomMember(userID)
|
|
if err != nil {
|
|
return model.RoomMemberStatusUnknown, err
|
|
}
|
|
return rur.Status, nil
|
|
}
|
|
|
|
func (r *Room) LoadOrCreateRoomMember(userID string) (*model.RoomMember, error) {
|
|
if r.Settings.DisableJoinNewUser {
|
|
return r.LoadRoomMember(userID)
|
|
}
|
|
if r.IsGuest(userID) && (r.Settings.DisableGuest || !settings.EnableGuest.Get()) {
|
|
return nil, errors.New("guest is disabled")
|
|
}
|
|
member, ok := r.members.Load(userID)
|
|
if ok {
|
|
return member, nil
|
|
}
|
|
var conf []db.CreateRoomMemberRelationConfig
|
|
if r.IsCreator(userID) {
|
|
conf = append(
|
|
conf,
|
|
db.WithRoomMemberStatus(model.RoomMemberStatusActive),
|
|
db.WithRoomMemberPermissions(model.AllPermissions),
|
|
db.WithRoomMemberRole(model.RoomMemberRoleCreator),
|
|
db.WithRoomMemberAdminPermissions(model.AllAdminPermissions),
|
|
)
|
|
} else {
|
|
if r.IsGuest(userID) {
|
|
conf = append(
|
|
conf,
|
|
db.WithRoomMemberPermissions(model.NoPermission),
|
|
db.WithRoomMemberRole(model.RoomMemberRoleMember),
|
|
db.WithRoomMemberAdminPermissions(model.NoAdminPermission),
|
|
)
|
|
} else {
|
|
conf = append(
|
|
conf,
|
|
db.WithRoomMemberPermissions(r.Settings.UserDefaultPermissions),
|
|
db.WithRoomMemberRole(model.RoomMemberRoleMember),
|
|
db.WithRoomMemberAdminPermissions(model.NoAdminPermission),
|
|
)
|
|
}
|
|
if r.Settings.JoinNeedReview {
|
|
conf = append(conf, db.WithRoomMemberStatus(model.RoomMemberStatusPending))
|
|
} else {
|
|
conf = append(conf, db.WithRoomMemberStatus(model.RoomMemberStatusActive))
|
|
}
|
|
}
|
|
member, err := db.FirstOrCreateRoomMemberRelation(r.ID, userID, conf...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if r.IsCreator(userID) {
|
|
member.Role = model.RoomMemberRoleCreator
|
|
member.Permissions = model.AllPermissions
|
|
member.AdminPermissions = model.AllAdminPermissions
|
|
member.Status = model.RoomMemberStatusActive
|
|
} else if r.IsGuest(userID) {
|
|
member.Role = model.RoomMemberRoleMember
|
|
member.Permissions = model.NoPermission
|
|
member.AdminPermissions = model.NoAdminPermission
|
|
if member.Status.IsBanned() {
|
|
member.Status = model.RoomMemberStatusActive
|
|
}
|
|
} else if member.Role.IsAdmin() {
|
|
member.Permissions = model.AllPermissions
|
|
}
|
|
member, _ = r.members.LoadOrStore(userID, member)
|
|
return member, nil
|
|
}
|
|
|
|
func (r *Room) LoadRoomMember(userID string) (*model.RoomMember, error) {
|
|
if r.IsGuest(userID) && (r.Settings.DisableGuest || !settings.EnableGuest.Get()) {
|
|
return nil, errors.New("guest is disabled")
|
|
}
|
|
member, ok := r.members.Load(userID)
|
|
if ok {
|
|
return member, nil
|
|
}
|
|
member, err := db.GetRoomMember(r.ID, userID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get room member failed: %w", err)
|
|
}
|
|
if r.IsCreator(userID) {
|
|
member.Role = model.RoomMemberRoleCreator
|
|
member.Permissions = model.AllPermissions
|
|
member.AdminPermissions = model.AllAdminPermissions
|
|
member.Status = model.RoomMemberStatusActive
|
|
} else if r.IsGuest(userID) {
|
|
member.Role = model.RoomMemberRoleMember
|
|
member.Permissions = model.NoPermission
|
|
member.AdminPermissions = model.NoAdminPermission
|
|
if member.Status.IsBanned() {
|
|
member.Status = model.RoomMemberStatusActive
|
|
}
|
|
} else if member.Role.IsAdmin() {
|
|
member.Permissions = model.AllPermissions
|
|
}
|
|
member, _ = r.members.LoadOrStore(userID, member)
|
|
return member, nil
|
|
}
|
|
|
|
func (r *Room) LoadRoomMemberPermission(userID string) (model.RoomMemberPermission, error) {
|
|
if r.IsCreator(userID) {
|
|
return model.AllPermissions, nil
|
|
}
|
|
member, err := r.LoadRoomMember(userID)
|
|
if err != nil {
|
|
return model.NoPermission, err
|
|
}
|
|
return member.Permissions, nil
|
|
}
|
|
|
|
func (r *Room) LoadRoomAdminPermission(userID string) (model.RoomAdminPermission, error) {
|
|
if r.IsCreator(userID) {
|
|
return model.AllAdminPermissions, nil
|
|
}
|
|
member, err := r.LoadRoomMember(userID)
|
|
if err != nil {
|
|
return model.NoAdminPermission, err
|
|
}
|
|
return member.AdminPermissions, nil
|
|
}
|
|
|
|
func (r *Room) NeedPassword() bool {
|
|
return len(r.HashedPassword) != 0
|
|
}
|
|
|
|
func (r *Room) SetPassword(password string) error {
|
|
if r.CheckPassword(password) && r.NeedPassword() {
|
|
return errors.New("password is the same")
|
|
}
|
|
var hashedPassword []byte
|
|
if password != "" {
|
|
var err error
|
|
hashedPassword, err = bcrypt.GenerateFromPassword(stream.StringToBytes(password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
atomic.StoreUint32(&r.version, crc32.ChecksumIEEE(hashedPassword))
|
|
}
|
|
r.HashedPassword = hashedPassword
|
|
return db.SetRoomHashedPassword(r.ID, hashedPassword)
|
|
}
|
|
|
|
func (r *Room) GetMoviesCount() int {
|
|
return r.movies.Len()
|
|
}
|
|
|
|
func (r *Room) DeleteMovieByID(id string) error {
|
|
if r.current.current.MovieID == id {
|
|
return errors.New("cannot delete current movie")
|
|
}
|
|
return r.movies.DeleteMovieByID(id)
|
|
}
|
|
|
|
func (r *Room) DeleteMoviesByID(ids []string) error {
|
|
if r.current.current.MovieID != "" {
|
|
for _, id := range ids {
|
|
if id == r.current.current.MovieID {
|
|
return errors.New("cannot delete current movie")
|
|
}
|
|
}
|
|
}
|
|
return r.movies.DeleteMoviesByID(ids)
|
|
}
|
|
|
|
func (r *Room) ClearMovies() error {
|
|
_ = r.SetCurrentMovie("", false)
|
|
return r.movies.Clear()
|
|
}
|
|
|
|
func (r *Room) GetMovieByID(id string) (*Movie, error) {
|
|
return r.movies.GetMovieByID(id)
|
|
}
|
|
|
|
func (r *Room) Current() *Current {
|
|
c := r.current.Current()
|
|
return &c
|
|
}
|
|
|
|
func (r *Room) CurrentMovieID() string {
|
|
return r.current.current.MovieID
|
|
}
|
|
|
|
var ErrNoCurrentMovie = errors.New("no current movie")
|
|
|
|
func (r *Room) CurrentMovie() (*Movie, error) {
|
|
if r.current.current.MovieID == "" {
|
|
return nil, ErrNoCurrentMovie
|
|
}
|
|
return r.GetMovieByID(r.current.current.MovieID)
|
|
}
|
|
|
|
func (r *Room) CheckCurrentExpired(expireId uint64) (bool, error) {
|
|
m, err := r.CurrentMovie()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return m.CheckExpired(expireId), nil
|
|
}
|
|
|
|
func (r *Room) SetCurrentMovie(movieID string, play bool) error {
|
|
currentMovie, err := r.CurrentMovie()
|
|
if err != nil {
|
|
if err != ErrNoCurrentMovie {
|
|
return err
|
|
}
|
|
} else {
|
|
err = currentMovie.ClearCache()
|
|
if err != nil {
|
|
return fmt.Errorf("clear cache failed: %w", err)
|
|
}
|
|
}
|
|
if movieID == "" {
|
|
r.current.SetMovie("", false, play)
|
|
return nil
|
|
}
|
|
m, err := r.GetMovieByID(movieID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.current.SetMovie(m.ID, m.Base.Live, play)
|
|
return nil
|
|
}
|
|
|
|
func (r *Room) SwapMoviePositions(id1, id2 string) error {
|
|
return r.movies.SwapMoviePositions(id1, id2)
|
|
}
|
|
|
|
func (r *Room) GetMoviesWithPage(page, pageSize int, creator string) ([]*Movie, int) {
|
|
return r.movies.GetMoviesWithPage(page, pageSize, creator)
|
|
}
|
|
|
|
func (r *Room) NewClient(user *User, conn *websocket.Conn) (*Client, error) {
|
|
r.lazyInitHub()
|
|
cli := newClient(user, r, conn)
|
|
err := r.hub.RegClient(cli)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cli, nil
|
|
}
|
|
|
|
func (r *Room) RegClient(cli *Client) error {
|
|
r.lazyInitHub()
|
|
return r.hub.RegClient(cli)
|
|
}
|
|
|
|
func (r *Room) UnregisterClient(cli *Client) error {
|
|
r.lazyInitHub()
|
|
return r.hub.UnRegClient(cli)
|
|
}
|
|
|
|
func (r *Room) UserIsOnline(userID string) bool {
|
|
r.lazyInitHub()
|
|
return r.hub.IsOnline(userID)
|
|
}
|
|
|
|
func (r *Room) UserOnlineCount(userID string) int {
|
|
r.lazyInitHub()
|
|
return r.hub.OnlineCount(userID)
|
|
}
|
|
|
|
func (r *Room) SetCurrentStatus(playing bool, seek float64, rate float64, timeDiff float64) *Status {
|
|
return r.current.SetStatus(playing, seek, rate, timeDiff)
|
|
}
|
|
|
|
func (r *Room) SetCurrentSeekRate(seek float64, rate float64, timeDiff float64) *Status {
|
|
return r.current.SetSeekRate(seek, rate, timeDiff)
|
|
}
|
|
|
|
func (r *Room) SetSettings(settings *model.RoomSettings) error {
|
|
err := db.SaveRoomSettings(r.ID, settings)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.Settings = settings
|
|
if settings.DisableGuest {
|
|
return r.KickUser(db.GuestUserID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *Room) UpdateSettings(settings map[string]any) error {
|
|
rs, err := db.UpdateRoomSettings(r.ID, settings)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.Settings = rs
|
|
if rs.DisableGuest {
|
|
return r.KickUser(db.GuestUserID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *Room) ResetMemberPermissions(userID string) error {
|
|
return r.SetMemberPermissions(userID, r.Settings.UserDefaultPermissions)
|
|
}
|
|
|
|
func (r *Room) SetMemberPermissions(userID string, permissions model.RoomMemberPermission) error {
|
|
if r.IsCreator(userID) {
|
|
return errors.New("you are creator, cannot set permissions")
|
|
}
|
|
if r.IsGuest(userID) {
|
|
return errors.New("cannot set permissions to guest")
|
|
}
|
|
defer r.members.Delete(userID)
|
|
return db.SetMemberPermissions(r.ID, userID, permissions)
|
|
}
|
|
|
|
func (r *Room) AddMemberPermissions(userID string, permissions model.RoomMemberPermission) error {
|
|
if r.IsGuest(userID) {
|
|
return errors.New("cannot add permissions to guest")
|
|
}
|
|
if r.IsAdmin(userID) {
|
|
return errors.New("cannot add permissions to admin")
|
|
}
|
|
defer r.members.Delete(userID)
|
|
return db.AddMemberPermissions(r.ID, userID, permissions)
|
|
}
|
|
|
|
func (r *Room) RemoveMemberPermissions(userID string, permissions model.RoomMemberPermission) error {
|
|
if r.IsAdmin(userID) {
|
|
return errors.New("cannot remove permissions from admin")
|
|
}
|
|
defer r.members.Delete(userID)
|
|
return db.RemoveMemberPermissions(r.ID, userID, permissions)
|
|
}
|
|
|
|
func (r *Room) ApprovePendingMember(userID string) error {
|
|
if r.IsCreator(userID) {
|
|
return errors.New("you are creator, cannot approve")
|
|
}
|
|
defer r.members.Delete(userID)
|
|
return db.RoomApprovePendingMember(r.ID, userID)
|
|
}
|
|
|
|
func (r *Room) BanMember(userID string) error {
|
|
if r.IsCreator(userID) {
|
|
return errors.New("you are creator, cannot ban")
|
|
}
|
|
if r.IsGuest(userID) {
|
|
return errors.New("cannot ban guest")
|
|
}
|
|
defer func() {
|
|
r.members.Delete(userID)
|
|
_ = r.KickUser(userID)
|
|
}()
|
|
return db.RoomBanMember(r.ID, userID)
|
|
}
|
|
|
|
func (r *Room) UnbanMember(userID string) error {
|
|
if r.IsCreator(userID) {
|
|
return errors.New("you are creator, cannot unban")
|
|
}
|
|
defer r.members.Delete(userID)
|
|
return db.RoomUnbanMember(r.ID, userID)
|
|
}
|
|
|
|
func (r *Room) ResetAdminPermissions(userID string) error {
|
|
return r.SetAdminPermissions(userID, model.DefaultAdminPermissions)
|
|
}
|
|
|
|
func (r *Room) SetAdminPermissions(userID string, permissions model.RoomAdminPermission) error {
|
|
if r.IsCreator(userID) {
|
|
return errors.New("you are creator, cannot set admin permissions")
|
|
}
|
|
if r.IsGuest(userID) {
|
|
return errors.New("cannot set admin permissions to guest")
|
|
}
|
|
if member, err := r.LoadRoomMember(userID); err != nil {
|
|
return err
|
|
} else if !member.Role.IsAdmin() {
|
|
return errors.New("not admin")
|
|
}
|
|
defer r.members.Delete(userID)
|
|
return db.RoomSetAdminPermissions(r.ID, userID, permissions)
|
|
}
|
|
|
|
func (r *Room) AddAdminPermissions(userID string, permissions model.RoomAdminPermission) error {
|
|
if r.IsCreator(userID) {
|
|
return errors.New("you are creator, cannot add admin permissions")
|
|
}
|
|
if r.IsGuest(userID) {
|
|
return errors.New("cannot add admin permissions to guest")
|
|
}
|
|
if member, err := r.LoadRoomMember(userID); err != nil {
|
|
return err
|
|
} else if !member.Role.IsAdmin() {
|
|
return errors.New("not admin")
|
|
}
|
|
defer r.members.Delete(userID)
|
|
return db.RoomSetAdminPermissions(r.ID, userID, permissions)
|
|
}
|
|
|
|
func (r *Room) RemoveAdminPermissions(userID string, permissions model.RoomAdminPermission) error {
|
|
if r.IsCreator(userID) {
|
|
return errors.New("you are creator, cannot remove admin permissions")
|
|
}
|
|
if r.IsGuest(userID) {
|
|
return errors.New("cannot remove admin permissions from guest")
|
|
}
|
|
if member, err := r.LoadRoomMember(userID); err != nil {
|
|
return err
|
|
} else if !member.Role.IsAdmin() {
|
|
return errors.New("not admin")
|
|
}
|
|
defer r.members.Delete(userID)
|
|
return db.RoomSetAdminPermissions(r.ID, userID, 0)
|
|
}
|
|
|
|
func (r *Room) SetAdmin(userID string, permissions model.RoomAdminPermission) error {
|
|
if r.IsCreator(userID) {
|
|
return errors.New("you are creator, cannot set admin")
|
|
}
|
|
if r.IsGuest(userID) {
|
|
return errors.New("cannot set guest as admin")
|
|
}
|
|
defer r.members.Delete(userID)
|
|
return db.RoomSetAdmin(r.ID, userID, permissions)
|
|
}
|
|
|
|
func (r *Room) SetMember(userID string, permissions model.RoomMemberPermission) error {
|
|
if r.IsCreator(userID) {
|
|
return errors.New("you are creator, cannot set member")
|
|
}
|
|
defer r.members.Delete(userID)
|
|
return db.RoomSetMember(r.ID, userID, permissions)
|
|
}
|