mirror of
https://github.com/openvk/openvk
synced 2024-12-22 08:31:18 +03:00
Notifications: WIP: Add ajax notifications :D
This commit is contained in:
parent
931f2a1149
commit
0b7b67e2e7
10 changed files with 244 additions and 27 deletions
83
ServiceAPI/Notifications.php
Normal file
83
ServiceAPI/Notifications.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\ServiceAPI;
|
||||
use Latte\Engine as TemplatingEngine;
|
||||
use RdKafka\{Conf as RDKConf, KafkaConsumer};
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use openvk\Web\Models\Repositories\{Notifications as N};
|
||||
|
||||
class Notifications implements Handler
|
||||
{
|
||||
protected $user;
|
||||
protected $notifs;
|
||||
|
||||
function __construct(?User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->notifs = new N;
|
||||
}
|
||||
|
||||
function ack(callable $resolve, callable $reject): void
|
||||
{
|
||||
$this->user->updateNotificationOffset();
|
||||
$this->user->save();
|
||||
$resolve("OK");
|
||||
}
|
||||
|
||||
function fetch(callable $resolve, callable $reject): void
|
||||
{
|
||||
$kafkaConf = OPENVK_ROOT_CONF["openvk"]["credentials"]["notificationsBroker"];
|
||||
if(!$kafkaConf["enable"]) {
|
||||
$reject(1999, "Disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
$kafkaConf = $kafkaConf["kafka"];
|
||||
$conf = new RDKConf();
|
||||
$conf->set("metadata.broker.list", $kafkaConf["addr"] . ":" . $kafkaConf["port"]);
|
||||
$conf->set("group.id", "UserFetch-" . $this->user->getId()); # Чтобы уведы приходили только на разные устройства одного чебупелика
|
||||
$conf->set("auto.offset.reset", "latest");
|
||||
|
||||
set_time_limit(30);
|
||||
$consumer = new KafkaConsumer($conf);
|
||||
$consumer->subscribe([ $kafkaConf["topic"] ]);
|
||||
|
||||
while(true) {
|
||||
$message = $consumer->consume(30*1000);
|
||||
switch ($message->err) {
|
||||
case RD_KAFKA_RESP_ERR_NO_ERROR:
|
||||
$descriptor = $message->payload;
|
||||
[,$user,] = explode(",", $descriptor);
|
||||
if(((int) $user) === $this->user->getId()) {
|
||||
$data = (object) [];
|
||||
$notification = $this->notifs->fromDescriptor($descriptor, $data);
|
||||
if(!$notification) {
|
||||
$reject(1982, "Server Error");
|
||||
return;
|
||||
}
|
||||
|
||||
$tplDir = __DIR__ . "/../Web/Presenters/templates/components/notifications/";
|
||||
$tplId = "$tplDir$data->actionCode/_$data->originModelType" . "_" . $data->targetModelType . "_.xml";
|
||||
$latte = new TemplatingEngine;
|
||||
$latte->setTempDirectory(CHANDLER_ROOT . "/tmp/cache/templates");
|
||||
$latte->addFilter("translate", fn($trId) => tr($trId));
|
||||
$resolve([
|
||||
"title" => tr("notif_" . $data->actionCode . "_" . $data->originModelType . "_" . $data->targetModelType),
|
||||
"body" => trim(preg_replace('%(\s){2,}%', "$1", $latte->renderToString($tplId, ["notification" => $notification]))),
|
||||
"ava" => $notification->getModel(1)->getAvatarUrl(),
|
||||
"priority" => 1,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
case RD_KAFKA_RESP_ERR__TIMED_OUT:
|
||||
case RD_KAFKA_RESP_ERR__PARTITION_EOF:
|
||||
$reject(1983, "Nothing to report");
|
||||
break 2;
|
||||
default:
|
||||
$reject(1981, "Kafka Error: " . $message->errstr());
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ namespace openvk\Web\Models\Entities\Notifications;
|
|||
use openvk\Web\Models\RowModel;
|
||||
use openvk\Web\Models\Entities\{User};
|
||||
use openvk\Web\Util\DateTime;
|
||||
use RdKafka\{Conf, Producer};
|
||||
|
||||
class Notification
|
||||
{
|
||||
|
@ -80,14 +81,14 @@ class Notification
|
|||
return false;
|
||||
|
||||
$data = [
|
||||
$this->recipient->getId(),
|
||||
$this->encodeType($this->originModel),
|
||||
$this->originModel->getId(),
|
||||
$this->encodeType($this->targetModel),
|
||||
$this->targetModel->getId(),
|
||||
$this->actionCode,
|
||||
$this->data,
|
||||
$this->time,
|
||||
"recipient" => $this->recipient->getId(),
|
||||
"originModelType" => $this->encodeType($this->originModel),
|
||||
"originModelId" => $this->originModel->getId(),
|
||||
"targetModelType" => $this->encodeType($this->targetModel),
|
||||
"targetModelId" => $this->targetModel->getId(),
|
||||
"actionCode" => $this->actionCode,
|
||||
"additionalPayload" => $this->data,
|
||||
"timestamp" => $this->time,
|
||||
];
|
||||
|
||||
$edb = $e->getConnection();
|
||||
|
@ -96,12 +97,33 @@ class Notification
|
|||
$query = <<<'QUERY'
|
||||
SELECT * FROM `notifications` WHERE `recipientType`=0 AND `recipientId`=? AND `originModelType`=? AND `originModelId`=? AND `targetModelType`=? AND `targetModelId`=? AND `modelAction`=? AND `additionalData`=? AND `timestamp` > (? - ?)
|
||||
QUERY;
|
||||
$result = $edb->query($query, ...array_merge($data, [ $this->threshold ]));
|
||||
$result = $edb->query($query, ...array_merge(array_values($data), [ $this->threshold ]));
|
||||
if($result->getRowCount() > 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
$edb->query("INSERT INTO notifications VALUES (0, ?, ?, ?, ?, ?, ?, ?, ?)", ...array_values($data));
|
||||
|
||||
$edb->query("INSERT INTO notifications VALUES (0, ?, ?, ?, ?, ?, ?, ?, ?)", ...$data);
|
||||
$kafkaConf = OPENVK_ROOT_CONF["openvk"]["credentials"]["notificationsBroker"];
|
||||
if($kafkaConf["enable"]) {
|
||||
$kafkaConf = $kafkaConf["kafka"];
|
||||
$brokerConf = new Conf();
|
||||
$brokerConf->set("log_level", (string) LOG_DEBUG);
|
||||
$brokerConf->set("debug", "all");
|
||||
|
||||
$producer = new Producer($brokerConf);
|
||||
$producer->addBrokers($kafkaConf["addr"] . ":" . $kafkaConf["port"]);
|
||||
|
||||
$descriptor = implode(",", [
|
||||
str_replace("\\", ".", get_class($this)),
|
||||
$this->recipient->getId(),
|
||||
base64_encode(serialize((object) $data)),
|
||||
]);
|
||||
|
||||
$notifTopic = $producer->newTopic($kafkaConf["topic"]);
|
||||
$notifTopic->produce(RD_KAFKA_PARTITION_UA, RD_KAFKA_MSG_F_BLOCK, $descriptor);
|
||||
$producer->flush(100);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -43,6 +43,19 @@ class Notifications
|
|||
return $query;
|
||||
}
|
||||
|
||||
private function assemble(int $act, int $originModelType, int $originModelId, int $targetModelType, int $targetModelId, int $recipientId, int $timestamp, $data, ?string $class = NULL): Notification
|
||||
{
|
||||
$class ??= 'openvk\Web\Models\Entities\Notifications\Notification';
|
||||
|
||||
$originModel = $this->getModel($originModelType, $originModelId);
|
||||
$targetModel = $this->getModel($targetModelType, $targetModelId);
|
||||
$recipient = (new Users)->get($recipientId);
|
||||
|
||||
$notification = new $class($recipient, $originModel, $targetModel, $timestamp, $data);
|
||||
$notification->setActionCode($act);
|
||||
return $notification;
|
||||
}
|
||||
|
||||
function getNotificationCountByUser(User $user, int $offset, bool $archived = false): int
|
||||
{
|
||||
$db = $this->getEDB(false);
|
||||
|
@ -64,13 +77,38 @@ class Notifications
|
|||
|
||||
$results = $this->getEDB()->query($this->getQuery($user, false, $offset, $archived, $page, $perPage));
|
||||
foreach($results->fetchAll() as $notif) {
|
||||
$originModel = $this->getModel($notif->originModelType, $notif->originModelId);
|
||||
$targetModel = $this->getModel($notif->targetModelType, $notif->targetModelId);
|
||||
$recipient = (new Users)->get($notif->recipientId);
|
||||
|
||||
$notification = new Notification($recipient, $originModel, $targetModel, $notif->timestamp, $notif->additionalData);
|
||||
$notification->setActionCode($notif->modelAction);
|
||||
yield $notification;
|
||||
yield $this->assemble(
|
||||
$notif->modelAction,
|
||||
$notif->originModelType,
|
||||
$notif->originModelId,
|
||||
|
||||
$notif->targetModelType,
|
||||
$notif->targetModelId,
|
||||
|
||||
$notif->recipientId,
|
||||
$notif->timestamp,
|
||||
$notif->additionalData
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function fromDescriptor(string $descriptor, ?object &$parsedData = nullptr)
|
||||
{
|
||||
[$class, $recv, $data] = explode(",", $descriptor);
|
||||
$class = str_replace(".", "\\", $class);
|
||||
|
||||
$parsedData = unserialize(base64_decode($data));
|
||||
return $this->assemble(
|
||||
$parsedData->actionCode,
|
||||
$parsedData->originModelType,
|
||||
$parsedData->originModelId,
|
||||
|
||||
$parsedData->targetModelType,
|
||||
$parsedData->targetModelId,
|
||||
|
||||
$parsedData->recipient,
|
||||
$parsedData->timestamp,
|
||||
$parsedData->additionalPayload,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,8 +58,8 @@ final class InternalAPIPresenter extends OpenVKPresenter
|
|||
try {
|
||||
$params = array_merge($input->params ?? [], [function($data) {
|
||||
$this->succ($data);
|
||||
}, function($data) {
|
||||
$this->fail($data);
|
||||
}, function(int $errno, string $errstr) {
|
||||
$this->fail($errno, $errstr);
|
||||
}]);
|
||||
$handler->{$method}(...$params);
|
||||
} catch(\TypeError $te) {
|
||||
|
@ -68,4 +68,4 @@ final class InternalAPIPresenter extends OpenVKPresenter
|
|||
$this->fail(-32603, "Uncaught " . get_class($ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -252,12 +252,13 @@
|
|||
<a href="/privacy" class="link">{_footer_privacy}</a>
|
||||
</div>
|
||||
<p>OpenVK <a href="/about:openvk2">{php echo OPENVK_VERSION}</a> | PHP: {phpversion()} | DB: {$dbVersion}</p>
|
||||
<p n:ifcontent>
|
||||
<p n:ifcontent="ifcontent">
|
||||
{php echo OPENVK_ROOT_CONF["openvk"]["appearance"]["motd"]}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script src="https://rawgit.com/kawanet/msgpack-lite/master/dist/msgpack.min.js"></script>
|
||||
{script "js/node_modules/msgpack-lite/dist/msgpack.min.js"}
|
||||
{script "js/node_modules/soundjs/lib/soundjs.min.js"}
|
||||
{script "js/node_modules/ky/umd.js"}
|
||||
{script "js/messagebox.js"}
|
||||
{script "js/notifications.js"}
|
||||
|
@ -265,6 +266,10 @@
|
|||
{script "js/al_wall.js"}
|
||||
{script "js/al_api.js"}
|
||||
|
||||
{ifset $thisUser}
|
||||
{script "js/al_notifs.js"}
|
||||
{/ifset}
|
||||
|
||||
<script src="https://unpkg.com/fartscroll@1.0.0/fartscroll.js"></script>
|
||||
<script n:if="OPENVK_ROOT_CONF['openvk']['preferences']['bellsAndWhistles']['fartscroll']">
|
||||
fartscroll(400);
|
||||
|
|
|
@ -4,8 +4,3 @@
|
|||
<a href="{$user->getURL()}"><b>{$user->getCanonicalName()}</b></a>
|
||||
{$notification->getDateTime()} {_nt_liked_yours}
|
||||
<a href="/wall{$post->getPrettyId()}"><b>{_nt_post_nominative}</b></a> {_nt_from} {$post->getPublicationTime()}.
|
||||
|
||||
<?php
|
||||
// костыльно скрыл лол, сами исправите проблему - гфх
|
||||
//{tr('notifications_like', '<a href="'.$user->getURL().'"><b>'.$user->getCanonicalName().'</b></a>', '<a href="/wall'.$post->getPrettyId().'"><b>', '</b></a>', $post->getPublicationTime())}
|
||||
?>
|
||||
|
|
BIN
Web/static/audio/Bruh.mp3
Normal file
BIN
Web/static/audio/Bruh.mp3
Normal file
Binary file not shown.
37
Web/static/js/al_notifs.js
Normal file
37
Web/static/js/al_notifs.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
createjs.Sound.registerSound("/assets/packages/static/openvk/audio/Bruh.mp3", "notification");
|
||||
|
||||
function __actualPlayNotifSound() {
|
||||
createjs.Sound.play("notification");
|
||||
}
|
||||
|
||||
window.playNotifSound = Function.noop;
|
||||
|
||||
async function setupNotificationListener() {
|
||||
console.warn("Setting up notifications listener...");
|
||||
|
||||
while(true) {
|
||||
let notif;
|
||||
try {
|
||||
notif = await API.Notifications.fetch();
|
||||
} catch(rejection) {
|
||||
if(rejection.message !== "Nothing to report") {
|
||||
console.error(rejection);
|
||||
break;
|
||||
}
|
||||
|
||||
console.info("No new notifications discovered... Redialing event broker");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
playNotifSound();
|
||||
NewNotification(notif.title, notif.body, notif.ava, Function.noop, notif.priority * 6000);
|
||||
console.info("New notification", notif);
|
||||
|
||||
API.Notifications.ack();
|
||||
}
|
||||
};
|
||||
|
||||
setupNotificationListener();
|
||||
|
||||
u(document.body).on("click", () => window.playNotifSound = window.__actualPlayNotifSound);
|
|
@ -7,11 +7,13 @@
|
|||
"ky": "^0.19.0",
|
||||
"literallycanvas": "^0.5.2",
|
||||
"monaco-editor": "^0.20.0",
|
||||
"msgpack-lite": "^0.1.26",
|
||||
"plotly.js-dist": "^1.52.3",
|
||||
"react": "15.1",
|
||||
"react-dom": "15.1",
|
||||
"react-dom-factories": "^1.0.2",
|
||||
"requirejs": "^2.3.6",
|
||||
"soundjs": "^1.0.1",
|
||||
"umbrellajs": "^3.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,11 @@ encoding@^0.1.11:
|
|||
dependencies:
|
||||
iconv-lite "^0.6.2"
|
||||
|
||||
event-lite@^0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/event-lite/-/event-lite-0.1.2.tgz#838a3e0fdddef8cc90f128006c8e55a4e4e4c11b"
|
||||
integrity sha512-HnSYx1BsJ87/p6swwzv+2v6B4X+uxUteoDfRxsAb1S1BePzQqOLevVmkdA15GHJVd9A9Ok6wygUR18Hu0YeV9g==
|
||||
|
||||
fancy-file-input@~2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/fancy-file-input/-/fancy-file-input-2.0.4.tgz#698c216482e07649a827681c4db3054fddc9a32b"
|
||||
|
@ -91,11 +96,26 @@ iconv-lite@^0.6.2:
|
|||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3.0.0"
|
||||
|
||||
ieee754@^1.1.8:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
|
||||
int64-buffer@^0.1.9:
|
||||
version "0.1.10"
|
||||
resolved "https://registry.yarnpkg.com/int64-buffer/-/int64-buffer-0.1.10.tgz#277b228a87d95ad777d07c13832022406a473423"
|
||||
integrity sha1-J3siiofZWtd30HwTgyAiQGpHNCM=
|
||||
|
||||
is-stream@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
||||
|
||||
isarray@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||
|
||||
isomorphic-fetch@^2.1.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
|
||||
|
@ -148,6 +168,16 @@ monaco-editor@^0.20.0:
|
|||
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.20.0.tgz#5d5009343a550124426cb4d965a4d27a348b4dea"
|
||||
integrity sha512-hkvf4EtPJRMQlPC3UbMoRs0vTAFAYdzFQ+gpMb8A+9znae1c43q8Mab9iVsgTcg/4PNiLGGn3SlDIa8uvK1FIQ==
|
||||
|
||||
msgpack-lite@^0.1.26:
|
||||
version "0.1.26"
|
||||
resolved "https://registry.yarnpkg.com/msgpack-lite/-/msgpack-lite-0.1.26.tgz#dd3c50b26f059f25e7edee3644418358e2a9ad89"
|
||||
integrity sha1-3TxQsm8FnyXn7e42REGDWOKprYk=
|
||||
dependencies:
|
||||
event-lite "^0.1.1"
|
||||
ieee754 "^1.1.8"
|
||||
int64-buffer "^0.1.9"
|
||||
isarray "^1.0.0"
|
||||
|
||||
node-fetch@^1.0.1:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
|
||||
|
@ -229,6 +259,11 @@ skatejs@0.13.17:
|
|||
resolved "https://registry.yarnpkg.com/skatejs/-/skatejs-0.13.17.tgz#7a21fbb3434da45e52b47b61647168ee9e778071"
|
||||
integrity sha1-eiH7s0NNpF5StHthZHFo7p53gHE=
|
||||
|
||||
soundjs@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/soundjs/-/soundjs-1.0.1.tgz#99970542d28d0df2a1ebd061ae75c961a98c8180"
|
||||
integrity sha512-MgFPvmKYfpcNiE3X5XybNvScie3DMQlZgmNzUn4puBcpw64f4LqjH/fhM8Sb/eTJ8hK57Crr7mWy0bfJOqPj6Q==
|
||||
|
||||
trim-extra-html-whitespace@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/trim-extra-html-whitespace/-/trim-extra-html-whitespace-1.3.0.tgz#b47efb0d1a5f2a56a85cc45cea525651e93404cf"
|
||||
|
|
Loading…
Reference in a new issue