├── .github └── workflows │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── embeds ├── apple-music.ts ├── apple-podcasts.ts ├── apple-tv.ts ├── bandcamp.ts ├── codepen.ts ├── flat_io.ts ├── generic-preview.ts ├── github_gist.ts ├── index.ts ├── instagram.ts ├── lite-yt-embed.js ├── noteflight.ts ├── reddit.ts ├── twitter.ts ├── vimeo.ts ├── whimsical.ts └── youtube.ts ├── jest.config.js ├── main.ts ├── manifest.json ├── package.json ├── rollup.config.js ├── screenshots ├── android.png ├── demo.gif └── ios.png ├── settings-tab.ts ├── settings.ts ├── styles.css ├── test ├── is-within-text.test.ts └── youtube.test.ts ├── tsconfig.json ├── versions.json └── view-plugin.ts /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: ["*"] 5 | env: 6 | PLUGIN_NAME: simple-embed 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 12 | - uses: actions/checkout@v2 13 | 14 | - name: Install modules 15 | run: npm install 16 | 17 | - name: Run build 18 | run: npm run build 19 | 20 | - name: Package 21 | run: | 22 | mkdir ${{ env.PLUGIN_NAME }} 23 | cp main.js manifest.json styles.css ${{ env.PLUGIN_NAME }} 24 | zip -r ${{ env.PLUGIN_NAME}}.zip ${{ env.PLUGIN_NAME }} 25 | echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)" 26 | 27 | - name: Create Release 28 | id: create_release 29 | uses: actions/create-release@v1 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | VERSION: ${{ github.ref }} 33 | with: 34 | tag_name: ${{ github.ref }} 35 | release_name: ${{ github.ref }} 36 | draft: true 37 | prerelease: false 38 | 39 | - name: Upload main.js 40 | id: upload-main 41 | uses: actions/upload-release-asset@v1 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | with: 45 | upload_url: ${{ steps.create_release.outputs.upload_url }} 46 | asset_path: ./main.js 47 | asset_name: main.js 48 | asset_content_type: text/javascript 49 | 50 | - name: Upload manifest.json 51 | id: upload-manifest 52 | uses: actions/upload-release-asset@v1 53 | env: 54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | with: 56 | upload_url: ${{ steps.create_release.outputs.upload_url }} 57 | asset_path: ./manifest.json 58 | asset_name: manifest.json 59 | asset_content_type: application/json 60 | 61 | - name: Upload styles.css 62 | id: upload-styles 63 | uses: actions/upload-release-asset@v1 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | with: 67 | upload_url: ${{ steps.create_release.outputs.upload_url }} 68 | asset_path: ./styles.css 69 | asset_name: styles.css 70 | asset_content_type: text/css 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | genericPreviewCache.json 2 | 3 | # VSCode 4 | .vscode 5 | 6 | # npm 7 | node_modules 8 | package-lock.json 9 | 10 | # build 11 | main.js 12 | *.js.map 13 | 14 | # obsidian 15 | data.json 16 | 17 | .DS_Store 18 | .nova 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | # [1.15.0] 9 | 10 | ### Added 11 | 12 | - improved Reddit embed support 13 | - Thanks to [@chardinson](https://github.com/samwarnick/obsidian-simple-embeds/pull/71)! 14 | 15 | # [1.14.1] 16 | 17 | ### Fixed 18 | 19 | - Fix generic links not working in live preview. 20 | - Thanks [@udbhav-s](https://github.com/samwarnick/obsidian-simple-embeds/pull/62) for fixing! 21 | 22 | # [1.14.0] 23 | 24 | ### Added 25 | 26 | - Support for [Whimsical](https://whimsical.com) links. 27 | - Thanks to [@toddmoy](https://github.com/samwarnick/obsidian-simple-embeds/pull/58) for adding! 28 | 29 | # [1.13.0] 30 | 31 | ### Added 32 | 33 | - A new generic link embed. 34 | - Enable to show metadata from any link that does not match any other enabled embed sources. 35 | - For now, desktop only. Looking into an issue with a dependency on iOS. 36 | - Thanks [@udbhav-s](https://github.com/samwarnick/obsidian-simple-embeds/pull/47) for adding! 37 | 38 | # [1.12.2] 39 | 40 | ### Fixed 41 | 42 | - Fixed issue with keeping links and placing embeds below. 43 | - Thanks [@udbhav-s](https://github.com/samwarnick/obsidian-simple-embeds/pull/53) for fixing! 44 | 45 | # [1.12.1] 46 | 47 | ### Fixed 48 | 49 | - Migrated to CodeMirror 6.0 required in Obsidian v0.15. 50 | - Thanks [@NomarCub](https://github.com/samwarnick/obsidian-simple-embeds/pull/50) for fixing! 51 | 52 | # [1.12.0] 53 | 54 | ### Added 55 | 56 | - Support for YouTube Shorts. 57 | 58 | ### Improved 59 | 60 | - Replaced YouTube embeds with [`lite-youtube-embed`](https://github.com/paulirish/lite-youtube-embed), which should be much faster. 61 | # [1.11.0] 62 | 63 | ### Added 64 | 65 | - Support for Bandcamp links. 66 | - Support for Vimeo links. 67 | - Support for Reddit links. 68 | 69 | # [1.10.2] 70 | 71 | ### Fixed 72 | 73 | - Removed a negative look behind in a regex, which would break in the iOS app. 74 | 75 | # [1.10.1] 76 | 77 | ### Added 78 | 79 | - Disabled live preview support by default and mark as beta. 80 | 81 | # [1.10.0] 82 | 83 | ### Added 84 | 85 | - Live preview support. 86 | - Can be disabled in settings. 87 | 88 | # [1.9.0] 89 | 90 | ### Added 91 | 92 | - Make embed full width by add `|fullwidth` to link description. 93 | - For example, `[Useful description|fullwidth](https://youtube.com/watch?v=acdefghijk)` 94 | - Add option to center embeds. 95 | - Add support for Apple Podcasts links. 96 | - Add partial support for Apple TV links. 97 | - For some reason, playback fails. Clicking play will open Apple TV instead. 98 | - Add partial support for Apple Music. 99 | - Unable to sign into your Apple Music account, so you only get the previews. 100 | 101 | # [1.8.0] 102 | 103 | ### Added 104 | 105 | - Add support for CodePen embeds 106 | - Add support for GitHub gists 107 | 108 | # [1.7.0] 109 | 110 | ### Added 111 | 112 | - Add support for Instagram tv and instagram reels 113 | 114 | # [1.6.3] 115 | 116 | ### Fixed 117 | 118 | - `|noembed` was not being removed from link text. 119 | 120 | # [1.6.2] 121 | 122 | ### Fixed 123 | 124 | - Text within links was being modified even when it was not being replaced with an embed, stripping out any styles. 125 | 126 | ## [1.6.1] 127 | 128 | ### Fixed 129 | 130 | - YouTube links using a start time in the format of 0h0m0s will now be parsed correctly. 131 | 132 | ## [1.6.0] 133 | 134 | ### Added 135 | 136 | - Support for YouTube video start times in links. 137 | - Support for dark themes for Twitter embeds. 138 | - With setting for automatic, always dark, and always light. 139 | 140 | ## [1.5.0] 141 | 142 | ### Added 143 | 144 | - Support for [Flat.io](https://flat.io) links. 145 | - Support for [Noteflight](https://www.noteflight.com) links. 146 | 147 | ## [1.4.0] - 2021-10-29 148 | 149 | ### Added 150 | 151 | - Support for Instagram links. 152 | 153 | ## [1.3.0] - 2021-10-07 154 | 155 | ### Fixed 156 | 157 | - Links will not be replaced with embeds when they are within text. 158 | 159 | ## [1.2.0] - 2021-10-03 160 | 161 | ### Added 162 | 163 | - Setting to choose whether to place embeds above or below the link when "Keep links in preview" is enabled. 164 | 165 | ## [1.1.0] - 2021-10-02 166 | 167 | ### Added 168 | 169 | - Prevent a link from being replaced with an embed by adding `|noembed` to the link text. 170 | - For example, `[Useful description|noembed](https://twitter.com/user/status/123)` 171 | - New setting to place embeds above link in preview, instead of replacing it. 172 | - New setting to disable automatic embeds and require `|embed` to be added to link text. 173 | - For example, `[Some description|embed](https://twitter.com/user/status/123)` 174 | 175 | ## [1.0.2] - 2021-09-29 176 | 177 | ### Fixed 178 | 179 | - Recognize Twitter links for the mobile site 180 | 181 | ## [1.0.1] - 2021-09-21 182 | 183 | ### Changed 184 | 185 | - Only load Twitter embed script if a Twitter link is found 186 | - Add sandbox properties to YouTube iframe 187 | 188 | ## [1.0.0] - 2021-09-15 189 | 190 | - Initial release 🎉 191 | 192 | [unreleased]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.15.0...HEAD 193 | [1.15.0]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.14.1...1.15.0 194 | [1.14.1]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.14.0...1.14.1 195 | [1.14.0]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.13.0...1.14.0 196 | [1.13.0]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.12.2...1.13.0 197 | [1.12.2]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.12.1...1.12.2 198 | [1.12.1]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.12.0...1.12.1 199 | [1.12.0]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.11.0...1.12.0 200 | [1.11.0]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.10.2...1.11.0 201 | [1.10.2]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.10.1...1.10.2 202 | [1.10.1]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.10.0...1.10.1 203 | [1.10.0]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.9.0...1.10.0 204 | [1.9.0]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.8.0...1.9.0 205 | [1.8.0]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.7.0...1.8.0 206 | [1.7.0]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.6.3...1.7.0 207 | [1.6.3]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.6.2...1.6.3 208 | [1.6.2]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.6.1...1.6.2 209 | [1.6.1]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.6.0...1.6.1 210 | [1.6.0]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.5.0...1.6.0 211 | [1.5.0]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.4.0...1.5.0 212 | [1.4.0]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.3.0...1.4.0 213 | [1.3.0]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.2.0...1.3.0 214 | [1.2.0]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.1.0...1.2.0 215 | [1.1.0]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.0.2...1.1.0 216 | [1.0.2]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.0.1...1.0.2 217 | [1.0.1]: https://github.com/samwarnick/obsidian-simple-embeds/compare/1.0.0...1.0.1 218 | [1.0.0]: https://github.com/samwarnick/obsidian-simple-embeds/releases/tag/1.0.0 219 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sam Warnick 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 | 2 | **Notice**: This plugin is no longer actively maintained. Feel free to fork and create a new version. 3 | 4 | 5 | # Simple Embeds 6 | 7 | This [Obsidian](https://obsidian.md) plugin will turn Twitter and YouTube links into embeds when the file is previewed, without changing the contents of your file. Even works when hovering over internal links. 8 | 9 | Just add links like you normally would: 10 | 11 | ```md 12 | [Twitter link](https://twitter.com/johnvoorhees/status/1437735225086316548?s=21) 13 | [YouTube link](https://youtu.be/C4sAUc_ZGMY) 14 | ``` 15 | 16 | If you would like to disable a particular link, add `|noembed` to the link text. For example: 17 | ```md 18 | [Twitter link|noembed](https://twitter.com/johnvoorhees/status/1437735225086316548?s=21) 19 | ``` 20 | 21 | By default, most embeds have a max width of 550px (the max width of Twitter embeds). To make an embed full width[^1], add `|fullwidth` to the link text. For example: 22 | 23 | ```md 24 | [YouTube link|fullwidth](https://www.youtube.com/watch?v=aqafn8kFDyY) 25 | ``` 26 | ## Supported links 27 | 28 | - Apple Music[^2] 29 | - Apple Podcasts 30 | - Apple TV[^2] 31 | - CodePen 32 | - Flat.io 33 | - Github Gists 34 | - Instagram 35 | - Noteflight 36 | - Twitter 37 | - YouTube 38 | - Reddit 39 | 40 | ## Styling 41 | 42 | Each embed is wrapped in a container with the class of `.embed-container` and a class unique to each embed type: 43 | | Embed | Class | 44 | | ------------- | ------------- | 45 | | Apple Music | `.apple-music` | 46 | | Apple Podcasts | `.apple-podcasts` | 47 | | Apple TV | `.apple-tv` | 48 | | CodePen | `.codepen` | 49 | | Flat.io | `.flat_io` | 50 | | GitHub Gists | `.github_gist` | 51 | | Instagram | `.instagram` | 52 | | Noteflight | `.noteflight` | 53 | | Twitter | `.twitter` | 54 | | YouTube | `.youtube` | 55 | | Reddit | `.reddit` | 56 | 57 | You can use these in your own [CSS snippets](https://help.obsidian.md/How+to/Add+custom+styles#Use+Themes+and+or+CSS+snippets). For example, if you would like to make all YouTube embeds full width, you could add the following: 58 | 59 | ```css 60 | .embed-container.youtube { 61 | max-width: 100%; 62 | } 63 | ``` 64 | 65 | Or if you want to center all Twitter embeds: 66 | 67 | ```css 68 | .embed-container.twitter { 69 | margin: 0 auto; 70 | } 71 | ``` 72 | 73 | ## Screenshots 74 | 75 | ![Simple Embeds demo](https://raw.githubusercontent.com/samwarnick/obsidian-simple-embeds/main/screenshots/demo.gif) 76 | 77 | _Demo_ 78 | 79 | ![Screenshot of embeds in iOS app](https://raw.githubusercontent.com/samwarnick/obsidian-simple-embeds/main/screenshots/ios.png) 80 | 81 | _iOS app_ 82 | 83 | ![Screenshot of embeds in Android app](https://raw.githubusercontent.com/samwarnick/obsidian-simple-embeds/main/screenshots/android.png) 84 | 85 | _Android app_ 86 | 87 | [^1]: Many themes set a max width on the preview area, often around 750px. Embeds will not be wider than your theme allows. 88 | [^2]: Partial support. Some known issues exist. 89 | -------------------------------------------------------------------------------- /embeds/apple-music.ts: -------------------------------------------------------------------------------- 1 | import { EmbedSource, EnableEmbedKey } from "./"; 2 | 3 | const APPLE_MUSIC_LINK = new RegExp( 4 | /https:\/\/(?:embed\.)?music\.apple\.com\/.+\/(?:playlist|album)\/.+\/.+/, 5 | ); 6 | const APPLE_MUSIC_SONG_LINK = new RegExp( 7 | /https:\/\/(?:embed\.)?music\.apple\.com\/.+\/(?:playlist|album)\/.+\/.+\?i=\d+/, 8 | ); 9 | 10 | declare global { 11 | interface Window { 12 | MusicKit: any; 13 | } 14 | } 15 | 16 | export class AppleMusicEmbed implements EmbedSource { 17 | name = "Apple Music"; 18 | enabledKey: EnableEmbedKey = "replaceAppleMusicLinks"; 19 | regex = APPLE_MUSIC_LINK; 20 | 21 | createEmbed(link: string, container: HTMLElement) { 22 | const isSong = APPLE_MUSIC_SONG_LINK.test(link); 23 | const iframe = document.createElement("iframe"); 24 | 25 | iframe.src = link.replace( 26 | "https://music.apple", 27 | "https://embed.music.apple", 28 | ); 29 | iframe.setAttr("frameborder", "0"); 30 | iframe.allow = "autoplay *; encrypted-media *; fullscreen *"; 31 | iframe.setAttr( 32 | "sandbox", 33 | "allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation-by-user-activation", 34 | ); 35 | iframe.height = isSong ? "150" : "450"; 36 | iframe.style.width = "100%"; 37 | iframe.style.overflow = "hidden"; 38 | iframe.style.background = "transparent"; 39 | container.classList.add("apple-music"); 40 | container.appendChild(iframe); 41 | 42 | return container; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /embeds/apple-podcasts.ts: -------------------------------------------------------------------------------- 1 | import { EmbedSource, EnableEmbedKey } from "../embeds"; 2 | import { PluginSettings } from "../settings"; 3 | 4 | const APPLE_PODCAST_LINK = new RegExp( 5 | /https:\/\/podcasts.apple.com\/(?[a-z-]+)\/podcast\/(?.*\/id[0-9]+(?:\?i=[0-9]+)?)/, 6 | ); 7 | 8 | export class ApplePodcastsEmbed implements EmbedSource { 9 | name = "Apple Podcasts"; 10 | enabledKey: EnableEmbedKey = "replaceApplePodcastsLinks"; 11 | regex = APPLE_PODCAST_LINK; 12 | 13 | createEmbed(link: string, container: HTMLElement, settings: PluginSettings, currentTheme: "light" | "dark") { 14 | const iframe = document.createElement("iframe"); 15 | 16 | const matches = link.match(APPLE_PODCAST_LINK); 17 | const locale = matches.groups.locale; 18 | const content = matches.groups.content; 19 | const isEpisode = content.contains("?i="); 20 | const src = `https://embed.podcasts.apple.com/${locale}/podcast/${content}${ 21 | isEpisode ? "&" : "?" 22 | }theme=${currentTheme}`; 23 | 24 | iframe.src = src; 25 | iframe.setAttribute("frameborder", "0"); 26 | iframe.setAttribute( 27 | "sandbox", 28 | "allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation-by-user-activation", 29 | ); 30 | iframe.allow = "autoplay *; encrypted-media *;"; 31 | iframe.height = isEpisode ? "175px" : "450px"; 32 | container.appendChild(iframe); 33 | container.classList.add("apple-podcasts"); 34 | return container; 35 | } 36 | 37 | updateTheme(theme: "dark" | "light", settings: PluginSettings) { 38 | const podcastEmbeds = document.querySelectorAll( 39 | ".embed-container.apple-podcasts iframe", 40 | ) as NodeListOf; 41 | podcastEmbeds.forEach((embed) => { 42 | let src = embed.src; 43 | if (theme === "dark") { 44 | src = src.replace("theme=light", "theme=dark"); 45 | } else { 46 | src = src.replace("theme=dark", "theme=light"); 47 | } 48 | embed.src = src; 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /embeds/apple-tv.ts: -------------------------------------------------------------------------------- 1 | import { EmbedSource, EnableEmbedKey } from "../embeds"; 2 | import { PluginSettings } from "../settings"; 3 | 4 | const APPLE_TV_LINK = new RegExp( 5 | /https:\/\/tv.apple.com\/(?[a-z-]+)\/(?episode|show|movie)\/(?\S*)/, 6 | ); 7 | 8 | export class AppleTVEmbed implements EmbedSource { 9 | name = "Apple TV+"; 10 | enabledKey: EnableEmbedKey = "replaceAppleTVLinks"; 11 | regex = APPLE_TV_LINK; 12 | 13 | createEmbed(link: string, container: HTMLElement, settings: PluginSettings, currentTheme: "light" | "dark") { 14 | const wrapper = document.createElement("div"); 15 | wrapper.classList.add("video-wrapper"); 16 | const iframe = document.createElement("iframe"); 17 | 18 | const fakeLink = document.createElement("a"); 19 | fakeLink.href = link; 20 | fakeLink.classList.add("fake-link"); 21 | 22 | const matches = link.match(APPLE_TV_LINK); 23 | const locale = matches.groups.locale; 24 | const type = matches.groups.type; 25 | const content = matches.groups.content; 26 | const src = `https://embed.tv.apple.com/${locale}/${type}/${content}`; 27 | 28 | iframe.src = src; 29 | iframe.setAttribute("frameborder", "0"); 30 | iframe.setAttribute( 31 | "sandbox", 32 | "allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation-by-user-activation", 33 | ); 34 | iframe.allow = "autoplay *; encrypted-media *;"; 35 | wrapper.appendChild(iframe); 36 | wrapper.appendChild(fakeLink); 37 | container.appendChild(wrapper); 38 | container.classList.add("apple-tv"); 39 | return container; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /embeds/bandcamp.ts: -------------------------------------------------------------------------------- 1 | import { requestUrl } from "obsidian"; 2 | import { EmbedSource, EnableEmbedKey } from "./"; 3 | 4 | const BANDCAMP_LINK = new RegExp( 5 | /http(s)?:\/\/([a-z0-9-]+\.)?bandcamp\.com\/(?album|track)\/[\w&$+,\/:;=?@#\._~%-]+/, 6 | ); 7 | const ID = new RegExp(/