├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── components ├── Code.tsx ├── CopyClipboard.tsx ├── DownloadBackup.tsx ├── Info.tsx ├── Navbar.tsx └── UploadBackup.tsx ├── next.config.ts ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── fonts │ ├── GeistMonoVF.woff │ └── GeistVF.woff └── index.tsx ├── postcss.config.mjs ├── public ├── favicon.ico ├── file.svg ├── globe.svg ├── next.svg ├── vercel.svg └── window.svg ├── styles └── globals.css ├── tailwind.config.ts ├── tsconfig.json └── utils └── catalog.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # env files (can opt-in for commiting if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | .account 42 | 43 | .prettierrc 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2024] [Hidden Cinemeta] 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 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/pages/api-reference/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 20 | 21 | [API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 22 | 23 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) instead of React pages. 24 | 25 | This project uses [`next/font`](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 26 | 27 | ## Learn More 28 | 29 | To learn more about Next.js, take a look at the following resources: 30 | 31 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 32 | - [Learn Next.js](https://nextjs.org/learn-pages-router) - an interactive Next.js tutorial. 33 | 34 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 35 | 36 | ## Deploy on Vercel 37 | 38 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 39 | 40 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/pages/building-your-application/deploying) for more details. 41 | -------------------------------------------------------------------------------- /components/Code.tsx: -------------------------------------------------------------------------------- 1 | interface CodeProps { 2 | children: React.ReactNode; 3 | } 4 | 5 | const Code: React.FC = ({ children }) => { 6 | return ( 7 | 8 | {children?.toString()} 9 | 10 | ); 11 | } 12 | 13 | export default Code; 14 | 15 | 16 | -------------------------------------------------------------------------------- /components/CopyClipboard.tsx: -------------------------------------------------------------------------------- 1 | import CopyToClipboard from 'react-copy-to-clipboard' 2 | import { toast } from 'react-hot-toast' 3 | import Code from './Code' 4 | 5 | interface CopyClipboardProps { 6 | children: React.ReactNode 7 | } 8 | 9 | const CopyClipboard: React.FC = ({ children }) => { 10 | if (!children) return null; 11 | return ( 12 |
13 |
14 | {children} 15 | { 16 | toast.success("Copied to Clipboard!") 17 | }} text={children?.toString()}> 18 | 19 | 20 |
21 |
22 | ) 23 | } 24 | 25 | export default CopyClipboard 26 | -------------------------------------------------------------------------------- /components/DownloadBackup.tsx: -------------------------------------------------------------------------------- 1 | import toast from "react-hot-toast"; 2 | 3 | interface DownloadJSONProps { 4 | data: object; 5 | fileName: string; 6 | } 7 | 8 | const DownloadBackup: React.FC = ({ data, fileName }) => { 9 | const downloadJSON = () => { 10 | const jsonData = new Blob([JSON.stringify(data)], { type: 'application/json' }); 11 | const jsonURL = URL.createObjectURL(jsonData); 12 | const link = document.createElement('a'); 13 | link.href = jsonURL; 14 | link.download = `${fileName}.json`; 15 | document.body.appendChild(link); 16 | link.click(); 17 | document.body.removeChild(link); 18 | toast.success("Downloading backup file!") 19 | }; 20 | 21 | return ( 22 | 23 | ); 24 | }; 25 | 26 | export default DownloadBackup; 27 | -------------------------------------------------------------------------------- /components/Info.tsx: -------------------------------------------------------------------------------- 1 | import Code from "./Code" 2 | 3 | const Info = () => { 4 | return ( 5 |
6 |

Frequently Asked Questions:

7 |
8 | 9 |
Who is this for?
10 |
11 |

For users looking to remove Cinemeta catalogs from the Board and Discover pages in Stremio while keeping them available in search results, this workaround provides an effective solution.

12 |
13 |
14 |
15 | 16 |
How does it work?
17 |
18 |

Largely uses the same approach as {" "} 19 | 20 | Stremio Addon Manager 21 | 22 |

23 |

This uses the Stremio API behind the scenes. Specifically the addonCollectionSet and addonCollectionGet endpoints. 24 |

25 |

26 | They are documented in detail here: {" "} 27 | 28 | Stremio API Documentation 29 | 30 |

31 |
32 |
33 |
34 | 35 |
Is it safe?
36 |
37 |

This is more a work-around than an official solution so your mileage may vary. In my own testing it appears to work without issue.

38 |

39 | If you have any issues try to leave a comment in the Reddit announcement post in the /r/StremioAddons page {" "} 40 | 41 | Reddit Post 42 | 43 |

44 |
45 |
46 | {/* Is it open-source */} 47 |
48 | 49 |
Is this utility open-source?
50 |
51 |

52 | Yes, under MIT License. Check-out the source code here: {" "} 53 | 54 | Github Repository 55 | 56 |

57 |
58 |
59 | {/* What are these "modes"? */} 60 |
61 | 62 |
What are these modes?
63 |
64 |

65 | {`Near the top of the page there is a button where you can pick which mode this page is in.`} 66 |

67 |

68 | {`"Hide" mode will let you make and download a backup of your current addons as well as hide Cinemeta Catalogs from your board and discover pages in Stremio`} 69 |

70 |

71 | {`"Restore" mode will let you restore your stremio addons based on the backup file that was created`} 72 |

73 |
74 |
75 | {/* Why just hide? */} 76 |
77 | 78 |
Why hide Cinemeta Catalogs instead of removing Cinemeta
79 |
80 |

81 | {`From what I can gather, Cinemeta is a core add-on for Stremio's functionality. Removing it breaks things.`} 82 |

83 |

84 | {`By only hiding we can avoid many potential issues`} 85 |

86 |
87 |
88 | 89 | {/* How do Backups work? */} 90 |
91 | 92 |
How do backups work?
93 |
94 |

95 | {`In "hide" mode, during step 2 you will have the opportunity to save your current add-ons to a backup file that you can tuck away in a safe place`} 96 |

97 |

98 | {`If you ever want to restore your addons to that backup, simply select "Restore" in the mode option near the top of the page and follow the steps`} 99 |

100 |
101 |
102 | 103 |
104 | ) 105 | } 106 | 107 | export default Info 108 | -------------------------------------------------------------------------------- /components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import React, { useState } from 'react' 3 | import toast from 'react-hot-toast'; 4 | 5 | interface NavbarProps { 6 | pageMode: "hide" | "restore" 7 | setPageMode: React.Dispatch>; 8 | addonsLength: number 9 | } 10 | 11 | const modes = [{ 12 | id: "hide", 13 | name: "Hide Cinemeta Catalogs", 14 | prefix: "Hide", 15 | description: "Select Cinemeta catalogs to hide" 16 | }, 17 | { 18 | id: "restore", 19 | name: "Restore Stremio Addons", 20 | prefix: "Restore", 21 | description: "Restore stremio addons using a backup file" 22 | }] 23 | 24 | const icons = { 25 | hide: 26 | 27 | , 28 | restore: 29 | 30 | 31 | 32 | 33 | } 34 | 35 | const Navbar: React.FC = ({ setPageMode, pageMode, addonsLength }) => { 36 | 37 | const [openDropdown, setOpenDropdown] = useState(false) 38 | const currentMode = modes.filter(mode => mode.id === pageMode); 39 | 40 | return ( 41 |
42 |
43 | hidden cinemeta 44 |
45 |
46 | {addonsLength === 0 && ( 47 | 48 |
49 | Mode:{" "} 50 |
{ 51 | setOpenDropdown(true) 52 | }}>{currentMode[0].prefix} 53 | 54 | 55 | 56 |
57 | 84 |
85 | )} 86 |
87 |
88 | ) 89 | } 90 | 91 | export default Navbar 92 | -------------------------------------------------------------------------------- /components/UploadBackup.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import toast from 'react-hot-toast'; 3 | 4 | interface UploadBackupProps { 5 | setBackupData: React.Dispatch>, 6 | } 7 | 8 | 9 | const UploadBackup: React.FC = ({ setBackupData }) => { 10 | 11 | const handleFileUpload = (event: React.ChangeEvent) => { 12 | const file = event.target.files?.[0]; 13 | if (!file) { 14 | toast.error("No file selected!"); 15 | return; 16 | } 17 | 18 | const reader = new FileReader(); 19 | reader.onload = (e) => { 20 | try { 21 | const result = e.target?.result as string; 22 | const parsedData = JSON.parse(result); 23 | setBackupData(parsedData); 24 | toast.success("Backup file uploaded successfully!"); 25 | } catch (error) { 26 | toast.error("Failed to parse JSON file."); 27 | console.error("Error parsing JSON:", error); 28 | } 29 | }; 30 | reader.readAsText(file); 31 | }; 32 | 33 | return ( 34 |
35 | 36 | {/* {backupData &&
{JSON.stringify(backupData, null, 2)}
} */} 37 |
38 | ); 39 | }; 40 | 41 | export default UploadBackup; 42 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | reactStrictMode: true, 6 | }; 7 | 8 | export default nextConfig; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "invisible-cinemeta", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": "15.0.1", 13 | "react": "19.0.0-rc-69d4b800-20241021", 14 | "react-confetti": "^6.1.0", 15 | "react-copy-to-clipboard": "^5.1.0", 16 | "react-dom": "19.0.0-rc-69d4b800-20241021", 17 | "react-hook-form": "^7.53.1", 18 | "react-hot-toast": "^2.4.1", 19 | "react-use": "^17.5.1" 20 | }, 21 | "devDependencies": { 22 | "@types/node": "^20", 23 | "@types/react": "^18", 24 | "@types/react-copy-to-clipboard": "^5.0.7", 25 | "@types/react-dom": "^18", 26 | "daisyui": "^4.12.13", 27 | "eslint": "^8", 28 | "eslint-config-next": "15.0.1", 29 | "postcss": "^8", 30 | "tailwindcss": "^3.4.1", 31 | "typescript": "^5" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "@/styles/globals.css"; 2 | import { Toaster } from "react-hot-toast"; 3 | import type { AppProps } from "next/app"; 4 | 5 | export default function App({ Component, pageProps }: AppProps) { 6 | return ( 7 | <> 8 | 9 | ; 10 | 11 | ) 12 | 13 | } 14 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from "next/document"; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /pages/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skarian/hidden-cinemeta/0d143c785effaded8600128234c4b592641382de/pages/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /pages/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skarian/hidden-cinemeta/0d143c785effaded8600128234c4b592641382de/pages/fonts/GeistVF.woff -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { useCallback, useEffect, useState } from 'react'; 3 | import DownloadBackup from '@/components/DownloadBackup' 4 | import Info from '@/components/Info'; 5 | import { toast } from 'react-hot-toast' 6 | import CopyClipboard from '@/components/CopyClipboard'; 7 | import Navbar from '@/components/Navbar'; 8 | import { useForm } from 'react-hook-form'; 9 | import { SimpleCatalog, updateAddons } from '@/utils/catalog'; 10 | import UploadBackup from '@/components/UploadBackup'; 11 | import useWindowSize from 'react-use/lib/useWindowSize' 12 | import Confetti from 'react-confetti' 13 | 14 | const stremioAPIBase = "https://api.strem.io/api/"; 15 | 16 | type PageSetup = "hide" | "restore"; 17 | 18 | export default function Home() { 19 | const { width, height } = useWindowSize() 20 | const [stremioAuthKey, setStremioAuthKey] = useState(''); 21 | const [addons, setAddons] = useState([]); 22 | const [updatedAddons, setUpdatedAddons] = useState([]); 23 | const [loading, setLoading] = useState(false); 24 | const [catalogs, setCatalogs] = useState([]); 25 | const [pageMode, setPageMode] = useState("hide"); 26 | const [syncStatus, setSyncStatus] = useState(false) 27 | 28 | function getCatalogKey(catalog: SimpleCatalog): string { 29 | return `${catalog.id}_${catalog.type}_${catalog.name}`; 30 | } 31 | 32 | const [hiddenItems, setHiddenItems] = useState([]); 33 | const { handleSubmit } = useForm(); 34 | 35 | // Handle hide/show field toggling 36 | const handleCheckboxChange = (catalog: SimpleCatalog) => { 37 | // We are setting the hiddenItems value 38 | setHiddenItems((prevHiddenItems) => 39 | // We are checking if the value is already hidden 40 | prevHiddenItems.some( 41 | (item) => 42 | item.id === catalog.id && 43 | item.type === catalog.type && 44 | item.name === catalog.name // Check all 3 values 45 | ) 46 | // If already hidden, then filter out the entry and update hidden items state 47 | ? prevHiddenItems.filter( 48 | (item) => 49 | !(item.id === catalog.id && item.type === catalog.type && item.name === catalog.name) // Unhide by matching all 3 values 50 | ) 51 | // If not hidden already, then add it to state 52 | : [...prevHiddenItems, catalog] // Hide the catalog 53 | ); 54 | }; 55 | 56 | // Handle form submission 57 | const onHideFormSubmit = () => { 58 | console.log('Hidden Items', hiddenItems) 59 | const processedAddons = updateAddons(addons, hiddenItems) 60 | setUpdatedAddons(processedAddons) 61 | console.log("Updated add-ons", processedAddons) 62 | // TODO: At some point reset hiddenItems and Catalogs 63 | }; 64 | 65 | const extractValidCatalogs = useCallback((addons: any[]) => { 66 | // TODO: Better validation 67 | const cinemetaAddon = addons.find(addon => addon.manifest.id === "com.linvo.cinemeta"); 68 | 69 | if (!cinemetaAddon) { 70 | toast.error("Cinemeta Addon not found in list!"); 71 | return; // Exit early if the addon is not found 72 | } 73 | 74 | const validCatalogs: SimpleCatalog[] = cinemetaAddon.manifest.catalogs 75 | .filter((catalog: any) => catalog.id !== "last-videos" && catalog.id !== "calendar-videos" && catalog.id !== "year") 76 | .map((catalog: any) => ({ 77 | name: catalog.name, 78 | id: catalog.id, 79 | type: catalog.type 80 | })); 81 | 82 | setCatalogs(validCatalogs); 83 | }, []); 84 | 85 | // Anytime we add entries to addons list, ensure we have also extracted valid catalogs 86 | useEffect(() => { 87 | if (addons.length > 0) { 88 | extractValidCatalogs(addons); 89 | } 90 | }, [addons, extractValidCatalogs]); 91 | 92 | // For Debugging 93 | useEffect(() => { 94 | console.log("Update to catalogs", catalogs) 95 | }, [catalogs]) 96 | 97 | useEffect(() => { 98 | console.log("Update to hidden items", hiddenItems) 99 | }, [hiddenItems]) 100 | 101 | useEffect(() => { 102 | console.log("Update to updated addons", updatedAddons) 103 | }, [updatedAddons]) 104 | 105 | 106 | const loadUserAddons = async () => { 107 | if (!stremioAuthKey) { 108 | console.error('No auth key provided'); 109 | toast.error('No auth key provided :('); 110 | return; 111 | } 112 | 113 | setLoading(true); 114 | 115 | toast.promise( 116 | fetch(`${stremioAPIBase}addonCollectionGet`, { 117 | method: 'POST', 118 | body: JSON.stringify({ 119 | type: 'AddonCollectionGet', 120 | authKey: stremioAuthKey, 121 | update: true, 122 | }), 123 | }) 124 | .then(async (res) => { 125 | const data = await res.json(); 126 | if (data.result && data.result.addons) { 127 | setAddons(data.result.addons); 128 | console.log('Add-ons loaded:', data.result.addons); 129 | } else { 130 | return Promise.reject('Failed to load add-ons') 131 | } 132 | }), 133 | { 134 | loading: 'Loading add-ons...', 135 | success: Add-ons loaded successfully!, 136 | error: Failed to load add-ons., 137 | } 138 | ) 139 | .catch((error) => { 140 | console.error('Error fetching add-ons:', error); 141 | }) 142 | .finally(() => { 143 | setLoading(false); 144 | }); 145 | }; 146 | 147 | const syncUserAddons = async () => { 148 | if (!stremioAuthKey) { 149 | console.error('No auth key provided'); 150 | toast.error('No auth key provided :('); 151 | return; 152 | } 153 | 154 | setLoading(true); 155 | 156 | toast.promise( 157 | fetch(`${stremioAPIBase}addonCollectionSet`, { 158 | method: 'POST', 159 | body: JSON.stringify({ 160 | type: 'AddonCollectionSet', 161 | authKey: stremioAuthKey, 162 | addons: updatedAddons, // Using updatedAddons 163 | }), 164 | }) 165 | .then(async (res) => { 166 | const data = await res.json(); 167 | if (data.result && data.result.success) { 168 | console.log('Add-ons synced successfully'); 169 | setSyncStatus(true); // Added this line 170 | } else { 171 | return Promise.reject('Failed to sync add-ons'); 172 | } 173 | }), 174 | { 175 | loading: 'Syncing add-ons...', 176 | success: Add-ons synced successfully!, 177 | error: Failed to sync add-ons., 178 | } 179 | ) 180 | .catch((error) => { 181 | console.error('Error syncing add-ons:', error); 182 | }) 183 | .finally(() => { 184 | setLoading(false); 185 | }); 186 | }; 187 | 188 | if (syncStatus) { 189 | console.log('Sync status is true, rendering confetti...'); 190 | } 191 | 192 | const currentTitle = pageMode === "hide" ? "A Utility to Hide Cinemeta Catalogs in Stremio" : "A Utility to Restore Stremio Add-ons via Backup File" 193 | 194 | const currentDescription = pageMode === "hide" ? `A wise man once said, "Cinemeta catalogs are ugly"... and he was absolutely right.` : `A wise man once said "Backups are a good idea"... and he was RIGHT` 195 | 196 | return ( 197 |
198 | 199 |
200 |
201 | {/* Title Section */} 202 |
203 |

