9.27 更新

优化 bbtalk.js 方法,解决 pjax 兼容问题

6.28 更新:Safari 无法播放 mp4 问题已破案

下文提到哔哔闪念发送视频后,存储在我的cdn.guole.fun上,结果发现 Safari 无法播放。一顿操作猛如虎,各种排查最终确认问题如下,且已根本解决。

原因:

罪魁祸首:workbox ,我早前通过正则给 cdn.guole.fun 域名加了 workbox 缓存,结果发现如果不特殊配置,workbox 缓存的视频无法正常响应 Safari 的 range 请求。甚至 Chrome 也偶现无法播放,不光 Safari。我原来是这样用的,简单粗暴:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
workbox.routing.registerRoute(
/^https:\/\/cdn\.guole\.fun/,
new workbox.strategies.CacheFirst({
cacheName: "cdn-static-libs",
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 1000,
maxAgeSeconds: 60 * 60 * 24 * 30 //30day
}),
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);

解决方案:

只需改为如下即可。压根不用 workbox 缓存 JSON 、mp4 资源了(workbox缓存音频后,也会影响其正常响应)。Chrome 官方给了说明,该日再研究折腾:提供缓存的音频和视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
workbox.routing.registerRoute(
({url}) => {
return url.href.match(/^https:\/\/cdn\.guole\.fun/) && !url.pathname.includes('/json/bb-json/') && !url.pathname.includes('/media/bb-media/');
},
new workbox.strategies.CacheFirst({
cacheName: "cdn-static-libs",
plugins: [
new workbox.expiration.ExpirationPlugin({
maxEntries: 1000,
maxAgeSeconds: 60 * 60 * 24 * 30 //30day
}),
new workbox.cacheableResponse.CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
);

6.26 再更新:新增说说转储 JSON 功能

如题,现在云函数可以在你新增 / 删除 / 修改 / 插入 / 追加说说内容后,自动拉取变动数据生成 JSON 文件,每个 JSON 文件的条数 = 环境变量设置的PageSize值。也就是说,前端读 JSON 即可,不用直接请求 LeanCloud 了,提升哔哔闪念的加载速度!(JSON 自动上传腾讯云 COS ,配合 CDN 加持,老上头了)

新的资源 URL 是下面这样,json/bb-json 这部分是你自己在环境变量中设置的上传路径 Tcb_JsonPath 值:

1
https://cdn.guole.fun/json/bb-json/bbtalk_page${page}.json

生成的 JSON 暂时明明格式为:”bbtalk_page=” + 页数 + “.json” 。前端读取时,只用递增这个页面即可。首页轮播哔哔,固定读取 bbtalk_page1.json 就完事儿了!配合上述功能,哔哔闪念独立页面的前端 JS 方法,调整 fetch 请求地址为如下类型即可(v1.0.1 以上版本):

1
fetch(`https://cdn.guole.fun/json/bb-json/bbtalk_page${page}.json`)

首页轮播哔哔,其中的 jsonUrl 变量值调整为如下这种写死即可:

1
jsonUrl = 'https://cdn.guole.fun/json/bb-json/bbtalk_page1.json';

更新:解决 Safari 无法播放视频问题

  1. 原因:Safari 处理video标签时,会先发一个获取视频 1 字节信息的请求,拿视频封面之类的。只有用户点击播放时,才会再发一个请求,获取后面的完整内容。这里需要 cos 正常响应range请求。但是我原本使用了本站的cdn.guole.fun来获取媒体资源,但这个 CDN 加速域名配置的加速类型是“ CDN 网页小文件”,似乎会覆盖导致存储桶本身自动给 .mp4 文件设置的Content-Type: video/mp4 头部信息不生效,Chrome 中正常,但 iOS Safari 中就不行(iOS 里的 Chrome / 微信内嵌浏览器都使用 Safari 内核……)
  2. 解决:测试直接放存储桶的资源链接,是可以正常播放的。但是我这边重新设置cdn.guole.fun的加速类型为“音视频点播”时报错,设置不成功。我就曲线解决了,重新创建了个media.guole.fun加速域名(与cdn.guole.fun都关联同一个存储桶……),专门用来读取多媒体资源,cdn.guole.fun先保留用作它用,后续考虑要不要删除重新创建为“音视频点播”(2023.6.26 已删除重建解决)。
  3. 创建好相关域名后,拆分成:顶级域名、二级域名、子域,在下面环境变量中配置既可。

综上所述,如果你准备新建一个存储桶用来放说说多媒体资源,一定记得选加速类型为“音视频点播”。不建议直接暴露使用存储桶的原始域名(没有 CDN 加速)。同时,存储桶和加速域名设置好防盗链,免得被盗刷……

原文

分享下本站说说的发展历程。

早前,使用黑石哔哔、木木的 bber 、小康的 ispeak 等,后来腾讯云开发收费了,就陆续没法用了。

目前本站哔哔存储在 LeanCloud (其他 MongoDB 啥的应该也没问题),/bb/ 和首页轮播是我写了个 Vercel 云函数接口,加了个缓存功能,同时避免 LeanCloud 的访问秘钥在前端被泄露。欢迎扫描关注,先行体验效果再决定要不要折腾。

哔哔闪念

这个端午竟然没出去玩!!就把之前用木木的 bber 复刻了出来,同时新增了发视频、发位置、发链接卡片等功能。微信其实还支持个语音消息,由于是 amr 格式音频,浏览器没法播放,服务端复用 FFmpeg 等插件转换格式,又一直环境不对……心态崩了,干脆摆烂……谁搞定了,欢迎 PR 或下面留言通知我啊!

注:本文包含的微信发说说功能,部署在腾讯云 Serverless 里,其他 Vercel 、Railway 应该也没问题,我没折腾。之前域名备案,一定要有服务器之类的 IP ,就买了三年 Serverless 套餐,个人使用不超量的话也很便宜,所以 也不能闲着便宜了腾讯不是……

效果演示

支持特性

  • 包含
    • 部署在云函数上,无需服务器
    • 使用微信随时随地发布闪念瞬间(memos 很香,但是得有机器……)
    • 在原来 @木木木bber-weixin 基础上升级而来,新增支持:发位置、发链接卡片、发视频功能
    • 发图片:直接拍照片或发本地图片给公众号既可(存在腾讯云 cos ,原有的去不图床逻辑没删,但我也没验证);
    • 发视频:录视频、或本地视频直接发给公众号既可(存在腾讯云 cos,支持 .mp4 / .flv / .ts ,其他格式受腾讯 CDN 支持范围影响,不支持 Safari 播放);
    • 发位置:需要在 /bb/ 页面引入资源,详见使用指南;(其他页面嵌入地图,见另一个插件:hexo-tag-map
    • 发链接卡片:直接分享卡片到公众号;
    • 发语音:本来想借用 FFmpeg 在服务端转换语音音频 amr 为 mp3,前端再调 aplayer 等播放,但是夭折了……云函数里一直搞不定 FFmpeg 的环境……

部署到 LeanCloud

这部分很简单,到 LeanCloud 注册个 国际版 账号,然后创建一个应用,比如就叫 bb,然后新建 2 个集合 contentUserBindingStatus

LeanCloud 可以在 LeanCloud 导入导出功能里,把你原来的数据导进去(建议部署后,发布 1 条消息,数据库自动创建字段后,再导出到本地,按照字段映射手动修改后,再导入以前的数据进去。做好备份,大不了多试几次)。content 集合差不多有这几个字段:

字段名类型说明
objectIdString唯一标识符,LeanCloud 自动创建,无视它;
ACLACL读写权限,LeanCloud 自动创建,无视它
MsgTypeString微信消息类型,前端用来处理一些逻辑
contentString说说内容
fromString说说来源,自定义
otherString一些特殊消息,比如位置消息,保存脚本,前端插入后渲染出地图
createdAtDate创建日期时间,LeanCloud 自动创建,无视它;若导入数据,得修改这个字段,ISO 格式的时间戳,比如 2023-06-24T07:20:55.650Z
updatedAtDate更新日期时间,LeanCloud 自动创建,无视它

UserBindingStatus 集合不用管,字段云函数会自动创建。(其实 content 集合应该也不用手动创建字段……只是我没试过)

获取 LeanCloud 凭证

  1. 上述 content 和 UserBindingStatus 集合需要在同一个应用里,这样 appid 才一样;
  2. LeanCloud 应用凭证 界面(设置——应用凭证),找到 AppID / AppKey / MasterKey 信息,记录下来下文使用。

获取 LeanCloud 凭证

获取腾讯云 API 访问密钥

  1. 注册并认证腾讯云开发者;
  2. 在腾讯云 访问管理 界面新建 1 个密钥。(建议创建一个子用户,可以更小颗粒度限制权限范围,更安全);
  3. 如果使用子用户(访问方式选择“编程访问”),在关联策略中,搜索 cos ,选择QcloudCOSDataFullControl 对象存储(COS)数据读、写、删除、列出的访问权限 这一项既可;(为啥要删除权限,因为你发指令删除说说时,如果包含了视频图片等资源,是同步删了存储桶文件的)

QcloudCOSDataFullControl
4. 记录下 SecretId / SecretKey

腾讯云 API 访问密钥

获取微信公众号凭证

  1. 注册一个微信公众号,个人只能注册订阅号 微信公众平台
  2. 在公众平台 —— 设置与开发 —— 基本设置界面,记录下 AppID / AppSecret
  3. 在服务器配置中,选择兼容模式,自定义Token,随机生成 EncodingAESKey都记录下来;
  4. 不要关闭这个页面,等下回来配置服务器地址;

获取微信公众号凭证

创建云函数

  1. 访问腾讯云云函数平台 腾讯云 Serverless;
  2. 创建一个云函数,选择 从头开始,函数类型:事件函数;函数名称:自定义;地域:中国香港(要访问 LeanCloud,香港可能好一点);运行环境:Nodejs 16.13;

创建云函数
3. 其他配置中,启用日志投递。默认格式;启用固定公网出口 IP;触发器选择 自定义创建,触发方式选择 API 网关触发,勾选集成响应,其他用默认的不用改。
4. 创建一个 src 目录,函数代码拷贝我 Github 仓库:BBtalk-Serverless,总共是 6 个文件,都放在 src目录下保存。不要在线安装依赖,部署太慢了,往下看;
5. 本地创建个空目录,git clone 或手动下载解压缩都可以,下载我的代码包Github 仓库:BBtalk-Serverless,然后 cd src 后,npm install 安装依赖;
6. 进入 src下的 node_modules 目录,全选压缩为 .zip 压缩包;(一定要进入node_modules目录全选压缩)
7. 在腾讯云云函数页面,选择,新建,名称随意,上传刚才压缩的 zip 包;
8. 进入函数服务 —— 刚才创建的云函数 —— 层管理,绑定刚才创建的

层管理
9. 回到 函数配置,右上角编辑,创建以下环境变量(Tcb_Bucket / Tcb_Region / Tcb_ImagePath / Tcb_MediaPath / Tcb_JsonPath 请自行手动创建):

keyvalue
Binding_Key绑定密钥,自定义一条字符串
LeanCloud_ID上面记录的 LeanCloud AppID
LeanCloud_KEY上面记录的 LeanCloud AppKey
LeanCloud_MasterKey上面记录的 LeanCloud MasterKey
Tcb_SecretId上面记录的腾讯云 API 访问秘钥 SecretId
Tcb_SecretKey上面记录的腾讯云 API 访问秘钥 SecretKey
WeChat_Token上面记录的微信公众平台自定义的 Token
WeChat_appId上面记录的微信公众平台自定义的 AppID
WeChat_appSecret上面记录的微信公众平台自定义的 AppSecret
WeChat_encodingAesKey上面记录的微信公众平台自定义的 EncodingAESKey
Upload_Media_Method图片 / 视频的上传方式,可选值:cosqubu,要使用发视频功能必须配置为cosqubu我没测试…
Tcb_Bucket腾讯云存储桶名称(如“file-1258755354”)。若 Upload_Media_Method 为 cos ,此项必填
Tcb_Region腾讯云存储桶地域(如“ap-guangzhou”)。若 Upload_Media_Method 为 cos ,此项必填
Tcb_ImagePath腾讯云存储桶图片路径(如“/img/bb-img/”,即图片上传你要放在什么路径)。若 Upload_Media_Method 为 cos ,此项必填
Tcb_MediaPath腾讯云存储桶媒体路径(如“/media/bb-media/”,即媒体上传你要放在什么路径)。若 Upload_Media_Method 为 cos ,此项必填
Tcb_JsonPath腾讯云存储桶 JSON 转储路径(如“/json/bb-json/”,即媒体上传你要放在什么路径)。若 Upload_Media_Method 为 cos ,此项必填
TopDomain顶级域名(如 media.guole.fun 中的 “fun”)。若 Upload_Media_Method 为 cos ,此项必填
SecondLevelDomain二级域名(如 media.guole.fun 中的 “guole”)。若 Upload_Media_Method 为 cos ,此项必填
SubDomain子域(如 media.guole.fun 中的 “media”)。若 Upload_Media_Method 为 cos ,此项必填
PageSize从 LeanCloud 获取数据转 Json 的每页条数。若 Upload_Media_Method 为 cos ,此项必填
  1. 提交保存环境变量后,回到函数配置界面,点击部署
  2. 在云函数触发管理中,记录下访问路径

完成微信公众号配置

在刚才停留的公众号配置界面,服务器地址填写上面记录的云函数访问路径,然后提交,应该就成功了。

IP 白名单:一定要配置这个,不然发图、视频时,取不到微信 Access token 。ip 就是上面云函数的固定出口 IP 地址。

ip白名单 这个地方如果微信校验不通过,重点检查云函数环境变量 Token 与微信这边填的是否相同,验证云函数执行方法是否为index.main_handler,浏览器访问云函数访问路径,显示403错误应该就没问题;

Hexo 创建说说页面

在 Blog 根目录,执行hexo new page bb新建一个页面,bb是说说页面路径,可以自定义;接着:

/bb/ 目录下的 index.md 文件中,添加以下内容:

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
---
title: 哔哔闪念
date: 2021-06-07 17:29:43
type: "bb"
aplayer: false
top_img: false
#aside: false
---

{% note success %}
本页使用「哔哔闪念 2.0」生成,记录 @夜的第八章 的胡思乱想<a href="/posts/17745" class="banner-button"><i class="iconfont icon-you"></i><span class="banner-button-text">部署指引</span></a>
{% endnote %}

<!-- 存放闪念的容器 -->
<!-- <div class="js-pjax"> -->
<div id="bbtalk"><p class="info"></p></div>
<div id="loading" style="text-align: center"><img src='/img/loading1.gif' alt="loading…" class="no-lightbox nolazyload" style="width: 11rem;"></img></div>
<button id="load-more-btn" class="next" style="background:linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab); background-size: 1000% 1000%;box-shadow: 0 0px 6px 1px rgb(7,17,27,15%)">再翻翻</button>
<div class="bbtalk-bottom-tip" id="bbtalk-bottom-tip" style="display: none;color:#CACACA;text-align: center;font-size: 14px;">~&nbsp;我是有底线的&nbsp;~</div>
<!-- <div class="gaodeMap-22.521799-113.99757" id="gaodeMap-22.521799-113.99757"></div> -->
<link rel="stylesheet" href="//unpkg.com/hexo-tag-map/lib/leaflet@1.7.1.css">
<link rel="stylesheet" href="/css/bbtalk.css" onload="this.media='all'">
<script data-pjax="" src="//unpkg.com/hexo-tag-map/lib/leaflet@1.7.1.js"></script>
<script data-pjax="" src="//unpkg.com/hexo-tag-map/lib/leaflet.ChineseTmsProviders@1.0.4.js"></script>
<script src="/js/timeago@4.0.2.min.js"></script>
<script src="/js/BBtalk.js"></script>
<script>
bbtalk.init({
bbtalk_name: '夜的第八章', // 昵称
bbtalk_avatar: '/img/gl.jpg', // 头像
bbtalk_avatar_svg: '', //头像后的图标,可留空使用默认的
aplayer_js: '/js/APlayer.min.js', //自定义引入 aplayer
// leaflet_css: '', // 无必要可注释
// leaflet_js: '', // 无必要可注释
// ChineseTmsProviders_js: '', // 无必要可注释
});
</script>

再去 /source/css/ 目录下创建一个 bbtalk.css 文件(也可以命名其他的或者放在其他地方,上面 md 文件里记得同步修改),内容如下:

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
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
[data-theme=light] {
--bbtalk-from-color: #fff;
--bbtalk-from-chrome: #aa96f5;
--bbtalk-from-wechat: #49c65b;
--bbtalk-from-postman: #7194a4;
--bbtalk-from-edge: #2196f3;
--bbtalk-from-siri: #8290d6;
--bbtalk-from-bg: #9e9e9e;
--bb-TimeTitle-bg: rgba(48, 49, 51, 0.85);
--bb-TimeTitle: rgba(255, 255, 255, 0.9);
--bbtalk-url-bg: #fff;
}
[data-theme=dark] {
--bbtalk-from-color: rgba(255,255,255,0.7);
--bbtalk-from-chrome: #8573c8;
--bbtalk-from-wechat: #31933f;
--bbtalk-from-postman: #607d8b;
--bbtalk-from-edge: #1b84d8;
--bbtalk-from-siri: #6b7bce;
--bbtalk-from-bg: #847f7f;
--bb-TimeTitle-bg: #042537;
--bb-TimeTitle: rgba(255, 255, 255, 0.7);
--bbtalk-url-bg: #222528;
}

#article-container img:not(#loading img) {margin: unset;}
#bbtalk div.timenode {
background: unset;
box-shadow: unset;
border: 1px solid var(--guole-card-border);
margin-top: 1.8rem;
}
#bbtalk div.timenode:hover {
background: unset;
box-shadow: unset;
border: 1px solid rgba(28,105,237,0.5);
}
#loading img {
text-align: center;
display: inline-block;
}
#loading .img-alt.is-center {
text-align: center
}

