Skip to content

Commit

Permalink
Update spanish-tv-guide extension (#15437)
Browse files Browse the repository at this point in the history
- Bump depdendencies
- Consume new API
- Merge pull request #2 from doktor500/tv-guide-v2
- Downgrade eslint to 8.X
- Add eslint file
- Fix lint issues
- Renamet metadata directory to docs
- Fix changelog typo
- Update changelog date
- Update changelog
- Merge pull request #1 from doktor500/tv-guide-v2
- Formatting
- Refactor
- Imrpove markdown
- Always display up to date channel schedule
- Rename file
- Rename to DTO
- Bump dependencies
- Fix imports
- Fix time issue
- Display if a program is live
- Set selected program based on user interaction
- Refactor types
- Fix timezone issue
- Move get program details to repository
- Add maybe type
- Rename property
- Display program details
- Fix lint
- Optimise icon generation process
- Extract toId function
- Drop previous broadcasted programs when the list is too long
- Fix flickering issue
- Fix keys
- Restructure directories
- Refactor Channel details component
- Refactor Channel component
- Extract icon utils
- Refactor channels component
- Fix lint
- Use navigation api
- Selected channel view
- Create all channels view
- Use reducer
- Remove type
- Fix lint
- Update raycast username
- User friendly error handling
- Set live stream icon
- Bump dependencies
- Wait for icons to be loaded
- Fix metadata and readme
- Update CHANGELOG.md
- Fix lint
- Cleanup
- Update changelog
- Extract constant
- Bump dependencies
- Resize icons
- Optimize
- Pull contributions
- Remove live logic
- Update demo.gif
- Fix title
- Add screenshot
- Fix icon
- Bump dependencies
- Initial version
  • Loading branch information
doktor500 authored Nov 21, 2024
1 parent 617915c commit c2a8ae9
Show file tree
Hide file tree
Showing 18 changed files with 2,046 additions and 819 deletions.
3 changes: 3 additions & 0 deletions extensions/spanish-tv-guide/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# spanish-tv-guide Changelog

## [Consume new dedicated API hosted on vercel] - 2024-11-19
- Consume new API and adapt behaviour to the new API model

## [UX improvements and bug fixes] - 2024-06-02
- Display if a program is live
- Fix timezone issues
Expand Down
2,556 changes: 1,951 additions & 605 deletions extensions/spanish-tv-guide/package-lock.json

Large diffs are not rendered by default.

27 changes: 13 additions & 14 deletions extensions/spanish-tv-guide/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"Media"
],
"license": "MIT",
"type": "module",
"commands": [
{
"name": "index",
Expand All @@ -18,32 +19,30 @@
}
],
"dependencies": {
"@raycast/api": "^1.75.2",
"@raycast/api": "^1.85.2",
"friendly-truncate": "^1.3.0",
"isomorphic-fetch": "^3.0.0",
"jimp": "^0.22.12",
"lodash": "^4.17.21",
"luxon": "^3.4.4",
"monet": "^0.9.3",
"node-html-parser": "^6.1.13",
"ramda": "^0.30.0"
"luxon": "^3.5.0",
"ramda": "^0.30.1"
},
"devDependencies": {
"@raycast/eslint-config": "1.0.8",
"@babel/preset-env": "^7.26.0",
"@raycast/eslint-config": "1.0.11",
"@types/isomorphic-fetch": "^0.0.39",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.17.4",
"@types/jest": "^29.5.14",
"@types/lodash": "^4.17.13",
"@types/luxon": "^3.4.2",
"@types/node": "20.13.0",
"@types/ramda": "^0.30.0",
"@types/react": "18.3.3",
"@types/node": "22.9.0",
"@types/ramda": "^0.30.2",
"@types/react": "18.3.12",
"eslint": "^8.56.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"prettier": "^3.3.0",
"ts-jest": "^29.1.4",
"prettier": "^3.3.3",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
"typescript": "^5.6.3"
},
"scripts": {
"build": "ray build -e dist",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Color, Icon, List } from "@raycast/api";

