arsse.php conf save-defaults [] arsse.php user [list] arsse.php user add [] arsse.php user remove arsse.php user set-pass [] [--oldpass=] [--fever] arsse.php user unset-pass [--oldpass=] [--fever] arsse.php user auth [--fever] arsse.php import [] [-f | --flat] [-r | --replace] arsse.php export [] [-f | --flat] arsse.php --version arsse.php -h | --help The Arsse command-line interface can be used to perform various administrative tasks such as starting the newsfeed refresh service, managing users, and importing or exporting data. Commands: daemon Starts the newsfeed refreshing service, which will refresh stale feeds at the configured interval automatically. feed refresh-all Refreshes any stale feeds once, then exits. This performs the same function as the daemon command without looping; this is useful if use of a scheduler such a cron is preferred over a persitent service. feed refresh Refreshes a single feed by numeric ID. This is principally for internal use as the feed ID numbers are not usually exposed to the user. conf save-defaults [] Prints default configuration parameters to standard output, or to if specified. Each parameter is annotated with a short description of its purpose and usage. user [list] Prints a list of all existing users, one per line. user add [] Adds the user specified by , with the provided password . If no password is specified, a random password will be generated and printed to standard output. user remove Removes the user specified by . Data related to the user, including folders and subscriptions, are immediately deleted. Feeds to which the user was subscribed will be retained and refreshed until the configured retention time elapses. user set-pass [] Changes 's password to . If no password is specified, a random password will be generated and printed to standard output. The --oldpass= option can be used to supply a user's exiting password if this is required by the authentication driver to change a password. Currently this is not used by any existing driver. The --fever option sets a user's Fever protocol password instead of their general password. As Fever requires that passwords be stored insecurely, users do not have Fever passwords by default, and logging in to the Fever protocol is disabled until a password is set. It is highly recommended that a user's Fever password be different from their general password. user unset-pass Unsets a user's password, effectively disabling their account. As with password setting, the --oldpass and --fever options may be used. user auth Tests logging in as with password . This only checks that the user's password is correctly recognized; it has no side effects. The --fever option may be used to test the user's Fever protocol password, if any. import [] Imports the feeds, folders, and tags found in the OPML formatted into the account of . If no file is specified, data is instead read from standard input. The --replace option interprets the OPML file as the list of all desired feeds, folders and tags, performing any deletion or moving of existing entries which do not appear in the flle. If this option is not specified, the file is assumed to list desired additions only. The --flat option can be used to ignore any folder structures in the file, importing any feeds only into the root folder. export [] Exports 's feeds, folders, and tags to the OPML file specified by , or standard output if none is provided. Note that due to a limitation of the OPML format, any commas present in tag names will not be retained in the export. The --flat option can be used to omit folders from the export. Some OPML implementations may not support folders, or arbitrary nesting; this option may be used when planning to import into such software. USAGE_TEXT; protected function usage($prog): string { $prog = basename($prog); return str_replace("arsse.php", $prog, self::USAGE); } protected function command(array $options, $args): string { foreach ($options as $cmd) { foreach (explode(" ", $cmd) as $part) { if (!$args[$part]) { continue 2; } } return $cmd; } return ""; } protected function loadConf(): bool { $conf = file_exists(BASE."config.php") ? new Conf(BASE."config.php") : new Conf; Arsse::load($conf); return true; } protected function resolveFile($file, string $mode): string { // TODO: checking read/write permissions on the provided path may be useful $stdinOrStdout = in_array($mode, ["r", "r+"]) ? "php://input" : "php://output"; return ($file === "-" ? null : $file) ?? $stdinOrStdout; } public function dispatch(array $argv = null) { $argv = $argv ?? $_SERVER['argv']; $argv0 = array_shift($argv); $args = \Docopt::handle($this->usage($argv0), [ 'argv' => $argv, 'help' => false, ]); try { $cmd = $this->command(["-h", "--help", "--version", "daemon", "feed refresh", "feed refresh-all", "conf save-defaults", "user", "export", "import"], $args); if ($cmd && !in_array($cmd, ["-h", "--help", "--version", "conf save-defaults"])) { // only certain commands don't require configuration to be loaded $this->loadConf(); } switch ($cmd) { case "-h": case "--help": echo $this->usage($argv0).\PHP_EOL; return 0; case "--version": echo Arsse::VERSION.\PHP_EOL; return 0; case "daemon": $this->getInstance(Service::class)->watch(true); return 0; case "feed refresh": return (int) !Arsse::$db->feedUpdate((int) $args[''], true); case "feed refresh-all": $this->getInstance(Service::class)->watch(false); return 0; case "conf save-defaults": $file = $this->resolveFile($args[''], "w"); return (int) !$this->getInstance(Conf::class)->exportFile($file, true); case "user": return $this->userManage($args); case "export": $u = $args['']; $file = $this->resolveFile($args[''], "w"); return (int) !$this->getInstance(OPML::class)->exportFile($file, $u, ($args['--flat'] || $args['-f'])); case "import": $u = $args['']; $file = $this->resolveFile($args[''], "r"); return (int) !$this->getInstance(OPML::class)->importFile($file, $u, ($args['--flat'] || $args['-f']), ($args['--replace'] || $args['-r'])); } } catch (AbstractException $e) { $this->logError($e->getMessage()); return $e->getCode(); } } // @codeCoverageIgnore /** @codeCoverageIgnore */ protected function logError(string $msg) { fwrite(STDERR, $msg.\PHP_EOL); } /** @codeCoverageIgnore */ protected function getInstance(string $class) { return new $class; } protected function userManage($args): int { switch ($this->command(["add", "remove", "set-pass", "unset-pass", "list", "auth"], $args)) { case "add": return $this->userAddOrSetPassword("add", $args[""], $args[""]); case "set-pass": if ($args['--fever']) { $passwd = $this->getInstance(Fever::class)->register($args[""], $args[""]); if (is_null($args[""])) { echo $passwd.\PHP_EOL; } return 0; } else { return $this->userAddOrSetPassword("passwordSet", $args[""], $args[""], $args["--oldpass"]); } // no break case "unset-pass": if ($args['--fever']) { $this->getInstance(Fever::class)->unregister($args[""]); } else { Arsse::$user->passwordUnset($args[""], $args["--oldpass"]); } return 0; case "remove": return (int) !Arsse::$user->remove($args[""]); case "auth": return $this->userAuthenticate($args[""], $args[""], $args["--fever"]); case "list": case "": return $this->userList(); } } // @codeCoverageIgnore protected function userAddOrSetPassword(string $method, string $user, string $password = null, string $oldpass = null): int { $passwd = Arsse::$user->$method(...array_slice(func_get_args(), 1)); if (is_null($password)) { echo $passwd.\PHP_EOL; } return 0; } protected function userList(): int { $list = Arsse::$user->list(); if ($list) { echo implode(\PHP_EOL, $list).\PHP_EOL; } return 0; } protected function userAuthenticate(string $user, string $password, bool $fever = false): int { $result = $fever ? $this->getInstance(Fever::class)->authenticate($user, $password) : Arsse::$user->auth($user, $password); if ($result) { echo Arsse::$lang->msg("CLI.Auth.Success").\PHP_EOL; return 0; } else { echo Arsse::$lang->msg("CLI.Auth.Failure").\PHP_EOL; return 1; } } }