@font-face {
font-family: "iconfont";
src: url("//at.alicdn.com/t/font_1755564_z4mhxbw13mq.eot?t=1586882866168");
/* IE9 */
src: url("//at.alicdn.com/t/font_1755564_z4mhxbw13mq.eot?t=1586882866168#iefix")
format("embedded-opentype"),
/* IE6-IE8 */
url("data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAALcAAsAAAAAB2gAAAKPAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCCfgqCUII8ATYCJAMMCwgABCAFhG0HQBuEBsiuMRkynL4YFCHtLKs4b+k7/80U8DwHIB6+xtr7u3vniDZI6tmnkyDS8NC5iFgJlE7pTPcO8e5b+803aYQy8YtH1JPKyZzY3ttb1EonioVGSBAiRJp4JV9cFppCxLpe2z8sUs37O4QEDFRM0lFeAw4XN6bQPG06zTG8DRwHFOBeGEVWIjE+mP8wduVFPCbQNK4ktN8/OgFJhbEqEFemLkEyFVcUVqoL1ZKDRbyIqafb9AA8+9+PX41IklQyY+PBSZ8KnR+9iBx0OxZcp8PxvGDHyFgACnFY6j8QCfMLIppKPmoCrGtW8OG/y0eufLX/8AiJqDKa22AGWpMzUth6p6aARySQQRUUL4AnSBmuyvIwPdbz21OX5ybO5duOJifaLl6cvkrd1HR7kWEy6yikff29PvgO17V/Tf/9f/oN3PDqz22a719IYlZJHQetpLY08MV6fwFDrF0B6F4XyB3/FyEEnZnU2XGtVwHvpxtA1d6pftKB4AcoWo4U1QQbiiy35Xow27whbwK2NNFlhAc99zeOtM4S6ikFQ1IzhqxuglAoC6ho2UBV3Raa5g0cbxnCRURpYM4pQOh3h6TXG7J+j4RCeULFqFdU9UcETYfRdGHLVJjpHk6SIBXlTTQV5mmWS6SD1jHSVx2JJ1U+a4p4YKawvrSSLQ2QR3yMLcGa3hBCQ40zF/vBc+Q4DH3OLFJExRDCb64un5S9qaIwF9L2cEQiEBWSbUImBePR3PFCOvf5MUS3yiHhNR0VySmEC5jTo7qKWgvCgN5r1XEtrwTW6BoEQYM0HONC/cBLHDPPIL98noUohAqjR7uvWVV1HlVbfWV+o/t8J6DJOJ4jRY6idmVKnmVS1q8tK8zfBAAA")
format("woff2"),
url("//at.alicdn.com/t/font_1755564_z4mhxbw13mq.woff?t=1586882866168")
format("woff"),
url("//at.alicdn.com/t/font_1755564_z4mhxbw13mq.ttf?t=1586882866168")
format("truetype"),
/* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url("//at.alicdn.com/t/font_1755564_z4mhxbw13mq.svg?t=1586882866168#iconfont")
format("svg");
/* iOS 4.1- */
}
#bbtalk img {
display: block;
max-height: 16rem;
}

