alist.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import _ from 'https://underscorejs.org/underscore-esm-min.js'
  2. import { distance } from 'https://unpkg.com/fastest-levenshtein@1.0.16/esm/mod.js'
  3. /**
  4. * alist js
  5. * 配置设置 {"key":"Alist","name":"Alist","type":3,"api":"http://xxx.com/alist.js","searchable":0,"quickSearch":0,"filterable":0,"ext":"http://xxx.com/alist.json"}
  6. * alist.json [{
  7. name:'名称',
  8. server:'地址',
  9. startPage:'/', //启动文件夹
  10. showAll: false , //是否显示全部文件,默认false只显示 视频和文件夹
  11. params:{ //对应文件夹参数 如设置对应文件夹的密码
  12. '/abc':{ password : '123' },
  13. '/abc/abc':{ password : '123' },
  14. }
  15. }]
  16. * 提示 想要加载文件夹里面全部视频到详情(看剧可以自动播放下一集支持历史记录)
  17. * 需要改软件才能支持,,建议长按文件夹时添加判断 tag == folder 时跳转 DetailActivity
  18. */
  19. const http = function (url, options = {}) {
  20. if(options.method =='POST' && options.data){
  21. options.body = JSON.stringify(options.data)
  22. options.headers = Object.assign({'content-type':'application/json'}, options.headers)
  23. }
  24. const res = req(url, options);
  25. res.json = () => res.content ? JSON.parse(res.content) : null;
  26. res.text = () => res.content;
  27. return res
  28. };
  29. ["get", "post"].forEach(method => {
  30. http[method] = function (url, options = {}) {
  31. return http(url, Object.assign(options, {method: method.toUpperCase()}));
  32. }
  33. })
  34. const __drives = {}
  35. function get_drives_path(tid) {
  36. const index = tid.indexOf('$');
  37. const name = tid.substring(0, index);
  38. const path = tid.substring(index + 1)
  39. return { drives: get_drives(name), path }
  40. }
  41. function get_drives(name) {
  42. const { settings, api, server } = __drives[name]
  43. if (settings.v3 == null) { //获取 设置
  44. settings.v3 = false
  45. const data = http.get(server + '/api/public/settings').json().data;
  46. if (_.isArray(data)) {
  47. settings.title = data.find(x => x.key == 'title')?.value;
  48. settings.v3 = false;
  49. settings.version = data.find(x => x.key == 'version')?.value;
  50. settings.enableSearch = data.find(x => x.key == 'enable search')?.value == 'true';
  51. } else {
  52. settings.title = data.title;
  53. settings.v3 = true;
  54. settings.version = data.version;
  55. settings.enableSearch = false; //v3 没有找到 搜索配置
  56. }
  57. //不同版本 接口不一样
  58. api.path = settings.v3 ? '/api/fs/list' : '/api/public/path';
  59. api.file = settings.v3 ? '/api/fs/get' : '/api/public/path';
  60. api.search = settings.v3 ? '/api/public/search' : '/api/public/search';
  61. }
  62. return __drives[name]
  63. }
  64. function init(ext) {
  65. const data = http.get(ext).json();
  66. data.forEach(item => __drives[item.name] = {
  67. name: item.name,
  68. server: item.server.endsWith("/") ? url.substring(0, item.server.length() - 1) : item.server,
  69. startPage: item.startPage || '/', //首页
  70. showAll: item.showAll === true, //默认只显示 视频和文件夹,如果想显示全部 showAll 设置true
  71. params: item.params || {},
  72. _path_param: item.params ? _.sortBy(Object.keys(item.params), function(x) { return -x.length }) : [],
  73. settings: {},
  74. api: {},
  75. getParams(path) {
  76. const key = this._path_param.find(x => path.startsWith(x))
  77. return Object.assign({}, this.params[key], { path })
  78. },
  79. getPath(path) {
  80. const res = http.post(this.server + this.api.path, { data: this.getParams(path) }).json()
  81. return this.settings.v3 ? res.data.content : res.data.files
  82. },
  83. getFile(path) {
  84. const res = http.post(this.server + this.api.file, { data: this.getParams(path) }).json()
  85. const data = this.settings.v3 ? res.data : res.data.files[0]
  86. if (!this.settings.v3) data.raw_url = data.url //v2 的url和v3不一样
  87. return data
  88. },
  89. isFolder(data) { return data.type == 1 },
  90. isVideo(data) { //判断是否是 视频文件
  91. return this.settings.v3 ? data.type == 2 : data.type == 3
  92. },
  93. is_subt(data) {
  94. if (data.type == 1) return false
  95. const ext = [".srt", ".ass", ".scc", ".stl", ".ttml"]
  96. return ext.some(x => data.name.endsWith(x))
  97. },
  98. getPic(data) {
  99. let pic = this.settings.v3 ? data.thumb : data.thumbnail;
  100. return pic || (this.isFolder(data) ? "http://img1.3png.com/281e284a670865a71d91515866552b5f172b.png" : '')
  101. }
  102. })
  103. }
  104. function home(filter) {
  105. let classes = Object.keys(__drives).map(key => ({
  106. type_id: `${key}$${__drives[key].startPage}`,
  107. type_name: key,
  108. type_flag: '1',
  109. }))
  110. return JSON.stringify({ 'class': classes });
  111. }
  112. function homeVod(params) {
  113. return JSON.stringify({ 'list': [] })
  114. }
  115. function category(tid, pg, filter, extend) {
  116. let { drives, path } = get_drives_path(tid)
  117. const id = tid.endsWith('/') ? tid : tid + '/'
  118. const list = drives.getPath(path)
  119. let subList = []
  120. let vodFiles = []
  121. let allList = []
  122. list.forEach(item => {
  123. if (drives.is_subt(item)) subList.push(item.name)
  124. if (!drives.showAll && !drives.isFolder(item) && !drives.isVideo(item)) return //只显示视频文件和文件夹
  125. let remark = get_size(item.size)
  126. const vod = {
  127. 'vod_id': id + item.name + (drives.isFolder(item) ? '/' : ''),
  128. 'vod_name': item.name.replaceAll("$", "").replaceAll("#", ""),
  129. 'vod_pic': drives.getPic(item),
  130. 'vod_tag': drives.isFolder(item) ? 'folder' : 'file',
  131. 'vod_remarks': drives.isFolder(item) ? remark + ' 文件夹' : remark
  132. }
  133. if (drives.isVideo(item)) vodFiles.push(vod)
  134. allList.push(vod)
  135. })
  136. if (vodFiles.length == 1 && subList.length > 0) { //只有一个视频 一个或者多个字幕 取相似度最高的
  137. let sub = subList.length == 1 ? subList[0] : _.chain(allList).sortBy(x => (x.includes('chs') ? 100 : 0) + levenshteinDistance(x, vodFiles[0].vod_name)).last().value()
  138. vodFiles[0].vod_id += "@@@" + sub
  139. //vodFiles[0].vod_remarks += " 有字幕"
  140. } else {
  141. vodFiles.forEach(item => {
  142. const lh = 0
  143. let sub
  144. subList.forEach(s => {
  145. //编辑距离相似度
  146. const l = levenshteinDistance(s, item.vod_name)
  147. if (l > 60 && l > lh) sub = s
  148. })
  149. if (sub) {
  150. item.vod_id += "@@@" + sub
  151. //item.vod_remarks += " 有字幕"
  152. }
  153. })
  154. }
  155. return JSON.stringify({
  156. 'page': 1,
  157. 'pagecount': 1,
  158. 'limit': allList.length,
  159. 'total': allList.length,
  160. 'list': allList,
  161. });
  162. }
  163. function detail(tid) {
  164. let { drives, path } = get_drives_path(tid)
  165. if (path.endsWith("/")) { //长按文件夹可以 加载里面全部视频到详情
  166. const content = category(tid, null, false, null)
  167. const { list } = JSON.parse(content)
  168. const vod_play_url = []
  169. list.forEach(x => {
  170. if (x.vod_tag == 'file') vod_play_url.push(`${x.vod_name}$${x.vod_id.substring(x.vod_id.indexOf('$') + 1)}`)
  171. })
  172. const pl = path.split("/");
  173. const vod_name = pl[pl.length - 2] || drives.name;
  174. let vod = {
  175. vod_id: tid,
  176. vod_name: vod_name,
  177. type_name: "文件夹",
  178. vod_pic: "https://avatars.githubusercontent.com/u/97389433?s=200&v=4",
  179. vod_content: tid,
  180. vod_tag: 'folder',
  181. vod_play_from: drives.name,
  182. vod_play_url: vod_play_url.join('#'),
  183. vod_remarks: drives.settings.title,
  184. }
  185. return JSON.stringify({ 'list': [vod] })
  186. } else {
  187. let paths = path.split("@@@");
  188. let vod_name = paths[0].substring(paths[0].lastIndexOf("/") + 1)
  189. let vod = {
  190. vod_id: tid,
  191. vod_name: vod_name,
  192. type_name: "文件",
  193. vod_pic: "https://avatars.githubusercontent.com/u/97389433?s=200&v=4",
  194. vod_content: tid,
  195. vod_play_from: drives.name,
  196. vod_play_url: vod_name + "$" + path,
  197. vod_remarks: drives.settings.title,
  198. }
  199. return JSON.stringify({
  200. 'list': [vod]
  201. });
  202. }
  203. }
  204. function play(flag, id, flags) {
  205. const drives = get_drives(flag)
  206. const urls = id.split("@@@")
  207. const vod = {
  208. 'parse': 0,
  209. 'playUrl': '',
  210. 'url': drives.getFile(urls[0]).raw_url
  211. }
  212. if (urls.length >= 2) {
  213. const path = urls[0].substring(0, urls[0].lastIndexOf('/') + 1)
  214. vod.subt = drives.getFile(path + urls[1]).raw_url
  215. }
  216. return JSON.stringify(vod)
  217. }
  218. function search(wd, quick) {
  219. return JSON.stringify({
  220. 'list': []
  221. });
  222. }
  223. function get_size(sz) {
  224. if (sz <= 0) return "";
  225. let filesize = "";
  226. if (sz > 1024 * 1024 * 1024 * 1024.0) {
  227. sz /= (1024 * 1024 * 1024 * 1024.0);
  228. filesize = "TB";
  229. } else if (sz > 1024 * 1024 * 1024.0) {
  230. sz /= (1024 * 1024 * 1024.0);
  231. filesize = "GB";
  232. } else if (sz > 1024 * 1024.0) {
  233. sz /= (1024 * 1024.0);
  234. filesize = "MB";
  235. } else {
  236. sz /= 1024.0;
  237. filesize = "KB";
  238. }
  239. return sz.toFixed(2) + filesize
  240. }
  241. function levenshteinDistance(str1, str2) {
  242. return 100 - 100 * distance(str1, str2) / Math.max(str1.length, str2.length)
  243. }
  244. __JS_SPIDER__ = {
  245. init: init,
  246. home: home,
  247. homeVod: homeVod,
  248. category: category,
  249. detail: detail,
  250. play: play,
  251. search: search
  252. }