|
@@ -6,6 +6,8 @@ import cookieParser from 'cookie-parser';
|
|
|
import { readFileSync } from 'fs';
|
|
import { readFileSync } from 'fs';
|
|
|
import { fileURLToPath } from 'url';
|
|
import { fileURLToPath } from 'url';
|
|
|
import { dirname, join } from 'path';
|
|
import { dirname, join } from 'path';
|
|
|
|
|
+import { CookieJar } from 'tough-cookie';
|
|
|
|
|
+import { wrapper } from 'axios-cookiejar-support';
|
|
|
|
|
|
|
|
// node-rsa 是 CommonJS 模块,需要使用默认导入
|
|
// node-rsa 是 CommonJS 模块,需要使用默认导入
|
|
|
const NodeRSA = nodeRSA;
|
|
const NodeRSA = nodeRSA;
|
|
@@ -360,6 +362,13 @@ async function handleHomeAssistantLogin(config, credentials) {
|
|
|
console.log(`用户名: ${credentials.username}`);
|
|
console.log(`用户名: ${credentials.username}`);
|
|
|
console.log(`密码: ${'*'.repeat(credentials.password.length)}`);
|
|
console.log(`密码: ${'*'.repeat(credentials.password.length)}`);
|
|
|
|
|
|
|
|
|
|
+ // 创建 Cookie jar 来保持会话
|
|
|
|
|
+ const cookieJar = new CookieJar();
|
|
|
|
|
+ const client = wrapper(axios.create({
|
|
|
|
|
+ jar: cookieJar,
|
|
|
|
|
+ withCredentials: true
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
const baseHeaders = {
|
|
const baseHeaders = {
|
|
|
'Content-Type': 'application/json',
|
|
'Content-Type': 'application/json',
|
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
@@ -372,15 +381,17 @@ async function handleHomeAssistantLogin(config, credentials) {
|
|
|
let initialCookies = [];
|
|
let initialCookies = [];
|
|
|
try {
|
|
try {
|
|
|
console.log('访问首页获取初始 Cookie...');
|
|
console.log('访问首页获取初始 Cookie...');
|
|
|
- const homeResponse = await axios.get(`${targetBaseUrl}/`, {
|
|
|
|
|
|
|
+ const homeResponse = await client.get(`${targetBaseUrl}/`, {
|
|
|
headers: {
|
|
headers: {
|
|
|
'User-Agent': baseHeaders['User-Agent']
|
|
'User-Agent': baseHeaders['User-Agent']
|
|
|
},
|
|
},
|
|
|
- withCredentials: true,
|
|
|
|
|
maxRedirects: 5
|
|
maxRedirects: 5
|
|
|
});
|
|
});
|
|
|
initialCookies = homeResponse.headers['set-cookie'] || [];
|
|
initialCookies = homeResponse.headers['set-cookie'] || [];
|
|
|
console.log(`获取到 ${initialCookies.length} 个初始 Cookie`);
|
|
console.log(`获取到 ${initialCookies.length} 个初始 Cookie`);
|
|
|
|
|
+ // 从 Cookie jar 中获取 Cookie
|
|
|
|
|
+ const jarCookies = await cookieJar.getCookies(targetBaseUrl);
|
|
|
|
|
+ console.log(`Cookie jar 中有 ${jarCookies.length} 个 Cookie`);
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.log('访问首页失败(可能不需要):', error.message);
|
|
console.log('访问首页失败(可能不需要):', error.message);
|
|
|
}
|
|
}
|
|
@@ -389,7 +400,7 @@ async function handleHomeAssistantLogin(config, credentials) {
|
|
|
console.log('步骤1: 创建登录流程...');
|
|
console.log('步骤1: 创建登录流程...');
|
|
|
let flowResponse;
|
|
let flowResponse;
|
|
|
try {
|
|
try {
|
|
|
- flowResponse = await axios.post(
|
|
|
|
|
|
|
+ flowResponse = await client.post(
|
|
|
`${targetBaseUrl}/auth/login_flow`,
|
|
`${targetBaseUrl}/auth/login_flow`,
|
|
|
{
|
|
{
|
|
|
client_id: `${targetBaseUrl}/`,
|
|
client_id: `${targetBaseUrl}/`,
|
|
@@ -398,7 +409,6 @@ async function handleHomeAssistantLogin(config, credentials) {
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
headers: baseHeaders,
|
|
headers: baseHeaders,
|
|
|
- withCredentials: true,
|
|
|
|
|
maxRedirects: 0,
|
|
maxRedirects: 0,
|
|
|
validateStatus: function (status) {
|
|
validateStatus: function (status) {
|
|
|
return status >= 200 && status < 500;
|
|
return status >= 200 && status < 500;
|
|
@@ -450,7 +460,7 @@ async function handleHomeAssistantLogin(config, credentials) {
|
|
|
console.log('步骤2: 提交用户名和密码...');
|
|
console.log('步骤2: 提交用户名和密码...');
|
|
|
let loginResponse;
|
|
let loginResponse;
|
|
|
try {
|
|
try {
|
|
|
- loginResponse = await axios.post(
|
|
|
|
|
|
|
+ loginResponse = await client.post(
|
|
|
`${targetBaseUrl}/auth/login_flow/${flowId}`,
|
|
`${targetBaseUrl}/auth/login_flow/${flowId}`,
|
|
|
{
|
|
{
|
|
|
username: credentials.username,
|
|
username: credentials.username,
|
|
@@ -459,7 +469,6 @@ async function handleHomeAssistantLogin(config, credentials) {
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
headers: baseHeaders,
|
|
headers: baseHeaders,
|
|
|
- withCredentials: true,
|
|
|
|
|
maxRedirects: 0,
|
|
maxRedirects: 0,
|
|
|
validateStatus: function (status) {
|
|
validateStatus: function (status) {
|
|
|
return status >= 200 && status < 500;
|
|
return status >= 200 && status < 500;
|
|
@@ -495,11 +504,29 @@ async function handleHomeAssistantLogin(config, credentials) {
|
|
|
|
|
|
|
|
// Home Assistant 登录成功时,type 为 "create_entry"
|
|
// Home Assistant 登录成功时,type 为 "create_entry"
|
|
|
if (responseType === 'create_entry') {
|
|
if (responseType === 'create_entry') {
|
|
|
- // 合并所有请求的 Cookie
|
|
|
|
|
|
|
+ // 从 Cookie jar 中获取所有 Cookie
|
|
|
|
|
+ console.log('从 Cookie jar 中获取 Cookie...');
|
|
|
|
|
+ const jarCookies = await cookieJar.getCookies(targetBaseUrl);
|
|
|
|
|
+ console.log(`Cookie jar 中有 ${jarCookies.length} 个 Cookie`);
|
|
|
|
|
+
|
|
|
|
|
+ // 合并所有请求的 Cookie(从响应头和 Cookie jar)
|
|
|
const flowCookies = flowResponse.headers['set-cookie'] || [];
|
|
const flowCookies = flowResponse.headers['set-cookie'] || [];
|
|
|
const loginCookies = loginResponse.headers['set-cookie'] || [];
|
|
const loginCookies = loginResponse.headers['set-cookie'] || [];
|
|
|
const allCookies = [...initialCookies, ...flowCookies, ...loginCookies];
|
|
const allCookies = [...initialCookies, ...flowCookies, ...loginCookies];
|
|
|
|
|
|
|
|
|
|
+ // 从 Cookie jar 转换为 Set-Cookie 格式
|
|
|
|
|
+ jarCookies.forEach(cookie => {
|
|
|
|
|
+ let cookieStr = `${cookie.key}=${cookie.value}`;
|
|
|
|
|
+ if (cookie.path) cookieStr += `; Path=${cookie.path}`;
|
|
|
|
|
+ if (cookie.domain) cookieStr += `; Domain=${cookie.domain}`;
|
|
|
|
|
+ if (cookie.expires) cookieStr += `; Expires=${cookie.expires.toUTCString()}`;
|
|
|
|
|
+ if (cookie.maxAge) cookieStr += `; Max-Age=${cookie.maxAge}`;
|
|
|
|
|
+ if (cookie.secure) cookieStr += `; Secure`;
|
|
|
|
|
+ if (cookie.httpOnly) cookieStr += `; HttpOnly`;
|
|
|
|
|
+ if (cookie.sameSite) cookieStr += `; SameSite=${cookie.sameSite}`;
|
|
|
|
|
+ allCookies.push(cookieStr);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
// 去重 Cookie(保留最后一个)
|
|
// 去重 Cookie(保留最后一个)
|
|
|
const cookieMap = new Map();
|
|
const cookieMap = new Map();
|
|
|
allCookies.forEach(cookie => {
|
|
allCookies.forEach(cookie => {
|
|
@@ -531,23 +558,14 @@ async function handleHomeAssistantLogin(config, credentials) {
|
|
|
|
|
|
|
|
console.log(`访问授权端点: ${authorizeUrl}`);
|
|
console.log(`访问授权端点: ${authorizeUrl}`);
|
|
|
|
|
|
|
|
- // 构建 Cookie 字符串用于授权请求
|
|
|
|
|
- const cookieHeader = uniqueCookies.map(cookie => {
|
|
|
|
|
- const cookieStr = cookie.split(';')[0]; // 只取 name=value 部分
|
|
|
|
|
- return cookieStr;
|
|
|
|
|
- }).join('; ');
|
|
|
|
|
-
|
|
|
|
|
- console.log(`使用 Cookie 头: ${cookieHeader.substring(0, 100)}...`);
|
|
|
|
|
-
|
|
|
|
|
- const authorizeResponse = await axios.get(authorizeUrl, {
|
|
|
|
|
|
|
+ // 使用 Cookie jar 的客户端访问授权端点
|
|
|
|
|
+ const authorizeResponse = await client.get(authorizeUrl, {
|
|
|
headers: {
|
|
headers: {
|
|
|
'User-Agent': baseHeaders['User-Agent'],
|
|
'User-Agent': baseHeaders['User-Agent'],
|
|
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
|
|
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
|
|
- 'Referer': `${targetBaseUrl}/`,
|
|
|
|
|
- 'Cookie': cookieHeader
|
|
|
|
|
|
|
+ 'Referer': `${targetBaseUrl}/`
|
|
|
},
|
|
},
|
|
|
- withCredentials: true,
|
|
|
|
|
maxRedirects: 5,
|
|
maxRedirects: 5,
|
|
|
validateStatus: function (status) {
|
|
validateStatus: function (status) {
|
|
|
return status >= 200 && status < 400;
|
|
return status >= 200 && status < 400;
|
|
@@ -557,15 +575,33 @@ async function handleHomeAssistantLogin(config, credentials) {
|
|
|
console.log(`授权响应状态码: ${authorizeResponse.status}`);
|
|
console.log(`授权响应状态码: ${authorizeResponse.status}`);
|
|
|
console.log(`授权响应 URL: ${authorizeResponse.request?.res?.responseUrl || authorizeResponse.config?.url}`);
|
|
console.log(`授权响应 URL: ${authorizeResponse.request?.res?.responseUrl || authorizeResponse.config?.url}`);
|
|
|
|
|
|
|
|
|
|
+ // 从 Cookie jar 中获取授权后的 Cookie
|
|
|
|
|
+ const authorizeJarCookies = await cookieJar.getCookies(targetBaseUrl);
|
|
|
|
|
+ console.log(`授权后 Cookie jar 中有 ${authorizeJarCookies.length} 个 Cookie`);
|
|
|
|
|
+
|
|
|
// 获取授权响应中的 Cookie
|
|
// 获取授权响应中的 Cookie
|
|
|
const authorizeCookies = authorizeResponse.headers['set-cookie'] || [];
|
|
const authorizeCookies = authorizeResponse.headers['set-cookie'] || [];
|
|
|
console.log(`授权响应获取到 ${authorizeCookies.length} 个 Cookie`);
|
|
console.log(`授权响应获取到 ${authorizeCookies.length} 个 Cookie`);
|
|
|
|
|
|
|
|
- // 合并授权 Cookie
|
|
|
|
|
|
|
+ // 从 Cookie jar 转换为 Set-Cookie 格式并合并
|
|
|
|
|
+ authorizeJarCookies.forEach(cookie => {
|
|
|
|
|
+ let cookieStr = `${cookie.key}=${cookie.value}`;
|
|
|
|
|
+ if (cookie.path) cookieStr += `; Path=${cookie.path}`;
|
|
|
|
|
+ if (cookie.domain) cookieStr += `; Domain=${cookie.domain}`;
|
|
|
|
|
+ if (cookie.expires) cookieStr += `; Expires=${cookie.expires.toUTCString()}`;
|
|
|
|
|
+ if (cookie.maxAge) cookieStr += `; Max-Age=${cookie.maxAge}`;
|
|
|
|
|
+ if (cookie.secure) cookieStr += `; Secure`;
|
|
|
|
|
+ if (cookie.httpOnly) cookieStr += `; HttpOnly`;
|
|
|
|
|
+ if (cookie.sameSite) cookieStr += `; SameSite=${cookie.sameSite}`;
|
|
|
|
|
+ cookieMap.set(cookie.key, cookieStr);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 合并授权响应中的 Cookie
|
|
|
authorizeCookies.forEach(cookie => {
|
|
authorizeCookies.forEach(cookie => {
|
|
|
const name = cookie.split('=')[0];
|
|
const name = cookie.split('=')[0];
|
|
|
cookieMap.set(name, cookie);
|
|
cookieMap.set(name, cookie);
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
uniqueCookies = Array.from(cookieMap.values());
|
|
uniqueCookies = Array.from(cookieMap.values());
|
|
|
|
|
|
|
|
console.log(`授权完成!最终获取到 ${uniqueCookies.length} 个唯一 Cookie`);
|
|
console.log(`授权完成!最终获取到 ${uniqueCookies.length} 个唯一 Cookie`);
|