Last active 1748142743

将ech0说说展示到前端网站页面

ech0-shuoshuo.js Raw
1function renderTalks() {
2 const talkContainer = document.querySelector('#talk');
3 if (!talkContainer) return;
4 talkContainer.innerHTML = '';
5 const generateIconSVG = () => {
6 return `<svg viewBox="0 0 512 512"xmlns="http://www.w3.org/2000/svg"class="is-badge icon"><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>`;
7 }
8 const waterfall = (a) => {
9 function b(a, b) {
10 var c = window.getComputedStyle(b);
11 return parseFloat(c["margin" + a]) || 0
12 }
13
14 function c(a) {
15 return a + "px"
16 }
17
18 function d(a) {
19 return parseFloat(a.style.top)
20 }
21
22 function e(a) {
23 return parseFloat(a.style.left)
24 }
25
26 function f(a) {
27 return a.clientWidth
28 }
29
30 function g(a) {
31 return a.clientHeight
32 }
33
34 function h(a) {
35 return d(a) + g(a) + b("Bottom", a)
36 }
37
38 function i(a) {
39 return e(a) + f(a) + b("Right", a)
40 }
41
42 function j(a) {
43 a = a.sort(function (a, b) {
44 return h(a) === h(b) ? e(b) - e(a) : h(b) - h(a)
45 })
46 }
47
48 function k(b) {
49 f(a) != t && (b.target.removeEventListener(b.type, arguments.callee), waterfall(a))
50 }
51 "string" == typeof a && (a = document.querySelector(a));
52 var l = [].map.call(a.children, function (a) {
53 return a.style.position = "absolute", a
54 });
55 a.style.position = "relative";
56 var m = [];
57 l.length && (l[0].style.top = "0px", l[0].style.left = c(b("Left", l[0])), m.push(l[0]));
58 for (var n = 1; n < l.length; n++) {
59 var o = l[n - 1],
60 p = l[n],
61 q = i(o) + f(p) <= f(a);
62 if (!q) break;
63 p.style.top = o.style.top, p.style.left = c(i(o) + b("Left", p)), m.push(p)
64 }
65 for (; n < l.length; n++) {
66 j(m);
67 var p = l[n],
68 r = m.pop();
69 p.style.top = c(h(r) + b("Top", p)), p.style.left = c(e(r)), m.push(p)
70 }
71 j(m);
72 var s = m[0];
73 a.style.height = c(h(s) + b("Bottom", s));
74 var t = f(a);
75 window.addEventListener ? window.addEventListener("resize", k) : document.body.onresize = k
76 };
77
78 const fetchAndRenderTalks = () => {
79 const url = 'https://says.liushen.fun/api';
80 const cacheKey = 'talksCache';
81 const cacheTimeKey = 'talksCacheTime';
82 const cacheDuration = 30 * 60 * 1000; // 半个小时 (30 分钟)
83
84 const cachedData = localStorage.getItem(cacheKey);
85 const cachedTime = localStorage.getItem(cacheTimeKey);
86 const currentTime = new Date().getTime();
87
88 // 判断缓存是否有效
89 if (cachedData && cachedTime && (currentTime - cachedTime < cacheDuration)) {
90 const data = JSON.parse(cachedData);
91 renderTalks(data); // 使用缓存渲染数据
92 } else {
93 if (talkContainer) {
94 talkContainer.innerHTML = '';
95 fetch(url + "/messages/page", {
96 method: 'POST',
97 headers: {
98 'Content-Type': 'application/json',
99 },
100 body: JSON.stringify({
101 page: 1,
102 pageSize: 30
103 })
104 })
105 .then(res => res.json())
106 .then(data => {
107 if (data.code === 1 && data.data && Array.isArray(data.data.items)) {
108 // 缓存数据
109 localStorage.setItem(cacheKey, JSON.stringify(data.data.items));
110 localStorage.setItem(cacheTimeKey, currentTime.toString());
111 renderTalks(data.data.items); // 渲染数据
112 }
113 })
114 .catch(error => {
115 console.error('Error fetching data:', error);
116 });
117 }
118 }
119
120 // 渲染函数
121 function renderTalks(list) {
122 // 确保 data 是一个数组
123 if (Array.isArray(list)) {
124 let items = list.map(item => formatTalk(item, url));
125 items.forEach(item => talkContainer.appendChild(generateTalkElement(item)));
126 waterfall('#talk');
127 } else {
128 console.error('Data is not an array:', list);
129 }
130 }
131 };
132
133
134 const formatTalk = (item, url) => {
135 let date = formatTime(new Date(item.created_at).toString());
136 let content = item.content;
137 const baseUrl = url;
138 let imgs = Array.isArray(item.images) ? item.images.map(img => {
139 const imageUrl = img.image_url;
140 // 如果是相对地址(不以 http 或 https 开头),则拼接 baseUrl
141 return /^https?:\/\//.test(imageUrl) ? imageUrl : `${baseUrl}${imageUrl}`;
142 }) : [];
143 let text = content;
144 content = text.replace(/\[(.*?)\]\((.*?)\)/g, `<a href="$2" target="_blank" rel="nofollow noopener noreferrer">@$1</a>`)
145 .replace(/- \[ \]/g, '⚪')
146 .replace(/- \[x\]/g, '⚫');
147 // 保留换行符,转换 \n 为 <br>
148 content = content.replace(/\n/g, '<br>');
149 // 将content用一个类包裹,便于后续处理
150 content = `<div class="talk_content_text">${content}</div>`;
151 if (imgs.length > 0) {
152 const imgDiv = document.createElement('div');
153 imgDiv.className = 'zone_imgbox';
154 imgs.forEach(e => {
155 const imgLink = document.createElement('a');
156 imgLink.href = e;
157 imgLink.setAttribute('data-fancybox', 'gallery');
158 imgLink.className = 'fancybox';
159 imgLink.setAttribute('data-thumb', e);
160 const imgTag = document.createElement('img');
161 imgTag.src = e;
162 imgLink.appendChild(imgTag);
163 imgDiv.appendChild(imgLink);
164 });
165 content += imgDiv.outerHTML;
166 }
167
168 // 外链分享功能
169 if (item.extension_type ==="WEBSITE") {
170 let extension = item.extension ? JSON.parse(item.extension) : {};
171 const externalUrl = extension.site;
172 const externalTitle = extension.title;
173 const externalFavicon = "https://p.liiiu.cn/i/2025/03/13/67d2fc82d329c.webp" // item.externalFavicon;
174
175 const externalContainer = `
176 <div class="shuoshuo-external-link">
177 <a class="external-link" href="${externalUrl}" target="_blank" rel="nofollow noopener noreferrer">
178 <div class="external-link-left" style="background-image: url(${externalFavicon})"></div>
179 <div class="external-link-right">
180 <div class="external-link-title">${externalTitle}</div>
181 <div>点击跳转<i class="fa-solid fa-angle-right"></i></div>
182 </div>
183 </a>
184 </div>`;
185
186 content += externalContainer;
187 }
188
189 // const ext = JSON.parse(item.ext || '{}');
190
191 else if (item.extension_type === "MUSIC") {
192 const musicUrl = item.extension;
193 if (musicUrl && musicUrl.includes('music.163.com')) {
194 const match = musicUrl.match(/id=(\d+)/);
195 const musicServer = "netease";
196 const musicType = "song";
197 const musicId = match ? match[1] : '';
198
199 content += `
200 <meting-js server="${musicServer}" type="${musicType}" id="${musicId}" api="https://met.liiiu.cn/meting/api?server=:server&type=:type&id=:id&r=:r"></meting-js>
201 `;
202 } else {
203 // 懒得适配,自己搞
204 }
205 }
206
207 // if (ext.doubanMovie && ext.doubanMovie.id) {
208 // const doubanMovie = ext.doubanMovie;
209 // const doubanMovieUrl = doubanMovie.url;
210 // const doubanTitle = doubanMovie.title;
211 // // const doubanDesc = doubanMovie.desc || '暂无描述';
212 // const doubanImage = doubanMovie.image;
213 // const doubanDirector = doubanMovie.director || '未知导演';
214 // const doubanRating = doubanMovie.rating || '暂无评分';
215 // // const doubanReleaseDate = doubanMovie.releaseDate || '未知上映时间';
216 // // const doubanActors = doubanMovie.actors || '未知演员';
217 // const doubanRuntime = doubanMovie.runtime || '未知时长';
218
219 // content += `
220 // <a class="douban-card" href="${doubanMovieUrl}" target="_blank" rel="nofollow noopener noreferrer">
221 // <div class="douban-card-bgimg" style="background-image: url('${doubanImage}');"></div>
222 // <div class="douban-card-left">
223 // <div class="douban-card-img" style="background-image: url('${doubanImage}');"></div>
224 // </div>
225 // <div class="douban-card-right">
226 // <div class="douban-card-item"><span>电影名: </span><strong>${doubanTitle}</strong></div>
227 // <div class="douban-card-item"><span>导演: </span><span>${doubanDirector}</span></div>
228 // <div class="douban-card-item"><span>评分: </span><span>${doubanRating}</span></div>
229 // <div class="douban-card-item"><span>时长: </span><span>${doubanRuntime}</span></div>
230 // </div>
231 // </a>
232 // `;
233 // }
234
235 // if (ext.doubanBook && ext.doubanBook.id) {
236 // const doubanBook = ext.doubanBook;
237 // const bookUrl = doubanBook.url;
238 // const bookTitle = doubanBook.title;
239 // // const bookDesc = doubanBook.desc;
240 // const bookImage = doubanBook.image;
241 // const bookAuthor = doubanBook.author;
242 // const bookRating = doubanBook.rating;
243 // const bookPubDate = doubanBook.pubDate;
244
245 // const bookTemplate = `
246 // <a class="douban-card" href="${bookUrl}" target="_blank" rel="nofollow noopener noreferrer">
247 // <div class="douban-card-bgimg" style="background-image: url('${bookImage}');"></div>
248 // <div class="douban-card-left">
249 // <div class="douban-card-img" style="background-image: url('${bookImage}');"></div>
250 // </div>
251 // <div class="douban-card-right">
252 // <div class="douban-card-item">
253 // <span>书名: </span><strong>${bookTitle}</strong>
254 // </div>
255 // <div class="douban-card-item">
256 // <span>作者: </span><span>${bookAuthor}</span>
257 // </div>
258 // <div class="douban-card-item">
259 // <span>出版年份: </span><span>${bookPubDate}</span>
260 // </div>
261 // <div class="douban-card-item">
262 // <span>评分: </span><span>${bookRating}</span>
263 // </div>
264 // </div>
265 // </a>
266 // `;
267
268 // content += bookTemplate;
269 // }
270
271 else if (item.extension_type === "VIDEO") {
272 // const videoType = ext.video.type;
273 const BVId = item.extension;
274 // Bilibili 视频模板
275 // const NewVideoUrl = videoUrl.replace("player.bilibili.com/player.html", "www.bilibili.com/blackboard/html5mobileplayer.html")
276 const biliTemplate = `
277 <div style="position: relative; padding: 30% 45%; margin-top: 10px;">
278 <iframe
279 style="position: absolute; width: 100%; height: 100%; left: 0; top: 0; border-radius: 12px;"
280 src="https://www.bilibili.com/blackboard/html5mobileplayer.html?bvid=${BVId}&as_wide=1&high_quality=1&danmaku=0"
281 scrolling="no"
282 border="0"
283 frameborder="no"
284 framespacing="0"
285 allowfullscreen="true"
286 loading="lazy"
287 >
288 </iframe>
289 </div>
290 `;
291 // 将模板插入到 DOM 中
292 content += biliTemplate;
293
294
295 // else if (videoType === 'youtube') {
296 // // YouTube 视频模板
297 // // 从形如https://youtu.be/2V6lvCUPT8I?si=DVhUas6l6qlAr6Ru的链接中提取视频 ID2V6lvCUPT8I
298 // const youtubeTemplate = `
299 // <div style="position: relative; padding: 30% 45%; margin-top: 10px;">
300 // <iframe width="100%"
301 // style="position: absolute; width: 100%; height: 100%; left: 0; top: 0; border-radius: 12px;"
302 // src="${videoUrl}"
303 // title="YouTube video player"
304 // frameborder="0"
305 // allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
306 // referrerpolicy="strict-origin-when-cross-origin"
307 // allowfullscreen>
308 // </iframe>
309 // </div>
310 // `;
311 // // 将模板插入到 DOM 中
312 // content += youtubeTemplate;
313 // }
314 }
315
316 return {
317 content: content,
318 user: item.username || '匿名',
319 avatar: 'https://p.liiiu.cn/i/2024/03/29/66061417537af.png',
320 date: date,
321 location: item.location || '陕西西安',
322 tags: item.tags ? item.tags.split(',').filter(tag => tag.trim() !== '') : ['日常点滴'],
323 text: content.replace(/\[(.*?)\]\((.*?)\)/g, '[链接]' + `${imgs.length ? '[图片]' : ''}`)
324 };
325 };
326
327 const generateTalkElement = (item) => {
328 const talkItem = document.createElement('div');
329 talkItem.className = 'talk_item';
330
331 const talkMeta = document.createElement('div');
332 talkMeta.className = 'talk_meta';
333
334 const avatar = document.createElement('img');
335 avatar.className = 'no-lightbox avatar';
336 avatar.src = item.avatar;
337
338 const info = document.createElement('div');
339 info.className = 'info';
340
341 const talkNick = document.createElement('span');
342 talkNick.className = 'talk_nick';
343 talkNick.innerHTML = `${item.user} ${generateIconSVG()}`;
344
345 const talkDate = document.createElement('span');
346 talkDate.className = 'talk_date';
347 talkDate.textContent = item.date;
348
349 const talkContent = document.createElement('div');
350 talkContent.className = 'talk_content';
351 talkContent.innerHTML = item.content;
352
353 const talkBottom = document.createElement('div');
354 talkBottom.className = 'talk_bottom';
355
356 const TagContainer = document.createElement('div');
357
358 const talkTag = document.createElement('span');
359 talkTag.className = 'talk_tag';
360 talkTag.textContent = `🏷️${item.tags}`;
361
362 const locationTag = document.createElement('span');
363 locationTag.className = 'location_tag';
364 locationTag.textContent = `🌍${item.location}`;
365
366 TagContainer.appendChild(talkTag);
367 TagContainer.appendChild(locationTag);
368
369 const commentLink = document.createElement('a');
370 commentLink.href = 'javascript:;';
371 commentLink.onclick = () => goComment(item.text);
372 const commentIcon = document.createElement('span');
373 commentIcon.className = 'icon';
374 const commentIconInner = document.createElement('i');
375 commentIconInner.className = 'fa-solid fa-message fa-fw';
376 commentIcon.appendChild(commentIconInner);
377 commentLink.appendChild(commentIcon);
378
379 talkMeta.appendChild(avatar);
380 info.appendChild(talkNick);
381 info.appendChild(talkDate);
382 talkMeta.appendChild(info);
383 talkItem.appendChild(talkMeta);
384 talkItem.appendChild(talkContent);
385 talkBottom.appendChild(TagContainer);
386 talkBottom.appendChild(commentLink);
387 talkItem.appendChild(talkBottom);
388
389 return talkItem;
390 };
391
392 const goComment = (e) => {
393 const match = e.match(/<div class="talk_content_text">([\s\S]*?)<\/div>/);
394 const textContent = match ? match[1] : "";
395 const n = document.querySelector(".atk-textarea");
396 n.value = `> ${textContent}\n\n`;
397 n.focus();
398 btf.snackbarShow("已为您引用该说说,不删除空格效果更佳");
399 // const n = document.querySelector(".atk-textarea");
400 // n.value = `> ${e}\n\n`;
401 // n.focus();
402 // btf.snackbarShow("已为您引用该说说,不删除空格效果更佳");
403 };
404
405 const formatTime = (time) => {
406 const d = new Date(time);
407 const ls = [
408 d.getFullYear(),
409 d.getMonth() + 1,
410 d.getDate(),
411 d.getHours(),
412 d.getMinutes(),
413 d.getSeconds(),
414 ];
415 const r = ls.map((a) => (a.toString().length === 1 ? '0' + a : a));
416 return `${r[0]}-${r[1]}-${r[2]} ${r[3]}:${r[4]}`;
417 };
418
419 fetchAndRenderTalks();
420}
421
422renderTalks();
423
424// function whenDOMReady() {
425// const talkContainer = document.querySelector('#talk');
426// talkContainer.innerHTML = '';
427// fetchAndRenderTalks();
428// }
429// whenDOMReady();
430// document.addEventListener("pjax:complete", whenDOMReady);
twikoo-comments.js Raw
1const goComment = (e) => {
2 const n = document.querySelector(".el-textarea__inner");
3 n.value = `> ${e}\n\n`;
4 n.focus();
5 btf.snackbarShow("已为您引用该说说,不删除空格效果更佳");
6 };
7
8// 如果是twikoo评论区,请自行替换goComment函数,注意,如果评论区开了懒加载可能导致无法找到元素。