fix(chat): Remove commands

Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Joas Schilling 2024-04-12 14:41:53 +02:00
parent a2bc199716
commit cc4e29258c
No known key found for this signature in database
GPG key ID: 74434EFE0D2E2205
38 changed files with 40 additions and 2244 deletions

View file

@ -73,13 +73,9 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m
<step>OCA\Talk\Migration\FixNamespaceInDatabaseTables</step>
</pre-migration>
<post-migration>
<step>OCA\Talk\Migration\CreateHelpCommand</step>
<step>OCA\Talk\Migration\ClearResourceAccessCache</step>
<step>OCA\Talk\Migration\CacheUserDisplayNames</step>
</post-migration>
<install>
<step>OCA\Talk\Migration\CreateHelpCommand</step>
</install>
</repair-steps>
<commands>
@ -89,11 +85,6 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m
<command>OCA\Talk\Command\Bot\State</command>
<command>OCA\Talk\Command\Bot\Setup</command>
<command>OCA\Talk\Command\Bot\Uninstall</command>
<command>OCA\Talk\Command\Command\Add</command>
<command>OCA\Talk\Command\Command\AddSamples</command>
<command>OCA\Talk\Command\Command\Delete</command>
<command>OCA\Talk\Command\Command\ListCommand</command>
<command>OCA\Talk\Command\Command\Update</command>
<command>OCA\Talk\Command\Developer\UpdateDocs</command>
<command>OCA\Talk\Command\Monitor\Calls</command>
<command>OCA\Talk\Command\Monitor\HasActiveCalls</command>

View file

