├── .editorconfig ├── .formatter.exs ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── fixture └── vcr_cassettes │ ├── instagram_invalid.json │ ├── instagram_short_valid.json │ ├── instagram_valid.json │ ├── pinterest_pin_invalid.json │ ├── pinterest_pin_valid.json │ ├── playbuzz_http_valid.json │ ├── playbuzz_https_valid.json │ ├── soundcloud_invalid.json │ ├── soundcloud_valid.json │ ├── vimeo_valid.json │ ├── youtu_be_invalid.json │ ├── youtu_be_valid.json │ ├── youtube_invalid.json │ ├── youtube_playlist_invalid.json │ ├── youtube_playlist_valid.json │ └── youtube_valid.json ├── lib ├── oembed.ex └── oembed │ ├── provider.ex │ ├── providers │ ├── discoverable_provider.ex │ ├── instagram_provider.ex │ ├── pinterest_provider.ex │ ├── vimeo_provider.ex │ └── youtube_provider.ex │ ├── resource.ex │ └── resources │ ├── link.ex │ ├── photo.ex │ ├── rich.ex │ └── video.ex ├── mix.exs └── test ├── oembed_test.exs └── test_helper.exs /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | 15 | [*.md] 16 | indent_size = 4 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | env: 10 | MIX_ENV: test 11 | 12 | jobs: 13 | static_code_analysis: 14 | name: Static Code Analysis 15 | runs-on: ubuntu-24.04 16 | strategy: 17 | matrix: 18 | otp: ["26.2.1"] 19 | elixir: ["1.16.1", "1.17.3"] 20 | steps: 21 | - name: Cancel Previous Runs 22 | uses: styfle/cancel-workflow-action@0.6.0 23 | with: 24 | access_token: ${{ github.token }} 25 | 26 | - name: Checkout 27 | uses: actions/checkout@v2 28 | with: 29 | fetch-depth: 0 30 | 31 | - name: Setup 32 | uses: erlef/setup-beam@v1.18.2 33 | with: 34 | otp-version: ${{matrix.otp}} 35 | elixir-version: ${{ matrix.elixir }} 36 | 37 | - name: Install Dependencies 38 | if: steps.mix-cache.outputs.cache-hit != 'true' 39 | run: | 40 | mkdir -p priv/plts 41 | mix local.rebar --force 42 | mix local.hex --force 43 | mix deps.get 44 | mix deps.compile 45 | 46 | - name: Check Code Format 47 | run: mix format --check-formatted 48 | 49 | - name: Run Credo 50 | run: mix credo 51 | 52 | unit_tests: 53 | name: Unit Tests 54 | runs-on: ubuntu-24.04 55 | strategy: 56 | fail-fast: false 57 | matrix: 58 | otp: ["26.2.1"] 59 | elixir: ["1.16.1", "1.17.3"] 60 | steps: 61 | - name: Cancel Previous Runs 62 | uses: styfle/cancel-workflow-action@0.6.0 63 | with: 64 | access_token: ${{ github.token }} 65 | 66 | - name: Checkout 67 | uses: actions/checkout@v2 68 | with: 69 | fetch-depth: 0 70 | 71 | - name: Setup 72 | uses: erlef/setup-beam@v1.18.2 73 | with: 74 | otp-version: ${{matrix.otp}} 75 | elixir-version: ${{ matrix.elixir }} 76 | 77 | - name: Install Dependencies 78 | if: steps.mix-cache.outputs.cache-hit != 'true' 79 | run: | 80 | mkdir -p priv/plts 81 | mix local.rebar --force 82 | mix local.hex --force 83 | mix deps.get 84 | mix deps.compile 85 | 86 | - name: Run test 87 | run: mix test --trace --slowest 10 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | oembed-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | 28 | # Ignore Mix lock file 29 | mix.lock 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.5.0] - 2024-12-14 4 | 5 | Maintenance release. 6 | 7 | ### Fixed 8 | - Fix "defined in application" runtime warnings ([@alexgleason](https://github.com/alexgleason)) 9 | - Remove unused @requred_keys ([@alexgleason](https://github.com/alexgleason)) 10 | - Fix duplicate keys warnings ([@alexgleason](https://github.com/alexgleason)) 11 | 12 | ### Chore 13 | 14 | - Upgrade dev dependencies 15 | - Remove empty config file 16 | - Regenerate formatter config and gitignore with the latest Elixir 17 | - Fix CI workflow 18 | 19 | ## [0.4.1] - 2021-01-14 20 | ### Fixed 21 | - Call the youtube oembed with https ([@fatboypunk](https://github.com/fatboypunk)) 22 | 23 | ## [0.4.0] - 2020-07-21 24 | ### Added 25 | - Add Youtube provider ([@Kuret](https://github.com/Kuret)) 26 | 27 | ## [0.3.0] - 2019-03-27 28 | ### Added 29 | - Add Vimeo provider ([@fatboypunk](https://github.com/fatboypunk)) 30 | 31 | ## [0.2.2] - 2018-09-11 32 | ### Fixed 33 | - Fix link selector for Discoverable provider 34 | 35 | ## [0.2.1] - 2017-11-02 36 | ### Fixed 37 | - Fix handling of relative endpoint urls ([@slavone](https://github.com/slavone)) 38 | 39 | ## [0.2.0] - 2017-09-13 40 | ### Added 41 | - Ability to add custom providers via mix config in parent application ([@slavone](https://github.com/slavone)) 42 | 43 | ### Fixed 44 | - Encode Instagram photo URL in API request. 45 | 46 | ## [0.1.1] - 2017-03-08 47 | ### Fixed 48 | - Fix crashing when URL is empty or nil. 49 | 50 | ## [0.1.0] - 2017-02-25 51 | 52 | Initial release. 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sergey Storchay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OEmbed 2 | 3 | [![CI](https://github.com/r8/elixir-oembed/workflows/CI/badge.svg)](https://github.com/r8/elixir-oembed/actions?query=workflow%3ACI) 4 | [![Hex.pm](https://img.shields.io/hexpm/v/oembed.svg?style=flat-square)](https://hex.pm/packages/oembed) 5 | [![Hex.pm](https://img.shields.io/hexpm/dt/oembed.svg?style=flat-square)](https://hex.pm/packages/oembed) 6 | 7 | oEmbed consumer library for Elixir applications. 8 | 9 | > oEmbed is a format for allowing an embedded representation of a URL on third party sites. The simple API allows a website to display embedded content (such as photos or videos) when a user posts a link to that resource, without having to parse the resource directly. 10 | > 11 | > -- See [oembed.com](http://oembed.com) for more info about the protocol. 12 | 13 | This library supports any discoverable oEmbed endpoint and some other services via custom adapters. 14 | Among them: 15 | 16 | - YouTube 17 | - Instagram 18 | - Pinterest 19 | - Vimeo 20 | 21 | ## Installation 22 | 23 | Add `oembed` to your list of dependencies in `mix.exs`: 24 | 25 | ```elixir 26 | def deps do 27 | [{:oembed, "~> 0.5.0"}] 28 | end 29 | ``` 30 | 31 | ## Usage 32 | 33 | ```elixir 34 | {:ok, result} = OEmbed.for("https://www.youtube.com/watch?v=dQw4w9WgXcQ") 35 | ``` 36 | 37 | ## Custom providers 38 | 39 | You can implement modules that support provider behaviour and add them to the provider list from your app config 40 | 41 | ```elixir 42 | config :oembed, :providers, [MyApp.OEmbed.SomeProvider, MyApp.OEmbed.SomeOtherProvider] 43 | ``` 44 | -------------------------------------------------------------------------------- /fixture/vcr_cassettes/instagram_invalid.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "body": "", 5 | "headers": [], 6 | "method": "get", 7 | "options": { 8 | "follow_redirect": "true", 9 | "ssl_options": { 10 | "versions": [ 11 | "tlsv1.2" 12 | ] 13 | } 14 | }, 15 | "request_body": "", 16 | "url": "https://api.instagram.com/oembed?url=https%3A%2F%2Fwww.instagram.com%2Fp%2Finvalid_url%2F" 17 | }, 18 | "response": { 19 | "body": "No Media Match", 20 | "headers": { 21 | "Content-Type": "text/html; charset=utf-8", 22 | "Cache-Control": "private, no-cache, no-store, must-revalidate", 23 | "Pragma": "no-cache", 24 | "Expires": "Sat, 01 Jan 2000 00:00:00 GMT", 25 | "Vary": "Cookie, Accept-Language", 26 | "Content-Language": "en", 27 | "Date": "Mon, 16 Oct 2017 11:18:31 GMT", 28 | "Set-Cookie": "rur=FTW; Path=/", 29 | "Connection": "keep-alive", 30 | "Content-Length": "14" 31 | }, 32 | "status_code": 404, 33 | "type": "ok" 34 | } 35 | } 36 | ] -------------------------------------------------------------------------------- /fixture/vcr_cassettes/instagram_short_valid.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "body": "", 5 | "headers": [], 6 | "method": "get", 7 | "options": { 8 | "follow_redirect": "true", 9 | "ssl_options": { 10 | "versions": [ 11 | "tlsv1.2" 12 | ] 13 | } 14 | }, 15 | "request_body": "", 16 | "url": "https://api.instagram.com/oembed?url=http%3A%2F%2Finstagr.am%2Fp%2FBaOHwvclrzJ%2F" 17 | }, 18 | "response": { 19 | "body": "{\"version\": \"1.0\", \"title\": \"Trise valtyje neskaitant rudens\\nWe love Lithuania \\u2013 Photo by: Tomas Ki\\u0161k\\u016bnas\\n#Lithuania \\u2013 #Lietuva\\nWeloveLithuania.com/trise-valtyje-neskaitant-rudens/\", \"author_name\": \"welovelithuania\", \"author_url\": \"https://www.instagram.com/welovelithuania\", \"author_id\": 1306238348, \"media_id\": \"1625270651333295305_1306238348\", \"provider_name\": \"Instagram\", \"provider_url\": \"https://www.instagram.com\", \"type\": \"rich\", \"width\": 658, \"height\": null, \"html\": \"\\u003cblockquote class=\\\"instagram-media\\\" data-instgrm-captioned data-instgrm-version=\\\"7\\\" style=\\\" background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:658px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);\\\"\\u003e\\u003cdiv style=\\\"padding:8px;\\\"\\u003e \\u003cdiv style=\\\" background:#F8F8F8; line-height:0; margin-top:40px; padding:33.93518518518518% 0; text-align:center; width:100%;\\\"\\u003e \\u003cdiv style=\\\" background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAMUExURczMzPf399fX1+bm5mzY9AMAAADiSURBVDjLvZXbEsMgCES5/P8/t9FuRVCRmU73JWlzosgSIIZURCjo/ad+EQJJB4Hv8BFt+IDpQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;\\\"\\u003e\\u003c/div\\u003e\\u003c/div\\u003e \\u003cp style=\\\" margin:8px 0 0 0; padding:0 4px;\\\"\\u003e \\u003ca href=\\\"https://www.instagram.com/p/BaOHwvclrzJ/\\\" style=\\\" color:#000; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none; word-wrap:break-word;\\\" target=\\\"_blank\\\"\\u003eTrise valtyje neskaitant rudens We love Lithuania \\u2013 Photo by: Tomas Ki\\u0161k\\u016bnas #Lithuania \\u2013 #Lietuva WeloveLithuania.com/trise-valtyje-neskaitant-rudens/\\u003c/a\\u003e\\u003c/p\\u003e \\u003cp style=\\\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\\\"\\u003eA post shared by We love Lithuania (@welovelithuania) on \\u003ctime style=\\\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\\\" datetime=\\\"2017-10-14T07:49:41+00:00\\\"\\u003eOct 14, 2017 at 12:49am PDT\\u003c/time\\u003e\\u003c/p\\u003e\\u003c/div\\u003e\\u003c/blockquote\\u003e\\n\\u003cscript async defer src=\\\"//platform.instagram.com/en_US/embeds.js\\\"\\u003e\\u003c/script\\u003e\", \"thumbnail_url\": \"https://instagram.fiev2-1.fna.fbcdn.net/t51.2885-15/s640x640/sh0.08/e35/22429598_1930822020575673_721450674893619200_n.jpg\", \"thumbnail_width\": 640, \"thumbnail_height\": 434}", 20 | "headers": { 21 | "Content-Type": "application/json", 22 | "Cache-Control": "private, no-cache, no-store, must-revalidate", 23 | "Pragma": "no-cache", 24 | "Expires": "Sat, 01 Jan 2000 00:00:00 GMT", 25 | "Vary": "Cookie, Accept-Language, Accept-Encoding", 26 | "Content-Language": "en", 27 | "Date": "Mon, 16 Oct 2017 11:18:30 GMT", 28 | "Set-Cookie": "rur=FTW; Path=/", 29 | "Connection": "keep-alive", 30 | "Content-Length": "2974" 31 | }, 32 | "status_code": 200, 33 | "type": "ok" 34 | } 35 | } 36 | ] -------------------------------------------------------------------------------- /fixture/vcr_cassettes/instagram_valid.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "body": "", 5 | "headers": [], 6 | "method": "get", 7 | "options": { 8 | "follow_redirect": "true", 9 | "ssl_options": { 10 | "versions": [ 11 | "tlsv1.2" 12 | ] 13 | } 14 | }, 15 | "request_body": "", 16 | "url": "https://api.instagram.com/oembed?url=https%3A%2F%2Fwww.instagram.com%2Fp%2FBaOHwvclrzJ%2F" 17 | }, 18 | "response": { 19 | "body": "{\"version\": \"1.0\", \"title\": \"Trise valtyje neskaitant rudens\\nWe love Lithuania \\u2013 Photo by: Tomas Ki\\u0161k\\u016bnas\\n#Lithuania \\u2013 #Lietuva\\nWeloveLithuania.com/trise-valtyje-neskaitant-rudens/\", \"author_name\": \"welovelithuania\", \"author_url\": \"https://www.instagram.com/welovelithuania\", \"author_id\": 1306238348, \"media_id\": \"1625270651333295305_1306238348\", \"provider_name\": \"Instagram\", \"provider_url\": \"https://www.instagram.com\", \"type\": \"rich\", \"width\": 658, \"height\": null, \"html\": \"\\u003cblockquote class=\\\"instagram-media\\\" data-instgrm-captioned data-instgrm-version=\\\"7\\\" style=\\\" background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:658px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);\\\"\\u003e\\u003cdiv style=\\\"padding:8px;\\\"\\u003e \\u003cdiv style=\\\" background:#F8F8F8; line-height:0; margin-top:40px; padding:33.93518518518518% 0; text-align:center; width:100%;\\\"\\u003e \\u003cdiv style=\\\" background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAMUExURczMzPf399fX1+bm5mzY9AMAAADiSURBVDjLvZXbEsMgCES5/P8/t9FuRVCRmU73JWlzosgSIIZURCjo/ad+EQJJB4Hv8BFt+IDpQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;\\\"\\u003e\\u003c/div\\u003e\\u003c/div\\u003e \\u003cp style=\\\" margin:8px 0 0 0; padding:0 4px;\\\"\\u003e \\u003ca href=\\\"https://www.instagram.com/p/BaOHwvclrzJ/\\\" style=\\\" color:#000; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none; word-wrap:break-word;\\\" target=\\\"_blank\\\"\\u003eTrise valtyje neskaitant rudens We love Lithuania \\u2013 Photo by: Tomas Ki\\u0161k\\u016bnas #Lithuania \\u2013 #Lietuva WeloveLithuania.com/trise-valtyje-neskaitant-rudens/\\u003c/a\\u003e\\u003c/p\\u003e \\u003cp style=\\\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\\\"\\u003eA post shared by We love Lithuania (@welovelithuania) on \\u003ctime style=\\\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\\\" datetime=\\\"2017-10-14T07:49:41+00:00\\\"\\u003eOct 14, 2017 at 12:49am PDT\\u003c/time\\u003e\\u003c/p\\u003e\\u003c/div\\u003e\\u003c/blockquote\\u003e\\n\\u003cscript async defer src=\\\"//platform.instagram.com/en_US/embeds.js\\\"\\u003e\\u003c/script\\u003e\", \"thumbnail_url\": \"https://instagram.fiev2-1.fna.fbcdn.net/t51.2885-15/s640x640/sh0.08/e35/22429598_1930822020575673_721450674893619200_n.jpg\", \"thumbnail_width\": 640, \"thumbnail_height\": 434}", 20 | "headers": { 21 | "Content-Type": "application/json", 22 | "Cache-Control": "private, no-cache, no-store, must-revalidate", 23 | "Pragma": "no-cache", 24 | "Expires": "Sat, 01 Jan 2000 00:00:00 GMT", 25 | "Vary": "Cookie, Accept-Language, Accept-Encoding", 26 | "Content-Language": "en", 27 | "Date": "Mon, 16 Oct 2017 11:18:33 GMT", 28 | "Set-Cookie": "rur=FTW; Path=/", 29 | "Connection": "keep-alive", 30 | "Content-Length": "2974" 31 | }, 32 | "status_code": 200, 33 | "type": "ok" 34 | } 35 | } 36 | ] -------------------------------------------------------------------------------- /fixture/vcr_cassettes/pinterest_pin_invalid.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "body": "", 5 | "headers": [], 6 | "method": "get", 7 | "options": { 8 | "follow_redirect": "true", 9 | "ssl_options": { 10 | "versions": [ 11 | "tlsv1.2" 12 | ] 13 | } 14 | }, 15 | "request_body": "", 16 | "url": "https://www.pinterest.com/pin/invalid_url/" 17 | }, 18 | "response": { 19 | "body": "\n\n\n\n \n \n\n \n \n\n \n \n \n \n\n \n \n \n \n \n\n \n\n \n \n\n \n\n \n\n \n \n \n \n \n \n\n \n \n\n \n\n \n\n \n \n\n \n \n \n\n \n \n \n\n \n \n\n \n \n \n \n \n \n\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n\n \n \n \n\n \n \n\n\n Pinterest\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n\n\n\n\n \n\n \n
\n\n\n\n\n\n\n\n \n\n\n\n\n
\n
\n \n \n \n\n \n \n\n \n \n
\n\n
Welcome to Pinterest