#bbtalk-content a:not(.leaflet-control-container a) {
display: inline-block;
text-align: left;
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

.icon-lianjie:before {
content: "\e6a3";
}

.icon-lianjie-copy:before {
content: "\e6a4";
}

#article-container .banner-button {
height: 40px;
width: 118px;
border-radius: 20px;
justify-content: center;
background: #67b010;
color: #fff;;
align-items: center;
z-index: 1;
transition: .3s;
cursor: pointer;
backdrop-filter: saturate(180%) blur(20px);
-webkit-backdrop-filter: blur(20px);
transform: translateZ(0);
border-bottom: unset !important;
right: 1.4rem;
position: absolute;
display: inline-flex;
bottom: 0.68rem;
}

#article-container .banner-button:hover {
background: #007909;
color: #fff;
}

#article-container .banner-button i {
margin-right: 6px;
font-size: 22px
}

@media screen and (max-width: 1200px) {
#article-container .banner-button {
height: 32px;
width: 98px;
right: 0.8rem;
font-size: 14px;
}

#article-container .banner-button i {
margin-right: 4px;
font-size: 16px
}

.map-box .leaflet-container {
height: 300px;
}
}
/* bb样式 */
/* 移动端自定义布局适配 */
@media screen and (max-width: 768px) {
#bbtalk div.timenode .body {
margin: 0;
padding: 5px;
border-radius: 8px;
display: block;
}
#bbtalk div.timenode .card-name {
display: flex;
align-items: center;
margin: 10px 5px 5px 5px;
}
#bbtalk div.timenode {
padding: 8px 14px;
margin-top: 1.4rem;
}
}
div.timenode {
position: relative;
padding: 10px 20px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.1);
box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.06);
overflow: hidden;
margin-top: 32px;
user-select: none;
}
div.timenode:hover {
box-shadow: 0 2px 10px 5px rgba(7, 17, 27, 0.16);
transition: all .15s ease-in-out;
}
div.timenode:last-child:after {
height: calc(100% - 26px - 16px);
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
div.timenode .time {
position: relative;
font-size: 12px;
line-height: 32px;
height: 32px;
margin-left: 10px;
cursor: pointer;
}
div.timenode .time p {
font-weight: bold;
margin: 0 0 0 24px;
}
div.timenode .body:empty {
display: none;
}
div.timenode .body {
margin: 0 0 16px;
}
div.timenode .body > *:first-child {
margin: 0.25em 0 0.8rem 0;
}
div.timenode .body > *:last-child {
margin-bottom: 0.25em;
}
div.timenode .body .highlight {
border: 1px solid #e4e4e4;
}
div.timenode time::after {
content: attr(data-msg);
box-sizing: border-box;
width: 0;
height: 0;
position: absolute;
left: 0;
overflow: hidden;
}
div.timenode time:hover:after {
background: var(--bb-TimeTitle-bg);
color: var(--bb-TimeTitle);
left: calc(50% - 70px);
top: 36px;
padding: 8px 10px;
border-radius: 6px;
width: 180px;
height: auto;
opacity: 0.9;
line-height: 1.2;
text-align: center;
font-size: 14px;
font-weight: 500;
transition: opacity 1s;
z-index: 10;
}
/* 小三角标 */
div.timenode time::before {
content: "";
box-sizing: border-box;
position: absolute;
opacity: 0;
}
div.timenode time:hover:before {
top: 24px;
left: calc(50% - 4px);
text-align: center;
width: 8px;
height: 6px;
border: 6px dashed var(--bb-TimeTitle-bg);
border-color: transparent transparent var(--bb-TimeTitle-bg) transparent;
opacity: 0.9;
/* transition: opacity 0.5s; */
}
div.timenode .body {
margin: 0 15px;
padding: 0 16px 16px 16px;
border-radius: 8px;
/* background: #f6f6f6; */
display: block;
}

/* 微信语音消息样式 */
#bbtalk-media .aplayer.aplayer-withlrc.aplayer-withlist {
position: relative;
margin: 0;
padding: 0;
border: none;
background-color: transparent;
font-size: 14px;
line-height: 1.5;
color: #333;
}

