made chages to relevant files to add the requested features

This commit is contained in:
Swayam Raut 2025-11-02 15:55:27 +05:30
parent 536be36005
commit b3b28d4abb
4 changed files with 239 additions and 0 deletions

32
add-meta-links.patch Normal file
View file

@ -0,0 +1,32 @@
diff --git a/src/routes/MetaDetails/MetaDetails.js b/src/routes/MetaDetails/MetaDetails.js
index 1234567..89abcde 100644
--- a/src/routes/MetaDetails/MetaDetails.js
+++ b/src/routes/MetaDetails/MetaDetails.js
@@ -11,6 +11,7 @@ const useMetaDetails = require('./useMetaDetails');
const withCoreSuspender = require('stremio/common/withCoreSuspender');
const MetaVideosList = require('./VideosList/VideosList');
const MetaStreamsList = require('./StreamsList/StreamsList');
+const MetaLinks = require('../../components/MetaLinks').default;
const MetaDetails = ({ urlParams, queryParams }) => {
const metaDetails = useMetaDetails(urlParams);
@@ -100,6 +101,19 @@ const MetaDetails = ({ urlParams, queryParams }) => {
</section>
) : null}
+ {/* Additional links section */}
+ {metaDetails && metaDetails.links && metaDetails.links.length > 0 && (
+ <section
+ className="meta-links-section"
+ aria-label="Additional links"
+ style={{ marginTop: 20 }}
+ >
+ <h3 style={{ marginBottom: 8 }}>Additional links</h3>
+ <MetaLinks links={metaDetails.links} meta={metaDetails} />
+ </section>
+ )}
+
+
{metaDetails && metaDetails.trailers && metaDetails.trailers.length ? (
<section className="trailers">
<h3>Trailers</h3>

View file

@ -0,0 +1,114 @@
// src/components/MetaLinks.jsx
import React from 'react';
import PropTypes from 'prop-types';
import { handleMetaLink, copyToClipboard } from '../utils/linkHandler';
/**
* MetaLinks component
* Props:
* - links: array of meta.link objects
* - meta: the meta object (passed to playInApp as context)
*/
export default function MetaLinks({ links = [], meta = {} }) {
if (!links || !links.length) return null;
const onPrimaryClick = async (link) => {
await handleMetaLink(link, meta);
};
const onCopyClick = async (e, link) => {
e.stopPropagation();
try {
await copyToClipboard(link.url);
showTemporaryToast('Link copied');
} catch (err) {
showTemporaryToast('Copy failed');
}
};
const showTemporaryToast = (text) => {
const toast = document.createElement('div');
toast.innerText = text;
toast.style.position = 'fixed';
toast.style.bottom = '20px';
toast.style.right = '20px';
toast.style.padding = '8px 12px';
toast.style.background = 'rgba(0,0,0,0.8)';
toast.style.color = 'white';
toast.style.borderRadius = '6px';
toast.style.zIndex = 9999;
document.body.appendChild(toast);
setTimeout(() => {
try { document.body.removeChild(toast); } catch (e) {}
}, 1400);
};
const renderLabel = (link) => {
if (link.name) return link.name;
if (link.type) return link.type;
try {
return new URL(link.url).hostname;
} catch (e) {
return link.url;
}
};
return (
<div className="meta-links" style={{ display: 'flex', gap: 10, flexWrap: 'wrap', marginTop: 14 }}>
{links.map((link, idx) => (
<div
key={`${link.url}-${idx}`}
className="meta-link"
role="button"
tabIndex={0}
onClick={() => onPrimaryClick(link)}
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') onPrimaryClick(link); }}
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
padding: '8px 12px',
borderRadius: 8,
border: '1px solid rgba(0,0,0,0.08)',
cursor: 'pointer',
minWidth: 140,
background: 'var(--card-bg, #fff)',
boxShadow: 'var(--card-shadow, 0 1px 2px rgba(0,0,0,0.04))',
}}
aria-label={`Meta link ${renderLabel(link)}`}
>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 14, fontWeight: 600 }}>{renderLabel(link)}</div>
{link.info ? (
<div style={{ fontSize: 12, color: 'rgba(0,0,0,0.6)' }}>{link.info}</div>
) : null}
</div>
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<button
type="button"
aria-label={`Copy link ${renderLabel(link)}`}
onClick={(e) => onCopyClick(e, link)}
style={{ border: 'none', background: 'transparent', cursor: 'pointer', fontSize: 16 }}
>
📋
</button>
<button
type="button"
aria-label={`Open ${renderLabel(link)}`}
onClick={(e) => { e.stopPropagation(); onPrimaryClick(link); }}
style={{ border: 'none', background: 'transparent', cursor: 'pointer', fontSize: 16 }}
>
</button>
</div>
</div>
))}
</div>
);
}
MetaLinks.propTypes = {
links: PropTypes.arrayOf(PropTypes.object),
meta: PropTypes.object,
};

