Skip to content

Commit

Permalink
✨ [nico] ニコ生から直接コメントを取得できるように (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
ci7lus committed Mar 5, 2022
1 parent 0da5c75 commit c62a552
Show file tree
Hide file tree
Showing 9 changed files with 1,341 additions and 186 deletions.
211 changes: 211 additions & 0 deletions src/miraktest-nico/NicoRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import axios from "axios"
import React, { useEffect, useState } from "react"
import { atom, useRecoilValue, useRecoilState } from "recoil"
import YAML from "yaml"
import { InitPlugin } from "../@types/plugin"
import { SayaDefinition } from "../miraktest-annict/types"
import tailwind from "../tailwind.scss"
import { KakologStream } from "./streams/Kakolog"
import { NicoLiveList } from "./streams/NicoLiveList"
import { NicoSetting } from "./types"

const _id = "io.github.ci7lus.miraktest-plugins.nico"
const prefix = "plugins.ci7lus.nico"
const meta = {
id: _id,
name: "ニコニコ実況",
author: "ci7lus",
version: "0.2.0",
description:
"ニコニコ実況からコメントを取得するプラグインです。対応するコメントレンダラープラグインが必要です。",
}

export const NicoRenderer: InitPlugin["renderer"] = ({ atoms }) => {
const settingAtom = atom<NicoSetting>({
key: `${prefix}.setting`,
default: {
isLiveEnabled: true,
isTimeshiftEnabled: true,
},
})

let isDplayerFound = false
let isPkrFound = false

return {
...meta,
exposedAtoms: [],
sharedAtoms: [
{
type: "atom",
atom: settingAtom,
},
],
storedAtoms: [
{
type: "atom",
atom: settingAtom,
},
],
setup({ plugins }) {
isDplayerFound = !!plugins.find(
(plugin) => plugin.id === "io.github.ci7lus.miraktest-plugins.dplayer"
)
isPkrFound = !!plugins.find(
(plugin) => plugin.id === "io.github.ci7lus.miraktest-plugins.pecore"
)
},
components: [
{
id: `${prefix}.onPlayer`,
position: "onPlayer",
component: () => {
const setting = useRecoilValue(settingAtom)
const service = useRecoilValue(atoms.contentPlayerServiceSelector)
const program = useRecoilValue(atoms.contentPlayerProgramSelector)
const isSeekable = useRecoilValue(
atoms.contentPlayerIsSeekableSelector
)
const time = useRecoilValue(atoms.contentPlayerPlayingTimeSelector)

const [sayaDefinition, setSayaDefinition] =
useState<SayaDefinition | null>(null)
useEffect(() => {
if (!isPkrFound && !isDplayerFound) {
console.warn("コメント送信先の取得に失敗しています")
return
}
axios
.get<string>(
"https://cdn.jsdelivr.net/gh/SlashNephy/saya@dev/docs/definitions.yml",
{
responseType: "text",
}
)
.then((r) => {
const parsed: SayaDefinition = YAML.parse(r.data)
setSayaDefinition(parsed)
})
.catch(console.error)
}, [])

return (
<>
{setting.isLiveEnabled && sayaDefinition && (
<NicoLiveList
sayaDefinition={sayaDefinition}
service={service}
program={program}
setting={setting}
isSeekable={isSeekable}
isDplayerFound={isDplayerFound}
isPkrFound={isPkrFound}
/>
)}
{setting.isTimeshiftEnabled && sayaDefinition && (
<KakologStream
sayaDefinition={sayaDefinition}
service={service}
program={program}
isSeekable={isSeekable}
time={time}
setting={setting}
isDplayerFound={isDplayerFound}
isPkrFound={isPkrFound}
/>
)}
</>
)
},
},
{
id: `${prefix}.settings`,
position: "onSetting",
label: meta.name,
component: () => {
const [setting, setSetting] = useRecoilState(settingAtom)
const [mail /*, setMail*/] = useState(setting.mail)
const [pass /*, _setPass*/] = useState(setting.pass)
const [isLiveEnabled, setIsLiveEnabled] = useState(
setting.isLiveEnabled
)
const [isTimeshiftEnabled, setIsTimeshiftEnabled] = useState(
setting.isTimeshiftEnabled
)
//const [isHidden, setIsHidden] = useState(true)

return (
<>
<style>{tailwind}</style>
<form
className="m-4"
onSubmit={(e) => {
e.preventDefault()
setSetting({
mail,
pass,
isLiveEnabled,
isTimeshiftEnabled,
})
}}
>
<label className="block mt-4">
<span>ニコニコ実況</span>
<input
type="checkbox"
className="block mt-2 form-checkbox"
checked={isLiveEnabled || false}
onChange={() => setIsLiveEnabled((enabled) => !enabled)}
/>
</label>
<label className="block mt-4">
<span>タイムシフト有効</span>
<input
type="checkbox"
className="block mt-2 form-checkbox"
checked={isTimeshiftEnabled || false}
onChange={() =>
setIsTimeshiftEnabled((enabled) => !enabled)
}
/>
</label>
{/*<label className="mt-4 block">
<span>モリタポのメールアドレス</span>
<input
type="text"
placeholder="[email protected]"
className="block mt-2 form-input rounded-md w-full text-gray-900"
value={mail || ""}
onChange={(e) => setMail(e.target.value)}
/>
</label>
<label className="mt-4 block">
<span>モリタポのパスワード</span>
<input
type={isHidden ? "password" : "text"}
placeholder="*****"
className="block mt-2 form-input rounded-md w-full text-gray-900"
value={pass || ""}
onChange={(e) => setPass(e.target.value)}
onFocus={() => setIsHidden(false)}
onBlur={() => setIsHidden(true)}
/>
</label>*/}
<button
type="submit"
className="bg-gray-100 text-gray-800 p-2 px-2 my-4 rounded-md focus:outline-none cursor-pointer"
>
保存
</button>
</form>
</>
)
},
},
],
destroy() {
return
},
windows: {},
}
}
58 changes: 58 additions & 0 deletions src/miraktest-nico/casAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import axios from "axios"
import { LiveProgram } from "./types/cas"
import { EmbeddedData } from "./types/embedded-data"

// https://github.com/SlashNephy/saya/blob/3dfd347a745dff751c8cdd17dc933943b73a1b0f/src/main/kotlin/blue/starry/saya/services/nicolive/LiveNicoliveCommentProvider.kt

export const casClient = axios.create({
baseURL: "https://api.cas.nicovideo.jp/v2",
headers: {
"x-frontend-id": "89",
},
})

export type CasMeta = {
status: number
totalCount?: number
ssId: string
}

export type LivePrograms = {
meta: CasMeta
data?: LiveProgram[]
}

export const getLivePrograms = async ({
searchWord,
}: {
searchWord: string
}) => {
return await casClient
.get<LivePrograms>("/search/programs.json", {
params: {
liveStatus: "onair",
sort: "startTime",
limit: 20,
searchWord,
searchTargets: "tagsExact",
order: "desc",
},
})
.then((data) => data.data?.data ?? [])
}

export const getEmbeddedData = async ({ liveId }: { liveId: string }) => {
const livePage = await axios.get<Document>(
`https://live.nicovideo.jp/watch/${liveId}`,
{ responseType: "document" }
)
const embeddedDataDom = livePage.data.getElementById("embedded-data")
if (!embeddedDataDom) {
throw new Error("#embedded-data not found on " + liveId)
}
const data = embeddedDataDom.getAttribute("data-props")
if (!data) {
throw new Error("data-props not found on " + liveId)
}
return JSON.parse(data) as EmbeddedData
}
Loading

0 comments on commit c62a552

Please sign in to comment.