#bbtalk-media .aplayer.aplayer-withlrc.aplayer-withlist .aplayer-body {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
width: 100%;
height: 40px;
margin: 0;
padding: 0;
border-radius: 20px;
background-color: #f5f5f5;
cursor: pointer;
}

#bbtalk-media .aplayer.aplayer-withlrc.aplayer-withlist .aplayer-body:hover {
background-color: #e6e6e6;
}

#bbtalk-media .aplayer.aplayer-withlrc.aplayer-withlist .aplayer-info {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
width: 100%;
height: 100%;
margin: 0;
padding: 0 12px;
border-radius: 20px;
background-color: #f5f5f5;
}

#bbtalk-media .aplayer.aplayer-withlrc.aplayer-withlist .aplayer-info:hover {
background-color: #e6e6e6;
}

#bbtalk-media .aplayer.aplayer-withlrc.aplayer-withlist .aplayer-icon {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
margin: 0;
padding: 0;
border-radius: 20px;
background-color: #4cd964;
color: #fff;
font-size: 18px;
}

#bbtalk-media .aplayer.aplayer-withlrc.aplayer-withlist .aplayer-icon:hover {
background-color: #44c662;
}

#bbtalk-media .aplayer.aplayer-withlrc.aplayer-withlist .aplayer-icon i {
margin: 0;
padding: 0;
}

#bbtalk-media .aplayer.aplayer-withlrc.aplayer-withlist .aplayer-name {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
width: calc(100% - 40px);
height: 100%;
margin: 0;
padding: 0 12px;
border-radius: 20px;
background-color: #f5f5f5;
font-size: 14px;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

#bbtalk-media .aplayer.aplayer-withlrc.aplayer-withlist .aplayer-author {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
width: calc(100% - 40px);
height: 100%;
margin: 0;
padding: 0 12px;
border-radius: 20px;
background-color: #f5f5f5;
font-size: 12px;
color: #999;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

/* 微信分享卡片样式 */
.bbtalk-url {
position: relative;
display: inline-block;
width: 100%;
max-width: 600px;
margin: 10px 0;
padding: 20px;
border-radius: 10px;
background-color: var(--bbtalk-url-bg);
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.2);
text-decoration: auto !important;
}

.bbtalk-url-info {
position: absolute;
top: 20px;
left: 20px;
width: 80px;
height: 80px;
background-size: cover;
border-radius: 10px;
}

.bbtalk-url-info i {
font-size: 4rem;
color: #5d91a366;
}

.bbtalk-url-title {
font-size: 18px;
font-weight: bold;
margin: 0 0 10px 110px;
line-height: 1.4rem;
}

.bbtalk-url-desc {
font-size: 14px;
color: #999;
margin: 0 0 10px 110px;
line-height: 1.2rem;
}

@media screen and (max-width: 768px) {
.bbtalk-url-info {
opacity: 0.6;
bottom: 20px;
right: 20px;
top: unset;
left: unset;
}
.bbtalk-url-title, .bbtalk-url-desc {
margin: 0 0 10px 0px;
}
}

/* 地图卡片样式 */
#bbtalk-content .leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
bottom: -30px !important;
left: -86px !important;
}

#bbtalk-content .map-box {
margin: 0.8rem 0 1.2rem 0;
}

/* 视频样式 */
#bbtalk-content video {
margin-top: 0.8rem;
max-height: 16rem;
}

div.timenode .card-name {
display: flex;
align-items: center;
margin: 15px 5px 15px 15px;
}
div.timenode .card-name .is-badge {
height: 20px;
width: 20px;
margin-left: 8px;
}
div.timenode .card-name .avatar {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 10px;
}
div.timenode .card-name .avatar-img {
width: 100%;
height: unset;
border-radius: 50%;
}
div.timenode .bbtalk-from {
/* background-color: #f596aa; */
color: var(--bbtalk-from-color);
font-weight: 600;
border-radius: 4px;
padding: 0 8px 2px 2px;
width:fit-content;
width:-webkit-fit-content;
width:-moz-fit-content;
font-size: 12px;
}
div.timenode .loading {
text-align: center;
}
@keyframes Gradient {
0% {
background-position: 0 50%;
}

50% {
background-position: 100% 50%;
}

to {
background-position: 0 50%;
}
}
button.next {
cursor: pointer;
color: #fff;
border: 0;
margin: 2rem auto 3rem auto;
border-radius: 0.3125rem;
display: none;
padding: 0 1rem;
height: 40px;
font-weight: 500;
text-align: center;
transition: all 0.5s ease-out;
background: -webkit-linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background-size: 1000% 1000%;
animation: Gradient 10s linear infinite;
outline: 0;
box-shadow: 0 0px 6px 1px rgb(7 17 27 / 15%);
}
button.next:hover {
box-shadow: 0 0px 6px 2px rgb(7 17 27 / 15%);
}
#bbtalk .load-ctn a{padding:8px 18px;background:#eaeded;border-radius:6px;color:#333}
#bbtalk .load-ctn {padding-top:30px;cursor:pointer;}
blockquote {
background: var(--guole-secondbg);
border: 1px solid var(--guole-card-border);
box-shadow: none;
margin: 0;
margin-bottom: 0.5rem;
font-size: 0.6rem;
color: var(--guole-secondtext);
border-radius: 8px;
padding: 0.5rem 0.8rem;
}
blockquote p {
margin: 0 !important;
}
#bbtalk .bbtalk-bottom {
display: flex;
justify-content: space-between;
width: 100%;
-webkit-user-select: none;
}
#bbtalk a.bbtalk-reply {
line-height: initial;
align-items: center;
display: flex;
cursor: pointer;
position: relative;
}
#bbtalk a.bbtalk-reply:hover {
text-decoration: unset;
}
#bbtalk a.bbtalk-reply i {
font-size: 1.3rem;
font-weight: bold;
color: var(--bbtalk-reply)
}
#bbtalk a.bbtalk-reply:hover i {
color: var(--guole-main)
}
#bbtalk a.bbtalk-reply::before {
content: "";
box-sizing: border-box;
position: absolute;
opacity: 0;
}
#bbtalk a.bbtalk-reply:hover:before {
bottom: 24px;
left: 4px;
text-align: center;
width: 8px;
height: 6px;
border: 6px dashed var(--bb-TimeTitle-bg);
border-color: transparent transparent var(--bb-TimeTitle-bg) transparent;
opacity: 0.9;
transform: rotate(180deg);
}
#bbtalk a.bbtalk-reply::after {
content: "引用回复";
box-sizing: border-box;
width: 0;
height: 0;
position: absolute;
left: 0;
overflow: hidden;
height: calc(100% - 26px - 16px);
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
#bbtalk a.bbtalk-reply:hover:after {
background: var(--bb-TimeTitle-bg);
color: var(--bb-TimeTitle);
left: -42px;
bottom: 36px;
padding: 8px 10px;
border-radius: 6px;
width: 100px;
height: auto;
opacity: 0.9;
line-height: 1.2;
text-align: center;
font-size: 14px;
font-weight: 500;
transition: opacity 1s;
z-index: 10;
position: absolute;
}

引入的 timeago.js 内容如下,放到 source/js/ 目录下(其他位置也可,记得修改 md 中地址):

