mirror of
https://github.com/Mabbs/mabbs.github.io
synced 2025-09-28 16:35:42 +08:00
Compare commits
38 Commits
AR-Backup-
...
AR-Backup-
Author | SHA1 | Date | |
---|---|---|---|
c7cabd991a | |||
9382acdabd | |||
78d65eae30 | |||
7a450f5ec2 | |||
9170efdaa3 | |||
d3eefbba2d | |||
3bfbd78385 | |||
8c26bc57d5 | |||
85594ca8b1 | |||
05ba801c23 | |||
2f6eadd14f | |||
f455ccfdd7 | |||
87807dd50c | |||
85d90f5f2f | |||
aa83c0efc1 | |||
b0bf30adcd | |||
ae668ef412 | |||
763f0768ea | |||
b0704e26fc | |||
1b4fd0de9b | |||
eee3103f93 | |||
3ab930348e | |||
19f5a7b7f9 | |||
e6bf9e886e | |||
82d6628c12 | |||
e7bc272a81 | |||
7785bc18c7 | |||
9a0af3f555 | |||
b1d25236a5 | |||
7ce15b01f8 | |||
c374f914ac | |||
24f8def5ab | |||
796511e5eb | |||
bb46247e97 | |||
4fb00a1975 | |||
e1977bd6ae | |||
4d12271d57 | |||
433d5110c2 |
1
5b60338bca964816af2f0b76965a1b84.txt
Normal file
1
5b60338bca964816af2f0b76965a1b84.txt
Normal file
@ -0,0 +1 @@
|
||||
5b60338bca964816af2f0b76965a1b84
|
1
Gemfile
1
Gemfile
@ -6,6 +6,7 @@ group :jekyll_plugins do
|
||||
gem "jekyll-assets", "~> 1.0.0"
|
||||
gem "jekyll-sitemap", "~> 1.4.0"
|
||||
gem "jekyll-feed", "~> 0.15.1"
|
||||
gem "jekyll-include-cache", "~> 0.2.1"
|
||||
gem "jekyll-theme-minimal"
|
||||
gem "jekyll-paginate", "~> 1.1.0"
|
||||
gem "kramdown-parser-gfm", "~> 1.1.0"
|
||||
|
@ -14,6 +14,7 @@ Powered by [Jekyll](https://github.com/jekyll/jekyll)
|
||||
[jekyll-toc](https://github.com/allejo/jekyll-toc)
|
||||
[Live2dHistoire](https://github.com/eeg1412/Live2dHistoire)
|
||||
[Simple-Jekyll-Search](https://github.com/christian-fei/Simple-Jekyll-Search)
|
||||
[jekyll-anchor-headings](https://github.com/allejo/jekyll-anchor-headings)
|
||||
|
||||
## 使用的网络资源
|
||||
[Github](https://github.com/) | 包含:
|
||||
@ -27,7 +28,7 @@ Powered by [Jekyll](https://github.com/jekyll/jekyll)
|
||||
|
||||
[网易云音乐](https://music.163.com/)
|
||||
[CDNJS](https://cdnjs.com/)
|
||||
[unpkg](https://unpkg.com/)
|
||||
[jsDelivr](https://www.jsdelivr.com/)
|
||||
|
||||
## 版权声明
|
||||
未经作者同意,请勿转载
|
||||
|
@ -1,6 +1,7 @@
|
||||
theme: jekyll-theme-minimal
|
||||
title: Mayx的博客
|
||||
logo: https://avatars0.githubusercontent.com/u/17966333
|
||||
lang: zh-CN
|
||||
author: mayx
|
||||
description: Mayx's Home Page
|
||||
timezone: Asia/Shanghai
|
||||
@ -9,6 +10,7 @@ paginate: 7
|
||||
plugins:
|
||||
- jekyll-sitemap
|
||||
- jekyll-feed
|
||||
- jekyll-include-cache
|
||||
feed:
|
||||
path: atom.xml
|
||||
google_analytics: UA-137710294-1
|
||||
|
14
_data/links.csv
Normal file
14
_data/links.csv
Normal file
@ -0,0 +1,14 @@
|
||||
title,link,feed_url,description
|
||||
花火学园,https://www.sayhanabi.net/,,和谐融洽的ACG交流以及资源聚集地
|
||||
资源统筹局,https://gkdworld.com/,,统筹保管用户分享的资源
|
||||
贫困的蚊子,https://mozz.ie/,https://mozz.ie/index.xml,*No description*
|
||||
极客兔兔,https://geektutu.com/,https://geektutu.com/atom.xml,致力于分享有趣的技术实践
|
||||
维基萌,https://www.wikimoe.com/,https://www.wikimoe.com/rss,萌即是正义!一名热爱acg的前端设计师的小站!
|
||||
7gugu's blog,https://www.7gugu.com/,https://7gugu.com/index.php/feed/,"一个用来存放我爱好的地方,编程,摄影之类的空间"
|
||||
云游君,https://www.yunyoujun.cn/,https://www.yunyoujun.cn/atom.xml,希望能成为一个有趣的人。
|
||||
Kingfish404,https://blog.kingfish404.cn/,https://blog.kingfish404.cn/index.xml,"Stay curious,stay naive. WUT. Jin Yu's Blog"
|
||||
FKUN,https://blog.fkun.tech/,https://blog.fkun.tech/feed/,*No description*
|
||||
Sinofine,https://sinofine.me/,https://sinofine.me/atom.xml,*No description*
|
||||
JiaoYuan's blog,https://yuanj.top/,https://yuanj.top/index.xml,思绪来得快去得也快,偶尔会在这里停留
|
||||
花生莲子粥,https://blog.hslzz.cn/,https://blog.hslzz.cn/atom.xml,与世无争,不染于泥
|
||||
南蛮子懋和,https://www.dao.js.cn/,https://www.dao.js.cn/feed.php,李懋和,俗名李栋梁。书法、国画爱好者,互联网安全与前端建设者。
|
|
24
_data/proxylist.yml
Normal file
24
_data/proxylist.yml
Normal file
@ -0,0 +1,24 @@
|
||||
proxies:
|
||||
- https://blog.mayx.workers.dev/
|
||||
- https://mayx.deno.dev/
|
||||
- https://mayx.glitch.me/
|
||||
- https://yuki.gear.host/
|
||||
- https://mayx.serv00.net/
|
||||
mirrors:
|
||||
- https://mayx.gitlab.io/
|
||||
- https://mayx.pages.dev/
|
||||
- https://mayx.eu.org/
|
||||
- https://mayx.vercel.app/
|
||||
- https://mayx.netlify.app/
|
||||
- https://mayx.4everland.app/
|
||||
- https://mayx.dappling.network/
|
||||
- https://mayx-blog.statichost.eu/
|
||||
others:
|
||||
- https://unmayx.blogspot.com/
|
||||
- https://unmayx.blog.fc2blog.us/
|
||||
- https://unmayx.wordpress.com/
|
||||
- https://mayx.code.blog/
|
||||
- https://mayx.home.blog/
|
||||
- https://unmayx.medium.com/
|
||||
- https://mayx.cnblogs.com/
|
||||
- https://mayx.xlog.app/
|
174
_includes/anchor_headings.html
Normal file
174
_includes/anchor_headings.html
Normal file
@ -0,0 +1,174 @@
|
||||
{% capture headingsWorkspace %}
|
||||
{% comment %}
|
||||
Copyright (c) 2018 Vladimir "allejo" Jimenez
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
{% endcomment %}
|
||||
{% comment %}
|
||||
Version 1.0.13
|
||||
https://github.com/allejo/jekyll-anchor-headings
|
||||
|
||||
"Be the pull request you wish to see in the world." ~Ben Balter
|
||||
|
||||
Usage:
|
||||
{% include anchor_headings.html html=content anchorBody="#" %}
|
||||
|
||||
Parameters:
|
||||
* html (string) - the HTML of compiled markdown generated by kramdown in Jekyll
|
||||
|
||||
Optional Parameters:
|
||||
* beforeHeading (bool) : false - Set to true if the anchor should be placed _before_ the heading's content
|
||||
* headerAttrs (string) : '' - Any custom HTML attributes that will be added to the heading tag; you may NOT use `id`;
|
||||
the `%heading%` and `%html_id%` placeholders are available
|
||||
* anchorAttrs (string) : '' - Any custom HTML attributes that will be added to the `<a>` tag; you may NOT use `href`, `class` or `title`;
|
||||
the `%heading%` and `%html_id%` placeholders are available
|
||||
* anchorBody (string) : '' - The content that will be placed inside the anchor; the `%heading%` placeholder is available
|
||||
* anchorClass (string) : '' - The class(es) that will be used for each anchor. Separate multiple classes with a space
|
||||
* anchorTitle (string) : '' - The `title` attribute that will be used for anchors
|
||||
* h_min (int) : 1 - The minimum header level to build an anchor for; any header lower than this value will be ignored
|
||||
* h_max (int) : 6 - The maximum header level to build an anchor for; any header greater than this value will be ignored
|
||||
* bodyPrefix (string) : '' - Anything that should be inserted inside of the heading tag _before_ its anchor and content
|
||||
* bodySuffix (string) : '' - Anything that should be inserted inside of the heading tag _after_ its anchor and content
|
||||
* generateId (true) : false - Set to true if a header without id should generate an id to use.
|
||||
|
||||
Output:
|
||||
The original HTML with the addition of anchors inside of all of the h1-h6 headings.
|
||||
{% endcomment %}
|
||||
|
||||
{% assign minHeader = include.h_min | default: 1 %}
|
||||
{% assign maxHeader = include.h_max | default: 6 %}
|
||||
{% assign beforeHeading = include.beforeHeading %}
|
||||
{% assign headerAttrs = include.headerAttrs %}
|
||||
{% assign nodes = include.html | split: '<h' %}
|
||||
|
||||
{% capture edited_headings %}{% endcapture %}
|
||||
|
||||
{% for _node in nodes %}
|
||||
{% capture node %}{{ _node | strip }}{% endcapture %}
|
||||
|
||||
{% if node == "" %}
|
||||
{% continue %}
|
||||
{% endif %}
|
||||
|
||||
{% assign nextChar = node | replace: '"', '' | strip | slice: 0, 1 %}
|
||||
{% assign headerLevel = nextChar | times: 1 %}
|
||||
|
||||
<!-- If the level is cast to 0, it means it's not a h1-h6 tag, so let's see if we need to fix it -->
|
||||
{% if headerLevel == 0 %}
|
||||
<!-- Split up the node based on closing angle brackets and get the first one. -->
|
||||
{% assign firstChunk = node | split: '>' | first %}
|
||||
|
||||
<!-- If the first chunk does NOT contain a '<', that means we've broken another HTML tag that starts with 'h' -->
|
||||
{% unless firstChunk contains '<' %}
|
||||
{% capture node %}<h{{ node }}{% endcapture %}
|
||||
{% endunless %}
|
||||
|
||||
{% capture edited_headings %}{{ edited_headings }}{{ node }}{% endcapture %}
|
||||
{% continue %}
|
||||
{% endif %}
|
||||
|
||||
{% capture _closingTag %}</h{{ headerLevel }}>{% endcapture %}
|
||||
{% assign _workspace = node | split: _closingTag %}
|
||||
{% capture _hAttrToStrip %}{{ _workspace[0] | split: '>' | first }}>{% endcapture %}
|
||||
{% assign header = _workspace[0] | replace: _hAttrToStrip, '' %}
|
||||
{% assign escaped_header = header | strip_html | strip %}
|
||||
|
||||
{% assign _classWorkspace = _workspace[0] | split: 'class="' %}
|
||||
{% assign _classWorkspace = _classWorkspace[1] | split: '"' %}
|
||||
{% assign _html_class = _classWorkspace[0] %}
|
||||
|
||||
{% if _html_class contains "no_anchor" %}
|
||||
{% assign skip_anchor = true %}
|
||||
{% else %}
|
||||
{% assign skip_anchor = false %}
|
||||
{% endif %}
|
||||
|
||||
{% assign _idWorkspace = _workspace[0] | split: 'id="' %}
|
||||
{% if _idWorkspace[1] %}
|
||||
{% assign _idWorkspace = _idWorkspace[1] | split: '"' %}
|
||||
{% assign html_id = _idWorkspace[0] %}
|
||||
{% assign h_attrs = headerAttrs %}
|
||||
{% elsif include.generateId %}
|
||||
<!-- If the header did not have an id we create one. -->
|
||||
{% assign html_id = escaped_header | slugify %}
|
||||
{% if html_id == "" %}
|
||||
{% assign html_id = false %}
|
||||
{% endif %}
|
||||
<!-- Append the generated id to other potential header attributes. -->
|
||||
{% capture h_attrs %}{{ headerAttrs }} id="%html_id%"{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Build the anchor to inject for our heading -->
|
||||
{% capture anchor %}{% endcapture %}
|
||||
|
||||
{% if skip_anchor == false and html_id and headerLevel >= minHeader and headerLevel <= maxHeader %}
|
||||
{% if h_attrs %}
|
||||
{% capture _hAttrToStrip %}{{ _hAttrToStrip | split: '>' | first }} {{ h_attrs | strip | replace: '%heading%', escaped_header | replace: '%html_id%', html_id }}>{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
{% capture anchor %}href="#{{ html_id }}"{% endcapture %}
|
||||
|
||||
{% if include.anchorClass %}
|
||||
{% capture anchor %}{{ anchor }} class="{{ include.anchorClass }}"{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
{% if include.anchorTitle %}
|
||||
{% capture anchor %}{{ anchor }} title="{{ include.anchorTitle | replace: '%heading%', escaped_header }}"{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
{% if include.anchorAttrs %}
|
||||
{% capture anchor %}{{ anchor }} {{ include.anchorAttrs | replace: '%heading%', escaped_header | replace: '%html_id%', html_id }}{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
{% capture anchor %}<a {{ anchor }}>{{ include.anchorBody | replace: '%heading%', escaped_header | default: '' }}</a>{% endcapture %}
|
||||
|
||||
<!-- In order to prevent adding extra space after a heading, we'll let the 'anchor' value contain it -->
|
||||
{% if beforeHeading %}
|
||||
{% capture anchor %}{{ anchor }} {% endcapture %}
|
||||
{% else %}
|
||||
{% capture anchor %} {{ anchor }}{% endcapture %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% capture new_heading %}
|
||||
<h{{ _hAttrToStrip }}
|
||||
{{ include.bodyPrefix }}
|
||||
{% if beforeHeading %}
|
||||
{{ anchor }}{{ header }}
|
||||
{% else %}
|
||||
{{ header }}{{ anchor }}
|
||||
{% endif %}
|
||||
{{ include.bodySuffix }}
|
||||
</h{{ headerLevel }}>
|
||||
{% endcapture %}
|
||||
|
||||
<!--
|
||||
If we have content after the `</hX>` tag, then we'll want to append that here so we don't lost any content.
|
||||
-->
|
||||
{% assign chunkCount = _workspace | size %}
|
||||
{% if chunkCount > 1 %}
|
||||
{% capture new_heading %}{{ new_heading }}{{ _workspace | last }}{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
{% capture edited_headings %}{{ edited_headings }}{{ new_heading }}{% endcapture %}
|
||||
{% endfor %}
|
||||
{% endcapture %}{% assign headingsWorkspace = '' %}{{ edited_headings | strip }}
|
@ -1,6 +1,30 @@
|
||||
{% capture tocWorkspace %}
|
||||
{% comment %}
|
||||
Version 1.0.7
|
||||
Copyright (c) 2017 Vladimir "allejo" Jimenez
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
{% endcomment %}
|
||||
{% comment %}
|
||||
Version 1.2.1
|
||||
https://github.com/allejo/jekyll-toc
|
||||
|
||||
"...like all things liquid - where there's a will, and ~36 hours to spare, there's usually a/some way" ~jaybe
|
||||
@ -19,77 +43,147 @@
|
||||
* h_max (int) : 6 - the maximum TOC header level to use; any header greater than this value will be ignored
|
||||
* ordered (bool) : false - when set to true, an ordered list will be outputted instead of an unordered list
|
||||
* item_class (string) : '' - add custom class(es) for each list item; has support for '%level%' placeholder, which is the current heading level
|
||||
* baseurl (string) : '' - add a base url to the TOC links for when your TOC is on another page than the actual content
|
||||
* submenu_class (string) : '' - add custom class(es) for each child group of headings; has support for '%level%' placeholder which is the current "submenu" heading level
|
||||
* base_url (string) : '' - add a base url to the TOC links for when your TOC is on another page than the actual content
|
||||
* anchor_class (string) : '' - add custom class(es) for each anchor element
|
||||
* skip_no_ids (bool) : false - skip headers that do not have an `id` attribute
|
||||
* flat_toc (bool) : false - when set to true, the TOC will be a single level list
|
||||
|
||||
Output:
|
||||
An ordered or unordered list representing the table of contents of a markdown block. This snippet will only
|
||||
generate the table of contents and will NOT output the markdown given to it
|
||||
{% endcomment %}
|
||||
|
||||
{% capture my_toc %}{% endcapture %}
|
||||
{% capture newline %}
|
||||
{% endcapture %}
|
||||
{% assign newline = newline | rstrip %} <!-- Remove the extra spacing but preserve the newline -->
|
||||
|
||||
{% capture deprecation_warnings %}{% endcapture %}
|
||||
|
||||
{% if include.baseurl %}
|
||||
{% capture deprecation_warnings %}{{ deprecation_warnings }}<!-- jekyll-toc :: "baseurl" has been deprecated, use "base_url" instead -->{{ newline }}{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
{% if include.skipNoIDs %}
|
||||
{% capture deprecation_warnings %}{{ deprecation_warnings }}<!-- jekyll-toc :: "skipNoIDs" has been deprecated, use "skip_no_ids" instead -->{{ newline }}{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
{% capture jekyll_toc %}{% endcapture %}
|
||||
{% assign orderedList = include.ordered | default: false %}
|
||||
{% assign flatToc = include.flat_toc | default: false %}
|
||||
{% assign baseURL = include.base_url | default: include.baseurl | default: '' %}
|
||||
{% assign skipNoIDs = include.skip_no_ids | default: include.skipNoIDs | default: false %}
|
||||
{% assign minHeader = include.h_min | default: 1 %}
|
||||
{% assign maxHeader = include.h_max | default: 6 %}
|
||||
{% assign nodes = include.html | split: '<h' %}
|
||||
{% assign firstHeader = true %}
|
||||
{% assign nodes = include.html | strip | split: '<h' %}
|
||||
|
||||
{% capture listModifier %}{% if orderedList %}1.{% else %}-{% endif %}{% endcapture %}
|
||||
{% assign firstHeader = true %}
|
||||
{% assign currLevel = 0 %}
|
||||
{% assign lastLevel = 0 %}
|
||||
|
||||
{% capture listModifier %}{% if orderedList %}ol{% else %}ul{% endif %}{% endcapture %}
|
||||
|
||||
{% for node in nodes %}
|
||||
{% if node == "" %}
|
||||
{% continue %}
|
||||
{% endif %}
|
||||
|
||||
{% assign headerLevel = node | replace: '"', '' | slice: 0, 1 | times: 1 %}
|
||||
{% assign currLevel = node | replace: '"', '' | slice: 0, 1 | times: 1 %}
|
||||
|
||||
{% if headerLevel < minHeader or headerLevel > maxHeader %}
|
||||
{% if currLevel < minHeader or currLevel > maxHeader %}
|
||||
{% continue %}
|
||||
{% endif %}
|
||||
|
||||
{% if firstHeader %}
|
||||
{% assign firstHeader = false %}
|
||||
{% assign minHeader = headerLevel %}
|
||||
{% endif %}
|
||||
|
||||
{% assign indentAmount = headerLevel | minus: minHeader | add: 1 %}
|
||||
{% assign _workspace = node | split: '</h' %}
|
||||
|
||||
{% assign _idWorkspace = _workspace[0] | split: 'id="' %}
|
||||
{% assign _idWorkspace = _idWorkspace[1] | split: '"' %}
|
||||
{% assign html_id = _idWorkspace[0] %}
|
||||
{% assign htmlID = _idWorkspace[0] %}
|
||||
|
||||
{% assign _classWorkspace = _workspace[0] | split: 'class="' %}
|
||||
{% assign _classWorkspace = _classWorkspace[1] | split: '"' %}
|
||||
{% assign html_class = _classWorkspace[0] %}
|
||||
{% assign htmlClass = _classWorkspace[0] %}
|
||||
|
||||
{% if html_class contains "no_toc" %}
|
||||
{% if htmlClass contains "no_toc" %}
|
||||
{% continue %}
|
||||
{% endif %}
|
||||
|
||||
{% if firstHeader %}
|
||||
{% assign minHeader = currLevel %}
|
||||
{% endif %}
|
||||
|
||||
{% capture _hAttrToStrip %}{{ _workspace[0] | split: '>' | first }}>{% endcapture %}
|
||||
{% assign header = _workspace[0] | replace: _hAttrToStrip, '' %}
|
||||
|
||||
{% assign space = '' %}
|
||||
{% for i in (1..indentAmount) %}
|
||||
{% assign space = space | prepend: ' ' %}
|
||||
{% endfor %}
|
||||
|
||||
{% unless include.item_class == blank %}
|
||||
{% capture listItemClass %}{:.{{ include.item_class | replace: '%level%', headerLevel }}}{% endcapture %}
|
||||
{% endunless %}
|
||||
|
||||
{% capture my_toc %}{{ my_toc }}
|
||||
{{ space }}{{ listModifier }} {{ listItemClass }} [{% if include.sanitize %}{{ header | strip_html }}{% else %}{{ header }}{% endif %}]({% if include.baseurl %}{{ include.baseurl }}{% endif %}#{{ html_id }}){% if include.anchor_class %}{:.{{ include.anchor_class }}}{% endif %}{% endcapture %}
|
||||
{% endfor %}
|
||||
|
||||
{% if include.class %}
|
||||
{% capture my_toc %}{:.{{ include.class }}}
|
||||
{{ my_toc | lstrip }}{% endcapture %}
|
||||
{% if include.item_class and include.item_class != blank %}
|
||||
{% capture listItemClass %} class="{{ include.item_class | replace: '%level%', currLevel | split: '.' | join: ' ' }}"{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
{% if include.id %}
|
||||
{% capture my_toc %}{: #{{ include.id }}}
|
||||
{{ my_toc | lstrip }}{% endcapture %}
|
||||
{% if include.submenu_class and include.submenu_class != blank %}
|
||||
{% assign subMenuLevel = currLevel | minus: 1 %}
|
||||
{% capture subMenuClass %} class="{{ include.submenu_class | replace: '%level%', subMenuLevel | split: '.' | join: ' ' }}"{% endcapture %}
|
||||
{% endif %}
|
||||
{% endcapture %}{% assign tocWorkspace = '' %}{{ my_toc | markdownify | strip }}
|
||||
|
||||
{% capture anchorBody %}{% if include.sanitize %}{{ header | strip_html }}{% else %}{{ header }}{% endif %}{% endcapture %}
|
||||
|
||||
{% if htmlID %}
|
||||
{% capture anchorAttributes %} href="{% if baseURL %}{{ baseURL }}{% endif %}#{{ htmlID }}"{% endcapture %}
|
||||
|
||||
{% if include.anchor_class %}
|
||||
{% capture anchorAttributes %}{{ anchorAttributes }} class="{{ include.anchor_class | split: '.' | join: ' ' }}"{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
{% capture listItem %}<a{{ anchorAttributes }}>{{ anchorBody }}</a>{% endcapture %}
|
||||
{% elsif skipNoIDs == true %}
|
||||
{% continue %}
|
||||
{% else %}
|
||||
{% capture listItem %}{{ anchorBody }}{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
{% if currLevel > lastLevel and flatToc == false %}
|
||||
{% capture jekyll_toc %}{{ jekyll_toc }}<{{ listModifier }}{{ subMenuClass }}>{% endcapture %}
|
||||
{% elsif currLevel < lastLevel and flatToc == false %}
|
||||
{% assign repeatCount = lastLevel | minus: currLevel %}
|
||||
|
||||
{% for i in (1..repeatCount) %}
|
||||
{% capture jekyll_toc %}{{ jekyll_toc }}</li></{{ listModifier }}>{% endcapture %}
|
||||
{% endfor %}
|
||||
|
||||
{% capture jekyll_toc %}{{ jekyll_toc }}</li>{% endcapture %}
|
||||
{% else %}
|
||||
{% capture jekyll_toc %}{{ jekyll_toc }}</li>{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
{% capture jekyll_toc %}{{ jekyll_toc }}<li{{ listItemClass }}>{{ listItem }}{% endcapture %}
|
||||
|
||||
{% assign lastLevel = currLevel %}
|
||||
{% assign firstHeader = false %}
|
||||
{% endfor %}
|
||||
|
||||
{% if flatToc == true %}
|
||||
{% assign repeatCount = 1 %}
|
||||
{% else %}
|
||||
{% assign repeatCount = minHeader | minus: 1 %}
|
||||
{% assign repeatCount = lastLevel | minus: repeatCount %}
|
||||
{% endif %}
|
||||
|
||||
{% for i in (1..repeatCount) %}
|
||||
{% capture jekyll_toc %}{{ jekyll_toc }}</li></{{ listModifier }}>{% endcapture %}
|
||||
{% endfor %}
|
||||
|
||||
{% if jekyll_toc != '' %}
|
||||
{% assign rootAttributes = '' %}
|
||||
{% if include.class and include.class != blank %}
|
||||
{% capture rootAttributes %} class="{{ include.class | split: '.' | join: ' ' }}"{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
{% if include.id and include.id != blank %}
|
||||
{% capture rootAttributes %}{{ rootAttributes }} id="{{ include.id }}"{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
{% if rootAttributes %}
|
||||
{% assign nodes = jekyll_toc | split: '>' %}
|
||||
{% capture jekyll_toc %}<{{ listModifier }}{{ rootAttributes }}>{{ nodes | shift | join: '>' }}>{% endcapture %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endcapture %}{% assign tocWorkspace = '' %}{{ deprecation_warnings }}{{ jekyll_toc -}}
|
1
_includes/word_count.html
Normal file
1
_includes/word_count.html
Normal file
@ -0,0 +1 @@
|
||||
{% assign count = 0 %}{% for post in site.posts %}{% assign single_count = post.content | strip_html | strip_newlines | remove: " " | size %}{% assign count = count | plus: single_count %}{% endfor %}{{ count }}
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="{{ site.lang | default: "zh-CN" }}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
@ -26,12 +26,20 @@
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', '{{ site.google_analytics }}');
|
||||
</script>
|
||||
{% endif %}
|
||||
<script>
|
||||
var lastUpdated = new Date("{{ site.time | date: "%FT%T%z" }}");
|
||||
var BlogAPI = "https://summary.mayx.eu.org";
|
||||
function getSearchJSON(callback) {
|
||||
var searchData = JSON.parse(localStorage.getItem("blog_" + lastUpdated.valueOf()));
|
||||
if (!searchData) {
|
||||
localStorage.clear();
|
||||
for (var i = 0; i < localStorage.length; i++) {
|
||||
var key = localStorage.key(i);
|
||||
if (key.startsWith('blog_')) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
$.getJSON("/search.json", function (data) {
|
||||
localStorage.setItem("blog_" + lastUpdated.valueOf(), JSON.stringify(data));
|
||||
callback(data);
|
||||
@ -41,27 +49,7 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
<style>
|
||||
.backToTop {
|
||||
display: none;
|
||||
width: 18px;
|
||||
line-height: 1.2;
|
||||
padding: 5px 0;
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
_position: absolute;
|
||||
right: 10px;
|
||||
bottom: 100px;
|
||||
_bottom: "auto";
|
||||
cursor: pointer;
|
||||
opacity: .6;
|
||||
filter: Alpha(opacity=60);
|
||||
}
|
||||
</style>
|
||||
<script src="//instant.page/5.2.0" type="module" integrity="sha384-jnZyxPjiipYXnSU0ygqeac2q7CVYMbh84q0uHVRRxEtvFPiQYbXWUorga2aqZJ0z"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -140,7 +128,7 @@
|
||||
<!-- <![endif]-->
|
||||
<footer>
|
||||
<p>
|
||||
<small>Made with ❤ by Mayx<br />Last updated at <script>document.write(lastUpdated.toLocaleString());</script><br /> 总字数:{% assign count = 0 %}{% for post in site.posts %}{% assign single_count = post.content | strip_html | strip_newlines | remove: " " | size %}{% assign count = count | plus: single_count %}{% endfor %}{% if count > 10000 %}{{ count | divided_by: 10000 }} 万 {{ count | modulo: 10000 }}{% else %}{{ count }}{% endif %} - 文章数:{% for post in site.posts %}{% assign co = co | plus: 1 %}{% endfor %}{{ co }} - <a href="{{ "/atom.xml" | relative_url }}" >Atom</a> - <a href="{{ "/README.html" | relative_url }}" >About</a></small>
|
||||
<small>Made with ❤ by Mayx<br />Last updated at <script>document.write(lastUpdated.toLocaleString());</script><br /> 总字数:{% include_cached word_count.html %} - 文章数:{{ site.posts.size }} - <a href="{{ site.feed.path | relative_url }}" >Atom</a> - <a href="{{ "/README.html" | relative_url }}" >About</a></small>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
@ -75,10 +75,10 @@ layout: default
|
||||
|
||||
{% include toc.html html=content sanitize=true h_max=3 %}
|
||||
|
||||
{{content}}
|
||||
{% if post.layout == "encrypt" %} {{content}} {% else %} <main class="post-content" role="main">{% include anchor_headings.html html=content beforeHeading=true anchorBody="<svg class='octicon' viewBox='0 0 16 16' version='1.1' width='16' height='32' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>" %}</main> {% endif %}
|
||||
|
||||
{% if page.tags %}
|
||||
<small>tags: <em>{{ page.tags | join: "</em> - <em>" }}</em></small>
|
||||
<small style="display: block">tags: {% for tag in page.tags %}<a href="/search.html?keyword={{ tag | url_encode }}"><em>{{ tag }}</em></a>{% unless forloop.last %} - {% endunless %}{% endfor %} <span style="float: right;"><a href="{% if site.github %}{{ site.github.repository_url }}{% else %}https://gitlab.com/mayx/mayx.gitlab.io{% endif %}/tree/master/{{ page.path }}">查看原始文件</a></span></small>
|
||||
{% endif %}
|
||||
<br />
|
||||
<br />
|
||||
@ -132,10 +132,9 @@ $.get(BlogAPI + "/suggest?id=" + blogurl + "&update=" + lastUpdated.valueOf(), f
|
||||
<div id="gitalk-container"></div>
|
||||
|
||||
<script>
|
||||
if (window.location.host != "mabbs.github.io") {
|
||||
var gitalk = new Gitalk({
|
||||
clientID: '098934a2556425f19d6e',
|
||||
clientSecret: '0bd44eed8425e5437ce43c4ba9b2791fbc04581d',
|
||||
clientID: (window.location.host != "mabbs.github.io")?'098934a2556425f19d6e':'36557aec4c3cb04f7ac6',
|
||||
clientSecret: (window.location.host != "mabbs.github.io")?'0bd44eed8425e5437ce43c4ba9b2791fbc04581d':'ac32993299751cb5a9ba81cf2b171cca65879cdb',
|
||||
repo: 'mabbs.github.io',
|
||||
owner: 'Mabbs',
|
||||
admin: ['Mabbs'],
|
||||
@ -143,20 +142,6 @@ $.get(BlogAPI + "/suggest?id=" + blogurl + "&update=" + lastUpdated.valueOf(), f
|
||||
distractionFreeMode: false, // Facebook-like distraction free mode
|
||||
proxy: "https://cors-anywhere.mayx.eu.org/?https://github.com/login/oauth/access_token"
|
||||
})
|
||||
}
|
||||
else {
|
||||
var gitalk = new Gitalk({
|
||||
clientID: '36557aec4c3cb04f7ac6',
|
||||
clientSecret: 'ac32993299751cb5a9ba81cf2b171cca65879cdb',
|
||||
repo: 'mabbs.github.io',
|
||||
owner: 'Mabbs',
|
||||
admin: ['Mabbs'],
|
||||
id: '{{ page.id }}', // Ensure uniqueness and length less than 50
|
||||
distractionFreeMode: false, // Facebook-like distraction free mode
|
||||
proxy: "https://cors-anywhere.mayx.eu.org/?https://github.com/login/oauth/access_token"
|
||||
})
|
||||
}
|
||||
|
||||
gitalk.render('gitalk-container')
|
||||
</script>
|
||||
<!-- <![endif]-->
|
@ -22,20 +22,12 @@ tags: [Mayx, 计算机, 学习]
|
||||
后来加入了一个叫批处理之家的论坛,我叫做[111](http://www.bathome.net/space.php?uid=51236)(LOL真不敢相信这个论坛一直到今天还活着),在这里我学到了不少关于批处理的事情。
|
||||
因为学批处理是基于某工具箱的,所以用批处理写的程序也是工具箱,就叫做批处理工具。以下是该程序的源代码:
|
||||
|
||||
<script>
|
||||
function showcode() {
|
||||
$('.showbutton').toggle();
|
||||
$('.language-code').toggle();
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.language-code{ display:none; }
|
||||
.language-shell{ display:none; }
|
||||
</style>
|
||||
<button onclick="showcode()" class="showbutton">Show Code</button>
|
||||
<button onclick="showcode()" class="showbutton" style="display:none;">Hide Code</button>
|
||||
<details markdown="1">
|
||||
<summary markdown="span">
|
||||
Show Code
|
||||
</summary>
|
||||
|
||||
```code
|
||||
```bat
|
||||
@echo off
|
||||
color f0
|
||||
mode con cols=50 lines=10
|
||||
@ -2791,6 +2783,7 @@ if /i '%shy%'=='exit' goto _max
|
||||
if /i '%shy%'=='ai学习机' goto aixx
|
||||
::在这里加入新的命令
|
||||
```
|
||||
</details>
|
||||
|
||||
**注:因为隐私原因,部分代码稍作修改**
|
||||
当然,这个程序有不少地方是抄的,而且很烂……(毕竟是小时候写的嘛)
|
||||
@ -2799,14 +2792,10 @@ if /i '%shy%'=='ai学习机' goto aixx
|
||||
因为手机(Android)系统基于Linux,所以我开始[学习Linux Shell](http://c.biancheng.net/cpp/shell/)(没错,当时就是在这个网站上学的),
|
||||
以前用批处理学写工具箱的习惯当然也继承到了学写Linux Shell上,在Linux上写的工具箱的名字叫做myx,代码如下:
|
||||
|
||||
<script>
|
||||
function showcode2() {
|
||||
$('.showbutton2').toggle();
|
||||
$('.language-shell').toggle();
|
||||
}
|
||||
</script>
|
||||
<button onclick="showcode2()" class="showbutton2">Show Code</button>
|
||||
<button onclick="showcode2()" class="showbutton2" style="display:none;">Hide Code</button>
|
||||
<details markdown="1">
|
||||
<summary markdown="span">
|
||||
Show Code
|
||||
</summary>
|
||||
|
||||
```shell
|
||||
#!/system/bin/sh
|
||||
@ -3230,6 +3219,7 @@ sleep 2
|
||||
esac
|
||||
done
|
||||
```
|
||||
</details>
|
||||
|
||||
**注:因为隐私原因,部分代码稍作修改**
|
||||
|
||||
|
@ -38,7 +38,7 @@ tags: [Github, 封禁, 博客]
|
||||
> The repository has been deleted per your request.
|
||||
> Kindly note further instances that hosts a script that leverages git.io URL shortener to redirect to a malicious site may lead to further action, such as permanent suspension.
|
||||
|
||||
🌿,原来是我3年前写的[让Git.io无限制](/2019/03/23/gitio.html)所提供的服务被人利用做坏事了,麻了,这Github是真的不长嘴吗?提前说一声我又不是不会删,而且我的服务被利用,上来就先干我是吧?这和某政府对付ISP有什么区别。
|
||||
🌿,原来是我3年前写的[让Git.io无限制](/2019/03/23/gitio.html)所提供的服务被人利用做坏事了,麻了,这Github是真的不长嘴吗?提前说一声我又不是不会删,而且我的服务被利用,上来就先干我是吧?这和某政府对付ICP有什么区别。
|
||||
|
||||
# 造成的损失
|
||||
1. 我的博客所有Star、Fork和评论全部消失
|
||||
|
@ -13,7 +13,7 @@ tags: [Mayx, Github, Gitlab, 分发]
|
||||
去年我在[研究博客平台的时候](/2021/08/15/blog.html)已经调查过很多放静态站的平台了,所以这次进行分发的时候有了之前的经验,也简单了不少。
|
||||
## 源代码托管平台的选择
|
||||
因为Github不可信,于是我自然想到了用Gitlab来存放博客源代码。虽然吧Gitlab曾经也发生过用户数据丢失的问题,不过反正目标也是同时放在Github和Gitlab上,总不至于两个一起炸吧。其实最开始我的计划是用Github Actions进行同步,不过在我进行调查之后我发现Gitlab功能还是挺强大的,它支持对一个Git仓库进行自动的推送和拉取,也不需要做过多的配置,就只需要配置个地址和令牌就可以,还是挺方便的。
|
||||
在我做完Github与Gitlab双向同步之后,我发现Gitlab还挺好用的,首先,Gitlab有个很棒的地方就是没被墙,我有时候写文章的时候不挂梯子用Github真的是非常难受,目前依我所感受,防火长城会对Github先进行一下TCP RST,然后刷新一下让你连上,连上之后如果长连接断开或者大概5分钟的样子就再阻断,然后再RST一波,非常的挑战心态。有时候我写了半天然后点预览结果就阻断,等半天还是连不上,还要挂梯子,能预览的时候就得赶紧提交,万一提交的时候再阻断要是没备份就炸了。像Gitlab我就从来没遇到过类似的情况,这一点还是很不错的,大概是因为Gitlab不是社区,而且滥用的人也少,所以政府也不太关系吧。
|
||||
在我做完Github与Gitlab双向同步之后,我发现Gitlab还挺好用的,首先,Gitlab有个很棒的地方就是没被墙,我有时候写文章的时候不挂梯子用Github真的是非常难受,目前依我所感受,防火长城会对Github先进行一下TCP RST,然后刷新一下让你连上,连上之后如果长连接断开或者大概5分钟的样子就再阻断,然后再RST一波,非常的挑战心态。有时候我写了半天然后点预览结果就阻断,等半天还是连不上,还要挂梯子,能预览的时候就得赶紧提交,万一提交的时候再阻断要是没备份就炸了。像Gitlab我就从来没遇到过类似的情况,这一点还是很不错的,大概是因为Gitlab不是社区,而且滥用的人也少,所以政府也不太关心吧。
|
||||
另外就是Web IDE,相比Github的VSCode Web IDE,Gitlab的要轻量很多了,也不容易发生卡的情况,而且其实Github的VSCode Web IDE也装不了几个插件,功能上也没强到哪去。
|
||||
还有就是翻译,明明用Github的中国人/华人挺多的,官方就是不出中文界面,明明文档都有中文了……Gitlab可能是因为作为一个开源产品,i18n做的很好,虽然吧英文也不影响我使用,但是毕竟作为用户体验的一项,Gitlab做的确实更好。
|
||||
不过其实我觉得Gitlab也许只是表面没那么出名,毕竟不是做社区的,大多数公司都用的是自建Gitlab托管代码,而且很多时候Github其实是在抄Gitlab的(虽然最早是Gitlab抄Github),比如Actions抄CI/CD,还有最近又出的一堆什么代码扫描和检查,Gitlab出现的都更早。不过这说着也跑题了这个文章又不是为了专门夸Gitlab的😂。
|
||||
|
@ -235,8 +235,8 @@ export default {
|
||||
}
|
||||
```
|
||||
另外也写了配套的前端代码(用的jQuery,其实应该用Fetch的😂):
|
||||
```html
|
||||
{% raw %}
|
||||
```html
|
||||
<b>AI摘要</b>
|
||||
<p id="ai-output">正在生成中……</p>
|
||||
<script>
|
||||
@ -279,8 +279,8 @@ export default {
|
||||
}
|
||||
ai_gen();
|
||||
</script>
|
||||
{% endraw %}
|
||||
```
|
||||
{% endraw %}
|
||||
本来文章内容应该从html里读更好一些,但是标签啥的还得用正则去掉,感觉不如Liquid方便😂。另外博客计数器不应该用MD5的,但懒得改之前的数据了,还好Cloudflare Workers为了兼容是支持MD5的,免得我还得想办法改数据库里的数据。
|
||||
|
||||
# 使用方法
|
||||
|
@ -14,6 +14,12 @@ tags: [Python, 木马, 病毒]
|
||||
# 提取源代码
|
||||
pyinstaller解包还是挺简单的,用[PyInstaller Extractor](https://github.com/extremecoders-re/pyinstxtractor)就可以,首先我在我的电脑上尝试解包,不过因为Python版本不对,里面的PYZ文件不能解包,并且提示我使用Python 2.7的环境再试一次。我找了台装有Python 2.7环境的服务器又执行了一次之后就全部解包完了。想不到这个木马居然没有加密😂,直接就能解压,不过就算加密了我之前看过一篇[文章](https://www.cnblogs.com/liweis/p/15891170.html)可以进行解密。
|
||||
不过现在得到的文件都是字节码pyc文件,还需要反编译才能看到源代码,这个步骤也很简单,安装个[uncompyle6](https://github.com/rocky/python-uncompyle6)工具就可以。它的主程序名字叫“ii.py”,于是我反编译了一下,不过看起来作者还整了一些混淆,但是极其简单,就把几个函数换成一串变量而已,所以写了个简单的脚本给它还原回去了,最终处理的结果如下(里面有个[混淆过的PowerShell版mimikatz](https://github.com/DanMcInerney/Invoke-Cats),太长了所以我给删掉了):
|
||||
|
||||
<details markdown="1">
|
||||
<summary markdown="span">
|
||||
Show Code
|
||||
</summary>
|
||||
|
||||
```python
|
||||
# uncompyle6 version 3.9.2
|
||||
# Python bytecode version base 2.7 (62211)
|
||||
@ -1492,7 +1498,15 @@ while var == 1:
|
||||
|
||||
# global h_one ## Warning: Unused global
|
||||
```
|
||||
</details>
|
||||
|
||||
里面有两个不是公开的库,mysmb和psexec,其中mysmb看起来是[永恒之蓝RCE中的代码](https://github.com/0xsyr0/OSCP/blob/main/exploits/CVE-2017-0144-EternalBlue-MS17-010-RCE/mysmb.py),psexec有找到几个相似的但是没找到一样的,所以代码也放上来:
|
||||
|
||||
<details markdown="1">
|
||||
<summary markdown="span">
|
||||
Show Code
|
||||
</summary>
|
||||
|
||||
```python
|
||||
# uncompyle6 version 3.9.2
|
||||
# Python bytecode version base 2.7 (62211)
|
||||
@ -1864,6 +1878,7 @@ class PSEXEC:
|
||||
s.deleteFile(installService.getShare(), 'temp\\tmp.vbs')
|
||||
return False
|
||||
```
|
||||
</details>
|
||||
|
||||
# 行为分析
|
||||
那这个代码都干了些什么呢?首先动态分析一下吧,我用微步云沙箱检查了一下,不过好像有人已经上传过了,[这个是报告](https://s.threatbook.com/report/file/60b6d7664598e6a988d9389e6359838be966dfa54859d5cb1453cbc9b126ed7d)。好像也没啥特别的,先给445端口开了个防火墙,估计是防止其他人利用永恒之蓝入侵,然后整了几个请求几个“beahh.com”域名的定时任务,另外就是同网段扫描啥的,应该是找其他机器继续尝试用漏洞入侵感染这个木马。
|
||||
|
26
_posts/2025-02-09-server.md
Normal file
26
_posts/2025-02-09-server.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
layout: post
|
||||
title: 新旧服务器的使用体验与对比
|
||||
tags: [服务器, Dell, 使用体验]
|
||||
---
|
||||
|
||||
花更多钱可以收获更多吗?<!--more-->
|
||||
|
||||
# 起因
|
||||
最近由于某些原因需要买点服务器,从我平时用的东西来看,其实很多年前的产品就已经满足大多数应用了,业务的发展跟不上时代的发展,就根本不需要更好的性能。所以既然要买服务器,还是买洋垃圾比较好,那些淘汰下来的服务器特别便宜。虽然这么说,但是我也好奇现在的技术到底发展到一个什么样的程度,所以也整个新的服务器玩玩吧。
|
||||
|
||||
# 选择服务器
|
||||
那选哪个服务器比较合适呢?我在大学里用过R730,那款服务器给我留下的印象很不错,拆装很方便,也有很好用的带外管理功能(iDRAC),现在的R730已经非常便宜了,我看了看CPU觉得既然洋垃圾很便宜,那就要选个厉害的CPU,最终我选择了双路20核40线程的[英特尔® 至强® 处理器 E5-2698 v4](https://www.intel.cn/content/www/cn/zh/products/sku/91753/intel-xeon-processor-e52698-v4-50m-cache-2-20-ghz/specifications.html),总共40核80线程,另外配了4根32GiB 2400MT/s的DDR4内存,看起来参数还是挺唬人的🤣,而且价格才2k多CNY,感觉还挺不错。
|
||||
那新的用啥呢?我上Intel的官网看了看,至强6是现在最新的Intel服务器CPU,至于AMD的……主要是给我买服务器的人不喜欢AMD🤣,所以只能选Intel的。既然旧的选了Dell,新的也选Dell吧,我看搭载至强6的戴尔服务器是R770,但是目前还买不到😅,而且价格贵的吓人。次一级就是R760,可以上第四或第五代至强可扩展处理器,不过看了一眼价格也有点贵……但这个机器有个青春版,叫R760xs,也能上第四或第五代至强可扩展处理器,扩展性稍微差一点,但是价格比较便宜,他们管这叫“成本优化版”。最终选来选去选了个单路16核32线程的[英特尔® 至强® Gold 6426Y 处理器](https://www.intel.cn/content/www/cn/zh/products/sku/232377/intel-xeon-gold-6426y-processor-37-5m-cache-2-50-ghz/specifications.html),外加4条16GiB 4800MT/s的DDR5内存,总共花了将近4wCNY,感觉还是相当贵啊……
|
||||
|
||||
# 使用体验与对比
|
||||
服务器拿到手之后自然要先跑个分,我给新服务器安装了Ubuntu Server 24.04,旧的因为核心数多感觉应该能干点别的所以安装了Vmware ESXi 6.7,然后在上面安装了个Ubuntu Server 24.04的虚拟机。跑分用的是sysbench。最终新的服务器单核跑分2853.45events/s,多核47054.35events/s,旧服务器单核876.22events/s,多核52792.15events/s。从这里来看这个新服务器让人非常失望啊,单核才3倍多点差距,尤其我试了试13代i5的单核跑分能到4290.80events/s,家用的处理器可是要便宜的多啊。多核虽然说16核比40核少了点,能跑出差不多的分数已经很厉害了,但是考虑到这两个服务器20倍的价格差,还是深深的感到不值啊……
|
||||
当然服务器的性能并不是它的优势,扩展性才是,但是R730的定位比R760xs的定位要高啊😂,扩展性显然是旧服务器更强……那新服务器就没什么优势了吗?倒也不是,新服务器的处理器至少把漏洞都修完了,除了幽灵漏洞之外,至少不受其他漏洞影响,安全性更强了。旧处理器和酷睿5代是同一个时代的,所以会受各种CPU漏洞的影响。不过这个服务器又不会当云服务器租给别人用,有没有漏洞根本无所谓啊😅。
|
||||
那管理性呢?新的带外管理用的是iDRAC9,旧的是iDRAC8,两个界面上差距倒是挺大的,不过功能基本上都差不多,从功能上来看9比8多了个修改BIOS的功能,但是修改完还是得重启才能生效😅,那不如花几十块钱买个企业版订阅然后用虚拟KVM直接重启进BIOS修改呢……不过如果是大规模的话可能是可以统一修改BIOS选项,那就有点意义了,不过对我来说没啥意义😥。
|
||||
那还有别的优势吗?我看网上说第四、第五代至强可扩展处理器新出了个指令集,叫AMX,可以用来加速AI推理,正好最近国内一个叫DeepSeek-R1的模型挺火的,那就拿来试试看呗,要是这个AMX指令集能大幅提高CPU的推理速度,那我还是挺认同它的价格的,毕竟内存可以随便加,显存……都被老黄垄断了,价格巨贵无比😂。现在的[llama.cpp](https://github.com/ggerganov/llama.cpp)已经支持了AMX加速,具体的使用方法可以看Intel官网上的[论文](https://www.intel.cn/content/www/cn/zh/content-details/791610/optimizing-and-running-llama2-on-intel-cpu.html),看起来需要安装Intel oneAPI的库才能编译使用。我折腾了一下编译完跑了一下DeepSeek-R1 32B Q4_K_M蒸馏版,速度大概是5.2token/s。然后我安装了个[Ollama](https://ollama.com/),它自带的这个llama服务器只支持AVX2指令集加速,但是我试了一下速度能达到4.8token/s,也就是说AMX指令集加速了个寂寞,几乎没起倒什么作用,难怪没什么人讨论。不过我也听说纯CPU跑大模型主要瓶颈在内存带宽上,我插4条也就是四通道,其实也不是它的全部实力,它最大支持八通道,也许给它插满效果会好一些吧……
|
||||
那旧服务器呢?我倒也试了一下,用Ollama跑一样的模型大概是2token/s多的速度,也就是说新的相比旧的也只快了1倍多一点,而且旧的每个CPU只有2条内存,只有双通道,速度也只有新的一半,结果新的才领先了一倍多一点,都上了那么多黑科技……看来Intel是真不行了。
|
||||
当然5.2token/s的速度显然是无法接受的,还是有点慢了,再加上DeepSeek-R1还有思维链,在回答问题前还要生成一堆废话,那就更慢了(其实要我说它那个思维链其实就是把之前的AutoGPT的结果作为训练材料训练的,相当于集成到模型里了,我自己测了一下水平还是不够用,包括官网的满血版也一样)。我之前听说有一种叫做“投机采样”的推理加速技术,不知道为什么凉了,llama.cpp编译的产物里还有这个技术的PoC。于是我就下了个DeepSeek-R1 7B Q4_K_M蒸馏版,拿来试试看用它来加速32B的怎么样。首先我单独测试7B的速度可以达到20token/s,然后我用“llama-speculative”测了一下,感觉有点一言难尽……一阵快一阵慢的,总体来说感觉不如直接跑的快,难怪这个技术凉了😥,不过也可能是因为这两个模型的什么token分布不太一致,毕竟是蒸馏的模型估计还是有点区别,所以体验不太好吧。
|
||||
那除了大语言模型之外还有什么可测的吗?其实就像我开始说的,要说能满足业务,洋垃圾显然是绰绰有余,尤其还是顶尖的洋垃圾,普通的业务甚至都不能让洋垃圾产生瓶颈,新的不就更不可能了😥……
|
||||
|
||||
# 感想
|
||||
从上面来看,新服务器真的没什么优势啊,性能提高了一些,但是价格翻几十倍,当然那些洋垃圾当年也是超级贵的东西,只是被淘汰了所以失去了价值……不过说来这个价值也许并不是服务器硬件本身的价值,“服务”也是很值钱的啊,像那个支持服务(比如远程诊断、上门服务,现场响应之类的)就是它贵的原因吧,二手的旧服务器2019年就结束支持了,新的有3年的支持期,能到2027年,不过我感觉在这支持期内恐怕没有能用到的地方啊,服务器还是挺难坏的,它最值钱的地方似乎只能被浪费掉了🥲。所以总的来说只有行业领先的业务,才配得上最新的服务器,小规模的业务还是用二手服务器吧😆。
|
35
_posts/2025-02-22-llm.md
Normal file
35
_posts/2025-02-22-llm.md
Normal file
@ -0,0 +1,35 @@
|
||||
---
|
||||
layout: post
|
||||
title: 近期LLM的部署与应用经历
|
||||
tags: [LLM, AI, 人工智能]
|
||||
---
|
||||
|
||||
玩AI开始变的有些烧钱了啊……<!--more-->
|
||||
|
||||
# 起因
|
||||
在几年前我就已经[探索并玩过很多LLM了](/2023/04/05/ai.html),不过近些日子在这方面的发展似乎影响到了我的生活……由于近期某公司开发的DeepSeek在国内非常火,导致我也不得不跟上这个热潮去考虑怎么应用它。当然对于普通人来说,使用它并没有什么难度,即使DeepSeek的官方网站和APP现在基本不能用,现在各家大公司也都自行搭建了,目前我感觉使用DeepSeek体验最好的是百度,其他家使用无论是可用性还是速度都比不过百度,而且目前百度也没有限制使用量之类,还是挺不错的。
|
||||
但是对我来说却不能直接使用其他公司的产品,其实要从成本来说接入其他公司的接口显然是要便宜的多,但是我需要应用的地方可能连不上那些接口😅,所以需要考虑自己搭建。
|
||||
|
||||
# 部署经历
|
||||
为了能自己搭建DeepSeek,首先就得买硬件了……虽然前段时间[整了台新服务器](/2025/02/09/server.html),但是让CPU来跑还是太吃力了,速度太慢了……所以为了能轻松的跑起来,最近整了张RTX4090 48GiB显存魔改版(但是手头没有空闲的机器了,只能插在一台用着[i5-8400](https://www.intel.cn/content/www/cn/zh/products/sku/126687/intel-core-i58400-processor-9m-cache-up-to-4-00-ghz/specifications.html)处理器的主机,这下成狗骑吕布了🤣)。有了这张显卡,跑DeepSeek-R1的蒸馏模型(从1.5B到70B的Q4_K_M量化版)倒是轻轻松松,用Ollama跑70B的模型也能到20Tps的速度。但是根据测试来看,这些蒸馏模型的效果很差,基本上没法用,这些模型经常会发生不遵守指令,内容随机掺杂英文,而且也经常发生逻辑错误,和671B的完整版完全不能比,用起来还不如Qwen2.5各规模的模型。
|
||||
那怎么办呢?前几天清华大学的某个团队更新了一款叫做[KTransformers](https://github.com/kvcache-ai/ktransformers)的框架,据说它可以利用Intel的AMX指令集然后配一张RTX4090可以让DeepSeek-R1 671B Q4_K_M量化版跑到13Tps,能跑到这个速度那至少是可用级别了,调其他公司的接口基本上也就是这个速度,之前买的新服务器不就有这个指令集嘛(之前还感觉这个指令集有点鸡肋呢,看来还是开发度不够啊😆),如果再配一个CPU,然后把内存插满也许就可以了?可惜R760xs插不了全高的显卡,要想插全高的估计就只能买R760了,或者用PCI-E延长线?不过那样感觉不太可靠……不过之后肯定还是会想办法上完整版的模型,毕竟它的效果确实是不错,最关键的是它的市场认可度高,上了就能提高产品竞争力,所以之后应该会想办法搞到满足KTransformers的硬件然后跑起来,或者等[llama.cpp](https://github.com/ggml-org/llama.cpp)合并它的算法,然后用llama.cpp会更好一些。
|
||||
不过我更倾向于等Mac Studio M4 Ultra出来,应该过几个月就能出,按照目前发展趋势来看,新款Mac Studio应该会有更大的内存,理论上可以跑的动一些效果更好的[动态量化版](https://unsloth.ai/blog/deepseekr1-dynamic)(现在能在M2 Ultra上跑的那个1.58位的效果还是不太行),相比于价格十几万的服务器,Mac Studio估计不到十万,可以说是非常有性价比了。当然如果等不及的话应该还是会选择花十几万买个有双路第四代至强可扩展处理器加512GiB内存的服务器吧……
|
||||
|
||||
# 应用经历
|
||||
有了模型之后如果只是聊天那就没必要费这么大劲了,费劲搭当然是为了能让它参与到实际的工作当中。不过该如何应用它呢?首先要让它知道工作的内容,所以第一步要搞出知识库。知识库的原理倒是很简单,我之前就给我博客的[聊天机器人加了RAG功能](/2024/09/27/rag.html),核心就是嵌入模型和向量数据库。不过我写的那个全都是为了能使用Cloudflare的功能,脱离了Cloudflare就没用了。那如果要在本地搞应该怎么办呢?我之前用过的[1Panel](/2024/02/03/1panel.html)开发它的公司旗下有个叫[MaxKB](https://github.com/1Panel-dev/MaxKB)的产品看起来很不错,它使用了PGSQL和[pgvector](https://github.com/pgvector/pgvector)作为向量数据库来搭建知识库,而且它是用Python写的,还能用Python来写自定义功能的函数库,另外它还能用可视化的方式来设计工作流,可以轻松构建需要的逻辑,从功能上来说我还是挺满意的。
|
||||
使用也挺简单,在设置里可以添加使用其他公司API的模型,也可以使用Ollama,不过这一步有个坑,Ollama并不支持设置API Key,但是它添加模型却要求配置一个API Key,文档说可以输入任意内容,我输了一个空格,可以保存,但是使用的时候会报网络错误,所以它文档里怎么不说明一下是除了空格之外的任意内容😅,浪费了我不少时间。
|
||||
在添加知识库的时候可以除了[内置的嵌入模型](https://github.com/shibing624/text2vec)(好像是腾讯的员工搞的模型),也可以用Ollama的嵌入模型。它自带的嵌入模型用的是CPU,文档规模大的情况速度比较慢,因为在Cloudflare上我用的是BAAI的BGE模型,效果还可以,所以这次我还是选了它,但是选的是中文模型,这样就不需要再翻译了🤣。
|
||||
开始我对MaxKB印象还是挺不错的,但是用着用着……在建第六个应用的时候它显示社区版只能创建五个应用😅,对于开源软件这样做限制我也是大开眼界了,要是说有些专业版功能不开源,是DLC的形式,付钱来获取更多的功能代码,我还能理解,在开源代码上做数量上的限制,这垃圾公司多少有点看不起人了😅。
|
||||
那对于这种挑衅行为该怎么反制呢?它的代码倒是没有混淆之类的,还算不错,比我以前用过的[KodExplorer](https://github.com/kalcaddle/KodExplorer)要好,它还整个“部分开源”,有个[关键文件](https://github.com/kalcaddle/KodExplorer/blob/master/app/controller/utils.php)直接是混淆过的,想改都改不了😅,至少MaxKB还能随便改。
|
||||
我大概看了眼代码,只需要改两个文件就行,一个是“apps/common/util/common.py”,把其中“valid_license”函数进行判断的部分全部注释,另外一个文件是“apps/setting/serializers/valid_serializers.py”,把“ValidSerializer”方法中的“valid”方法里进行判断的部分全部注释就可以了,开源还做限制我是真的无法理解……
|
||||
如果是用1Panel部署的,可以把那两个文件放到“/opt/1panel/apps/maxkb/maxkb”目录下,然后在docker-compose.yml文件的volumes段添加:
|
||||
```yml
|
||||
- ./common.py:/opt/maxkb/app/apps/common/util/common.py
|
||||
- ./valid_serializers.py:/opt/maxkb/app/apps/setting/serializers/valid_serializers.py
|
||||
```
|
||||
就可以了。
|
||||
不过总体来说从功能上我还算比较满意,就原谅它搞出这种奇葩的行为吧😆。
|
||||
MaxKB主要是为了能给更多人使用,所以是网页版,部署也略显麻烦,如果是自己用呢?我之前看到过一个桌面软件,叫做[Cherry Studio](https://github.com/CherryHQ/cherry-studio)。它更适合开箱即用一些,功能上可能不如MaxKB强大,但是比较方便一些。比如上传文档,MaxKB需要在流程图中自行处理,这个软件会帮你处理好;添加知识库可以直接添加本地的文件夹,不用上传到服务器上;另外安装比较方便,不像MaxKB搭环境比较麻烦些,所以个人用的话可以用Cherry Studio。
|
||||
|
||||
# 感想
|
||||
总的来看,DeepSeek的出现还算可以,虽然它受到的关注和它的能力也许并不匹配,但是毕竟现在的它已经是人人都能蹭的东西了,谁都能挂它的名头,我们来蹭一蹭也能分点它的好处。当然这样的结果倒也不差,开发DeepSeek的公司只能获得他们应得的部分,其他的关注度就应该被各家公司瓜分😆。我在这期间虽然很难获得什么实质性的收获,但是能在这期间能搞点很贵的硬件之类的玩玩也是不错的体验啊🤣。
|
27
_posts/2025-03-08-llm2.md
Normal file
27
_posts/2025-03-08-llm2.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
layout: post
|
||||
title: 近期LLM的部署与应用经历(2)
|
||||
tags: [LLM, AI, 人工智能]
|
||||
---
|
||||
|
||||
最近AI发展好快啊~<!--more-->
|
||||
|
||||
# 起因
|
||||
自从[上次](/2025/02/22/llm.html)写完文章之后,最近这段时间LLM圈又有了不少更新,感觉很值得试试看。所以这次就来看看这些新东西有什么特别的地方吧。
|
||||
|
||||
# 关于阿里QwQ模型的体验
|
||||
前两天阿里的推理模型QwQ模型更新到正式版了,不过其实我也没试过他们的预览版效果怎么样……但按照他们的说法,他们的32b参数的模型水平已经相当于DeepSeek-R1 671b的模型了。如果真是这样,那就太好了,毕竟那个671b参数的模型部署难度还是相当大的,在当时想部署一个能用级别的还是挺烧钱的。但如果这个32b参数的模型能达到相同水平,那就完全没有必要买那么贵的硬件了。像上次买的RTX4090 48GiB显存魔改版可以轻松跑QwQ 32b Q8量化的版本(速度能达到23T/s),就算想跑没有量化的fp16版,也只需要再买一张RTX4090 48GiB就够了,这个成本相比DeepSeek-R1低太多了。
|
||||
所以刚发布的那天我下午就把模型下载下来试了试,随便试了几个问题,答得效果确实不错,我对比了一下DeepSeek-R1,试了试“世界上最长的单词中哪个字母最多”这个问题,两边回答的格式几乎一样,都说的是“硅肺病”的英文,并且都进行了字母数量分析,主要的结论都分析正确了,但是第二多和第三多的字母数量两边说的都不完全正确。另外我还试了试DeepSeek-R1的14b和70b蒸馏版,虽然回答正确了,但是并没有分析具体字母的数量,所以从这一点来看确实是和DeepSeek-R1的水平很相似。不过后来我又让其他人试了试文本分析之类的能力,似乎没能达到他们的预期,另外我还测了测比较宽泛的问题,以及解析文本之类的问题,结果很多问题没能正确回答……所以还是不能和DeepSeek-R1相比较,不过相比DeepSeek-R1各个蒸馏版的水平还是强了不少的,至少没有出现在回答结果中随机输出英文的情况,但是偶尔会出现没有闭合标签“</think>”的情况,看起来应该不能用于生产环境……要想正经用还是得用完整版的DeepSeek-R1,但毕竟成本问题还是很大啊……所以如果需要考虑成本问题的话用QwQ还是很不错的选择。
|
||||
不过QwQ相比DeepSeek-R1还有一个优势,那就是支持Agent能力,原生支持调用用户提供的函数,像它虽然解析文本的能力不怎么强,但是它可以调用工具来处理,而DeepSeek-R1要想支持就得写提示词,但是毕竟没有专门训练过,不一定能正确使用工具(虽然我没试过😝)。
|
||||
另外说到Agent,好像有个叫“Manus”的产品挺火?但那个我实在没兴趣,一点技术含量都没有,还搞什么邀请码,一看就是买的水军,而且还被人不到一天时间实现了开源版[OpenManus](https://github.com/mannaandpoem/OpenManus),给人笑掉大牙了🤣。
|
||||
|
||||
# 关于新出的Mac Studio的看法
|
||||
搭完整版的DeepSeek-R1即使是使用上次所说的[KTransformers](https://github.com/kvcache-ai/ktransformers)框架也是相当费钱的,最起码也得10万CNY左右。但最近几天苹果出了新的Mac Studio,最高配的M3 Ultra可以选配512GiB的内存,可以轻松跑DeepSeek-R1 671b Q4_K的版本,然后价格最低仅需7.5万CNY。我之前还想着是出M4 Ultra呢……结果出了个M4 Max,不过新的Mac Studio出的速度比我预期的快了好多,我本来以为会在WWDC25的时候出呢……看来是想借DeepSeek-R1大卖一波,当然从这个产品来说确实应该是会大卖的,回头看看能不能搞一个来。不过现在才刚开售,还没人拿到实物呢,也没人实机跑一下,所以先等等最早买到的人跑一波看看,如果效果好的话也许能整一个呢……
|
||||
|
||||
# 关于如何查看MaxKB的完整接口文档
|
||||
上一篇文章我说明了一下如何解除MaxKB用户、应用以及知识库的数量限制,后来我发现它还限制了社区版查看完整API文档的能力😅,这个限制给我看的那叫一个大开眼界,它居然还给这个文档整了个硬编码的密码,从来没见过这么搞开源的,具体就是[这一行](https://github.com/1Panel-dev/MaxKB/blob/f1a1c40724ceba108febb416aadb01ccb71c3add/apps/common/init/init_doc.py#L80)。虽然我不知道这里面提到的MD5对应的密码是多少,但是既然是开源代码,我把这句话删了不就行了……不过实际上不太行,因为它使用了Django的国际化功能,直接删掉会影响这个文件的行数,程序会报错。不过可以仔细看一下关于“init_chat_doc”这一行在密码的判断后面加了个“or True”,看来是MaxKB的开发者后来应老板要求放开“chat_doc”的限制,但是又懒得改国际化那边的东西所以加的这个吧🤣,那既然这样,我直接给“init_app_doc”对应的那句话也加个“or True”不就行了,加完之后打开“/doc/”路径,就可以看到MaxKB的完整API文档了,不需要自己手动再去抓包测试了。
|
||||
至于其他的专业版功能我看了一下应该确实是需要用到XPACK包的(不过其实关于修改页面风格的前端开源了,后端在XPACK里,要想用得自己实现接口),开源的这部分最多只能到这里了,估计是这些限制没法单独搞一个包,所以他们就直接在开源代码上做限制😅,看来他们老板也是没眼力啊。
|
||||
其实与其余用MaxKB,不如用[Dify](https://github.com/langgenius/dify),至少它没有在代码里塞莫名其妙的东西来恶心人,文档也相对更完备,不过它目前还是相当的不成熟,有很多BUG,比如上传知识库显示支持Excel,但是解析的时候会失败,上传知识库如果通过改配置超过15M解析也会失败,还有它的插件很多也是不能用,比如目前阿里云的百炼会报错,退回上个版本就不支持思维链的展示等等……总之不太适合生产使用。
|
||||
|
||||
# 感想
|
||||
现在的AI发展确实是快啊,才几天时间又有一堆有意思的发展,应该说现在很多公司都在趁这个机会来发布自己的产品吧,感觉现在也是一个能有很多机会的时刻,不过AI对研究能力的要求也是相当高的,想在这个时间蹭热度也得有相当厉害的能力……像阿里的水平也是相当强的,可惜营销水平不太行😆。只是像我应该也只能看着大公司的百花齐放吧,看看接下来的时间还会不会出现一些有意思的东西。
|
26
_posts/2025-03-22-hifi.md
Normal file
26
_posts/2025-03-22-hifi.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
layout: post
|
||||
title: 关于HiFi的尝试与探索
|
||||
tags: [HiFi, 音乐]
|
||||
---
|
||||
|
||||
如何才能听到最原始的音乐呢?<!--more-->
|
||||
|
||||
# 起因
|
||||
前段时间,有人在QQ群中送网易云音乐的7天体验VIP,于是随手领了一份。有了VIP之后除了可以下载仅限VIP的音乐以外,还可以选择更好的音质。我现在用的是[MacBook Pro](/2023/02/03/mbp.html),据说在笔记本中音响效果是最好的,那么我为了能对得起这优秀的音响,也不该听垃圾音质的音乐,所以就来探索一下如何听到HiFi的音乐吧。
|
||||
|
||||
# 获得音乐
|
||||
下载音乐很简单,直接下一个网易云音乐客户端就可以,不过需要注意要在设置中修改下载音质,默认选项不是最高音质。另外它这个VIP还不是最高的,再往上还有SVIP,可以听所谓的“超清母带”的音质,我不太清楚这个无损以上的那些音质到底是什么东西,也不可能为了这点东西给网易云充钱,所以我就选了个“高清臻音”的选项。
|
||||
当我在下载一些免费歌曲的时候,下载到的文件是flac格式,看起来应该是没什么问题。但是下载VIP独享音乐的时候,正在下载时是flac格式,可是下载完就变成ncm格式了……虽然我知道有一些解密这些格式的软件(GitHub上有,不过好多都被DMCA takedown了,虽然也能搜到[一些](https://github.com/rainlotus97/unlock-music)……),不过我还是比较好奇这个过程,既然它下载时是flac,那我在它刚下载完要变成ncm之前把网易云音乐强制结束掉不就可以获得完整的flac文件了嘛。试了一下还真可以,也就是说这个ncm加密的过程是在客户端完成的,而不是在服务器上,这还真是有点离谱……我用这个方法下载了几首喜欢听的歌,试了一下都能正常播放。不过用这个办法下载的音乐在客户端的下载中看不到,所以就没有歌词之类的东西了。
|
||||
|
||||
# 分析音乐
|
||||
虽然说下载下来的文件是flac格式,但是不代表这就是无损的音乐。毕竟从网易云音乐的“无损”以上的选项都是flac的,那到底它这个无损是真无损吗?首先我在网上搜了一下,网易云音乐的黑历史很多,有些人在网易云音乐上上传了mp3的音乐,结果也有无损的选项。也就是说它这个flac很有可能是直接用mp3转换格式过来的。那这样我就不愿意了,我可以接受下不到无损,但是不能接受本来是mp3格式然后转成flac结果文件体积大增,给我的硬盘塞一堆没用的数据,所以现在我需要证明刚刚下载的音乐不是一堆没用的垃圾。
|
||||
我看有人说可以使用[spek](https://github.com/alexkay/spek)查看时频谱来验证,如果是直接用mp3格式转换的flac文件会被整齐的砍一刀,因为mp3格式支持的最大采样率是48kHz,而根据香农采样定理,采样频率应该大于等于模拟信号频谱中最高频率的2倍,那么mp3支持的最高频率就是24kHz,所以用mp3转换出来的flac一般会在24kHz那里切一刀,更有甚者,如果是44.1kHz采样率的mp3就会在22kHz左右的位置切一刀。不过理论上人类的听力上限就是20kHz,更高的频率理论上人类应该是听不到。但毕竟我们追求的是HiFi,和人类能不能听到没有关系,要保证的是完整的复刻**所有**的信息。
|
||||
于是我在我的Mac上用brew安装了spek,安装好之后直接执行spek+音乐文件的位置就可以了,我看了一下刚刚从网易云上下载的音乐,全都是96kHz采样率的音乐,而且没有被切过的痕迹。那这样就能证明网易云音乐就是真无损了吗?其实我也不知道,因为我没有从发行商直接获得的原始文件,一般要对比原始文件才知道是不是无损的……不过我在网上看说无论是“高清臻音”还是“超清母带”无一例外全都是用AI升频制作的,所以看时频谱已经没有意义了……但是我又没有证伪的方法,那就只能先凑合听喽~
|
||||
|
||||
# 播放音乐
|
||||
既然音乐已经下好了,那么我直接用我的MacBook Pro播放的音乐它够HiFi吗?虽然我能听出mp3中128kbps和320kbps的区别,但是再高的我也听不出来……不过HiFi要的不是人能不能听出来,而是它发出的声音是不是完美还原。这要怎么证明呢?虽然我没有办法听出来,但如果有可视化的分析至少能看出来,于是我在手机上下载了一款“声音分析仪”软件,它可以用FFT算法分析手机话筒收集到频谱然后展现出来。只是可视化之后……我也很难看出来它够不够HiFi啊,当然理论上如果能保证播放音乐的音响和收听音乐的话筒都是最好的,那么两边的频谱应该是一样的,但是现实中还有底噪的存在,不可能完全一样……虽然如此,但我在看频谱的时候发现,播放的音乐最高频率似乎只有20kHz,我已经测过手机的话筒是能接收到更高的频率的,既然MacBook Pro的音响是最好的,怎么会只能播放20kHz的声音呢?而且它这个20kHz很明显有一刀切的感觉,应该是哪里配置错了。
|
||||
于是我搜了一下,Mac默认输出的声音貌似只有44100Hz的采样率,需要在“音频MIDI设置”中将扬声器输出的格式改成更高的才能播放更高的频率。不过这也挺奇怪的,44.1kHz的最高频率是22kHz啊,为什么会在20kHz那里砍一刀呢?看香农采样定理所说的是大于等于,也许就是这个原因吧?既然我的音乐都是96kHz采样率的音乐,那么我就应该把这里的设置改成一样的。改完之后又测试了一下,发现确实是突破了20kHz,但好像没有超过22kHz,不过至少没有“砍一刀”的痕迹了,也许是音乐本身就是这样,或者是扬声器最高只能到这个水平了吧。其实我也没有那么追求HiFi,能到这样我已经很满意了。
|
||||
|
||||
# 感想
|
||||
虽然对人来说也许听HiFi并不能听出来什么,但是追求HiFi还是挺有意思的,毕竟提高还原程度是可以通过可视化的方式看到的,既然如此,那就是有追求的价值。看不见的东西是玄学,可以不去追求,但是HiFi是实实在在存在的,这样也就能理解为什么会有人花大价钱去买各种昂贵的设备来提高还原度了,因为这是真的可以起到作用的啊……当然对我来说,能0成本做到尽可能的HiFi才是最重要的,花钱达到HiFi就没什么必要了🤣。
|
39
_posts/2025-03-25-utm.md
Normal file
39
_posts/2025-03-25-utm.md
Normal file
@ -0,0 +1,39 @@
|
||||
---
|
||||
layout: post
|
||||
title: 在UTM中使用苹果虚拟化的各种尝试
|
||||
tags: [虚拟化, 苹果, UTM]
|
||||
---
|
||||
|
||||
用官方的方式做非官方的事!<!--more-->
|
||||
|
||||
# 起因
|
||||
在几年前刚[收到MacBook Pro](/2023/02/03/mbp.html)的时候,我曾安装过虚拟机软件[UTM](https://github.com/utmapp/UTM)。但是因为我的Mac内存很小,用虚拟机的体验很差,所以就把UTM卸载掉了。不过以前还我还[装过一台黑苹果](/2024/06/16/hackintosh.html),在上面也安装了UTM。
|
||||
最近正好由于某些原因我需要在macOS上安装虚拟机,既然有UTM用就继续用UTM了。当然正常情况就是按正常的方式安装系统然后正常的用,这并没有什么意思。所以我想整点有意思的事情,想试试不太正常的使用UTM😝。
|
||||
|
||||
# 在UTM中使用苹果虚拟化框架安装Windows
|
||||
如果用过UTM的话应该知道,UTM有很多选项,比如底层的虚拟化框架可以用QEMU或者[Virtualization.framework](https://developer.apple.com/documentation/Virtualization)(VZ),而QEMU的后端可以选TCG或者是[Hypervisor.framework](https://developer.apple.com/documentation/hypervisor)(HVF)。它们有很多特色,像TCG的兼容性最好,可以模拟任何架构的CPU,但是性能最差,HVF使用硬件虚拟化加速,只能运行宿主机架构的程序,但是性能比较好,而VZ经过了苹果官方优化,性能最好。
|
||||
那么现在我想安装Windows,又想有最好的性能,那我应该选择VZ吧?可是UTM不允许我这样选择,如果选择安装Windows就会强制使用QEMU……只有Linux或者macOS(在ARM处理器)才能使用VZ……那我应该如何绕过这个限制呢?
|
||||
我想起来之前[让没用的主机感染木马](/2024/11/02/trojan.html)的文章中使用了[一键DD/重装脚本](https://github.com/bin456789/reinstall)把我服务器的Linux系统重装成了Windows系统,那么我能不能用相同的方式先按照正常的方式用VZ安装一个Linux系统然后使用这个脚本重装成Windows?我觉得理论上应该没问题,所以就尝试了一下。
|
||||
我在这之前已经安装过了一个用了VZ的Ubuntu虚拟机,新建比较费时间所以就直接把这个虚拟机复制了一份。然后下载了重装脚本准备重装系统,但是看说明现在不能让脚本自己查找系统镜像安装了,不过没关系,前段时间我下了一份Windows 10的镜像,接下来我只需要在镜像所在目录执行
|
||||
```bash
|
||||
python3 -m http.server
|
||||
```
|
||||
开启一个文件服务器,然后在虚拟机中执行
|
||||
```bash
|
||||
bash reinstall.sh windows --image-name "Windows 10 Pro" --iso "http://192.168.64.1:8000/windows.iso"
|
||||
```
|
||||
就可以了,执行后重启就可以在UTM的虚拟机界面中看到脚本执行的一系列操作。在这期间都很顺利,然而在它执行完之后,虚拟机的屏幕就黑了,而且重启也没有任何变化,看来是实验失败了?不过也可能是因为苹果整的虚拟显示器在Windows中识别不出来,所以显示不出东西,因为我看活动监视器中CPU的占用率也在跳变,虚拟机应该仍然在运行,于是我下载了[Windows App](https://apps.apple.com/us/app/windows-app/id1295203466)(以前的远程桌面),使用虚拟机之前的IP进行连接,结果连接成功了😆。看来苹果的虚拟化框架是能运行Windows的嘛,居然没有一个人尝试一下。
|
||||
不过屏幕不能亮是真的没有驱动吗?我看了眼设备管理器,搜了一下那个没有安装驱动的视频控制器的设备ID“1af4:1050”,好像是Virtio GPU,这个驱动我记得在[virtio-win](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/)里是有的,而且重装脚本也会自动下载这个驱动,为什么会没有自动安装呢?可能是设备ID和驱动不一致吧……不过不影响,我选择更新驱动,在列表中选择“Red Hat VirtIO GPU DOD controller”之后UTM的虚拟屏幕中就可以看到画面了,虽然分辨率只能是1024*768……不过能用就很不错了。
|
||||
再接下来我就需要验证一下它的性能是不是最好的,我把这个虚拟机的硬盘复制了一份,新建了一个使用HVF后端的QEMU虚拟机,把这个硬盘挂载上,然后使用国际象棋跑分,看了一下VZ的跑分相比HVF的跑分高了大概5%-10%,还是挺厉害的。
|
||||
至于其他方面,我看了一眼用HVF的QEMU虚拟机CPU不能显示正确的型号,而VZ是可以的,另外VZ的SMBIOS信息中也可以看到Apple的字样,证明这个Windows确确实实是跑在了苹果的虚拟化框架。不过以上的测试都是基于x86架构的macOS,等回头我的Mac Studio到了之后再在ARM架构的macOS上再测一下,看看能不能用相同的方式安装,如果可以的话,说明VZ的虚拟机没什么兼容性的问题,UTM应该放开使用VZ安装Windows的选项,让我们测测苹果的技术才对。
|
||||
|
||||
# 在macOS 12中的UTM使用苹果虚拟化框架安装Linux
|
||||
虽然在刚刚的测试中,用VZ安装Linux就和其他普通的虚拟机安装Linux一样简单,但是之前的测试是在macOS 15上测的。现在我遇到了一个新问题,我现在有一台2016年的Mac,上面运行着macOS 12,而且不能用OCLP升级到macOS 15(因为不是我的电脑)。现在我想在这台电脑上用苹果虚拟化框架安装Linux,虽然用QEMU更简单,但是感觉没意思。在macOS 12中不支持UEFI bootloader,所以我需要手工准备内核镜像之类的东西。
|
||||
当然从零开始有点难,我打算先用QEMU安装一遍Ubuntu Server。在创建虚拟机之后需要注意,要把刚创建好的虚拟机的硬盘删掉,因为那是qcow2格式的,在VZ中只支持img格式的硬盘,所以删掉之后需要创建一个“RAW映像”,然后按照正常的方式安装系统。
|
||||
安装好之后从“/boot”目录中把“vmlinuz”和“initrd.img”复制出来,作为Linux内核和初始Ramdisk,我看说明上要未经压缩的Linux内核映像,但是好像是压缩的也能用🤔。随后关机把在QEMU中的硬盘映像复制出来,作为根文件系统映像。
|
||||
至于启动参数,可以看“/boot/grub/grub.cfg”中内核后面跟的那串,然后再加上“console=hvc0”,因为macOS 12中使用VZ没有虚拟屏幕,只能用虚拟串口连接。在一切准备好之后就可以开机了,在一串内核信息不停滚动后,显示出了登录的提示符,实验就成功结束了。
|
||||
不过这样启动的话在系统中所有对内核以及对initramfs的更新就全都不会生效了,毕竟虚拟机根本读不到内核了……这倒是影响不大,反正不更新也不是不能用,更何况macOS都不打算更新,虚拟机不更新又能怎样呢🤣。
|
||||
|
||||
# 感想
|
||||
看来苹果的“不支持”不代表真的不支持,想想既然是虚拟机,当然就不应该限制系统类型啊,毕竟虚拟机虚拟的是硬件,又不是软件。不过倒是也能理解苹果不需要声明支持自己的竞品,所以也没必要做相应的兼容和测试,但居然没见到有人尝试一下,也挺奇怪,明明用Mac的人也有不少对技术很有探索精神的人啊……
|
||||
不过随着macOS的更新,像这些非官方支持的办法估计也很有可能出问题,毕竟苹果并不对这些情况进行任何形式的保障,也许以后苹果的哪次更新这个方法就用不了了呢……
|
77
_posts/2025-04-04-search.md
Normal file
77
_posts/2025-04-04-search.md
Normal file
@ -0,0 +1,77 @@
|
||||
---
|
||||
layout: post
|
||||
title: 最近对博客搜索功能的优化记录
|
||||
tags: [博客, 搜索, 优化]
|
||||
---
|
||||
|
||||
看看其他的博客也会有新的灵感啊~<!--more-->
|
||||
|
||||
# 起因
|
||||
前段时间,我闲来无事在GitHub上搜和我使用相同模板[minimal](https://github.com/pages-themes/minimal)的博客。但搜索结果中有许多人用这个模板制作的是简历或作品集,这让我有些失望。不过这倒也能理解,因为这个模版并不算博客模板,没有文章列表之类的代码,这些都只能自己写。当然多找找还是能找到一些的,毕竟这个模板在GitHub Pages中算是最受欢迎,至少符合大众的审美。像我就搜到了一个叫[Guanzhou Hu的博客](https://github.com/josehu07/josehu07.github.io),他对模板的样式做了不少的改动,而且改的还挺好看的,尤其是右上角的导航栏,看起来挺有意思,只是这个源代码……导航栏有点硬编码的感觉,我不是很喜欢这种实现方式……
|
||||
|
||||
# 使用标签作为关键词进行搜索
|
||||
之后我又看了看其他博客,看到了[Matt Walker Blog](https://github.com/mhwalker/mhwalker.github.io)。他没有对模板做很多改动,只是把section元素变得更宽了,但是他没有改手机版自适应的样式,导致界面基本上没法在手机上查看。不过在他的首页中,我对他把文章标签放在文章列表这个操作非常感兴趣,因为每次我都有给文章打标签,但是几乎没什么用。他的标签点进去之后会跳转到该标签下的所有文章,我其实很早就想做这个功能了,但是在不用插件的情况下Jekyll基本上做不出来这种功能,因为没有插件的情况下是不能使用Liquid标签创建文件的,我看了下他的实现,原来是提前创建好的标签页面然后进行筛选的,这个实现我也不喜欢,这样的话我每次打标签都要新建一个标签对应的页面,这种事情不让程序做我会很不爽……(其实现在的GitHub Pages构建网站都是用的Actions了,完全可以自己写一个可以使用插件的Actions来进行构建,不过我也懒得折腾了🤣)
|
||||
要么还有一个选择,可以单独搞一个页面,里面有所有标签对应的文章,点击文章的标签之后使用锚链接定位到对应标签所在的位置。但这样会导致一个页面有可能有一堆相同的文章链接,结果这个页面比归档页面的链接还多,那就感觉有点糟糕了……
|
||||
不过我想起来以前做的[博客全文搜索功能](/2021/07/23/search.html),如果把标签作为关键词进行查询,那也能起到筛选出标签对应文章的作用吧?而且这样即使我没给那个文章打标签也能搜出来,其实也算不错的选择,另外自从我做出来那个全文搜索的功能之后也没用过几次,没有关键词的话也一时半会想不出来搜什么比较好。于是说做就做,直接把Matt Walker Blog那段在文章列表生成标签的代码复制过来,感觉好像还不错😆?
|
||||
顺便我也把文章里面的标签也加了链接到搜索的功能,不过原来的代码用的是`.join`实现的,现在加上这个功能的话就只能老老实实用循环写了😥……
|
||||
|
||||
# 搜索后使用高亮标记关键词
|
||||
上面的标签搜索效果还不错,只是有些关键词搜完之后有点难发现。我搜索出来之后怎么证明搜到的内容里面一定有对应的关键词呢?虽然从程序的角度来说这是理所应当的事情,一定是有的数据才可能被搜到,但有时候不用Ctrl+F看一眼都不知道是哪里搜到了……所以我觉得应该像其他网站一样对搜到的内容用高亮进行标记。标记应该用什么呢?用样式也许不错,不过现在的H5标签里有一个叫mark的标签可以直接用,用这个标签包裹的内容背景颜色就会变成黄色,就像用荧光笔标记了一样,这样就不需要写样式了。
|
||||
至于关键词用查询字符串传过去就好了,那我该怎么做呢?我用的搜索脚本叫[Simple-Jekyll-Search](https://github.com/christian-fei/Simple-Jekyll-Search),它的文档其实根本没有写怎么把搜索的请求传到模版里,还好它有个[关于模版的测试脚本](https://github.com/christian-fei/Simple-Jekyll-Search/blob/master/tests/Templater.test.js)里面有写,有个query关键词可以把搜索内容给模版渲染出来,既然做了这个功能怎么不写在文档里😅,不过这个项目已经停止,也没法提出什么建议了……
|
||||
这个功能听起来相当简单,我都懒得写了,这种简单的功能直接让AI写才对!于是我把需求告诉它,让它给我实现一份,于是这就是让AI给我写的高亮关键词的JS代码(经过了一点修改):
|
||||
```javascript
|
||||
$(function() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const keyword = urlParams.get('kw')?.trim();
|
||||
|
||||
if (!keyword) return;
|
||||
|
||||
// 转义正则表达式特殊字符,避免安全问题
|
||||
const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
// 创建不区分大小写的正则表达式(全局匹配)
|
||||
const regex = new RegExp(`(${escapedKeyword})`, 'gi');
|
||||
|
||||
// 递归遍历并高亮文本节点
|
||||
function highlightTextNodes(element) {
|
||||
$(element).contents().each(function() {
|
||||
if (this.nodeType === Node.TEXT_NODE) {
|
||||
const $this = $(this);
|
||||
const text = $this.text();
|
||||
// 使用正则替换并保留原始大小写
|
||||
if (regex.test(text)) {
|
||||
const replaced = text.replace(regex, '<mark>$1</mark>');
|
||||
$this.replaceWith(replaced);
|
||||
}
|
||||
} else if (
|
||||
this.nodeType === Node.ELEMENT_NODE &&
|
||||
!$(this).is('script, style, noscript, textarea')
|
||||
) {
|
||||
highlightTextNodes(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('section').each(function() {
|
||||
highlightTextNodes(this);
|
||||
});
|
||||
});
|
||||
```
|
||||
我测试了一下,非常符合我的需求,各种情况都能按照我的预期工作,虽然说功能非常简单,但是能正常运行,AI写的还是挺不错的。
|
||||
|
||||
# 近期的其他修改
|
||||
除了对搜索功能的优化,我还做了些别的功能:
|
||||
## 随机跳转文章
|
||||
前段时间我看到有其他人的博客增加了一个随机跳转文章的功能,不过他的博客是动态博客,实现也比较奇葩,是渲染页面时就已经决定好要随机的文章,也就是说无论用户想不想随便看看,程序都已经随机好了。当然用着静态博客的我来说,从原理上也做不到这一点,不过既然我之前在做[相似文章推荐功能时](/2024/10/01/suggest.html)已经对搜索功能的数据进行了缓存,那么直接用缓存的内容直接随机就好了吧……所以就随便写了写,代码也极其简单:
|
||||
```html
|
||||
<a href="javascript:getSearchJSON(function(data){window.location = data[Math.floor(Math.random()*data.length)].url})">Random</a>
|
||||
```
|
||||
## 给文章内标题添加锚链接
|
||||
最近在修改我的博客的时候我更新了一下[给文章生成目录的组件](https://github.com/allejo/jekyll-toc),在这时候我想看看它还有什么有意思的组件可以用,然后就发现了[jekyll-anchor-headings](https://github.com/allejo/jekyll-anchor-headings),它可以像GitHub展示Markdown文件一样在标题上添加点击后就可以直接跳转到对应标题的锚链接,而且示例里也给出了怎么做[可以像GitHub的风格](https://github.com/allejo/jekyll-anchor-headings/wiki/Examples#github-style-octicon-links)。看起来挺有意思,所以就给自己加上了😆。
|
||||
## 添加能跳转到原始Markdown的链接
|
||||
在修改博客的时候我参考了一下Jekyll的官方文档,在这个时候发现了page.path这个变量。我想了一下这个变量可以用来链接到我的文章内容,然后就在文章标签位置的右侧加上了这个链接,为了能让它显示在右侧,我用的是`float: right`,但是这样会导致和文章标签不在同一行,查了一下才知道用了浮动就会强制将元素转换成块级元素,而文章标签用的是行内元素,所以对不齐,没办法就只能把这一整行都转换成块级元素了……于是代码如下:
|
||||
```html
|
||||
{% raw %}<span style="float: right;"><a href="{{ site.github.repository_url }}/tree/master/{{ page.path }}">查看原始文件</a></span>{% endraw %}
|
||||
```
|
||||
|
||||
# 感想
|
||||
多看看其他人的博客看来也挺不错,可以看看其他人的想法,说不定就有可以参考的价值呢……不只是文章内容,网站本身的一些功能也是作者的想法啊……而对于那些只套别人模版,没什么自己的改动的博客,那就没什么意思了(当然不会代码的那就没办法了~)。有些人说博客中只有文章才是最重要的,但我觉得对于技术博客来说网站的代码也是展示自己的部分,所以折腾博客本身也是很重要的!
|
30
_posts/2025-04-08-feed.md
Normal file
30
_posts/2025-04-08-feed.md
Normal file
@ -0,0 +1,30 @@
|
||||
---
|
||||
layout: post
|
||||
title: 如何使用JS通过订阅源查看文章?
|
||||
tags: [JavaScript, RSS, Feed, AI]
|
||||
---
|
||||
|
||||
懒得写代码?那就让AI写!<!--more-->
|
||||
|
||||
# 起因
|
||||
前段时间,我看到有些博客给自己的友链页面做了通过订阅源查看友链最近更新文章的功能,看起来挺有意思的,有点想整一个。不过对于我的博客来说,作为静态博客想要做到这样的功能估计没那么简单吧……毕竟一般的订阅软件需要隔段时间请求一下对应博客的订阅链接,然后再把结果存到数据库才行。但是我想了想,对我来说没必要做成订阅啊,我又不需要知道对应博客是什么时候更新的,只要在有人想知道的时候去请求一下订阅链接,然后展示出来就行,感觉似乎又没有那么复杂。
|
||||
既然不复杂,那这个功能就让AI来做吧,正好前段时间有个朋友买了一个月的Devin.ai订阅,据说是可以自己调试代码,还能操作浏览器,而且代码基本上写出来就能用。我对这个挺感兴趣的,所以这次的功能就让它来写吧!
|
||||
|
||||
# 让AI编写代码
|
||||
既然是让AI来写,至少得把我的需求说清楚,所以首先我应该告诉它:
|
||||
> 创建一个JavaScript函数来实现[Links](/links.html)表格中链接的RSS/Atom源预览。
|
||||
> - 当鼠标悬停在表中的链接上时,检查该网站是否有RSS/Atom源,并将结果显示在一个浮动窗口中
|
||||
> - 在鼠标光标后的浮动窗口中显示提要中的5篇最新文章
|
||||
> - 在窗口中只包含标题和时间,不需要链接和内容
|
||||
> - 跳过所有不包含RSS/Atom源的链接,而不显示任何错误
|
||||
> - 当鼠标离开链接时,浮动预览应该消失
|
||||
|
||||
不过在正式编写之前,我还得考虑一下可行性,毕竟是很简单的功能,我不写但我不能不知道怎么写。首先让JS解析Feed数据也就是XML数据应该是很简单的事情,JS应该有自带的函数来实现这种功能。然后是获取数据,在JS中使用fetch就可以了,但是这里有个很重要的事情,浏览器请求其他网站存在跨域的问题,还好我之前在CF Workers上用[cloudflare-cors-anywhere](https://github.com/Zibri/cloudflare-cors-anywhere)搭了个CORS代理: <https://cors-anywhere.mayx.eu.org/> 。所以我应该在说明中给它说清楚:
|
||||
> - 如果存在源,请使用CORS代理:https://cors-anywhere.mayx.eu.org/ 获取并解析它
|
||||
|
||||
随后我就开始让它编写代码了。接下来就能看到AI在浏览器和编辑器中切换,不停的进行编写和调试,等了一段时间,它把第一版代码写好了。不过也许我说的不够清楚,这个CORS代理的用法和其他的CORS代理不太一样,代理链接和被代理的链接之间需要使用“?”分开,另外第一版我也没说清楚RSS/Atom源的链接在哪,所以它选择遍历常见的几种订阅源的路径,这样有点不太好,除了速度慢,对我的CORS代理消耗也比较大。所以我告诉它代理的正确用法,以及让它假设超链接中包含“data-feed”属性,其中包含订阅源的链接,并且随便挑了个网站拿给它作为示例。
|
||||
随后就能看到它继续改改改,改了几次之后我把最后生成的JS复制到浏览器上执行了一下,效果还不错,于是就把它放到我的博客上了。
|
||||
它的水平还是挺不错的,至少正确的实现了功能。不过我有点担心它的代码会不会不太可靠,毕竟要从其他网站上获取数据,得避免出现XSS之类的问题,于是我把代码丢给DeepSeek-R1让它检查了一下,果不其然Devin.ai写的代码似乎有XSS的隐患,如果链接列表中标题有html标签似乎就会解析(虽然我没试过),于是根据DeepSeek的提示修改了一下,增加了一个过滤特殊字符的函数,改完又放到博客上,最终的代码就是:[rss-feed-preview.js](/js/rss-feed-preview.js)。
|
||||
|
||||
# 感想
|
||||
让AI全自动写代码感觉还挺方便,有种当产品经理的感觉了🤣,像这种AI就是Agent吧,这也算是我头一次使用Agent了,感觉用起来还挺不错的。不过从这次尝试来看确实AI也有一定的局限性,像是直接写出来的代码可能存在一些安全性问题,除非单独让AI检查,不然很有可能会写出功能正常但是存在漏洞的代码,所以还是得人看着点,AI搞出事故可是**不负责**的啊😇~
|
79
assets/css/style.scss
Normal file
79
assets/css/style.scss
Normal file
@ -0,0 +1,79 @@
|
||||
---
|
||||
---
|
||||
|
||||
@import "{{ site.theme }}";
|
||||
|
||||
.backToTop {
|
||||
display: none;
|
||||
width: 18px;
|
||||
line-height: 1.2;
|
||||
padding: 5px 0;
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
_position: absolute;
|
||||
right: 10px;
|
||||
bottom: 100px;
|
||||
_bottom: "auto";
|
||||
cursor: pointer;
|
||||
opacity: .6;
|
||||
filter: Alpha(opacity=60);
|
||||
}
|
||||
|
||||
.post-content {
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.post-content h1 {
|
||||
text-indent: -8px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.post-content h2 {
|
||||
text-indent: -6px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.post-content h3 {
|
||||
text-indent: -5px;
|
||||
}
|
||||
|
||||
.post-content h4 {
|
||||
text-indent: -4px;
|
||||
}
|
||||
|
||||
.post-content h5 {
|
||||
text-indent: -3px;
|
||||
}
|
||||
|
||||
.post-content h6 {
|
||||
text-indent: -2px;
|
||||
}
|
||||
|
||||
h1 .octicon,
|
||||
h2 .octicon,
|
||||
h3 .octicon,
|
||||
h4 .octicon,
|
||||
h5 .octicon,
|
||||
h6 .octicon {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
h1:hover .octicon,
|
||||
h2:hover .octicon,
|
||||
h3:hover .octicon,
|
||||
h4:hover .octicon,
|
||||
h5:hover .octicon,
|
||||
h6:hover .octicon {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.octicon {
|
||||
fill: currentColor;
|
||||
padding: 0;
|
||||
margin-left: -16px;
|
||||
vertical-align: middle;
|
||||
}
|
@ -875,6 +875,33 @@
|
||||
.gt-container .gt-btn-login {
|
||||
margin-right: 0;
|
||||
}
|
||||
.gt-btn-login::after {
|
||||
content: "如果不想登录,请点击上方评论数跳转至对应ISSUE进行评论";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: #333;
|
||||
color: #fff;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s, visibility 0.2s;
|
||||
z-index: 10;
|
||||
}
|
||||
.gt-btn-login:hover::after {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
.gt-btn-login::after {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.gt-btn-login::after {
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
.gt-container .gt-btn-preview {
|
||||
background-color: #fff;
|
||||
color: #6190e8;
|
||||
|
19
index.html
19
index.html
@ -3,7 +3,7 @@ layout: default
|
||||
title: 首页 - 我的文章
|
||||
---
|
||||
|
||||
<h1 style="display:inline"> 首页 - 我的文章 </h1><small><a href="/archives.html">Archives</a></small><br /><br />
|
||||
<h1 style="display:inline"> 首页 - 我的文章 </h1><small><a href="/archives.html">Archives</a> | <a href="javascript:getSearchJSON(function(data){window.location = data[Math.floor(Math.random()*data.length)].url})">Random</a></small><br /><br />
|
||||
|
||||
<hr />
|
||||
|
||||
@ -19,6 +19,13 @@ title: 首页 - 我的文章
|
||||
<div class="content">
|
||||
{{ post.excerpt | strip_html | strip_newlines }}
|
||||
</div>
|
||||
{% if post.tags %}
|
||||
<span>
|
||||
{% for tag in post.tags %}
|
||||
<a href="/search.html?keyword={{ tag | url_encode }}"><code class="highligher-rouge"><nobr>#{{ tag }}</nobr></code></a>
|
||||
{% endfor %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
@ -26,15 +33,15 @@ title: 首页 - 我的文章
|
||||
<div class="pagination">
|
||||
{% if paginator.previous_page %}
|
||||
{% if paginator.previous_page == 1 %}
|
||||
<a href="{{ '/index.html' | prepend: site.baseurl | replace: '//', '/' }}">« Prev</a>
|
||||
<a href="/index.html">« Prev</a>
|
||||
{% else %}
|
||||
<a href="{{ paginator.previous_page_path | prepend: site.baseurl | replace: '//', '/' }}/">« Prev</a>
|
||||
<a href="{{ paginator.previous_page_path }}/">« Prev</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span>« Prev</span>
|
||||
{% endif %}
|
||||
|
||||
<select onchange="window.location = this.value == 1 ? '{{ '/index.html' | prepend: site.baseurl | replace: '//', '/' }}' : '{{ '/page' | prepend: site.baseurl | replace: '//', '/' }}' + this.value + '/'">
|
||||
<select onchange="window.location = this.value == 1 ? '/index.html' : '/page' + this.value + '/'">
|
||||
{% for page in (1..paginator.total_pages) %}
|
||||
{% if page == paginator.page %}
|
||||
<option value="{{ page }}" selected>{{ page }}</option>
|
||||
@ -45,7 +52,7 @@ title: 首页 - 我的文章
|
||||
</select>
|
||||
|
||||
{% if paginator.next_page %}
|
||||
<a href="{{ paginator.next_page_path | prepend: site.baseurl | replace: '//', '/' }}/">Next »</a>
|
||||
<a href="{{ paginator.next_page_path }}/">Next »</a>
|
||||
{% else %}
|
||||
<span>Next »</span>
|
||||
{% endif %}
|
||||
@ -58,7 +65,7 @@ title: 首页 - 我的文章
|
||||
|
||||
<a href="/service.html">Mayx的公开服务</a><br>
|
||||
|
||||
<a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/">拯救凯露</a><br>
|
||||
凯露&危险生存( <a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/?cn">CHS</a> | <a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/">JA</a> | <a href="{% unless site.github %}https://mabbs.github.io{% endunless %}/karyl-yabaival/?kr">KO</a> )<br>
|
||||
|
||||
<a href="/message.html">留言板</a><br>
|
||||
|
||||
|
36
js/main.js
36
js/main.js
@ -41,6 +41,42 @@ $(function () {
|
||||
}
|
||||
});
|
||||
|
||||
$(function() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const keyword = urlParams.get('kw')?.trim();
|
||||
|
||||
if (!keyword) return;
|
||||
|
||||
// 转义正则表达式特殊字符,避免安全问题
|
||||
const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
// 创建不区分大小写的正则表达式(全局匹配)
|
||||
const regex = new RegExp(`(${escapedKeyword})`, 'gi');
|
||||
|
||||
// 递归遍历并高亮文本节点
|
||||
function highlightTextNodes(element) {
|
||||
$(element).contents().each(function() {
|
||||
if (this.nodeType === Node.TEXT_NODE) {
|
||||
const $this = $(this);
|
||||
const text = $this.text();
|
||||
// 使用正则替换并保留原始大小写
|
||||
if (regex.test(text)) {
|
||||
const replaced = text.replace(regex, '<mark>$1</mark>');
|
||||
$this.replaceWith(replaced);
|
||||
}
|
||||
} else if (
|
||||
this.nodeType === Node.ELEMENT_NODE &&
|
||||
!$(this).is('script, style, noscript, textarea')
|
||||
) {
|
||||
highlightTextNodes(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('section').each(function() {
|
||||
highlightTextNodes(this);
|
||||
});
|
||||
});
|
||||
|
||||
today = new Date();
|
||||
timeold = (today.getTime() - lastUpdated.getTime());
|
||||
secondsold = Math.floor(timeold / 1000);
|
||||
|
236
js/rss-feed-preview.js
Normal file
236
js/rss-feed-preview.js
Normal file
@ -0,0 +1,236 @@
|
||||
/**
|
||||
* RSS/Atom Feed Preview for Links Table
|
||||
*/
|
||||
|
||||
(function() {
|
||||
const existingPreviews = document.querySelectorAll('#rss-feed-preview');
|
||||
existingPreviews.forEach(el => el.remove());
|
||||
|
||||
const CORS_PROXY = 'https://cors-anywhere.mayx.eu.org/?';
|
||||
|
||||
const createPreviewElement = () => {
|
||||
const existingPreview = document.getElementById('rss-feed-preview');
|
||||
if (existingPreview) {
|
||||
return existingPreview;
|
||||
}
|
||||
|
||||
const previewEl = document.createElement('div');
|
||||
previewEl.id = 'rss-feed-preview';
|
||||
previewEl.style.cssText = `
|
||||
position: fixed;
|
||||
display: none;
|
||||
width: 300px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
background-color: white;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
`;
|
||||
document.body.appendChild(previewEl);
|
||||
return previewEl;
|
||||
};
|
||||
|
||||
const parseRSS = (xmlText) => {
|
||||
const parser = new DOMParser();
|
||||
const xml = parser.parseFromString(xmlText, 'text/xml');
|
||||
|
||||
const rssItems = xml.querySelectorAll('item');
|
||||
if (rssItems.length > 0) {
|
||||
return Array.from(rssItems).slice(0, 5).map(item => {
|
||||
return {
|
||||
title: item.querySelector('title')?.textContent || 'No title',
|
||||
date: item.querySelector('pubDate')?.textContent || 'No date',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const atomItems = xml.querySelectorAll('entry');
|
||||
if (atomItems.length > 0) {
|
||||
return Array.from(atomItems).slice(0, 5).map(item => {
|
||||
return {
|
||||
title: item.querySelector('title')?.textContent || 'No title',
|
||||
date: item.querySelector('updated')?.textContent || 'No date',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const checkFeed = async (url) => {
|
||||
try {
|
||||
const response = await fetch(CORS_PROXY + url);
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
return parseRSS(text);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const findFeedUrl = async (siteUrl, linkElement) => {
|
||||
if (linkElement && linkElement.hasAttribute('data-feed')) {
|
||||
const dataFeedUrl = linkElement.getAttribute('data-feed');
|
||||
if (dataFeedUrl) {
|
||||
const feedItems = await checkFeed(dataFeedUrl);
|
||||
if (feedItems) {
|
||||
return { url: dataFeedUrl, items: feedItems };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const escapeHTML = (str) => {
|
||||
return String(str).replace(/[&<>"'/]/g, (c) => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'/': '/'
|
||||
}[c]));
|
||||
};
|
||||
|
||||
const renderFeedItems = (previewEl, items, siteName) => {
|
||||
if (!items || items.length === 0) {
|
||||
previewEl.innerHTML = '<p>No feed items found.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `<h3>Latest from ${siteName}</h3><ul style="list-style: none; padding: 0; margin: 0;">`;
|
||||
|
||||
items.forEach(item => {
|
||||
const safeTitle = escapeHTML(item.title);
|
||||
const safeDate = escapeHTML(new Date(item.date).toLocaleDateString());
|
||||
html += `
|
||||
<li style="margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid #eee;">
|
||||
<div style="color: #24292e; font-weight: bold;">
|
||||
${safeTitle}
|
||||
</div>
|
||||
<div style="color: #586069; font-size: 12px; margin: 3px 0;">
|
||||
${safeDate}
|
||||
</div>
|
||||
</li>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</ul>';
|
||||
previewEl.innerHTML = html;
|
||||
};
|
||||
|
||||
const positionPreview = (previewEl, event) => {
|
||||
const viewportWidth = window.innerWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
|
||||
let left = event.clientX + 20;
|
||||
let top = event.clientY + 20;
|
||||
|
||||
const rect = previewEl.getBoundingClientRect();
|
||||
|
||||
if (left + rect.width > viewportWidth) {
|
||||
left = event.clientX - rect.width - 20;
|
||||
}
|
||||
|
||||
if (top + rect.height > viewportHeight) {
|
||||
top = event.clientY - rect.height - 20;
|
||||
}
|
||||
|
||||
left = Math.max(10, left);
|
||||
top = Math.max(10, top);
|
||||
|
||||
previewEl.style.left = `${left}px`;
|
||||
previewEl.style.top = `${top}px`;
|
||||
};
|
||||
|
||||
const initFeedPreview = () => {
|
||||
const previewEl = createPreviewElement();
|
||||
|
||||
const tableLinks = document.querySelectorAll('main table tbody tr td a');
|
||||
|
||||
const feedCache = {};
|
||||
|
||||
let currentLink = null;
|
||||
let loadingTimeout = null;
|
||||
|
||||
tableLinks.forEach(link => {
|
||||
link.addEventListener('mouseenter', async (event) => {
|
||||
currentLink = link;
|
||||
const url = link.getAttribute('href');
|
||||
const siteName = link.textContent;
|
||||
|
||||
previewEl.innerHTML = '<p>Checking for RSS/Atom feed...</p>';
|
||||
previewEl.style.display = 'block';
|
||||
positionPreview(previewEl, event);
|
||||
|
||||
if (loadingTimeout) {
|
||||
clearTimeout(loadingTimeout);
|
||||
}
|
||||
|
||||
loadingTimeout = setTimeout(async () => {
|
||||
if (feedCache[url]) {
|
||||
renderFeedItems(previewEl, feedCache[url].items, siteName);
|
||||
positionPreview(previewEl, event); // Reposition after content is loaded
|
||||
return;
|
||||
}
|
||||
|
||||
const feedData = await findFeedUrl(url, link);
|
||||
|
||||
if (currentLink === link) {
|
||||
if (feedData) {
|
||||
feedCache[url] = feedData;
|
||||
renderFeedItems(previewEl, feedData.items, siteName);
|
||||
positionPreview(previewEl, event); // Reposition after content is loaded
|
||||
} else {
|
||||
previewEl.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
|
||||
link.addEventListener('mousemove', (event) => {
|
||||
if (previewEl.style.display === 'block') {
|
||||
window.requestAnimationFrame(() => {
|
||||
positionPreview(previewEl, event);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
link.addEventListener('mouseleave', () => {
|
||||
if (loadingTimeout) {
|
||||
clearTimeout(loadingTimeout);
|
||||
loadingTimeout = null;
|
||||
}
|
||||
|
||||
currentLink = null;
|
||||
previewEl.style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
if (!previewEl.contains(event.target)) {
|
||||
previewEl.style.display = 'none';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (!window.rssFeedPreviewInitialized) {
|
||||
window.rssFeedPreviewInitialized = true;
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initFeedPreview);
|
||||
} else {
|
||||
initFeedPreview();
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
6
js/simple-jekyll-search.min.js
vendored
Normal file
6
js/simple-jekyll-search.min.js
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/*!
|
||||
* Simple-Jekyll-Search
|
||||
* Copyright 2015-2020, Christian Fei
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
!function(){"use strict";var f={compile:function(r){return i.template.replace(i.pattern,function(t,e){var n=i.middleware(e,r[e],i.template);return void 0!==n?n:r[e]||t})},setOptions:function(t){i.pattern=t.pattern||i.pattern,i.template=t.template||i.template,"function"==typeof t.middleware&&(i.middleware=t.middleware)}};const i={pattern:/\{(.*?)\}/g,template:"",middleware:function(){}};var n=function(t,e){var n=e.length,r=t.length;if(n<r)return!1;if(r===n)return t===e;t:for(var i=0,o=0;i<r;i++){for(var u=t.charCodeAt(i);o<n;)if(e.charCodeAt(o++)===u)continue t;return!1}return!0},e=new function(){this.matches=function(t,e){return n(e.toLowerCase(),t.toLowerCase())}},r=new function(){this.matches=function(e,t){return!!e&&(e=e.trim().toLowerCase(),(t=t.trim().toLowerCase()).split(" ").filter(function(t){return 0<=e.indexOf(t)}).length===t.split(" ").length)}},d={put:function(t){if(l(t))return a(t);if(function(t){return Boolean(t)&&"[object Array]"===Object.prototype.toString.call(t)}(t))return function(n){const r=[];s();for(let t=0,e=n.length;t<e;t++)l(n[t])&&r.push(a(n[t]));return r}(t);return undefined},clear:s,search:function(t){return t?function(e,n,r,i){const o=[];for(let t=0;t<e.length&&o.length<i.limit;t++){var u=function(t,e,n,r){for(const i in t)if(!function(n,r){for(let t=0,e=r.length;t<e;t++){var i=r[t];if(new RegExp(i).test(n))return!0}return!1}(t[i],r.exclude)&&n.matches(t[i],e))return t}(e[t],n,r,i);u&&o.push(u)}return o}(u,t,c.searchStrategy,c).sort(c.sort):[]},setOptions:function(t){c=t||{},c.fuzzy=t.fuzzy||!1,c.limit=t.limit||10,c.searchStrategy=t.fuzzy?e:r,c.sort=t.sort||o,c.exclude=t.exclude||[]}};function o(){return 0}const u=[];let c={};function s(){return u.length=0,u}function l(t){return Boolean(t)&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return u.push(t),u}c.fuzzy=!1,c.limit=10,c.searchStrategy=c.fuzzy?e:r,c.sort=o,c.exclude=[];var p={load:function(t,e){const n=window.XMLHttpRequest?new window.XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP");n.open("GET",t,!0),n.onreadystatechange=h(n,e),n.send()}};function h(e,n){return function(){if(4===e.readyState&&200===e.status)try{n(null,JSON.parse(e.responseText))}catch(t){n(t,null)}}}var m=function y(t){if(!(e=t)||!("undefined"!=typeof e.required&&e.required instanceof Array))throw new Error("-- OptionsValidator: required options missing");var e;if(!(this instanceof y))return new y(t);const r=t.required;this.getRequiredOptions=function(){return r},this.validate=function(e){const n=[];return r.forEach(function(t){"undefined"==typeof e[t]&&n.push(t)}),n}},w={merge:function(t,e){const n={};for(const r in t)n[r]=t[r],"undefined"!=typeof e[r]&&(n[r]=e[r]);return n},isJSON:function(t){try{return t instanceof Object&&JSON.parse(JSON.stringify(t))?!0:!1}catch(e){return!1}}};!function(t){let i={searchInput:null,resultsContainer:null,json:[],success:Function.prototype,searchResultTemplate:'<li><a href="{url}" title="{desc}">{title}</a></li>',templateMiddleware:Function.prototype,sortMiddleware:function(){return 0},noResultsText:"No results found",limit:10,fuzzy:!1,debounceTime:null,exclude:[]},n;const e=function(t,e){e?(clearTimeout(n),n=setTimeout(t,e)):t.call()};var r=["searchInput","resultsContainer","json"];const o=m({required:r});function u(t){d.put(t),i.searchInput.addEventListener("input",function(t){-1===[13,16,20,37,38,39,40,91].indexOf(t.which)&&(c(),e(function(){l(t.target.value)},i.debounceTime))})}function c(){i.resultsContainer.innerHTML=""}function s(t){i.resultsContainer.innerHTML+=t}function l(t){var e;(e=t)&&0<e.length&&(c(),function(e,n){var r=e.length;if(0===r)return s(i.noResultsText);for(let t=0;t<r;t++)e[t].query=n,s(f.compile(e[t]))}(d.search(t),t))}function a(t){throw new Error("SimpleJekyllSearch --- "+t)}t.SimpleJekyllSearch=function(t){var n;0<o.validate(t).length&&a("You must specify the following required options: "+r),i=w.merge(i,t),f.setOptions({template:i.searchResultTemplate,middleware:i.templateMiddleware}),d.setOptions({fuzzy:i.fuzzy,limit:i.limit,sort:i.sortMiddleware,exclude:i.exclude}),w.isJSON(i.json)?u(i.json):(n=i.json,p.load(n,function(t,e){t&&a("failed to get JSON ("+n+")"),u(e)}));t={search:l};return"function"==typeof i.success&&i.success.call(t),t}}(window)}();
|
20
links.md
20
links.md
@ -6,22 +6,13 @@ id: links
|
||||
tags: [links]
|
||||
---
|
||||
|
||||
| Links | Introduce |
|
||||
| Link | Description |
|
||||
| - | - |
|
||||
| [花火学园](https://www.sayhanabi.net/) | 和谐融洽的ACG交流以及资源聚集地 |
|
||||
| [资源统筹局](https://gkdworld.com/) | 统筹保管用户分享的资源 |
|
||||
| [贫困的蚊子](https://mozz.ie/) | *No description* |
|
||||
| [极客兔兔](https://geektutu.com/) | 致力于分享有趣的技术实践 |
|
||||
| [维基萌](https://www.wikimoe.com/) | 萌即是正义!一名热爱acg的前端设计师的小站! |
|
||||
| [7gugu's blog](https://www.7gugu.com/) | 一个用来存放我爱好的地方,编程,摄影之类的空间 |
|
||||
| [云游君](https://www.yunyoujun.cn/) | 希望能成为一个有趣的人。 |
|
||||
| [Kingfish404](https://blog.kingfish404.cn/) | Stay curious,stay naive. WUT. Jin Yu's Blog |
|
||||
| [FKUN](https://blog.fkun.tech/) | *No description* |
|
||||
| [Sinofine](https://sinofine.me/) | *No description* |
|
||||
|
||||
{% for item in site.data.links %}| <a href="{{ item.link }}" target="_blank" rel="noopener" data-feed="{{ item.feed_url }}">{{ item.title }}</a> | {{ item.description }} |
|
||||
{% endfor %}
|
||||
|
||||
## Links申请
|
||||
请在下面留言或者直接发起[Pull request](https://github.com/Mabbs/mabbs.github.io/pull/new/master)
|
||||
请在下面留言或者直接[修改Links](https://github.com/Mabbs/mabbs.github.io/edit/master/_data/links.csv)并发起PR
|
||||
请在申请之前加上本站友链
|
||||
要求:
|
||||
1. 全站HTTPS
|
||||
@ -32,5 +23,8 @@ tags: [links]
|
||||
名称:Mayx的博客
|
||||
简介:Mayx's Home Page
|
||||
链接:<https://mabbs.github.io>
|
||||
订阅:<https://mabbs.github.io/atom.xml>
|
||||
头像:<https://avatars0.githubusercontent.com/u/17966333>
|
||||
Logo:<https://mabbs.github.io/favicon.ico>
|
||||
|
||||
<script src="/js/rss-feed-preview.js"></script>
|
108
proxylist.md
108
proxylist.md
@ -10,28 +10,96 @@ title: 代理列表
|
||||
# 代理列表
|
||||
考虑到中国对于Github Pages在很多地区都有一定程度的解析异常,所以我为我的博客做了很多反向代理。以下代理站均为官方授权:
|
||||
(根据可能的可用性排序)
|
||||
- <https://blog.mayx.workers.dev/> <img src="https://blog.mayx.workers.dev/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||
- <https://mayx.deno.dev/> <img src="https://mayx.deno.dev/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||
- <https://mayx.glitch.me/> <img src="https://mayx.glitch.me/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||
- <https://yuki.gear.host/> <img src="https://yuki.gear.host/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||
- <https://mayx.serv00.net/> <img src="https://mayx.serv00.net/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||
{% for item in site.data.proxylist.proxies %}- <{{ item }}> <img src="{{ item }}images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||
{% endfor %}
|
||||
|
||||
# 镜像列表
|
||||
由于[Github已经不再可信](/2022/01/04/banned.html),所以现在提供以下镜像站:
|
||||
- <https://mayx.gitlab.io/> <img src="https://mayx.gitlab.io/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||
- <https://mayx.pages.dev/> <img src="https://mayx.pages.dev/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||
- <https://mayx.eu.org/> <img src="https://mayx.eu.org/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||
- <https://mayx.vercel.app/> <img src="https://mayx.vercel.app/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||
- <https://mayx.netlify.app/> <img src="https://mayx.netlify.app/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||
- <https://mayx.4everland.app/> <img src="https://mayx.4everland.app/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||
- <https://mayx.dappling.network/> <img src="https://mayx.dappling.network/images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||
{% for item in site.data.proxylist.mirrors %}- <{{ item }}> <img src="{{ item }}images/online.svg" style="width:22px;vertical-align: bottom" onerror="this.src = '/images/offline.svg'"/>
|
||||
{% endfor %}
|
||||
|
||||
# 服务架构
|
||||
```mermaid
|
||||
graph LR;
|
||||
Users@{ shape: stadium, label: "Users" }
|
||||
GH@{ shape: bow-rect, label: "GitHub" }
|
||||
GL@{ shape: bow-rect, label: "GitLab" }
|
||||
GE@{ shape: bow-rect, label: "Gitee" }
|
||||
CFP@{ shape: docs, label: "CloudFlare Pages" }
|
||||
GHP@{ shape: docs, label: "GitHub Pages" }
|
||||
GLP@{ shape: docs, label: "GitLab Pages" }
|
||||
FELH@{ shape: docs, label: "4EVERLAND Hosting" }
|
||||
IPFS@{ shape: lin-cyl, label: "IPFS" }
|
||||
GF@{ shape: lin-cyl, label: "Greenfield" }
|
||||
Vercel@{ shape: docs, label: "Vercel" }
|
||||
Netlify@{ shape: docs, label: "Netlify" }
|
||||
SH@{ shape: docs, label: "statichost.eu" }
|
||||
DA@{ shape: docs, label: "dAppling" }
|
||||
CFW@{ label: "CloudFlare Workers" }
|
||||
CFAI@{ shape: procs, label: "CloudFlare AI" }
|
||||
CFD@{ shape: lin-cyl, label: "CloudFlare D1" }
|
||||
Deno@{ shape: curv-trap, label: "Deno" }
|
||||
Glitch@{ shape: curv-trap, label: "Glitch" }
|
||||
Other@{ shape: curv-trap, label: "Other..." }
|
||||
subgraph Repo
|
||||
GH
|
||||
GL
|
||||
GE
|
||||
end
|
||||
|
||||
subgraph Pages
|
||||
GHP
|
||||
GLP
|
||||
CFP
|
||||
SH
|
||||
FELH
|
||||
DA
|
||||
Vercel
|
||||
Netlify
|
||||
end
|
||||
|
||||
subgraph API[API Service]
|
||||
CFAI
|
||||
CFD
|
||||
CFW
|
||||
end
|
||||
|
||||
subgraph Proxies
|
||||
Deno
|
||||
Glitch
|
||||
Other
|
||||
end
|
||||
|
||||
subgraph DS[Decentralized storage]
|
||||
IPFS
|
||||
GF
|
||||
end
|
||||
|
||||
GH <-- Sync --> GL
|
||||
GH -- Sync --> GE
|
||||
GH -- Deploy --> GHP & SH & Netlify & FELH & DA
|
||||
GL -- Deploy --> CFP & Vercel & GLP
|
||||
CFW -- Reverse Proxy --> GHP
|
||||
Deno -- Reverse Proxy --> GHP
|
||||
Glitch -- Reverse Proxy --> GHP
|
||||
Other -- Reverse Proxy --> GHP
|
||||
CFD <--> CFW
|
||||
CFAI <--> CFW
|
||||
API -- API/Proxy Service <--> Users
|
||||
Pages -- Serviced --> Users
|
||||
Proxies -- Serviced --> Users
|
||||
FELH --> IPFS & GF
|
||||
DA --> IPFS
|
||||
```
|
||||
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||
mermaid.initialize({ startOnLoad: false });
|
||||
await mermaid.run({
|
||||
querySelector: '.language-mermaid',
|
||||
});
|
||||
</script>
|
||||
|
||||
# 其他平台博客(备用)
|
||||
- <https://unmayx.blogspot.com/>
|
||||
- <https://unmayx.blog.fc2blog.us/>
|
||||
- <https://unmayx.wordpress.com/>
|
||||
- <https://mayx.code.blog/>
|
||||
- <https://mayx.home.blog/>
|
||||
- <https://unmayx.medium.com/>
|
||||
- <https://mayx.cnblogs.com/>
|
||||
- <https://mayx.xlog.app/>
|
||||
{% for item in site.data.proxylist.others %}- <{{ item }}>
|
||||
{% endfor %}
|
||||
|
25
search.html
25
search.html
@ -4,27 +4,14 @@ title: 搜索
|
||||
---
|
||||
|
||||
<h1>搜索</h1>
|
||||
<!-- HTML elements for search -->
|
||||
<p>Keyword: <input type="text" id="search-input" placeholder="Search blog posts.."> <img src="/images/loading.svg" id="search-loading" style="width:22px;vertical-align: bottom"></p>
|
||||
<ul id="results-container"></ul>
|
||||
|
||||
<!-- or without installing anything -->
|
||||
<script src="https://unpkg.com/simple-jekyll-search@latest/dest/simple-jekyll-search.min.js"></script>
|
||||
<script src="/js/simple-jekyll-search.min.js"></script>
|
||||
<script>
|
||||
function getQueryVariable(variable)
|
||||
{
|
||||
var query = window.location.search.substring(1);
|
||||
var vars = query.split("&");
|
||||
for (var i=0;i<vars.length;i++) {
|
||||
var pair = vars[i].split("=");
|
||||
if(pair[0] == variable){return pair[1];}
|
||||
}
|
||||
return(false);
|
||||
}
|
||||
var mykeyword = decodeURI(getQueryVariable("keyword"));
|
||||
var sbox = document.getElementById('search-input');
|
||||
var status = false;
|
||||
if(mykeyword != null && mykeyword.toString().length>1){
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const mykeyword = urlParams.get('keyword')?.trim();
|
||||
const sbox = document.getElementById('search-input');
|
||||
if (mykeyword) {
|
||||
sbox.value = mykeyword;
|
||||
}
|
||||
getSearchJSON(function(json){
|
||||
@ -32,7 +19,7 @@ getSearchJSON(function(json){
|
||||
searchInput: sbox,
|
||||
resultsContainer: document.getElementById('results-container'),
|
||||
json: json,
|
||||
searchResultTemplate: '<p><li>{date} - <a href="{url}">{title}</a></li></p>',
|
||||
searchResultTemplate: '<p><li>{date} - <a href="{url}?kw={query}">{title}</a></li></p>',
|
||||
limit: 20
|
||||
});
|
||||
sjs.search(mykeyword);
|
||||
|
@ -11,7 +11,7 @@ title: Mayx的公开服务
|
||||
| 博客用AI摘要等接口 | <https://summary.mayx.eu.org> | 参考:[使用Cloudflare Workers制作博客AI摘要](/2024/07/03/ai-summary.html) |
|
||||
| 无限制一言接口 | <https://hitokoto.mayx.eu.org> | 参考:[cf-hitokoto](https://github.com/Mabbs/cf-hitokoto) |
|
||||
| Mayx DoH | <https://dns.mayx.eu.org> | 上游是 <https://dns.google> |
|
||||
| Docker镜像源 | <https://docker.mayx.eu.org> | *待补充* |
|
||||
| Docker镜像源 | <https://docker.mayx.eu.org> | 参考[CF-Workers-docker.io](https://github.com/cmliu/CF-Workers-docker.io) |
|
||||
| GitHub镜像源 | <https://github.mayx.eu.org> | 参考[gh-proxy](https://github.com/hunshcn/gh-proxy) |
|
||||
| Pixiv图片代理 | <https://pixiv.mayx.eu.org> | 参考[Pixiv圖片代理](https://pixiv.cat/reverseproxy.html) |
|
||||
| jsproxy | <https://jsproxy.mayx.eu.org> | 参考[jsproxy](https://github.com/EtherDream/jsproxy) |
|
||||
|
Reference in New Issue
Block a user