diff --git a/sites/s.mxtv.jp/README.md b/sites/s.mxtv.jp/README.md new file mode 100644 index 00000000..ffe7c67b --- /dev/null +++ b/sites/s.mxtv.jp/README.md @@ -0,0 +1,28 @@ +# s.mxtv.jp + + + +## Index + +- [Index](#index) +- [Download the guide](#download-the-guide) +- [Update channel list](#update-channel-list) +- [Test](#test) + +## Download the guide + +```sh +npm run grab -- --site=s.mxtv.jp +``` + +## Update channel list + +```sh +npm run channels:parse -- --config=./sites/s.mxtv.jp/s.mxtv.jp.config.js --output=./sites/s.mxtv.jp/s.mxtv.jp.channels.xml +``` + +## Test + +```sh +npm test -- s.mxtv.jp +``` diff --git a/sites/s.mxtv.jp/s.mxtv.jp.channels.xml b/sites/s.mxtv.jp/s.mxtv.jp.channels.xml new file mode 100644 index 00000000..36a0de80 --- /dev/null +++ b/sites/s.mxtv.jp/s.mxtv.jp.channels.xml @@ -0,0 +1,5 @@ + + + Tokyo MX1 + Tokyo MX2 + diff --git a/sites/s.mxtv.jp/s.mxtv.jp.config.js b/sites/s.mxtv.jp/s.mxtv.jp.config.js new file mode 100644 index 00000000..e676d826 --- /dev/null +++ b/sites/s.mxtv.jp/s.mxtv.jp.config.js @@ -0,0 +1,81 @@ +const dayjs = require('dayjs') +const duration = require("dayjs/plugin/duration") +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) +dayjs.extend(duration) + +module.exports = { + site: 's.mxtv.jp', + days: 1, + lang: 'ja', + url: function ({ date, channel }) { + const id = `SV${channel.site_id}EPG${date.format('YYYYMMDD')}` + return `https://s.mxtv.jp/bangumi_file/json01/${id}.json` + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.Event_name, + description: item.Event_text, + category: parseCategory(item), + image: parseImage(item), + start: parseStart(item), + stop: parseStop(item) + }) + }) + return programs + }, + channels() { + return [ + { + lang: 'ja', + site_id: '1', + name: 'Tokyo MX1', + xmltv_id: 'TokyoMX1.jp' + }, + { + lang: 'ja', + site_id: '2', + name: 'Tokyo MX2', + xmltv_id: 'TokyoMX2.jp' + } + ] + } +} + +function parseImage(item) { + // Should return a string if we can output an image URL + // Might be done with `https://s.mxtv.jp/bangumi/link/weblinkU.csv?1722421896752` ? + return null +} + +function parseCategory(item) { + // Should return a string if we can determine the category + // Might be done with `https://s.mxtv.jp/index_set/csv/ranking_bangumi_allU.csv` ? + return null +} + +function parseStart(item) { + return dayjs.tz(item.Start_time.toString(), 'YYYY年MM月DD日HH時mm分ss秒', 'Asia/Tokyo') +} + +function parseStop(item) { + // Add the duration to the start time + const durationDate = dayjs(item.Duration, 'HH:mm:ss'); + return parseStart(item).add(dayjs.duration({ + hours: durationDate.hour(), + minutes: durationDate.minute(), + seconds: durationDate.second() + })) +} + +function parseItems(content) { + return JSON.parse(content) || [] +} diff --git a/sites/s.mxtv.jp/s.mxtv.jp.test.js b/sites/s.mxtv.jp/s.mxtv.jp.test.js new file mode 100644 index 00000000..f2219d31 --- /dev/null +++ b/sites/s.mxtv.jp/s.mxtv.jp.test.js @@ -0,0 +1,47 @@ +const { parser, url } = require('./s.mxtv.jp.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-08-01', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '2', + name: 'Tokyo MX2', + xmltv_id: 'TokyoMX2.jp' +} +const content = `[{ "Event_id": "0x6a57", "Start_time": "2024年07月27日05時00分00秒", "Duration": "01:00:00", "Event_name": "ヒーリングタイム&ヘッドラインニュース", "Event_text": "ねこの足跡", "Component": "480i 16:9 パンベクトルなし", "Sound": "ステレオ", "Event_detail": ""}]` + +it('can generate valid url', () => { + const result = url({ date, channel }) + expect(result).toBe('https://s.mxtv.jp/bangumi_file/json01/SV2EPG20240801.json') +}) + +it('can parse response', () => { + const result = parser({ date, channel, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2024-07-26T20:00:00.000Z', // UTC time + stop: '2024-07-26T21:00:00.000Z', // UTC + title: 'ヒーリングタイム&ヘッドラインニュース', + description: 'ねこの足跡', + image: null, + category: null + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '[]' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/skyperfectv.co.jp/README.md b/sites/skyperfectv.co.jp/README.md new file mode 100644 index 00000000..f6ad307b --- /dev/null +++ b/sites/skyperfectv.co.jp/README.md @@ -0,0 +1,28 @@ +# skyperfectv.co.jp + + + +## Index + +- [Index](#index) +- [Download the guide](#download-the-guide) +- [Update channel list](#update-channel-list) +- [Test](#test) + +## Download the guide + +```sh +npm run grab -- --site=skyperfectv.co.jp +``` + +## Update channel list + +```sh +npm run channels:parse -- --config=./sites/skyperfectv.co.jp/skyperfectv.co.jp.config.js --output=./sites/skyperfectv.co.jp/skyperfectv.co.jp.channels.xml +``` + +## Test + +```sh +npm test -- skyperfectv.co.jp +``` diff --git a/sites/skyperfectv.co.jp/skyperfectv.co.jp.channels.xml b/sites/skyperfectv.co.jp/skyperfectv.co.jp.channels.xml new file mode 100644 index 00000000..a998b9a0 --- /dev/null +++ b/sites/skyperfectv.co.jp/skyperfectv.co.jp.channels.xml @@ -0,0 +1,140 @@ + + + WOWOWシネマ + スターチャンネル + WOWOWプラス 映画・ドラマ・スポーツ・音楽 + 日本映画専門チャンネル + 東映チャンネル + 衛星劇場 + 映画・チャンネルNECO + ザ・シネマ + ムービープラス + J SPORTS 1 + J SPORTS 2 + J SPORTS 3 + J SPORTS 4 + スカイA + GAORA SPORTS + 日テレジータス + ゴルフネットワーク + スポーツライブ+ + WOWOWプライム + WOWOWライブ + ディズニー・チャンネル + TBSチャンネル1 最新ドラマ・音楽・映画 + TBSチャンネル2 名作ドラマ・スポーツ・アニメ + テレ朝チャンネル1 + テレ朝チャンネル2 + 日テレプラス ドラマ・アニメ・音楽ライブ + エンタメ~テレ☆シネドラバラエティ + チャンネル銀河 歴史ドラマ・サスペンス・日本のうた + フジテレビONE スポーツ・バラエティ + フジテレビTWO ドラマ・アニメ + フジテレビNEXT ライブ・プレミアム + スカチャン1 + 100%ヒッツ!スペースシャワーTV プラス + 音楽・ライブ! スペースシャワーTV + MTV + ミュージック・エア + MUSIC ON! TV(エムオン!) + 歌謡ポップスチャンネル + スーパー!ドラマTV #海外ドラマ☆エンタメ + アクションチャンネル + Dlife + 女性チャンネル♪LaLa TV + ミステリーチャンネル + KBS World 韓流専門チャンネル + Mnet + TAKARAZUKA SKY STAGE + 時代劇専門チャンネル + ファミリー劇場 + ホームドラマチャンネル 韓流・時代劇・国内ドラマ + MONDO TV + アニマックス + キッズステーション テレビアニメ・劇場版・OVA + カートゥーン ネットワーク 海外アニメ国内アニメ + アニメシアターX(AT-X) + ディスカバリーチャンネル + アニマルプラネット + ヒストリーチャンネル 日本・世界の歴史&エンタメ + ナショナル ジオグラフィック + 日テレNEWS24 + TBS NEWS + BBCニュース + CNNj + BS釣りビジョン + ディズニージュニア + 囲碁・将棋チャンネル + グリーンチャンネル + ショップチャンネル + QVC(キューヴィーシー) + WOWOW 4K + V☆パラダイス + エキサイティング・グランプリ + スポーツライブ+ 2 + FIGHTING TV サムライ + 刺激ストロングチャンネル + ダンスチャンネル by エンタメ~テレ + スカチャン5 + スカチャン6 + スカチャン7 + スカチャン8 + スカチャン9 + スカチャン10 + スカチャン11 + スカチャン12 + スカチャン13 + スカチャン14 + ミュージック・ジャパンTV + ミュージック・グラフィティTV + アジアドラマチックTV(アジドラ) + KNTV + 大人のイキヌキ!ヌーヴェルパラダイス + アイドル専門チャンネルPigoo + SORA―お天気チャンネル― + CNN U.S. + 中国テレビ★大富チャンネル + 日経CNBC + ベターライフチャンネル + パチンコ★パチスロTV! + パチ・スロ サイトセブンTV + 釣りビジョンHD + 寄席チャンネル + 旅チャンネル + 鉄道チャンネル + 南関東地方競馬チャンネル + JLC680 + JLC681 + JLC682 + JLC683 + JLC684 + グリーンチャンネル2 + SPEEDチャンネル(競輪ライブ) 690 + SPEEDチャンネル(競輪ライブ) 691 + SPEEDチャンネル(競輪ライブ) 692 + SPEEDチャンネル(競輪ライブ) 693 + SPEEDチャンネル(競輪ライブ) 694 + スピードプラスワン695 + 地方競馬ナイン 701 + 地方競馬ナイン 702 + 地方競馬ナイン 703 + フェニックステレビ(鳳凰衛視) + ジュエリー☆GSTV + セレクトショッピング + kmpチャンネル + プレイボーイ チャンネル + レインボーチャンネル + ミッドナイト・ブルー + パラダイステレビ + チェリーボム + VENUS + バニラスカイチャンネル + エンタ!959 + Zaptv + ダイナマイトTV + AV王 + レッドチェリー + Splash + フラミンゴ + スカパー!プロモ599 + \ No newline at end of file diff --git a/sites/skyperfectv.co.jp/skyperfectv.co.jp.config.js b/sites/skyperfectv.co.jp/skyperfectv.co.jp.config.js new file mode 100644 index 00000000..c2d4aa23 --- /dev/null +++ b/sites/skyperfectv.co.jp/skyperfectv.co.jp.config.js @@ -0,0 +1,114 @@ +const axios = require('axios') +const dayjs = require('dayjs') +const cheerio = require('cheerio') +const duration = require('dayjs/plugin/duration') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) +dayjs.extend(duration) + +const exported = { + site: 'skyperfectv.co.jp', + days: 1, + lang: 'ja', + url: function ({ date, channel }) { + let [type, ...code] = channel.site_id.split('_') + code = code.join('_') + return `https://www.skyperfectv.co.jp/program/schedule/${type}/channel:${code}/date:${date.format('YYMMDD')}` + }, + logo: function ({ channel }) { + return `https://www.skyperfectv.co.jp/library/common/img/channel/icon/basic/m_${channel.site_id.toLowerCase()}.gif` + }, + // Specific function that permits to gather NSFW channels (needs confirmation) + async fetchSchedule({ date, channel }) { + const url = exported.url({ date, channel }) + const response = await axios.get(url, { + headers: { + 'Cookie': 'adult_auth=true' + } + }) + return response.data + }, + parser({ content, date }) { + const $ = cheerio.load(content) + const programs = [] + + const sections = [ + { id: 'js-am', addition: 0 }, + { id: 'js-pm', addition: 0 }, + { id: 'js-md', addition: 1 } + ] + + sections.forEach(({ id, addition }) => { + $(`#${id} > td`).each((index, element) => { + // `td` is a column for a day + // the next `td` will be the next day + const today = date.add(index + addition, 'd').tz('Asia/Tokyo') + + const parseTime = (timeString) => { + // timeString is in the format "HH:mm" + // replace `today` with the time from timeString + const [hour, minute] = timeString.split(':').map(Number) + return today.hour(hour).minute(minute) + } + + const $element = $(element) // Wrap element with Cheerio + $element.find('.p-program__item').each((itemIndex, itemElement) => { + const $itemElement = $(itemElement) // Wrap itemElement with Cheerio + const [start, stop] = $itemElement.find('.p-program__range').first().text().split('〜').map(parseTime) + const title = $itemElement.find('.p-program__name').first().text() + const image = $itemElement.find('.js-program_thumbnail').first().attr('data-lazysrc') + programs.push({ + title, + start, + stop, + image + }) + }) + }) + }) + + return programs + }, + async channels() { + const pageParser = (content, type) => { + // type: "basic" | "premium" + // Returns an array of channel objects + + const $ = cheerio.load(content) + const channels = [] + + $('.p-channel').each((index, element) => { + const site_id = `${type}_${$(element).find('.p-channel__id').text()}` + const name = $(element).find('.p-channel__name').text() + channels.push({ site_id, name, lang: 'ja' }) + }) + + return channels + } + + const getChannels = async (type) => { + const response = await axios.get(`https://www.skyperfectv.co.jp/program/schedule/${type}/`, { + headers: { + 'Cookie': 'adult_auth=true;' + } + }) + return pageParser(response.data, type) + } + + const fetchAllChannels = async () => { + const basicChannels = await getChannels('basic') + const premiumChannels = await getChannels('premium') + const results = [...basicChannels, ...premiumChannels] + return results + } + + return await fetchAllChannels() + } +} + +module.exports = exported \ No newline at end of file diff --git a/sites/skyperfectv.co.jp/skyperfectv.co.jp.test.js b/sites/skyperfectv.co.jp/skyperfectv.co.jp.test.js new file mode 100644 index 00000000..eae08e3f --- /dev/null +++ b/sites/skyperfectv.co.jp/skyperfectv.co.jp.test.js @@ -0,0 +1,53 @@ +const { parser, url } = require('./skyperfectv.co.jp.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-08-01', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'basic_BS193', + name: 'WOWOWシネマ', + xmltv_id: 'WOWOWCinema.jp' +} + +const content = ` +番組表(アニメシアターX(AT-X))|スカパー!

