diff --git a/VKAPI/Handlers/Audio.php b/VKAPI/Handlers/Audio.php index 58bce510..9670a7ff 100644 --- a/VKAPI/Handlers/Audio.php +++ b/VKAPI/Handlers/Audio.php @@ -566,6 +566,14 @@ final class Audio extends VKAPIRequestHandler $owner_id = $owner_id == 0 ? $this->getUser()->getId() : $owner_id; $playlists = []; + + if($owner_id > 0 && $owner_id != $this->getUser()->getId()) { + $user = (new \openvk\Web\Models\Repositories\Users)->get($owner_id); + + if(!$user->getPrivacyPermission("audios.read", $this->getUser())) + $this->fail(50, "Access to playlists denied"); + } + foreach((new Audios)->getPlaylistsByEntityId($owner_id, $offset, $count) as $playlist) { if(!$playlist->canBeViewedBy($this->getUser())) { if($drop_private == 1) diff --git a/VKAPI/Handlers/Status.php b/VKAPI/Handlers/Status.php index 843f42bd..a1b104a2 100644 --- a/VKAPI/Handlers/Status.php +++ b/VKAPI/Handlers/Status.php @@ -8,13 +8,23 @@ final class Status extends VKAPIRequestHandler function get(int $user_id = 0, int $group_id = 0) { $this->requireUser(); - if($user_id == 0 && $group_id == 0) { - return $this->getUser()->getStatus(); - } else { - if($group_id > 0) - $this->fail(501, "Group statuses are not implemented"); - else - return (new UsersRepo)->get($user_id)->getStatus(); + + if($user_id == 0 && $group_id == 0) + $user_id = $this->getUser()->getId(); + + if($group_id > 0) + $this->fail(501, "Group statuses are not implemented"); + else { + $user = (new UsersRepo)->get($user_id); + $audioStatus = $user->getCurrentAudioStatus(); + if($audioStatus) { + return [ + "status" => $user->getStatus(), + "audio" => $audioStatus->toVkApiStruct(), + ]; + } + + return $user->getStatus(); } } diff --git a/VKAPI/Handlers/Users.php b/VKAPI/Handlers/Users.php index a4298787..68bf828f 100644 --- a/VKAPI/Handlers/Users.php +++ b/VKAPI/Handlers/Users.php @@ -95,6 +95,12 @@ final class Users extends VKAPIRequestHandler case "status": if($usr->getStatus() != NULL) $response[$i]->status = $usr->getStatus(); + + $audioStatus = $usr->getCurrentAudioStatus(); + + if($audioStatus) + $response[$i]->status_audio = $audioStatus->toVkApiStruct(); + break; case "screen_name": if($usr->getShortCode() != NULL) diff --git a/Web/Models/Entities/Audio.php b/Web/Models/Entities/Audio.php index 4fab06be..0fe110e3 100644 --- a/Web/Models/Entities/Audio.php +++ b/Web/Models/Entities/Audio.php @@ -313,8 +313,8 @@ class Audio extends Media $lastListen = $listensTable->where([ "entity" => $entityId, "audio" => $this->getId(), - ])->fetch(); - + ])->order("index DESC")->fetch(); + if(!$lastListen || (time() - $lastListen->time >= $this->getLength())) { $listensTable->insert([ "entity" => $entityId, @@ -331,6 +331,9 @@ class Audio extends Media $playlist->incrementListens(); $playlist->save(); } + + $entity->setLast_played_track($this->getId()); + $entity->save(); } return true; diff --git a/Web/Models/Entities/Club.php b/Web/Models/Entities/Club.php index c89f0b47..126c5127 100644 --- a/Web/Models/Entities/Club.php +++ b/Web/Models/Entities/Club.php @@ -421,4 +421,5 @@ class Club extends RowModel use Traits\TBackDrops; use Traits\TSubscribable; + use Traits\TAudioStatuses; } diff --git a/Web/Models/Entities/Traits/TAudioStatuses.php b/Web/Models/Entities/Traits/TAudioStatuses.php new file mode 100644 index 00000000..d49c4b29 --- /dev/null +++ b/Web/Models/Entities/Traits/TAudioStatuses.php @@ -0,0 +1,37 @@ +getRecord()->audio_broadcast_enabled; + } + + function getCurrentAudioStatus() + { + if(!$this->isBroadcastEnabled()) return NULL; + + $audioId = $this->getRecord()->last_played_track; + + if(!$audioId) return NULL; + $audio = (new Audios)->get($audioId); + + if(!$audio || $audio->isDeleted()) + return NULL; + + $listensTable = DatabaseConnection::i()->getContext()->table("audio_listens"); + $lastListen = $listensTable->where([ + "entity" => $this->getRealId(), + "audio" => $audio->getId(), + "time >" => (time() - $audio->getLength()) - 10, + ])->fetch(); + + if($lastListen) + return $audio; + + return NULL; + } +} \ No newline at end of file diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php index 2114b5ab..ac903404 100644 --- a/Web/Models/Entities/User.php +++ b/Web/Models/Entities/User.php @@ -1249,7 +1249,32 @@ class User extends RowModel return $res; } - + + function getFriendsAudios() + { + $friendsCount = $this->getFriendsCount(); + $friends = $this->getFriends(max(rand(1, (int)ceil($friendsCount / 6)), 1), 6); + + $shuffleSeed = openssl_random_pseudo_bytes(6); + $shuffleSeed = hexdec(bin2hex($shuffleSeed)); + + $friends = knuth_shuffle($friends, $shuffleSeed); + $returnArr = []; + + foreach($friends as $friend) { + $returnArr[] = [ + "id" => $friend->getRealId(), + "name" => $friend->getCanonicalName(), + "avatar" => $friend->getAvatarURL("miniscule"), + "tracksCount" => (new \openvk\Web\Models\Repositories\Audios)->getUserCollectionSize($friend), + "nowListening" => $friend->getCurrentAudioStatus(), + ]; + } + + return $returnArr; + } + use Traits\TBackDrops; use Traits\TSubscribable; + use Traits\TAudioStatuses; } diff --git a/Web/Presenters/AudioPresenter.php b/Web/Presenters/AudioPresenter.php index 2b5786c1..a673e7d1 100644 --- a/Web/Presenters/AudioPresenter.php +++ b/Web/Presenters/AudioPresenter.php @@ -109,6 +109,9 @@ final class AudioPresenter extends OpenVKPresenter $this->template->mode = $mode; $this->template->page = $page; + + if(in_array($mode, ["list", "new", "popular"])) + $this->template->friendsAudios = $this->user->identity->getFriendsAudios(); } function renderEmbed(int $owner, int $id): void diff --git a/Web/Presenters/UserPresenter.php b/Web/Presenters/UserPresenter.php index 8dc65840..56d5685b 100644 --- a/Web/Presenters/UserPresenter.php +++ b/Web/Presenters/UserPresenter.php @@ -47,7 +47,8 @@ final class UserPresenter extends OpenVKPresenter $this->template->notesCount = (new Notes)->getUserNotesCount($user); $this->template->audios = (new Audios)->getRandomThreeAudiosByEntityId($user->getId()); $this->template->audiosCount = (new Audios)->getUserCollectionSize($user); - + $this->template->audioStatus = $user->getCurrentAudioStatus(); + $this->template->user = $user; } } @@ -171,6 +172,7 @@ final class UserPresenter extends OpenVKPresenter if ($this->postParam("gender") <= 1 && $this->postParam("gender") >= 0) $user->setSex($this->postParam("gender")); + $user->setAudio_broadcast_enabled($this->checkbox("broadcast_music")); if(!empty($this->postParam("phone")) && $this->postParam("phone") !== $user->getPhone()) { if(!OPENVK_ROOT_CONF["openvk"]["credentials"]["smsc"]["enable"]) @@ -243,6 +245,7 @@ final class UserPresenter extends OpenVKPresenter } $user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status")); + $user->setAudio_broadcast_enabled($this->postParam("broadcast") == 1); $user->save(); $this->returnJson([ diff --git a/Web/Presenters/templates/Audio/tabs.xml b/Web/Presenters/templates/Audio/tabs.xml index 96db234e..3f58c94f 100644 --- a/Web/Presenters/templates/Audio/tabs.xml +++ b/Web/Presenters/templates/Audio/tabs.xml @@ -20,5 +20,20 @@ {if $ownerId > 0}{_playlists_user}{else}{_playlists_club}{/if} {_new_playlist} {/if} + + {if $friendsAudios} +
+ +
+ + +
+ {$fr["name"]} + {$fr["nowListening"] ? $fr["nowListening"]->getName() : tr("audios_count", $fr["tracksCount"])} +
+
+
+
+ {/if} \ No newline at end of file diff --git a/Web/Presenters/templates/User/Edit.xml b/Web/Presenters/templates/User/Edit.xml index b8e8398c..0a28b879 100644 --- a/Web/Presenters/templates/User/Edit.xml +++ b/Web/Presenters/templates/User/Edit.xml @@ -160,6 +160,13 @@ + + + + + + + diff --git a/Web/Presenters/templates/User/Settings.xml b/Web/Presenters/templates/User/Settings.xml index 8da520ce..3ddf4561 100644 --- a/Web/Presenters/templates/User/Settings.xml +++ b/Web/Presenters/templates/User/Settings.xml @@ -390,9 +390,10 @@ diff --git a/Web/Presenters/templates/User/View.xml b/Web/Presenters/templates/User/View.xml index 58a9c33e..f5cb6176 100644 --- a/Web/Presenters/templates/User/View.xml +++ b/Web/Presenters/templates/User/View.xml @@ -417,6 +417,10 @@
+
@@ -425,11 +429,20 @@