@ -31,7 +31,6 @@ return array_merge_recursive(
include(__DIR__ . '/routes/routesCallController.php'),
include(__DIR__ . '/routes/routesCertificateController.php'),
include(__DIR__ . '/routes/routesChatController.php'),
include(__DIR__ . '/routes/routesCommandController.php'),
include(__DIR__ . '/routes/routesFederationController.php'),
include(__DIR__ . '/routes/routesFilesIntegrationController.php'),
include(__DIR__ . '/routes/routesGuestController.php'),

View file

@ -1,35 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
*
* @author Lukas Reschke <lukas@statuscode.ch>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
$requirements = [
'apiVersion' => '(v1)',
];
return [
'ocs' => [
/** @see \OCA\Talk\Controller\CommandController::index() */
['name' => 'Command#index', 'url' => '/api/{apiVersion}/command', 'verb' => 'GET', 'requirements' => $requirements],
],
];

View file

@ -2,101 +2,4 @@
!!! warning
**Deprecation:** Commands are deprecated in favor of [Bots](bots.md).
---
!!! note
For security reasons commands can only be added via the
command line. `./occ talk:command:add --help` gives you
a short overview of the required arguments, but they are
explained here in more depth.
---
## "Add command" arguments
| Argument | Allowed chars | Description |
|------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `cmd` | [a-z0-9] | The keyword the user has to type to run this command (min. 1, max. 64 characters) |
| `name` | * | The author name of the response that is posted by the command (min. 1, max. 64 characters) |
| `script` | * | Actual command that is being ran. The script must be executable by the user of your webserver and has to use absolute paths only! See the parameter table below for options. The script is invoked with `--help` as argument on set up, to check if it can be executed correctly. |
| `response` | 0-2 | Who should see the response: 0 - No one, 1 - User who executed the command, 2 - Everyone |
| `enabled` | 0-3 | Who can use the command: 0 - No one, 1 - Moderators of the room, 2 - Logged in users, 3 - Everyone |
## Script parameter
| Parameter | Description |
|---------------|----------------------------------------------------|
| `{ROOM}` | The token of the room the command was used in |
| `{USER}` | ID of the user that called the command |
| `{ARGUMENTS}` | Everything the user write after the actual command |
## Example
### Create `/path/to/calc.sh`
```
while test $# -gt 0; do
case "$1" in
--help)
echo "/calc - A Nextcloud Talk chat wrapper for gnome-calculator"
echo " "
echo "Simple equations: /calc 3 + 4 * 5"
echo "Complex equations: /calc sin(3) + 3^3 * sqrt(5)"
exit 0
;;
*)
break
;;
esac
done
set -f
echo "$@ ="
echo $(gnome-calculator --solve="$@")
```
Please note, that your command should also understand the argument `--help`.
It should return a useful description, the first line is also displayed in a list of all commands when the user just types `/help`.
### Register command
Make sure to use the absolute path to your script when registering the command:
```
./occ talk:command:add calculator calculator "/path/to/calc.sh {ARGUMENTS} {ROOM} {USER}" 1 3
```
### Explanation
* User input by user `my user id` in the chat of room `index.php/call/4tf349j`:
```
/calculator 1 + 2 + 3 + "hello"
```
* Executed shell command:
```
/path/to/calc.sh '1 + 2 + 3 + "hello"' '4tf349j' 'my user id'
```
## Aliases
It is also possible to define an alias for a command. This allows e.g. to get the `/help` command also with the german word `/hilfe`.
An alias for the `/calculator` command from above could be created using the following command:
```
./occ talk:command:add calc calculator "alias:calculator" 1 3
```
Now `/calculator 1 + 2 + 3` and `/calc 1 + 2 + 3` result in the same message.
!!! note
The enabled and response flag of the alias are ignored and the flags of the original command will be used and respected.
**Deprecation:** Commands have been removed in favor of [Bots](bots.md).

View file

@ -156,17 +156,6 @@ listen to the `OCA\Talk\Events\SystemMessagesMultipleSentEvent` event instead.
* Since: 18.0.0
* Since: 19.0.0 - Method `getParent()` was added
### Deprecated events
These events were not using the typed-event mechanism and are therefore deprecated and will be removed in a future version.
#### Command execution for apps
* Event class: `OCA\Talk\Events\CommandEvent`
* Event name: `OCA\Talk\Chat\Command\Executor::EVENT_APP_EXECUTE`
* Since: 8.0.0
* Deprecated: 17.0.0 - Commands are deprecated, please migrate to bots instead
## Other events
### Turn servers get

View file

@ -17,7 +17,6 @@
* [Call experience](call-experience.md)
* [Occ commands](occ.md)
* [Bots](bot-list.md)
* [Commands (deprecated)](commands.md)
* [Matterbridge integration](matterbridge.md)
## Developer documentation

View file

@ -106,83 +106,6 @@ Uninstall a bot from the server
| `--output` | Output format (plain, json or json_pretty, default is plain) | yes | no | no | `'plain'` |
| `--url` | The URL of the bot (required when no ID is given, ignored otherwise) | yes | yes | no | *Required* |
## talk:command:add
Add a new command
### Usage
* `talk:command:add [--output [OUTPUT]] [--] <cmd> <name> <script> <response> <enabled>`
| Arguments | Description | Is required | Is array | Default |
|---|---|---|---|---|
| `cmd` | The command as used in the chat "/help" => "help" | yes | no | *Required* |
| `name` | Name of the user posting the response | yes | no | *Required* |
| `script` | Script to execute (Must be using absolute paths only) | yes | no | *Required* |
| `response` | Who should see the response: 0 - No one, 1 - User, 2 - All | yes | no | *Required* |
| `enabled` | Who can use this command: 0 - Disabled, 1 - Moderators, 2 - Users, 3 - Guests | yes | no | *Required* |
| Options | Description | Accept value | Is value required | Is multiple | Default |
|---|---|---|---|---|---|
| `--output` | Output format (plain, json or json_pretty, default is plain) | yes | no | no | `'plain'` |
## talk:command:add-samples
Adds some sample commands: /wiki, …
### Usage
* `talk:command:add-samples`
## talk:command:delete
Remove an existing command
### Usage
* `talk:command:delete <command-id>`
| Arguments | Description | Is required | Is array | Default |
|---|---|---|---|---|
| `command-id` | | yes | no | *Required* |
## talk:command:list
List all available commands
### Usage
* `talk:command:list [--output [OUTPUT]] [--] [<app>]`
| Arguments | Description | Is required | Is array | Default |
|---|---|---|---|---|
| `app` | Only list the commands of a specific app, "custom" to list all custom commands | no | no | `NULL` |
| Options | Description | Accept value | Is value required | Is multiple | Default |
|---|---|---|---|---|---|
| `--output` | Output format (plain, json or json_pretty, default is plain) | yes | no | no | `'plain'` |
## talk:command:update
Add a new command
### Usage
* `talk:command:update [--output [OUTPUT]] [--] <command-id> <cmd> <name> <script> <response> <enabled>`
| Arguments | Description | Is required | Is array | Default |
|---|---|---|---|---|
| `command-id` | | yes | no | *Required* |
| `cmd` | The command as used in the chat "/help" => "help" | yes | no | *Required* |
| `name` | Name of the user posting the response | yes | no | *Required* |
| `script` | Script to execute (Must be using absolute paths only) | yes | no | *Required* |
| `response` | Who should see the response: 0 - No one, 1 - User, 2 - All | yes | no | *Required* |
| `enabled` | Who can use this command: 0 - Disabled, 1 - Moderators, 2 - Users, 3 - Guests | yes | no | *Required* |
| Options | Description | Accept value | Is value required | Is multiple | Default |
|---|---|---|---|---|---|
| `--output` | Output format (plain, json or json_pretty, default is plain) | yes | no | no | `'plain'` |
## talk:monitor:calls
Prints a list with conversations that have an active call as well as their participant count

View file

@ -35,10 +35,8 @@ use OCA\Files_Sharing\Event\BeforeTemplateRenderedEvent;
use OCA\Talk\Activity\Listener as ActivityListener;
use OCA\Talk\Capabilities;
use OCA\Talk\Chat\Changelog\Listener as ChangelogListener;
use OCA\Talk\Chat\Command\Listener as CommandListener;
use OCA\Talk\Chat\Listener as ChatListener;
use OCA\Talk\Chat\Parser\Changelog;
use OCA\Talk\Chat\Parser\Command;
use OCA\Talk\Chat\Parser\ReactionParser;
use OCA\Talk\Chat\Parser\SystemMessage;
use OCA\Talk\Chat\Parser\UserMention;
@ -57,7 +55,6 @@ use OCA\Talk\Events\AttendeesRemovedEvent;
use OCA\Talk\Events\BeforeAttendeeRemovedEvent;
use OCA\Talk\Events\BeforeAttendeesAddedEvent;
use OCA\Talk\Events\BeforeCallEndedForEveryoneEvent;
use OCA\Talk\Events\BeforeChatMessageSentEvent;
use OCA\Talk\Events\BeforeDuplicateShareSentEvent;
use OCA\Talk\Events\BeforeGuestJoinedRoomEvent;
use OCA\Talk\Events\BeforeParticipantModifiedEvent;
@ -219,15 +216,11 @@ class Application extends App implements IBootstrap {
// Chat parser
$context->registerEventListener(MessageParseEvent::class, Changelog::class, -75);
$context->registerEventListener(MessageParseEvent::class, Command::class);
$context->registerEventListener(MessageParseEvent::class, ReactionParser::class);
$context->registerEventListener(MessageParseEvent::class, SystemMessage::class);
$context->registerEventListener(MessageParseEvent::class, SystemMessage::class, 9999);
$context->registerEventListener(MessageParseEvent::class, UserMention::class, -100);
// Command listener
$context->registerEventListener(BeforeChatMessageSentEvent::class, CommandListener::class);
// Files integration listeners
$context->registerEventListener(BeforeGuestJoinedRoomEvent::class, FilesListener::class);
$context->registerEventListener(BeforeUserJoinedRoomEvent::class, FilesListener::class);

View file

@ -300,7 +300,6 @@ class ChatManager {
string $referenceId = '',
bool $silent = false,
bool $rateLimitGuestMentions = true,
bool $forceLastMessageUpdate = false, // Remove when dropping commands
): IComment {
if ($chat->isFederatedConversation()) {
$e = new MessagingNotAllowedException();
@ -354,10 +353,9 @@ class ChatManager {
$this->participantService->updateLastReadMessage($participant, (int) $comment->getId());
}
// Update last_message (not for commands)
// Update last_message
if ($comment->getActorType() !== Attendee::ACTOR_BOTS
|| $comment->getActorId() === Attendee::ACTOR_ID_CHANGELOG
|| $forceLastMessageUpdate
|| str_starts_with($comment->getActorId(), Attendee::ACTOR_BOT_PREFIX)) {
$this->roomService->setLastMessage($chat, $comment);
$this->unreadCountCache->clear($chat->getId() . '-');

View file

@ -1,200 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Chat\Command;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Events\CommandEvent;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\Command;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\CommandService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Comments\IComment;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IL10N;
use Psr\Log\LoggerInterface;
class Executor {
/** @deprecated Commands are deprecated, please switch to Nextcloud Talk bots */
public const EVENT_APP_EXECUTE = self::class . '::execApp';
public const PLACEHOLDER_ROOM = '{ROOM}';
public const PLACEHOLDER_USER = '{USER}';
public const PLACEHOLDER_ARGUMENTS = '{ARGUMENTS}';
public const PLACEHOLDER_ARGUMENTS_DOUBLEQUOTE_ESCAPED = '{ARGUMENTS_DOUBLEQUOTE_ESCAPED}';
public function __construct(
protected IEventDispatcher $dispatcher,
protected ShellExecutor $shellExecutor,
protected CommandService $commandService,
protected LoggerInterface $logger,
protected IL10N $l,
) {
}
public function isCommandAvailableForParticipant(Command $command, Participant $participant): bool {
if ($command->getEnabled() === Command::ENABLED_OFF) {
return false;
}
if ($command->getEnabled() === Command::ENABLED_MODERATOR && !$participant->hasModeratorPermissions()) {
return false;
}
if ($command->getEnabled() === Command::ENABLED_USERS && $participant->isGuest()) {
return false;
}
return true;
}
public function exec(Room $room, IComment $message, Command $command, string $arguments, Participant $participant): void {
try {
$command = $this->commandService->resolveAlias($command);
} catch (DoesNotExistException $e) {
$user = $message->getActorType() === Attendee::ACTOR_USERS ? $message->getActorId() : '';
$message->setMessage(json_encode([
'user' => $user,
'visibility' => $command->getResponse(),
'output' => $e->getMessage(),
]), ChatManager::MAX_CHAT_LENGTH);
$message->setActor('bots', $command->getName());
$message->setVerb(ChatManager::VERB_COMMAND);
return;
}
if (($command->getApp() === '' || $command->getApp() === null) && $command->getCommand() === 'help') {
$output = $this->execHelp($room, $message, $arguments, $participant);
} elseif ($command->getApp() !== '' && $command->getApp() !== null) {
$output = $this->execApp($room, $message, $command, $arguments);
} else {
$output = $this->execShell($room, $message, $command, $arguments);
}
$user = $message->getActorType() === Attendee::ACTOR_USERS ? $message->getActorId() : '';
$message->setMessage(json_encode([
'user' => $user,
'visibility' => $command->getResponse(),
'output' => $output,
]), ChatManager::MAX_CHAT_LENGTH);
$message->setActor('bots', $command->getName());
$message->setVerb(ChatManager::VERB_COMMAND);
}
protected function execHelp(Room $room, IComment $message, string $arguments, Participant $participant): string {
if ($arguments !== '' && $arguments !== 'help') {
return $this->execHelpSingleCommand($room, $message, $arguments);
}
$helps = [];
$commands = $this->commandService->findAll();
foreach ($commands as $command) {
if ($command->getApp() !== '' && $command->getApp() !== null) {
$response = $this->execHelpSingleCommand($room, $message, $command->getApp() . ' ' . $command->getCommand());
} else {
if ($command->getCommand() === 'help' || str_contains($command->getScript(), 'alias:') ||
!$this->isCommandAvailableForParticipant($command, $participant)) {
continue;
}
$response = $this->execHelpSingleCommand($room, $message, $command->getCommand());
}
$response = trim($response);
$newLinePosition = strpos($response, "\n");
if ($newLinePosition !== false) {
$tempHelp = substr($response, 0, $newLinePosition);
if ($tempHelp === 'Description:') {
$hasHelpSection = strpos($response, "\nHelp:\n");
if ($hasHelpSection !== false) {
// Symfony console command with --help detected
$tempHelp = substr($response, $hasHelpSection + 7);
$tempHelp = substr($tempHelp, 0, strpos($tempHelp, "\n"));
}
}
$helps[] = $tempHelp;
} else {
$helps[] = $response;
}
}
if (empty($helps)) {
return $this->l->t('There are currently no commands available.');
}
// FIXME Implement a useful help
return implode("\n", $helps);
}
protected function execHelpSingleCommand(Room $room, IComment $message, string $arguments): string {
try {
$input = explode(' ', $arguments, 2);
if (count($input) === 1) {
$command = $this->commandService->find('', $arguments);
$response = $this->execShell($room, $message, $command, '--help');
if (str_starts_with($response, 'Description:')) {
$hasHelpSection = strpos($response, "\nHelp:\n");
if ($hasHelpSection !== false) {
// Symfony console command with --help detected
$response = substr($response, $hasHelpSection + 7);
}
}
return $response;
}
[$app, $cmd] = $input;
$command = $this->commandService->find($app, $cmd);
return $this->execApp($room, $message, $command, '--help');
} catch (DoesNotExistException $e) {
return $this->l->t('The command does not exist');
}
}
protected function execApp(Room $room, IComment $message, Command $command, string $arguments): string {
$event = $this->createEvent($room, $message, $command, $arguments);
$this->dispatcher->dispatch(self::EVENT_APP_EXECUTE, $event);
return $event->getOutput();
}
protected function createEvent(Room $room, IComment $message, Command $command, string $arguments): CommandEvent {
return new CommandEvent($room, $message, $command, $arguments);
}
public function execShell(Room $room, IComment $message, Command $command, string $arguments): string {
try {
return $this->shellExecutor->execShell(
$command->getScript(),
$arguments,
$room->getToken(),
$message->getActorType() === Attendee::ACTOR_USERS ? $message->getActorId() : ''
);
} catch (\InvalidArgumentException $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
return $this->l->t('An error occurred while running the command. Please ask an administrator to check the logs.');
}
}
}

View file

@ -1,117 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Chat\Command;
use OCA\Talk\Events\BeforeChatMessageSentEvent;
use OCA\Talk\Model\Command;
use OCA\Talk\Participant;
use OCA\Talk\Service\CommandService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
/**
* @template-implements IEventListener<Event>
*/
class Listener implements IEventListener {
public function __construct(
protected CommandService $commandService,
protected Executor $executor,
) {
}
public function handle(Event $event): void {
if ($event instanceof BeforeChatMessageSentEvent) {
$this->executeCommand($event);
}
}
public function executeCommand(BeforeChatMessageSentEvent $event): void {
$participant = $event->getParticipant();
if (!$participant instanceof Participant) {
// No commands for bots 🚓
return;
}
$message = $event->getComment();
if (str_starts_with($message->getMessage(), '//')) {
return;
}
try {
[$command, $arguments] = $this->getCommand($message->getMessage());
$command = $this->commandService->resolveAlias($command);
} catch (DoesNotExistException) {
return;
}
if (!$this->executor->isCommandAvailableForParticipant($command, $participant)) {
$command = $this->commandService->find('', 'help');
$arguments = trim($message->getMessage());
}
$this->executor->exec($event->getRoom(), $message, $command, $arguments, $participant);
}
/**
* @param string $message
* @return array{0: Command, 1: string}
* @throws DoesNotExistException
*/
protected function getCommand(string $message): array {
[$app, $cmd, $arguments] = $this->matchesCommand($message);
if ($app === '') {
throw new DoesNotExistException('No command found');
}
try {
return [$this->commandService->find($app, $cmd), trim($arguments)];
} catch (DoesNotExistException) {
}
try {
return [$this->commandService->find('', $app), trim($cmd . ' ' . $arguments)];
} catch (DoesNotExistException) {
}
return [$this->commandService->find('', 'help'), trim($message)];
}
protected function matchesCommand(string $message): array {
if (!str_starts_with($message, '/')) {
return ['', '', ''];
}
$cmd = explode(' ', substr($message, 1), 3);
return [
$cmd[0],
$cmd[1] ?? '',
$cmd[2] ?? '',
];
}
}

View file

@ -1,76 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Chat\Command;
class ShellExecutor {
public const PLACEHOLDER_ROOM = '{ROOM}';
public const PLACEHOLDER_USER = '{USER}';
public const PLACEHOLDER_ARGUMENTS = '{ARGUMENTS}';
// Legacy placeholder just returning the --help nowadays
public const PLACEHOLDER_ARGUMENTS_DOUBLEQUOTE_ESCAPED = '{ARGUMENTS_DOUBLEQUOTE_ESCAPED}';
/**
* @param string $cmd
* @param string $arguments
* @param string $room
* @param string $user
* @return string
* @throws \InvalidArgumentException
*/
public function execShell(string $cmd, string $arguments, string $room = '', string $user = ''): string {
if (str_contains($cmd, self::PLACEHOLDER_ARGUMENTS_DOUBLEQUOTE_ESCAPED)) {
throw new \InvalidArgumentException('Talk commands using the {ARGUMENTS_DOUBLEQUOTE_ESCAPED} are not supported anymore.');
}
$cmd = str_replace([
self::PLACEHOLDER_ROOM,
self::PLACEHOLDER_USER,
self::PLACEHOLDER_ARGUMENTS,
], [
escapeshellarg($room),
escapeshellarg($user),
escapeshellarg($arguments),
], $cmd);
return $this->wrapExec($cmd);
}
/**
* @param string $cmd
* @return string
* @throws \InvalidArgumentException
*/
protected function wrapExec(string $cmd): string {
$output = [];
$returnCode = 0;
@exec($cmd, $output, $returnCode);
if ($returnCode) {
throw new \InvalidArgumentException('Chat command failed [Code: ' . $returnCode . ']: ' . $cmd);
}
return implode("\n", $output);
}
}

View file

@ -35,6 +35,10 @@ use OCP\EventDispatcher\IEventListener;
* @template-implements IEventListener<Event>
*/
class Command implements IEventListener {
public const RESPONSE_NONE = 0;
public const RESPONSE_USER = 1;
public const RESPONSE_ALL = 2;
public function handle(Event $event): void {
if (!$event instanceof MessageParseEvent) {
return;
@ -46,6 +50,8 @@ class Command implements IEventListener {
return;
}
$message->setVisibility(false);
$comment = $message->getComment();
$data = json_decode($comment->getMessage(), true);
if (!\is_array($data)) {
@ -54,13 +60,13 @@ class Command implements IEventListener {
$event->stopPropagation();
if ($data['visibility'] === \OCA\Talk\Model\Command::RESPONSE_NONE) {
if ($data['visibility'] === self::RESPONSE_NONE) {
$message->setVisibility(false);
return;
}
$participant = $message->getParticipant();
if ($data['visibility'] !== \OCA\Talk\Model\Command::RESPONSE_ALL &&
if ($data['visibility'] !== self::RESPONSE_ALL &&
$participant !== null &&
($participant->getAttendee()->getActorType() !== Attendee::ACTOR_USERS
|| $data['user'] !== $participant->getAttendee()->getActorId())) {

View file

@ -1,119 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Command\Command;
use OC\Core\Command\Base;
use OCA\Talk\Service\CommandService;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Add extends Base {
use TRenderCommand;
public function __construct(
private CommandService $service,
) {
parent::__construct();
}
protected function configure(): void {
parent::configure();
$this
->setName('talk:command:add')
->setDescription('Add a new command')
->addArgument(
'cmd',
InputArgument::REQUIRED,
'The command as used in the chat "/help" => "help"'
)
->addArgument(
'name',
InputArgument::REQUIRED,
'Name of the user posting the response'
)
->addArgument(
'script',
InputArgument::REQUIRED,
'Script to execute (Must be using absolute paths only)'
)
->addArgument(
'response',
InputArgument::REQUIRED,
'Who should see the response: 0 - No one, 1 - User, 2 - All'
)
->addArgument(
'enabled',
InputArgument::REQUIRED,
'Who can use this command: 0 - Disabled, 1 - Moderators, 2 - Users, 3 - Guests'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$cmd = $input->getArgument('cmd');
$name = $input->getArgument('name');
$script = $input->getArgument('script');
$response = (int) $input->getArgument('response');
$enabled = (int) $input->getArgument('enabled');
try {
$command = $this->service->create('', $cmd, $name, $script, $response, $enabled);
} catch (\InvalidArgumentException $e) {
switch ($e->getCode()) {
case 1:
$output->writeln('<error>The command already exists or is invalid</error>');
break;
case 2:
$output->writeln('<error>The name is invalid</error>');
break;
case 3:
$output->writeln('<error>The script is invalid</error>');
break;
case 4:
$output->writeln('<error>The response value is invalid</error>');
break;
case 5:
$output->writeln('<error>The enabled value is invalid</error>');
break;
case 6:
$output->writeln('<error>The placeholders {ROOM}, {USER} and {ARGUMENTS} must not be used inside quotes</error>');
break;
default:
$output->writeln('<error>The command could not be added</error>');
break;
}
return 1;
}
$output->writeln('<info>Command added</info>');
$output->writeln('');
$this->renderCommands($input, $output, [$command]);
$output->writeln('');
$output->writeln("<comment>If you think your command makes sense for other users as well, feel free to share it in the following github issue:\n https://github.com/nextcloud/spreed/issues/1566</comment>");
return 0;
}
}

View file

@ -1,130 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Command\Command;
use OC\Core\Command\Base;
use OCA\Talk\Model\Command;
use OCA\Talk\Service\CommandService;
use OCP\App\AppPathNotFoundException;
use OCP\App\IAppManager;
use OCP\AppFramework\Db\DoesNotExistException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class AddSamples extends Base {
use TRenderCommand;
protected array $commands = [];
public function __construct(
private CommandService $service,
protected IAppManager $appManager,
) {
parent::__construct();
}
protected function configure(): void {
$this
->setName('talk:command:add-samples')
->setDescription('Adds some sample commands: /wiki, …')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int {
try {
$appPath = $this->appManager->getAppPath('spreed');
} catch (AppPathNotFoundException $e) {
$output->writeln('<error>Could not determine the spreed/ app directory.</error>');
return 1;
}
$this->installCommand(
$output,
'wiki',
'Wikipedia',
'php ' . $appPath . '/sample-commands/wikipedia.php {ARGUMENTS}'
);
$chmod = fileperms($appPath . '/sample-commands/calc.sh');
if (!($chmod & 0x0040 || $chmod & 0x0008 || $chmod & 0x0001)) {
$output->writeln('<error>sample-commands/calc.sh is not executable</error>');
} elseif (!shell_exec('which bc')) {
$output->writeln('<error>Can not add calculator command, because Basic calculator package (bc - https://www.gnu.org/software/bc/) is missing</error>');
} else {
$this->installCommand(
$output,
'calculator',
'Calculator',
$appPath . '/sample-commands/calc.sh {ARGUMENTS}',
Command::RESPONSE_USER
);
$this->installCommand(
$output,
'calc',
'Calculator',
'alias:calculator'
);
}
$this->installCommand(
$output,
'hackernews',
'Hacker News',
'php ' . $appPath . '/sample-commands/hackernews.php {ARGUMENTS}'
);
if (empty($this->commands)) {
return 1;
}
$output->writeln('<info>Commands added</info>');
$output->writeln('');
$this->renderCommands($input, $output, $this->commands);
return 0;
}
protected function installCommand(OutputInterface $output, string $command, string $name, string $script, int $resonse = Command::RESPONSE_ALL, int $enable = Command::ENABLED_ALL): void {
try {
$this->service->find('', $command);
$output->writeln('<comment>Command ' . $command . ' already exists</comment>');
return;
} catch (DoesNotExistException $e) {
}
try {
$this->commands[] = $this->service->create(
'',
$command,
$name,
$script,
$resonse,
$enable
);
} catch (\InvalidArgumentException $e) {
$output->writeln('<error>An error occured while setting up the ' . $command . ' command</error>');
}
}
}

View file

@ -1,65 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Command\Command;
use OC\Core\Command\Base;
use OCA\Talk\Service\CommandService;
use OCP\AppFramework\Db\DoesNotExistException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Delete extends Base {
public function __construct(
private CommandService $service,
) {
parent::__construct();
}
protected function configure():void {
$this
->setName('talk:command:delete')
->setDescription('Remove an existing command')
->addArgument(
'command-id',
InputArgument::REQUIRED
);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$id = (int) $input->getArgument('command-id');
try {
$this->service->delete($id);
} catch (DoesNotExistException $e) {
$output->writeln('<error>The given command ID does not exist</error>');
return 1;
} catch (\InvalidArgumentException $e) {
$output->writeln('<error>The help command cannot be deleted</error>');
return 2;
}
return 0;
}
}

View file

@ -1,62 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Command\Command;
use OC\Core\Command\Base;
use OCA\Talk\Service\CommandService;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ListCommand extends Base {
use TRenderCommand;
public function __construct(
private CommandService $service,
) {
parent::__construct();
}
protected function configure(): void {
parent::configure();
$this
->setName('talk:command:list')
->setDescription('List all available commands')
->addArgument('app', InputArgument::OPTIONAL, 'Only list the commands of a specific app, "custom" to list all custom commands')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$app = $input->getArgument('app');
if ($app === null) {
$commands = $this->service->findAll();
} else {
$commands = $this->service->findByApp($app === 'custom' ? '' : $app);
}
$this->renderCommands($input, $output, $commands, true);
return 0;
}
}

View file

@ -1,56 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Command\Command;
use OC\Core\Command\Base;
use OCA\Talk\Model\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
trait TRenderCommand {
protected function renderCommands(InputInterface $input, OutputInterface $output, array $commands, bool $showHelp = false): void {
$result = array_map(function (Command $command) {
return $command->asArray();
}, $commands);
$plainText = $input->getOption('output') ?? Base::OUTPUT_FORMAT_PLAIN;
if ($plainText === Base::OUTPUT_FORMAT_PLAIN) {
if ($showHelp) {
$output->writeln('Response values: 0 - No one, 1 - User, 2 - All');
$output->writeln('Enabled values: 0 - Disabled, 1 - Moderators, 2 - Users, 3 - Guests');
$output->writeln('');
}
$table = new Table($output);
if (isset($result[0])) {
$table->setHeaders(array_keys($result[0]));
}
$table->addRows($result);
$table->render();
} else {
$this->writeMixedInOutputFormat($input, $output, $result);
}
}
}

View file

@ -1,125 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Command\Command;
use OC\Core\Command\Base;
use OCA\Talk\Service\CommandService;
use OCP\AppFramework\Db\DoesNotExistException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class Update extends Base {
use TRenderCommand;
public function __construct(
private CommandService $service,
) {
parent::__construct();
}
protected function configure(): void {
parent::configure();
$this
->setName('talk:command:update')
->setDescription('Add a new command')
->addArgument(
'command-id',
InputArgument::REQUIRED
)
->addArgument(
'cmd',
InputArgument::REQUIRED,
'The command as used in the chat "/help" => "help"'
)
->addArgument(
'name',
InputArgument::REQUIRED,
'Name of the user posting the response'
)
->addArgument(
'script',
InputArgument::REQUIRED,
'Script to execute (Must be using absolute paths only)'
)
->addArgument(
'response',
InputArgument::REQUIRED,
'Who should see the response: 0 - No one, 1 - User, 2 - All'
)
->addArgument(
'enabled',
InputArgument::REQUIRED,
'Who can use this command: 0 - Disabled, 1 - Moderators, 2 - Users, 3 - Guests'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$id = (int) $input->getArgument('command-id');
$cmd = $input->getArgument('cmd');
$name = $input->getArgument('name');
$script = $input->getArgument('script');
$response = (int) $input->getArgument('response');
$enabled = (int) $input->getArgument('enabled');
try {
$command = $this->service->update($id, $cmd, $name, $script, $response, $enabled);
} catch (DoesNotExistException $e) {
$output->writeln('<error>The given command ID does not exist</error>');
return 1;
} catch (\InvalidArgumentException $e) {
switch ($e->getCode()) {
case 0:
$output->writeln('<error>The help command and predefined commands cannot be updated</error>');
break;
case 1:
$output->writeln('<error>The command already exists or is invalid</error>');
break;
case 2:
$output->writeln('<error>The name is invalid</error>');
break;
case 3:
$output->writeln('<error>The script is invalid</error>');
break;
case 4:
$output->writeln('<error>The response value is invalid</error>');
break;
case 5:
$output->writeln('<error>The enabled value is invalid</error>');
break;
default:
$output->writeln('<error>The command could not be updated</error>');
break;
}
return 2;
}
$output->writeln('<info>Command updated</info>');
$output->writeln('');
$this->renderCommands($input, $output, [$command]);
return 0;
}
}

View file

@ -1,59 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Controller;
use OCA\Talk\Model\Command;
use OCA\Talk\Service\CommandService;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
class CommandController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
protected CommandService $commandService,
) {
parent::__construct($appName, $request);
}
/**
* @deprecated Commands are deprecated in favor of Bots
*/
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
public function index(): DataResponse {
$commands = $this->commandService->findAll();
$result = array_map(static function (Command $command) {
return $command->asArray();
}, $commands);
return new DataResponse($result);
}
}

View file

@ -1,72 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Events;
use OCA\Talk\Model\Command;
use OCA\Talk\Room;
use OCP\Comments\IComment;
/**
* @deprecated
*/
class CommandEvent extends ARoomEvent {
protected string $output = '';
public function __construct(
Room $room,
protected IComment $message,
protected Command $command,
protected string $arguments,
) {
parent::__construct($room);
}
public function getComment(): IComment {
return $this->message;
}
public function shouldSkipLastActivityUpdate(): bool {
return false;
}
public function isSilentMessage(): bool {
return false;
}
public function getCommand(): Command {
return $this->command;
}
public function getArguments(): string {
return $this->arguments;
}
public function setOutput(string $output): void {
$this->output = $output;
}
public function getOutput(): string {
return $this->output;
}
}

View file

@ -132,7 +132,6 @@ class Operation implements IOperation {
$this->prepareMention($mode, $participant) . $message,
new \DateTime(),
rateLimitGuestMentions: false,
forceLastMessageUpdate: true,
);
} catch (UnexpectedValueException|ParticipantNotFoundException|RoomNotFoundException) {
continue;

View file

@ -1,68 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019, Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Migration;
use OCA\Talk\Model\Command;
use OCA\Talk\Service\CommandService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
class CreateHelpCommand implements IRepairStep {
public function __construct(
protected CommandService $service,
) {
}
public function getName(): string {
return 'Create help command';
}
public function run(IOutput $output): void {
try {
$command = $this->service->find('', 'help');
$this->service->update(
$command->getId(),
'help',
'talk',
'help',
Command::RESPONSE_USER,
Command::ENABLED_ALL
);
} catch (DoesNotExistException $e) {
$this->service->create(
'',
'help',
'talk',
'help',
Command::RESPONSE_USER,
Command::ENABLED_ALL
);
}
}
}

View file

@ -1,92 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\Entity;
/**
* @method void setApp(string $app)
* @method string getApp()
* @method void setName(string $name)
* @method string getName()
* @method void setCommand(string $command)
* @method string getCommand()
* @method void setScript(string $name)
* @method string getScript()
* @method void setResponse(int $response)
* @method int getResponse()
* @method void setEnabled(int $enabled)
* @method int getEnabled()
*/
class Command extends Entity {
public const RESPONSE_NONE = 0;
public const RESPONSE_USER = 1;
public const RESPONSE_ALL = 2;
public const ENABLED_OFF = 0;
public const ENABLED_MODERATOR = 1;
public const ENABLED_USERS = 2;
public const ENABLED_ALL = 3;
/** @var string */
protected $app;
/** @var string */
protected $name;
/** @var string */
protected $command;
/** @var string */
protected $script;
/** @var int */
protected $response;
/** @var int */
protected $enabled;
public function __construct() {
$this->addType('app', 'string');
$this->addType('name', 'string');
$this->addType('command', 'string');
$this->addType('script', 'string');
$this->addType('response', 'int');
$this->addType('enabled', 'int');
}
public function asArray(): array {
return [
'id' => $this->getId(),
'app' => $this->getApp(),
'name' => $this->getName(),
'command' => $this->getCommand(),
'script' => $this->getScript(),
'response' => $this->getResponse(),
'enabled' => $this->getEnabled(),
];
}
}

View file

@ -1,100 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Model;
use OCP\AppFramework\Db\QBMapper;
use OCP\IDBConnection;
/**
* @template-extends QBMapper<Command>
*/
class CommandMapper extends QBMapper {
/**
* @param IDBConnection $db
*/
public function __construct(IDBConnection $db) {
parent::__construct($db, 'talk_commands', Command::class);
}
/**
* @return Command[]
*/
public function findAll(): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->orderBy('id', 'ASC');
return $this->findEntities($query);
}
/**
* @param int $id
* @return Command
* @throws \OCP\AppFramework\Db\DoesNotExistException
*/
public function findById(int $id): Command {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('id', $query->createNamedParameter($id)));
return $this->findEntity($query);
}
/**
* @param string $app
* @return Command[]
*/
public function findByApp(string $app): array {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('app', $query->createNamedParameter($app)))
->orderBy('id', 'ASC');
return $this->findEntities($query);
}
/**
* @param string $app
* @param string $cmd
* @return Command
* @throws \OCP\AppFramework\Db\DoesNotExistException
*/
public function find(string $app, string $cmd): Command {
$query = $this->db->getQueryBuilder();
$query->select('*')
->from($this->getTableName())
->where($query->expr()->eq('command', $query->createNamedParameter($cmd)));
if ($app === '') {
$query->andWhere($query->expr()->emptyString('app'));
} else {
$query->andWhere($query->expr()->eq('app', $query->createNamedParameter($app)));
}
return $this->findEntity($query);
}
}

View file

@ -1,240 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Talk\Service;
use OCA\Talk\Chat\Command\ShellExecutor;
use OCA\Talk\Model\Command;
use OCA\Talk\Model\CommandMapper;
use OCP\AppFramework\Db\DoesNotExistException;
class CommandService {
public function __construct(
protected CommandMapper $mapper,
protected ShellExecutor $shellExecutor,
) {
}
/**
* @param string $app
* @param string $cmd
* @param string $name
* @param string $script
* @param int $response
* @param int $enabled
* @return Command
* @throws \InvalidArgumentException
*/
public function create(string $app, string $cmd, string $name, string $script, int $response, int $enabled): Command {
try {
$this->mapper->find($app, $cmd);
throw new \InvalidArgumentException('command', 1);
} catch (DoesNotExistException $e) {
}
$command = new Command();
$command->setApp($app);
$command->setCommand($cmd);
$command->setName($name);
$command->setScript($script);
$command->setResponse($response);
$command->setEnabled($enabled);
$this->validateCommand($command);
return $this->mapper->insert($command);
}
/**
* @param int $id
* @param int $response
* @param int $enabled
* @return Command
* @throws \InvalidArgumentException
* @throws DoesNotExistException
*/
public function updateFromWeb(int $id, int $response, int $enabled): Command {
$command = $this->mapper->findById($id);
return $this->update($id, $command->getCommand(), $command->getName(), $command->getScript(), $response, $enabled);
}
/**
* @param int $id
* @param string $cmd
* @param string $name
* @param string $script
* @param int $response
* @param int $enabled
* @return Command
* @throws \InvalidArgumentException
* @throws DoesNotExistException
*/
public function update(int $id, string $cmd, string $name, string $script, int $response, int $enabled): Command {
$command = $this->mapper->findById($id);
$command->setName($name);
$command->setScript($script);
$command->setResponse($response);
$command->setEnabled($enabled);
if ($cmd !== $command->getCommand()) {
try {
$this->mapper->find('', $cmd);
throw new \InvalidArgumentException('command', 1);
} catch (DoesNotExistException $e) {
$command->setCommand($cmd);
}
}
$this->validateCommand($command);
return $this->mapper->update($command);
}
/**
* @param Command $command
* @throws \InvalidArgumentException
*/
protected function validateCommand(Command $command): void {
if (preg_match('/^[a-z0-9]{1..64}$/', $command->getCommand())) {
throw new \InvalidArgumentException('command', 1);
}
if (preg_match('/^.{1..64}$/', $command->getName())) {
throw new \InvalidArgumentException('name', 2);
}
if ($command->getApp() === '' || $command->getApp() === null) {
$script = $command->getScript();
if (str_starts_with($script, 'alias:')) {
try {
$this->resolveAlias($command);
} catch (DoesNotExistException $e) {
throw new \InvalidArgumentException('script', 3);
}
} elseif ($script !== 'help') {
if (preg_match('/[`\'"]{(?:ARGUMENTS|ROOM|USER)}[`\'"]/i', $script)) {
throw new \InvalidArgumentException('script-parameters', 6);
}
if (str_contains($script, '{ARGUMENTS_DOUBLEQUOTE_ESCAPED}')) {
throw new \InvalidArgumentException('script-parameters', 6);
}
try {
$this->shellExecutor->execShell($script, '--help');
} catch (\InvalidArgumentException $e) {
throw new \InvalidArgumentException('script', 3);
}
}
}
if (!\in_array($command->getResponse(), [Command::RESPONSE_NONE, Command::RESPONSE_USER, Command::RESPONSE_ALL], true)) {
throw new \InvalidArgumentException('response', 4);
}
if (!\in_array($command->getEnabled(), [Command::ENABLED_OFF, Command::ENABLED_MODERATOR, Command::ENABLED_USERS, Command::ENABLED_ALL], true)) {
throw new \InvalidArgumentException('enabled', 5);
}
}
/**
* @param Command $command
* @return Command
* @throws DoesNotExistException
*/
public function resolveAlias(Command $command): Command {
$script = $command->getScript();
if (str_starts_with($script, 'alias:')) {
$alias = explode(':', $script, 3);
if (isset($alias[2])) {
[, $app, $cmd] = $alias;
} else {
$app = '';
$cmd = $alias[1];
}
if ($app === $command->getApp() && $cmd === $command->getCommand()) {
throw new DoesNotExistException('The command is an alias for itself');
}
try {
return $this->find($app, $cmd);
} catch (DoesNotExistException $e) {
throw new DoesNotExistException('The command for ' . $command->getCommand() . ' does not exist');
}
}
return $command;
}
/**
* @param int $id
* @return Command
* @throws DoesNotExistException
* @throws \InvalidArgumentException
*/
public function delete(int $id): Command {
$command = $this->mapper->findById($id);
if (($command->getApp() !== '' && $command->getApp() !== null) || $command->getCommand() === 'help') {
throw new \InvalidArgumentException('app', 0);
}
return $this->mapper->delete($command);
}
/**
* @param string $app
* @param string $cmd
* @return Command
* @throws DoesNotExistException
*/
public function find(string $app, string $cmd): Command {
return $this->mapper->find($app, $cmd);
}
/**
* @param string $app
* @return Command[]
*/
public function findByApp(string $app): array {
return $this->mapper->findByApp($app);
}
/**
* @param int $id
* @return Command
* @throws DoesNotExistException
*/
public function findById(int $id): Command {
return $this->mapper->findById($id);
}
/**
* @return Command[]
*/
public function findAll(): array {
return $this->mapper->findAll();
}
}

View file

@ -26,10 +26,8 @@ namespace OCA\Talk\Settings\Admin;
use OCA\Talk\Config;
use OCA\Talk\Exceptions\WrongPermissionsException;
use OCA\Talk\MatterbridgeManager;
use OCA\Talk\Model\Command;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\CommandService;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IAppConfig;
use OCP\AppFramework\Services\IInitialState;
@ -51,7 +49,6 @@ class AdminSettings implements ISettings {
private Config $talkConfig,
private IConfig $serverConfig,
private IAppConfig $appConfig,
private CommandService $commandService,
private IInitialState $initialState,
private ICacheFactory $memcacheFactory,
private IGroupManager $groupManager,
@ -69,7 +66,6 @@ class AdminSettings implements ISettings {
public function getForm(): TemplateResponse {
$this->initGeneralSettings();
$this->initAllowedGroups();
$this->initCommands();
$this->initFederation();
$this->initMatterbridge();
$this->initStunServers();
@ -102,16 +98,6 @@ class AdminSettings implements ISettings {
$this->initialState->provideInitialState('allowed_groups', $groups);
}
protected function initCommands(): void {
$commands = $this->commandService->findAll();
$result = array_map(function (Command $command) {
return $command->asArray();
}, $commands);
$this->initialState->provideInitialState('commands', $result);
}
protected function initFederation(): void {
$this->initialState->provideInitialState('federation_enabled', $this->talkConfig->isFederationEnabled());
$this->initialState->provideInitialState('federation_incoming_enabled', $this->appConfig->getAppValueBool('federation_incoming_enabled', true));

View file

@ -52,3 +52,5 @@ nav:
- 'Overview': 'TURN.md'
- 'coTURN': 'coturn.md'
- 'eturnal': 'eturnal.md'
- 'Removed features':
- 'Commands': 'commands.md'

View file

@ -1,28 +0,0 @@
#!/usr/bin/env bash
CALCULATOR=$(which "bc")
if ! [ -x "$CALCULATOR" ]; then
echo "Basic calculator package (bc - https://www.gnu.org/software/bc/) not found"
exit 1
fi
while test $# -gt 0; do
case "$1" in
--help)
echo "/calc - A basic calculator for Nextcloud Talk based on gnu BC"
echo "See the official documentation for more information:"
echo "https://www.gnu.org/software/bc/manual/html_mono/bc.html"
echo " "
echo "Simple equations: /calc 3 + 4 * 5"
echo "Complex equations: /calc sin(3) + 3^3 * sqrt(5)"
exit 0
;;
*)
break
;;
esac
done
set -f
echo "$@ ="
echo $(echo "$@" | bc)

View file

@ -1,75 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
if (PHP_SAPI !== 'cli') {
// Only allow access via the console
exit;
}
if ($argc < 2) {
echo 'Missing search term in call to hackernews.php';
return 1;
}
$mode = $argv[1];
if ($mode === '--help' || !in_array($mode, ['top', 'new', 'best', ''], true)) {
echo '/hackernews - A simple command to list the Top 5 top, new or best stories' . "\n";
echo "\n";
echo 'Example: /hackernews top' . "\n";
return;
}
$mode = $mode ?: 'top';
$endpoint = 'https://hacker-news.firebaseio.com/v0/' . $mode . 'stories.json';
$content = file_get_contents($endpoint);
$results = json_decode($content, true);
$stories = array_slice($results, 0, 5);
$response = 'Hackernews ' . ucfirst($mode) . ' 5:' . "\n";
$length = 120;
foreach ($stories as $storyId) {
$endpoint = 'https://hacker-news.firebaseio.com/v0/item/' . $storyId . '.json';
$content = file_get_contents($endpoint);
$result = json_decode($content, true);
$link = " - {$result['url']}\n";
$remainingLength = max(strlen($result['title']), $length - strlen($link));
if ($remainingLength < strlen('* ' . $result['title'])) {
$response .= substr('* ' . $result['title'], 0, $remainingLength) . '…' . $link;
} else {
$response .= '* ' . $result['title'] . $link;
}
}
$page = 'news';
if ($mode === 'new') {
$page = 'newest';
} elseif ($mode === 'best') {
$page = 'best';
}
$response .= "Find more at https://news.ycombinator.com/$page";
echo($response);

View file

@ -1,87 +0,0 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
if (PHP_SAPI !== 'cli') {
// Only allow access via the console
exit;
}
if ($argc < 2) {
echo 'Missing search term in call to wikipedia.php';
return 1;
}
$searchTerm = $argv[1];
if ($searchTerm === '--help') {
echo '/wiki - A simple command to find wikipedia articles for a term' . "\n";
echo "\n";
echo 'Example: /wiki Nextcloud' . "\n";
$searchTerm = 'Nextcloud';
}
$endpoint = 'https://en.wikipedia.org/w/api.php';
$parameters = [
'action' => 'query',
'generator' => 'prefixsearch',
'gpssearch' => $searchTerm,
'prop' => 'description|info',
'format' => 'json',
'formatversion' => 2,
'inprop' => 'url',
];
$content = file_get_contents($endpoint . '?' . http_build_query($parameters));
$results = json_decode($content, true);
if (empty($results['query']['pages'])) {
echo 'Wikipedia did not have any results for "' . $searchTerm . '" :(' . "\n";
return;
}
$pages = $results['query']['pages'];
$numArticles = count($pages);
$response = 'Wikipedia search results for "' . $searchTerm . '":' . "\n";
$maxArticles = $numArticles > 7 ? 5 : $numArticles;
$length = (int) ((800 - strlen($response)) / $maxArticles);
foreach ($pages as $key => $page) {
if ($key >= $maxArticles) {
break;
}
$link = " - {$page['canonicalurl']}\n";
$remainingLength = max(strlen($page['title']), $length - strlen($link));
if ($remainingLength < strlen("* {$page['title']} - {$page['description']}")) {
$response .= substr("* {$page['title']} - {$page['description']}", 0, $remainingLength) . '…' . $link;
} else {
$response .= "* {$page['title']} - {$page['description']}" . $link;
}
}
if ($maxArticles < $numArticles) {
$response .= '* and ' . ($numArticles - $maxArticles) ." more articles found\n";
}
echo($response);

View file

@ -1,144 +0,0 @@
<!--
- @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
-
- @author Joas Schilling <coding@schilljs.com>
-
- @license AGPL-3.0-or-later
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<section id="chat_commands" class="commands section">
<h2>
{{ t('spreed', 'Commands') }}
<small>{{ t('spreed', 'Deprecated') }}</small>
</h2>
<!-- eslint-disable-next-line vue/no-v-html -->
<p class="settings-hint" v-html="commandHint" />
<div id="commands_list">
<div class="head name">
{{ t('spreed', 'Name') }}
</div>
<div class="head command">
{{ t('spreed', 'Command') }}
</div>
<div class="head script">
{{ t('spreed', 'Script') }}
</div>
<div class="head response">
{{ t('spreed', 'Response to') }}
</div>
<div class="head enabled">
{{ t('spreed', 'Enabled for') }}
</div>
<template v-for="command in commands">
<div :key="`${command.id}_name`" class="name">
{{ command.name }}
</div>
<div :key="`${command.id}_command`" class="command">
{{ command.command }}
</div>
<div :key="`${command.id}_script`" class="script">
{{ command.script }}
</div>
<div :key="`${command.id}_response`" class="response">
{{ translateResponse(command.response) }}
</div>
<div :key="`${command.id}_enabled`" class="enabled">
{{ translateEnabled(command.enabled) }}
</div>
</template>
</div>
</section>
</template>
<script>
import { loadState } from '@nextcloud/initial-state'
export default {
name: 'Commands',
data() {
return {
commands: {},
}
},
computed: {
commandHint() {
return t('spreed', 'Commands are a new beta feature in Nextcloud Talk. They allow you to run scripts on your Nextcloud server. You can define them with our command line interface. An example of a calculator script can be found in our {linkstart}documentation{linkend}.')
.replace('{linkstart}', '<a target="_blank" rel="noreferrer nofollow" class="external" href="https://nextcloud-talk.readthedocs.io/en/latest/commands/">')
.replace('{linkend}', ' ↗</a>')
},
},
mounted() {
this.commands = loadState('spreed', 'commands')
},
methods: {
translateResponse(response) {
switch (response) {
case 0:
return t('spreed', 'None')
case 1:
return t('spreed', 'User')
default:
return t('spreed', 'Everyone')
}
},
translateEnabled(enabled) {
switch (enabled) {
case 0:
return t('spreed', 'Disabled')
case 1:
return t('spreed', 'Moderators')
case 2:
return t('spreed', 'Users')
default:
return t('spreed', 'Everyone')
}
},
},
}
</script>
<style lang="scss" scoped>
.commands.section {
#commands_list {
display: grid;
grid-template-columns: minmax(100px, 200px) minmax(100px, 200px) 1fr minmax(100px, 200px) minmax(100px, 200px);
grid-column-gap: 5px;
grid-row-gap: 10px;
.head {
padding-bottom: 5px;
border-bottom: 1px solid var(--color-border);
font-weight: bold;
}
}
small {
color: var(--color-warning);
border: 1px solid var(--color-warning);
border-radius: 16px;
padding: 0 9px;
}
}
</style>

View file

@ -499,7 +499,7 @@ export default {
return false // Edited messages are not grouped
}
if (message1.actorType === ATTENDEE.ACTOR_TYPE.BOTS // Don't group messages of commands and bots
if (message1.actorType === ATTENDEE.ACTOR_TYPE.BOTS // Don't group messages of bots
&& message1.actorId !== ATTENDEE.CHANGELOG_BOT_ID) { // Apart from the changelog bot
return false
}

View file

@ -693,8 +693,7 @@ const actions = {
/**
* Only use the last message as lastMessage when:
* 1. It's not a command reply
* 2. It's not a temporary message starting with "/" which is a user posting a command
* 3. It's not a reaction or deletion of a reaction
* 2. It's not a reaction or deletion of a reaction
* 3. It's not a deletion of a message
*/
if ((lastMessage.actorType !== ATTENDEE.ACTOR_TYPE.BOTS
@ -704,10 +703,7 @@ const actions = {
&& lastMessage.systemMessage !== 'reaction_deleted'
&& lastMessage.systemMessage !== 'reaction_revoked'
&& lastMessage.systemMessage !== 'message_deleted'
&& lastMessage.systemMessage !== 'message_edited'
&& !(typeof lastMessage.id.startsWith === 'function'
&& lastMessage.id.startsWith('temp-')
&& lastMessage.message.startsWith('/'))) {
&& lastMessage.systemMessage !== 'message_edited') {
commit('updateConversationLastMessage', { token, lastMessage })
}
},

View file

@ -1202,28 +1202,6 @@ describe('conversationsStore', () => {
const changedConversation = store.getters.conversation(testToken)
expect(changedConversation.lastMessage).toBe(testLastMessage)
})
test('ignore update from temporary if posting a command', () => {
const testLastMessage = {
actorType: 'users',
actorId: 'admin',
systemMessage: '',
id: 'temp-42',
message: '/quit',
}
testConversation.lastMessage = previousLastMessage
store.dispatch('addConversation', testConversation)
store.dispatch('updateConversationLastMessage', {
token: testToken,
lastMessage: testLastMessage,
})
const changedConversation = store.getters.conversation(testToken)
expect(changedConversation.lastMessage).toBe(previousLastMessage)
})
})
describe('creating conversations', () => {

View file

@ -27,7 +27,6 @@
<AllowedGroups />
<Federation v-if="supportFederation" />
<BotsSettings />
<Commands />
<WebServerSetupChecks />
<StunServers />
<TurnServers />
@ -43,7 +42,6 @@ import { getCapabilities } from '@nextcloud/capabilities'
import AllowedGroups from '../components/AdminSettings/AllowedGroups.vue'
import BotsSettings from '../components/AdminSettings/BotsSettings.vue'
import Commands from '../components/AdminSettings/Commands.vue'
import Federation from '../components/AdminSettings/Federation.vue'
import GeneralSettings from '../components/AdminSettings/GeneralSettings.vue'
import HostedSignalingServer from '../components/AdminSettings/HostedSignalingServer.vue'
@ -63,7 +61,6 @@ export default {
components: {
AllowedGroups,
BotsSettings,
Commands,
Federation,
GeneralSettings,
HostedSignalingServer,

View file

@ -14,14 +14,6 @@
<p class="settings-hint"><?php p($l->t('Users that cannot use Talk anymore will still be listed as participants in their previous conversations and also their chat messages will be kept.')); ?></p>
</div>
<div class="videocalls section" id="chat_commands">
<h2><?php p($l->t('Commands')) ?></h2>
<p class="settings-hint"><?php p($l->t('Specify commands the users can use in chats')); ?></p>
<div class="commands">
</div>
</div>
<div id="stun_server" class="videocalls section">
<h2><?php p($l->t('STUN servers')) ?></h2>
<p class="settings-hint"><?php p($l->t('A STUN server is used to determine the public IP address of participants behind a router.')); ?></p>

View file

@ -1,44 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.17.0@c620f6e80d0abfca532b00bda366062aaedf6e5d">
<files psalm-version="5.23.1@8471a896ccea3526b26d082f4461eeea467f10a4">
<file src="lib/AppInfo/Application.php">
<UndefinedClass>
<code>BeforeTemplateRenderedEvent</code>
<code>BeforeTemplateRenderedEvent</code>
<code><![CDATA[BeforeTemplateRenderedEvent]]></code>
<code><![CDATA[BeforeTemplateRenderedEvent]]></code>
</UndefinedClass>
</file>
<file src="lib/BackgroundJob/CheckReferenceIdColumn.php">
<UndefinedClass>
<code>SchemaWrapper</code>
<code><![CDATA[SchemaWrapper]]></code>
</UndefinedClass>
</file>
<file src="lib/Chat/ChatManager.php">
<UndefinedClass>
<code>NullCache</code>
<code><![CDATA[NullCache]]></code>
</UndefinedClass>
</file>
<file src="lib/Chat/MessageParser.php">
<UndefinedVariable>
<code>$guestNames</code>
<code><![CDATA[$guestNames]]></code>
</UndefinedVariable>
</file>
<file src="lib/Chat/Parser/SystemMessage.php">
<UndefinedClass>
<code>\OC_Util</code>
<code><![CDATA[\OC_Util]]></code>
</UndefinedClass>
</file>
<file src="lib/Command/Command/AddSamples.php">
<ForbiddenCode>
<code><![CDATA[shell_exec('which bc')]]></code>
</ForbiddenCode>
</file>
<file src="lib/Config.php">
<NullArgument>
<code>null</code>
<code><![CDATA[null]]></code>
</NullArgument>
</file>
<file src="lib/Controller/TempAvatarController.php">
<UndefinedClass>
<code>Filesystem</code>
<code><![CDATA[Filesystem]]></code>
</UndefinedClass>
</file>
<file src="lib/Federation/Authenticator.php">
@ -48,7 +43,7 @@
</file>
<file src="lib/Files/Util.php">
<InvalidArgument>
<code>$fileId</code>
<code><![CDATA[$fileId]]></code>
</InvalidArgument>
</file>
<file src="lib/MatterbridgeManager.php">
@ -56,7 +51,7 @@
<code><![CDATA[$this->tokenProvider]]></code>
<code><![CDATA[$this->tokenProvider]]></code>
<code><![CDATA[$this->tokenProvider]]></code>
<code>private</code>
<code><![CDATA[private]]></code>
</UndefinedClass>
</file>
<file src="lib/Migration/Version2001Date20170707115443.php">
@ -73,30 +68,30 @@
</file>
<file src="lib/PublicShare/TemplateLoader.php">
<UndefinedClass>
<code>BeforeTemplateRenderedEvent</code>
<code><![CDATA[BeforeTemplateRenderedEvent]]></code>
</UndefinedClass>
</file>
<file src="lib/PublicShareAuth/TemplateLoader.php">
<UndefinedClass>
<code>BeforeTemplateRenderedEvent</code>
<code><![CDATA[BeforeTemplateRenderedEvent]]></code>
</UndefinedClass>
</file>
<file src="lib/Service/AvatarService.php">
<UndefinedClass>
<code>Filesystem</code>
<code>\OC_Image</code>
<code><![CDATA[Filesystem]]></code>
<code><![CDATA[\OC_Image]]></code>
</UndefinedClass>
</file>
<file src="lib/Service/RecordingService.php">
<LessSpecificReturnStatement>
<code>$recordingFolder</code>
<code><![CDATA[$recordingFolder]]></code>
</LessSpecificReturnStatement>
<MoreSpecificReturnType>
<code>Folder</code>
<code><![CDATA[Folder]]></code>
</MoreSpecificReturnType>
<UndefinedClass>
<code>NoUserException</code>
<code>NoUserException</code>
<code><![CDATA[NoUserException]]></code>
<code><![CDATA[NoUserException]]></code>
</UndefinedClass>
</file>
<file src="lib/Share/Listener.php">
@ -104,20 +99,20 @@
<code><![CDATA[$event->getView()]]></code>
<code><![CDATA[$event->getView()]]></code>
<code><![CDATA[$event->getView()]]></code>
<code>$view</code>
<code>$view</code>
<code>$view</code>
<code>Filesystem</code>
<code><![CDATA[$view]]></code>
<code><![CDATA[$view]]></code>
<code><![CDATA[$view]]></code>
<code><![CDATA[Filesystem]]></code>
</UndefinedClass>
</file>
<file src="lib/Share/RoomShareProvider.php">
<UndefinedClass>
<code>Cache</code>
<code><![CDATA[Cache]]></code>
</UndefinedClass>
</file>
<file src="lib/TInitialState.php">
<UndefinedClass>
<code>NoUserException</code>
<code><![CDATA[NoUserException]]></code>
</UndefinedClass>
</file>
</files>