{"id":13466385,"url":"https://github.com/Stremio/addon-helloworld-ruby","last_synced_at":"2025-03-25T21:32:09.498Z","repository":{"id":146339722,"uuid":"170493092","full_name":"Stremio/addon-helloworld-ruby","owner":"Stremio","description":"Ruby addon example for stremio","archived":false,"fork":false,"pushed_at":"2019-02-15T15:12:53.000Z","size":8,"stargazers_count":4,"open_issues_count":0,"forks_count":4,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-10-29T19:18:30.053Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Stremio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-02-13T11:02:40.000Z","updated_at":"2024-09-10T05:55:16.000Z","dependencies_parsed_at":"2024-01-16T06:16:44.543Z","dependency_job_id":"654c37f1-ba9b-42f7-b568-82f756747e2d","html_url":"https://github.com/Stremio/addon-helloworld-ruby","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stremio%2Faddon-helloworld-ruby","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stremio%2Faddon-helloworld-ruby/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stremio%2Faddon-helloworld-ruby/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Stremio%2Faddon-helloworld-ruby/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Stremio","download_url":"https://codeload.github.com/Stremio/addon-helloworld-ruby/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245548217,"owners_count":20633538,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T15:00:43.299Z","updated_at":"2025-03-25T21:32:09.173Z","avatar_url":"https://github.com/Stremio.png","language":"Ruby","funding_links":[],"categories":["Addon Developer Resources"],"sub_categories":["Catalogs"],"readme":"# Hello world add-on for Stremio in Ruby\n\n## Adds a few public domain movies to Stremio\n\nThis example shows how to make a Stremio Add-on in Ruby with Rack.\n\n## Quick Start\n\nClone or download this repo and run the following commands in the repo's root directory:\n\n```sh\nbundle install\nbundle exec rackup -p 9292 config.ru\n```\n\nIf you don't have `bundler` installed, you can do that easily with:\n\n```sh\ngem install bundler\n```\n\n## Basic tutorial on how to re-create this add-on step by step\n\n## Step 1: init a project\n\n### Pre-requisites: git, ruby, rubygems\n\n\u003e NOTE: On some operation systems rubygems are included in the ruby package\n\nThis is the first, boilerplate step of creating an add-on for Stremio.\n\n```sh\nmkdir stremio-hello-world\ncd stremio-hello-world\nbundle init\necho \"gem 'rack'\" \u003e\u003e Gemfile\nbundle install\ngit init\ngit add *\ngit commit -a -m \"initial commit\"\n```\n\n## Step 2: Create `stremio-addon.rb` and `config.ru`\n\nLet's start with our add-on basic functionality. Create `stremio-addon.rb` with the following contents:\n\n```ruby\nrequire 'json'\nrequire 'uri'\n\nMANIFEST = {\n  id: \"org.stremio.helloruby\",\n  version: \"1.0.0\",\n\n  name: \"Hello Ruby Add-on\",\n  description: \"Sample addon made with Rack providing a few public domain movies\",\n\n  types: [ :movie, :series ],\n\n  catalogs: [],\n\n  resources: []\n}\n\nclass NotFound\n  def call(env)\n    [404, {\"Content-Type\" =\u003e \"text/plain\"}, [\"404 Not Found\"]]\n  end\nend\n\n# Base class with some common behaviour\nclass Resource\n  @@headers = {\n    \"Access-Control-Allow-Origin\" =\u003e \"*\",\n    \"Access-Control-Allow-Headers\" =\u003e \"*\",\n    \"Content-Type\" =\u003e \"application/json\"\n  }\n\n  def initialize(app)\n    @app = app\n  end\n\n  def parse_request(env)\n    segments = env[\"PATH_INFO\"][1..-1] # Remove the leading slash\n      .sub(/\\.\\w+$/, '') # Remove extension if any\n      .split(\"/\")\n      .map { |seg| URI.decode(seg) }\n\n    { type: segments[0], id: segments[1], extraArgs: segments[2..-1] }\n  end\nend\n\nclass Manifest \u003c Resource\n  def call(env)\n    return @app.call(env) unless env[\"PATH_INFO\"].empty?\n\n    [200, @@headers, [ MANIFEST.to_json ]]\n  end\nend\n```\n\nThe `NotFound` class will be used by our app to serve any request that we don't handle explicitly.\n\n`Resource` is a helper class that provides a common functionality for all other classes.\n\nThe manifest is the most important part of our add-on. It tells Stremio what are the add-on's capabilities. We define an `MANIFEST` object in the beginning of our scrip and the `Manifest` class serves it statically. The full specification of the add-on manifest is described [here](https://github.com/Stremio/stremio-addon-sdk/blob/master/docs/api/responses/manifest.md).\n\nWe need a basic `rack` app, so we will use `Rack::Builder` in our `config.ru` for a quick start:\n\n```ruby\nrequire './stremio-addon.rb'\n\napp = Rack::Builder.new do\n  use Rack::Reloader\n  use Rack::ContentLength\n\n  map \"/manifest.json\" do\n    use Manifest\n  end\n\n  run NotFound.new\nend.to_app\n\nrun app\n```\n\nNow we can test that everything is working by running:\n\n```shell\nbundle exec rackup -p 9292 config.ru\n```\n\nNow go to [http://localhost:9292/manifest.json](http://localhost:9292/manifest.json) and check if the add-on manifest is served correctly.\n\nYou can stop the add-on by pressing \u003ckbd\u003eCtrl\u003c/kbd\u003e+\u003ckbd\u003eC\u003c/kbd\u003e.\n\n## Step 3: Basic streaming\n\nFirst let's update our manifest, so Stremio will know that our add-on support streams. Change the `resources` section of the manifest from:\n\n```ruby\n  resources: []\n```\n\nto this:\n\n```ruby\n  resources: [\n    { name: \"stream\", types: [ \"movie\", \"series\" ], idPrefixes: [ \"tt\", \"hrb\" ] }\n  ]\n```\n\nTo implement basic streaming, we will define a hash with a few public domain movies, just after the manifest definition.\n\n```ruby\nSTREAMS = {\n  \"movie\" =\u003e {\n    \"tt0032138\" =\u003e [\n      { title: \"Torrent\", infoHash: \"24c8802e2624e17d46cd555f364debd949f2c81e\", fileIdx: 0 }\n    ],\n    \"tt0017136\" =\u003e [\n      { title: \"Torrent\", infoHash: \"dca926c0328bb54d209d82dc8a2f391617b47d7a\", fileIdx: 1 }\n    ],\n    \"tt0051744\" =\u003e [\n      { title: \"Torrent\", infoHash: \"9f86563ce2ed86bbfedd5d3e9f4e55aedd660960\" }\n    ],\n    \"tt1254207\" =\u003e [\n      { title: \"HTTP URL\", url: \"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4\" }\n    ],\n    \"tt0031051\" =\u003e [\n      { title: \"YouTube\", ytId: \"m3BKVSpP80s\" }\n    ],\n    \"tt0137523\" =\u003e [\n      { title: \"External URL\", externalUrl: \"https://www.netflix.com/watch/26004747\" }\n    ]\n  },\n\n  \"series\" =\u003e {\n    \"tt1748166:1:1\" =\u003e [\n      { title: \"Torrent\", infoHash: \"07a9de9750158471c3302e4e95edb1107f980fa6\" }\n    ],\n\n    \"hrbtt0147753:1:1\" =\u003e [\n      { title: \"YouTube\", ytId: \"5EQw5NYlbyE\" }\n    ],\n    \"hrbtt0147753:1:2\" =\u003e [\n      { title: \"YouTube\", ytId: \"ZzdBKcVzx9Y\" }\n    ],\n  }\n}\n```\n\nAnd then implement the `/stream/` path.\n\nFirst add the class that will handle the response inside `stremio-addon.rb`:\n\n```ruby\nclass Stream \u003c Resource\n  def call(env)\n    args = parse_request(env)\n    return @app.call(env) unless STREAMS.key?(args[:type])\n\n    streams = {\n      streams: STREAMS[args[:type]][args[:id]] || []\n    }\n\n    [200, @@headers, [streams.to_json]]\n  end\nend\n```\n\nNow let `rack` know that our `Stream` class handles that path by appending one more mapping after `Manifest`:\n\n```ruby\n  map \"/stream\" do\n    use Stream\n  end\n```\n\nSo your `config.ru` should look like this:\n\n```ruby\nrequire './stremio-addon.rb'\n\napp = Rack::Builder.new do\n  use Rack::Reloader\n  use Rack::ContentLength\n\n  map \"/manifest.json\" do\n    use Manifest\n  end\n\n  map \"/stream\" do\n    use Stream\n  end\n\n  run NotFound.new\nend.to_app\n\nrun app\n```\n\n**As you can see, this is an add-on that allows Stremio to stream 6 public domain movies and 3 series episode - in very few lines of code.**\n\nDepending on your source, you can implement streaming (/stream/) or catalogs (/catalog/) of movie, series, channel or tv content types.\n\nTo load that add-on in the desktop Stremio, start the add-on, as described above, then click the add-on button (puzzle piece icon) on the top right, and write [http://127.0.0.1:9292/manifest.json](http://127.0.0.1:9292/manifest.json) in the \"Addo-n Repository Url\" field on the top left.\n\n## Step 4: Implement catalog\n\nWe have 2 types of resources serving meta:\n\n    /catalog/ serves basic metadata (id, type, name, poster) and handles loading of both the catalog feed and searching;\n\n    /meta/ serves advanced metadata for individual items, for imdb ids though (what we're using in this example add-on), we usualy do not need to handle this method at all as it is handled automatically by Stremio's Cinemeta\n\n**For now, we have the simple goal of loading the movies we provide on the top of Discover.**\n\nLets's tell Stremio that we will handle catalog.\n\nWe will update our manifest to reflect that.\n\n```ruby\n  catalogs: [\n    { type: :movie, id: \"Hello, Ruby\" },\n    { type: :series, id: \"Hello, Ruby\" }\n  ],\n\n  resources: [\n    \"catalog\",\n    { name: \"stream\", types: [ \"movie\", \"series\" ], idPrefixes: [ \"tt\", \"hrb\" ] }\n  ]\n```\n\nAs you can see we define two catalogs - one for movies and one for series. In the `resources` section we state that the `catalog` resource is also supported by our add-on.\n\nNow we can append our catalog data into the `stremio-addon.rb` file:\n\n```ruby\nCATALOG = {\n  \"movie\" =\u003e [\n    { id: \"tt0032138\", name: \"The Wizard of Oz\", genres: [ :Adventure, :Family, :Fantasy, :Musical ] },\n    { id: \"tt0017136\", name: \"Metropolis\", genres: [:Drama, :\"Sci-Fi\"] },\n    { id: \"tt0051744\", name: \"House on Haunted Hill\", genres: [:Horror, :Mystery] },\n    { id: \"tt1254207\", name: \"Big Buck Bunny\", genres: [:Animation, :Short, :Comedy], },\n    { id: \"tt0031051\", name: \"The Arizona Kid\", genres: [:Music, :War, :Western] },\n    { id: \"tt0137523\", name: \"Fight Club\", genres: [:Drama] }\n  ],\n  \"series\" =\u003e [\n    {\n      id: \"tt1748166\",\n      name: \"Pioneer One\",\n      genres: [:Drama],\n      videos: [\n        { season: 1, episode: 1, id: \"tt1748166:1:1\", title: \"Earthfall\", released: \"2010-06-16\"  }\n      ]\n    },\n    {\n      id: \"hrbtt0147753\",\n      name: \"Captain Z-Ro\",\n      description: \"From his secret laboratory, Captain Z-Ro and his associates use their time machine, the ZX-99, to learn from the past and plan for the future.\",\n      releaseInfo: \"1955-1956\",\n      logo: \"https://fanart.tv/fanart/tv/70358/hdtvlogo/captain-z-ro-530995d5e979d.png\",\n      imdbRating: 6.9,\n      genres: [:\"Sci-Fi\"],\n      videos: [\n        { season: 1, episode: 1, id: \"hrbtt0147753:1:1\", title: \"Christopher Columbus\", released: \"1955-12-18\" },\n        { season: 1, episode: 2, id: \"hrbtt0147753:1:2\", title: \"Daniel Boone\", released: \"1955-12-25\" }\n      ]\n    }\n  ]\n}\n```\n\nThen let's handle the catalog route.\n\n```ruby\nclass Catalog \u003c Resource\n  def call(env)\n    args = parse_request(env)\n    return @app.call(env) unless CATALOG.key?(args[:type])\n\n    metaPreviews = CATALOG[args[:type]].map do |item|\n      {\n        id: item[:id],\n        type: args[:type],\n        name: item[:name],\n        genres: item[:genres],\n        poster: METAHUB_URL % item[:id]\n      }\n    end\n\n    catalog = { metas: metaPreviews }\n\n    [200, @@headers, [catalog.to_json]]\n  end\nend\n```\n\nDon't forget to update the `config.ru` with the new route:\n\n```ruby\n  map \"/catalog\" do\n    use Catalog\n  end\n```\n\nIf you run and install your add-on right now you'll notice that the new catalogs appear in the Stremio's Board and also you have this catalogs in the left pane of the discover. You can also play the videos from the sources we provide.\n\nHowever, for the sake of completeness, we'll handle one more case, where Cinemeta is unable to find information about the content.\n\n## Step 5: Implement meta\n\nMaybe you have noticed that in the catalog that we defined in **Step 4**, there is more data than the one we provide on catalog request. There is also an item with `id` prefixed with `hrb` instead of `tt`.\n\nCinemeta looks up the media by IMDB ID so it can't find data for our `hrb` prefixed entry. This is the case where our add-on must provide that data.\n\nWe already have all the information needed, so let's expose it.\n\nFirst we will again update our add-on's manifest.\n\n```ruby\n  resources: [\n    \"catalog\",\n    # The meta call will be invoked only for series with ID starting with hrb\n    { name: \"meta\", types: [ \"series\" ], idPrefixes: [ \"hrb\" ] },\n    { name: \"stream\", types: [ \"movie\", \"series\" ], idPrefixes: [ \"tt\", \"hrb\" ] }\n  ]\n```\n\nThe `idPrefixes` defines a list of prefixes that we can handle. Stremio will not ask our add-on for data on `ID` that doesn't start with some of the predefined prefixes. If we omit this parameter, Stremio will ask our add-on for any item it encounters.\n\nWith the updated manifest, we are ready to handle the `/meta/` request.\n\n```ruby\nOPTIONAL_META = [:posterShape, :background, :logo, :videos, :description, :releaseInfo, :imdbRating, :director, :cast, :dvdRelease, :released, :inTheaters, :certification, :runtime, :language, :country, :awards, :website, :isPeered]\n\nclass Meta \u003c Resource\n  def call(env)\n    args = parse_request(env)\n    return @app.call(env) unless CATALOG.key?(args[:type])\n\n    item = CATALOG[args[:type]].detect { |item| item[:id] == args[:id] }\n\n    meta = { meta: nil }\n\n    unless item.nil? then\n      # Build the meta info\n      meta[:meta] = {\n        id: item[:id],\n        type: args[:type],\n        name: item[:name],\n        genres: item[:genres],\n        poster: METAHUB_URL % item[:id]\n      }\n\n      # Populate optional values\n      OPTIONAL_META.each { |tag| meta[:meta][tag] = item[tag] if item.key?(tag) }\n    end\n\n    [200, @@headers, [meta.to_json]]\n  end\nend\n```\n\nThe last thing we have to do is to update our `config.ru` with the `/meta/` route.\n\n```ruby\n  map \"/meta\" do\n    use Meta\n  end\n```\n\nThat's it. We are ready with our add-on. You can now load it into Stremio and start steaming.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FStremio%2Faddon-helloworld-ruby","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FStremio%2Faddon-helloworld-ruby","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FStremio%2Faddon-helloworld-ruby/lists"}