OR

By continuing, you agree to Pinterest's Terms of Service, Privacy Policy
Oof! Looks like the page is broken.
We'll look into it!
\n
\n\n
\n
\n
\n
\n\n\n \n \n \n \n \n \n\n\n\n\n\n\n
\n
85c3b39e62ad614433bab0bb1bc18f0a
\n\n\n\n\n", 20 | "headers": { 21 | "Age": "0", 22 | "Content-Security-Policy": "media-src 'self' *.pinimg.com blob: data:; object-src 'self' h.online-metrix.net; connect-src 'self' *.pinimg.com *.pinterest.com *.branch.io cdn.ampproject.org pinterest-media-upload.s3.amazonaws.com pinterest-waterloo.s3.amazonaws.com *.cedexis.com *.cedexis-radar.net ; script-src 'nonce-USLSEoXTTd' 'strict-dynamic' 'self' *.pinterest.com *.pinimg.com *.google.com connect.facebook.net *.google-analytics.com *.googleapis.com *.gstatic.com *.accountkit.com *.facebook.com www.googleadservices.com googleads.g.doubleclick.net platform.twitter.com *.online-metrix.net *.bnc.lt bnc.lt *.branch.io *.yozio.com cdn.ampproject.org radar.cedexis.com *.cedexis-test.com 'unsafe-inline' 'unsafe-eval'; base-uri 'none'; report-uri /_/_/csp_report/", 23 | "Content-Type": "text/html; charset=utf-8", 24 | "P3P": "CP=\"This is not a P3P policy. See https://www.pinterest.com/_/_/help/articles/pinterest-and-p3p for more info.\"", 25 | "Set-Cookie": "_auth=0; Domain=.pinterest.com; expires=Thu, 11-Oct-2018 11:18:32 GMT; httponly; Max-Age=31103999; Path=/; secure", 26 | "Strict-Transport-Security": "max-age=63072000; includeSubDomains; preload", 27 | "X-Content-Type-Options": "nosniff", 28 | "X-Exp-Upstream-Env": "python", 29 | "X-Frame-Options": "SAMEORIGIN", 30 | "X-Pinterest-RID": "539486922308", 31 | "X-UA-Compatible": "IE=edge", 32 | "X-Upstream-Env": "python", 33 | "X-XSS-Protection": "1; mode=block", 34 | "Transfer-Encoding": "chunked", 35 | "Date": "Mon, 16 Oct 2017 11:18:33 GMT", 36 | "Connection": "keep-alive", 37 | "Vary": "User-Agent, Cookie, Accept-Encoding", 38 | "Pinterest-Generated-By": "coreapp-webapp-prod-0a014557", 39 | "Pinterest-Version": "7ba6d72" 40 | }, 41 | "status_code": 200, 42 | "type": "ok" 43 | } 44 | } 45 | ] -------------------------------------------------------------------------------- /fixture/vcr_cassettes/soundcloud_invalid.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "body": "", 5 | "headers": [], 6 | "method": "get", 7 | "options": { 8 | "follow_redirect": "true", 9 | "ssl_options": { 10 | "versions": [ 11 | "tlsv1.2" 12 | ] 13 | } 14 | }, 15 | "request_body": "", 16 | "url": "https://soundcloud.com/invalid_user/invalid_track" 17 | }, 18 | "response": { 19 | "binary": false, 20 | "body": "\n\n\n\n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n SoundCloud - Hear the world’s sounds\n\n \n \n\n \n \n\n \n \n\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n\n \n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n\n \n \n \n\n\n
\n \n \n \n
\n
\n
\n \n
\n
\n \n
\n
\n
\n \n \n \n \n \n \n\n \n \n\n \n\n \n\n \n
\n

\n Your current browser isn't compatible with SoundCloud.
\n Please download one of our supported browsers. Need help?\n

\n\n \n
\n\n \n\n \n
\n

Sorry! Something went wrong

\n
\n

Is your network connection unstable or browser outdated?

\n
\n \n
\n\n \n\n \n \n \n\n \n \n \n\n \n

\n Popular searches\n

\n\n \n
\n\n \n \n \n\n \n \n \n \n \n \n\n \n\n \n \n \n\n \n \n \n\n \n\n\n", 21 | "headers": { 22 | "Content-Type": "text/html", 23 | "Content-Length": "16020", 24 | "Connection": "keep-alive", 25 | "Set-Cookie": "sc_anonymous_id=890396-123522-648096-486817; path=/; expires=Fri, 08 Sep 2028 06:15:42 GMT; domain=.soundcloud.com", 26 | "X-Pants": "distant-towel", 27 | "X-XSS-Protection": "1; mode=block", 28 | "Cache-Control": "private, max-age=0, no-cache, no-store", 29 | "X-Frame-Options": "SAMEORIGIN", 30 | "Server-Timing": "rollouts; dur=12.268397; desc=\"api-v2/rollouts\", geoip; dur=3.367135; desc=\"geoip/geoip\", experiments; dur=7.561578; desc=\"api-v2/experiments\", privacySettings; dur=7.8053; desc=\"api-v2/privacySettings\", geoip; dur=3.558454; desc=\"geoip/geoip\"", 31 | "Date": "Tue, 11 Sep 2018 06:15:42 GMT", 32 | "Server": "am/2", 33 | "X-Cache": "Error from cloudfront", 34 | "Via": "1.1 b6a3e4c49d0265073859268bbecf413b.cloudfront.net (CloudFront)", 35 | "X-Amz-Cf-Id": "onGN_xliHEoPv6OAGVM2Ps33pUnCUD3XfADVK0v6vDlswNsiv-JnhQ==" 36 | }, 37 | "status_code": 404, 38 | "type": "ok" 39 | } 40 | } 41 | ] -------------------------------------------------------------------------------- /fixture/vcr_cassettes/soundcloud_valid.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "body": "", 5 | "headers": [], 6 | "method": "get", 7 | "options": { 8 | "follow_redirect": "true", 9 | "ssl_options": { 10 | "versions": [ 11 | "tlsv1.2" 12 | ] 13 | } 14 | }, 15 | "request_body": "", 16 | "url": "https://soundcloud.com/forss/flickermood" 17 | }, 18 | "response": { 19 | "binary": false, 20 | "body": "\n\n\n\n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n Flickermood by Forss | Free Listening on SoundCloud\n\n \n \n\n \n \n\n \n \n\n \n\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n\n \n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n\n \n \n \n\n\n
\n \n \n \n
\n
\n
\n \n
\n
\n \n
\n
\n
\n \n \n \n \n \n \n\n \n \n\n \n\n \n\n \n
\n

\n Your current browser isn't compatible with SoundCloud.
\n Please download one of our supported browsers. Need help?\n

\n\n \n
\n\n \n\n \n
\n

Sorry! Something went wrong

\n
\n

Is your network connection unstable or browser outdated?

\n
\n \n
\n\n \n\n \n \n \n\n \n \n \n\n \n

\n Popular searches\n

\n\n \n
\n\n \n \n \n\n \n \n \n \n \n \n\n \n\n \n \n \n\n \n \n \n\n \n\n\n", 21 | "headers": { 22 | "Content-Type": "text/html", 23 | "Content-Length": "30898", 24 | "Connection": "keep-alive", 25 | "Set-Cookie": "sc_anonymous_id=459778-166189-206397-64049; path=/; expires=Fri, 08 Sep 2028 06:15:41 GMT; domain=.soundcloud.com", 26 | "X-Pants": "distant-towel", 27 | "X-XSS-Protection": "1; mode=block", 28 | "Cache-Control": "private, max-age=0, no-cache, no-store", 29 | "X-Frame-Options": "SAMEORIGIN", 30 | "Server-Timing": "rollouts; dur=5.78745; desc=\"api-v2/rollouts\", geoip; dur=1.639321; desc=\"geoip/geoip\", experiments; dur=6.993893; desc=\"api-v2/experiments\", resolve; dur=52.007657; desc=\"api-v2/resolve\", privacySettings; dur=6.651687; desc=\"api-v2/privacySettings\", geoip; dur=2.516303; desc=\"geoip/geoip\", user; dur=12.293396; desc=\"api-v2/user\", trackComments; dur=103.328684; desc=\"api-v2/trackComments\"", 31 | "Date": "Tue, 11 Sep 2018 06:15:41 GMT", 32 | "Server": "am/2", 33 | "X-Cache": "Miss from cloudfront", 34 | "Via": "1.1 b6a3e4c49d0265073859268bbecf413b.cloudfront.net (CloudFront)", 35 | "X-Amz-Cf-Id": "o4mm9-zVRDq3eVcPP4i7_kTOHQ9fH9deeOjaw--zSPZOmO3Estv8Mw==" 36 | }, 37 | "status_code": 200, 38 | "type": "ok" 39 | } 40 | }, 41 | { 42 | "request": { 43 | "body": "", 44 | "headers": [], 45 | "method": "get", 46 | "options": { 47 | "follow_redirect": "true", 48 | "ssl_options": { 49 | "versions": [ 50 | "tlsv1.2" 51 | ] 52 | } 53 | }, 54 | "request_body": "", 55 | "url": "https://soundcloud.com/oembed?url=https%3A%2F%2Fsoundcloud.com%2Fforss%2Fflickermood&format=json" 56 | }, 57 | "response": { 58 | "binary": false, 59 | "body": "{\"version\":1.0,\"type\":\"rich\",\"provider_name\":\"SoundCloud\",\"provider_url\":\"http://soundcloud.com\",\"height\":400,\"width\":\"100%\",\"title\":\"Flickermood by Forss\",\"description\":\"From the Soulhack album,\\u0026nbsp;recently featured in this ad \\u003Ca href=\\\"https://www.dswshoes.com/tv_commercial.jsp?m=october2007\\\"\\u003Ehttps://www.dswshoes.com/tv_commercial.jsp?m=october2007\\u003C/a\\u003E \",\"thumbnail_url\":\"http://i1.sndcdn.com/artworks-000067273316-smsiqx-t500x500.jpg\",\"html\":\"\\u003Ciframe width=\\\"100%\\\" height=\\\"400\\\" scrolling=\\\"no\\\" frameborder=\\\"no\\\" src=\\\"https://w.soundcloud.com/player/?visual=true\\u0026url=https%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F293\\u0026show_artwork=true\\\"\\u003E\\u003C/iframe\\u003E\",\"author_name\":\"Forss\",\"author_url\":\"https://soundcloud.com/forss\"}", 60 | "headers": { 61 | "Content-Type": "application/json; charset=utf-8", 62 | "Content-Length": "780", 63 | "Connection": "keep-alive", 64 | "status": "200 OK", 65 | "cache-control": "private, max-age=0, must-revalidate", 66 | "access-control-allow-origin": "*", 67 | "access-control-expose-headers": "Date", 68 | "access-control-allow-headers": "Accept, Authorization, Content-Type, Origin", 69 | "access-control-allow-methods": "GET, PUT, POST, DELETE", 70 | "x-frame-options": "SAMEORIGIN, SAMEORIGIN", 71 | "etag": "\"16fe63ecf1b6ff13a9cd79e32b1fbdec\"", 72 | "date": "Tue, 11 Sep 2018 06:15:42 GMT", 73 | "Server": "am/2", 74 | "X-Cache": "Miss from cloudfront", 75 | "Via": "1.1 b6a3e4c49d0265073859268bbecf413b.cloudfront.net (CloudFront)", 76 | "X-Amz-Cf-Id": "ccZloq_XIcLcdhWcp96oFc-aeKBXiFNvCHe8xsMw6JmWSpcdwQi5bg==" 77 | }, 78 | "status_code": 200, 79 | "type": "ok" 80 | } 81 | } 82 | ] -------------------------------------------------------------------------------- /fixture/vcr_cassettes/vimeo_valid.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request":{ 4 | "body": "", 5 | "headers": [], 6 | "method": "get", 7 | "options": { 8 | "follow_redirect": "true", 9 | "ssl_options": { 10 | "versions": [ 11 | "tlsv1.2" 12 | ] 13 | } 14 | }, 15 | "request_body": "", 16 | "url": "https://vimeo.com/api/oembed.json?url=https%3A%2F%2Fplayer.vimeo.com%2Fvideos%2F123123123%2F" 17 | }, 18 | "response": { 19 | "body": "{\"width\":480,\"video_id\":301578941,\"version\":\"1.0\",\"uri\":\"\/videos\/301578941\",\"upload_date\":\"2018-11-19 05:47:26\",\"type\":\"video\",\"title\":\"embed test\",\"thumbnail_width\":295,\"thumbnail_url_with_play_button\":\"https:\/\/i.vimeocdn.com\/filter\/overlay?src0=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F740162198_295x166.jpg&src1=https%3A%2F%2Ff.vimeocdn.com%2Fp%2Fimages%2Fcrawler_play.png\",\"thumbnail_url\":\"https:\/\/i.vimeocdn.com\/video\/740162198_295x166.jpg\",\"thumbnail_height\":221,\"provider_url\":\"https:\/\/vimeo.com\/\",\"provider_name\":\"Vimeo\",\"is_plus\":\"1\",\"html\":\"