1
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e=e||self).timeago={})}(this,function(e){"use strict";var r=["second","minute","hour","day","week","month","year"];var a=["秒","分钟","小时","天","周","个月","年"];function t(e,t){n[e]=t}function i(e){return n[e]||n.en_US}var n={},f=[60,60,24,7,365/7/12,12];function o(e){return e instanceof Date?e:!isNaN(e)||/^\d+$/.test(e)?new Date(parseInt(e)):(e=(e||"").trim().replace(/\.\d+/,"").replace(/-/,"/").replace(/-/,"/").replace(/(\d)T(\d)/,"$1 $2").replace(/Z/," UTC").replace(/([+-]\d\d):?(\d\d)/," $1$2"),new Date(e))}function d(e,t){for(var n=e<0?1:0,r=e=Math.abs(e),a=0;e>=f[a]&&a<f.length;a++)e/=f[a];return(0===(a*=2)?9:1)<(e=Math.floor(e))&&(a+=1),t(e,a,r)[n].replace("%s",e.toString())}function l(e,t){return((t?o(t):new Date)-o(e))/1e3}var s="timeago-id";function h(e){return parseInt(e.getAttribute(s))}var p={},v=function(e){clearTimeout(e),delete p[e]};function m(e,t,n,r){v(h(e));var a=r.relativeDate,i=r.minInterval,o=l(t,a);e.innerText=d(o,n);var u,c=setTimeout(function(){m(e,t,n,r)},Math.min(1e3*Math.max(function(e){for(var t=1,n=0,r=Math.abs(e);e>=f[n]&&n<f.length;n++)e/=f[n],t*=f[n];return r=(r%=t)?t-r:t,Math.ceil(r)}(o),i||1),2147483647));p[c]=0,u=c,e.setAttribute(s,u)}t("en_US",function(e,t){if(0===t)return["just now","right now"];var n=r[Math.floor(t/2)];return 1<e&&(n+="s"),[e+" "+n+" ago","in "+e+" "+n]}),t("zh_CN",function(e,t){if(0===t)return["刚刚","片刻后"];var n=a[~~(t/2)];return[e+" "+n+"前",e+" "+n+"后"]}),e.cancel=function(e){e?v(h(e)):Object.keys(p).forEach(v)},e.format=function(e,t,n){return d(l(e,n&&n.relativeDate),i(t))},e.register=t,e.render=function(e,t,n){var r=e.length?e:[e];return r.forEach(function(e){m(e,e.getAttribute("datetime"),i(t),n||{})}),r},Object.defineProperty(e,"__esModule",{value:!0})});

最后,md 文件引用的 BBtalk.js 代码如下,放到 source/js/ 目录下(其他位置也可,记得修改 md 中地址):

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
var bbtalk = {
limit: 12,
bbtalk_name: '夜的第八章',
bbtalk_avatar: '/img/gl.jpg',
bbtalk_avatar_svg: '<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" class="is-badge"><path d="m512 268c0 17.9-4.3 34.5-12.9 49.7s-20.1 27.1-34.6 35.4c.4 2.7.6 6.9.6 12.6 0 27.1-9.1 50.1-27.1 69.1-18.1 19.1-39.9 28.6-65.4 28.6-11.4 0-22.3-2.1-32.6-6.3-8 16.4-19.5 29.6-34.6 39.7-15 10.2-31.5 15.2-49.4 15.2-18.3 0-34.9-4.9-49.7-14.9-14.9-9.9-26.3-23.2-34.3-40-10.3 4.2-21.1 6.3-32.6 6.3-25.5 0-47.4-9.5-65.7-28.6-18.3-19-27.4-42.1-27.4-69.1 0-3 .4-7.2 1.1-12.6-14.5-8.4-26-20.2-34.6-35.4-8.5-15.2-12.8-31.8-12.8-49.7 0-19 4.8-36.5 14.3-52.3s22.3-27.5 38.3-35.1c-4.2-11.4-6.3-22.9-6.3-34.3 0-27 9.1-50.1 27.4-69.1s40.2-28.6 65.7-28.6c11.4 0 22.3 2.1 32.6 6.3 8-16.4 19.5-29.6 34.6-39.7 15-10.1 31.5-15.2 49.4-15.2s34.4 5.1 49.4 15.1c15 10.1 26.6 23.3 34.6 39.7 10.3-4.2 21.1-6.3 32.6-6.3 25.5 0 47.3 9.5 65.4 28.6s27.1 42.1 27.1 69.1c0 12.6-1.9 24-5.7 34.3 16 7.6 28.8 19.3 38.3 35.1 9.5 15.9 14.3 33.4 14.3 52.4zm-266.9 77.1 105.7-158.3c2.7-4.2 3.5-8.8 2.6-13.7-1-4.9-3.5-8.8-7.7-11.4-4.2-2.7-8.8-3.6-13.7-2.9-5 .8-9 3.2-12 7.4l-93.1 140-42.9-42.8c-3.8-3.8-8.2-5.6-13.1-5.4-5 .2-9.3 2-13.1 5.4-3.4 3.4-5.1 7.7-5.1 12.9 0 5.1 1.7 9.4 5.1 12.9l58.9 58.9 2.9 2.3c3.4 2.3 6.9 3.4 10.3 3.4 6.7-.1 11.8-2.9 15.2-8.7z" fill="#1da1f2"></path></svg>',
aplayer_js: '//unpkg.com/aplayer@1.10.1/dist/APlayer.min.js',
init: function (options) {
this.bbtalk_name = options.bbtalk_name || this.bbtalk_name;
this.bbtalk_avatar = options.bbtalk_avatar || this.bbtalk_avatar;
this.bbtalk_avatar_svg = options.bbtalk_avatar_svg || this.bbtalk_avatar_svg;
this.aplayer_js = options.aplayer_js || this.aplayer_js;
this.leaflet_css = options.leaflet_css || this.leaflet_css;
this.leaflet_js = options.leaflet_js || this.leaflet_js;
this.ChineseTmsProviders_js = options.ChineseTmsProviders_js || this.ChineseTmsProviders_js;
}
};

let bbtalkPage = 1;
let bbtalkIsLoading = false;

function BBtalk_init() {
const loadMoreBtn = document.getElementById('load-more-btn');
const loading = document.getElementById('loading');
const bbTalkBottomTip = document.getElementById('bbtalk-bottom-tip');

if (!loading || !loadMoreBtn || bbtalkIsLoading) return;

bbtalkIsLoading = true;
loading.style.display = 'block';
loadMoreBtn.style.display = 'none';

const timestamp = Math.floor(Date.now() / 1000 / 60);
const jsonUrl = getJsonUrl(bbtalkPage, timestamp); // 生成 JSON URL
//console.log("bbtalkPage: ",bbtalkPage)

fetchData(jsonUrl)
.then(data => {
renderData(data); // 渲染数据
const info = document.querySelector('p.info');
if (info) info.innerHTML = `共计发送 ${data.count} 条闪念`;
bbtalkPage++;
bbtalkIsLoading = false;
loading.style.display = 'none';
loadMoreBtn.style.display = 'block';
loadMoreBtn.addEventListener("click", BBtalk_init);
bbTalkBottomTip.style.display = 'none';
// 检查是否还有更多数据可加载
if (data.results.length < bbtalk.limit) {
loadMoreBtn.style.display = 'none';
loading.style.display = 'none';
bbTalkBottomTip.style.display = 'block';
}
})
.catch(error => {
console.error(error);
bbtalkIsLoading = false;
loading.style.display = 'none';
loadMoreBtn.style.display = 'block';
});
}

function getJsonUrl(page, timestamp) {
// 根据当前页和时间戳生成 JSON URL
return location.host === 'blog.guole.fun'
? `https://cdn.guole.fun/json/bb-json/bbtalk_page${page}.json?t=${timestamp}`
: `/test/bbtalk_page${page}.json?t=${timestamp}`;
}

async function fetchData(url) {
// 从指定 URL 获取数据并返回 JSON 格式
const response = await fetch(url);
if (!response.ok) {
throw new Error(`获取数据失败: ${response.status} - ${response.statusText}`);
}
return response.json();
}

function renderData(data) {
// 渲染获取到的数据
data.results.forEach(item => {
const timenode = createTimenode(item);
const bbtalk = document.getElementById('bbtalk');
if (bbtalk) bbtalk.appendChild(timenode);

if (item.MsgType === 'location') {
const assets = document.createElement('script');
assets.innerHTML = item.other;
document.body.appendChild(assets);
} else if (item.MsgType === 'music') {
renderMusicAPlayer(item);
}
});
}