import { ChannelScheduleDto, ProgramDto } from "../modules/tv/domain/tvScheduleDto";
import { getTime } from "../utils/dateUtils";
import { truncate } from "../utils/stringUtils";

const { Item } = List;

Expand All @@ -24,7 +25,7 @@ const Program = ({ program }: { program: ProgramDto }) => {
return (
<Fragment>
<Item.Detail.Metadata.Label
title={program.title}
title={truncate(program.name)}
icon={program.isCurrentlyLive ? Icon.Clock : ""}
text={{ value: getTime(program.startTime), color: Color.SecondaryText }}
/>
Expand Down
42 changes: 28 additions & 14 deletions extensions/spanish-tv-guide/src/components/SelectedChannel.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,76 @@
import React, { useState } from "react";
import { Dispatch, SetStateAction, useState } from "react";
import { Action, ActionPanel, Icon, List, useNavigation } from "@raycast/api";

import { ChannelScheduleDto, ProgramDto, upToDateChannelSchedule } from "../modules/tv/domain/tvScheduleDto";
import { getTime } from "../utils/dateUtils";
import { iconPath } from "../utils/iconUtils";
import { truncate } from "../utils/stringUtils";
import { SelectedProgram } from "./SelectedProgram";
import { tvScheduleRepository } from "../modules/tv/repositories/tvScheduleRepository";

type ProgramProps = {
channel: ChannelScheduleDto;
program: ProgramDto;
index: number;
onSelect: (index: number) => void;
setLoading: Dispatch<SetStateAction<boolean>>;
};

const SELECT_PROGRAM_ACTION = "Select Program";

export const SelectedChannel = ({ channel }: { channel: ChannelScheduleDto }) => {
const schedule = upToDateChannelSchedule(channel.schedule);
const currentLiveProgram = schedule.findIndex((program) => program.isCurrentlyLive);

const [selectedProgramIndex, setSelectedProgramIndex] = useState<number>(currentLiveProgram);
const [loading, setLoading] = useState(false);

return (
<List selectedItemId={selectedProgramIndex.toString()} navigationTitle={channel.name}>
<List selectedItemId={selectedProgramIndex.toString()} navigationTitle={channel.name} isLoading={loading}>
<List.Section key={`channel-${channel.name}`}>
<List.Item key={channel.name} title={`${channel.name}`} icon={iconPath(channel.icon)} />
</List.Section>
<List.Section key={`schedule-${channel.name}`}>
{schedule.map((program, index) => (
<Program key={index} channel={channel} program={program} index={index} onSelect={setSelectedProgramIndex} />
<Program
key={index}
channel={channel}
program={program}
index={index}
onSelect={setSelectedProgramIndex}
setLoading={setLoading}
/>
))}
</List.Section>
</List>
);
};

const Program = ({ channel, program, index, onSelect: setSelectedProgramIndex }: ProgramProps) => {
const Program = ({ channel, program, index, onSelect: setSelectedProgramIndex, setLoading }: ProgramProps) => {
const { push } = useNavigation();

const handleSelectedProgram = () => {
setLoading(true);

tvScheduleRepository
.getProgramDetails(program)
.then((details) => push(<SelectedProgram channel={channel} program={{ ...program, ...details }} />))
.then(() => setSelectedProgramIndex(index))
.finally(() => setLoading(false));
};

const Actions = () => (
<ActionPanel>
<Action
title={SELECT_PROGRAM_ACTION}
icon={iconPath(channel.icon)}
onAction={() => {
setSelectedProgramIndex(index);
push(<SelectedProgram channel={channel} program={program} />);
}}
/>
<Action title={SELECT_PROGRAM_ACTION} icon={iconPath(channel.icon)} onAction={handleSelectedProgram} />
</ActionPanel>
);

return (
<List.Item
key={index}
id={index.toString()}
title={`${getTime(program.startTime)} - ${program.title}`}
title={`${getTime(program.startTime)} - ${truncate(program.name)}`}
icon={program.isCurrentlyLive ? Icon.Clock : ""}
accessories={[{ icon: program.isLive ? Icon.Livestream : "" }]}
actions={<Actions />}
/>
);
Expand Down
26 changes: 12 additions & 14 deletions extensions/spanish-tv-guide/src/components/SelectedProgram.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,37 @@
import React, { useEffect, useState } from "react";
import { Detail } from "@raycast/api";

import { tvScheduleRepository } from "../modules/tv/repositories/tvScheduleRepository";
import { ChannelScheduleDto, ProgramDto, ProgramDetailsDto } from "../modules/tv/domain/tvScheduleDto";
import { getTime } from "../utils/dateUtils";
import { Maybe } from "../utils/objectUtils";
import { truncate } from "../utils/stringUtils";

export const SelectedProgram = ({ channel, program }: { channel: ChannelScheduleDto; program: ProgramDto }) => {
const [programDetails, setProgramDetails] = useState<Maybe<ProgramDetailsDto>>();

useEffect(() => void tvScheduleRepository.getProgramDetails(program).then(setProgramDetails), [program]);
export const SelectedProgram = (props: { channel: ChannelScheduleDto; program: ProgramDto & ProgramDetailsDto }) => {
const { channel, program } = props;

return (
programDetails && (
program && (
<Detail
navigationTitle={channel.name}
markdown={formattedProgramDetails(programDetails)}
isLoading={!programDetails}
markdown={formattedProgramDetails(program)}
isLoading={!program}
metadata={
<Detail.Metadata>
<Detail.Metadata.Label title={channel.name} icon={channel.icon} />
<Detail.Metadata.Label title={program.title} />
<Detail.Metadata.Label title={truncate(program.name)} />
</Detail.Metadata>
}
/>
)
);
};

const formattedProgramDetails = ({ title, startTime, image, description }: ProgramDetailsDto) => `
### ${getTime(startTime)} ${title}
type Props = ProgramDetailsDto & { name: string; startTime: Date };

const formattedProgramDetails = ({ name, startTime, imageUrl, description }: Props) => `
### ${getTime(startTime)} ${truncate(name)}
---
${description}
![${title}](${image}?raycast-width=125&raycast-height=188)
![${truncate(name)}](${imageUrl})
`;
2 changes: 1 addition & 1 deletion extensions/spanish-tv-guide/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { showToast, Toast } from "@raycast/api";
import React, { useEffect, useReducer } from "react";
import { useEffect, useReducer } from "react";

import { TvScheduleDto } from "./modules/tv/domain/tvScheduleDto";
import { tvScheduleRepository } from "./modules/tv/repositories/tvScheduleRepository";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@ export type ChannelScheduleDto = {
};

export type ProgramDto = {
title: string;
url: string;
name: string;
startTime: Date;
isCurrentlyLive: boolean;
isLive: boolean;
url: string;
};

export type ProgramDetailsDto = {
title: string;
startTime: Date;
image: string;
imageUrl: string;
headline: string;
description: string;
};

Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,72 +1,36 @@
import fetch from "isomorphic-fetch";
import { parse } from "node-html-parser";

import { ChannelScheduleDto, ProgramDto, ProgramDetailsDto, TvScheduleDto } from "../domain/tvScheduleDto";
import { ProgramResponse } from "./dto/programResponse";
import { ChannelResponse } from "./dto/channelResponse";
import { now, parseTime, plusOneDay } from "../../../utils/dateUtils";
import { toString, truncate } from "../../../utils/stringUtils";
import { findLast, last, replace } from "../../../utils/collectionUtils";
import { Maybe } from "../../../utils/objectUtils";
import { ProgramDto, ProgramDetailsDto, TvScheduleDto } from "../domain/tvScheduleDto";
import { dateReviver } from "../../../utils/dateUtils";

const TV_GUIDE_URL = "https://www.movistarplus.es/programacion-tv?v=json";
const ICON_URL = "https://www.movistarplus.es/recorte/m-NEO/canal";
const ICON_EXTENSION = "png";

type Response = { data: object };
const TV_GUIDE_CHANNELS_URL = "https://spanish-tv-guide-api.vercel.app/api/guide/channels";
const TV_GUIDE_PROGRAM_DETAILS_URL = "https://spanish-tv-guide-api.vercel.app/api/guide/program";

const getAll = async (): Promise<TvScheduleDto> => {
return fetch(TV_GUIDE_URL, { headers: { Accept: "application/json" } })
.then((response) => response.json() as unknown as Response)
.then((response: Response) => Object.values(response.data))
.then((channels: ChannelResponse[]) => channels.map(mapToChannel))
.then((channelSchedules: ChannelScheduleDto[]) => channelSchedules.map(channelScheduleWithLiveProgram));
return fetch(TV_GUIDE_CHANNELS_URL)
.then((response) => response.json())
.then((response) => JSON.parse(JSON.stringify(response), dateReviver));
};

const getProgramDetails = async (program: ProgramDto): Promise<ProgramDetailsDto> => {
return fetch(program.url)
.then((response: { text: () => Promise<string> }) => response.text())
.then((html: string) => {
const document = parse(html);
const image = document.querySelector("div.cover > img")?.getAttribute("src");
const description = document.querySelector("div.show-content > div")?.innerText?.trim();
return { ...program, image: toString(image), description: toString(description) };
});
};
const url = buildGetProgramDetailsUrl(TV_GUIDE_PROGRAM_DETAILS_URL, program.url);

const mapToChannel = (channel: ChannelResponse): ChannelScheduleDto => {
return {
icon: `${ICON_URL}/${channel.DATOS_CADENA.CODIGO}.${ICON_EXTENSION}`,
name: channel.DATOS_CADENA.NOMBRE,
schedule: mapToSchedule(channel.PROGRAMAS),
};
return fetch(url)
.then((response) => response.json())
.then((response) => JSON.parse(JSON.stringify(response), dateReviver));
};

const mapToSchedule = (programs: ProgramResponse[]) => {
return programs.reduce((programs: ProgramDto[], program: ProgramResponse) => {
const currentProgram = mapToProgram(program, last(programs));
return [...programs, currentProgram];
}, []);
};
const buildGetProgramDetailsUrl = (baseUrl: string, url: string) => {
const encodedProgramUrl = encodeURI(url);

const mapToProgram = (program: ProgramResponse, lastProgram: Maybe<ProgramDto>): ProgramDto => {
const isLive = program.DIRECTO;
const startTime = parseTime(program.HORA_INICIO);
const fixedTime = lastProgram?.startTime && lastProgram.startTime > startTime ? plusOneDay(startTime) : startTime;

return { isLive, isCurrentlyLive: false, startTime: fixedTime, url: program.URL, title: truncate(program.TITULO) };
return `${baseUrl}?${toQueryString("url", encodedProgramUrl)}`;
};

const channelScheduleWithLiveProgram = ({ schedule, icon, name }: ChannelScheduleDto): ChannelScheduleDto => {
const currentProgram = findLast(schedule, (program) => program.startTime < now());
const programs = currentProgram ? scheduleWithLiveProgram(schedule, currentProgram) : schedule;
return { icon, name, schedule: programs };
};
const toQueryString = (key: string, value: string) => {
const params = new URLSearchParams();
params.append(key, value);

const scheduleWithLiveProgram = (programs: ProgramDto[], currentProgram: ProgramDto): ProgramDto[] => {
return replace(currentProgram)
.in(programs)
.with({ ...currentProgram, isCurrentlyLive: true });
return params.toString();
};

export const tvScheduleRepository = { getAll, getProgramDetails };
18 changes: 0 additions & 18 deletions extensions/spanish-tv-guide/src/utils/collectionUtils.ts

This file was deleted.

Loading

0 comments on commit c2a8ae9

Please sign in to comment.