View file

@ -13,6 +13,8 @@ const useMetaDetails = require('./useMetaDetails');
const useSeason = require('./useSeason');
const useMetaExtensionTabs = require('./useMetaExtensionTabs');
const styles = require('./styles');
const MetaLinks = require('../../components/MetaLinks').default;
const MetaDetails = ({ urlParams, queryParams }) => {
const { t } = useTranslation();
@ -94,6 +96,8 @@ const MetaDetails = ({ urlParams, queryParams }) => {
metaDetails.metaItem.content.content.background.length > 0
), [metaPath, metaDetails]);
return (
<div className={styles['metadetails-container']}>
{
@ -171,6 +175,11 @@ const MetaDetails = ({ urlParams, queryParams }) => {
metaId={metaDetails.metaItem.content.content.id}
ratingInfo={metaDetails.ratingInfo}
/>
{/* Render additional meta links if present */}
{metaDetails.metaItem?.content?.content?.links?.length > 0 && (
<MetaLinks links={metaDetails.metaItem.content.content.links} />
)}
</React.Fragment>
}
<div className={styles['spacing']} />

84
src/utils/linkHandler.js Normal file
View file

@ -0,0 +1,84 @@
// src/utils/linkHandler.js
// Utilities for handling meta.link objects and link actions.
export function openExternal(url) {
try {
// open in new tab with security flags
window.open(url, '_blank', 'noopener,noreferrer');
} catch (e) {
// fallback: navigate current window
window.location.href = url;
}
}
export function copyToClipboard(text) {
if (!navigator.clipboard) {
const el = document.createElement('textarea');
el.value = text;
el.style.position = 'fixed';
el.style.left = '-9999px';
document.body.appendChild(el);
el.select();
try { document.execCommand('copy'); } catch (err) {}
document.body.removeChild(el);
return Promise.resolve();
}
return navigator.clipboard.writeText(text);
}
/**
* Try to play the url in-app via the host bridge.
* - If window.Stremio.play exists, call it.
* - Otherwise dispatch a custom event 'stremio-play-link' (repo may listen for it).
* - Fallback: open externally after a short delay.
*/
export async function playInApp(url, meta = {}) {
try {
if (window.Stremio && typeof window.Stremio.play === 'function') {
return window.Stremio.play(url, meta);
}
// dispatch event for hosting wrapper to pick up
const ev = new CustomEvent('stremio-play-link', { detail: { url, meta }});
window.dispatchEvent(ev);
// fallback to open externally after 350ms if nothing else happens
setTimeout(() => openExternal(url), 350);
} catch (err) {
openExternal(url);
}
}
/**
* Decide how to handle a meta.link object.
* link = { name, type, url, img, info }
*/
export async function handleMetaLink(link, meta = {}) {
if (!link || !link.url) return;
const url = String(link.url);
const type = String(link.type || '').toLowerCase();
// treat magnet and torrent files as playable
if (url.startsWith('magnet:') || url.endsWith('.torrent')) {
return playInApp(url, meta);
}
// If type clearly indicates playable stream
const playableTypes = ['stream', 'video', 'play', 'http', 'https', 'magnet', 'torrent', 'm3u8', 'mp4'];
if (playableTypes.includes(type)) {
return playInApp(url, meta);
}
// types that are clearly external info links
const externalTypes = ['imdb', 'website', 'external', 'trailer', 'info', 'purchase'];
if (externalTypes.includes(type)) {
return openExternal(url);
}
// stremio protocol deep link -> in-app first
if (url.startsWith('stremio://')) {
return playInApp(url, meta);
}
// default fallback: open externally
return openExternal(url);
}