first commit
This commit is contained in:
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
1
downKsVideo/.pydio
Normal file
1
downKsVideo/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
c91bfd8e-55d1-40b8-85d4-fdec0878e61b
|
||||||
151
downKsVideo/package-lock.json
generated
Normal file
151
downKsVideo/package-lock.json
generated
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
{
|
||||||
|
"name": "autoKuaiShou",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "autoKuaiShou",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"graphql": "^15.5.0",
|
||||||
|
"graphql-request": "^3.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cross-fetch": {
|
||||||
|
"version": "3.1.8",
|
||||||
|
"resolved": "https://registry.npmmirror.com/cross-fetch/-/cross-fetch-3.1.8.tgz",
|
||||||
|
"integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==",
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "^2.6.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/extract-files": {
|
||||||
|
"version": "9.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/extract-files/-/extract-files-9.0.0.tgz",
|
||||||
|
"integrity": "sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": "^10.17.0 || ^12.0.0 || >= 13.7.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jaydenseric"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/graphql": {
|
||||||
|
"version": "15.9.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/graphql/-/graphql-15.9.0.tgz",
|
||||||
|
"integrity": "sha512-GCOQdvm7XxV1S4U4CGrsdlEN37245eC8P9zaYCMr6K1BG0IPGy5lUwmJsEOGyl1GD6HXjOtl2keCP9asRBwNvA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/graphql-request": {
|
||||||
|
"version": "3.7.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/graphql-request/-/graphql-request-3.7.0.tgz",
|
||||||
|
"integrity": "sha512-dw5PxHCgBneN2DDNqpWu8QkbbJ07oOziy8z+bK/TAXufsOLaETuVO4GkXrbs0WjhdKhBMN3BkpN/RIvUHkmNUQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"cross-fetch": "^3.0.6",
|
||||||
|
"extract-files": "^9.0.0",
|
||||||
|
"form-data": "^3.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"graphql": "14 - 16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-fetch": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||||
|
},
|
||||||
|
"node_modules/webidl-conversions": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "~0.0.3",
|
||||||
|
"webidl-conversions": "^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
downKsVideo/package.json
Normal file
16
downKsVideo/package.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "autoKuaiShou",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "node --max-old-space-size=20240 src/index.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"graphql": "^15.5.0",
|
||||||
|
"graphql-request": "^3.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
downKsVideo/src/.pydio
Normal file
1
downKsVideo/src/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
528077e4-c137-4dbf-9206-1909e22cda73
|
||||||
1
downKsVideo/src/config/.pydio
Normal file
1
downKsVideo/src/config/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
4357a39c-dc52-4180-a387-00d44344f293
|
||||||
49
downKsVideo/src/config/config.js
Normal file
49
downKsVideo/src/config/config.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
const { gql } = require("graphql-request");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
url: "https://www.kuaishou.com/graphql",
|
||||||
|
// 获取收藏的视频
|
||||||
|
visionProfileLikePhotoList: gql`
|
||||||
|
fragment photoContent on PhotoEntity {
|
||||||
|
__typename
|
||||||
|
id duration
|
||||||
|
caption originCaption
|
||||||
|
likeCount viewCount commentCount realLikeCount
|
||||||
|
coverUrl photoUrl photoH265Url manifest
|
||||||
|
manifestH265 videoResource coverUrls
|
||||||
|
{
|
||||||
|
url __typename
|
||||||
|
}
|
||||||
|
timestamp expTag animatedCoverUrl distance
|
||||||
|
videoRatio liked stereoType profileUserTopPhoto
|
||||||
|
musicBlocked riskTagContent riskTagUrl
|
||||||
|
}
|
||||||
|
fragment recoPhotoFragment on recoPhotoEntity {
|
||||||
|
__typename id duration caption originCaption likeCount
|
||||||
|
viewCount commentCount realLikeCount coverUrl photoUrl photoH265Url manifest
|
||||||
|
manifestH265 videoResource coverUrls
|
||||||
|
{ url __typename }
|
||||||
|
timestamp expTag animatedCoverUrl distance videoRatio liked stereoType
|
||||||
|
profileUserTopPhoto musicBlocked riskTagContent riskTagUrl
|
||||||
|
}
|
||||||
|
fragment feedContent on Feed {
|
||||||
|
type author
|
||||||
|
{
|
||||||
|
id name headerUrl following headerUrls
|
||||||
|
{
|
||||||
|
url __typename
|
||||||
|
} __typename
|
||||||
|
} photo {
|
||||||
|
...photoContent ...recoPhotoFragment __typename
|
||||||
|
} canAddComment llsid status currentPcursor tags
|
||||||
|
{ type name __typename }
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
query visionProfileLikePhotoList($pcursor: String, $page: String, $webPageArea: String) {
|
||||||
|
visionProfileLikePhotoList(pcursor: $pcursor, page: $page, webPageArea: $webPageArea) {
|
||||||
|
result llsid webPageArea feeds {
|
||||||
|
...feedContent __typename
|
||||||
|
} hostName pcursor __typename
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
};
|
||||||
1
downKsVideo/src/http/.pydio
Normal file
1
downKsVideo/src/http/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
9aaa7146-0671-40e8-8c0b-15fb571f35a8
|
||||||
24
downKsVideo/src/http/http.js
Normal file
24
downKsVideo/src/http/http.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const { GraphQLClient } = require("graphql-request");
|
||||||
|
const config = require("../config/config");
|
||||||
|
|
||||||
|
class GraphqlRequest {
|
||||||
|
constructor() {
|
||||||
|
this.client = new GraphQLClient(config.url, {
|
||||||
|
headers: {
|
||||||
|
Cookie: 'context=1|MS43NjQ1ODM2OTgyODY2OTgyLjYxODQzOTUyLjE3MjQ2MTEwOTkyMjkuNDcyNDA=|MS43NjQ1ODM2OTgyODY2OTgyLjE5ODE4Mjk4LjE3MjQ2MTEwOTkyMjkuNDcyNDE=|0|graphql-server|webservice|false|NA; kpf=PC_WEB; clientid=3; did=web_017a1f82461c8b4dc575f8df3fee78ff; userId=58344290; kpn=KUAISHOU_VISION; kuaishou.server.web_st=ChZrdWFpc2hvdS5zZXJ2ZXIud2ViLnN0EqABVncUlGSoudMExPNRzEXf8OSAig_Gst7K3MuSob5dchyTwVVPrkPebW5B_ZMl9MFrNPRDjNhSpmGW_PABiZxH3bKuG7Cj-IaMhtEaaQiqs8ajQZfJ5CNjHKAmVizdwjF8qRnULuxZbrDwBE3BdzuUEDB-xE1ybAhhF0zQGz0LqzVDIT4ohfCbbzXfEZWCygEQCArtAgQB8sEooFEfLPXiwBoS5Uf6LooXNTrpm4StA9DDXaoVIiDqFSEmUkB66mi_cuqecbrHKQ5fBC5x74q1-WPiNNwCMSgFMAE;'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async post(document, variables) {
|
||||||
|
try {
|
||||||
|
let res = await this.client.request(document, variables);
|
||||||
|
return res.visionProfileLikePhotoList;
|
||||||
|
} catch (error) {
|
||||||
|
console.log("===========================请求出错===========================");
|
||||||
|
console.log(error.message);
|
||||||
|
console.log("===========================请求出错===========================");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new GraphqlRequest();
|
||||||
126
downKsVideo/src/index.js
Normal file
126
downKsVideo/src/index.js
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
const request = require("./http/http");
|
||||||
|
const config = require("./config/config");
|
||||||
|
const process = require('child_process');
|
||||||
|
const https = require("https");
|
||||||
|
const fs = require("fs");
|
||||||
|
const pr = require('node:process');
|
||||||
|
|
||||||
|
let TotalFeeds = [], TotalCount = 0, principalId = "空";
|
||||||
|
|
||||||
|
// 爬虫下载视频的速度过快(已做20秒延迟),快手服务器会突然断开连接,
|
||||||
|
// 从而http请求失效引发错误导致程序直接退出
|
||||||
|
// 这边从进程级捕获错误,避免程序中断,
|
||||||
|
// 下面的http error 部分会重新再次下载刚被拒绝的视频
|
||||||
|
// 同时数据太大,会导致超出nodejs的默认堆内存导致程序崩溃
|
||||||
|
// 所以启动时需要加上参数 --max-old-space-size=20240
|
||||||
|
pr.on('uncaughtException', err => {
|
||||||
|
console.log(' pr NOT exit...');
|
||||||
|
console.log(err.stack);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动下载快手主播主页所有视频和图片
|
||||||
|
*/
|
||||||
|
const main = async () => {
|
||||||
|
console.log("开始 ==============================================================");
|
||||||
|
console.log(`当前页码为:${principalId}`);
|
||||||
|
let { pcursor, feeds } = await request.post(config.visionProfileLikePhotoList, {
|
||||||
|
page: "profile",
|
||||||
|
pcursor: principalId
|
||||||
|
});
|
||||||
|
principalId = pcursor;
|
||||||
|
console.log(`本单次请求到:${feeds.length} 条视频`);
|
||||||
|
|
||||||
|
// console.log(`下一页页码为:${principalId}`);
|
||||||
|
TotalFeeds = [...TotalFeeds, ...feeds];
|
||||||
|
console.log("当前总视频数为:", TotalFeeds.length);
|
||||||
|
console.log("");
|
||||||
|
feeds.forEach(async item => {
|
||||||
|
await getVideoData(item.photo.photoUrl, 'binary', item.photo.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送get请求获取视频流数据
|
||||||
|
* @param {*} url 视频地址
|
||||||
|
* @param {*} encoding 获取的类型
|
||||||
|
* @param {*} filename 文件名
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const getVideoData = (url, encoding, filename) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let req = https.get(url, function (res) {
|
||||||
|
let result = ''
|
||||||
|
encoding && res.setEncoding(encoding)
|
||||||
|
res.on('data', function (d) {
|
||||||
|
result += d
|
||||||
|
})
|
||||||
|
res.on('end', function () {
|
||||||
|
savefileToPath(result, filename)
|
||||||
|
})
|
||||||
|
res.on('error', function (e) {
|
||||||
|
console.log("1 res.on 出错了:", e);
|
||||||
|
console.log("重新请求数据中:", url);
|
||||||
|
getVideoData(url, encoding, filename)
|
||||||
|
// reject(e)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
req.end()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存视频到本地
|
||||||
|
* @param {*} fileData 视频流数据
|
||||||
|
* @param {*} fileName 文件名
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const savefileToPath = async (fileData, fileName) => {
|
||||||
|
let fileFullName = `dist/${fileName}.mp4`;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.writeFile(fileFullName, fileData, 'binary', function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.log('savefileToPath error:', err)
|
||||||
|
} else {
|
||||||
|
TotalCount += 1;
|
||||||
|
console.log(`第 ${TotalCount} 条视频:${fileName}.mp4 已下载!`);
|
||||||
|
Screenshot(fileName)
|
||||||
|
if (TotalCount >= TotalFeeds.length) {
|
||||||
|
console.log("");
|
||||||
|
console.log(`已下载:${TotalFeeds.length} 条视频,20秒后将开始继续下载..`);
|
||||||
|
console.log("结束 ==============================================================");
|
||||||
|
setTimeout(() => {
|
||||||
|
main();
|
||||||
|
}, 10000)
|
||||||
|
}
|
||||||
|
resolve('已下载')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对下载的视频进行截图
|
||||||
|
* @param {*} fileName 视频文件名称
|
||||||
|
*/
|
||||||
|
const Screenshot = async (fileName) => {
|
||||||
|
var shell = `ffmpeg -ss 00:00:01 -i dist/${fileName}.mp4 -y -f image2 -frames:v 1 screenshot/${fileName}.jpg`;
|
||||||
|
console.log(`正在为视频: ${fileName}.mp4 进行截图:`);
|
||||||
|
process.execSync(shell);
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
// ffmpeg -ss 00:00:06 -i dist/video_1.mp4 -y -f image2 -frames:v 1 screenshot/sample.jpg
|
||||||
|
|
||||||
|
// 实际下载:1648
|
||||||
|
// 缺少1个视频
|
||||||
|
|
||||||
|
// 真实被收藏视频:1649
|
||||||
|
// 原因:计数器从1开始的,需要改成0
|
||||||
|
|
||||||
|
// document.querySelectorAll(".feed-list .feed-list-item").length 1680
|
||||||
|
// document.querySelectorAll(".feed-list .feed-list-item .multiple").length 3
|
||||||
|
// document.querySelectorAll(".feed-list .feed-list-item .vertical").length 26
|
||||||
|
// document.querySelectorAll(".feed-list .feed-list-item .single").length 2
|
||||||
1
ksAll/.pydio
Normal file
1
ksAll/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
147d1215-c158-49c2-9f80-d0a1da337dfe
|
||||||
1
ksAll/api/.pydio
Normal file
1
ksAll/api/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
49c02adf-94f0-41bf-aaae-090f830b99d9
|
||||||
288
ksAll/api/play.js
Normal file
288
ksAll/api/play.js
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
var express = require('express');
|
||||||
|
var router = express.Router();
|
||||||
|
const config = require("../config");
|
||||||
|
const GraphqlRequest = require("../http");
|
||||||
|
var request = require('request');
|
||||||
|
var axios = require('axios');
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对视频进行点赞
|
||||||
|
*/
|
||||||
|
|
||||||
|
router.get('/likeVideo', async function (req, res, next) {
|
||||||
|
let principalId = req.query.principalId;
|
||||||
|
let photoId = req.query.photoId;
|
||||||
|
let cancel = req.query.cancel;
|
||||||
|
let data = (await (new GraphqlRequest("kuaishou")).post(
|
||||||
|
config.likeVideo(principalId, photoId, cancel))
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("data: ", data);
|
||||||
|
// res.json({
|
||||||
|
// pcursor,
|
||||||
|
// list
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取主播首页的视频
|
||||||
|
* http://127.0.0.1:8090/api/publicFeeds?principalId=3xfjgnm9f89bave&count=200
|
||||||
|
*/
|
||||||
|
router.get('/publicFeeds', async function (req, res, next) {
|
||||||
|
let principalId = req.query.principalId;
|
||||||
|
let page = req.query.pcursor || "";
|
||||||
|
let count = req.query.count || "";
|
||||||
|
let { list, pcursor } = (await (new GraphqlRequest("kuaishou")).post(config.publicFeeds(principalId, page, count))).publicFeeds;
|
||||||
|
|
||||||
|
mkDir(principalId);
|
||||||
|
|
||||||
|
let imageNums = 0;
|
||||||
|
list.forEach(async (val, index) => {
|
||||||
|
console.log("val: ", val);
|
||||||
|
// if (val.useVideoPlayer == false) {
|
||||||
|
// imageNums += val.imgUrls.length
|
||||||
|
// console.log(val.id);
|
||||||
|
// DownImage(val)
|
||||||
|
// } else {
|
||||||
|
// getVideoData()
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
pcursor,
|
||||||
|
list
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频真实播放地址
|
||||||
|
*/
|
||||||
|
router.get('/play', async function (req, res, next) {
|
||||||
|
let principalId = req.query.principalId;
|
||||||
|
let photoId = req.query.photoId;
|
||||||
|
res.json((await (new GraphqlRequest()).post(config.feedById(principalId, photoId))).feedById.currentWork);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频相关的推荐
|
||||||
|
* http://127.0.0.1:8090/api/RecommendFeeds?photoId=3xhzfvheg3wpsra
|
||||||
|
*/
|
||||||
|
router.get('/RecommendFeeds', async function (req, res, next) {
|
||||||
|
let photoId = req.query.photoId;
|
||||||
|
let result = await (new GraphqlRequest()).post(config.RecommendFeeds(photoId))
|
||||||
|
res.json(result.videoRecommendFeeds);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取评论
|
||||||
|
* http://127.0.0.1:8090/api/CommentList?photoId=3xz5ue2p6betigm
|
||||||
|
*/
|
||||||
|
router.get('/CommentList', async function (req, res, next) {
|
||||||
|
let photoId = req.query.photoId;
|
||||||
|
let pcursor = req.query.pcursor || "";
|
||||||
|
res.json((await (new GraphqlRequest()).post(config.shortVideoCommentList(photoId, pcursor))).shortVideoCommentList);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取评论展开
|
||||||
|
* http://127.0.0.1:8090/api/subCommentList?photoId=3xz5ue2p6betigm&rootCommentId=225526137195
|
||||||
|
*/
|
||||||
|
router.get('/subCommentList', async function (req, res, next) {
|
||||||
|
let photoId = req.query.photoId;
|
||||||
|
let rootCommentId = req.query.rootCommentId;
|
||||||
|
let pcursor = req.query.pcursor || "";
|
||||||
|
let ddd = (await (new GraphqlRequest()).post(config.subCommentList(photoId, rootCommentId, pcursor))).subCommentList
|
||||||
|
res.json(ddd);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快手官网,获取无水印视频!!
|
||||||
|
* http://127.0.0.1:8090/api/likeList
|
||||||
|
*/
|
||||||
|
router.get('/likeList', async function (req, res, next) {
|
||||||
|
let pcursor = req.query.pcursor || "";
|
||||||
|
let rea = await (new GraphqlRequest("kuaishou")).post(config.visionProfileLikePhotoList, {
|
||||||
|
page: "profile",
|
||||||
|
pcursor: pcursor
|
||||||
|
});
|
||||||
|
res.json(rea.visionProfileLikePhotoList);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取收藏的视频
|
||||||
|
* http://127.0.0.1:8090/api/list
|
||||||
|
*/
|
||||||
|
router.get('/list', async function (req, res, next) {
|
||||||
|
let page = req.query.page || "";
|
||||||
|
res.json({
|
||||||
|
data: (await (new GraphqlRequest("kuaishou")).post(config.likedFeeds(page))).likedFeeds
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不是接口,主要作用是为了循环下载主播主页的视频和图片到本地
|
||||||
|
* 用的新接口,下载的是无水印的视频
|
||||||
|
* http://127.0.0.1:8090/api/down?userId=3xfjgnm9f89bave
|
||||||
|
*/
|
||||||
|
router.get('/down', async function (req, res, next) {
|
||||||
|
let userId = req.query.userId;
|
||||||
|
let pcursors = req.query.pcursor || '';
|
||||||
|
|
||||||
|
|
||||||
|
// https://live.kuaishou.com/live_api/profile/public?count=157&pcursor=&principalId=3xfjgnm9f89bave&hasMore=true
|
||||||
|
let result = await axios.get(config.visionProfilePhotoList, {
|
||||||
|
params: {
|
||||||
|
count: 220,
|
||||||
|
pcursor: pcursors,
|
||||||
|
principalId: userId,
|
||||||
|
hasMore: true
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
"Cookie": `did=web_45504037cde380da3c57769b2726e6f2`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let list = result.data.data.list;
|
||||||
|
// 创建文件夹
|
||||||
|
mkDir(userId)
|
||||||
|
|
||||||
|
list.forEach(async (val, index) => {
|
||||||
|
// 如果遇到 图片
|
||||||
|
if (val.workType == 'vertical' || val.workType == 'multiple' || val.workType == 'single') {
|
||||||
|
DownImage(val)
|
||||||
|
} else {
|
||||||
|
console.log(val.id);
|
||||||
|
DownMp4(val.playUrl,val.id, userId)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// res.json(list);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送get请求获取视频流数据
|
||||||
|
* @param {*} url 视频地址
|
||||||
|
* @param {*} encoding 获取的类型
|
||||||
|
* @param {*} filename 文件名
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const getVideoData = (url, encoding, filename) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let req = https.get(url, function (res) {
|
||||||
|
let result = ''
|
||||||
|
encoding && res.setEncoding(encoding)
|
||||||
|
res.on('data', function (d) {
|
||||||
|
result += d
|
||||||
|
})
|
||||||
|
res.on('end', function () {
|
||||||
|
savefileToPath(result, filename)
|
||||||
|
})
|
||||||
|
res.on('error', function (e) {
|
||||||
|
console.log("1 res.on 出错了:", e);
|
||||||
|
console.log("重新请求数据中:", url);
|
||||||
|
getVideoData(url, encoding, filename)
|
||||||
|
// reject(e)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
req.end()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存视频到本地
|
||||||
|
* @param {*} fileData 视频流数据
|
||||||
|
* @param {*} fileName 文件名
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const savefileToPath = async (fileData, fileName) => {
|
||||||
|
let fileFullName = `dist/${fileName}.mp4`;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.writeFile(fileFullName, fileData, 'binary', function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.log('savefileToPath error:', err)
|
||||||
|
} else {
|
||||||
|
TotalCount += 1;
|
||||||
|
console.log(`第 ${TotalCount} 条视频:${fileName}.mp4 已下载!`);
|
||||||
|
Screenshot(fileName)
|
||||||
|
if (TotalCount >= TotalFeeds.length) {
|
||||||
|
console.log("");
|
||||||
|
console.log(`已下载:${TotalFeeds.length} 条视频,20秒后将开始继续下载..`);
|
||||||
|
console.log("结束 ==============================================================");
|
||||||
|
setTimeout(() => {
|
||||||
|
main();
|
||||||
|
}, 10000)
|
||||||
|
}
|
||||||
|
resolve('已下载')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// *
|
||||||
|
// * @param {*} url 视频下载地址
|
||||||
|
// * @param {*} id 视频文件名
|
||||||
|
// * @param {*} page 用主播id作为文件夹名称
|
||||||
|
// */
|
||||||
|
// function DownMp4(url, id, page) {
|
||||||
|
// var options = {
|
||||||
|
// url: url,
|
||||||
|
// headers: {
|
||||||
|
// 'Upgrade-Insecure-Requests': '1',
|
||||||
|
// 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36',
|
||||||
|
// "Cookie": `did=web_45504037cde380da3c57769b2726e6f2`
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// //发送get请求下载视频
|
||||||
|
// request(options)
|
||||||
|
// .on('error', function (err) {
|
||||||
|
// console.log(err)
|
||||||
|
// })
|
||||||
|
// // 将视频写入到mp4文件夹里
|
||||||
|
// .pipe(fs.createWriteStream(`mp4/${page}/${id}.mp4`))
|
||||||
|
// }
|
||||||
|
|
||||||
|
function DownImage(data) {
|
||||||
|
let promise = Promise.resolve();
|
||||||
|
data.imgUrls.forEach((val, ix) => {
|
||||||
|
promise = promise.then(() => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(async () => {
|
||||||
|
let index = val.indexOf("atlas/") + 6
|
||||||
|
var options = {
|
||||||
|
url: val,
|
||||||
|
headers: { 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36' }
|
||||||
|
};
|
||||||
|
//发送get请求下载视频
|
||||||
|
request(options)
|
||||||
|
.on('error', function (err) {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
// 将视频写入到mp4文件夹里
|
||||||
|
.pipe(fs.createWriteStream(`mp4/${data.author.id}/${val.substring(index)}`))
|
||||||
|
|
||||||
|
resolve('成功');
|
||||||
|
|
||||||
|
}, 1)
|
||||||
|
})
|
||||||
|
.then(val => {
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建文件夹
|
||||||
|
* @param {*} page
|
||||||
|
*/
|
||||||
|
function mkDir(page) {
|
||||||
|
fs.mkdir(`mp4/${page}`, 0777, err => err ? console.log(err) : console.log('文件夹创建成功'))
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
57
ksAll/app.js
Normal file
57
ksAll/app.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
var createError = require('http-errors');
|
||||||
|
var express = require('express');
|
||||||
|
var path = require('path');
|
||||||
|
var cookieParser = require('cookie-parser');
|
||||||
|
var lessMiddleware = require('less-middleware');
|
||||||
|
var logger = require('morgan');
|
||||||
|
|
||||||
|
var indexRouter = require('./routes/index');
|
||||||
|
var playApi = require('./api/play');
|
||||||
|
|
||||||
|
var app = express();
|
||||||
|
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
res.header("Access-Control-Allow-Credentials",true);
|
||||||
|
res.header('Access-Control-Allow-Origin', '*');
|
||||||
|
res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, OPTIONS') ;
|
||||||
|
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With')
|
||||||
|
|
||||||
|
if (req.method == 'OPTIONS') {
|
||||||
|
res.sendStatus(200)
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// view engine setup
|
||||||
|
app.set('views', path.join(__dirname, 'views'));
|
||||||
|
app.set('view engine', 'pug');
|
||||||
|
|
||||||
|
app.use(logger('dev'));
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.urlencoded({ extended: false }));
|
||||||
|
app.use(cookieParser());
|
||||||
|
app.use(lessMiddleware(path.join(__dirname, 'public')));
|
||||||
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
|
||||||
|
app.use('/', indexRouter);
|
||||||
|
|
||||||
|
app.use('/api', playApi);
|
||||||
|
|
||||||
|
// catch 404 and forward to error handler
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
next(createError(404));
|
||||||
|
});
|
||||||
|
|
||||||
|
// error handler
|
||||||
|
app.use(function(err, req, res, next) {
|
||||||
|
// set locals, only providing error in development
|
||||||
|
res.locals.message = err.message;
|
||||||
|
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||||
|
|
||||||
|
// render the error page
|
||||||
|
res.status(err.status || 500);
|
||||||
|
res.render('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
1
ksAll/bin/.pydio
Normal file
1
ksAll/bin/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
69ee9bf0-611c-4b18-a9fa-8c82a7d43d3d
|
||||||
87
ksAll/bin/www
Executable file
87
ksAll/bin/www
Executable file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var app = require('../app');
|
||||||
|
var debug = require('debug')('kuaishou:server');
|
||||||
|
var http = require('http');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get port from environment and store in Express.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var port = normalizePort(process.env.PORT || '8090');
|
||||||
|
app.set('port', port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create HTTP server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var server = http.createServer(app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen on provided port, on all network interfaces.
|
||||||
|
*/
|
||||||
|
|
||||||
|
server.listen(port);
|
||||||
|
server.on('error', onError);
|
||||||
|
server.on('listening', onListening);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a port into a number, string, or false.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function normalizePort(val) {
|
||||||
|
var port = parseInt(val, 10);
|
||||||
|
|
||||||
|
if (isNaN(port)) {
|
||||||
|
// named pipe
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port >= 0) {
|
||||||
|
// port number
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for HTTP server "error" event.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onError(error) {
|
||||||
|
if (error.syscall !== 'listen') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bind = typeof port === 'string'
|
||||||
|
? 'Pipe ' + port
|
||||||
|
: 'Port ' + port;
|
||||||
|
|
||||||
|
// handle specific listen errors with friendly messages
|
||||||
|
switch (error.code) {
|
||||||
|
case 'EACCES':
|
||||||
|
console.error(bind + ' requires elevated privileges');
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
case 'EADDRINUSE':
|
||||||
|
console.error(bind + ' is already in use');
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for HTTP server "listening" event.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onListening() {
|
||||||
|
var addr = server.address();
|
||||||
|
console.log(`App is Run: http://127.0.0.1:8090`)
|
||||||
|
}
|
||||||
1
ksAll/config/.pydio
Normal file
1
ksAll/config/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0a757647-86fa-49d6-8a71-a8c49600cda3
|
||||||
235
ksAll/config/index.js
Normal file
235
ksAll/config/index.js
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
const { gql } = require("graphql-request");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// 快手直播,视频有版权
|
||||||
|
url: 'https://live.kuaishou.com/live_graphql',
|
||||||
|
// 快手官方 视频无版权
|
||||||
|
kuaishouUrl: 'https://www.kuaishou.com/graphql',
|
||||||
|
|
||||||
|
|
||||||
|
// 没有水印的 获取主播主页的视频
|
||||||
|
visionProfilePhotoList: `https://live.kuaishou.com/live_api/profile/public`,
|
||||||
|
|
||||||
|
// 没有水印的 你点赞的视频
|
||||||
|
visionProfileLikePhotoList: gql`
|
||||||
|
fragment photoContent on PhotoEntity {
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
duration
|
||||||
|
caption
|
||||||
|
originCaption
|
||||||
|
likeCount
|
||||||
|
viewCount
|
||||||
|
commentCount
|
||||||
|
realLikeCount
|
||||||
|
coverUrl
|
||||||
|
photoUrl
|
||||||
|
photoH265Url
|
||||||
|
manifest
|
||||||
|
manifestH265
|
||||||
|
videoResource
|
||||||
|
coverUrls {
|
||||||
|
url
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
timestamp
|
||||||
|
expTag
|
||||||
|
animatedCoverUrl
|
||||||
|
distance
|
||||||
|
videoRatio
|
||||||
|
liked
|
||||||
|
stereoType
|
||||||
|
profileUserTopPhoto
|
||||||
|
musicBlocked
|
||||||
|
}
|
||||||
|
fragment recoPhotoFragment on recoPhotoEntity {
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
duration
|
||||||
|
caption
|
||||||
|
originCaption
|
||||||
|
likeCount
|
||||||
|
viewCount
|
||||||
|
commentCount
|
||||||
|
realLikeCount
|
||||||
|
coverUrl
|
||||||
|
photoUrl
|
||||||
|
photoH265Url
|
||||||
|
manifest
|
||||||
|
manifestH265
|
||||||
|
videoResource
|
||||||
|
coverUrls {
|
||||||
|
url
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
timestamp
|
||||||
|
expTag
|
||||||
|
animatedCoverUrl
|
||||||
|
distance
|
||||||
|
videoRatio
|
||||||
|
liked
|
||||||
|
stereoType
|
||||||
|
profileUserTopPhoto
|
||||||
|
musicBlocked
|
||||||
|
}
|
||||||
|
fragment feedContent on Feed {
|
||||||
|
type
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
headerUrl
|
||||||
|
following
|
||||||
|
headerUrls {
|
||||||
|
url
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
photo {
|
||||||
|
...photoContent
|
||||||
|
...recoPhotoFragment
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
canAddComment
|
||||||
|
llsid
|
||||||
|
status
|
||||||
|
currentPcursor
|
||||||
|
tags {
|
||||||
|
type
|
||||||
|
name
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
query visionProfileLikePhotoList(
|
||||||
|
$pcursor: String
|
||||||
|
$page: String
|
||||||
|
$webPageArea: String
|
||||||
|
) {
|
||||||
|
visionProfileLikePhotoList(
|
||||||
|
pcursor: $pcursor
|
||||||
|
page: $page
|
||||||
|
webPageArea: $webPageArea
|
||||||
|
) {
|
||||||
|
result
|
||||||
|
llsid
|
||||||
|
webPageArea
|
||||||
|
feeds {
|
||||||
|
...feedContent
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
hostName
|
||||||
|
pcursor
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
|
||||||
|
// 获取展开的评论
|
||||||
|
subCommentList: (photoId, rootCommentId, pcursor = "") => {
|
||||||
|
return `
|
||||||
|
{
|
||||||
|
subCommentList(photoId: "${photoId}", rootCommentId: "${rootCommentId}", pcursor: "${pcursor}", count: 20) {
|
||||||
|
pcursor
|
||||||
|
subCommentsList {
|
||||||
|
commentId,authorId,authorName,content
|
||||||
|
headurl,timestamp,authorEid,status,replyToUserName,replyTo,
|
||||||
|
replyToEid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
},
|
||||||
|
// 获取视频相关的推荐
|
||||||
|
RecommendFeeds: (photoId) => {
|
||||||
|
return `
|
||||||
|
{
|
||||||
|
videoRecommendFeeds(photoId: "${photoId}", count: 40) {
|
||||||
|
list {
|
||||||
|
user {
|
||||||
|
id,avatar,name
|
||||||
|
},
|
||||||
|
id,thumbnailUrl,poster,workType,type,useVideoPlayer,imgUrls,
|
||||||
|
imgSizes,magicFace,musicName,caption,location,liked,onlyFollowerCanComment,
|
||||||
|
relativeHeight,timestamp,width,height,
|
||||||
|
counts {
|
||||||
|
displayView,displayLike,displayComment
|
||||||
|
},
|
||||||
|
expTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
},
|
||||||
|
// 获取默认评论
|
||||||
|
shortVideoCommentList: (photoId, pcursor = "") => {
|
||||||
|
return `{
|
||||||
|
shortVideoCommentList(photoId: "${photoId}", page: 1, pcursor: "${pcursor}", count: 100) {
|
||||||
|
commentCount,realCommentCount,pcursor,
|
||||||
|
commentList {
|
||||||
|
commentId,authorId,authorName,content,headurl,timestamp,authorEid,status,
|
||||||
|
subCommentCount,subCommentsPcursor,likedCount,liked,
|
||||||
|
subComments {
|
||||||
|
commentId,authorId,authorName,content,headurl,timestamp,authorEid
|
||||||
|
status,replyToUserName,replyTo,replyToEid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
},
|
||||||
|
// 喜欢页面 请求参数
|
||||||
|
likedFeeds: (pcursor = "") => {
|
||||||
|
return `{
|
||||||
|
likedFeeds(principalId: "geekhelp", pcursor: "${pcursor}", count: 42) {
|
||||||
|
pcursor
|
||||||
|
list {
|
||||||
|
id,thumbnailUrl,poster,workType,type,useVideoPlayer,imgUrls,imgSizes,magicFace,
|
||||||
|
musicName,caption,location,liked,onlyFollowerCanComment,relativeHeight,timestamp,width,height,
|
||||||
|
counts {
|
||||||
|
displayView,displayLike,displayComment
|
||||||
|
},
|
||||||
|
user {
|
||||||
|
id,eid,name,avatar,
|
||||||
|
},
|
||||||
|
expTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
},
|
||||||
|
// 视频真实播放地址 请求参数
|
||||||
|
feedById: (principalId, photoId) => {
|
||||||
|
return `{
|
||||||
|
feedById(principalId: "${principalId}", photoId: "${photoId}") {
|
||||||
|
currentWork {
|
||||||
|
playUrl,poster,caption
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
publicFeeds: (principalId, pcursor = "", count = 24) => {
|
||||||
|
return `{
|
||||||
|
publicFeeds(principalId: "${principalId}", pcursor: "${pcursor}", count: ${count}) {
|
||||||
|
pcursor,
|
||||||
|
list {
|
||||||
|
id,thumbnailUrl,poster,workType,type,useVideoPlayer,imgUrls,imgSizes,magicFace,musicName,
|
||||||
|
caption,location,liked,onlyFollowerCanComment,relativeHeight,timestamp,width,height,
|
||||||
|
counts {
|
||||||
|
displayView,displayLike,displayComment
|
||||||
|
},
|
||||||
|
user {
|
||||||
|
id,eid,name,avatar,cityName
|
||||||
|
},
|
||||||
|
expTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
},
|
||||||
|
likeVideo: (principalId, photoId, cancel) => {
|
||||||
|
return `
|
||||||
|
{
|
||||||
|
visionVideoLike(photoId: "${photoId}", photoAuthorId: "${principalId}", cancel: ${cancel}, expTag: "1_i/2001342433838605970_xpcwebprofilexxnull0") {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
1
ksAll/http/.pydio
Normal file
1
ksAll/http/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
570a0730-7f5e-4356-9111-ba94a852c40e
|
||||||
32
ksAll/http/index.js
Normal file
32
ksAll/http/index.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
const { GraphQLClient } = require("graphql-request");
|
||||||
|
const config = require("../config/index");
|
||||||
|
|
||||||
|
class GraphqlRequest {
|
||||||
|
constructor(type = "live") {
|
||||||
|
|
||||||
|
// 快手直播
|
||||||
|
if (type == "live") {
|
||||||
|
this.url = config.url;
|
||||||
|
this.Cookie = `
|
||||||
|
did=web_fca770657d0a24abb53ddc92bb42e612; userId=58344290; didv=1700407978000; clientid=3; did=web_fca770657d0a24abb53ddc92bb42e612; client_key=65890b29; kpn=GAME_ZONE; kuaishou.live.bfb1s=7206d814e5c089a58c910ed8bf52ace5; kuaishou.live.web_st=ChRrdWFpc2hvdS5saXZlLndlYi5zdBKgAadO1AwZrrDjDVApuLOcmaVq4X_RXVF5KJZvdJVZO9n7cf4DDin4xZvgIOyepAgwiLJeBEfdxb9_H7iugru6VP2LBB47UUFCGLjPgZXHEStjjagw362F9Yi4JFbIIWNGYsdiRryj9OEPT5PjTOszl3zVcQQ6XPZr2g2lHnCYvS6MWN2cAboiME0uj9spofgVv1dFvzJCzrDUwiVTnchKaHIaEurKvNghXkU0vIm_W_UXPUfuwSIgd-2ItB4wzvCYphOA8BDDJTvmcWYWbseKriH_-Y5S3K8oBTAB; kuaishou.live.web_ph=30510c6ff3d99c967bd2e315e8a2f380ede5; userId=58344290`
|
||||||
|
|
||||||
|
// 快手官网
|
||||||
|
} else {
|
||||||
|
this.url = config.kuaishouUrl;
|
||||||
|
this.Cookie = `kpf=PC_WEB; clientid=3; did=web_45504037cde380da3c57769b2726e6f2; kpn=KUAISHOU_VISION; userId=58344290; kuaishou.server.web_st=ChZrdWFpc2hvdS5zZXJ2ZXIud2ViLnN0EqABGBVneqKw-P0F43gl7XIJeMC3Hw86Wtx2hf_KyD7-v5KlsAi95M2EB6G8m0OGY1r-Hln5hG__IL9_0FW60aF3q-xSLPa6-Fn6jmxfOEuJGgcKyPv95zvJ6fR2cJDi6k_fN3dk1V6I7g3Br_UZqqELrUHfVcZGQfiQQ-GlKMLHesdumI1lktCPQqpQg0P9G3VIK9TkDRPuTZ1ZD28YA7DWaxoSfVpfCMyKId3v5a5yUz8_gvvmIiBVEf98RjrMmTM0XEteAo1RCcy9opqKVWs4ktXA69FPNSgFMAE; kuaishou.server.web_ph=284969ffdaacafbd765a34bf2e0df1cc274d`
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client = new GraphQLClient(this.url, {
|
||||||
|
headers: {
|
||||||
|
Cookie: this.Cookie,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async post(document, variables) {
|
||||||
|
return await this.client.request(document, variables)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = GraphqlRequest;
|
||||||
1
ksAll/mp4/.pydio
Normal file
1
ksAll/mp4/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
e1d29bc7-cec6-4fd5-b9a4-c408417c449f
|
||||||
3034
ksAll/package-lock.json
generated
Normal file
3034
ksAll/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
ksAll/package.json
Normal file
22
ksAll/package.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "kuaishou",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "node-dev ./bin/www",
|
||||||
|
"test": "node test.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.21.4",
|
||||||
|
"cookie-parser": "^1.4.5",
|
||||||
|
"debug": "^4.3.1",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"graphql": "^15.4.0",
|
||||||
|
"graphql-request": "^3.3.0",
|
||||||
|
"http-errors": "^1.8.1",
|
||||||
|
"less-middleware": "^3.1.0",
|
||||||
|
"morgan": "^1.10.0",
|
||||||
|
"pug": "^3.0.0",
|
||||||
|
"request": "^2.88.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
ksAll/public/.pydio
Normal file
1
ksAll/public/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
34772b88-49a3-4114-a9d4-1c185188e99e
|
||||||
1
ksAll/public/images/.pydio
Normal file
1
ksAll/public/images/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
8f4c1da5-8f6e-4eba-b406-0f2771bc6df3
|
||||||
1
ksAll/public/javascripts/.pydio
Normal file
1
ksAll/public/javascripts/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1fd0ed1e-e902-4979-a707-36d7e75c863e
|
||||||
155
ksAll/public/javascripts/index.js
Normal file
155
ksAll/public/javascripts/index.js
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
let pageNum = "";
|
||||||
|
|
||||||
|
function checkAjax() {
|
||||||
|
if (location.search.length == 0) {
|
||||||
|
getVideoList();
|
||||||
|
} else {
|
||||||
|
getRecommendFeeds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取和 photoId 相似的推荐数据
|
||||||
|
*/
|
||||||
|
function getRecommendFeeds() {
|
||||||
|
Http("/RecommendFeeds", {
|
||||||
|
photoId: location.search.split("=")[1]
|
||||||
|
}, res => {
|
||||||
|
let { list } = res;
|
||||||
|
console.log(list);
|
||||||
|
list.forEach(function (val, index) {
|
||||||
|
RenderList(val);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送ajax请求被收藏的数据
|
||||||
|
*/
|
||||||
|
function getVideoList() {
|
||||||
|
Http("/list", { page: pageNum }, res => {
|
||||||
|
let MMList = res.data;
|
||||||
|
pageNum = MMList.pcursor;
|
||||||
|
MMList.list.forEach(function (val, index) {
|
||||||
|
RenderList(val);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function RenderList(val) {
|
||||||
|
$("ul").append(`
|
||||||
|
<li class="list">
|
||||||
|
<div class="back">
|
||||||
|
<div class="zz" style="background: url('${val.thumbnailUrl}'); background-position: center center;"></div>
|
||||||
|
<img src="${val.thumbnailUrl}"/>
|
||||||
|
</div>
|
||||||
|
<div class="list-bottom">
|
||||||
|
<div class="bottom-info">
|
||||||
|
<a target="_blank" title="${val.user.id}" href="https://live.kuaishou.com/profile/${val.user.id}">
|
||||||
|
<img class="avatar" src="${val.user.avatar}"/>
|
||||||
|
<span class="title">${val.caption}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="pid">${val.id}</div>
|
||||||
|
<div class="action" principalId="${val.user.id}" photoId="${val.id}">
|
||||||
|
<a class="down">下载</a>
|
||||||
|
<a class="play">播放</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
`);
|
||||||
|
|
||||||
|
BindPlayClick();
|
||||||
|
BindDownClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 播放视频
|
||||||
|
function BindPlayClick() {
|
||||||
|
document.querySelectorAll(".play").forEach((val, index) => {
|
||||||
|
val.onclick = function () {
|
||||||
|
Http('/play', {
|
||||||
|
principalId: $(val).parent().attr("principalId"),
|
||||||
|
photoId: $(val).parent().attr("photoId")
|
||||||
|
}, url => {
|
||||||
|
$(".video video").attr("src", url.playUrl);
|
||||||
|
$(".video").slideDown("slow");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载视频
|
||||||
|
function BindDownClick() {
|
||||||
|
|
||||||
|
document.querySelectorAll(".down").forEach((val, index) => {
|
||||||
|
val.onclick = function () {
|
||||||
|
Http('/play', {
|
||||||
|
principalId: $(val).parent().attr("principalId"),
|
||||||
|
photoId: $(val).parent().attr("photoId")
|
||||||
|
}, url => {
|
||||||
|
downMp4(url.playUrl, $(val).parent().attr("photoId"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 滚动加载数据
|
||||||
|
$(window).scroll(function () {
|
||||||
|
var scrollTop = $(this).scrollTop();
|
||||||
|
var scrollHeight = $(document).height();
|
||||||
|
var windowHeight = $(this).height();
|
||||||
|
console.log("scrollTop: ", scrollTop);
|
||||||
|
console.log("windowHeight: ", windowHeight);
|
||||||
|
console.log("scrollHeight: ", scrollHeight);
|
||||||
|
console.log("scrollTop + windowHeight: ", scrollTop + windowHeight);
|
||||||
|
if (Number((scrollTop + windowHeight).toFixed()) == scrollHeight) {
|
||||||
|
console.log("到底了,发起请求")
|
||||||
|
checkAjax()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 关闭视频播放弹窗
|
||||||
|
$(".close").click(function () {
|
||||||
|
$("video").get(0).pause();
|
||||||
|
$(".video video").attr("src", "");
|
||||||
|
$(".video").slideUp("slow");
|
||||||
|
});
|
||||||
|
|
||||||
|
// video 父元素被悬浮就开始视频播放
|
||||||
|
// 以实现不静音而自动播放
|
||||||
|
$(".video").mouseover(function () {
|
||||||
|
$("video").get(0).play()
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// 发送ajax请求
|
||||||
|
function Http(URL, params, callback) {
|
||||||
|
$.ajax({
|
||||||
|
url: `http://127.0.0.1:8090/api${URL}`,
|
||||||
|
type: 'GET',
|
||||||
|
data: params,
|
||||||
|
success: (res) => {
|
||||||
|
callback(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载MP4视频
|
||||||
|
function downMp4(urls, photoId) {
|
||||||
|
fetch(urls).then(res => res.blob()).then(blob => {
|
||||||
|
const a = document.createElement('a');
|
||||||
|
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.style.display = 'none';
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
a.href = url;
|
||||||
|
a.download = `bmy-${photoId}.mp4`;
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAjax();
|
||||||
30
ksAll/public/javascripts/recommend.js
Normal file
30
ksAll/public/javascripts/recommend.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
class recommend {
|
||||||
|
constructor() {
|
||||||
|
this.getRecommendFeeds();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
getRecommendFeeds() {
|
||||||
|
this.Http("/RecommendFeeds", {
|
||||||
|
photoId: location.search.split("=")[1]
|
||||||
|
}, res => {
|
||||||
|
let { list } = res;
|
||||||
|
console.log(list);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Http(URL, params, callback) {
|
||||||
|
$.ajax({
|
||||||
|
url: `http://127.0.0.1:8090/api${URL}`,
|
||||||
|
type: 'GET',
|
||||||
|
data: params,
|
||||||
|
success: (res) => {
|
||||||
|
callback(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
new recommend();
|
||||||
1
ksAll/public/javascripts/utils/.pydio
Normal file
1
ksAll/public/javascripts/utils/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0f1f1ba9-af22-4072-9da3-28317b98af52
|
||||||
7
ksAll/public/javascripts/utils/index.js
Normal file
7
ksAll/public/javascripts/utils/index.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
class Utils {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var {
|
||||||
|
|
||||||
|
} = new Utils();
|
||||||
1
ksAll/public/stylesheets/.pydio
Normal file
1
ksAll/public/stylesheets/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
45365c93-931b-44bb-92e0-47b88e544036
|
||||||
1
ksAll/public/stylesheets/index.css
Normal file
1
ksAll/public/stylesheets/index.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ul{display:flex;overflow:hidden;justify-content:space-around;flex-wrap:wrap}.video{text-align:center;background:rgba(0,0,0,0.87843137);width:100%;height:100%;position:fixed;z-index:9;display:none}.video .close{width:60px;height:60px;background:rgba(255,255,255,0.61176471);border-radius:100%;color:#615e5e;text-align:center;line-height:55px;position:absolute;right:20px;top:30px;font-size:31px;cursor:pointer}.list{width:15%;margin:10px 10px 10px 10px;background:#fff}.list .back{width:100%;height:178px;text-align:center;position:relative;overflow:hidden}.list .back .zz{position:absolute;width:100%;height:100%;z-index:2;background:#000;filter:blur(10px)}.list .back img{width:103px;height:178px;position:absolute;z-index:3;left:50%;margin-left:-51.5px}.list .list-bottom{padding:10px 5px 5px 5px;box-sizing:border-box}.list .list-bottom .action{display:flex;justify-content:space-between;height:36px;align-items:center;padding:3px 5px 0 5px;color:#b7b7b7;font-size:13px}.list .list-bottom .action a{cursor:pointer}.list .list-bottom .pid{font-size:16px;margin:10px 0;color:#8e8d8d}.list .list-bottom .bottom-info a{display:flex;justify-content:flex-start;align-items:center;color:#bababa}.list .list-bottom .bottom-info a .avatar{width:35px;height:35px;border-radius:100%}.list .list-bottom .bottom-info a .title{font-size:13px;width:230px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;margin-left:10px}
|
||||||
101
ksAll/public/stylesheets/index.less
Normal file
101
ksAll/public/stylesheets/index.less
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
ul {
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
justify-content: space-around;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video {
|
||||||
|
text-align: center;
|
||||||
|
background: rgba(0, 0, 0, 0.8784313725490196);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 9;
|
||||||
|
display: none;
|
||||||
|
.close {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
background: rgba(255, 255, 255, 0.611764705882353);
|
||||||
|
border-radius: 100%;
|
||||||
|
color: #615e5e;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 55px;
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
top: 30px;
|
||||||
|
font-size: 31px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
width: 15%;
|
||||||
|
margin: 10px 10px 10px 10px;
|
||||||
|
background: #fff;
|
||||||
|
.back {
|
||||||
|
width: 100%;
|
||||||
|
height: 178px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
.zz {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
background: #000;
|
||||||
|
filter: blur(10px);
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
width: 103px;
|
||||||
|
height: 178px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 3;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -103px / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.list-bottom {
|
||||||
|
padding: 10px 5px 5px 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
.action {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 36px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 3px 5px 0 5px;
|
||||||
|
color: #b7b7b7;
|
||||||
|
font-size: 13px;
|
||||||
|
a {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pid {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 10px 0;
|
||||||
|
color: #8e8d8d;
|
||||||
|
}
|
||||||
|
.bottom-info {
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
color: #bababa;
|
||||||
|
.avatar {
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 13px;
|
||||||
|
width: 230px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow:ellipsis;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
0
ksAll/public/stylesheets/recommend.css
Normal file
0
ksAll/public/stylesheets/recommend.css
Normal file
0
ksAll/public/stylesheets/recommend.less
Normal file
0
ksAll/public/stylesheets/recommend.less
Normal file
1
ksAll/public/stylesheets/style.css
Normal file
1
ksAll/public/stylesheets/style.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
body,ul,li,p,a{margin:0;padding:0;list-style:none;text-decoration:none}body{background:#f5f5f5}body,html{width:100%;height:100%}video{outline:none}
|
||||||
18
ksAll/public/stylesheets/style.less
Normal file
18
ksAll/public/stylesheets/style.less
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
body, ul,li,p,a{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
body,html {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
video {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
1
ksAll/routes/.pydio
Normal file
1
ksAll/routes/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
a3db7e41-c64b-45ef-a225-7840515358a8
|
||||||
15
ksAll/routes/index.js
Normal file
15
ksAll/routes/index.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
var express = require('express');
|
||||||
|
var router = express.Router();
|
||||||
|
const config = require("../config");
|
||||||
|
const GraphqlRequest = require("../http");
|
||||||
|
|
||||||
|
router.get('/', async (req, res, next) => {
|
||||||
|
let page = req.query.page || "";
|
||||||
|
res.render('index');
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/recommend', async (req, res, next) => {
|
||||||
|
res.render('recommend');
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
138
ksAll/test.js
Normal file
138
ksAll/test.js
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
const { GraphQLClient, gql } = require("graphql-request");
|
||||||
|
|
||||||
|
let endpoint = "https://www.kuaishou.com/graphql"
|
||||||
|
|
||||||
|
|
||||||
|
const client = new GraphQLClient(endpoint,{
|
||||||
|
headers: {
|
||||||
|
Cookie: `kpf=PC_WEB; clientid=3; did=web_fca770657d0a24abb53ddc92bb42e612; userId=58344290; kuaishou.server.web_st=ChZrdWFpc2hvdS5zZXJ2ZXIud2ViLnN0EqAB1fPdEwVz-66onvsCh3_P63LjDKblDxAADr2K_kXSZrxEVrX_oU2KKUvZl5YqwPjiOUq-a9m0TlOEs10dBkNF5QpOYQ_s3fbtjyCLi1qrDML64T3Nv3pFwAl1GFULe4arg_hUh6AATsNaXUVr_BteoLpk1WW804aC1U_F1oUPwxulQzqXvUIXngHXO6YPd1dAHtgR77dRIXaK9UswU846xhoS7YoRGiN2PM_7zCD1Dj9m5oYoIiAxx8D3DhwHW8EIVCxWj6oplmurEtbP8DIF7aORbq6y1igFMAE; kuaishou.server.web_ph=bd89383a1964446aa9fb3a806f64776467fc; kpn=KUAISHOU_VISION`,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const document = gql`
|
||||||
|
fragment photoContent on PhotoEntity {
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
duration
|
||||||
|
caption
|
||||||
|
originCaption
|
||||||
|
likeCount
|
||||||
|
viewCount
|
||||||
|
commentCount
|
||||||
|
realLikeCount
|
||||||
|
coverUrl
|
||||||
|
photoUrl
|
||||||
|
photoH265Url
|
||||||
|
manifest
|
||||||
|
manifestH265
|
||||||
|
videoResource
|
||||||
|
coverUrls {
|
||||||
|
url
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
timestamp
|
||||||
|
expTag
|
||||||
|
animatedCoverUrl
|
||||||
|
distance
|
||||||
|
videoRatio
|
||||||
|
liked
|
||||||
|
stereoType
|
||||||
|
profileUserTopPhoto
|
||||||
|
musicBlocked
|
||||||
|
}
|
||||||
|
fragment recoPhotoFragment on recoPhotoEntity {
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
duration
|
||||||
|
caption
|
||||||
|
originCaption
|
||||||
|
likeCount
|
||||||
|
viewCount
|
||||||
|
commentCount
|
||||||
|
realLikeCount
|
||||||
|
coverUrl
|
||||||
|
photoUrl
|
||||||
|
photoH265Url
|
||||||
|
manifest
|
||||||
|
manifestH265
|
||||||
|
videoResource
|
||||||
|
coverUrls {
|
||||||
|
url
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
timestamp
|
||||||
|
expTag
|
||||||
|
animatedCoverUrl
|
||||||
|
distance
|
||||||
|
videoRatio
|
||||||
|
liked
|
||||||
|
stereoType
|
||||||
|
profileUserTopPhoto
|
||||||
|
musicBlocked
|
||||||
|
}
|
||||||
|
fragment feedContent on Feed {
|
||||||
|
type
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
headerUrl
|
||||||
|
following
|
||||||
|
headerUrls {
|
||||||
|
url
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
photo {
|
||||||
|
...photoContent
|
||||||
|
...recoPhotoFragment
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
canAddComment
|
||||||
|
llsid
|
||||||
|
status
|
||||||
|
currentPcursor
|
||||||
|
tags {
|
||||||
|
type
|
||||||
|
name
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
query visionProfileLikePhotoList(
|
||||||
|
$pcursor: String
|
||||||
|
$page: String
|
||||||
|
$webPageArea: String
|
||||||
|
) {
|
||||||
|
visionProfileLikePhotoList(
|
||||||
|
pcursor: $pcursor
|
||||||
|
page: $page
|
||||||
|
webPageArea: $webPageArea
|
||||||
|
) {
|
||||||
|
result
|
||||||
|
llsid
|
||||||
|
webPageArea
|
||||||
|
feeds {
|
||||||
|
...feedContent
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
hostName
|
||||||
|
pcursor
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const variables = {
|
||||||
|
page: "profile",
|
||||||
|
pcursor: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
client.request(document,variables)
|
||||||
|
.then(res => {
|
||||||
|
console.log("请求的数据为:");
|
||||||
|
console.log(res);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log("请求的错误为:");
|
||||||
|
console.log(error);
|
||||||
|
})
|
||||||
1
ksAll/views/.pydio
Normal file
1
ksAll/views/.pydio
Normal file
@@ -0,0 +1 @@
|
|||||||
|
8df6645b-0e4a-447e-84c3-9d8dd3478df6
|
||||||
6
ksAll/views/error.pug
Normal file
6
ksAll/views/error.pug
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
extends layout
|
||||||
|
|
||||||
|
block content
|
||||||
|
h1= message
|
||||||
|
h2= error.status
|
||||||
|
pre #{error.stack}
|
||||||
14
ksAll/views/index.pug
Normal file
14
ksAll/views/index.pug
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
extends layout
|
||||||
|
|
||||||
|
block css
|
||||||
|
link(rel='stylesheet', href='/stylesheets/index.css')
|
||||||
|
|
||||||
|
block content
|
||||||
|
.video
|
||||||
|
.close x
|
||||||
|
video(src="", loop, controls, height="100%")
|
||||||
|
.like 喜欢
|
||||||
|
ul
|
||||||
|
|
||||||
|
block js
|
||||||
|
script(src="/javascripts/index.js")
|
||||||
12
ksAll/views/layout.pug
Normal file
12
ksAll/views/layout.pug
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
doctype html
|
||||||
|
html
|
||||||
|
head
|
||||||
|
title= title
|
||||||
|
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||||
|
link(rel='stylesheet', href='http://at.alicdn.com/t/font_3359132_azrkp1lj7lc.css')
|
||||||
|
script(src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js")
|
||||||
|
block css
|
||||||
|
body
|
||||||
|
block content
|
||||||
|
|
||||||
|
block js
|
||||||
10
ksAll/views/recommend.pug
Normal file
10
ksAll/views/recommend.pug
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
extends layout
|
||||||
|
|
||||||
|
block css
|
||||||
|
link(rel='stylesheet', href='/stylesheets/index.css')
|
||||||
|
|
||||||
|
block content
|
||||||
|
.recommend 相关推荐
|
||||||
|
|
||||||
|
block js
|
||||||
|
script(src="/javascripts/recommend.js")
|
||||||
201
ksAll/快手接口整理.txt
Normal file
201
ksAll/快手接口整理.txt
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
快手接口整理
|
||||||
|
|
||||||
|
接口地址:https://live.kuaishou.com/live_graphql
|
||||||
|
|
||||||
|
注意:
|
||||||
|
1:下面接口调用时,请先在PC端登录快手然后复制请求头中的cookies,
|
||||||
|
然后使用后端伪造请求头来调用快手接口,后端拿到数据后
|
||||||
|
将数据返回给我们自己的前端。
|
||||||
|
2:项目中只有这一个接口地址,通过调用接口中不同方法来获取不同数据
|
||||||
|
3:后端需要使用专门的 graphql 插件去发送请求
|
||||||
|
4:更多的接口请自行分析快手PC端的NetWork
|
||||||
|
|
||||||
|
使用注意:
|
||||||
|
1:PC端登录快手,然后复制请求头cookies,修改 http/index.js 中的 cookies的数值为刚刚复制的即可!
|
||||||
|
|
||||||
|
1:用户主页喜欢视频列表接口
|
||||||
|
请求:POST
|
||||||
|
调用方法:likedFeedsQuery()
|
||||||
|
方法参数:
|
||||||
|
1:principalId 用户快手id(用户自定义的用户名)
|
||||||
|
2:pcursor 当前的页码(类似分页的page参数),如果留空,默认第一页
|
||||||
|
3:count 一页展示多少条数据(类似分页的limit参数),默认24
|
||||||
|
调用实例:
|
||||||
|
query {
|
||||||
|
likedFeeds(principalId: "geekhelp", pcursor: "1.582321864999E12", count: 24) {
|
||||||
|
pcursor
|
||||||
|
list {
|
||||||
|
id,thumbnailUrl,poster,workType,type,useVideoPlayer,imgUrls,imgSizes,magicFace,
|
||||||
|
musicName,caption,location,liked,onlyFollowerCanComment,relativeHeight,timestamp,width,height,
|
||||||
|
counts {
|
||||||
|
displayView,displayLike,displayComment
|
||||||
|
},
|
||||||
|
user {
|
||||||
|
id,eid,name,avatar,
|
||||||
|
},
|
||||||
|
expTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
2:获取视频的评论
|
||||||
|
请求:POST
|
||||||
|
调用方法:commentListQuery()
|
||||||
|
方法参数:
|
||||||
|
1:count 评论数
|
||||||
|
2:page 无用参数,但是必填默认1即可
|
||||||
|
3:pcursor 分页的页面,每次请求都会返回下次页码
|
||||||
|
4:photoId 要获取视频评论的id
|
||||||
|
query {
|
||||||
|
shortVideoCommentList(photoId: '', page: 1, pcursor: '', count: 20t) {
|
||||||
|
commentCount,realCommentCount,pcursor,
|
||||||
|
commentList {
|
||||||
|
commentId,authorId,authorName,content,headurl,timestamp,authorEid,status,
|
||||||
|
subCommentCount,subCommentsPcursor,likedCount,liked,
|
||||||
|
subComments {
|
||||||
|
commentId,authorId,authorName,content,headurl,timestamp,authorEid
|
||||||
|
status,replyToUserName,replyTo,replyToEid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
3:获取热门推荐视频
|
||||||
|
请求:POST
|
||||||
|
调用方法:videoRecommendFeeds()
|
||||||
|
方法参数:
|
||||||
|
1:count 展示多少条
|
||||||
|
2:photoId 视频的id参数
|
||||||
|
调用实例:
|
||||||
|
query {
|
||||||
|
videoRecommendFeeds(photoId: $photoId, count: $count) {
|
||||||
|
list {
|
||||||
|
user {
|
||||||
|
id,avatar,name
|
||||||
|
},
|
||||||
|
id,thumbnailUrl,poster,workType,type,useVideoPlayer,imgUrls,
|
||||||
|
imgSizes,magicFace,musicName,caption,location,liked,onlyFollowerCanComment,
|
||||||
|
relativeHeight,timestamp,width,height,
|
||||||
|
counts {
|
||||||
|
displayView,displayLike,displayComment
|
||||||
|
},
|
||||||
|
expTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
4:获取视频的真实MP4播放地址
|
||||||
|
请求:POST
|
||||||
|
调用方法:feedById()
|
||||||
|
方法参数:
|
||||||
|
1:principalId 主播的快手id,从 likedFeeds 方法 list.user.id 获得
|
||||||
|
2:photoId 视频的id参数,从 likedFeeds 方法 list.id 中获得
|
||||||
|
|
||||||
|
调用实例:
|
||||||
|
query {
|
||||||
|
feedById(principalId: "xxx", photoId: "xxxx") {
|
||||||
|
currentWork {
|
||||||
|
playUrl,poster
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
5:获取主播首页的视频
|
||||||
|
请求:POST
|
||||||
|
调用方法:publicFeeds()
|
||||||
|
方法参数:
|
||||||
|
|
||||||
|
1:principalId 主播的快手id
|
||||||
|
2:pcursor 分页页码
|
||||||
|
3:count 一页展示多少数据
|
||||||
|
|
||||||
|
调用实例:
|
||||||
|
query {
|
||||||
|
publicFeeds(principalId: "xxxx", pcursor: "xxxx", count: 24) {
|
||||||
|
pcursor,
|
||||||
|
list {
|
||||||
|
id,thumbnailUrl,poster,workType,type,useVideoPlayer,imgUrls,imgSizes,magicFace,musicName,
|
||||||
|
caption,location,liked,onlyFollowerCanComment,relativeHeight,timestamp,width,height,
|
||||||
|
counts {
|
||||||
|
displayView,displayLike,displayComment
|
||||||
|
},
|
||||||
|
user {
|
||||||
|
id,eid,name,avatar,cityName
|
||||||
|
},
|
||||||
|
expTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
6:获取视频回复的展开更多
|
||||||
|
请求:POST
|
||||||
|
调用方法:subCommentList()
|
||||||
|
方法参数:
|
||||||
|
|
||||||
|
1:photoId 视频id
|
||||||
|
2:rootCommentId 回复的顶级评论
|
||||||
|
3:pcursor 下标
|
||||||
|
4:count 数量
|
||||||
|
|
||||||
|
调用实例:
|
||||||
|
query {
|
||||||
|
subCommentList(photoId: $photoId, rootCommentId: $rootCommentId, pcursor: $pcursor, count: $count) {
|
||||||
|
pcursor
|
||||||
|
subCommentsList {
|
||||||
|
commentId,authorId,authorName,content
|
||||||
|
headurl,timestamp,authorEid,status,replyToUserName,replyTo,
|
||||||
|
replyToEid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
7:对视频进行 喜欢收藏
|
||||||
|
请求:POST
|
||||||
|
调用方法:likeVideo()
|
||||||
|
方法参数:
|
||||||
|
|
||||||
|
1:photoId 视频id
|
||||||
|
2:principalId 快手用户id
|
||||||
|
3:cancel 0 喜欢,1 取消喜欢
|
||||||
|
query {
|
||||||
|
likeVideo(photoId: $photoId, principalId: $principalId, cancel: $cancel) {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
7:获取自己关注的主播,有哪些正在进行直播
|
||||||
|
|
||||||
|
请求:GET
|
||||||
|
https://live.kuaishou.com/rest/wd/live/liveStream/myfollow
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"operationName":"visionProfileLikePhotoList",
|
||||||
|
"variables": {
|
||||||
|
"pcursor":"${pcursor}","page":"${page}"
|
||||||
|
},
|
||||||
|
"query": "
|
||||||
|
fragment photoContent on PhotoEntity
|
||||||
|
{ __typename id duration caption originCaption likeCount viewCount commentCount realLikeCount coverUrl photoUrl photoH265Url manifest manifestH265 videoResource coverUrls { url __typename } timestamp expTag animatedCoverUrl distance videoRatio liked stereoType profileUserTopPhoto musicBlocked }
|
||||||
|
|
||||||
|
fragment recoPhotoFragment on recoPhotoEntity { __typename id duration caption originCaption likeCount viewCount commentCount realLikeCount coverUrl photoUrl photoH265Url manifest manifestH265 videoResource coverUrls { url __typename } timestamp expTag animatedCoverUrl distance videoRatio liked stereoType profileUserTopPhoto musicBlocked }
|
||||||
|
|
||||||
|
fragment feedContent on Feed { type author { id name headerUrl following headerUrls { url __typename } __typename } photo { ...photoContent ...recoPhotoFragment __typename } canAddComment llsid status currentPcursor tags { type name __typename } __typename }
|
||||||
|
|
||||||
|
query visionProfileLikePhotoList($pcursor: String, $page: String, $webPageArea: String) {
|
||||||
|
visionProfileLikePhotoList(pcursor: $pcursor, page: $page, webPageArea: $webPageArea) {
|
||||||
|
result,llsid,webPageArea,
|
||||||
|
feeds {
|
||||||
|
...feedContent,__typename
|
||||||
|
},
|
||||||
|
hostName,pcursor,__typename
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user