This repository has been archived by the owner on Dec 12, 2022. It is now read-only.
forked from chitosai/bili-guessYouLike
-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.js
421 lines (410 loc) · 13.3 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
const LOG_PREFIX = '[哔哩哔哩猜你喜欢]';
let activeTab = null; // 当前tab
let recommandMax = 12; // 一次获取几个推荐视频
// ajax
const HTTP = {
get(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = () => {
if( xhr.status == 200 ) {
resolve(xhr.responseText);
} else {
reject(xhr.status);
}
}
xhr.send();
});
}
}
// database
const DB = {
local: chrome.storage.local,
get(key, cb) {
DB.local.get(key, (data) => {
if( typeof(key) == 'string' ) {
typeof(cb) == 'function' && cb(data[key]);
} else {
typeof(cb) == 'function' && cb(data);
}
});
},
set(obj, cb) {
DB.local.set(obj, cb);
},
remove(key) {
DB.local.remove(key);
},
saveRecommands(bvid, videos) {
let obj = {};
obj[bvid] = videos;
DB.set(obj);
DB.recommandsCountAdd(videos.length);
},
recommandsCountAdd(delta) {
DB.get('count', (_c) => {
let count = _c || 0;
count += delta;
DB.set({count});
});
},
logUserViewHistory(bvid) {
DB.getUserViewHistory((history) => {
// 判断浏览记录里是否已经包含这个bvid
const exist = history.indexOf(bvid);
if( exist > -1 ) {
// 如果已存在,就把原有的那个访问记录删掉
history.splice(exist, 1);
}
// 在队列末尾插入bvid
history.unshift(bvid);
// 保持访问记录最多99条
if( history.length > 99 ) {
const removedId = history.pop();
// 删除这个bvid对应的推荐视频
DB.get(removedId, (v) => {
DB.remove(removedId);
DB.recommandsCountAdd(-v.length);
});
}
DB.set({history});
});
},
getUserViewHistory(cb) {
DB.get('history', (history) => {
cb(history || []);
});
}
}
// 获取推荐
const RECOMMAND = {
// 获取av号对应的推荐视频
get(bvid) {
DB.get(bvid, (videos) => {
if( !videos ) {
// 没有获取过推荐视频要去服务端获取
//HTTP.get(`https://api.bilibili.com/x/web-interface/view/detail?bvid=${bvid}&aid=&jsonp=jsonp&callback=_`).then((_raw) => {
//let raw = _raw.substring(2, _raw.length - 1);
HTTP.get(`https://api.bilibili.com/x/web-interface/view/detail?bvid=${bvid}&aid=&jsonp=jsonp&callback=`).then((_raw) => {
let raw = _raw.substring(0, _raw.length - 0);
let res;
try {
res = JSON.parse(raw);
} catch(e) {
return console.error(`${LOG_PREFIX} 解析detail接口返回值失败:${e}`);
}
if( res.code != 0 ) {
return console.error(`${LOG_PREFIX} detail接口的返回值不为0:${res}`);
}
// 去掉我们不需要的信息,节约存储空间..
let data = res.data.Related.map((v) => {
return {
aid: v.aid,
bvid: v.bvid,
title: v.title,
pic: v.pic.replace('http://', 'https://'), // 返回的图片地址里带了http://,append到dom tree的时候会出警告,我们自己处理掉
duration: v.duration,
stat: v.stat,
up: v.owner
}
});
// 保存到数据库
DB.saveRecommands(String(bvid), data);
});
}
});
},
// 根据当前用户访问记录获取n个随机推荐视频
recommand(n) {
DB.getUserViewHistory((vh) => {
let max = Math.min(12, vh.length); // 只根据最近观看的12个视频来生成推荐
let keys = vh.slice(0, max);
DB.get(keys, (recommandArray) => {
let allVideos = [];
keys.forEach((key) => {
allVideos = allVideos.concat(recommandArray[key]);
});
let max = Math.min(allVideos.length, n);
let ids = [], videos = [];
while( ids.length < max ) {
let i = Math.floor(Math.random() * allVideos.length);
const v = allVideos[i];
if( !ids.includes(v.bvid) ) {
ids.push(v.bvid);
videos.push(v);
}
};
UI.updateRecommands(videos);
});
});
}
}
// 与页面交互的逻辑
const UI = {
node: null,
// 是否首页
isIndex() {
let path = window.location.pathname;
if( path == '/' || path == '/index.html' ) {
return true
} else {
return false;
}
},
// 是否视频播放页
isVideo() {
let path = window.location.pathname;
if( path.indexOf('video/BV') > -1 ) {
return true;
} else {
return false;
}
},
// 获取BVID
getBVID() {
let url = window.location.href,
m = /\/BV(\w+)/.exec(url);
if( m ) {
return m[1];
} else {
console.error(`${LOG_PREFIX} 找不到BV号:${url}`);
}
return null;
},
// 插入推荐模块
insertRecommands() {
return new Promise((resolve, reject) => {
// 复制「动画」模块来做一个「猜你喜欢」
// 20191125 b站改版,几个首页模块变成后渲染了,需要异步获取
let refSearchCount = 0;
function loopFrame() {
// 插入dom
let douga = document.querySelector('#bili_douga');
if( !douga ) {
if( refSearchCount < 99 ) {
setTimeout(loopFrame, 100);
refSearchCount++;
return;
} else {
throw new Error('无法获取到#bili_douga');
}
}
const node = douga.cloneNode(true);
node.id = '_bili_guessyoulike';
// 仅保留视频列表部分,其他东西统统删掉(有时候会有广告
Array.from(node.children).filter(c=>!c.classList.contains('report-scroll-module')).forEach(c=>c.remove());
// 替换文本内容
const name = node.querySelector('.name');
name.href = 'javascript: null;';
name.target = '';
name.textContent = '猜你喜欢';
// 修改结构
const text = document.createElement('div');
//text.innerHTML = '<div class="text-info" style="color: #ccc;"><span>这是一个非官方的猜你喜欢模块,有任何建议或bug反馈请联系 <a href="https://weibo.com/chitosai" target="_blank">@千歳</a></span></div>'
text.textContent = '这是一个非官方的猜你喜欢模块,有任何建议或bug反馈请联系 ';
text.classList.add("text-info");
text.style.color = '#ccc';
var info = document.createElement('a');
info.href = "https://weibo.com/chitosai";
info.target = "_blank";
info.textContent = "@千歳";
text.appendChild(info);
var info2 = document.createTextNode(",Firefox版兼容性问题请反馈");
text.appendChild(info2);
var info2Link = document.createElement('a');
info2Link.href = "https://github.com/niu541412/bili-guessYouLike";
info2Link.target = "_blank";
info2Link.textContent = "至此";
text.appendChild(info2Link);
name.insertAdjacentElement('afterend', text);
// 移除右侧「排行」
const rank = node.querySelector('.rank-list');
rank.remove();
// 移除「更多」
const more = node.querySelector('.btn.more');
more.remove();
// 「换一换」默认创建出来一直在转圈。。神经病啊
const changeButtonIcon = node.querySelector('.bili-icon_caozuo_huanyihuan');
changeButtonIcon.classList.remove('quan');
const change = node.querySelector('.btn-change');
// 点这个按钮就通知插件换一批推荐视频
change.addEventListener('click', () => {
window.postMessage({
type: 'UPDATE_RECOMMANDS'
}, '*');
});
// 插入页面
let ref = document.querySelector('#reportFirst2');
ref.insertAdjacentElement('beforebegin', node);
UI.node = node;
// 插入样式
const styleSheet = document.createElement('style');
const styleList = [
// 把猜你喜欢视频平铺占满100%,每行显示6个
'#_bili_guessyoulike .zone-list-box { width: 100% !important; }' +
'#_bili_guessyoulike .video-card-common { width: 15.6%; }' +
'#_bili_guessyoulike .video-card-common:nth-child(n+9) { display: block; }' +
// 1438px以下宽度时每行只显示5个视频,不然太密集了
'@media screen and (max-width: 1438px) { ' +
'#_bili_guessyoulike .video-card-common { width: 19%; }' +
'#_bili_guessyoulike .video-card-common:nth-child(n+11) { display: none; }' +
'}'
]
styleSheet.textContent = styleList.join('');
document.head.append(styleSheet);
resolve();
}
loopFrame();
});
},
// 获取推荐模块的引用
getRecommandNode() {
return new Promise(async (resolve) => {
// 检查是否有已插入的节点
UI.node = document.querySelector('#_bili_guessyoulike');
if( !UI.node ) {
// 没有就创建
await UI.insertRecommands();
}
resolve();
});
},
async updateRecommands(videos) {
await UI.getRecommandNode();
const node = UI.node;
// 移除原有的视频
const stage = node.querySelector('.zone-list-box');
let html = '';
//Firefox
while (stage.hasChildNodes()) {
stage.removeChild(stage.firstChild);
}
if( videos.length ) {
function toWan(number) {
return number > 9999 ? ((number/10000).toFixed(1) + '万') : number;
}
function toMin(seconds) {
return Math.floor(seconds/60) + ':' + (seconds % 60);
}
// 插入新视频
videos.forEach((video) => {
//let v = `<div class="video-card-common"><div class="card-pic" data-aid="${video.aid}" data-bvid="${video.bvid}"><a href="/video/${video.bvid}" target="_blank"><img src="${video.pic}@206w_116h_1c_100q.webp"><div class="count"><div class="left"><span><i class="bilifont bili-icon_shipin_bofangshu"></i>${toWan(video.stat.view)}</span><span><i class="bilifont bili-icon_shipin_dianzanshu"></i>${toWan(video.stat.like)}</span></div><div class="right"><span>${toMin(video.duration)}</span></div></div></a></div><a href="/video/${video.bvid}" target="_blank" title="${video.title}" class="title">${video.title}</a><a href="//space.bilibili.com/${video.up.mid}/" target="_blank" class="up"><i class="bilifont bili-icon_xinxi_UPzhu"></i>${video.up.name}</a></div>`;
//html += v;
node1 = document.createElement("div");
node1.classList.add("video-card-common");
node2 = document.createElement("div");
node2.classList.add("card-pic");
node2.setAttribute("data-aid",video.aid);
node2.setAttribute("data-bvid",video.bvid);
node1.appendChild(node2);
node3 = document.createElement("a");
node3.href = `/video/${video.bvid}`;
node3.target = "_blank";
node2.appendChild(node3);
node4 = document.createElement("img");
node4.src = `${video.pic}@206w_116h_1c_100q.webp`;
node3.appendChild(node4);
node5 = document.createElement("div");
node5.classList.add("count");
node3.appendChild(node5);
node6 = document.createElement("div");
node6.classList.add("left");
node5.appendChild(node6);
node7 = document.createElement("span");
node6.appendChild(node7);
node8 = document.createElement("i");
node8.classList.add("bilifont", "bili-icon_shipin_bofangshu");
node7.appendChild(node8);
node7.textContent=toWan(video.stat.view);
node9 = document.createElement("span");
node6.appendChild(node9);
node10 = document.createElement("i");
node10.classList.add("bilifont", "bili-icon_shipin_dianzanshu");
node9.appendChild(node10);
node9.textContent=toWan(video.stat.like);
node11 = document.createElement("div");
node11.classList.add("right");
node5.appendChild(node11);
node12 = document.createElement("span");
node12.textContent = toMin(video.duration);
node11.appendChild(node12);
node13 = document.createElement("a");
node13.href = `/video/${video.bvid}`;
node13.target="_blank";
node13.title = video.title;
node13.classList.add("title");
node13.textContent = video.title;
node1.appendChild(node13);
node14 = document.createElement("a");
node14.href = `//space.bilibili.com/${video.up.mid}/`;
node14.target="_blank";
node14.classList.add("up");
node1.appendChild(node14);
node15= document.createElement("i");
node15.classList.add("bilifont", "bili-icon_xinxi_UPzhu");
node14.appendChild(node15);
node14.textContent=video.up.name;
stage.appendChild(node1);
});
} else {
html = document.createElement("p");
html.style.cssText = "color: #777; line-height: 486px; text-align: center;";
html.textContent = "观看记录为空,快去看几个视频吧~";
stage.appendChild(html);
}
//stage.innerHTML = html;
// 绑定van-framepreview
Array.from(stage.querySelectorAll('.card-pic')).forEach(bili_van_framepreview);
},
// 监听来自页面的更新请求
listen() {
window.addEventListener('message', (ev) => {
if( ev.data.type && ev.data.type == 'UPDATE_RECOMMANDS' ) {
RECOMMAND.recommand(recommandMax);
}
});
}
}
// 在视频页直接点击关联视频并不会刷新页面,而是直接ajax加载改变url,所以我们要监听hashchange
// 试了下hashchange事件好像监听不到?不知道为啥,写个dirty check吧
const URLLISTENER = {
timer: null,
bvid: '',
tick() {
const bvid = UI.getBVID();
if( bvid !== URLLISTENER.bvid ) {
DB.logUserViewHistory(bvid);
RECOMMAND.get(bvid);
URLLISTENER.bvid = bvid;
console.log(`${LOG_PREFIX} Logged ${bvid}`);
}
},
init() {
URLLISTENER.timer = setInterval(URLLISTENER.tick, 10000); // 10s检查一次差不多了吧
URLLISTENER.tick();
}
}
function init() {
// 当前是否首页?
if( UI.isIndex() ) {
RECOMMAND.recommand(recommandMax);
UI.listen();
}
// 当前是否视频播放页?
// 如果是视频播放页,则获取当前视频的相关推荐视频
if( UI.isVideo() ) {
URLLISTENER.init();
}
}
// 增加了bvid字段,需要清除以前的数据
DB.get('_20200325_clear_data', (cleared) => {
if( cleared ) {
init();
} else {
chrome.storage.local.clear(() => {
DB.set({'_20200325_clear_data': true});
init();
});
}
});