{$user->getFullName()}

- {if !is_null($user->getStatus())} -
{$user->getStatus()}
- {elseif $thatIsThisUser} -
-
[ {_change_status} ]
+ + {if !$audioStatus} + {if !is_null($user->getStatus())} +
{$user->getStatus()}
+ {elseif $thatIsThisUser} +
+
[ {_change_status} ]
+
+ {/if} + {else} +
+
+ {$audioStatus->getName()} +
{/if}
@@ -594,7 +607,7 @@
-
+
{_audios}
@@ -758,12 +771,14 @@ async function changeStatus() { const status = document.status_popup_form.status.value; + const broadcast = document.status_popup_form.broadcast.checked; document.status_popup_form.submit.innerHTML = "
"; document.status_popup_form.submit.disabled = true; const formData = new FormData(); formData.append("status", status); + formData.append("broadcast", Number(broadcast)); formData.append("hash", document.status_popup_form.hash.value); const response = await ky.post("/edit?act=status", {body: formData}); diff --git a/Web/static/css/audios.css b/Web/static/css/audios.css index 7db5a3fa..6d08d861 100644 --- a/Web/static/css/audios.css +++ b/Web/static/css/audios.css @@ -566,4 +566,86 @@ z-index: 199; width: 156px; margin-top: -65px !important; +} + +.audiosSearchBox input[type='search'] { + height: 25px; + width: 77%; + padding-left: 21px; + padding-top: 4px; + background: rgb(255, 255, 255) url("/assets/packages/static/openvk/img/search_icon.png") 5px 6px no-repeat; +} + +.audiosSearchBox { + padding-bottom: 10px; + padding-top: 7px; + display: flex; +} + +.audiosSearchBox select { + width: 30%; + padding-left: 7px; + margin-left: -2px; +} + +.audioStatus { + color: #2B587A; + margin-top: -3px; +} + +.audioStatus::before { + background-image: url('/assets/packages/static/openvk/img/audios_controls.png'); + background-repeat: no-repeat; + width: 11px; + height: 11px; + background-position: -66px -51px; + margin-top: 1px; + display: inline-block; + vertical-align: bottom; + content: ""; + padding-right: 2px; +} + +.friendsAudiosList { + margin-left: -7px; + margin-top: 8px; +} + +.friendsAudiosList .elem { + display: flex; + padding: 1px 1px; + width: 100%; +} + +.friendsAudiosList .elem img { + width: 30px; + border-radius: 2px; +} + +.friendsAudiosList .elem .additionalInfo { + margin-left: 7px; + padding-top: 1px; + width: 100%; + display: flex; + flex-direction: column; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.friendsAudiosList .elem .additionalInfo .name { + color: #2B587A; +} + +.friendsAudiosList .elem .additionalInfo .desc { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + color: #878A8F; + font-size: 11px; +} + +.friendsAudiosList .elem:hover { + background: #E8EBF0; + cursor: pointer; } \ No newline at end of file diff --git a/Web/static/js/al_music.js b/Web/static/js/al_music.js index d0bd604d..3b4bc170 100644 --- a/Web/static/js/al_music.js +++ b/Web/static/js/al_music.js @@ -96,8 +96,9 @@ class bigPlayer { this.nodes["thisPlayer"] = document.querySelector(".bigPlayer") this.nodes["thisPlayer"].classList.add("lagged") + this.nodes["audioPlayer"] = document.createElement("audio") - this.player = () => { return this.nodes["thisPlayer"].querySelector("audio.audio") } + this.player = () => { return this.nodes["audioPlayer"] } this.nodes["playButtons"] = this.nodes["thisPlayer"].querySelector(".playButtons") this.nodes["dashPlayer"] = dashjs.MediaPlayer().create() @@ -365,34 +366,62 @@ class bigPlayer { u(this.player()).on("ended", (e) => { e.preventDefault() - - let playlist = this.context.context_type == "playlist_context" ? this.context.context_id : null - - $.ajax({ - type: "POST", - url: `/audio${this.tracks["currentTrack"].id}/listen`, - data: { - hash: u("meta[name=csrf]").attr("value"), - playlist: playlist - }, - success: (response) => { - if(response.success) { - console.info("Listen is counted.") - - if(response.new_playlists_listens) - document.querySelector("#listensCount").innerHTML = tr("listens_count", response.new_playlists_listens) - } else - console.info("Listen is not counted.") - } - }) + // в начало очереди if(!this.tracks.nextTrack) { - this.setTrack(this.tracks.tracks[0].id) + if(!this.context["playedPages"].includes("1")) { + $.ajax({ + type: "POST", + url: "/audios/context", + data: { + context: this["context"].context_type, + context_entity: this["context"].context_id, + hash: u("meta[name=csrf]").attr("value"), + page: 1 + }, + success: (response_2) => { + this.tracks["tracks"] = response_2["items"].concat(this.tracks["tracks"]) + this.context["playedPages"].push(String(1)) + + this.setTrack(this.tracks["tracks"][0].id) + } + }) + } else { + this.setTrack(this.tracks.tracks[0].id) + } + return } this.showNextTrack() }) + + u(this.player()).on("loadstart", (e) => { + let playlist = this.context.context_type == "playlist_context" ? this.context.context_id : null + + let tempThisId = this.tracks.currentTrack.id + setTimeout(() => { + if(tempThisId != this.tracks.currentTrack.id) return + + $.ajax({ + type: "POST", + url: `/audio${this.tracks["currentTrack"].id}/listen`, + data: { + hash: u("meta[name=csrf]").attr("value"), + playlist: playlist + }, + success: (response) => { + if(response.success) { + console.info("Listen is counted.") + + if(response.new_playlists_listens) + document.querySelector("#listensCount").innerHTML = tr("listens_count", response.new_playlists_listens) + } else + console.info("Listen is not counted.") + } + }) + }, 2000) + }) if(localStorage.volume != null && localStorage.volume < 1 && localStorage.volume > 0) this.player().volume = localStorage.volume @@ -537,7 +566,7 @@ class bigPlayer { else this.tracks["tracks"] = this.tracks["tracks"].concat(newArr["items"]) - this.context["playedPages"].push(Number(newArr["page"])) + this.context["playedPages"].push(String(newArr["page"])) if(lesser) this.tracks["previousTrack"] = this.tracks["tracks"].at(this.tracks["tracks"].indexOf(obj) - 1).id @@ -1310,7 +1339,7 @@ $(document).on("click", ".audiosContainer .paginator a", (e) => { }, success: (response_2) => { window.player.tracks["tracks"] = window.player.tracks["tracks"].concat(response_2["items"]) - window.player.context["playedPages"].push(page) + window.player.context["playedPages"].push(String(page)) console.info("Page is switched") } }) diff --git a/Web/static/js/al_playlists.js b/Web/static/js/al_playlists.js index 1316e96b..443e6f30 100644 --- a/Web/static/js/al_playlists.js +++ b/Web/static/js/al_playlists.js @@ -6,7 +6,7 @@ if(document.querySelector("#editPlaylistForm")) { context_id = document.querySelector("#editPlaylistForm").dataset.id } -if(document.querySelector(".showMoreAudiosPlaylist").dataset.club != null) { +if(document.querySelector(".showMoreAudiosPlaylist") && document.querySelector(".showMoreAudiosPlaylist").dataset.club != null) { context_type = "entity_audios" context_id = Number(document.querySelector(".showMoreAudiosPlaylist").dataset.club) * -1 } diff --git a/install/sqls/gamma-00000-disco.sql b/install/sqls/gamma-00000-disco.sql index df7a2054..3783c05f 100644 --- a/install/sqls/gamma-00000-disco.sql +++ b/install/sqls/gamma-00000-disco.sql @@ -91,4 +91,5 @@ CREATE TABLE IF NOT EXISTS `playlist_relations` ( KEY `audio` (`media`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; -ALTER TABLE `groups` ADD `everyone_can_upload_audios` TINYINT(1) NOT NULL DEFAULT '0' AFTER `backdrop_2`; \ No newline at end of file +ALTER TABLE `groups` ADD `everyone_can_upload_audios` TINYINT(1) NOT NULL DEFAULT '0' AFTER `backdrop_2`; +ALTER TABLE `profiles` ADD `last_played_track` BIGINT(20) UNSIGNED NULL DEFAULT NULL AFTER `client_name`, ADD `audio_broadcast_enabled` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `last_played_track`; \ No newline at end of file diff --git a/locales/en.strings b/locales/en.strings index 2e35498f..c1077a36 100644 --- a/locales/en.strings +++ b/locales/en.strings @@ -568,7 +568,7 @@ "privacy_setting_add_to_friends" = "Who can add me to friends"; "privacy_setting_write_wall" = "Who can publish post on my wall"; "privacy_setting_write_messages" = "Who can write messages to me"; -"privacy_setting_view_audio" = "Who can view my audios"; +"privacy_setting_view_audio" = "Who can see my audios"; "privacy_value_anybody" = "Anybody"; "privacy_value_anybody_dative" = "Anybody"; "privacy_value_users" = "OpenVK users"; @@ -830,6 +830,8 @@ "no_access_clubs" = "There are no groups where you are an administrator."; "audio_successfully_uploaded" = "Audio has been successfully uploaded and is currently being processed."; +"broadcast_audio" = "Broadcast audio to status"; + /* Notifications */ "feedback" = "Feedback"; diff --git a/locales/ru.strings b/locales/ru.strings index ab9d9a02..e0e66b84 100644 --- a/locales/ru.strings +++ b/locales/ru.strings @@ -541,7 +541,7 @@ "privacy_setting_add_to_friends" = "Кто может называть меня другом"; "privacy_setting_write_wall" = "Кто может писать у меня на стене"; "privacy_setting_write_messages" = "Кто может писать мне сообщения"; -"privacy_setting_view_audio" = "Кто может видеть мои аудиозаписи"; +"privacy_setting_view_audio" = "Кому видно мои аудиозаписи"; "privacy_value_anybody" = "Все желающие"; "privacy_value_anybody_dative" = "Всем желающим"; "privacy_value_users" = "Пользователям OpenVK"; @@ -785,6 +785,8 @@ "no_access_clubs" = "Нет групп, где вы являетесь администратором."; "audio_successfully_uploaded" = "Аудио успешно загружено и на данный момент обрабатывается."; +"broadcast_audio" = "Транслировать аудио в статус"; + /* Notifications */ "feedback" = "Ответы";