Implement audiostatuses

Добавлены аудиостатусы (у пользователей), блок с друзьями, слушающих музыку на странице аудиозаписей, объект status_audio в users.get, улучшены настройки приватности и ещё что-то
This commit is contained in:
lalka2018 2023-11-01 15:09:05 +03:00
parent a4583c6b22
commit ef26b1044a
19 changed files with 298 additions and 48 deletions

View file

@ -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)

View file

@ -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();
}
}

View file

@ -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)

View file

@ -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;

View file

@ -421,4 +421,5 @@ class Club extends RowModel
use Traits\TBackDrops;
use Traits\TSubscribable;
use Traits\TAudioStatuses;
}

View file

@ -0,0 +1,37 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Traits;
use openvk\Web\Models\Repositories\Audios;
use Chandler\Database\DatabaseConnection;
trait TAudioStatuses
{
function isBroadcastEnabled(): bool
{
return (bool) $this->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;
}
}

View file

@ -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;
}

View file

@ -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

View file

@ -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([

View file

@ -20,5 +20,20 @@
<a n:attr="id => $mode === 'playlists' && $ownerId != $thisUser->getId() ? 'used' : 'ki'" href="/playlists{$ownerId}" n:if="isset($ownerId) && !$isMy">{if $ownerId > 0}{_playlists_user}{else}{_playlists_club}{/if}</a>
<a href="/audios/newPlaylist{if $isMyClub}?gid={abs($ownerId)}{/if}" n:if="isset($thisUser) && $isMyClub">{_new_playlist}</a>
{/if}
{if $friendsAudios}
<div class="friendsAudiosList">
<a href="/audios{$fr['id']}" style="width: 94%;padding-left: 10px;" n:foreach="$friendsAudios as $fr">
<div class="elem">
<img src="{$fr['avatar']}" />
<div class="additionalInfo">
<span class="name">{$fr["name"]}</span>
<span class="desc">{$fr["nowListening"] ? $fr["nowListening"]->getName() : tr("audios_count", $fr["tracksCount"])}</span>
</div>
</div>
</a>
</div>
{/if}
</div>
</div>

View file

@ -160,6 +160,13 @@
</select>
</td>
</tr>
<tr>
<td width="120" valign="top">
</td>
<td>
<label><input type="checkbox" name="broadcast_music" n:attr="checked => $user->isBroadcastEnabled()">{_broadcast_audio}</label>
</td>
</tr>
<tr>
<td>

View file

@ -390,9 +390,10 @@
</td>
<td>
<select name="audios.read" style="width: 164px;">
<option value="2" {if $user->getPrivacySetting('audios.read') == 2}selected{/if}>{_privacy_value_anybody}</option>
<option value="1" {if $user->getPrivacySetting('audios.read') == 1}selected{/if}>{_privacy_value_friends}</option>
<option value="0" {if $user->getPrivacySetting('audios.read') == 0}selected{/if}>{_privacy_value_nobody}</option>
<option value="3" {if $user->getPrivacySetting('audios.read') == 3}selected{/if}>{_privacy_value_anybody_dative}</option>
<option value="2" {if $user->getPrivacySetting('audios.read') == 2}selected{/if}>{_privacy_value_users}</option>
<option value="1" {if $user->getPrivacySetting('audios.read') == 1}selected{/if}>{_privacy_value_friends_dative}</option>
<option value="0" {if $user->getPrivacySetting('audios.read') == 0}selected{/if}>{_privacy_value_only_me_dative}</option>
</select>
</td>
</tr>

View file

@ -417,6 +417,10 @@
<form name="status_popup_form" onsubmit="changeStatus(); return false;">
<div style="margin-bottom: 10px;">
<input type="text" name="status" size="50" value="{$user->getStatus()}" />
<label style="width: 316px;display: block;">
<input type="checkbox" name="broadcast" n:attr="checked => $user->isBroadcastEnabled()" />
{_broadcast_audio}
</label>
</div>
<input type="hidden" name="hash" value="{$csrfToken}" />
<button type="submit" name="submit" class="button" style="height: 22px;">{_send}</button>
@ -425,11 +429,20 @@
<div class="accountInfo clearFix">
<div class="profileName">
<h2>{$user->getFullName()}</h2>
{if !is_null($user->getStatus())}
<div n:class="page_status, $thatIsThisUser ? page_status_edit_button" n:attr="id => $thatIsThisUser ? page_status_text : NULL">{$user->getStatus()}</div>
{elseif $thatIsThisUser}
<div class="page_status">
<div n:class="edit_link, $thatIsThisUser ? page_status_edit_button" id="page_status_text">[ {_change_status} ]</div>
{if !$audioStatus}
{if !is_null($user->getStatus())}
<div n:class="page_status, $thatIsThisUser ? page_status_edit_button" n:attr="id => $thatIsThisUser ? page_status_text : NULL">{$user->getStatus()}</div>
{elseif $thatIsThisUser}
<div class="page_status">
<div n:class="edit_link, $thatIsThisUser ? page_status_edit_button" id="page_status_text">[ {_change_status} ]</div>
</div>
{/if}
{else}
<div class="page_status" style="display: flex;">
<div n:class="audioStatus, $thatIsThisUser ? page_status_edit_button" id="page_status_text">
{$audioStatus->getName()}
</div>
</div>
{/if}
</div>
@ -594,7 +607,7 @@
</div>
</div>
<div n:if="$audiosCount > 0">
<div n:if="$audiosCount > 0 && $user->getPrivacyPermission('audios.read', $thisUser ?? NULL)">
<div class="content_title_expanded" onclick="hidePanel(this, {$audiosCount});">
{_audios}
</div>
@ -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 = "<div class=\"button-loading\"></div>";
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});

View file

@ -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;
}

View file

@ -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")
}
})

View file

@ -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
}

View file

@ -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`;
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`;

View file

@ -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";

View file

@ -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" = "Ответы";