function createTimenode(item) {
// 创建并返回 timenode 的 DOM 元素
const timenode = document.createElement('div');
let time = formatTime(item.createdAt);
let time_ago = timeago.format(time, 'zh_CN');
let content = urlToLink(item.content, true);
let quote = urlToLink(item.content, item.MsgType, false);
let fromColor;
switch (true) {
case item.from.includes('Chrome'):
fromColor = 'var(--bbtalk-from-chrome)';
break;
case item.from.includes('WeChat'):
fromColor = 'var(--bbtalk-from-wechat)';
break;
case item.from.includes('Postman'):
fromColor = 'var(--bbtalk-from-postman)';
break;
case item.from.includes('Edge'):
fromColor = 'var(--bbtalk-from-edge)';
break;
case item.from.includes('Siri'):
fromColor = 'var(--bbtalk-from-siri)';
break;
default:
fromColor = 'var(--bbtalk-from-bg)';
break;
}
timenode.innerHTML = `
<div class="card-name">
<div class="avatar">
<img src=${bbtalk.bbtalk_avatar} class="avatar-img no-lightbox nolazyload">
</div>
<div class="name">${bbtalk.bbtalk_name}</div>
${bbtalk.bbtalk_avatar_svg}
<div class="time" style="margin-left:18px;">
<p>
<time datetime="${time}" data-msg="${time}">${time_ago}</time>
</p>
</div>
</div>
<div class="body">
<div class="bbtalk-content">
${content}
<div class="bbtalk-media" id="${item.objectId}"></div>
</div>
<div class="bbtalk-bottom">
<div class="bbtalk-from" style="background-color: ${fromColor};color: var(--bbtalk-from-color)">
<span>${item.from}</span>
</div>
<a id="bbtalk-reply" class="bbtalk-reply" onclick="quoteComment(\`${quote}\`)">
<i class="iconfont icon-xiaoxi4"></i>
</a>
</div>
</div>
`;
timenode.classList.add('timenode', 'wow', 'animate__zoomIn');
return timenode;
}

function renderMusicAPlayer(item) {
// 渲染音乐的 APlayer
let lrc = `${item.other["lrc"]}`;
let lrcType = 3;
if (!lrc) {
lrcType = 1;
lrc = "[00:00.00] 暂无歌词";
}
const aplayerCode = `
const ap_${item.objectId} = new APlayer({
container: document.getElementById('${item.objectId}'),
lrcType: ${lrcType},
audio: [{
name: '${item.other["name"]}',
artist: '${item.other["artist"]}',
url: '${item.other["url"]}',
cover: '${item.other["cover"]}',
lrc: '${lrc}',
}]
});
`
const assets = document.createElement('script');
assets.innerHTML = aplayerCode;
document.getElementById(`${item.objectId}`).appendChild(assets);
}

function formatTime(time) {
const timestamp = Date.parse(time);
if (isNaN(timestamp)) return '';
const date = new Date(timestamp);
const year = date.getFullYear();
const month = padStart(date.getMonth() + 1);
const day = padStart(date.getDate());
const hour = padStart(date.getHours());
const minute = padStart(date.getMinutes());
const second = padStart(date.getSeconds());
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
}

function padStart(number) {
return number.toString().padStart(2, '0');
}

function urlToLink(str, msgType = 'text', convertToHtml = true) {
const re = /(?<!<img\s+[^>]*\bsrc=["'])(?<!<video\s+[^>]*\bsrc=["'])(?<!\.(png|jpe?g|webp|gif|amr|speex|mp4|ts|flv)(\?.*)?$)\bhttps?:\/\/\S+\b/g;
const re_forpic = /\bhttps?:[^:<>"]*\/([^:<>"]*)(\.(jpeg)|(png)|(jpg)|(webp)|(gif))/ig;
const music_type = /(&songmid=)|(songid=)|(songDetail)|(\/m\/song\?)|(cdn\.guole\.fun\/mp3\/)/g;
const reVideo = /\bhttps?:[^:<>"]*\/([^:<>"]*)(\.(mp4)|(ts)|(flv))/ig;
const reMap = /(<div[^>]*>(.*?)<\/div>|<\/div>)/is;
const lineBreak = /<br>/g;
const html = /<[^>]+>/g;
const tencet = /y\.qq\.com/g;
const netease = /y\.music\.163\.com/g;
const urlRegex = /((http?s):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|])/g;
let type, server, song_id;

if (convertToHtml) {
str = str.replace(re_forpic, url => `<a href="${url}" target="_blank" data-fancybox="group" class="fancybox"><img src="${url}" data-lazy-src="${url}"></a>`)
.replace(reVideo, url => `<video src="${url}" controls></video>`)
//.replace(re, url => `<a href='${url}' rel='noopener noreferrer' target='_blank'>外部链接</a>`);
if (tencet.test(str) || netease.test(str)) {
let url = str.match(urlRegex)[0];
url = new URL(str);
const urlPath = url.pathname;
const urlParams = new URLSearchParams(url.search);
type = urlPath.includes("playlist") || urlPath.includes("details") ? "playlist" : "song";
server = url.host === "i.y.qq.com" ? "tencent" : "netease";
song_id = urlParams.get("songid") || urlParams.get("songmid") || urlParams.get("id");
str = `<meting-js server="${server}" type="${type}" id="${song_id}"></meting-js>`;
}
if (window.qqWechatEmotionParser) {
str = qqWechatEmotionParser(str);
}
} else {
if (music_type.test(str)) {
str = str.replace(re, " [音乐]");
if (msgType === "music") str = str + " [音乐]" ;
} else {
str = str.replace(re_forpic, " [图片]")
.replace(reVideo, " [视频]")
.replace(reMap, " [位置]")
.replace(re, " [外部链接]")
.replace(lineBreak, "")
.replace(html, "")
}
}
return str;
}

//引用到评论
function quoteComment(txt) {
//rm.hideRightMenu();
let input = document.getElementsByClassName('el-textarea__inner')[0];
let evt = new Event('input', {
bubbles: true,
cancelable: true
});
let inputValue = replaceAll(txt, '\n', '\n> ')
input.value = '> ' + inputValue + '\n\n';
input.dispatchEvent(evt);
let domTop = document.querySelector("#post-comment").offsetTop;
window.scrollTo(0, domTop - 80);
input.focus();
input.setSelectionRange(-1, -1);
if (document.getElementById("comment-tips")) {
document.getElementById("comment-tips").style.display = "flex";
}
}

document.addEventListener("pjax:complete", (function () {
bbtalkPage = 1;
BBtalk_init();
}));

BBtalk_init();

首页轮播说说

自定义或放到其他能全局引入的 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
//- Guo Le's Blog
//- https://guole.fun/
//- 2023.5.10

