Files
tvbox_collect/jaychouqq/yingshi/js2/rrmj.js
T
2026-06-29 03:41:14 +00:00

411 lines
10 KiB
JavaScript

const CryptoJS = createCryptoJS()
const cheerio = createCheerio()
// 會員數據來源: baby
let deviceId = '4E292B5D-FE99-4860-8054-5B0A11CC27AF'
let vipToken = 'rrtv-bb3643e9bc9d62ebea4c30b20e7c313d5f57ab8a'
let appConfig = {
ver: 20251202,
title: '人人',
site: 'https://api.rrmj.plus',
tabs: [
{
id: '1',
name: '電影',
ext: {
dramaType: 'MOVIE',
area: '',
},
},
{
id: '1',
name: '電視劇',
ext: {
dramaType: 'TV',
area: '',
},
},
{
id: '1',
name: '綜藝',
ext: {
dramaType: 'VARIETY',
area: '',
},
},
{
id: '1',
name: '紀錄片',
ext: {
dramaType: 'DOCUMENTARY',
area: '',
},
},
{
id: '1',
name: '動漫',
ext: {
dramaType: 'COMIC',
area: '',
},
},
{
id: '1',
name: '美劇',
ext: {
dramaType: 'TV',
area: '美国',
},
},
{
id: '1',
name: '韓劇',
ext: {
dramaType: 'TV',
area: '韩国',
},
},
{
id: '1',
name: '泰劇',
ext: {
dramaType: 'TV',
area: '泰国',
},
},
{
id: '1',
name: '日劇',
ext: {
dramaType: 'TV',
area: '日本',
},
},
{
id: '1',
name: '英劇',
ext: {
dramaType: 'TV',
area: '英国',
},
},
],
}
async function getConfig() {
return jsonify(appConfig)
}
async function getCards(ext) {
ext = argsify(ext)
let cards = []
let { dramaType, area = '', page = 1 } = ext
try {
const url = appConfig.site + `/m-station/drama/drama_filter_search`
const body = {
area: area,
sort: 'new',
year: '',
dramaType: dramaType,
plotType: '',
contentLabel: '',
page: page,
rows: 30,
}
const headers = buildSignedHeaders({
method: 'POST',
url,
deviceId,
})
headers['Content-Type'] = 'application/json'
const { data } = await $fetch.post(url, body, {
headers,
})
const decryptedData = decrypt(data)
argsify(decryptedData).data.forEach((e) => {
cards.push({
vod_id: `${e.dramaId}`,
vod_name: e.title,
vod_pic: e.coverUrl,
vod_remarks: e.subtitle || '',
vod_duration: e.cornerMark || '',
ext: {
id: `${e.dramaId}`,
},
})
})
return jsonify({
list: cards,
})
} catch (error) {
$print(error)
}
}
async function getTracks(ext) {
ext = argsify(ext)
let tracks = []
let id = ext.id
let url = appConfig.site + `/m-station/drama/page`
let params = {
hsdrOpen: 0,
isAgeLimit: 0,
dramaId: id,
quality: 'AI4K',
hevcOpen: 1,
tria4k: 1,
}
let headers = buildSignedHeaders({
method: 'GET',
url,
params,
deviceId,
})
const { data } = await $fetch.get(`${url}?${sortedQueryString(params)}`, {
headers,
})
const decryptedData = decrypt(data)
const quality = argsify(decryptedData).data.watchInfo.sortedItems[0].qualityCode
argsify(decryptedData).data.episodeList.forEach((e) => {
tracks.push({
name: `${e.episodeNo}`,
pan: '',
ext: {
dramaId: id,
episodeSid: e.sid,
quality,
},
})
})
return jsonify({
list: [
{
title: '默认分组',
tracks,
},
],
})
}
async function getPlayinfo(ext) {
await getLatestToken()
ext = argsify(ext)
let { dramaId, episodeSid, quality } = ext
let url = appConfig.site + `/m-station/drama/play`
try {
let params = {
hsdrOpen: 0,
dramaId: dramaId,
episodeSid: episodeSid,
quality: quality,
hevcOpen: 1,
tria4k: 1,
}
let headers = buildSignedHeaders({
method: 'GET',
url,
params,
deviceId,
token: vipToken,
})
const { data } = await $fetch.get(`${url}?${sortedQueryString(params)}`, {
headers,
})
const decryptedData = decrypt(data)
let encryptedurl = argsify(decryptedData).data.m3u8.url
let newSign = argsify(decryptedData).data.newSign
function decryptUrl(url, newSign) {
var key = CryptoJS.enc.Utf8.parse(newSign.substring(4, 20))
var iv = CryptoJS.enc.Utf8.parse('b1da7878016e4e2b')
return CryptoJS.AES.decrypt(url, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
}).toString(CryptoJS.enc.Utf8)
}
let playUrl = decryptUrl(encryptedurl, newSign)
return jsonify({ urls: [playUrl] })
} catch (error) {
$print(error)
}
}
async function search(ext) {
ext = argsify(ext)
let cards = []
const text = encodeURIComponent(ext.text)
const page = ext.page || 1
if (page > 1) return
const url = `${appConfig.site}/m-station/search/drama`
const params = {
keywords: text,
size: 20,
order: 'match',
search_after: '',
isExecuteVipActivity: true,
}
const headers = buildSignedHeaders({
method: 'GET',
url,
params,
deviceId,
})
const { data } = await $fetch.get(`${url}?${sortedQueryString(params)}`, {
headers,
})
const decryptedData = decrypt(data)
argsify(decryptedData).data.searchDramaList.forEach((e) => {
cards.push({
vod_id: `${e.id}`,
vod_name: e.title,
vod_pic: e.cover,
vod_remarks: e.subtitle || '',
vod_duration: e.cornerMark || '',
ext: {
id: `${e.id}`,
},
})
})
return jsonify({
list: cards,
})
}
async function getLatestToken() {
let postUrl = 'https://t.me/Jsforbaby/219'
try {
const { data } = await $fetch.get(postUrl)
const $ = cheerio.load(data)
let text = $('meta[property="og:description"]').attr('content')
let umid = text.match(
/umid:\s+([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})/
)[1]
let token = text.match(/token:\s+(rrtv-[0-9a-fA-F]+)/)[1]
if (umid && token) {
deviceId = umid
vipToken = token
}
} catch (error) {
return
}
}
function sortedQueryString(params = {}) {
const normalized = {}
for (const [k, v] of Object.entries(params)) {
if (typeof v === 'boolean') normalized[k] = v ? 'true' : 'false'
else if (v == null) normalized[k] = ''
else normalized[k] = String(v)
}
const keys = []
for (const key in normalized) {
if (Object.prototype.hasOwnProperty.call(normalized, key)) {
keys.push(key)
}
}
keys.sort()
const pairs = []
for (const key of keys) {
const encodedKey = encodeURIComponent(key)
const encodedValue = encodeURIComponent(normalized[key])
pairs.push(`${encodedKey}=${encodedValue}`)
}
return pairs.join('&')
}
function getPathname(url) {
let pathnameStart = url.indexOf('//') + 2
if (pathnameStart === 1) pathnameStart = 0
const pathStart = url.indexOf('/', pathnameStart)
if (pathStart === -1) return '/'
const queryStart = url.indexOf('?', pathStart)
const hashStart = url.indexOf('#', pathStart)
let pathEnd = queryStart !== -1 ? queryStart : hashStart !== -1 ? hashStart : url.length
const pathname = url.substring(pathStart, pathEnd)
return pathname || '/'
}
function buildSignedHeaders({ method, url, params = {}, deviceId, token }) {
const ClientProfile = {
client_type: 'web_pc',
client_version: '1.0.0',
user_agent: 'Mozilla/5.0',
origin: 'https://rrsp.com.cn/',
referer: 'https://rrsp.com.cn/',
}
const pathname = getPathname(url)
const qs = sortedQueryString(params)
const nowMs = Date.now()
const SIGN_SECRET = 'ES513W0B1CsdUrR13Qk5EgDAKPeeKZY'
const xCaSign = generateSignature(
method,
deviceId,
ClientProfile.client_type,
ClientProfile.client_version,
nowMs,
pathname,
qs,
SIGN_SECRET
)
return {
clientVersion: ClientProfile.client_version,
deviceId,
clientType: ClientProfile.client_type,
t: String(nowMs),
aliId: deviceId,
umid: deviceId,
token: token || '',
cv: ClientProfile.client_version,
ct: ClientProfile.client_type,
uet: '9',
'x-ca-sign': xCaSign,
Accept: 'application/json',
'User-Agent': ClientProfile.user_agent,
Origin: ClientProfile.origin,
Referer: ClientProfile.referer,
}
}
function generateSignature(method, aliId, ct, cv, timestamp, path, sortedQuery, secret) {
const signStr = `${method.toUpperCase()}\naliId:${aliId}\nct:${ct}\ncv:${cv}\nt:${timestamp}\n${path}?${sortedQuery}`
const signature = CryptoJS.HmacSHA256(signStr, secret)
return CryptoJS.enc.Base64.stringify(signature)
}
function decrypt(str) {
const AES_KEY = '3b744389882a4067'
let raw = CryptoJS.enc.Base64.parse(str)
let key = CryptoJS.enc.Utf8.parse(AES_KEY)
let decrypted = CryptoJS.AES.decrypt(CryptoJS.lib.CipherParams.create({ ciphertext: raw }), key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
})
return decrypted.toString(CryptoJS.enc.Utf8)
}