{currentTitle}

204 |
205 |

{currentDescription}

206 |
207 |
208 |
209 | {/* Hide and Restore - Step 1. AuthKeyStep */} 210 | {addons.length === 0 && ( 211 |
212 |

Step 1: Get Stremio Authentication Key

213 |
    214 |
  1. 215 | Login to https://web.stremio.com/ using your Stremio credentials in your browser (Chrome Preferred). 216 |
  2. 217 |
  3. 218 | {"Open the developer console (F12 or right click page and click 'Inspect'), go to the 'Console' tab."} 219 |
  4. 220 |
  5. 221 | Paste the follow code snippet into the console and press enter: 222 | {"JSON.parse(localStorage.getItem(\"profile\")).auth.key"} 223 |
  6. 224 |
  7. 225 | It should print out a code. Copy the value (leave out the quotation marks) and paste into the box below and press enter 226 |
  8. 227 |
228 |
229 | setStremioAuthKey(e.target.value)} 235 | /> 236 | 239 |
240 |
241 | )} 242 | {/* Hide - Step 2. Configuration Step */} 243 | {(addons.length !== 0 && catalogs.length !== 0 && updatedAddons.length === 0) && (pageMode === "hide") && ( 244 |
245 |

Step 2: Select which catalogs to no longer show

246 |
    247 |
  1. [OPTIONAL]: This is a good time to make a back-up, click the button below to download your current add-ons configuration to your computer 248 |
  2. 249 |
  3. 250 | Note: You can restore your add-ons from the backup file by switching modes at the top of the page 251 |
  4. 252 |
    253 | 254 |
    255 |
  5. 256 | Choose from the catalogs listed below (have excluded non-visual catalogs used for Stremio core functionality) 257 |
  6. 258 |
259 | {/* Form to Pick Catalogs to Hide */} 260 |
261 |
262 |
263 | 264 | 265 | {/* head */} 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | {catalogs.map((catalog, key) => ( 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | ))} 286 | 287 |
IDTypeNameHide?
{key + 1}{catalog.id}{catalog.type}{catalog.name} handleCheckboxChange(catalog)} checked={hiddenItems.includes(catalog)} />
288 | 289 |
290 | 291 |
292 |
293 |
294 | )} 295 | {/* Restore: Step 2: Upload file */} 296 | {(addons.length !== 0 && catalogs.length !== 0 && updatedAddons.length === 0) && (pageMode === "restore") && ( 297 |
298 |

Step 2: Select the backup file to restore your account to

299 |
    300 |
  1. 301 | If you already made a backup on this page and wanted to restore your stremio account to that backup, simply select the backup file in the input below and press apply changes 302 |
  2. 303 |
304 | {/* Form to Pick Catalogs to Hide */} 305 |
306 | 307 |
308 |
309 | )} 310 | {/* After Update */} 311 | {updatedAddons.length !== 0 && ( 312 |
313 |

{`Step 3: Sync Changes to Stremio's Servers`}

314 | {!syncStatus ? ( 315 | <> 316 |
    317 |
  1. 318 | Press the button below to apply your selected changes 319 |
  2. 320 |
  3. 321 | As a reminder, recommend to save a backup in the previous step. You can reload this page to restart the process. 322 |
  4. 323 |
324 | {/* Button to Sync Changes to Stremio */} 325 |
326 | 329 |
330 | 331 | ) : ( 332 | <> 333 | {syncStatus && ( 334 | 339 | )} 340 |
Congrats! Changes are made successfully! Reload your Stremio application to see them
341 | 342 | )} 343 |
344 | )} 345 |
346 | 347 |
348 |
349 |
350 | ); 351 | } 352 | 353 | 354 | 355 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skarian/hidden-cinemeta/0d143c785effaded8600128234c4b592641382de/public/favicon.ico -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .test { 6 | @apply border border-2 border-red-800; 7 | } 8 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | import daisyui from 'daisyui' 3 | 4 | const config: Config = { 5 | content: [ 6 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 9 | ], 10 | theme: { 11 | extend: { 12 | colors: { 13 | background: "var(--background)", 14 | foreground: "var(--foreground)", 15 | }, 16 | }, 17 | }, 18 | daisyui: { 19 | themes: ["dark"] 20 | }, 21 | plugins: [daisyui], 22 | }; 23 | export default config; 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "paths": { 17 | "@/*": ["./*"] 18 | } 19 | }, 20 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /utils/catalog.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface SimpleCatalog { 3 | name: string, 4 | id: string, 5 | type: string 6 | } 7 | 8 | interface ExtraEntry { 9 | name: string; 10 | isRequired?: boolean; 11 | [key: string]: any; 12 | } 13 | 14 | interface Catalog { 15 | name: string; 16 | id: string; 17 | type: string; 18 | extra?: ExtraEntry[]; 19 | extraSupported?: string[]; 20 | [key: string]: any; 21 | } 22 | 23 | interface Manifest { 24 | id: string; 25 | catalogs?: Catalog[]; 26 | [key: string]: any; 27 | } 28 | 29 | interface Addon { 30 | manifest: Manifest; 31 | [key: string]: any; 32 | } 33 | 34 | export function updateAddons(addons: any[], hiddenItems: Catalog[]): any[] { 35 | return addons.map((addon: Addon) => { 36 | if (addon.manifest.id === "com.linvo.cinemeta") { 37 | const updatedCatalogs = addon.manifest.catalogs?.map((catalog: Catalog) => { 38 | const isHidden = hiddenItems.some( 39 | (hiddenItem) => 40 | hiddenItem.name === catalog.name && 41 | hiddenItem.id === catalog.id && 42 | hiddenItem.type === catalog.type 43 | ); 44 | 45 | let updatedExtra = catalog.extra ? [...catalog.extra] : []; 46 | let updatedExtraSupported = catalog.extraSupported ? [...catalog.extraSupported] : []; 47 | 48 | const extraSearchIndex = updatedExtra.findIndex((entry) => entry.name === "search"); 49 | 50 | if (isHidden) { 51 | // Catalog is in hiddenItems 52 | if (extraSearchIndex >= 0) { 53 | // 'search' entry exists 54 | if (updatedExtra[extraSearchIndex].isRequired !== true) { 55 | updatedExtra[extraSearchIndex] = { 56 | ...updatedExtra[extraSearchIndex], 57 | isRequired: true, 58 | }; 59 | } 60 | } else { 61 | // 'search' entry does not exist, add it 62 | updatedExtra.push({ name: "search", isRequired: true }); 63 | } 64 | 65 | // Ensure 'search' is in catalog.extraSupported 66 | if (!updatedExtraSupported.includes("search")) { 67 | updatedExtraSupported.push("search"); 68 | } 69 | } else { 70 | // Catalog is not in hiddenItems 71 | if (extraSearchIndex >= 0) { 72 | // 'search' entry exists 73 | if (updatedExtra[extraSearchIndex].hasOwnProperty('isRequired')) { 74 | const { isRequired, ...rest } = updatedExtra[extraSearchIndex]; 75 | updatedExtra[extraSearchIndex] = rest; 76 | } 77 | } 78 | // Optionally remove 'search' from extraSupported if not needed 79 | // This can be handled if required 80 | } 81 | 82 | return { 83 | ...catalog, 84 | extra: updatedExtra, 85 | extraSupported: updatedExtraSupported, 86 | }; 87 | }) ?? addon.manifest.catalogs; 88 | 89 | return { 90 | ...addon, 91 | manifest: { 92 | ...addon.manifest, 93 | catalogs: updatedCatalogs, 94 | }, 95 | }; 96 | } 97 | // Return other add-ons untouched 98 | return addon; 99 | }); 100 | } 101 | --------------------------------------------------------------------------------