現在マイリストを更新中です。

現在マイリストに登録中です。

現在マイリストから削除中です。

番組放送予定表

基本プラン
選択した日付から1週間の番組表を表示します
番組放送予定表
基本プラン
選択した日付から1週間の番組表を表示します

前週

次週

午前(4時~)

午後(12時~)

深夜(24時~)

  • 04:00〜04:30

    ヴァルキリードライヴマーメイド #06

    番組サムネイル
            newAppBannerImgnewAppBannerImg

            番組情報は予告なく変更となる場合がありますのでご了承ください

            ・このサイトでは、当日から1週間分はEPGと同等の番組情報が表示され、その先1ヶ月後まではガイド誌(有料)と同等の番組情報が表示されます。番組や放送予定は予告なく変更される場合がありますのでご了承ください。
            ・このサイトは、ウェブブラウザーMicrosoftEdge最新版、GoogleChrome最新版、Firefox最新版、Safari最新版での動作を確認しております。上記以外のウェブブラウザーで閲覧されますと、表示の乱れや予期せぬ動作を起こす場合がございますので、予めご了承ください。

            ##ERROR_MSG##

            ##ERROR_MSG##

            ##ERROR_MSG##

            マイリストから削除してもよいですか?

            ログインをしてお気に入り番組を登録しよう!
            Myスカパー!にログインをすると、マイリストにお気に入り番組リストを作成することができます!
            ログイン新規会員登録
            マイリストに番組を登録できません
            ##ERROR_MSG##
            +` + +it('can generate valid url', () => { + const result = url({ date, channel }) + expect(result).toBe('https://www.skyperfectv.co.jp/program/schedule/basic/channel:BS193/date:240801') +}) + +it('can parse response', async () => { + const result = (await parser({ date, channel, content })).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result.filter(p => p.title == 'ヴァルキリードライヴマーメイド #06')).toMatchObject([ + { + start: '2024-07-31T19:00:00.000Z', // UTC time + stop: '2024-07-31T19:30:00.000Z', // UTC + title: 'ヴァルキリードライヴマーメイド #06', + image: 'https://pm-img-ap.skyperfectv.co.jp/uploads/thumbnail/image/11301805/S_BC929697780313_be7975d4e26a4cad9b89fc6c94807e38_20240613144158569.jpg' + } + ]) +}) + + +const empty = ` +番組表(アニメシアターX(AT-X))|スカパー!

            現在マイリストを更新中です。

            現在マイリストに登録中です。

            現在マイリストから削除中です。

            番組放送予定表

            基本プラン
            選択した日付から1週間の番組表を表示します
            番組放送予定表
            基本プラン
            選択した日付から1週間の番組表を表示します

            前週

            次週

            午前(4時~)

            午後(12時~)

            深夜(24時~)

                        newAppBannerImgnewAppBannerImg

                        番組情報は予告なく変更となる場合がありますのでご了承ください

                        ・このサイトでは、当日から1週間分はEPGと同等の番組情報が表示され、その先1ヶ月後まではガイド誌(有料)と同等の番組情報が表示されます。番組や放送予定は予告なく変更される場合がありますのでご了承ください。
                        ・このサイトは、ウェブブラウザーMicrosoftEdge最新版、GoogleChrome最新版、Firefox最新版、Safari最新版での動作を確認しております。上記以外のウェブブラウザーで閲覧されますと、表示の乱れや予期せぬ動作を起こす場合がございますので、予めご了承ください。

                        ##ERROR_MSG##

                        ##ERROR_MSG##

                        ##ERROR_MSG##

                        マイリストから削除してもよいですか?

                        ログインをしてお気に入り番組を登録しよう!
                        Myスカパー!にログインをすると、マイリストにお気に入り番組リストを作成することができます!
                        ログイン新規会員登録
                        マイリストに番組を登録できません
                        ##ERROR_MSG##
                        +` + +it('can handle empty guide', async () => { + const result = parser({ + date, + channel, + content: empty + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.channels.xml b/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.channels.xml index 5ffd05b1..fc12c10b 100644 --- a/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.channels.xml +++ b/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.channels.xml @@ -75,7 +75,7 @@ ミュージック・エア ナショナル ジオグラフィック NHKBS1 - NHK BS4K + NHK BS4K NHKBSプレミアム NHK東京 教育 NHK東京 総合 @@ -120,20 +120,20 @@ ご案内チャンネル オンデマンドチャンネル290 オンデマンドチャンネル440 - ショップチャンネル プラス - MTV HD - ミステリーチャンネル - 音楽・ライブ! スペースシャワーTV HD - MONDOTV HD - 日経CNBC - パチンコ★パチスロTV! - ジュエリー☆GSTV - JSPORTS1 (4K) - JSPORTS2 (4K) - JSPORTS3 (4K) - JSPORTS4 (4K) - ショップチャンネル 4K - 4K QVC + ショップチャンネル プラス + MTV HD + ミステリーチャンネル + 音楽・ライブ! スペースシャワーTV HD + MONDOTV HD + 日経CNBC + パチンコ★パチスロTV! + ジュエリー☆GSTV + JSPORTS1 (4K) + JSPORTS2 (4K) + JSPORTS3 (4K) + JSPORTS4 (4K) + ショップチャンネル 4K + 4K QVC NHK BSP4K NHK BS MTV HD @@ -149,4 +149,5 @@ KBS京都 読売テレビ サンテレビ + TSS diff --git a/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.config.js b/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.config.js index 9140f5e8..9651de74 100644 --- a/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.config.js +++ b/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.config.js @@ -11,6 +11,7 @@ dayjs.extend(customParseFormat) module.exports = { site: 'tvguide.myjcom.jp', days: 2, + lang: 'ja', url: function ({ date, channel }) { const id = `${channel.site_id}_${date.format('YYYYMMDD')}`