base on A self-hosted, anti-social RSS reader. # Stringer
[![CircleCI](https://circleci.com/gh/stringer-rss/stringer/tree/main.svg?style=svg)](https://circleci.com/gh/stringer-rss/stringer/tree/main)
[![Code Climate](https://api.codeclimate.com/v1/badges/899c5407c870e541af4e/maintainability)](https://codeclimate.com/github/stringer-rss/stringer/maintainability)
[![Coverage Status](https://coveralls.io/repos/github/stringer-rss/stringer/badge.svg?branch=main)](https://coveralls.io/github/stringer-rss/stringer?branch=main)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/mockdeep?logo=github)](https://github.com/sponsors/mockdeep)
### A self-hosted, anti-social RSS reader.
Stringer has no external dependencies, no social recommendations/sharing, and no fancy machine learning algorithms.
But it does have keyboard shortcuts and was made with love!
![](screenshots/instructions.png)
![](screenshots/stories.png)
![](screenshots/feed.png)
## Installation
Stringer is a Ruby app based on Rails, PostgreSQL, Backbone.js and GoodJob.
[![Deploy to Heroku](https://cdn.herokuapp.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/stringer-rss/stringer)
Stringer will run just fine on the Eco/Basic Heroku plans.
Instructions are provided for deploying to [Heroku manually](/docs/Heroku.md), to any Ruby
compatible [Linux-based VPS](/docs/VPS.md), to [Docker](docs/Docker.md) and to [OpenShift](/docs/OpenShift.md).
## Niceties
### Keyboard Shortcuts
You can access the keyboard shortcuts when using the app by hitting `?`.
![](screenshots/keyboard_shortcuts.png)
### Using your own domain with Heroku
You can run Stringer at `http://reader.yourdomain.com` using a CNAME.
If you are on Heroku:
```
heroku domains:add reader.yourdomain.com
```
Go to your registrar and add a CNAME:
```
Record: CNAME
Name: reader
Target: your-heroku-instance.herokuapp.com
```
Wait a few minutes for changes to propagate.
### Fever API
Stringer implements a clone of [Fever's API](http://www.feedafever.com/api) so it can be used with any mobile client that supports Fever.
![image](https://f.cloud.github.com/assets/56947/546236/68456536-c288-11e2-834b-9043dc75a087.png)
Use the following settings:
```
Server: {path-to-stringer}/fever (e.g. http://reader.example.com/fever)
Email: stringer (case-sensitive)
Password: {your-stringer-password}
```
### Translations
Stringer has been translated to [several other languages](config/locales). Your language can be set with the `LOCALE` environment variable.
To set your locale on Heroku, run `heroku config:set LOCALE=en`.
If you would like to translate Stringer to your preferred language, please use [LocaleApp](http://www.localeapp.com/projects/4637).
### Clean up old read stories on Heroku
You can clean up old stories by running: `rake cleanup_old_stories`
By default, this removes read stories that are more than 30 days old (that
are not starred). You can either run this manually or add it as a scheduled
task.
## Development
Run the Ruby tests with `rspec`.
Run the Javascript tests with `rake test_js` and then open a browser to `http://localhost:4567/test`.
### Getting Started
To get started using Stringer for development you first need to install `foreman`.
gem install foreman
Then run the following commands.
```sh
bundle install
rails db:setup
foreman start
```
The application will be running on port `5000`.
You can launch an interactive console (a la `rails c`) using `rake console`.
## Acknowledgments
Most of the heavy-lifting is done by [`feedjira`](https://github.com/feedjira/feedjira) and [`feedbag`](https://github.com/dwillis/feedbag).
General sexiness courtesy of [`Twitter Bootstrap`](http://twitter.github.io/bootstrap/) and [`Flat UI`](http://designmodo.github.io/Flat-UI/).
ReenieBeanie Font Copyright © 2010 Typeco (
[email protected]). Licensed under [SIL Open Font License, 1.1](http://scripts.sil.org/OFL).
Lato Font Copyright © 2010-2011 by tyPoland Lukasz Dziedzic (
[email protected]). Licensed under [SIL Open Font License, 1.1](http://scripts.sil.org/OFL).
## Contact
If you have a question, feature idea, or are running into problems, our preferred method of contact is to open an issue on GitHub. This allows multiple people to weigh in, and we can keep everything in one place. Thanks!
## Maintainers
Robert Fletcher [boon.gl](https://boon.gl)
## Alumni
Matt Swanson (creator), [mdswanson.com](http://mdswanson.com), [@_swanson](http://twitter.com/_swanson)
Victor Koronen, [victor.koronen.se](http://victor.koronen.se/), [@victorkoronen](https://twitter.com/victorkoronen)
", Assign "at most 3 tags" to the expected json: {"id":"5097","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"