3 * @copyright Copyright (C) 2010-2023, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\Console;
27 use Friendica\Model\Contact as ContactModel;
28 use Friendica\Model\User as UserModel;
29 use Friendica\Network\Probe;
30 use Friendica\Util\Temporal;
32 use Seld\CliPrompt\CliPrompt;
35 * tool to manage contacts of users of the current node
37 class Contact extends \Asika\SimpleConsole\Console
39 protected $helpOptions = ['h', 'help', '?'];
50 protected function getHelp()
53 console contact - Modify contact settings per console commands.
55 bin/console contact add <user nick> <URL> [<network>] [-h|--help|-?] [-v]
56 bin/console contact remove <CID> [-h|--help|-?] [-v]
57 bin/console contact search id <CID> [-h|--help|-?] [-v]
58 bin/console contact search url <user nick> <URL> [-h|--help|-?] [-v]
59 bin/console contact terminate <CID> [-h|--help|-?] [-v]
62 Modify contact settings per console commands.
65 -h|--help|-? Show help information
66 -v Show more debug information
67 -y Non-interactive mode, assume "yes" as answer to the user deletion prompt
72 public function __construct(App\Mode $appMode, array $argv = null)
74 parent::__construct($argv);
76 $this->appMode = $appMode;
79 protected function doExecute(): int
81 if ($this->getOption('v')) {
82 $this->out('Class: ' . __CLASS__);
83 $this->out('Arguments: ' . var_export($this->args, true));
84 $this->out('Options: ' . var_export($this->options, true));
87 if (count($this->args) == 0) {
88 $this->out($this->getHelp());
92 if ($this->appMode->isInstall()) {
93 throw new RuntimeException('Database isn\'t ready or populated yet');
96 $command = $this->getArgument(0);
100 return $this->addContact();
102 return $this->removeContact();
104 return $this->searchContact();
106 return $this->terminateContact();
108 throw new \Asika\SimpleConsole\CommandArgsException('Wrong command.');
113 * Retrieves the user from a nick supplied as an argument or from a prompt
115 * @param int $arg_index Index of the nick argument in the arguments list
117 * @return array|boolean User record with uid field, or false if user is not found
118 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
120 private function getUserByNick($arg_index)
122 $nick = $this->getArgument($arg_index);
125 $this->out('Enter user nickname: ');
126 $nick = CliPrompt::prompt();
128 throw new RuntimeException('A user nickname must be specified.');
132 $user = UserModel::getByNickname($nick, ['uid', 'nickname']);
134 throw new RuntimeException('User not found');
141 * Adds a contact to a user from a URL
143 * @return bool True, if the command was successful
145 private function addContact()
147 $user = $this->getUserByNick(1);
149 $url = $this->getArgument(2);
151 $this->out('Enter contact URL: ');
152 $url = CliPrompt::prompt();
154 throw new RuntimeException('A contact URL must be specified.');
158 $url = Probe::cleanURI($url);
160 $contact = ContactModel::getByURL($url, null, [], $user['uid']);
161 if (!empty($contact)) {
162 throw new RuntimeException('Contact already exists');
165 $network = $this->getArgument(3);
166 if ($network === null) {
167 $this->out('Enter network, or leave blank: ');
168 $network = CliPrompt::prompt();
171 $result = ContactModel::createFromProbeForUser($user['uid'], $url, $network);
173 if ($result['success']) {
174 $this->out('User ' . $user['nickname'] . ' now connected to ' . $url . ', contact ID ' . $result['cid']);
176 throw new RuntimeException($result['message']);
181 * Sends an unfriend message.
183 * @return bool True, if the command was successful
186 private function terminateContact(): bool
188 $cid = $this->getArgument(1);
190 $this->out('Enter contact ID: ');
191 $cid = CliPrompt::prompt();
193 throw new RuntimeException('A contact ID must be specified.');
197 $contact = ContactModel::getById($cid);
198 if (empty($contact)) {
199 throw new RuntimeException('Contact not found');
202 if (empty($contact['uid'])) {
203 throw new RuntimeException('Contact must be user-specific (uid != 0)');
207 ContactModel::unfollow($contact);
209 $this->out('Contact was successfully unfollowed');
212 } catch (\Exception $e) {
213 DI::logger()->error($e->getMessage(), ['contact' => $contact]);
214 throw new RuntimeException('Unable to unfollow this contact, please check the log');
219 * Marks a contact for removal
221 private function removeContact()
223 $cid = $this->getArgument(1);
225 $this->out('Enter contact ID: ');
226 $cid = CliPrompt::prompt();
228 throw new RuntimeException('A contact ID must be specified.');
232 ContactModel::remove($cid);
236 * Returns a contact based on search parameter
238 * @return bool True, if the command was successful
240 private function searchContact()
256 $subCmd = $this->getArgument(1);
258 $table = new Console_Table();
259 $table->setHeaders(['ID', 'UID', 'Network', 'Name', 'Nick', 'URL', 'E-Mail', 'Created', 'Updated', 'Blocked', 'Deleted']);
261 $addRow = function ($row) use (&$table) {
270 Temporal::getRelativeDate($row['created']),
271 Temporal::getRelativeDate($row['updated']),
278 $cid = $this->getArgument(2);
279 $contact = ContactModel::getById($cid, $fields);
280 if (!empty($contact)) {
285 $user = $this->getUserByNick(2);
286 $url = $this->getArgument(3);
287 $contact = ContactModel::getByURLForUser($url, $user['uid'], false, $fields);
288 if (!empty($contact)) {
293 $this->out($this->getHelp());
297 $this->out($table->getTable());