PHPIndex

This page lists files in the current directory. You can view content, get download/execute commands for Wget, Curl, or PowerShell, or filter the list using wildcards (e.g., `*.sh`).

03_Backend
04_Frontend
Minz
01_Index.md
wget 'https://sme10.lists2.roe3.org/FreshRSS/docs/en/developers/01_Index.md'
View Content
# FreshRSS Development

## First Steps

Start by creating your development environment. A guide to setting up FreshRSS’s development environment can be found on [the appropriate page](02_First_steps.md).

## After That

* [GitHub Branching and Pushing](02_GitHub.md)
* [Running tests](03_Running_tests.md)
* [Creating a pull request](04_Pull_requests.md)
* [Releasing a new version](05_Release_new_version.md)
* [Reporting bugs](06_Reporting_Bugs.md)
* [Fever API](06_Fever_API.md)
* [GoogleReader API](06_GoogleReader_API.md)

## Backend Development

* [Making extensions for FreshRSS](03_Backend/05_Extensions.md)
* [Database Schema](03_Backend/01_Database_schema.md)
* [External libraries](03_Backend/03_External_libraries.md)

## Frontend Development

* [View files](04_Frontend/01_View_files.md)
* [Design (Themes/Theming)](04_Frontend/02_Design.md)

## Namespaces

* [OPML FreshRSS namespace](OPML.md)

## Minz

Minz is the homemade PHP framework used by FreshRSS. More information can be found [here](Minz/index.md).
02_First_steps.md
wget 'https://sme10.lists2.roe3.org/FreshRSS/docs/en/developers/02_First_steps.md'
View Content
# Environment configuration (Docker)

FreshRSS is built with PHP and uses a homemade framework, Minz. The dependencies are directly included in the source code, so you don’t need Composer.

There are various ways to configure your development environment. The easiest and most supported method is based on Docker, which is the solution documented below. If you already have a working PHP environment, you probably don’t need it.

We assume here that you use a GNU/Linux distribution, capable of running Docker. Otherwise, you’ll have to adapt the commands accordingly.

The commands that follow have to be executed in a console. They start by `$` when commands need to be executed as normal user, and by `#` when they need to be executed as root user. You don’t have to type these characters. A path may be indicated before these characters to help you identify where they need to be executed. For instance, `app$ echo 'Hello World'` indicates that you have to execute `echo` command in the `app/` directory.

