@opentabs-dev/opentabs-plugin-netflix 0.0.75 → 0.0.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -136
- package/dist/adapter.iife.js +25 -13
- package/dist/adapter.iife.js.map +3 -3
- package/dist/tools.json +1 -1
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -1,159 +1,69 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Netflix
|
|
2
2
|
|
|
3
|
-
OpenTabs plugin for Netflix
|
|
3
|
+
OpenTabs plugin for Netflix — gives AI agents access to Netflix through your authenticated browser session.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
|
+
```bash
|
|
8
|
+
opentabs plugin install netflix
|
|
7
9
|
```
|
|
8
|
-
netflix/
|
|
9
|
-
├── package.json # Plugin metadata (name, opentabs field, dependencies)
|
|
10
|
-
├── icon.svg # Optional custom icon (square SVG, max 8KB)
|
|
11
|
-
├── icon-inactive.svg # Optional manual inactive icon override
|
|
12
|
-
├── src/
|
|
13
|
-
│ ├── index.ts # Plugin class (extends OpenTabsPlugin)
|
|
14
|
-
│ └── tools/ # One file per tool (using defineTool)
|
|
15
|
-
│ └── example.ts
|
|
16
|
-
└── dist/ # Build output (generated)
|
|
17
|
-
├── adapter.iife.js # Bundled adapter injected into matching tabs
|
|
18
|
-
└── tools.json # Tool schemas for MCP registration
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Configuration
|
|
22
|
-
|
|
23
|
-
Plugin metadata is defined in `package.json` under the `opentabs` field:
|
|
24
|
-
|
|
25
|
-
```json
|
|
26
|
-
{
|
|
27
|
-
"name": "opentabs-plugin-netflix",
|
|
28
|
-
"main": "dist/adapter.iife.js",
|
|
29
|
-
"opentabs": {
|
|
30
|
-
"displayName": "Netflix",
|
|
31
|
-
"description": "OpenTabs plugin for Netflix",
|
|
32
|
-
"urlPatterns": ["*://*.netflix.com/*"]
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
- **`main`** — entry point for the bundled adapter IIFE
|
|
38
|
-
- **`opentabs.displayName`** — human-readable name shown in the side panel
|
|
39
|
-
- **`opentabs.description`** — short description of what the plugin does
|
|
40
|
-
- **`opentabs.urlPatterns`** — Chrome match patterns for tabs where the adapter is injected
|
|
41
|
-
|
|
42
|
-
## Custom Icons
|
|
43
|
-
|
|
44
|
-
By default, the side panel shows a colored letter avatar for your plugin. To use a custom icon, place an `icon.svg` file in the plugin root (next to `package.json`):
|
|
45
|
-
|
|
46
|
-
```
|
|
47
|
-
netflix/
|
|
48
|
-
├── package.json
|
|
49
|
-
├── icon.svg ← custom icon (optional)
|
|
50
|
-
├── icon-inactive.svg ← manual inactive override (optional, requires icon.svg)
|
|
51
|
-
├── src/
|
|
52
|
-
│ └── ...
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
**How it works:**
|
|
56
|
-
|
|
57
|
-
- `opentabs-plugin build` reads `icon.svg`, validates it, auto-generates a grayscale inactive variant, and embeds both in `dist/tools.json`
|
|
58
|
-
- To override the auto-generated inactive icon, provide `icon-inactive.svg` (must use only grayscale colors)
|
|
59
|
-
- If no `icon.svg` is provided, the letter avatar is used automatically
|
|
60
10
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
- Square SVG with a `viewBox` attribute (e.g., `viewBox="0 0 32 32"`)
|
|
64
|
-
- Maximum 8 KB file size
|
|
65
|
-
- No embedded `<image>`, `<script>`, or event handler attributes (`onclick`, etc.)
|
|
66
|
-
- Manual `icon-inactive.svg` must use only achromatic (grayscale) colors
|
|
67
|
-
|
|
68
|
-
## Development
|
|
11
|
+
Or install globally via npm:
|
|
69
12
|
|
|
70
13
|
```bash
|
|
71
|
-
npm install
|
|
72
|
-
npm run build # tsc && opentabs-plugin build
|
|
73
|
-
npm run dev # watch mode (tsc --watch + opentabs-plugin build --watch)
|
|
74
|
-
npm run type-check # tsc --noEmit
|
|
75
|
-
npm run lint # biome
|
|
14
|
+
npm install -g @opentabs-dev/opentabs-plugin-netflix
|
|
76
15
|
```
|
|
77
16
|
|
|
78
|
-
##
|
|
79
|
-
|
|
80
|
-
Create a new file in `src/tools/` using `defineTool`:
|
|
81
|
-
|
|
82
|
-
```ts
|
|
83
|
-
import { z } from 'zod';
|
|
84
|
-
import { defineTool } from '@opentabs-dev/plugin-sdk';
|
|
85
|
-
|
|
86
|
-
export const myTool = defineTool({
|
|
87
|
-
name: 'my_tool',
|
|
88
|
-
displayName: 'My Tool',
|
|
89
|
-
description: 'What this tool does',
|
|
90
|
-
icon: 'wrench',
|
|
91
|
-
input: z.object({ /* ... */ }),
|
|
92
|
-
output: z.object({ /* ... */ }),
|
|
93
|
-
handle: async (params) => {
|
|
94
|
-
// Tool implementation runs in the browser tab context
|
|
95
|
-
return { /* ... */ };
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
```
|
|
17
|
+
## Setup
|
|
99
18
|
|
|
100
|
-
|
|
19
|
+
1. Open [netflix.com](https://www.netflix.com/browse) in Chrome and log in
|
|
20
|
+
2. Open the OpenTabs side panel — the Netflix plugin should appear as **ready**
|
|
101
21
|
|
|
102
|
-
##
|
|
22
|
+
## Tools (19)
|
|
103
23
|
|
|
104
|
-
|
|
24
|
+
### Account (3)
|
|
105
25
|
|
|
106
|
-
|
|
107
|
-
|
|
26
|
+
| Tool | Description | Type |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `get_current_user` | Get current Netflix user info | Read |
|
|
29
|
+
| `list_profiles` | List all Netflix profiles | Read |
|
|
30
|
+
| `get_notifications` | Get Netflix notifications | Read |
|
|
108
31
|
|
|
109
|
-
|
|
110
|
-
const token = getLocalStorage('token');
|
|
32
|
+
### Browse (9)
|
|
111
33
|
|
|
112
|
-
|
|
113
|
-
|
|
34
|
+
| Tool | Description | Type |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `search_titles` | Search Netflix movies and shows | Read |
|
|
37
|
+
| `get_title` | Get details for a movie or show | Read |
|
|
38
|
+
| `get_title_details` | Get full details including cast and crew | Read |
|
|
39
|
+
| `get_seasons` | Get seasons and episodes for a show | Read |
|
|
40
|
+
| `list_trending` | Get trending titles on Netflix | Read |
|
|
41
|
+
| `list_top_10` | Get Netflix Top 10 titles | Read |
|
|
42
|
+
| `list_genre_titles` | Browse titles in a genre category | Read |
|
|
43
|
+
| `navigate_to_title` | Open a title page in the browser | Write |
|
|
44
|
+
| `navigate_to_genre` | Open a genre page in the browser | Write |
|
|
114
45
|
|
|
115
|
-
|
|
116
|
-
const appState = getPageGlobal('__APP_STATE__');
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
**Iframe fallback:** Some apps (e.g., Discord) delete `window.localStorage` after boot. `getLocalStorage` automatically tries a hidden same-origin iframe fallback before returning `null`, so you don't need to handle this case manually.
|
|
46
|
+
### Library (6)
|
|
120
47
|
|
|
121
|
-
|
|
48
|
+
| Tool | Description | Type |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `list_my_list` | Get titles in My List | Read |
|
|
51
|
+
| `add_to_my_list` | Save a title to My List | Write |
|
|
52
|
+
| `remove_from_my_list` | Remove a title from My List | Write |
|
|
53
|
+
| `list_continue_watching` | Get in-progress titles | Read |
|
|
54
|
+
| `get_watch_history` | Get recent viewing history | Read |
|
|
55
|
+
| `rate_title` | Rate a movie or show | Write |
|
|
122
56
|
|
|
123
|
-
|
|
57
|
+
### Playback (1)
|
|
124
58
|
|
|
125
|
-
|
|
59
|
+
| Tool | Description | Type |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `play_title` | Start playing a title | Write |
|
|
126
62
|
|
|
127
|
-
|
|
128
|
-
// src/schemas/channel.ts
|
|
129
|
-
import { z } from 'zod';
|
|
63
|
+
## How It Works
|
|
130
64
|
|
|
131
|
-
|
|
132
|
-
id: z.string().describe('Channel ID'),
|
|
133
|
-
name: z.string().describe('Channel name'),
|
|
134
|
-
});
|
|
65
|
+
This plugin runs inside your Netflix tab through the [OpenTabs](https://opentabs.dev) Chrome extension. It uses your existing browser session — no API tokens or OAuth apps required. All operations happen as you, with your permissions.
|
|
135
66
|
|
|
136
|
-
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
Then import and reuse in your tools:
|
|
140
|
-
|
|
141
|
-
```ts
|
|
142
|
-
// src/tools/list-channels.ts
|
|
143
|
-
import { channelSchema } from '../schemas/channel.js';
|
|
144
|
-
|
|
145
|
-
export const listChannels = defineTool({
|
|
146
|
-
name: 'list_channels',
|
|
147
|
-
displayName: 'List Channels',
|
|
148
|
-
description: 'List all available channels',
|
|
149
|
-
icon: 'list',
|
|
150
|
-
input: z.object({}),
|
|
151
|
-
output: z.object({ channels: z.array(channelSchema) }),
|
|
152
|
-
handle: async () => {
|
|
153
|
-
// ...
|
|
154
|
-
return { channels: [] };
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
|
-
```
|
|
67
|
+
## License
|
|
158
68
|
|
|
159
|
-
|
|
69
|
+
MIT
|
package/dist/adapter.iife.js
CHANGED
|
@@ -289,6 +289,8 @@
|
|
|
289
289
|
* (e.g., 'https://github.com'), not a match pattern.
|
|
290
290
|
*/
|
|
291
291
|
homepage;
|
|
292
|
+
/** Typed configuration schema — declares settings that users provide via config.json or the side panel. */
|
|
293
|
+
configSchema;
|
|
292
294
|
};
|
|
293
295
|
|
|
294
296
|
// src/netflix-api.ts
|
|
@@ -15108,21 +15110,21 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
15108
15110
|
};
|
|
15109
15111
|
var src_default = new NetflixPlugin();
|
|
15110
15112
|
|
|
15111
|
-
// dist/
|
|
15113
|
+
// dist/_adapter_entry_64fba8ff-d551-44dd-9554-c894e7597677.ts
|
|
15112
15114
|
if (!globalThis.__openTabs) {
|
|
15113
15115
|
globalThis.__openTabs = {};
|
|
15114
15116
|
} else {
|
|
15115
15117
|
const desc = Object.getOwnPropertyDescriptor(globalThis.__openTabs, "adapters");
|
|
15116
15118
|
if (desc && !desc.writable) {
|
|
15117
|
-
const
|
|
15119
|
+
const ot3 = globalThis.__openTabs;
|
|
15118
15120
|
const newAdaptersObj = {};
|
|
15119
|
-
if (
|
|
15120
|
-
for (const key of Object.keys(
|
|
15121
|
-
const d = Object.getOwnPropertyDescriptor(
|
|
15121
|
+
if (ot3.adapters) {
|
|
15122
|
+
for (const key of Object.keys(ot3.adapters)) {
|
|
15123
|
+
const d = Object.getOwnPropertyDescriptor(ot3.adapters, key);
|
|
15122
15124
|
if (d) Object.defineProperty(newAdaptersObj, key, d);
|
|
15123
15125
|
}
|
|
15124
15126
|
}
|
|
15125
|
-
globalThis.__openTabs = Object.assign({},
|
|
15127
|
+
globalThis.__openTabs = Object.assign({}, ot3, { adapters: newAdaptersObj });
|
|
15126
15128
|
}
|
|
15127
15129
|
}
|
|
15128
15130
|
if (!globalThis.__openTabs.adapters) {
|
|
@@ -15160,6 +15162,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
15160
15162
|
}
|
|
15161
15163
|
};
|
|
15162
15164
|
var restoreTransport = setLogTransport ? setLogTransport(logTransport) : void 0;
|
|
15165
|
+
var ot2 = globalThis.__openTabs;
|
|
15166
|
+
ot2._notifyReadinessChanged = () => {
|
|
15167
|
+
try {
|
|
15168
|
+
const nonce = globalThis.__openTabs?._readinessNonce;
|
|
15169
|
+
if (nonce) {
|
|
15170
|
+
window.postMessage({ type: "opentabs:readiness-changed", plugin: "netflix", nonce }, "*");
|
|
15171
|
+
}
|
|
15172
|
+
} catch {
|
|
15173
|
+
}
|
|
15174
|
+
};
|
|
15163
15175
|
var existing = adapters["netflix"];
|
|
15164
15176
|
if (existing) {
|
|
15165
15177
|
if (typeof existing.teardown === "function") {
|
|
@@ -15171,7 +15183,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
15171
15183
|
}
|
|
15172
15184
|
}
|
|
15173
15185
|
if (!Reflect.deleteProperty(adapters, "netflix")) {
|
|
15174
|
-
const
|
|
15186
|
+
const ot3 = globalThis.__openTabs;
|
|
15175
15187
|
const newAdapters = {};
|
|
15176
15188
|
for (const key of Object.keys(adapters)) {
|
|
15177
15189
|
if (key !== "netflix") {
|
|
@@ -15179,7 +15191,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
15179
15191
|
if (desc) Object.defineProperty(newAdapters, key, desc);
|
|
15180
15192
|
}
|
|
15181
15193
|
}
|
|
15182
|
-
globalThis.__openTabs = Object.assign({},
|
|
15194
|
+
globalThis.__openTabs = Object.assign({}, ot3, { adapters: newAdapters });
|
|
15183
15195
|
}
|
|
15184
15196
|
var hasLifecycleHooks = typeof src_default.onToolInvocationStart === "function" || typeof src_default.onToolInvocationEnd === "function";
|
|
15185
15197
|
for (const tool of src_default.tools) {
|
|
@@ -15240,12 +15252,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
15240
15252
|
}
|
|
15241
15253
|
}
|
|
15242
15254
|
};
|
|
15243
|
-
const
|
|
15244
|
-
if (!
|
|
15255
|
+
const ot3 = globalThis.__openTabs;
|
|
15256
|
+
if (!ot3._navigationInterceptor) {
|
|
15245
15257
|
const origPushState = history.pushState.bind(history);
|
|
15246
15258
|
const origReplaceState = history.replaceState.bind(history);
|
|
15247
15259
|
const callbacks = /* @__PURE__ */ new Map();
|
|
15248
|
-
|
|
15260
|
+
ot3._navigationInterceptor = { callbacks, origPushState, origReplaceState };
|
|
15249
15261
|
history.pushState = function(...args) {
|
|
15250
15262
|
origPushState(...args);
|
|
15251
15263
|
for (const cb of callbacks.values()) {
|
|
@@ -15259,7 +15271,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
15259
15271
|
}
|
|
15260
15272
|
};
|
|
15261
15273
|
}
|
|
15262
|
-
const interceptor =
|
|
15274
|
+
const interceptor = ot3._navigationInterceptor;
|
|
15263
15275
|
interceptor.callbacks.set("netflix", checkUrl);
|
|
15264
15276
|
window.addEventListener("popstate", checkUrl);
|
|
15265
15277
|
window.addEventListener("hashchange", checkUrl);
|
|
@@ -15314,5 +15326,5 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
15314
15326
|
};
|
|
15315
15327
|
delete src_default.onDeactivate;
|
|
15316
15328
|
}
|
|
15317
|
-
})();(function(){var o=(globalThis).__openTabs;if(o&&o.adapters&&o.adapters["netflix"]){var a=o.adapters["netflix"];a.__adapterHash="
|
|
15329
|
+
})();(function(){var o=(globalThis).__openTabs;if(o&&o.adapters&&o.adapters["netflix"]){var a=o.adapters["netflix"];a.__adapterHash="0870fb6bb01dd1c60e93efb7f16f7f22d6c45cae325e170fa1c2393bcacbfd36";if(a.tools&&Array.isArray(a.tools)){for(var i=0;i<a.tools.length;i++){Object.freeze(a.tools[i]);}Object.freeze(a.tools);}Object.freeze(a);Object.defineProperty(o.adapters,"netflix",{value:a,writable:false,configurable:false,enumerable:true});Object.defineProperty(o,"adapters",{value:o.adapters,writable:false,configurable:false});}})();
|
|
15318
15330
|
//# sourceMappingURL=adapter.iife.js.map
|