Redirect TikTok links to LiveContainer so they open in the containerized TikTok app.
// ==UserScript==
// @name TikTok → LiveContainer (iOS)
// @namespace sharmanhall
// @version 0.4
// @description Redirect TikTok links to LiveContainer so they open in the containerized TikTok app.
// @author You
// @match https://www.tiktok.com/*
// @match https://m.tiktok.com/*
// @match https://vm.tiktok.com/*
// @match https://vt.tiktok.com/*
// @match *://*/*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// ---- prefs ----
const VERBOSE = true; // set false to quiet logs
const AUTO_REDIRECT_ON_TIKTOK_PAGES = true; // when you're *on* a TikTok page
const REWRITE_LINKS_ON_ALL_PAGES = true; // rewrite <a> that point to TikTok anywhere
const ADD_LC_FLAG = true; // append lc=1 to avoid bounce loops
const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
const tiktokHosts = new Set([
'tiktok.com', 'www.tiktok.com', 'm.tiktok.com',
'vm.tiktok.com', 'vt.tiktok.com'
]);
function log(...args){ if (VERBOSE) console.log('[LC-TikTok]', ...args); }
function isTikTokURL(u) {
try {
const url = (u instanceof URL) ? u : new URL(u, location.href);
return [...tiktokHosts].some(h => url.hostname === h || url.hostname.endsWith('.' + h));
} catch { return false; }
}
// Encode for LiveContainer's open-web-page scheme (expects Base64)
function toBase64(str) { return btoa(unescape(encodeURIComponent(str))); }
function buildLcUrl(originalUrl) {
const url = new URL(originalUrl, location.href);
if (ADD_LC_FLAG && !url.searchParams.has('lc')) url.searchParams.set('lc', '1');
return `livecontainer://open-web-page?url=${toBase64(url.toString())}`;
}
function redirectToLC(u) {
const lc = buildLcUrl(u);
log('Redirecting to LiveContainer:', lc);
// replace() avoids adding extra history entries
location.replace(lc);
}
// 1) If we’re on a TikTok page already, bounce to LiveContainer (iOS only)
if (isIOS && AUTO_REDIRECT_ON_TIKTOK_PAGES && isTikTokURL(location.href)) {
// small guard to prevent rapid loops if you come back with lc=1
if (!new URL(location.href).searchParams.get('lc')) {
redirectToLC(location.href);
return; // stop executing further on this page
} else {
log('lc=1 present; skipping auto-redirect to avoid loop.');
}
}
// 2) Rewrite TikTok anchors anywhere on the web
if (isIOS && REWRITE_LINKS_ON_ALL_PAGES) {
const processAnchor = (a) => {
if (!a || !a.href) return;
if (!isTikTokURL(a.href)) return;
// Don’t mutate to custom-scheme href to keep long-press previews working; hook click instead.
a.addEventListener('click', (e) => {
try {
e.preventDefault();
e.stopPropagation();
redirectToLC(a.href);
} catch (err) {
log('Error redirecting:', err);
}
}, { capture: true, passive: false });
// As a fallback (if scripts disable listeners), set data attribute so you know it’s hooked.
a.dataset.lcTikTok = '1';
log('Hooked TikTok link:', a.href);
};
// Initial pass
document.querySelectorAll('a[href]').forEach(processAnchor);
// Observe dynamically-added links
const mo = new MutationObserver((muts) => {
for (const m of muts) {
for (const node of m.addedNodes) {
if (node.nodeType !== 1) continue;
if (node.tagName === 'A' && node.href) processAnchor(node);
else node.querySelectorAll?.('a[href]').forEach(processAnchor);
}
}
});
mo.observe(document.documentElement, { childList: true, subtree: true });
}
})();