SoundStudio handlers

This commit is contained in:
Ben 2019-09-09 22:27:52 +01:00
parent 4e8a73591b
commit 06c2ac68b2
3 changed files with 288 additions and 0 deletions

View File

@ -11,6 +11,19 @@ class PenguinTrack(db.Model):
sharing = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
pattern = db.Column(db.Text, nullable=False)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._likes = 0
@property
def likes(self):
return self._likes
@likes.setter
def likes(self, like_count):
self._likes = like_count
class TrackLike(db.Model):
__tablename__ = 'track_like'

View File

@ -0,0 +1,267 @@
from houdini import handlers
from houdini.handlers import XTPacket
from houdini.data import db
from houdini.data.music import PenguinTrack, TrackLike
from houdini.constants import ClientType
from houdini.crypto import Crypto
from gino.loader import ColumnLoader
from datetime import datetime, date
import asyncio
import re
class SoundStudio:
StudioRoomId = 120
DeckRoomId = 898
def __init__(self, server):
self.server = server
self.broadcasting = False
self.playlist = []
self.current_track = None
self.room = server.rooms[SoundStudio.StudioRoomId]
async def broadcast_next_track(self):
await self.get_tracks()
if not self.room.penguins_by_id or not self.playlist:
await self.send_broadcasted_tracks()
await self.stop_broadcasting()
else:
next_track = self.playlist[0]
if self.current_track is not None:
while next_track.id != self.current_track.id:
self.playlist.append(self.playlist.pop(0))
if next_track.id == self.playlist[0].id:
break
next_track = self.playlist[0]
self.playlist.append(self.playlist.pop(0))
self.current_track = self.playlist[0]
await self.send_broadcasted_tracks()
song_length = determine_song_length(self.current_track.pattern)
await asyncio.sleep(song_length)
async def broadcast_tracks(self):
while self.broadcasting:
await self.broadcast_next_track()
async def start_broadcasting(self):
if not self.broadcasting:
self.broadcasting = True
loop = asyncio.get_event_loop()
asyncio.ensure_future(self.broadcast_tracks(), loop=loop)
async def stop_broadcasting(self):
if self.broadcasting:
self.broadcasting = False
self.current_track = None
async def get_tracks(self):
self.playlist = []
likes = db.func.count(TrackLike.track_id)
tracks_query = db.select([PenguinTrack, likes]) \
.select_from(PenguinTrack.outerjoin(TrackLike)) \
.where((PenguinTrack.owner_id.in_(tuple(self.room.penguins_by_id.keys())))
& (PenguinTrack.sharing == True)) \
.group_by(PenguinTrack.id).gino.load(PenguinTrack.load(likes=ColumnLoader(likes)))
async with db.transaction():
async for track in tracks_query.iterate():
self.playlist.append(track)
async def send_broadcasted_tracks(self):
broadcasted_tracks = await self.get_broadcasted_tracks()
for penguin in self.room.penguins_by_id.values():
playlist_position = get_playlist_position(penguin)
await penguin.send_xt('broadcastingmusictracks', len(self.playlist),
playlist_position, broadcasted_tracks)
async def get_broadcasted_tracks(self):
penguins = self.server.penguins_by_id
broadcasted_tracks = ','.join(f'{track.owner_id}|{penguins[track.owner_id].data.nickname}|'
f'{track.owner_id}|{track.id}|{track.likes}'
for track in self.playlist)
return broadcasted_tracks
async def get_player_tracks(p):
player_tracks = []
likes = db.func.count(TrackLike.track_id)
tracks_query = db.select([PenguinTrack, likes])\
.select_from(PenguinTrack.outerjoin(TrackLike))\
.where(PenguinTrack.owner_id == p.data.id)\
.group_by(PenguinTrack.id).gino.load(PenguinTrack.load(likes=ColumnLoader(likes)))
async with db.transaction():
async for track in tracks_query.iterate():
player_tracks.append(f'{track.id}|{track.name}|{int(track.sharing)}|{track.likes}')
return player_tracks
async def get_shared_tracks(p):
shared_tracks = []
likes = db.func.count(TrackLike.track_id)
tracks_query = db.select([PenguinTrack, likes])\
.select_from(PenguinTrack.outerjoin(TrackLike))\
.where((PenguinTrack.owner_id.in_(tuple(p.server.penguins_by_id.keys())) & (PenguinTrack.sharing == True)))\
.group_by(PenguinTrack.id).gino.load(PenguinTrack.load(likes=ColumnLoader(likes)))
async with db.transaction():
async for track in tracks_query.iterate():
penguin = p.server.penguins_by_id[track.owner_id]
shared_tracks.append(f'{penguin.data.id}|{penguin.data.nickname}|{track.id}|{track.likes}')
return shared_tracks
async def get_track(owner_id, track_id):
likes = db.func.count(TrackLike.track_id)
track = await db.select([PenguinTrack, likes])\
.select_from(PenguinTrack.outerjoin(TrackLike))\
.where((PenguinTrack.owner_id == owner_id) & (PenguinTrack.id == track_id))\
.group_by(PenguinTrack.id).gino.load(PenguinTrack.load(likes=ColumnLoader(likes))).first()
return track
async def can_like_track(p, owner_id: int, track_id: int):
yesterday = datetime.combine(date.today(), datetime.min.time())
like = await db.select(TrackLike) \
.select_from(TrackLike.outerjoin(PenguinTrack)) \
.where((PenguinTrack.owner_id == owner_id)
& (TrackLike.penguin_id == p.data.id)
& (TrackLike.track_id == track_id)
& (TrackLike.date > yesterday)).gino.first()
return like is None
def get_playlist_position(p):
for position, track in enumerate(p.server.music.playlist):
if track.owner_id == p.data.id:
return position + 1
return -1
def encode_music_track(track_pattern):
encoded_track_pattern = track_pattern[::-1]
encoded_track_pattern = Crypto.hash(encoded_track_pattern)
return encoded_track_pattern[16:32] + encoded_track_pattern[0:16]
def determine_song_length(track_pattern):
track_length = track_pattern.split(',')[-1]
track_length = track_length.split('|')[1]
return int(track_length, 16) // 1000
@handlers.handler(XTPacket('musictrack', 'broadcastingmusictracks'), client=ClientType.Vanilla)
@handlers.player_in_room(SoundStudio.StudioRoomId)
async def handle_broadcasting_tracks(p):
if not p.server.music.broadcasting:
await p.server.music.start_broadcasting()
elif not p.server.music.playlist:
await p.send_xt('broadcastingmusictracks', 0, -1, '')
else:
playlist_position = get_playlist_position(p)
broadcasted_tracks = await p.server.music.get_broadcasted_tracks()
await p.send_xt('broadcastingmusictracks', len(p.server.music.playlist),
playlist_position, broadcasted_tracks)
@handlers.handler(XTPacket('musictrack', 'getmymusictracks'), client=ClientType.Vanilla)
@handlers.player_in_room(SoundStudio.StudioRoomId, SoundStudio.DeckRoomId)
async def handle_get_my_music_tracks(p):
player_tracks = await get_player_tracks(p)
await p.send_xt('getmymusictracks', len(player_tracks), ','.join(player_tracks))
@handlers.handler(XTPacket('musictrack', 'getsharedmusictracks'), client=ClientType.Vanilla)
@handlers.player_in_room(SoundStudio.StudioRoomId, SoundStudio.DeckRoomId)
async def handle_get_shared_music_tracks(p):
shared_tracks = await get_shared_tracks(p)
await p.send_xt('getsharedmusictracks', len(shared_tracks), shared_tracks)
@handlers.handler(XTPacket('musictrack', 'loadmusictrack'), client=ClientType.Vanilla)
@handlers.player_in_room(SoundStudio.StudioRoomId, SoundStudio.DeckRoomId)
async def handle_load_music_track(p, owner_id: int, track_id: int):
track = await get_track(owner_id, track_id)
encoded_track_pattern = encode_music_track(track.pattern)
await p.send_xt('loadmusictrack', track.id, track.name, int(track.sharing), track.pattern,
encoded_track_pattern, track.likes)
@handlers.handler(XTPacket('musictrack', 'savemymusictrack'), client=ClientType.Vanilla)
@handlers.player_in_room(SoundStudio.DeckRoomId)
@handlers.cooldown()
async def handle_save_my_music_track(p, track_name, track_pattern, track_hash):
encoded_track_pattern = encode_music_track(track_pattern)
song_length = determine_song_length(track_pattern)
if encoded_track_pattern != track_hash or song_length > 180:
return
pattern_regex = r'^([0-9a-fA-F]+,\d+\|){0,1000}[0-9a-fA-F]+,FFFF\|[0-9a-fA-F]+$'
if not re.match(pattern_regex, track_pattern):
return
track_count = await db.select([db.func.count(PenguinTrack.id)])\
.where(PenguinTrack.owner_id == p.data.id).gino.scalar()
if track_count >= 12:
return
track = await PenguinTrack.create(owner_id=p.data.id, name=track_name, pattern=track_pattern)
await p.send_xt('savemymusictrack', track.id)
@handlers.handler(XTPacket('musictrack', 'refreshmytracklikes'), client=ClientType.Vanilla)
@handlers.player_in_room(SoundStudio.StudioRoomId, SoundStudio.DeckRoomId)
async def handle_refresh_my_track_likes(p):
likes = db.func.count(TrackLike.track_id)
track_likes_query = db.select([PenguinTrack.id, likes])\
.select_from(PenguinTrack.outerjoin(TrackLike))\
.where(PenguinTrack.owner_id == p.data.id)\
.group_by(PenguinTrack.id).gino.load(PenguinTrack.load(likes=ColumnLoader(likes)))
async with db.transaction():
async for track in track_likes_query.iterate():
await p.send_xt('getlikecountfortrack', p.data.id, track.id, track.likes)
@handlers.handler(XTPacket('musictrack', 'sharemymusictrack'), client=ClientType.Vanilla)
@handlers.player_in_room(SoundStudio.StudioRoomId, SoundStudio.DeckRoomId)
@handlers.cooldown()
async def handle_share_my_music_track(p, track_id: int, sharing: int):
if sharing:
await PenguinTrack.update.values(sharing=False)\
.where(PenguinTrack.owner_id == p.data.id).gino.status()
await PenguinTrack.update.values(sharing=bool(sharing))\
.where((PenguinTrack.id == track_id)
& (PenguinTrack.owner_id == p.data.id)).gino.status()
await p.send_xt('sharemymusictrack', 1)
@handlers.handler(XTPacket('musictrack', 'deletetrack'), client=ClientType.Vanilla)
@handlers.player_in_room(SoundStudio.StudioRoomId, SoundStudio.DeckRoomId)
async def handle_delete_track(p, track_id: int):
await PenguinTrack.delete.where((PenguinTrack.id == track_id)
& (PenguinTrack.owner_id == p.data.id)).gino.status()
await p.send_xt('deletetrack', 1)
@handlers.handler(XTPacket('musictrack', 'canliketrack'), client=ClientType.Vanilla)
@handlers.player_in_room(SoundStudio.StudioRoomId, SoundStudio.DeckRoomId)
async def handle_can_like_track(p, owner_id: int, track_id: int):
can_like = await can_like_track(p, owner_id, track_id)
await p.send_xt('canliketrack', track_id, int(can_like))
@handlers.handler(XTPacket('musictrack', 'liketrack'), client=ClientType.Vanilla)
@handlers.player_in_room(SoundStudio.StudioRoomId, SoundStudio.DeckRoomId)
@handlers.cooldown()
async def handle_like_track(p, owner_id: int, track_id: int):
can_like = await can_like_track(p, owner_id, track_id)
if can_like:
await TrackLike.create(penguin_id=p.data.id, track_id=track_id)
like_count = await db.select([db.func.count(TrackLike.track_id)])\
.where(TrackLike.track_id == track_id).gino.scalar()
await p.room.send_xt('liketrack', owner_id, track_id, like_count)

View File

@ -42,6 +42,9 @@ from houdini.commands import CommandManager
from houdini.handlers.play.player import server_heartbeat
from houdini.handlers.play.player import server_egg_timer
from houdini.handlers.play.music import SoundStudio
class Houdini:
def __init__(self, server_name, **kwargs):
@ -95,6 +98,9 @@ class Houdini:
self.heartbeat = None
self.egg_timer = None
self.music = None
async def start(self):
self.config = config
@ -232,6 +238,8 @@ class Houdini:
self.heartbeat = asyncio.create_task(server_heartbeat(self))
self.egg_timer = asyncio.create_task(server_egg_timer(self))
self.music = SoundStudio(self)
async with self.server:
await self.server.serve_forever()