Skip to content

Commit f42c8b3

Browse files
committed
Upload source code
0 parents  commit f42c8b3

File tree

14 files changed

+627
-0
lines changed

14 files changed

+627
-0
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

.github/workflows/build.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: Build and Release Extension
2+
3+
on:
4+
push:
5+
workflow_dispatch:
6+
7+
permissions:
8+
contents: write
9+
10+
jobs:
11+
cleanup:
12+
runs-on: ubuntu-latest
13+
permissions:
14+
actions: write
15+
steps:
16+
- uses: actions/checkout@v4
17+
- uses: Bitte-ein-Git/action_clear-runs@main
18+
with:
19+
mode: 'repo'
20+
21+
build:
22+
runs-on: ubuntu-latest
23+
24+
steps:
25+
- name: Checkout Code
26+
uses: actions/checkout@v4
27+
28+
- name: Read Version from Manifest
29+
id: get_version
30+
run: |
31+
VERSION=$(jq -r .version manifest.json)
32+
echo "VERSION=$VERSION" >> $GITHUB_ENV
33+
34+
- name: Install ImageMagick
35+
run: sudo apt-get install -y imagemagick
36+
37+
- name: Generate and Commit Resized Icons
38+
run: |
39+
if [ -f "images/icon.png" ] && [ ! -f "images/icon-128.png" ]; then
40+
convert images/icon.png -resize 128x128 images/icon-128.png
41+
convert images/icon.png -resize 48x48 images/icon-48.png
42+
convert images/icon.png -resize 32x32 images/icon-32.png
43+
convert images/icon.png -resize 16x16 images/icon-16.png
44+
45+
git config --global user.name "github-actions[bot]"
46+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
47+
git add images/icon-*.png
48+
git commit -m "Auto-generate resized icons"
49+
git push
50+
fi
51+
52+
- name: Zip Extension Files
53+
run: |
54+
zip -r applink-extension.zip manifest.json background.js popup.html popup.js options.html options.js images/
55+
56+
- name: Create Release
57+
env:
58+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59+
uses: softprops/action-gh-release@v2
60+
with:
61+
tag_name: "v${{ env.VERSION }}"
62+
name: "📲 • AppLink v${{ env.VERSION }}"
63+
files: |
64+
applink-extension.zip
65+
66+
- name: Output Release URL
67+
run: |
68+
echo "::notice::Release available at: https://github.com/${{ github.repository }}/releases"

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<div id="toc">
2+
<ul align="center" style="list-style: none">
3+
<br>
4+
<img src="./images/icon.png" alt="Logo" width="128">
5+
<br>
6+
<summary>
7+
<h1 style="border-bottom: 0; display: inline-block;">
8+
<b><u>AppLink</u></b></br>
9+
<sub><i>Browser Extension</i> 🌐</sub></h1>
10+
</summary>
11+
Simple App Redirection for Spotify, YouTube, YT Music, and Twitch!
12+
</ul>
13+
</div>
14+
15+
> [!NOTE]
16+
> ⚠️ **Designed for iOS/iPadOS!**
17+
> While this extension is technically functional on Desktop and Android, it is practically useless there.
18+
> <details>
19+
> <summary><strong>Read why</strong></summary>
20+
>
21+
> 1. **Desktop:** The extension heavily relies on specific custom URI schemes (such as `youtube://` or `livecontainer://`) that are simply not supported in desktop environments.
22+
> 2. **Android:** The extension is entirely redundant here. Android natively handles deep links and opens apps by default anyway. This built-in OS behavior works perfectly right out of the box, even for sideloaded applications.
23+
>
24+
> </details>
25+
26+
27+
## What is **AppLink** ?
28+
29+
AppLink is a lightweight browser extension that automatically detects Spotify, YouTube, YouTube Music, and Twitch URLs and redirects them either to their native Apps or to the respective app inside 'LiveContainer'. Finally an 'Open in app...' experience as it should be by default!
30+
31+
## Features
32+
33+
'AppLink' continuously monitors your browser’s navigation events in the background. When it detects URLs from Spotify, YouTube, YouTube Music, or Twitch, it intelligently reroutes them based on your personalized preferences.
34+
35+
### The extension provides a simple popup menu with two categories:
36+
37+
1. 'Open in App'
38+
- Redirects to native or sideloaded App
39+
2. 'Open in LiveContainer'
40+
- Redirects to the respective App inside **LiveContainer**
41+
You can toggle each service independently.
42+
43+
## Privacy
44+
45+
- No personal data collected.
46+
47+
## Source Code & Support
48+
49+
Check out the source code on GitHub: [https://github.com/Bitte-ein-Git/applink-extension](https://github.com/Bitte-ein-Git/applink-extension)

background.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Default settings object
2+
let settings = {
3+
spotify: 'off',
4+
youtube: 'off',
5+
ytmusic: 'off',
6+
twitch: 'off'
7+
};
8+
9+
// Load settings from storage
10+
function loadSettings() {
11+
chrome.storage.sync.get(['appLinkSettings'], (data) => {
12+
if (data.appLinkSettings) {
13+
settings = data.appLinkSettings;
14+
}
15+
});
16+
}
17+
18+
// Initial settings load
19+
loadSettings();
20+
21+
// Listen for settings changes
22+
chrome.storage.onChanged.addListener((changes) => {
23+
if (changes.appLinkSettings) {
24+
settings = changes.appLinkSettings.newValue || settings;
25+
}
26+
});
27+
28+
// Intercept web navigation
29+
chrome.webNavigation.onBeforeNavigate.addListener((details) => {
30+
if (details.frameId !== 0) return;
31+
32+
const url = details.url;
33+
if (!url.startsWith('http')) return;
34+
35+
let targetUri = null;
36+
let service = null;
37+
38+
// Check for YouTube Music
39+
if (url.startsWith('http://music.youtube.com/') || url.startsWith('https://music.youtube.com/')) {
40+
service = 'ytmusic';
41+
if (settings.ytmusic !== 'off') {
42+
const rest = url.replace(/^https?:\/\/music\.youtube\.com\//, '');
43+
targetUri = 'youtubemusic://music.youtube.com/' + rest;
44+
}
45+
}
46+
// Check for YouTube
47+
else if (url.match(/^https?:\/\/(www\.)?youtube\.com\//)) {
48+
service = 'youtube';
49+
if (settings.youtube !== 'off') {
50+
const rest = url.replace(/^https?:\/\/(www\.)?youtube\.com\//, '');
51+
targetUri = 'youtube://youtube.com/' + rest;
52+
}
53+
} else if (url.match(/^https?:\/\/youtu\.be\//)) {
54+
service = 'youtube';
55+
if (settings.youtube !== 'off') {
56+
const rest = url.replace(/^https?:\/\/youtu\.be\//, '');
57+
targetUri = 'youtube://youtu.be/' + rest;
58+
}
59+
}
60+
// Check for Spotify
61+
else if (url.includes('spotify.com') || url.includes('spotify.com')) {
62+
service = 'spotify';
63+
if (settings.spotify !== 'off') {
64+
const match = url.match(/(track|artist|album|playlist)\/(.*)/);
65+
if (match) {
66+
targetUri = 'spotify://' + match[1] + '/' + match[2];
67+
}
68+
}
69+
}
70+
// Check for Twitch
71+
else if (url.match(/^https?:\/\/(www\.|m\.|clips\.)?twitch\.tv/)) {
72+
service = 'twitch';
73+
if (settings.twitch !== 'off') {
74+
const urlObj = new URL(url);
75+
const path = urlObj.pathname;
76+
77+
// Map specific Twitch routes to deep links
78+
if (path.startsWith('/videos/')) {
79+
const vid = path.split('/')[2];
80+
targetUri = 'twitch://video/' + vid;
81+
} else if (path.startsWith('/directory/game/')) {
82+
const game = path.split('/')[3];
83+
targetUri = 'twitch://game/' + game;
84+
} else if (urlObj.hostname === 'clips.twitch.tv' || path.includes('/clip/')) {
85+
targetUri = 'twitch://' + urlObj.hostname + path;
86+
} else {
87+
const matchStream = path.match(/^\/([a-zA-Z0-9_]+)$/);
88+
if (matchStream && !['directory', 'downloads', 'jobs', 'p'].includes(matchStream[1])) {
89+
targetUri = 'twitch://stream/' + matchStream[1];
90+
} else {
91+
targetUri = 'twitch://' + urlObj.hostname + path + urlObj.search;
92+
}
93+
}
94+
}
95+
}
96+
97+
// Redirect if target URI is generated
98+
if (targetUri && service && settings[service] !== 'off') {
99+
let finalUrl = targetUri;
100+
101+
// Encode and route via LiveContainer if enabled
102+
if (settings[service] === 'livecontainer') {
103+
finalUrl = 'livecontainer://open-web-page?url=' + btoa(targetUri);
104+
}
105+
106+
chrome.tabs.update(details.tabId, { url: finalUrl });
107+
}
108+
});

images/icon-128.png

15.6 KB
Loading

images/icon-16.png

2.55 KB
Loading

images/icon-32.png

3.3 KB
Loading

images/icon-48.png

4.73 KB
Loading

images/icon.png

211 KB
Loading

manifest.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"manifest_version": 3,
3+
"name": "AppLink",
4+
"version": "1.2.0",
5+
"description": "Automatically redirects Spotify, YouTube, YT Music, and Twitch links to their respective native apps or LiveContainer.",
6+
"permissions": [
7+
"webNavigation",
8+
"storage",
9+
"tabs"
10+
],
11+
"host_permissions": [
12+
"<all_urls>"
13+
],
14+
"background": {
15+
"service_worker": "background.js"
16+
},
17+
"action": {
18+
"default_popup": "popup.html",
19+
"default_title": "AppLink Configuration"
20+
},
21+
"options_ui": {
22+
"page": "options.html",
23+
"open_in_tab": true
24+
},
25+
"icons": {
26+
"16": "images/icon-16.png",
27+
"32": "images/icon-32.png",
28+
"48": "images/icon-48.png",
29+
"128": "images/icon-128.png"
30+
}
31+
}

0 commit comments

Comments
 (0)