//首页轮播闪念
var pathInfo = window.location.pathname;
function bbtalkBanner() {
let talkList = document.getElementById('bbtalk-list');
let bbtalkLoading = document.getElementById('bbtalk-loading');
let bbtalkRight = document.getElementById('bbtalk-right');
if (pathInfo === '/') {
if (talkList) {
const timestamp = Math.floor(Date.now() / 1000 / 60);
let jsonUrl;
if (location.host === 'blog.guole.fun') {
jsonUrl = `https://cdn.guole.fun/json/bb-json/bbtalk_page1.json?t=${timestamp}`;
//jsonUrl = 'https://api.guole.fun/bbtalk?page=1&limit=10';
} else {
jsonUrl = `/test/bbtalk_page1.json?t=${timestamp}`
}
let xhr = new XMLHttpRequest();
xhr.open('GET', jsonUrl, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
let res = JSON.parse(xhr.responseText);
let bberHtml = '';
res.results.forEach(function (item, i) {
const d = new Date(item.createdAt);
const date = d.getFullYear() + '/' + (d.getMonth() + 1) + '/' + d.getDate() + ' ' + d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds();
const dataTime = timeago.format(date, 'zh_CN');
const newdataTime = '<span class="datatime">' + dataTime + '</span>';
bberHtml += '<li class="bbtalk-list item-' + (i + 1) + '"><a href="/bb" style="color: var(--font-color);/*font-weight: normal;*/">' + newdataTime + ': ' + urlToLink(item.content,item.MsgType) + '</a></li>';
});
bbtalkLoading.style.display = 'none';
if (bbtalkLoading) bbtalkLoading.remove();
bbtalkRight.style.display = 'unset';
talkList.insertAdjacentHTML('beforeend', bberHtml);
}
};
xhr.send();

function urlToLink(str,MsgType) {
const imgTag = /(<br>)|([[\s\S]*])|(\()|(\))/g;
const reForImg = /\<[img|IMG].*?src=[\'|\"](https\:\/\/.*?(?:[\.jpg|\.jpeg|\.png|\.gif|\.bmp]))[\'|\"].*?[\/]?>/g;
const music = /(y.qq.com)|(music.163.com)/g;
const musicUrl = /[a-zA-z]+:\/\/[^\s]*/g;

if (music.test(str)) {
str = str.replace(musicUrl, function () {
return '<svg viewBox="0 0 1024 1024" width="16" height="16"><path d="M960 0H1024v736c0 88.3712-100.283733 160-224 160S576 824.3712 576 736s100.283733-160 224-160c62.685867 0 119.3472 18.397867 160 48.042667V256l-512 113.783467v494.216533c0 88.3712-100.283733 160-224 160S0 952.3712 0 864s100.283733-160 224-160c62.685867 0 119.3472 18.397867 160 48.042667V128L960 0z"></path></svg>';
});
} else {
if (MsgType === "music") {
str = str + '<svg viewBox="0 0 1024 1024" width="16" height="16"><path d="M960 0H1024v736c0 88.3712-100.283733 160-224 160S576 824.3712 576 736s100.283733-160 224-160c62.685867 0 119.3472 18.397867 160 48.042667V256l-512 113.783467v494.216533c0 88.3712-100.283733 160-224 160S0 952.3712 0 864s100.283733-160 224-160c62.685867 0 119.3472 18.397867 160 48.042667V128L960 0z"></path></svg>';
return str;
}
}

str = str.replace(reForImg, '$1');

const reForMd = /^!\[(.*)\]\((.*)\)/g;
str = str.replace(reForMd, '$2');
str = str.replace(imgTag, '');

const re = /\bhttps?:\/\/(?!\S+(?:jpe?g|png|bmp|gif|webp|gif|mp4))\S+/is;
const reForPic = /\bhttps?:\/\/.*?(\.gif|\.jpe?g|\.png|\.bmp|\.webp)/is;
const reImg = /<img.*?src="(.*?)".*?>/is;
const reA = /<a.*?href="(.*?)".*?>.*?<\/a>/is;
const reVideo = /\bhttps?:[^:<>"]*\/([^:<>"]*)(\.(mp4)|(ts)|(flv))/ig;
const reMap = /(<div[^>]*>(.*?)<\/div>|<\/div>)/is;
let count = 0;

str = str.replace(reImg, function () {
return '<svg viewBox="0 0 1024 1024" width="21" height="21"><path d="M821.6 120.93333333H195.4c-74.1 0-134.2 60.1-134.2 134.2v492c0 74.1 60.1 134.2 134.2 134.2h626.2c74.1 0 134.2-60.1 134.2-134.2v-492c0-74.1-60.1-134.2-134.2-134.2zM251.3 255.13333333c30.9 0 55.9 25 55.9 55.9s-25 55.9-55.9 55.9-55.9-25-55.9-55.9 25-55.9 55.9-55.9z m614.6 559.1H153.3c-37.3 0-58.2-43.1-35.1-72.4L302.1 508.33333333c17.9-22.7 52.4-22.7 70.3 0l76.5 97.2 148.6-260c17.2-30.1 60.5-30.1 77.7 0L904.8 747.33333333c17 29.8-4.5 66.9-38.9 66.9z"></path></svg>';
});

str = str.replace(reForPic, function () {
return '<svg viewBox="0 0 1024 1024" width="21" height="21"><path d="M821.6 120.93333333H195.4c-74.1 0-134.2 60.1-134.2 134.2v492c0 74.1 60.1 134.2 134.2 134.2h626.2c74.1 0 134.2-60.1 134.2-134.2v-492c0-74.1-60.1-134.2-134.2-134.2zM251.3 255.13333333c30.9 0 55.9 25 55.9 55.9s-25 55.9-55.9 55.9-55.9-25-55.9-55.9 25-55.9 55.9-55.9z m614.6 559.1H153.3c-37.3 0-58.2-43.1-35.1-72.4L302.1 508.33333333c17.9-22.7 52.4-22.7 70.3 0l76.5 97.2 148.6-260c17.2-30.1 60.5-30.1 77.7 0L904.8 747.33333333c17 29.8-4.5 66.9-38.9 66.9z"></path></svg>';
});

str = str.replace(reVideo, function () {
return '<svg viewBox="0 0 1025 1024" width="21" height="21"><path d="M892.8 128H131.2c-19.2 0-35.2 6.4-48 22.4-12.8 12.8-19.2 32-19.2 54.4V816c0 44.8 28.8 80 67.2 80h761.6c19.2 0 35.2-6.4 48-22.4s19.2-35.2 19.2-54.4V204.8c0-41.6-32-76.8-67.2-76.8zM224 832h-51.2c-25.6 0-44.8-22.4-44.8-51.2V640h96v192z m0-256h-96V448h96v128z m0-192h-96V243.2c0-28.8 19.2-51.2 44.8-51.2H224v192z m512 32v416H288V192h448v224z m160 364.8c0 28.8-19.2 51.2-44.8 51.2H800V640h96v140.8z m0-204.8h-96V448h96v128z m0-192h-96V192h54.4c22.4 0 41.6 22.4 41.6 51.2V384z" fill="#333333" p-id="4200"></path><path d="M444.8 627.2l144-73.6 25.6-12.8c32-16 32-41.6 0-57.6l-169.6-86.4c-16-6.4-28.8 0-28.8 16v192c0 22.4 12.8 28.8 28.8 22.4z" fill="#333333" p-id="4201"></path></svg>';
});

str = str.replace(reA, function () {
return '<svg viewBox="0 0 1025 1024" width="21" height="21"><path d="M333.06186 733.061768c-58.347896 52.210106-97.040127 49.051159-136.467091 9.492188l-45.156456-48.462758c-39.427988-39.541575-39.427988-103.667058 0-143.226029l193.260585-193.848986c39.426965-39.558971 103.355973-39.558971 142.78396 0l35.679617 35.794228c30.457686 30.555923 37.398772 75.762521 20.801768 112.997564l86.286202 66.040089c59.149145-59.33027 59.149145-155.517983 0-214.830857L523.162476 249.600755c-59.133795-59.33027-155.010423-59.33027-214.160591 0L44.350342 515.071965c-59.133795 59.313897-59.133795 155.50161 0 214.830857l107.08797 107.415428c59.133795 59.313897 155.026796 59.313897 214.176964 0l102.161774-105.647155-72.980151-70.034053L333.06186 733.061768zM987.196021 285.394982 880.1234 177.979554c-59.149145-59.33027-155.026796-59.33027-214.176964 0 0 0 4.223185-1.064238-57.988716 61.343113l71.113641 68.167542 31.604812-34.877345c39.427988-39.541575 103.356996-39.541575 142.78396 0l35.69599 35.8106c39.427988 39.541575 39.427988 103.667058 0 143.226029L714.818517 632.847345c-39.427988 39.541575-103.340623 39.541575-142.768611 0l-29.395494-48.462758c-61.883419-46.25344-42.865273-57.317427-37.611619-88.544639L426.548044 418.130076c-59.150168 59.33027-59.150168 155.517983 0 214.830857l107.072621 107.432825c59.149145 59.312874 155.026796 59.312874 214.176964 0l239.398392-240.166895C1071.582967 402.924769 987.196021 285.394982 987.196021 285.394982z"></path></svg>';
});

str = str.replace(re, function () {
return '<svg viewBox="0 0 1025 1024" width="21" height="21"><path d="M333.06186 733.061768c-58.347896 52.210106-97.040127 49.051159-136.467091 9.492188l-45.156456-48.462758c-39.427988-39.541575-39.427988-103.667058 0-143.226029l193.260585-193.848986c39.426965-39.558971 103.355973-39.558971 142.78396 0l35.679617 35.794228c30.457686 30.555923 37.398772 75.762521 20.801768 112.997564l86.286202 66.040089c59.149145-59.33027 59.149145-155.517983 0-214.830857L523.162476 249.600755c-59.133795-59.33027-155.010423-59.33027-214.160591 0L44.350342 515.071965c-59.133795 59.313897-59.133795 155.50161 0 214.830857l107.08797 107.415428c59.133795 59.313897 155.026796 59.313897 214.176964 0l102.161774-105.647155-72.980151-70.034053L333.06186 733.061768zM987.196021 285.394982 880.1234 177.979554c-59.149145-59.33027-155.026796-59.33027-214.176964 0 0 0 4.223185-1.064238-57.988716 61.343113l71.113641 68.167542 31.604812-34.877345c39.427988-39.541575 103.356996-39.541575 142.78396 0l35.69599 35.8106c39.427988 39.541575 39.427988 103.667058 0 143.226029L714.818517 632.847345c-39.427988 39.541575-103.340623 39.541575-142.768611 0l-29.395494-48.462758c-61.883419-46.25344-42.865273-57.317427-37.611619-88.544639L426.548044 418.130076c-59.150168 59.33027-59.150168 155.517983 0 214.830857l107.072621 107.432825c59.149145 59.312874 155.026796 59.312874 214.176964 0l239.398392-240.166895C1071.582967 402.924769 987.196021 285.394982 987.196021 285.394982z"></path></svg>';
});

while (reMap.test(str)) {
if (count === 0) {
str = str.replace(reMap, function () {
return '<svg viewBox="0 0 1025 1024" width="21" height="21"><path d="M512 85.333333a233.386667 233.386667 0 0 0-234.666667 232.746667c0 85.333333 69.973333 185.6 207.573334 308.053333a40.96 40.96 0 0 0 54.186666 0C676.693333 503.68 746.666667 402.56 746.666667 318.08A233.386667 233.386667 0 0 0 512 85.333333z m0 448c-100.053333-91.306667-149.333333-164.266667-149.333333-215.253333a149.333333 149.333333 0 0 1 298.666666 0c0 51.626667-49.066667 124.586667-149.333333 215.253333z" p-id="9700"></path><path d="M933.546667 788.053333l1.92-3.84 1.28-3.84c0-1.28 0-2.56 1.066666-4.053333a33.066667 33.066667 0 0 0 0-6.613333V490.666667a42.666667 42.666667 0 0 0-85.333333 0v249.386666l-251.52 108.16-258.56-159.146666h-1.493333a14.08 14.08 0 0 0-4.053334-1.92l-3.84-1.706667h-24.32l-3.626666 1.28a15.36 15.36 0 0 0-4.266667 1.92h-1.493333L170.666667 759.68V490.666667a42.666667 42.666667 0 0 0-85.333334 0v343.466666a38.4 38.4 0 0 0 0 4.693334 24.746667 24.746667 0 0 0 0 3.84l1.28 4.053333a35.626667 35.626667 0 0 0 1.706667 4.266667 7.68 7.68 0 0 1 0 1.92 36.693333 36.693333 0 0 0 5.973333 7.893333 44.16 44.16 0 0 0 12.373334 7.466667l2.346666 1.493333a42.666667 42.666667 0 0 0 7.253334 2.773333h2.56A39.253333 39.253333 0 0 0 128 874.666667a49.066667 49.066667 0 0 0 9.6 0h2.56a38.186667 38.186667 0 0 0 6.826667-2.773334h1.493333l170.666667-94.506666 256 157.013333c1.92 0 3.84 1.493333 5.546666 2.346667a26.026667 26.026667 0 0 0 5.12 2.133333 42.666667 42.666667 0 0 0 10.453334 1.706667H597.333333a49.493333 49.493333 0 0 0 9.6-1.28l2.986667-1.066667 4.266667-1.28 298.666666-128a40.96 40.96 0 0 0 6.613334-3.84l2.986666-2.346667 3.2-2.773333a39.04 39.04 0 0 0 2.773334-3.413333l2.56-3.2z" p-id="9701"></path><path d="M512 320m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0Z" p-id="9702"></path></svg>';
});
} else {
str = str.replace(reMap, "");
}
count++;
}
str = str.replace(/\s+</g, '<');
return str;
}
}
}
}