First, you need to install [Docker](https://docs.docker.com/install/linux/docker-ce/ubuntu/).

Once you’re done, clone the repository with:

```sh
git clone https://github.com/FreshRSS/FreshRSS.git
cd FreshRSS
```

Note that, if you want to contribute, you have to fork the repository first and clone your fork instead of the "root" one. Adapt the commands in consequence.

Then, the only command you need to know is the following:

```sh
make start
```

This might take some time while Docker downloads the image. If your user isn’t in the `docker` group, you’ll need to prepend the command with `sudo`.

**You can now access FreshRSS at [http://localhost:8080](http://localhost:8080).** Just follow the install process and select the SQLite database.

You can stop the containers by typing <kbd>Control</kbd> + <kbd>c</kbd> or with the following command, in another terminal:

```sh
make stop
```

If you’re interested in the configuration, the `make` commands are defined in the [`Makefile`](/Makefile).

If you need to use a different tag image (default is `alpine`), you can set the `TAG` environment variable:

```sh
TAG=alpine make start
```

You can find the full list of available tags [on the Docker hub](https://hub.docker.com/r/freshrss/freshrss/tags).

If you want to build the Docker image yourself, you can use the following command:

```sh
make build
```

The `TAG` variable can be anything (e.g. `local`). You can target a specific architecture by adding `-alpine` at the end of the tag (e.g. `local-alpine`).

## Project architecture

- the PHP framework: [Minz](Minz/index.md)

## Extensions

If you want to create your own FreshRSS extension, take a look at the [extension documentation](03_Backend/05_Extensions.md).

## Coding style

If you want to contribute to the source code, it’s important to follow the project’s coding style. The actual code doesn’t always follow it throughout the project, but we should fix it every time an opportunity presents itself.

Contributions which don’t follow the coding style will be rejected as long as the coding style is not fixed.

## GitHub Actions

The code will be checked for every pull request commit on GitHub via [GitHub Actions](https://github.com/FreshRSS/FreshRSS/actions).
See the configuration file [`tests.yml`](../../../.github/workflows/tests.yml).

## Running fixes & tests

Tests can be run locally, e.g. by running `make test-all`, and several problems can be automatically fixed by running `make fix-all`.

```sh
make fix-all
make test-all
```

This requires `make` and `npm` in addition to the FreshRSS requirements. See below for the precise requirements for a few platforms.

### Debian / Ubuntu

> ℹ️ Also applies to [Microsoft Windows](https://docs.microsoft.com/windows/wsl/install-win10) thanks to [WSL](https://ubuntu.com/wsl).

Here are the dependencies that need to be manually installed prior to running the fixes & tests.

```sh
sudo apt update && sudo apt install --no-install-recommends -y make npm php-cli php-curl php-mbstring php-xml unzip wget
```

### Fedora / Red Hat

```sh
yum install -y git make npm php-cli php-curl php-mbstring php-xml php-pdo unzip wget
```

### Alpine Linux

```sh
apk add git make npm php-cli php-curl php-ctype php-dom php-mbstring php-openssl php-phar php-simplexml php-xml php-pdo php-tokenizer php-xmlreader php-xmlwriter unzip wget
```

### Partial fixes & tests

- composer-based: `npm run fix && npm test` or see the [`scripts` section of `composer.json`](../../../composer.json) for individual tests or fixes such as `composer phpstan`
- npm-based: `npm run fix && npm test` or see the [`scripts` section of `package.json`](../../../package.json) for individual tests or fixes such as `npm run rtlcss`

### Tests summary

A short (not complete) summary:

#### PHP

- Syntax of `php` and `phtml` files is checked.
- translation files (`i18n`) are checked ([more information about i18n files](internationalization.html)).
- unit test (`tests`) are run by [PHPunit](https://phpunit.de/).
- Linter:
  - [PHP_Codesniffer (phpcs)](https://github.com/squizlabs/PHP_CodeSniffer)
  - [PHPstan](https://github.com/phpstan/phpstan)

### CSS

- Linter:
  - [PHP_Codesniffer (phpcs)](https://github.com/squizlabs/PHP_CodeSniffer)
  - via npm `.styleintrc.json`
  - check that RTL (right-to-left) CSS files match to standard CSS files

### JavaScript

- Linter:
  - via npm `.styleintrc.json` ([ECMAScript 2017](https://en.wikipedia.org/wiki/ECMAScript#8th_Edition_%E2%80%93_ECMAScript_2017))

### Markdown

- Linter:
  - via npm `.markdownlint.json`

## Spaces, tabs and other whitespace characters

### Indentation

Code indentation must use tabs.

### Alignment

Once the code has been correctly indented, it might be useful to align it for ease of reading. In that case, please use spaces.

```php
$result = a_function_with_a_really_long_name($param1, $param2,
                                             $param3, $param4);
```

### End of line

The newline character must be a line feed (LF), which is the default line ending on *NIX systems. This character must not follow other white space.

You can verify if there is any unintended white space at the end of line with the following Git command:

```sh
# command to check files before adding them in the Git index
git diff --check
# command to check files after adding them in the Git index
git diff --check --cached
```

### End of file

Every file must end by an empty line.

### Commas, dots and semi-columns

There should no space before those characters, but there should be one after.

### Operators

There should be a space before and after every operator.

```php
if ($a == 10) {
	// do something
}

echo $a ? 1 : 0;
```

### Parentheses

There should be no spaces in between brackets. There should be no spaces before the opening bracket, except if it’s after a keyword. There shouldn’t be any spaces after the closing bracket, except if it’s followed by a curly bracket.

```php
if ($a == 10) {
	// do something
}

if ((int)$a == 10) {
	// do something
}
```

### With chained functions

It happens most of the time in JavaScript files. When there are chained functions with closures and call-back functions, it’s hard to understand the code if not properly formatted. In those cases, we add a new indent level for the complete instruction and reset the indent for a new instruction on the same level.

```javascript
// First instruction
shortcut.add(shortcuts.mark_read, function () {
		//...
	}, {
		'disable_in_input': true
	});
// Second instruction
shortcut.add("shift+" + shortcuts.mark_read, function () {
		//...
	}, {
		'disable_in_input': true
	});
```

## Line length

Lines should strive to be shorter than 80 characters. However, this limit may be extended to 100 characters when strictly necessary.

With functions, parameters can be declared on multiple lines.

```php
function my_function($param_1, $param_2,
                     $param_3, $param_4) {
	// do something
}
```

## Naming

All code elements (functions, classes, methods and variables) must describe their usage succinctly.

### Functions and variables

Functions and variables must follow the "snake case" naming convention.

```php
// a function
function function_name() {
	// do something
}
// a variable
$variable_name;
```

### Methods

Methods must follow the "lower camel case" naming convention.

```php
private function methodName() {
	// do something
}
```

### Classes

Classes must follow the "upper camel case" naming convention.

```php
abstract class ClassName {}
```

## Encoding

Files must be encoded with the UTF-8 character set.

## PHP compatibility

Please ensure that your code works with the oldest PHP version officially supported by FreshRSS.

## Miscellaneous

### Operators on multiple lines

Operators must be at the end of the line if a condition is split over more than one line.

```php
if ($a == 10 ||
    $a == 20) {
	// do something
}
```

### End of PHP file

If the file contains only PHP code, the PHP closing tag must be omitted.

### Arrays

If an array declaration runs on more than one line, each element must be followed by a comma, including the last one.

```php
$variable = [
	"value 1",
	"value 2",
	"value 3",
];
```
02_GitHub.md
wget 'https://sme10.lists2.roe3.org/FreshRSS/docs/en/developers/02_GitHub.md'
View Content
# Branching

## Basic

If you are new to Git, here are some of the resources you might find useful:

* [GitHub’s blog post](https://github.com/blog/120-new-to-git)
* <http://try.github.com/>
* <http://sixrevisions.com/resources/git-tutorials-beginners/>
* <http://rogerdudler.github.io/git-guide/>

## Getting the latest code from the FreshRSS repository

First you need to add the official repo to your remote repo list:

```sh
git remote add upstream git@github.com:FreshRSS/FreshRSS.git
```

You can verify the remote repo is successfully added by using:

```sh
git remote -v show
```

Now you can pull the latest development code:

```sh
git checkout edge
git pull upstream edge
```

## Starting a new development branch

```sh
git checkout -b my-development-branch
```

## Sending a patch

```sh
# Add the changed file, here actualize_script.php
git add app/actualize_script.php
# Commit the change and write a proper commit message
git commit
# Double check all looks well
git show
# Push it to your fork
git push
```

Now you can create a PR based on your branch.

## How to write a commit message

A commit message should succinctly describe the changes on the first line. For example:

> Fix broken icon

If necessary, this can be followed by a blank line and a longer explanation.

For further tips, see [here](https://chris.beams.io/posts/git-commit/).
03_Running_tests.md
wget 'https://sme10.lists2.roe3.org/FreshRSS/docs/en/developers/03_Running_tests.md'
View Content
# Running tests

FreshRSS is tested with [PHPUnit](https://phpunit.de/), [PHPStan](https://phpstan.org/), [PHP\_CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer/), and more.
No code should be merged in `edge` if the tests don’t pass.

## Locally

As a developer, you can run the test suite on your PC easily with `make` commands. You can run the test suite with:

```sh
cd ./FreshRSS/
make test-all
```

Some syntax, formatting, whitespace, and i18n conventions can be fixed automatically with:

```sh
make fix-all
```

Some tests can run inside some Docker images, in particular to test against minimum and maximum versions of PHP:

```sh
# Prepare
make composer-test
docker build --pull --tag freshrss/freshrss:oldest -f Docker/Dockerfile-Oldest .
docker build --pull --tag freshrss/freshrss:newest -f Docker/Dockerfile-Newest .

# Run
docker run --rm -e FRESHRSS_ENV=development -e TZ=UTC -v $(pwd):/var/www/FreshRSS freshrss/freshrss:oldest bin/composer test
docker run --rm -e FRESHRSS_ENV=development -e TZ=UTC -v $(pwd):/var/www/FreshRSS freshrss/freshrss:newest bin/composer test
```

## GitHub Actions for Continuous Integration

Tests are automatically run when you open a pull request on GitHub.
They are performed with [GitHub Actions](https://github.com/FreshRSS/FreshRSS/actions).
This ensures your code will not introduce some kind of regression. We will not merge a PR if tests fail so we will ask you to fix any bugs before reviewing your code.

If you are interested, you can take a look at [the configuration file](https://github.com/FreshRSS/FreshRSS/blob/edge/.github/workflows/tests.yml).

## Using feed snapshots

As feed data is volatile, it’s better to work with snapshots when debugging some issues.
You can find the description to retrieve a snapshot [here](06_Reporting_Bugs.md#how-to-provide-feed-data).

To serve those snapshots, you can use a mock server.
Here we will demonstrate how to work with [WireMock](https://wiremock.org/) but other solutions exist.
Here are the steps to start using the WireMock mock server:

1. Go to the mock server home folder.
If you do not have one, you need to create one.
1. Inside the mock server home folder, create the ___file_ and _mappings_ folders.
1. Copy or move your snapshots in the ___file_ folder.
1. Create the _feed.json_ file in the _mappings_ folder with the following content:
	```js
	{
		"request": {
			"method": "GET",
			"urlPathPattern": "/.*"
		},
		"response": {
			"status": 200,
			"bodyFileName": "{{request.pathSegments.[0]}}",
			"transformers": ["response-template"],
			"headers": {
				"Content-Type": "application/rss+xml"
			}
		}
	}
	```
1. Launch the containerized server with the following command:
	```bash
	# <PORT> is the port used on the host to communicate with the server
	# <NETWORK> is the name of the docker network used (by default, it’s freshrss-network)
	docker run -it --rm -p <PORT>:8080 --name wiremock --network <NETWORK> -v $PWD:/home/wiremock wiremock/wiremock:latest-alpine --local-response-templating
	```
1. You can access the `<RSS>` mock file directly:
   * from the host by sending a GET request to `http://localhost:<PORT>/<RSS>`,
   * from any container connected on the same network by sending a GET request to `http://wiremock:8080/<RSS>`.
04_Pull_requests.md
wget 'https://sme10.lists2.roe3.org/FreshRSS/docs/en/developers/04_Pull_requests.md'
View Content
# Opening a pull request

So you want to propose a patch to the community? It’s time to open a [pull request](https://github.com/FreshRSS/FreshRSS/pulls)!

When you open a PR, your message will be prefilled with a message based on [a template](https://github.com/FreshRSS/FreshRSS/blob/edge/docs/pull_request_template.md). It contains a checklist to make sure you didn’t forget anything. It is very important to verify you did everything mentioned so documentation is up-to-date, the commit history stays clear and the code is always stable.

The rest of this document explains specific points.

## How to rebase your branch on `edge`

**TODO:** Update this section. With GitHub’s *squash and merge*, rebasing (and other forms of history rewriting) is more dangerous and annoying (e.g. breaking review mechanism) than useful.

Rebasing a branch is useful to make sure your code is based on the most recent version of FreshRSS and there are no conflicts. You have two ways to do that.

If you have any doubt, please let us know and we’ll help you! We all began with Git one day and it’s not an easy thing to work with.

### Rebasing

Rebasing is the cleanest method because the Git history will be completely linear and consequently easier to read and navigate. It might also be more difficult if you’re not at ease with Git since conflicts are harder to resolve.

Note that you should never rebase a branch if someone else is working on it. Otherwise, since it rewrites the history, it can be a real mess to sort it out.

To rebase a branch:

```sh
git checkout edge       # go on edge branch
git pull upstream edge  # pull the last version of edge
git checkout -          # go back to your branch
git rebase edge         # rebase your branch on edge
```

If you feel confident, you can use `git rebase -i edge` to rewrite your history and make it clearer.

### Merging

If you prefer, you can simply merge `edge` into your own branch. Conflicts might be easier to resolve, but your Git history will be less readable. Don’t worry, we will take care of it before merging your PR back into `edge`.

To merge `edge`:

```sh
git checkout edge       # go on edge branch
git pull upstream edge  # pull the last version of edge
git checkout -          # go back to your branch
git merge edge          # merge edge into your branch
```

## How to write a Git commit message

It’s important to have proper commit messages in order to facilitate later debugging, so please read the following advice. Commit messages should explain the choices made in the past (the “why?”)

The first line should start with a verb (e.g., “Add”) and explain the objective of the commit in few words. It’s usually less than 50 characters so it remains concise. You can consider this line the subject of your commit. Think of it as the second part of a sentence that starts with the words “This commit will.”

* This commit will *add feature X*

Then, insert a blank line, and start to write the body. It’s usually wrapped at 72 characters, but you are pretty free in the tone of the message. The body is the place where you can clarify the context of your patch. For instance, you can explain what you were doing when you identified a bug, or the problem you had before your patch. Providing this information helps other developers understand why a specific choice was made, especially when a patch introduces a bug that is identified months later.

You also can add references (e.g., the URL to the initial ticket in the bug tracker, or a reference to some forum explaining a point).

You can find more information about commit messages [on this blog post](https://chris.beams.io/posts/git-commit/).

## How to write tests

FreshRSS has few tests for now, but we’re working on it. We added this point to the checklist to help us to write more tests, and we would really appreciate it if you wrote a test that ensures your patch is working.

We use [PHPUnit](https://phpunit.de/) version 7.5 ([documentation](https://phpunit.readthedocs.io/en/7.5/)).

You’ll find more information on how to run tests [in this document](03_Running_tests.md).

Feel free to ask us for assistance. Not everything will be easy to test, so don’t spend too much time on this.

## Why you should write documentation

A friendly project should have correct and complete documentation, so newcomers don’t have to ask too many questions, and users can find answers to their problems. The documentation should not be written “later” or chances are it’ll never be.

Our documentation can still be improved quite a bit, so you’re very welcome if you want to help.
05_Release_new_version.md
wget 'https://sme10.lists2.roe3.org/FreshRSS/docs/en/developers/05_Release_new_version.md'
View Content
# Preparing the release

In order to get as much feedback as possible before a release, it’s preferable to announce it on GitHub by creating a dedicated ticket
([see examples](https://github.com/FreshRSS/FreshRSS/search?utf8=%E2%9C%93&q=Call+for+testing&type=Issues)). This should be done **at least one week in advance**.

It’s also recommended to make the announcement on <mailing@freshrss.org>.

## Check the dev status

Before releasing a new version of FreshRSS, you must ensure that the code is stable and free of major bugs. Ideally, our tests should be automated and executed before any publication.

You must also **make sure that the CHANGELOG file is up to date** with the updates of the version to be released.

## Git process

```console
$ git checkout edge
$ git pull
$ vim constants.php
# Update version number x.y.y.z of FRESHRSS_VERSION
$ git commit -a
Version x.y.z
$ git tag -a x.y.z
Version x.y.z
$ git push && git push --tags
```

## Updating `update.freshrss.org`

It’s important to update update.freshrss.org since this is the default service for automatic FreshRSS updates.

The repository managing the code is located on GitHub: [FreshRSS/update.freshrss.org](https://github.com/FreshRSS/update.freshrss.org/).

## Writing the update script

The scripts are located in the `./scripts/` directory and must take the form `update_to_x.y.z.z.php`. This directory  also contains `update_to_dev.php` intended for updates of the `edge` branch (this script must not include code specific to a particular version!) and `update_util.php`, which contains a list of functions useful for all scripts.

In order to write a new script, it’s better to copy/paste the last version or to start from `update_to_dev.php`. The first thing to do is to define the URL from which the FreshRSS package will be downloaded (`PACKAGE_URL`). The URL is in the form  of `https://codeload.github.com/FreshRSS/FreshRSS/zip/x.y.z`.

There are then 5 functions that have to be executed:

* `apply_update()` takes care of saving the directory containing the data, checking its structure, downloading the FreshRSS package, deploying it and cleaning it all up. This function is pre-filled but adjustments can be made if necessary (e.g., reorganization of the `./data` structure). It returns `true` if no problem has occurred or a string indicating a problem;
* `need_info_update()` returns `true` if the user must intervene during the update or `false` if not;
* `ask_info_update()` displays a form to the user if `need_info_update()` has returned `true`;
* `save_info_update()` is responsible for saving the information filled out by the user (from the `ask_info_update()` form);
* `do_post_update()` is executed at the end of the update and takes into account the code of the new version (e.g., if the new version changes the `Minz_Configuration` object, you will benefit from these improvements).

## Updating the versions file

Once the script has been written and versioned, it’s necessary to update the `./versions.php' file which contains a mapping table indicating which versions are updated to which other versions.

Here’s an example of a `versions.php` file:

```php
<?php
return array(
	// STABLE
	'0.8.0' => '1.0.0',
	'0.8.1' => '1.0.0',
	'1.0.0' => '1.0.1',  // doesn’t exist (yet)
	// DEV
	'1.1.2-dev' => 'dev',
	'1.1.3-dev' => 'dev',
	'1.1.4-dev' => 'dev',
);
```

And here’s how this table works:

* on the left you can find the N version, on the right the N+1 version;
* the `x.y.z.z-dev` versions are **all** updated to `edge`;
* stable versions are updated to stable versions;
* it’s possible to skip several versions at once, provided that the update scripts support it;
* it’s advisable to indicate the correspondence of the current version to its potential future version by specifying that this version does not yet exist. As long as the corresponding script does not exist, nothing will happen.

It’s**very strongly** recommended to keep this file organized according to version numbers by separating stable and dev versions.

## Deployment

Before updating update.freshrss.org, it’s better to test with dev.update.freshrss.org, which corresponds to pre-production. So update dev.update.freshrss.org and change the `FRESHRSS_UPDATE_WEBSITE` URL of your FreshRSS instance. Start the update and check that it’s running correctly.

When you’re satisfied, update update.freshrss.org with the new script, test it again, and then move on.

## Updating the FreshRSS services

Two services need to be updated immediately after the update.

* rss.freshrss.org;
* demo.freshrss.org (public login: `demo` / `demodemo`).

## Publicly announce the release

When everything’s working, it’s time to announce the release to the world!

* on GitHub by creating[a new release](https://github.com/FreshRSS/FreshRSS/releases/new)
* on the freshrss.org blog, at least for stable versions (write the article on[FreshRSS/freshrss.org](https://github.com/FreshRSS/freshrss.org))
* on Twitter ([@FreshRSS](https://twitter.com/FreshRSS) account)
* and on <mailing@freshrss.org>

## Starting the next development version

```console
$ git checkout edge
$ vim constants.php
# Update the FRESHRSS_VERSION
$ vim CHANGELOG.md
# Prepare the changelog for the next version
$ git add CHANGELOG.md && git commit && git push
```

Also remember to update update.freshrss.org so that it takes the current development version into account.
06_Fever_API.md
wget 'https://sme10.lists2.roe3.org/FreshRSS/docs/en/developers/06_Fever_API.md'
View Content
# FreshRSS - Fever API implementation

See [Mobile access](../users/06_Mobile_access.md) for general aspects of API access.
Additionally [page about our Google Reader compatible API](06_GoogleReader_API.md) for another possibility.


## RSS clients

There are many RSS clients that support the Fever API, but they seem to understand the Fever API a bit differently.
If your favourite client doesn’t work properly with this API, please create an issue and we’ll have a look.
But we can **only** do that for free clients.

### Usage & Authentication

Before you can start using this API, you have to enable and setup API access, which is [documented here](../users/06_Mobile_access.md),
and then reset the user’s API password.

Then point your mobile application to the `fever.php` address (e.g. `https://freshrss.example.net/api/fever.php`).

## Compatible clients

| App                                                                                | Platform            | License                                            |
|:----------------------------------------------------------------------------------:|:-------------------:|:--------------------------------------------------------:|
|[Fluent Reader](https://hyliu.me/fluent-reader/)                                    |Windows, Linux, macOS|[BSD-3-Clause](https://github.com/yang991178/fluent-reader/blob/master/LICENSE)|
|[Fluent Reader lite](https://hyliu.me/fluent-reader-lite/)                          |Android, iOS         |[BSD-3-Clause](https://github.com/yang991178/fluent-reader-lite)|
|[Read You](https://github.com/Ashinch/ReadYou/)                                     |Android              |[GPLv3](https://github.com/Ashinch/ReadYou/blob/main/LICENSE)|
|[Fiery Feeds](https://apps.apple.com/app/fiery-feeds-rss-reader/id1158763303)       |iOS                  |Closed Source                                             |
|[Newsflash](https://gitlab.com/news-flash/news_flash_gtk/)                          |Linux                |[GPLv3](https://gitlab.com/news-flash/news_flash_gtk/)|
|[Unread](https://apps.apple.com/app/unread-rss-reader/id1252376153)                 |iOS                  |Closed Source                                             |
|[Reeder](https://www.reederapp.com/)                                                |iOS                  |Closed Source                                              |
|[ReadKit](https://apps.apple.com/app/readkit/id588726889)                           |macOS                |Closed Source                                              |

## Features

The following features are implemented:

* fetching categories
* fetching feeds
* fetching RSS items (new, favorites, unread, by_id, by_feed, by_category, since)
* fetching favicons
* setting read marker for item(s)
* setting starred marker for item(s)
* setting read marker for feed
* setting read marker for category
* supports FreshRSS extensions, which use the `entry_before_display` hook

The following features are not supported:

* **Hot Links** aka **hot** as there is nothing in FreshRSS yet that is similar or could be used to simulate it.

## Testing and debugging

If this API does not work as expected in your RSS reader, you can test it manually with a tool like [Postman](https://www.getpostman.com/).

Configure a POST request to the URL <https://freshrss.example.net/api/fever.php?api> which should give you the result:
```json
{
	"api_version": 3,
	"auth": 0
}
```
Great, the base setup seems to work!

Now lets try an authenticated call. Fever uses an `api_key`, which is the MD5 hash of `"$username:$apiPassword"`.
Assuming the user is `kevin` and the password `freshrss`, here is a command-line example to compute the resulting `api_key`

```sh
api_key=`echo -n "kevin:freshrss" | md5sum | cut -d' ' -f1`
```

Add a body to your POST request encoded as `form-data` and one key named `api_key` with the value `your-password-hash`:

```sh
curl -s -F "api_key=$api_key" 'https://freshrss.example.net/api/fever.php?api'
```

This should give:
```json
{
	"api_version": 3,
	"auth": 1,
	"last_refreshed_on_time": "1520013061"
}
```
Perfect, you’re now authenticated and you can start testing the more advanced features. To do so, change the URL and append the possible API actions to your request parameters. Please refer to the [original Fever documentation](https://feedafever.com/api) for more information.

Some basic calls are:

* <https://freshrss.example.net/api/fever.php?api&items>
* <https://freshrss.example.net/api/fever.php?api&feeds>
* <https://freshrss.example.net/api/fever.php?api&groups>
* <https://freshrss.example.net/api/fever.php?api&unread_item_ids>
* <https://freshrss.example.net/api/fever.php?api&saved_item_ids>
* <https://freshrss.example.net/api/fever.php?api&items&since_id=some_id>
* <https://freshrss.example.net/api/fever.php?api&items&max_id=some_id>
* <https://freshrss.example.net/api/fever.php?api&mark=item&as=read&id=some_id>
* <https://freshrss.example.net/api/fever.php?api&mark=item&as=unread&id=some_id>

Replace `some_id` with a real ID from your `freshrss_username_entry` database.

### Debugging

If nothing helps and your client is still misbehaving, you can add the following lines to the beginning of the `fever.api` file to determine the cause of the problems:

```php
file_put_contents(__DIR__ . '/fever.log', $_SERVER['HTTP_USER_AGENT'] . ': ' . json_encode($_REQUEST) . PHP_EOL, FILE_APPEND);
```

Then use your RSS client to query the API and afterwards check the file `fever.log`.

## Credits

This plugin was inspired by the [tinytinyrss-fever-plugin](https://github.com/dasmurphy/tinytinyrss-fever-plugin).
06_GoogleReader_API.md
wget 'https://sme10.lists2.roe3.org/FreshRSS/docs/en/developers/06_GoogleReader_API.md'
View Content
# FreshRSS - Google Reader compatible API implementation

See [Mobile access](../users/06_Mobile_access.md) for general aspects of API access.

See also the [page about our Fever compatible API](06_Fever_API.md) for another possibility (less powerful).

## RSS clients

There are many RSS clients that support the Fever API, but they might understand the API a bit differently.
If your favourite client doesn’t work properly with this API, please create an issue and we’ll have a look.
But we can **only** do that for free clients.

## Usage & Authentication

Before you can start using this API, you have to enable and setup API access, which is [documented here](../users/06_Mobile_access.md),
and then reset the user’s API password.

Then point your mobile application to the `greader.php` address (e.g. `https://freshrss.example.net/api/greader.php`).

## Compatible clients

1. On the same FreshRSS API page, note the address given under “Your API address”, like `https://freshrss.example.net/api/greader.php`
2. Type the API address in a client, together with your FreshRSS username, and the corresponding special API password.

| App                                                                                | Platform            | License                                            |
|:----------------------------------------------------------------------------------:|:-------------------:|:--------------------------------------------------------:|
|[News+](https://github.com/noinnion/newsplus/blob/master/apk/NewsPlus_202.apk) with [News+ Google Reader extension](https://github.com/noinnion/newsplus/blob/master/apk/GoogleReaderCloneExtension_101.apk) |Android|Closed Source (Free), [partially open source](https://github.com/noinnion/newsplus/blob/master/extensions/GoogleReaderCloneExtension/src/com/noinnion/android/newsplus/extension/google_reader/GoogleReaderClient.java)|
|[FeedMe 3.5.3+](https://play.google.com/store/apps/details?id=com.seazon.feedme) |Android                  |Closed Source (Free)                                             |
|[EasyRSS](https://github.com/Alkarex/EasyRSS)                          |Android                |[GPLv3](https://github.com/Alkarex/EasyRSS/blob/master/license.txt) ([F-Droid](https://f-droid.org/packages/org.freshrss.easyrss/))|
|[Readrops](https://github.com/readrops/Readrops) |Android                  |[GPLv3](https://github.com/readrops/Readrops/blob/develop/LICENSE)                                             |
|[Fluent Reader Lite](https://hyliu.me/fluent-reader-lite/) |Android, iOS            |[BSD-3](https://github.com/yang991178/fluent-reader-lite)                                             |
|[Read You](https://github.com/Ashinch/ReadYou/)                                     |Android              |[GPLv3](https://github.com/Ashinch/ReadYou/blob/main/LICENSE)|
|[FocusReader](https://play.google.com/store/apps/details?id=allen.town.focus.reader) |Android                  |Closed Source(Free)                                              |
|[Newsflash](https://gitlab.com/news-flash/news_flash_gtk/)                          |Linux                |[GPLv3](https://gitlab.com/news-flash/news_flash_gtk/) |
|[lire](https://lireapp.com/)                                                        |iOS, macOS           |Closed Source                                             |
|[Newsboat 2.24+](https://newsboat.org/)                           |Linux                |[MIT](https://github.com/newsboat/newsboat/blob/master/LICENSE)                                              |
|[Vienna RSS](http://www.vienna-rss.com/)                           |macOS                |[Apache-2.0](https://github.com/ViennaRSS/vienna-rss/blob/master/LICENCE.md)                                              |
|[Reeder](https://www.reederapp.com/)                           |macOS, iOS                |Closed Source                                              |
|[FreshRSS-Notify](https://addons.mozilla.org/firefox/addon/freshrss-notify-webextension/)                           |Firefox                |Open Source                                              |

> ℹ️ See a [better table of compatible clients in our main Readme](https://github.com/FreshRSS/FreshRSS/blob/edge/README.md#apis--native-apps).

## Google Reader compatible API

Examples of basic queries:

```sh
# Initial login, using API password (Email and Passwd can be given either as GET, or POST - better)
curl 'https://freshrss.example.net/api/greader.php/accounts/ClientLogin?Email=alice&Passwd=Abcdef123456'
SID=alice/8e6845e089457af25303abc6f53356eb60bdb5f8
Auth=alice/8e6845e089457af25303abc6f53356eb60bdb5f8

# Examples of read-only requests
curl -s -H "Authorization:GoogleLogin auth=alice/8e6845e089457af25303abc6f53356eb60bdb5f8" \
  'https://freshrss.example.net/api/greader.php/reader/api/0/subscription/list?output=json'

curl -s -H "Authorization:GoogleLogin auth=alice/8e6845e089457af25303abc6f53356eb60bdb5f8" \
  'https://freshrss.example.net/api/greader.php/reader/api/0/unread-count?output=json'

curl -s -H "Authorization:GoogleLogin auth=alice/8e6845e089457af25303abc6f53356eb60bdb5f8" \
  'https://freshrss.example.net/api/greader.php/reader/api/0/tag/list?output=json'

# Retrieve a token for requests making modifications
curl -H "Authorization:GoogleLogin auth=alice/8e6845e089457af25303abc6f53356eb60bdb5f8" \
  'https://freshrss.example.net/api/greader.php/reader/api/0/token'
8e6845e089457af25303abc6f53356eb60bdb5f8ZZZZZZZZZZZZZZZZZ

# Get articles, piped to jq for easier JSON reading
curl -s -H "Authorization:GoogleLogin auth=alice/8e6845e089457af25303abc6f53356eb60bdb5f8" \
  'https://freshrss.example.net/api/greader.php/reader/api/0/stream/contents/reading-list' | jq .

# Unsubscribe from a feed
curl -H "Authorization:GoogleLogin auth=alice/8e6845e089457af25303abc6f53356eb60bdb5f8" \
  -d 'ac=unsubscribe&s=feed/52' 'https://freshrss.example.net/api/greader.php/reader/api/0/subscription/edit'
```

* [Source code of our API implementation](https://github.com/FreshRSS/FreshRSS/blob/edge/p/api/greader.php)

### API documentation from the original Google Reader

* [By Daniel Arowser](https://web.archive.org/web/20130710044440/http://undoc.in/api.html) ([source](https://github.com/arowser/google-reader-api))
* [By Martin Doms](https://web.archive.org/web/20210126115837/https://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/)
* [By Nick Bradbury](https://inessential.com/2013/03/14/google_reader_api_documentation)
* [By Niall Kennedy](https://web.archive.org/web/20170426184845/http://www.niallkennedy.com/blog/2005/12/google-reader-api.html)
* [By Mihai Parparita](https://web.archive.org/web/20140919042419/http://code.google.com/p/google-reader-api/w/list) ([source](https://github.com/mihaip/google-reader-api))

### API documentation from other compatible servers

* [FeedHQ](https://feedhq.readthedocs.io/en/latest/api/index.html)
* [Inoreader](https://www.inoreader.com/developers/)
* [The Old Reader](https://github.com/theoldreader/api)
* [pyrfeed](http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI)
* [BazQux](https://github.com/bazqux/bazqux-api)

### Synchronisation strategy

> ℹ️ If you are maintaining a client or planning to develop a new one, please read carefully the following pieces of advice,
as many clients start by having a very inneficient synchronisation strategy.

* [*Synchronisation recommendation* by Alkarex](https://github.com/FreshRSS/FreshRSS/issues/2566#issuecomment-541317776)
* [*The Right Way to Sync* by BazQux](https://github.com/bazqux/bazqux-api#user-content-the-right-way-to-sync)
06_Reporting_Bugs.md
wget 'https://sme10.lists2.roe3.org/FreshRSS/docs/en/developers/06_Reporting_Bugs.md'
View Content
# Reporting a bug or a suggestion

Despite the care given to FreshRSS, it’s still possible that bugs occur. Development is dynamic, so issues can be corrected quickly. You might also have a feature in mind that doesn’t yet exist. Regardless whether your idea seems silly, far-fetched, useless or too specific, please don’t hesitate to propose it to us! “Ideas in the air” often find an attentive ear. It’s new external perspectives that make the project evolve the most.

If you’re convinced that you should be heard, here’s how you can go about it.

## On GitHub

GitHub is the ideal platform to submit your requests. It allows us to discuss a problem or suggestion with others and it often generates new ideas. Let’s not neglect this “social” aspect!

1. [Go to the bug ticket manager](https://github.com/FreshRSS/FreshRSS/issues)
2. Start by checking if a similar request hasn’t already been made. If so, please feel free to add your voice to the request.
3. If your request is new, [open a new bug ticket](https://github.com/FreshRSS/FreshRSS/issues/new)
4. Finally, write your request. If you’re fluent in English, it’s the preferred language because it allows for discussion with the largest number of people.
5. Please follow the tips below to make it easier to let your ticket be heard.

## Informal

Not everyone likes or uses GitHub for a variety of legitimate reasons. That is why you can also contact us in a more informal way.

* On [our Mattermost chat](https://framateam.org/signup_user_complete/?id=e2680d3e3128b9fac8fdb3003b0024ee)
* On [our subreddit](https://www.reddit.com/r/freshrss/)
* At events / meetings around Free Software
* Over a beer in a bar
* Etc.

## Tips

Here are some tips to help you present your bug report or suggestion:


* **Pay attention to spelling**. Even if it’s not always easy, try your best!
* **Give an explicit title to your request**, even if it’s a bit long. This not only helps us understand your request, but also to find your ticket later.
* **One request = one ticket.** You may have lots of ideas while being afraid to spam the bug manager: it doesn’t matter. It’s better to have a few too many tickets than too many requests in one. We’ll close and consolidate requests when possible.
* If you report a bug, think about **providing us with the FreshRSS logs** (accessible in the FreshRSS `data/log/` folder) and the **PHP logs** (the location may vary by distribution, but consider searching in `/var/log/httpd` or `/var/log/apache`).
  * If you can’t find the log files, specify it in your ticket so we know you’ve already searched.
  * Not all bugs require logs, but if you have any doubts, it is better to provide them to us. Logs are important and very useful for debugging!
  * The logs may reveal confidential information, so **be careful not to disclose anything sensitive.**
* If you report a feed problem, it will be easier if you could provide a snapshot of its content in a text file.
See [here](#how-to-provide-feed-data) for more information.

In addition, when facing a bug, you’re encouraged to follow this message format (from the [Sam & Max website](http://sametmax.com/template-de-demande-daide-en-informatique/):

### What’s my goal?

Give the general context of what you were trying to do.

### What have I been trying to do?

Explain step by step what you have done so that we can reproduce the bug.

### What results have I achieved?

The bug: what you see that shouldn’t have happened. Here you can provide the logs.

### What was the expected result?

So that we understand what you consider to be the problem.

### What are my circumstances?

Remember to give the following information if you know it:

1. Which browser? Which version?
2. Which server: Apache, Nginx? Which version?
3. Which version of PHP?
4. Which database: SQLite, MySQL, MariaDB, PostgreSQL? Which version?
5. Which distribution runs on the server? And… which version?

## How to provide feed data

If you need us to investigate a feed problem, it will be easier if you provide a snapshot of the feed data.
To do that, you can launch the following command:

```bash
wget <feed url> -O output.rss.txt
```
Then you can drag-and-drop the generated file into the issue.
OPML.md
wget 'https://sme10.lists2.roe3.org/FreshRSS/docs/en/developers/OPML.md'
View Content
# OPML in FreshRSS

FreshRSS supports the [OPML](https://en.wikipedia.org/wiki/OPML) format to export and import lists of RSS/Atom feeds in a standard way, compatible with several other RSS aggregators.

However, FreshRSS also supports several additional features not covered by the basic OPML specification.
Luckily, the [OPML specification](http://opml.org/spec2.opml) allows extensions:

> *An OPML file may contain elements and attributes not described on this page, only if those elements are defined in a namespace.*

and:

> *OPML can also be extended by the addition of new values for the type attribute.*

## FreshRSS OPML extension

FreshRSS uses the XML namespace <https://freshrss.org/opml> to export/import extended information not covered by the basic OPML specification.

The list of the custom FreshRSS attributes can be seen in [the source code](https://github.com/FreshRSS/FreshRSS/blob/edge/app/views/helpers/export/opml.phtml), and here is an overview:

### HTML+XPath or XML+XPath

* `<outline type="HTML+XPath" ...`: Additional type of source, which is not RSS/Atom, but HTML Web Scraping using [XPath](https://www.w3.org/TR/xpath-10/) 1.0.

> ℹ️ [XPath 1.0](https://en.wikipedia.org/wiki/XPath) is a standard query language, which FreshRSS supports to enable [Web scraping](https://en.wikipedia.org/wiki/Web_scraping).

* `<outline type="XML+XPath" ...`: Same than `HTML+XPath` but using an XML parser.

The following attributes are using similar naming conventions than [RSS-Bridge](https://rss-bridge.github.io/rss-bridge/Bridge_API/XPathAbstract.html).

* `frss:xPathItem`: XPath expression for extracting the feed items from the source page.
	* Example: `//div[@class="news-item"]`
* `frss:xPathItemTitle`: XPath expression for extracting the item’s title from the item context.
	* Example: `descendant::h2`
* `frss:xPathItemContent`: XPath expression for extracting an item’s content from the item context.
	* Example: `.`
* `frss:xPathItemUri`: XPath expression for extracting an item link from the item context.
	* Example: `descendant::a/@href`
* `frss:xPathItemAuthor`: XPath expression for extracting an item author from the item context.
	* Example: `"Anonymous"`
* `frss:xPathItemTimestamp`: XPath expression for extracting an item timestamp from the item context. The result will be parsed by [`strtotime()`](https://php.net/strtotime).
* `frss:xPathItemTimeFormat`: Date/Time format to parse the timestamp, according to [`DateTime::createFromFormat()`](https://php.net/datetime.createfromformat).
* `frss:xPathItemThumbnail`: XPath expression for extracting an item’s thumbnail (image) URL from the item context.
	* Example: `descendant::img/@src`
* `frss:xPathItemCategories`: XPath expression for extracting a list of categories (tags) from the item context.
* `frss:xPathItemUid`: XPath expression for extracting an item’s unique ID from the item context. If left empty, a hash is computed automatically.

### JSON+DotNotation

* `<outline type="JSON+DotNotation" ...`: Similar to `HTML+XPath` but for JSON and using a dot/bracket syntax such as `object.object.array[2].property`.

* `frss:jsonItem`: JSON dot notation for extracting the feed items from the source page.
	* Example: `data.items`
* `frss:jsonItemTitle`: JSON dot notation for extracting the item’s title from the item context.
	* Example: `meta.title`
* `frss:jsonItemContent`: JSON dot notation for extracting an item’s content from the item context.
	* Example: `content`
* `frss:jsonItemUri`: JSON dot notation for extracting an item link from the item context.
	* Example: `meta.links[0]`
* `frss:jsonItemAuthor`: JSON dot notation for extracting an item author from the item context.
* `frss:jsonItemTimestamp`: JSON dot notation for extracting an item timestamp from the item context. The result will be parsed by [`strtotime()`](https://php.net/strtotime).
* `frss:jsonItemTimeFormat`: Date/Time format to parse the timestamp, according to [`DateTime::createFromFormat()`](https://php.net/datetime.createfromformat).
* `frss:jsonItemThumbnail`: JSON dot notation for extracting an item’s thumbnail (image) URL from the item context.
* `frss:jsonItemCategories`: JSON dot notation for extracting a list of categories (tags) from the item context.
* `frss:jsonItemUid`: JSON dot notation for extracting an item’s unique ID from the item context. If left empty, a hash is computed automatically.

### JSON Feed

* `<outline type="JSONFeed" ...`: Uses `JSON+DotNotation` behind the scenes to parse a [JSON Feed](https://www.jsonfeed.org/).

### cURL

A number of [cURL options](https://curl.se/libcurl/c/curl_easy_setopt.html) are supported:

* `frss:CURLOPT_COOKIE`
* `frss:CURLOPT_COOKIEFILE`
* `frss:CURLOPT_FOLLOWLOCATION`
* `frss:CURLOPT_HTTPHEADER`
* `frss:CURLOPT_MAXREDIRS`
* `frss:CURLOPT_POST`
* `frss:CURLOPT_POSTFIELDS`
* `frss:CURLOPT_PROXY`
* `frss:CURLOPT_PROXYTYPE`
* `frss:CURLOPT_USERAGENT`

### Miscellaneous

* `frss:cssFullContent`: [CSS Selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) to enable the download and extraction of the matching HTML section of each articles’ Web address.
	* Example: `div.main, .summary`
* `frss:cssFullContentFilter`: [CSS Selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) to remove the matching HTML elements from the full content retrieved by `frss:cssFullContent`.
	* Example: `.footer, .aside`
* `frss:filtersActionRead`: List (separated by a new line) of search queries to automatically mark a new article as read.

### Dynamic OPML (reading lists)

* `frss:opmlUrl`: If non-empty, indicates that this outline (category) should be dynamically populated from a remote OPML at the specified URL.

### Example

```xml
<?xml version="1.0" encoding="UTF-8"?>
<opml version="2.0">
	<head>
		<title>FreshRSS OPML extension example</title>
	</head>
	<body>
		<outline xmlns:frss="https://freshrss.org/opml"
			text="Example"
			type="HTML+XPath"
			xmlUrl="https://www.example.net/page.html"
			htmlUrl="https://www.example.net/page.html"
			description="Example of Web scraping"
			frss:xPathItem="//a[contains(@href, '/interesting/')]/ancestor::article"
			frss:xPathItemTitle="descendant::h2"
			frss:xPathItemContent="."
			frss:xPathItemUri="descendant::a[string-length(@href)&gt;0]/@href"
			frss:xPathItemThumbnail="descendant::img/@src"
			frss:cssFullContent="article"
			frss:filtersActionRead="intitle:⚡️ OR intitle:🔥&#10;something"
		/>
	</body>
</opml>
```