implement all lrt client functions

This commit is contained in:
Jurgis Sakalauskas
2022-12-08 12:39:41 +02:00
parent 9d00d549b6
commit d00601b52c
15 changed files with 307 additions and 121 deletions
@@ -0,0 +1,41 @@
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';
import { SearchResponseInterface } from '../Interface/search-response.interface';
import { SearchResponseDto } from '../Dto/search-response.dto';
import { SearchResponseItemDto } from '../Dto/search-response-item.dto';
@Injectable()
export class LrtApiCategoryClient {
constructor(private readonly httpService: HttpService) {}
async getCategory(catId: string): Promise<SearchResponseDto> {
const url = 'https://www.lrt.lt/api/search?type=3&category_id=' + catId;
const resp = await firstValueFrom(this.httpService.get(url));
const searchResponse: SearchResponseInterface = resp.data;
//todo - move to mapper
const responseDto = new SearchResponseDto(
parseInt(searchResponse.page),
parseInt(searchResponse.total_found),
//todo - consider calculating total pages?
);
for (const searchItem of searchResponse.items) {
const itemDto = new SearchResponseItemDto();
itemDto.label = searchItem.title;
itemDto.thumb =
'https://www.lrt.lt' +
searchItem.img_path_prefix +
'282x158' +
searchItem.img_path_postfix;
itemDto.categoryId = catId;
itemDto.date = searchItem.item_date;
itemDto.url = searchItem.url;
responseDto.addItem(itemDto);
}
return responseDto;
}
}
@@ -0,0 +1,18 @@
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';
import { MediaInfoResponseInterface } from '../Interface/media-info-response.interface';
@Injectable()
export class LrtApiPlaylistClient {
constructor(private readonly httpService: HttpService) {}
async getPlaylistUrl(mediaUrl: string): Promise<string> {
const url =
'https://www.lrt.lt/servisai/stream_url/vod/media_info/?url=/' + mediaUrl;
const resp = await firstValueFrom(this.httpService.get(url));
const mediaInfoResponse: MediaInfoResponseInterface = resp.data;
return mediaInfoResponse.playlist_item.file;
}
}
@@ -0,0 +1,66 @@
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';
import { SearchResponseInterface } from '../Interface/search-response.interface';
import { SearchResponseDto } from '../Dto/search-response.dto';
import { SearchResponseItemDto } from '../Dto/search-response-item.dto';
@Injectable()
export class LrtApiSearchClient {
constructor(private readonly httpService: HttpService) {}
async searchCategories(query: string): Promise<SearchResponseDto> {
query = query === 'viskas' ? '' : query;
const url = 'https://www.lrt.lt/api/search?type=3&tema=' + query;
//https://www.lrt.lt/api/search?page=1&count=44&order=desc
//https://www.lrt.lt/api/search?get_terms=1
const resp = await firstValueFrom(this.httpService.get(url));
const searchResponse: SearchResponseInterface = resp.data;
const cats = [];
//todo - move to mapper
const responseDto = new SearchResponseDto(
parseInt(searchResponse.page),
parseInt(searchResponse.total_found),
);
for (const searchItem of searchResponse.items) {
if (cats.indexOf(searchItem.category_title) > -1) {
continue;
}
const itemDto = new SearchResponseItemDto();
itemDto.label = searchItem.category_title;
itemDto.thumb =
'https://www.lrt.lt' +
searchItem.img_path_prefix +
'282x158' +
searchItem.img_path_postfix;
itemDto.categoryId = await this.findCategoryId(searchItem.category_url);
cats.push(searchItem.category_title);
responseDto.addItem(itemDto);
}
return responseDto;
}
private async findCategoryId(categoryUrl: string): Promise<string> {
//todo - cache to database
const url = 'https://www.lrt.lt' + categoryUrl;
const resp = await firstValueFrom(this.httpService.get(url));
const body: string = resp.data;
const start = body.indexOf('data.category_id');
if (start > -1) {
const interim = body.indexOf('"', start);
const end = body.indexOf('"', interim + 1);
if (end > interim) {
return body.substring(interim + 1, end);
} else {
throw 'unexpected response body 1';
}
} else {
throw 'unexpected response body 2';
}
}
}
@@ -0,0 +1,7 @@
export class SearchResponseItemDto {
label: string;
thumb: string;
categoryId: string;
date: string;
url: string;
}
@@ -0,0 +1,15 @@
import { SearchResponseItemDto } from './search-response-item.dto';
export class SearchResponseDto {
readonly items: Array<SearchResponseItemDto> = [];
constructor(readonly page: number, readonly total: number) {
this.page = page;
this.total = total;
}
addItem(item: SearchResponseItemDto) {
this.items.push(item);
return this;
}
}
@@ -0,0 +1,24 @@
export class MediaInfoResponseInterface {
id: number;
title: string;
type: number;
content: string;
category_id: number;
heritage: any;
date: string;
offset: number;
tags: Array<{
slug: string;
first: 1;
last: number;
name: string;
}>;
url: string;
full_url: string;
playlist_item: {
file: string;
title: string;
image: string;
mediaid: number;
};
}
@@ -1,4 +1,4 @@
interface SearchResponseItemInterface {
export interface SearchResponseItemInterface {
photo_horizontal: number;
category_img_path_postfix: string;
age_restriction: string;
@@ -29,4 +29,5 @@ interface SearchResponseItemInterface {
n18: number;
subtitle: string;
photo_count: number;
category_id: string;
}
@@ -1,10 +1,13 @@
interface SearchResponseInterface {
import { SearchResponseItemInterface } from './search-response-item.interface';
export interface SearchResponseInterface {
q: string;
meta: {
time: number;
total: number;
total_found: number;
time: string;
total: string;
total_found: string;
};
total_found: string | number;
page: string;
total_found: string;
items: Array<SearchResponseItemInterface>;
}
@@ -0,0 +1,22 @@
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
import { LrtApiSearchClient } from './Client/lrt-api-search.client';
import { LrtApiClient } from './lrt-api.client';
import { LrtApiCategoryClient } from './Client/lrt-api-category.client';
import { LrtApiPlaylistClient } from './Client/lrt-api-playlist.client';
@Module({
imports: [
HttpModule.register({
withCredentials: true,
}),
],
providers: [
LrtApiSearchClient,
LrtApiCategoryClient,
LrtApiPlaylistClient,
LrtApiClient,
],
exports: [LrtApiClient],
})
export class LrtApiClientModule {}
@@ -0,0 +1,26 @@
import { Injectable } from '@nestjs/common';
import { LrtApiSearchClient } from './Client/lrt-api-search.client';
import { SearchResponseDto } from './Dto/search-response.dto';
import { LrtApiCategoryClient } from './Client/lrt-api-category.client';
import { LrtApiPlaylistClient } from './Client/lrt-api-playlist.client';
@Injectable()
export class LrtApiClient {
constructor(
private readonly searchClient: LrtApiSearchClient,
private readonly categoryClient: LrtApiCategoryClient,
private readonly playlistClient: LrtApiPlaylistClient,
) {}
searchCategories(query: string): Promise<SearchResponseDto> {
return this.searchClient.searchCategories(query);
}
getCategory(catId: string): Promise<SearchResponseDto> {
return this.categoryClient.getCategory(catId);
}
getPlaylist(mediaUrl: string): Promise<string> {
return this.playlistClient.getPlaylistUrl(mediaUrl);
}
}
-62
View File
@@ -1,62 +0,0 @@
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';
@Injectable()
export class LrtApiClient {
constructor(private readonly httpService: HttpService) {}
async getSearch(topic: string): Promise<any> {
//todo return api response
topic = topic === 'viskas' ? topic : '';
const url = 'https://www.lrt.lt/api/search?type=3&tema=' + topic;
const resp = await firstValueFrom(this.httpService.get(url));
const data: SearchResponseInterface = resp.data;
const items: Array<any> = data.items;
const cats = [];
const returnItems = [];
if (items) {
for (const i in items) {
const item = items[i];
if (cats.indexOf(item.category_title) > -1) {
continue;
}
//todo refactor to api response item
const newItem = {
label: item.category_title,
isFolder: true,
thumb:
'https://www.lrt.lt' +
item.img_path_prefix +
'282x158' +
item.img_path_postfix,
categoryId: await this.findCategoryId(item.category_url),
};
cats.push(item.category_title);
returnItems.push(newItem);
}
}
return returnItems;
}
private async findCategoryId(categoryUrl: string): Promise<string> {
const url = 'https://www.lrt.lt' + categoryUrl;
const resp = await firstValueFrom(this.httpService.get(url));
const body: string = resp.data;
const start = body.indexOf('data.category_id');
if (start > -1) {
const interim = body.indexOf('"', start);
const end = body.indexOf('"', interim + 1);
if (end > interim) {
return body.substring(interim + 1, end);
} else {
throw 'unexpected response body 1';
}
} else {
throw 'unexpected response body 2';
}
}
}
+13 -41
View File
@@ -1,64 +1,36 @@
import { Controller, Get, Param, Query, Req } from '@nestjs/common';
import { Request } from 'express';
import { LrtService } from './lrt.service';
import { LrtApiClient } from './lrt-api.client';
import ApiResponse from '../Dto/api-response.dto';
@Controller('api/lrt')
export class LrtController {
constructor(
private readonly lrtService: LrtService,
private readonly lrtApiClient: LrtApiClient,
) {}
constructor(private readonly lrtService: LrtService) {}
@Get()
getLrtMenu() {
getLrtMenu(): ApiResponse {
return this.lrtService.getMainMenu();
}
@Get('search')
getSearch(@Req() request: Request, @Query('search') search: string) {
return {
mod: 'lrt',
path: request.url,
search: search ?? 'no',
};
}
@Get('recent')
async getRecent(@Req() request: Request) {
return {
mod: 'lrt',
path: request.url,
recent: true,
};
getSearch(@Query('search') search: string): Promise<ApiResponse> {
return this.lrtService.searchCategories(search);
}
@Get('cat/:id')
async getCategory(@Req() request: Request, @Param('id') id: number) {
return {
mod: 'lrt - category',
path: request.url,
category: id,
};
async getCategory(@Param('id') id: string): Promise<ApiResponse> {
return this.lrtService.getCategory(id);
}
@Get('tema/:topic')
async getTema(@Req() request: Request, @Param('topic') topic: string) {
const resp = await this.lrtApiClient.getSearch(topic);
return {
mod: 'lrt - category',
path: request.url,
topic: resp,
};
async getTema(@Param('topic') topic: string): Promise<ApiResponse> {
return this.lrtService.searchCategories(topic);
}
@Get('play/*')
getPlay(@Req() request: Request, @Param() params: string[]) {
const playPath = params[0];
return {
mod: 'lrt - play',
path: request.url,
playPath: playPath,
};
async getPlay(@Req() request: Request, @Param() params: string[]) {
const mediaUrl = params[0];
return this.lrtService.getPlayableItem(mediaUrl);
}
}
+3 -8
View File
@@ -2,16 +2,11 @@ import { Module } from '@nestjs/common';
import { LrtController } from './lrt.controller';
import { LrtService } from './lrt.service';
import { KodiApiResponseFactory } from '../kodi-api-response.factory';
import { HttpModule } from '@nestjs/axios';
import { LrtApiClient } from './lrt-api.client';
import { LrtApiClientModule } from './LrtApiClient/lrt-api-client.module';
@Module({
imports: [
HttpModule.register({
withCredentials: true,
}),
],
imports: [LrtApiClientModule],
controllers: [LrtController],
providers: [KodiApiResponseFactory, LrtService, LrtApiClient],
providers: [KodiApiResponseFactory, LrtService],
})
export class LrtModule {}
+48 -1
View File
@@ -1,12 +1,15 @@
import { Injectable } from '@nestjs/common';
import ApiResponse from '../Dto/api-response.dto';
import { KodiApiResponseFactory } from '../kodi-api-response.factory';
import { LrtApiClient } from './LrtApiClient/lrt-api.client';
@Injectable()
export class LrtService {
constructor(
private readonly kodiApiResponseFactory: KodiApiResponseFactory,
private readonly lrtApiClient: LrtApiClient,
) {}
getMainMenu(): ApiResponse {
const apiResponse = this.kodiApiResponseFactory.createApiResponse();
apiResponse.setNoSort().setTitle('LRT.lt');
@@ -17,7 +20,6 @@ export class LrtService {
private createMainMenu(apiResponse: ApiResponse): void {
const items = {
'lrt/recent': 'neseniai ieškoti',
'lrt/tema/vaikams': 'vaikams',
'lrt/tema/sportas': 'sportas',
'lrt/tema/kultura': 'kultura',
@@ -35,4 +37,49 @@ export class LrtService {
apiResponse.createItem().setLabel(label).setToFolder().setPath(path);
}
}
async searchCategories(query: string): Promise<ApiResponse> {
const searchResponseDto = await this.lrtApiClient.searchCategories(query);
const apiResponse = this.kodiApiResponseFactory.createApiResponse();
apiResponse.setTitle('LRT ' + query);
for (const itemDto of searchResponseDto.items) {
apiResponse
.createItem()
.setLabel(itemDto.label)
.setThumb(itemDto.thumb)
.setPath('lrt/cat/' + itemDto.categoryId)
.setToFolder();
}
return apiResponse;
}
async getCategory(catId: string): Promise<ApiResponse> {
const searchResponseDto = await this.lrtApiClient.getCategory(catId);
const apiResponse = this.kodiApiResponseFactory.createApiResponse();
apiResponse.setTitle('LRT.lt');
for (const itemDto of searchResponseDto.items) {
apiResponse
.createItem()
.setLabel(itemDto.label + ' ' + itemDto.date)
.setDate(itemDto.date)
.setThumb(itemDto.thumb)
.setPath('lrt/play' + itemDto.url)
.setToPlayable(true)
.setPlot(itemDto.date);
}
return apiResponse;
}
async getPlayableItem(mediaUrl: string): Promise<ApiResponse> {
const playableUrl = await this.lrtApiClient.getPlaylist(mediaUrl);
const apiResponse = this.kodiApiResponseFactory.createApiResponse();
apiResponse.setTitle('LRT.lt');
apiResponse.setToPlayable(playableUrl);
return apiResponse;
}
}