function bbtalkRoll() {
const bbtalkLi = document.querySelectorAll('li.bbtalk-list');
if (bbtalkLi.length >= 2) {
const curLi = bbtalkLi[0];
const lastLi = bbtalkLi[bbtalkLi.length - 1];
lastLi.parentNode.insertBefore(curLi, lastLi.nextSibling);
}
}

setInterval(bbtalkRoll, 3000);

//pjax 兼容
document.addEventListener("pjax:complete", (function () {
pathInfo = window.location.pathname;
bbtalkBanner();
console.log('触发 pjax:complete 事件。')
}))

bbtalkBanner();

theme/Butterfly/layout/index.pug中加入以下内容,icon 我用了阿里的,你可以自己换。

1
2
3
4
5
6
7
8
9
10
11
12
extends includes/layout.pug

block content
include ./includes/mixins/post-ui.pug
#recent-posts.recent-posts
#bber-talk(style='display: flex; ')
a.index-talk-left(style="margin-right: 10px;cursor: pointer;" href='/bb/' title='闪念')
i.iconfont.icon-xiaoxi4(style='font-size: 1.2rem; color: var(--guole-font-color);')
ul.talk-list(style='margin: 0; padding: 0; height: 36px; overflow: hidden; margin-bottom: -4px; transition: all 1s ease-in-out;')
li.item.loading= _p('闪念加载中…')
a.index-talk-right(style='text-align: right; position: relative; cursor: pointer; display:none' href='/bb/' title='查看全文')
i.iconfont.icon-you(style="margin-left: 5px; font-size: 1.2rem; color: var(--font-color);")

css 样式随便找个地方丢进去,全局引用即可。有不对的,在本站再扒一下,太久远了 很难翻。

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
/* 首页 bber 轮播 */
#bber-talk {
color: var(--guole-font-color);
padding: 0.4rem 1rem 0.4rem 0.7rem;
cursor: auto;
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
overflow: hidden;
transition: all 0.3s;
z-index: 10;
position: relative;
/* margin-top: 1rem; */
animation: slide-in 0.6s 0.4s backwards;
will-change: auto;
border: 1px solid #e3e8f7;
border-radius: 8px;
position: -webkit-sticky;
position: sticky;
}

@media screen and (min-width: 768px) {
#bber-talk:hover,
.recent-post-item:hover {
box-shadow: var(--guole-shadow-main) !important;
}
.card-widget,.card-widget:hover {
box-shadow: var(--guole-shadow-border) !important;
}
}

@media screen and (min-width: 1300px) {
#bber-talk {
margin-bottom: 1rem;
animation: slide-in 0.6s 0s backwards;
}
}

#bber-talk:not(a) {
font-weight: bold;
}

#bber-talk svg {
fill: currentColor;
vertical-align: middle;
display: inline;
margin-left: 5px;
margin-right: 5px;
margin-bottom: 2px;
}

@media (any-hover: hover) {
#bber-talk a.index-talk-left:hover i,
#bber-talk a.index-talk-right:hover i {
color: var(--guole-theme-color) !important;
transition: all 0.3s;
}

#bber-talk .talk-list:hover {
color: var(--guole-theme-color) !important;
transition: all .15s ease-in-out;
}
}

#bber-talk .talk-list li.item a {
cursor: pointer;
font-size: 16px;
}

#bber-talk .talk-list li {
list-style: none;
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}

大功告成

到这里,应该七七八八了。自己关注上面的微信号,发送以下指令绑定。Binding_Key是云函数配置的环境变量。

1
/b bb,${Binding_Key}

假如我的Binding_Key环境变量中配置为123456(实际使用时,尽量设置复杂点啊……比如 32 位随机字符串),那么绑定的命令就是:/b bb,123456

云函数代码中,我用了一些正则,如果换成你自己的,自行检查更新下正则内容……我大部分都是匹配 cdn.guole.fun 这个域名,处理一些逻辑的。上传图片 / 视频的逻辑,已抽取成环境变量,可自由配置(正则也做了环境变量导入)。如果你没有添加 自定义域名,那云函数index.js里不要加serverURL

1
2
3
4
5
6
AV.init({
appId: LeanCloud_ID,
appKey: LeanCloud_KEY,
masterKey: LeanCloud_MasterKey,
//serverURL: 'https://leancloud.guole.fun',
});

哔哔秘笈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
「哔哔秘笈」
==================
/l 查询最近 10 条哔哔
/l 数字 - 查询最近前几条,如 /l3
---------------
/a 文字 - 最新一条原内容后追加文字
/a 数字 文字 - 第几条原内容后追加文字,如 /a3 开心!
---------------
/f 文字 - 最新一条原内容前插入文字
/f 数字 文字 - 第几条原内容前插入文字,如 /f3 开心!
---------------
/s 关键词 - 搜索内容
---------------
/d 数字 - 删除第几条,如 /d2
---------------
/e 文字 - 编辑替换第 1 条
/e 数字 文字 - 编辑替换第几条,如 /e2 新内容
---------------
/nobber - 解除绑定

接下来,发条说说试试看吧。也欢迎关注我的,试试效果。

关注我的公众号「哔哔闪念」先行体验

哔哔闪念