mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 06:06:18 -08:00
feta: stalker portal support
This commit is contained in:
1 parent
c9f7735a3d
commit
811b32980b
2 files changed
+491
No files matched your search
@@ -0,0 +1,48 @@
|
||||
<app-navigation-bar
|
||||
[breadcrumbs]="breadcrumbs"
|
||||
[contentType]="selectedContentType"
|
||||
(searchTextChanged)="setSearchPhrase($event)"
|
||||
[clientSideSearch]="currentLayout === 'category_content' ? false : true"
|
||||
[searchVisible]="
|
||||
currentLayout === 'category' || currentLayout === 'category_content'
|
||||
"
|
||||
[contentTypeNavigationItems]="navigationContentTypes"
|
||||
[contentType]="selectedContentType"
|
||||
(contentTypeChanged)="getCategories($event)"
|
||||
(breadcrumbClicked)="breadcrumbClicked($event)"
|
||||
(favoritesClicked)="favoritesClicked()"
|
||||
/>
|
||||
|
||||
<app-category-view
|
||||
*ngIf="currentLayout === 'category'"
|
||||
(categoryClicked)="categoryClicked($event)"
|
||||
[items]="items"
|
||||
/>
|
||||
|
||||
<app-category-content-view
|
||||
*ngIf="currentLayout === 'category_content'"
|
||||
[items]="items"
|
||||
(itemClicked)="itemClicked($event)"
|
||||
/>
|
||||
|
||||
<app-vod-details
|
||||
*ngIf="currentLayout === 'vod-details'"
|
||||
[item]="itemDetails"
|
||||
(playClicked)="playVod()"
|
||||
(addToFavoritesClicked)="addToFavorites($event)"
|
||||
(removeFromFavoritesClicked)="removeFromFavorites($event)"
|
||||
/>
|
||||
|
||||
<app-category-content-view
|
||||
*ngIf="currentLayout === 'favorites'"
|
||||
[items]="favorites$ | async"
|
||||
(itemClicked)="itemClicked($event)"
|
||||
/>
|
||||
|
||||
<mat-paginator
|
||||
*ngIf="currentLayout === 'category_content'"
|
||||
[length]="length"
|
||||
[pageSize]="pageSize"
|
||||
(page)="handlePageChange($event)"
|
||||
aria-label="Select page"
|
||||
/>
|
||||
@@ -0,0 +1,443 @@
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { Component, NgZone, OnInit, Signal, effect } from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { StorageMap } from '@ngx-pwa/local-storage';
|
||||
import { Observable } from 'rxjs';
|
||||
import { IpcCommand } from '../../../shared/ipc-command.class';
|
||||
import {
|
||||
ERROR,
|
||||
OPEN_MPV_PLAYER,
|
||||
OPEN_VLC_PLAYER,
|
||||
STALKER_REQUEST,
|
||||
STALKER_RESPONSE,
|
||||
} from '../../../shared/ipc-commands';
|
||||
import { StalkerPortalActions } from '../../../shared/stalker-portal-actions.enum';
|
||||
import { DataService } from '../services/data.service';
|
||||
import { PlaylistsService } from '../services/playlists.service';
|
||||
import { Settings, VideoPlayer } from '../settings/settings.interface';
|
||||
import { STORE_KEY } from '../shared/enums/store-keys.enum';
|
||||
import { selectCurrentPlaylist } from '../state/selectors';
|
||||
import { Breadcrumb } from '../xtream/breadcrumb.interface';
|
||||
import { CategoryContentViewComponent } from '../xtream/category-content-view/category-content-view.component';
|
||||
import { CategoryViewComponent } from '../xtream/category-view/category-view.component';
|
||||
import { ContentTypeNavigationItem } from '../xtream/content-type-navigation-item.interface';
|
||||
import { ContentType } from '../xtream/content-type.enum';
|
||||
import { NavigationBarComponent } from '../xtream/navigation-bar/navigation-bar.component';
|
||||
import { PlayerDialogComponent } from '../xtream/player-dialog/player-dialog.component';
|
||||
import { PortalStore } from '../xtream/portal.store';
|
||||
import { VodDetailsComponent } from '../xtream/vod-details/vod-details.component';
|
||||
import { StalkerContentTypes } from './stalker-content-types';
|
||||
|
||||
@Component({
|
||||
selector: 'app-stalker-main-container',
|
||||
templateUrl: './stalker-main-container.component.html',
|
||||
/* styles: [
|
||||
`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
}
|
||||
`,
|
||||
], */
|
||||
standalone: true,
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
CategoryViewComponent,
|
||||
CategoryContentViewComponent,
|
||||
NavigationBarComponent,
|
||||
NgIf,
|
||||
MatPaginatorModule,
|
||||
VodDetailsComponent,
|
||||
],
|
||||
})
|
||||
export class StalkerMainContainerComponent implements OnInit {
|
||||
breadcrumbs: Breadcrumb[] = [];
|
||||
currentPlaylist = this.store.selectSignal(selectCurrentPlaylist);
|
||||
listeners = [];
|
||||
isLoading = true;
|
||||
selectedContentType: ContentType = ContentType.VODS;
|
||||
currentLayout = 'category';
|
||||
searchPhrase = this.portalStore.searchPhrase();
|
||||
settings = toSignal(
|
||||
this.storage.get(STORE_KEY.Settings)
|
||||
) as Signal<Settings>;
|
||||
favorites$: Observable<any>;
|
||||
|
||||
items: any[] = [];
|
||||
itemDetails!: {
|
||||
id: string;
|
||||
cmd: string;
|
||||
info: {
|
||||
movie_image: string;
|
||||
description: string;
|
||||
name: string;
|
||||
actors: string;
|
||||
director: string;
|
||||
releasedate: string;
|
||||
genre: string;
|
||||
};
|
||||
};
|
||||
|
||||
navigationContentTypes: ContentTypeNavigationItem[] = [
|
||||
{
|
||||
contentType: ContentType.ITV,
|
||||
label: 'Live Streams',
|
||||
},
|
||||
{
|
||||
contentType: ContentType.VODS,
|
||||
label: 'VOD Streams',
|
||||
},
|
||||
{
|
||||
contentType: ContentType.FAVORITES,
|
||||
label: 'Favorites',
|
||||
},
|
||||
];
|
||||
|
||||
currentCategoryId;
|
||||
|
||||
//pagination
|
||||
length = 0;
|
||||
pageSize = 14;
|
||||
pageIndex = 0;
|
||||
|
||||
commandsList = [
|
||||
new IpcCommand(STALKER_RESPONSE, (response: any) =>
|
||||
this.handleResponse(response)
|
||||
),
|
||||
new IpcCommand(ERROR, (response: { message: string; status: number }) =>
|
||||
this.showErrorAsNotification(response)
|
||||
),
|
||||
];
|
||||
|
||||
constructor(
|
||||
private dataService: DataService,
|
||||
private dialog: MatDialog,
|
||||
private ngZone: NgZone,
|
||||
private playlistService: PlaylistsService,
|
||||
private portalStore: PortalStore,
|
||||
private snackBar: MatSnackBar,
|
||||
private storage: StorageMap,
|
||||
private store: Store
|
||||
) {
|
||||
effect(() => {
|
||||
if (this.currentPlaylist()) {
|
||||
if (
|
||||
this.currentPlaylist().password &&
|
||||
this.currentPlaylist().username
|
||||
) {
|
||||
this.handshake();
|
||||
} else {
|
||||
this.selectedContentType = ContentType.VODS;
|
||||
this.getCategories(this.selectedContentType);
|
||||
}
|
||||
|
||||
this.favorites$ = this.playlistService.getPortalFavorites(
|
||||
this.currentPlaylist()._id
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this.portalStore.setSearchPhrase('');
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.commandsList.forEach((command) => {
|
||||
if (this.dataService.isElectron) {
|
||||
this.dataService.listenOn(command.id, (_event, response) =>
|
||||
this.ngZone.run(() => command.callback(response))
|
||||
);
|
||||
} else {
|
||||
const cb = (response) => {
|
||||
if (response.data.type === command.id) {
|
||||
command.callback(response.data);
|
||||
}
|
||||
};
|
||||
this.dataService.listenOn(command.id, cb);
|
||||
this.listeners.push(cb);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handshake() {
|
||||
this.sendRequest({ action: 'handshake', type: 'stb' });
|
||||
}
|
||||
|
||||
setInitialBreadcrumb(
|
||||
action = StalkerPortalActions.GetCategories,
|
||||
title = 'All categories'
|
||||
) {
|
||||
this.breadcrumbs = [{ title, action }];
|
||||
}
|
||||
|
||||
getCategories(contentType: ContentType = this.selectedContentType) {
|
||||
this.selectedContentType = contentType;
|
||||
this.currentLayout = 'category';
|
||||
const action = StalkerContentTypes[contentType].getCategoryAction;
|
||||
this.setInitialBreadcrumb(action);
|
||||
this.sendRequest({ action, type: contentType });
|
||||
}
|
||||
|
||||
sendRequest(params: Record<string, string | number>) {
|
||||
this.isLoading = true;
|
||||
if (params.action !== 'create_link') this.items = [];
|
||||
const { portalUrl, macAddress } = this.currentPlaylist();
|
||||
let token = {};
|
||||
if (sessionStorage.getItem(this.currentPlaylist()._id)) {
|
||||
token = sessionStorage.getItem(this.currentPlaylist()._id);
|
||||
}
|
||||
this.dataService.sendIpcEvent(STALKER_REQUEST, {
|
||||
url: portalUrl,
|
||||
macAddress,
|
||||
params: {
|
||||
...params,
|
||||
token,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
showErrorAsNotification(response: { message: string; status: number }) {
|
||||
this.snackBar.open(
|
||||
`Error: ${response?.message ?? 'Something went wrong'} (Status: ${
|
||||
response?.status ?? 0
|
||||
})`,
|
||||
null,
|
||||
{ duration: 4000 }
|
||||
);
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
handleResponse(response: {
|
||||
action: string;
|
||||
payload: { js: any; cmd?: string };
|
||||
}) {
|
||||
if (
|
||||
response.action === StalkerPortalActions.GetCategories ||
|
||||
response.action === StalkerPortalActions.GetGenres
|
||||
) {
|
||||
this.items = response.payload.js.map((item) => ({
|
||||
category_name: item.title,
|
||||
category_id: item.id,
|
||||
}));
|
||||
} else if (response.action === StalkerPortalActions.GetOrderedList) {
|
||||
if (response.payload.js.data && response.payload.js.data.length > 0)
|
||||
this.portalStore.setContent(response.payload.js.data);
|
||||
|
||||
this.items = response.payload.js.data.map((item) => ({
|
||||
...item,
|
||||
cover: item.screenshot_uri,
|
||||
}));
|
||||
this.length = response.payload.js.total_items;
|
||||
this.pageIndex = response.payload.js.cur_page;
|
||||
} else if (response.action === StalkerPortalActions.CreateLink) {
|
||||
let url = response.payload.js.cmd as string;
|
||||
if (url?.startsWith('ffmpeg')) {
|
||||
url = url.split(' ')[1];
|
||||
}
|
||||
|
||||
this.openPlayer(url, response.payload.js.name);
|
||||
} else if (response.action === 'handshake') {
|
||||
const token = response.payload.js.token;
|
||||
sessionStorage.setItem(this.currentPlaylist()._id, token);
|
||||
|
||||
this.sendRequest({
|
||||
action: 'do_auth',
|
||||
login: this.currentPlaylist().username,
|
||||
password: this.currentPlaylist().password,
|
||||
type: 'stb',
|
||||
token,
|
||||
});
|
||||
} else if (response.action === 'do_auth') {
|
||||
this.getCategories();
|
||||
}
|
||||
}
|
||||
|
||||
openPlayer(streamUrl: string, title: string) {
|
||||
const player = this.settings().player;
|
||||
if (player === VideoPlayer.MPV) {
|
||||
this.dataService.sendIpcEvent(OPEN_MPV_PLAYER, {
|
||||
url: streamUrl,
|
||||
});
|
||||
} else if (player === VideoPlayer.VLC) {
|
||||
this.dataService.sendIpcEvent(OPEN_VLC_PLAYER, {
|
||||
url: streamUrl,
|
||||
});
|
||||
} else {
|
||||
this.dialog.open(PlayerDialogComponent, {
|
||||
data: { streamUrl, player, title },
|
||||
width: '80%',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
categoryClicked(item: { category_name: string; category_id: string }) {
|
||||
this.currentLayout = 'category_content';
|
||||
this.currentCategoryId = item.category_id;
|
||||
const action =
|
||||
StalkerContentTypes[this.selectedContentType].getContentAction;
|
||||
this.portalStore.setSearchPhrase('');
|
||||
|
||||
this.breadcrumbs.push({
|
||||
title: item.category_name,
|
||||
category_id: item.category_id,
|
||||
action,
|
||||
});
|
||||
|
||||
this.sendRequest({
|
||||
action,
|
||||
type: this.selectedContentType,
|
||||
category: item.category_id,
|
||||
genre: item.category_id,
|
||||
});
|
||||
}
|
||||
|
||||
itemClicked(i: any) {
|
||||
if (this.selectedContentType === ContentType.ITV) {
|
||||
this.playVod(i.cmd);
|
||||
return;
|
||||
}
|
||||
const selectedContent = this.portalStore.getContentById(i.id)();
|
||||
this.itemDetails = !selectedContent
|
||||
? i.details // to read from favorites
|
||||
: {
|
||||
id: i.id,
|
||||
cmd: selectedContent.cmd,
|
||||
info: {
|
||||
movie_image: selectedContent.screenshot_uri,
|
||||
description: selectedContent.description,
|
||||
name: selectedContent.name,
|
||||
director: selectedContent.director,
|
||||
releasedate: selectedContent.year,
|
||||
genre: selectedContent.genres_str,
|
||||
actors: selectedContent.actors,
|
||||
},
|
||||
};
|
||||
this.breadcrumbs.push({
|
||||
title: this.itemDetails?.info?.name,
|
||||
action: StalkerPortalActions.GetOrderedList,
|
||||
});
|
||||
this.currentLayout = 'vod-details';
|
||||
}
|
||||
|
||||
playVod(cmd?: string) {
|
||||
const command = cmd ?? this.itemDetails.cmd;
|
||||
const action = StalkerContentTypes[this.selectedContentType].getLink;
|
||||
this.sendRequest({
|
||||
action,
|
||||
type: this.selectedContentType,
|
||||
cmd: command,
|
||||
forced_storage: 'undefined',
|
||||
disable_ad: '0',
|
||||
JsHttpRequest: '1-xml',
|
||||
});
|
||||
}
|
||||
|
||||
breadcrumbClicked(breadcrumb: Breadcrumb) {
|
||||
const itemIndex = this.breadcrumbs.findIndex((i) => i === breadcrumb);
|
||||
|
||||
// do nothing if last breadcrumb child was clicked
|
||||
if (itemIndex === this.breadcrumbs.length - 1) return;
|
||||
|
||||
this.breadcrumbs.splice(
|
||||
itemIndex + 1,
|
||||
this.breadcrumbs.length - itemIndex - 1
|
||||
);
|
||||
|
||||
if (breadcrumb.action === StalkerPortalActions.GetOrderedList) {
|
||||
this.currentLayout = 'category_content';
|
||||
} else if (
|
||||
breadcrumb.action === StalkerPortalActions.GetCategories ||
|
||||
breadcrumb.action === StalkerPortalActions.GetGenres
|
||||
) {
|
||||
this.currentLayout = 'category';
|
||||
} else if (breadcrumb.action === StalkerPortalActions.Favorites) {
|
||||
this.currentLayout = 'favorites';
|
||||
}
|
||||
|
||||
this.currentCategoryId = breadcrumb.category_id;
|
||||
this.sendRequest({
|
||||
action: breadcrumb.action,
|
||||
type: this.selectedContentType,
|
||||
...(breadcrumb.category_id
|
||||
? {
|
||||
category: breadcrumb.category_id,
|
||||
genre: breadcrumb.category_id,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
|
||||
addToFavorites(item: any) {
|
||||
this.playlistService
|
||||
.addPortalFavorite(this.currentPlaylist()._id, item)
|
||||
.subscribe(() => {
|
||||
this.snackBar.open('Added to favorites', null, {
|
||||
duration: 1000,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
removeFromFavorites(favoriteId: string) {
|
||||
this.playlistService
|
||||
.removeFromPortalFavorites(this.currentPlaylist()._id, favoriteId)
|
||||
.subscribe(() => {
|
||||
this.snackBar.open('Removed from favorites', null, {
|
||||
duration: 1000,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setSearchPhrase(searchPhrase: string) {
|
||||
if (
|
||||
this.currentLayout === 'category_content' &&
|
||||
this.searchPhrase !== searchPhrase
|
||||
)
|
||||
this.sendRequest({
|
||||
action: StalkerContentTypes[this.selectedContentType]
|
||||
.getContentAction,
|
||||
type: this.selectedContentType,
|
||||
search: searchPhrase,
|
||||
...(this.currentCategoryId
|
||||
? {
|
||||
category: this.currentCategoryId,
|
||||
genre: this.currentCategoryId,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
|
||||
handlePageChange(event: PageEvent) {
|
||||
this.sendRequest({
|
||||
action: StalkerContentTypes[this.selectedContentType]
|
||||
.getContentAction,
|
||||
type: this.selectedContentType,
|
||||
category: this.currentCategoryId,
|
||||
p: event.pageIndex + 1,
|
||||
});
|
||||
}
|
||||
|
||||
favoritesClicked() {
|
||||
this.currentLayout = 'favorites';
|
||||
this.setInitialBreadcrumb(
|
||||
StalkerPortalActions.Favorites,
|
||||
'My favorites'
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.dataService.isElectron) {
|
||||
this.commandsList.forEach((command) =>
|
||||
this.dataService.removeAllListeners(command.id)
|
||||
);
|
||||
} else {
|
||||
this.listeners.forEach((listener) => {
|
||||
window.removeEventListener('message', listener);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user