1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6namespace App\Libraries;
7
8use App\Exceptions\API;
9use App\Exceptions\InvariantException;
10use App\Models\Chat\Channel;
11use App\Models\User;
12use LaravelRedis as Redis;
13
14class Chat
15{
16 public static function ack(User $user)
17 {
18 $channelIds = $user->channels()->public()->pluck((new Channel())->qualifyColumn('channel_id'));
19 $userId = $user->getKey();
20 $timestamp = time();
21 $transaction = Redis::transaction();
22
23 foreach ($channelIds as $channelId) {
24 Channel::ack($channelId, $userId, $timestamp, $transaction);
25 }
26
27 $transaction->exec();
28 }
29
30 public static function createAnnouncement(User $sender, array $rawParams)
31 {
32 priv_check_user($sender, 'ChatAnnounce')->ensureCan();
33
34 $params = get_params($rawParams, null, [
35 'channel:array',
36 'message:string',
37 'target_ids:int[]',
38 'uuid',
39 ], ['null_missing' => true]);
40
41 if (!isset($params['target_ids'])) {
42 throw new InvariantException('missing target_ids parameter');
43 }
44
45 if (!isset($params['channel'])) {
46 throw new InvariantException('missing channel parameter');
47 }
48
49 $users = User::find($params['target_ids']);
50 if ($users->isEmpty()) {
51 throw new InvariantException('Nobody to broadcast to!');
52 }
53
54 $users = $users->push($sender)->uniqueStrict('user_id');
55
56 $channel = (new Channel())->getConnection()->transaction(function () use ($sender, $params, $users) {
57 $channel = Channel::createAnnouncement($users, $params['channel'], $params['uuid']);
58 static::sendMessage($sender, $channel, $params['message'], false);
59
60 return $channel;
61 });
62
63 return $channel;
64 }
65
66 // Do the restricted user lookup before calling this.
67 public static function sendPrivateMessage(User $sender, User $target, ?string $message, ?bool $isAction, ?string $uuid = null)
68 {
69 if ($target->is($sender)) {
70 abort(422, "can't send message to same user");
71 }
72
73 priv_check_user($sender, 'ChatPmStart', $target)->ensureCan();
74
75 return (new Channel())->getConnection()->transaction(function () use ($sender, $target, $message, $isAction, $uuid) {
76 $channel = Channel::findPM($target, $sender) ?? Channel::createPM($target, $sender);
77
78 return static::sendMessage($sender, $channel, $message, $isAction, $uuid);
79 });
80 }
81
82 public static function sendMessage(User $sender, Channel $channel, ?string $message, ?bool $isAction, ?string $uuid = null)
83 {
84 if ($channel->isPM()) {
85 // restricted users should be treated as if they do not exist
86 if (optional($channel->pmTargetFor($sender))->isRestricted()) {
87 abort(404, 'target user not found');
88 }
89 }
90
91 priv_check_user($sender, 'ChatChannelSend', $channel)->ensureCan();
92
93 try {
94 return $channel->receiveMessage($sender, $message, $isAction ?? false, $uuid);
95 } catch (API\ChatMessageEmptyException $e) {
96 abort(422, $e->getMessage());
97 } catch (API\ChatMessageTooLongException $e) {
98 abort(422, $e->getMessage());
99 } catch (API\ExcessiveChatMessagesException $e) {
100 abort(429, $e->getMessage());
101 }
102 }
103}