557 |
558 | ```
559 |
560 | 我们要裁剪的是 a 标签 href 指定的那张图片,使用下面这个 Plan 实现:
561 |
562 | ```json
563 | {
564 | ...
565 | "actions": [{
566 |
567 | "chAttr": {
568 | "pick": "img",
569 | "attr": "src",
570 | "type": "assign.from.ancestor-attr",
571 | "tElem": ["a"],
572 | "tAttr": "href"
573 | }
574 | }]
575 | }
576 | ```
577 |
578 | * pick 的类型为选择器,用来选中要操作的节点,我们选中了所有 img 标签。
579 | * attr 的值为要操作的属性名字,此例中,我们选择的是图片的 src 属性。
580 | * type 为 **assign.from.ancestor-attr** ,它表明我们要用找到节点的 **祖先节点** 的一个属性的值,来重写 attr 指定的属性。
581 | * tElem 的值为目标元素(target element)的选择器,此例子中,我们选中了 a 标签。
582 | * tAttr 的值为目标属性(target attribute)的名字, 此例中,我们用祖先节点的 href 属性重写图片的 src 属性。
583 |
584 |
585 | **注意** tElem 的值比较特殊,如下:
586 |
587 | * 必须是一个选择器元祖(数组),而且选择器的个数为 1 ~ 2 个。
588 | * 选择器只能是 CSS 选择器。
589 | * 第一个选择器用于匹配祖先节点
590 | * 第二个选择器用于选择祖先节点的内部节点,只有当第一个选择器无法确定目标节点时,才需要提供。(极少使用到)
591 |
592 |
593 | 还有一种 和 **assign.from.ancestor-attr** 相似的类型为: **assign.from.ancestor.child-attr** , 此种类型也需要提供 tElem ,且需要提供两个选择器。拿到的目标节点为祖先节点的后代节点。
594 |
595 |
596 |
597 | ------------------------------
598 |
599 |
600 | **例4**: 除了上面这几种动作, chAttr 还对 class 属性的修改做了支持。请看下方 Plan:
601 |
602 | ```json
603 | {
604 | ...
605 | "actions": [{
606 |
607 | "chAttr": {
608 | "pick": ".section",
609 | "attr": "class",
610 | "type": "split2list.remove",
611 | "value": "folded",
612 | "sep": " "
613 | }
614 | }]
615 | }
616 | ```
617 |
618 | * pick 的类型为选择器,用来选中要操作的节点,我们选中了所有包含类名为 section 的标签。
619 | * attr 的值为要操作的属性名字,此例中,我们选择的是 class 属性。
620 | * type 为 **split2list.remove** ,它表明我们要用操作的属性具有的值比较特殊,可以通过某个分隔符分成多个部分,该类型表明要移除其中一部分。
621 | * value 为要移除的那部分,可移除多个值(如:`"value": ["a", "b", "c"]`)
622 | * sep 为分隔符
623 |
624 | 还有一种修改动作, 跟该例子类似,它的类型为 **split2list.add**,该类型表明要往属性里面添加一项。
625 |
626 | 一般可以使用这两种动作,对网页折叠部分进行控制,使其达到我们想要的状态。这种方式不像上文的 show 参数那样粗暴地对 display 进行操控。
627 |
628 |
629 | ------------------------------
630 |
631 |
632 | **例5**: 这种可能不常见,即直接修改属性的值
633 |
634 |
635 | ```json
636 | {
637 | ...
638 | "actions": [{
639 |
640 | "chAttr": {
641 | "pick": ".formula",
642 | "attr": "type",
643 | "type": "assign.from.value",
644 | "value": "image/svg",
645 | }
646 | }]
647 | }
648 | ```
649 |
650 | 上面的例子会把 “type” 属性的值设置为 “image/svg”。
651 |
652 | ### formula 动作的使用 {#action-formula}
653 |
654 | 该动作用于创建一个叫做 `
` 或 `` 的标签来标记公式,并把原标签标记为忽略。
655 |
656 | 例如:
657 |
658 | 某个网页渲染数学公式的时候,为了兼容所有浏览器,使用了图片的形式,同时又保存其 Latex 公式,如下:
659 |
660 | ```html
661 |
662 | ```
663 |
664 | 我们在裁剪成 Markdown 格式时,完全没有必要保存这个图片,只需要保存这个 `data-formula` 属性的公式即可。
665 |
666 | 使用下方 Plan:
667 |
668 | ```json
669 | {
670 | ...
671 | "actions": [
672 | {
673 | "formula": {"pick": "img", "attr": "data-formula"},
674 | "tag": "md-only"
675 | }
676 | ]
677 | }
678 | ```
679 |
680 | * pick 的值为选择器,此例选中了所有 `
` 元素
681 | * attr 包含数学公式的属性名。
682 |
683 | 若该 `
` 标签是『块状公式』(独占一行), 应用该 Plan 后的 HTML 如下:
684 |
685 | ```HTML
686 |
687 |
688 | ```
689 |
690 | 若该 `
` 标签是『行内公式』(和其他文字在同一行), 应用该 Plan 后的 HTML 如下:
691 |
692 | ```HTML
693 |
694 |
695 | ```
696 |
697 | 可以看到原来的 `
` 标签被标记了 `data-mx-ignore` 属性,标记后 MaoXian 会忽略该标签。
698 |
699 | 在没有 `block` 参数的时候,formula 动作会自动根据选中元素的样式,决定是『块状公式』还是『行内公式』。
700 | 你也可以通过 `block` 参数直接指定。
701 |
702 | 比如下方 plan 直接指定了『块状公式』
703 |
704 | ```json
705 | {
706 | ...
707 | "actions": [
708 | {
709 | "formula": {"pick": "img", "attr": "data-formula", block: true},
710 | "tag": "md-only"
711 | }
712 | ]
713 | }
714 | ```
715 |
716 |
717 |
718 | ### pick 或 select 动作的使用 {#action-pick}
719 |
720 | 该动作选中要裁剪的节点。找到第一个匹配的节点就停止查找,如果你填写了多个选择器,则会按照选择器的顺序依次查找,也是找到第一个即停止。
721 |
722 | **注:该动作在『全局 Plan 』中无效**
723 |
724 | ### confirm 动作的使用 {#action-confirm}
725 |
726 | 该动作选中要裁剪的节点,并进行确认,执行完,会到填写表单那一步。找到第一个匹配的节点就停止查找,如果你填写了多个选择器,则会按照选择器的顺序依次查找,也是找到第一个即停止。
727 |
728 | **注:该动作在『全局 Plan 』中无效**
729 |
730 | ### clip 动作的使用 {#action-clip}
731 |
732 | 该动作选中要裁剪的节点,并立即自动进行裁剪。找到第一个匹配的节点就停止查找,如果你填写了多个选择器,则会按照选择器的顺序依次查找,也是找到第一个即停止。
733 |
734 | **注:该动作在『全局 Plan 』中无效**
735 |
736 |
737 | ### form 动作的使用 {#action-form}
738 |
739 | 用于预设置表单的输入值,MaoXian 会在显示表单的时候,自动帮你输入这些预设的值,都为选填。
740 |
741 | **注:该动作在『全局 Plan 』中无效**
742 |
743 | 详情如下:
744 |
745 | | 名字 | 类型 | 说明 | 默认值 |
746 | | -------- | -------- | -------- | -------- |
747 | | title | 选择器 | 用于选中包含标题的元素 | 无 |
748 | | category | 字符串 | 对应表单的目录(多级目录用 `/` 隔开) | 取决于你的设置(见扩展设置页面) |
749 | | tagstr | 字符串 | 对应表单的标签(多个标签用 `,` 或 `空格` 隔开) | 无 |
750 |
751 | **主要应用场景:**
752 |
753 | * 通过设置 title 来解决网页标题和内容标题不一致的问题(MaoXian 默认会使用网页的标题作为表单的标题输入值)。
754 | * 通过设置 category 来为不同的网站的网页设置不同的默认分类。
755 | * 通过设置 tagstr 来为不同的网站的网页,打上不同的默认标签。
756 |
757 | 例子:
758 |
759 | 请注意 `title` 的值是一个选择器,此例选中 `main h1` 对应的标题来作为『保存表单』的标题。
760 |
761 | ```json
762 | {
763 | ...
764 | "actions": [{
765 |
766 | "form": {
767 | "title": "main h1",
768 | "category": "news/read-later",
769 | "tagstr": "international,freedom-news"
770 | }
771 | }]
772 | }
773 | ```
774 |
775 | ### config 动作的使用 {#action-config}
776 |
777 | 重写某些配置项,并且重写的这个动作只在这个 Plan 的作用范围内有效。
778 |
779 | **注:该动作在『全局 Plan 』中无效**
780 |
781 | 当前允许重写的配置项如下,所有可配置项均为选填。注: 其中的默认值指的是 MaoXian 扩展提供的默认值,该值只作为参考,实际上使用的是你自己配置页面上的值。
782 |
783 | #### 存储相关 {#config-storage}
784 |
785 | 可在扩展的 `扩展 > 设置页面 > 存储设置` 一节找到可使用的变量及其说明。
786 |
787 |
788 | | 名字 | 说明 | 类型 | 默认值 |
789 | | -------- | -------- | -------- | -------- |
790 | | clippingHandler | 处理程序 | String | Browser |
791 | | saveFormat | 保存格式 | String | html |
792 | | rootFolder | 根目录 | String | mx-wc |
793 | | defaultCategory | 默认分类 | String | default |
794 | | clippingFolderName | 裁剪目录 | String | $YYYY-$MM-$DD-$TIME-INTSEC |
795 | | mainFileFolder | 主文件的存储目录 | String | $CLIPPING-PATH |
796 | | mainFileName | 主文件的文件名 | String | index.$FORMAT |
797 | | saveInfoFile | 是否保存元信息文件 | Boolean | true |
798 | | infoFileFolder | 元信息文件的存储目录 | String | $CLIPPING-PATH |
799 | | infoFileName | 元信息文件的文件名 | String | index.json |
800 | | saveTitleFile | 是否保存标题文件 | Boolean | true |
801 | | titleFileFolder | 标题文件的存储目录 | String | $CLIPPINT-PATH |
802 | | titleFileName | 标题文件的文件名 | String | a-title_$TITLE |
803 | | frameFileFolder | 内嵌的网页文件的存储目录 | String | $CLIPPING-PATH/frames |
804 | | frameFileName | 内嵌的网页文件的文件名 | String | $TIME-INTSEC-$MD5URL.frame.html |
805 | | assetFolder | 资源文件的存储目录 | String | $CLIPPING-PATH/assets |
806 | | assetFileName | 资源文件的文件名 | String | $TIME-INTSEC-$MD5URL$EXT |
807 |
808 |
809 | **备注**:
810 |
811 | * clippingHandler (处理程序)的可选值为 `Browser`(浏览器下载功能) 和 `NativeApp` (本地程序)
812 | * saveFormat (保存格式)的可选值为 `html` 或 `md`
813 | * saveInfoFile(是否保存元信息文件) 设置为 `false` 后,MaoXian 便不会保存裁剪历史。
814 |
815 |
816 |
817 | #### Markdown 文档相关 {#config-markdown}
818 |
819 | 请参考 `扩展 > 设置 > Markdown` 的说明文字进行配置。
820 |
821 |
822 | | 名字 | 说明 | 类型 | 默认值 | 可选值 |
823 | | -------- | -------- | -------- | -------- | -------- |
824 | | markdownTemplate | Markdown 模板 | String | `\n{{content}}\n` | |
825 | | markdownOptionHeadingStyle | 标题格式 | String | `atx` | `setext` 或 `atx` |
826 | | markdownOptionHr | 水平分割线 | String | `* * *` | `* * *` 或 `- - -` 等等 |
827 | | markdownOptionBulletListMarker | 子弹列表的行头符 | String | `*` | `*`,`+` 或 `-` |
828 | | markdownOptionCodeBlockStyle | 代码块格式 | String | `fenced` | `indented` 或 `fenced` |
829 | | markdownOptionFence | 代码块分隔符 | String | ```
| ```
或 `~~~` |
830 | | markdownOptionEmDelimiter | 强调(斜体)分隔符 | String | `_` | `_` 或 `*` |
831 | | markdownOptionStrongDelimiter | 加重(粗体)分隔符 | String | `**` | `**` 或 `__` |
832 | | markdownOptionLinkStyle | 链接格式 | String | `inlined` | `inlined` 或 `referenced` |
833 | | markdownOptionFormulaBlockWrapper | 块状公式的格式 | String | `padSameLine` | `sameLine`, `padSameLIne`, `multipleLine` 或 `mathCodeBlock` |
834 |
835 |
836 | **备注**:
837 |
838 | * markdownOptionHr (水平分割线)可填写的值很灵活,具体查看这里 [Thematic break](https://spec.commonmark.org/0.27/#thematic-breaks)
839 |
840 |
841 |
842 | #### HTML 文档相关 {#config-html}
843 |
844 | 请参考 `扩展 > 设置 > HTML` 的说明文字进行配置。
845 |
846 |
847 |
848 | | 名字 | 说明 | 类型 | 默认值 | 可选值 |
849 | | -------- | -------- | -------- | -------- | -------- |
850 | | htmlSaveClippingInformation | 追加裁剪信息到内容尾部 | Boolean | `false` | `false`、 `true` |
851 | | htmlCustomBodyBgCssEnabled | 允许自定义 body 标签的 CSS 背景颜色 | Boolean | `false` | `false`、 `true` |
852 | | htmlCustomBodyBgCssValue | body 标签的 CSS 背景颜色 | String | `#000000` | 查看 background-color (CSS) |
853 | | htmlCompressCss | 压缩样式(CSS) | Boolean | `false` | `false`、`true` |
854 | | htmlCaptureImage | 图片 | String | `saveAll` | `saveAll`、`saveCurrent` |
855 | | htmlCaptureAudio | 声音 | String | `remove` | `saveAll` 、`saveCurrent`、 `remove` |
856 | | htmlCaptureVideo | 影片 | String | `remove` | `saveAll`、 `saveCurrent`、 `remove` |
857 | | htmlCaptureApplet | Applets | String | `remove` | `saveAll`、 `remove` |
858 | | htmlCaptureEmbed | Embeds | String | `saveImage` | `saveAll`、 `saveImage`、 `remove`、 `filter` |
859 | | htmlCaptureObject | Objects | String | `saveImage` | `saveAll`、 `saveImage`、 `remove`、 `filter` |
860 | | htmlCaptureIcon | 网站图标 | String | `remove` | `saveAll`、 `saveFavicon`、 `remove` |
861 | | htmlCaptureCssRules | 样式规则 | String | `saveUserd` | `saveAll`、 `saveUserd` |
862 | | htmlCaptureWebFont | Web 字体 | String | `remove` | `saveAll`、 `remove`、 `filterList` |
863 | | htmlCaptureCssImage | 样式图片 | String | `remove` | `saveAll`、 `remove` |
864 | | htmlEmbedFilter | Embeds 过滤器 | String | `` | - |
865 | | htmlObjectFilter | Objects 过滤器 | String | `` | - |
866 | | htmlWebFontFilterList | Web 字体过滤器组 | String | `woff2|woff|otf|ttf` | - |
867 |
868 |
869 | #### Web 请求相关 {#config-web-request}
870 |
871 | 请参考 `扩展 > 设置 > 高级设置` 的说明文字进行配置。
872 |
873 |
874 | | 名字 | 说明 | 类型 | 默认值 | 可选值 |
875 | | -------- | -------- | -------- | -------- | -------- |
876 | | requestTimeout | 超时(秒) | Integer | 300 | 5 ~ 86400 |
877 | | requestMaxTries | 最大尝试次数 | Integer | 3 | 大于1的整数 |
878 | | requestReferrerPolicy | Referrer 请求头 | String | `originWhenCrossOrigin` | `noReferrer`、 `origin`、 `originWhenCrossOrigin`、 `unsafeUrl` |
879 |
880 |
881 |
882 | ## tags 参数的使用 {#arg-tags}
883 |
884 | 虽然 Plan 的 tags 属性是非必须的,但还是建议你为 Plan 打上标签,以便后期,我们能更好地管理他们。
885 |
--------------------------------------------------------------------------------
/lib/json-object.rb:
--------------------------------------------------------------------------------
1 | module JsonObject
2 |
3 | def self.included(klass)
4 | klass.extend ClassMethods
5 | klass.include ConditionFns
6 | end
7 |
8 | module ClassMethods
9 | DEFAULT_OPTIONS = {to_hash: true, collection: false}
10 |
11 | def required_attr(name, options = {})
12 | validate_defined_attr(name, options)
13 | @attr_defines ||= {}
14 | @attr_defines[name.to_s] = DEFAULT_OPTIONS.merge(options).merge({required: true})
15 | self.attr_reader name
16 | end
17 |
18 | def optional_attr(name, options = {})
19 | validate_defined_attr(name, options)
20 | @attr_defines ||= {}
21 | @attr_defines[name.to_s] = DEFAULT_OPTIONS.merge(options).merge({required: false})
22 | self.attr_reader name
23 | end
24 |
25 | def validate_defined_attr(name, options = {})
26 | if options[:collection] && !options.has_key?(:klass)
27 | raise "attribute: '#{name}' contains 'collection' option that requires 'klass' option"
28 | end
29 | if options[:optional_collection] && !options.has_key?(:klass)
30 | raise "attribute: '#{name}' contains 'optional_collection' option that requires 'klass' option"
31 | end
32 | end
33 | end
34 |
35 | module ConditionFns
36 | def value_in(name, *values)
37 | values.any? { |value| value == self.send(name.to_sym) }
38 | end
39 | end
40 |
41 |
42 | class Collection
43 | include Enumerable
44 | attr_reader :klass, :members
45 |
46 | def initialize(klass, members)
47 | @members = (members || []).map {|it| klass.new(it) }
48 | end
49 |
50 | def each(&blk)
51 | members.each(&blk)
52 | end
53 |
54 | def valid?
55 | @errors = []
56 | each do |it|
57 | unless it.valid?
58 | @errors << it.errors
59 | end
60 | end
61 | @errors.size == 0
62 | end
63 |
64 | def errors
65 | @errors
66 | end
67 |
68 | def to_hash
69 | members.map(&:to_hash)
70 | end
71 | end
72 |
73 | def initialize(params)
74 | attr_defines.each_pair do |attr, attr_define|
75 |
76 | if params.has_key?(attr)
77 | value = params[attr]
78 |
79 | if attr_define[:collection]
80 | instance_variable_set vname(attr), Collection.new(attr_define[:klass], value)
81 | elsif attr_define[:optional_collection]
82 | if value.is_a? Array
83 | instance_variable_set vname(attr), Collection.new(attr_define[:klass], value)
84 | else
85 | instance_variable_set vname(attr), attr_define[:klass].new(value)
86 | end
87 | elsif attr_define[:klass]
88 | instance_variable_set vname(attr), attr_define[:klass].new(value)
89 | elsif attr_define[:module]
90 | instance_variable_set vname(attr), attr_define[:module].new_instance(value)
91 | else
92 | instance_variable_set vname(attr), value
93 | end
94 | elsif attr_define[:default]
95 | instance_variable_set vname(attr), attr_define[:default]
96 | end
97 | end
98 | check_missing_defines(params)
99 | end
100 |
101 | def check_missing_defines(params)
102 | params.each_pair do |attr, value|
103 | unless attr_defines.has_key?(attr)
104 | puts "[WARN] expect #{self.class.name} to define attribute #{attr}"
105 | end
106 | end
107 | end
108 |
109 | def valid?
110 | @errors = []
111 | attr_defines.each_pair do |attr, attr_define|
112 | is_valid, error = validate_attr_define(attr, attr_define)
113 | @errors << error unless is_valid
114 | end
115 | return @errors.size == 0
116 | end
117 |
118 | def errors
119 | @errors
120 | end
121 |
122 | def validate_attr_define(attr, attr_define)
123 | validate_proc = Proc.new { |attr_define|
124 | attr_value = instance_variable_get(vname(attr))
125 | if attr_define[:required] && attr_value.nil?
126 | return [false, "Required attribute: \"#{attr}\" is not provided"]
127 | end
128 |
129 | if attr_define[:in] && !attr_define[:in].include?(attr_value)
130 | return [false, "The value of #{attr} must be a member of\n [#{attr_define[:in].map(&:to_s).join(', ')}], \nbut it's value is #{attr_value}"]
131 | end
132 |
133 | if (attr_define[:klass] || attr_define[:module]) && attr_value && !attr_value.valid?
134 | intent = " " * 4
135 | error = "#{attr}:\n" + attr_value.errors.map do |it|
136 | "#{intent}#{it}"
137 | end.join("\n")
138 | return [false, error]
139 | end
140 |
141 | return [true]
142 | }
143 |
144 | condition = attr_define[:required_if]
145 | if condition.is_a?(Proc)
146 | if self.instance_exec(&condition)
147 | return validate_proc.call(attr_define.merge({required: true}))
148 | else
149 | return [true]
150 | end
151 | else
152 | return validate_proc.call(attr_define)
153 | end
154 | end
155 |
156 | def to_hash
157 | hash = {}
158 | attr_defines.each_pair do |attr, attr_define|
159 | attr_value = instance_variable_get(vname(attr))
160 | if attr_define[:to_hash] && attr_value && (attr_define[:default].nil? || attr_define[:default] != attr_value)
161 | if attr_define[:collection] || attr_define[:klass] || attr_define[:module]
162 | hash[attr] = attr_value.to_hash
163 | else
164 | hash[attr] = attr_value
165 | end
166 | end
167 | end
168 | hash
169 | end
170 |
171 | def attr_defines
172 | @_attr_defines ||= self.class.instance_variable_get("@attr_defines")
173 | end
174 |
175 | # get instance variable name
176 | def vname(k)
177 | return "@#{k}"
178 | end
179 |
180 | end
181 |
--------------------------------------------------------------------------------
/lib/mx-plan.rb:
--------------------------------------------------------------------------------
1 | require_relative 'json-object'
2 | require_relative 'mx-plan/mx-action'
3 |
4 | class MxPlan
5 | include JsonObject
6 |
7 | required_attr :name
8 | required_attr :pattern
9 | optional_attr :excludePattern
10 | required_attr :version
11 | optional_attr :disabled
12 | optional_attr :actions, collection: true, klass: ::MxAction
13 | optional_attr :tags
14 | optional_attr :contributors
15 | optional_attr :comment, to_hash: false
16 | end
17 |
--------------------------------------------------------------------------------
/lib/mx-plan/action/ch-attr-defines.rb:
--------------------------------------------------------------------------------
1 | require_relative '../../json-object'
2 |
3 | module ChAttrActionTypes
4 | T01 = "assign.from.value"
5 | T02 = "assign.from.self-attr"
6 |
7 | T11 = "assign.from.parent-attr"
8 | T12 = "assign.from.ancestor-attr"
9 | T13 = "assign.from.ancestor.child-attr"
10 |
11 | T21 = "assign.from.first-child-attr"
12 | T22 = "assign.from.child-attr"
13 | T23 = "assign.from.descendent-attr"
14 |
15 | T51 = "url.file.set-ext-suffix"
16 | T52 = "url.file.rm-ext-suffix"
17 | T53 = "url.file.set-name-suffix"
18 | T54 = "url.file.rm-name-suffix"
19 | T55 = "url.search.edit"
20 |
21 |
22 | T71 = "replace.last-match"
23 | T72 = "replace.all"
24 |
25 | T91 = "split2list.add"
26 | T92 = "split2list.remove"
27 |
28 |
29 | TYPES = [
30 | T01, T02,
31 | T11, T12, T13,
32 | T21, T22, T23,
33 | T51, T52, T53, T54, T55,
34 |
35 | T71, T72,
36 | T91, T92,
37 | ];
38 | end
39 |
40 |
41 | class OldChAttr
42 | include JsonObject
43 | include ChAttrActionTypes
44 |
45 | required_attr :type, in: TYPES
46 | required_attr :pick
47 | required_attr :attr
48 | optional_attr :subStr, required_if: -> {value_in(:type, T71, T72)}
49 | optional_attr :newStr, required_if: -> {value_in(:type, T71, T72)}
50 | optional_attr :tElem, required_if: -> {value_in(:type, T12, T13, T22, T23)}
51 | optional_attr :tAttr, required_if: -> {value_in(:type, T02, T11, T12, T13, T21, T22, T23)}
52 | optional_attr :value, required_if: -> {value_in(:type, T01, T91, T92)}
53 | optional_attr :sep, required_if: -> {value_in(:type, T51, T52, T53, T54)}
54 | optional_attr :suffix, required_if: -> {value_in(:type, T51, T53)}
55 | optional_attr :whiteList #T52, T54
56 | optional_attr :change #T55
57 | optional_attr :delete #T55
58 |
59 | end
60 |
61 | # without attribute "pick"
62 | class NewChAttrAction
63 | include JsonObject
64 | include ChAttrActionTypes
65 |
66 | required_attr :type, in: TYPES
67 | required_attr :attr
68 | optional_attr :subStr, required_if: -> {value_in(:type, T71, T72)}
69 | optional_attr :newStr, required_if: -> {value_in(:type, T71, T72)}
70 | optional_attr :tElem, required_if: -> {value_in(:type, T12, T13, T22, T23)}
71 | optional_attr :tAttr, required_if: -> {value_in(:type, T02, T11, T12, T13, T21, T22, T23)}
72 | optional_attr :value, required_if: -> {value_in(:type, T01, T91, T92)}
73 | optional_attr :sep, required_if: -> {value_in(:type, T51, T52, T53, T54)}
74 | optional_attr :suffix, required_if: -> {value_in(:type, T51, T53)}
75 | optional_attr :whiteList #T52, T54
76 | optional_attr :change #T55
77 | optional_attr :delete #T55
78 | end
79 |
80 | class NewChAttr
81 | include JsonObject
82 | required_attr :pick
83 | required_attr :action, optional_collection: true, klass: NewChAttrAction
84 | end
85 |
86 |
--------------------------------------------------------------------------------
/lib/mx-plan/action/change-attribute.rb:
--------------------------------------------------------------------------------
1 | require_relative 'ch-attr-defines'
2 |
3 | module Action
4 | module ChangeAttribute
5 | def self.new_instance(params)
6 | if params.has_key? "action"
7 | # new structure
8 | NewChAttr.new(params)
9 | else
10 | OldChAttr.new(params)
11 | end
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/mx-plan/action/execute-user-script.rb:
--------------------------------------------------------------------------------
1 | require_relative '../../json-object'
2 |
3 | module Action
4 | class ExecuteUserScript
5 | include JsonObject
6 |
7 | required_attr :script
8 | optional_attr :args
9 | optional_attr :when, in: %w[actived selecting idle]
10 | optional_attr :once, in: [true, false]
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/mx-plan/action/formula.rb:
--------------------------------------------------------------------------------
1 | require_relative '../../json-object'
2 |
3 | module Action
4 | class Formula
5 | include JsonObject
6 | required_attr :pick
7 | required_attr :attr
8 | optional_attr :block
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/mx-plan/action/hide-except.rb:
--------------------------------------------------------------------------------
1 | require_relative '../../json-object'
2 |
3 | module Action
4 | class HideExcept
5 | include JsonObject
6 |
7 | =begin
8 | Example:
9 | {
10 | ...
11 | actions: [
12 | {
13 | hideExcept: {
14 | "inside": ".post",
15 | "except": [".post-title", ".post-content"]
16 | }
17 | }
18 | ]
19 | }
20 | =end
21 |
22 | required_attr :inside
23 | required_attr :except
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/mx-plan/mx-action.rb:
--------------------------------------------------------------------------------
1 | require_relative '../json-object'
2 | require_relative 'action/hide-except'
3 | require_relative 'action/execute-user-script'
4 | require_relative 'action/formula'
5 | require_relative 'action/change-attribute'
6 |
7 | class MxAction
8 | include JsonObject
9 | optional_attr :hide
10 | optional_attr :hideSibling
11 | optional_attr :hideExcept, klass: Action::HideExcept
12 | optional_attr :show
13 | optional_attr :chAttr, module: ::Action::ChangeAttribute
14 | optional_attr :exec, klass: ::Action::ExecuteUserScript
15 | optional_attr :formula, klass: ::Action::Formula
16 | optional_attr :pick
17 | optional_attr :select
18 | optional_attr :confirm
19 | optional_attr :clip
20 | optional_attr :form
21 | optional_attr :config
22 | optional_attr :tag
23 | end
24 |
--------------------------------------------------------------------------------
/migration.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'fileutils'
4 | require_relative 'migrations/convert_to_actions.rb'
5 |
6 | # convert plan structure to:
7 | # {actions: [...]}
8 |
9 | def migrate
10 | Dir.glob('plans/*/*.json') do |file|
11 | MigrationConvertToActions.migrate(file, file)
12 | end
13 | end
14 |
15 | def main
16 | if !Dir.exist? 'tmp'
17 | FileUtils.mkdir 'tmp'
18 | end
19 |
20 | if Dir.exist? 'tmp/plans'
21 | FileUtils.rm_r 'tmp/plans'
22 | end
23 |
24 | FileUtils.cp_r 'plans', 'tmp/'
25 |
26 | begin
27 | migrate
28 | rescue => e
29 | puts "Error occured"
30 | puts "The original plans are backuped inside tmp/plans"
31 | puts "Restore it from there"
32 | puts e
33 | exit 1
34 | end
35 |
36 | puts "All plan converted"
37 | puts "If anything goes wrong, plase restore from tmp/plans"
38 | end
39 |
40 | main
41 |
--------------------------------------------------------------------------------
/migrations/convert_to_actions.rb:
--------------------------------------------------------------------------------
1 | require 'json'
2 |
3 | module MigrationConvertToActions
4 | class << self
5 |
6 | def migrate(plan_path, output_path)
7 | content = File.open(plan_path, 'r').read
8 | plans = JSON.parse(content)
9 | new_plans = plans.map {|it| migrate_plan(it)}
10 |
11 | File.open(output_path, 'w') do |file|
12 | file.write JSON.pretty_generate(new_plans)
13 | end
14 | end
15 |
16 | def migrate_plan(plan)
17 | if plan.has_key? "actions"
18 | # already the new version
19 | return plan
20 | end
21 |
22 | new_plan = plan.slice(
23 | "name",
24 | "pattern",
25 | "excludePattern",
26 | "version",
27 | "contributors",
28 | "disabled",
29 | )
30 |
31 | attrs = %W[hide hideSibling hideExcept show chAttr config form pick]
32 |
33 | actions = []
34 |
35 | attrs.each do |attr|
36 | next if !plan.has_key?(attr)
37 |
38 | value = plan[attr]
39 |
40 | case attr
41 | when 'chAttr'
42 | value.each do |it|
43 | action = {"chAttr" => it}
44 | actions << action
45 | end
46 | break
47 | when 'hideExcept'
48 | value.each do |it|
49 | action = {"hideExcept" => it}
50 | actions << action
51 | end
52 | break
53 | else
54 | action = [[attr, value]].to_h
55 | actions << action
56 | end
57 | end
58 |
59 | new_plan["actions"] = actions
60 | if plan.has_key? "tags"
61 | new_plan["tags"] = plan["tags"]
62 | end
63 | if plan.has_key? "comment"
64 | new_plan["comment"] = plan["comment"]
65 | end
66 | new_plan["version"] = (Time.now.strftime "%Y%m%d").to_i
67 | new_plan
68 | end
69 |
70 | def attrbute2action(name, value)
71 | return [[name, value]].to_h
72 | end
73 |
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/plans/channel_default.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "default",
3 | "folder": "default",
4 | "description": "Default channel hosts plans that relative to international websites"
5 | }
6 |
--------------------------------------------------------------------------------
/plans/channel_test.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test",
3 | "folder": "test",
4 | "description": "this plan list is just for testing only."
5 | }
6 |
--------------------------------------------------------------------------------
/plans/channel_zh.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Zhong-Hua",
3 | "folder": "zh",
4 | "description": "华文网站"
5 | }
6 |
--------------------------------------------------------------------------------
/plans/default/arch-wiki.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "ArchWiki",
4 | "pattern": "https://wiki.archlinux.org/title/*",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "pick": "#bodyContent"
12 | }
13 | ],
14 | "tags": [
15 | "IT",
16 | "wiki",
17 | "doc"
18 | ]
19 | }
20 | ]
--------------------------------------------------------------------------------
/plans/default/github.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Github",
4 | "pattern": "https://github.com/",
5 | "version": 20240502,
6 | "contributors": [
7 | "yzqzss (author)"
8 | ],
9 | "actions": [
10 | {
11 | "hide": [
12 | ".avatar",
13 | ".avatar-parent-child",
14 | ".inline-comment-form-actions",
15 | ".flex-items-center.d-sm-flex.d-none",
16 | ".d-inline-block"
17 | ]
18 | },
19 | {
20 | "pick": [
21 | ".js-discussion",
22 | ".markdown-body"
23 | ]
24 | }
25 | ],
26 | "tags": [
27 | "IT",
28 | "geek",
29 | "git"
30 | ]
31 | }
32 | ]
--------------------------------------------------------------------------------
/plans/default/wikipedia.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "wikipedia.org",
4 | "pattern": "https://*.wikipedia.org/wiki/*",
5 | "excludePattern": "https://*.m.wikipedia.org/*/",
6 | "version": 20240502,
7 | "actions": [
8 | {
9 | "hide": [
10 | "div#toc",
11 | "table.infobox",
12 | "span.mw-editsection",
13 | "table.navbox",
14 | "div.thumb",
15 | "div.dablink",
16 | "table.metadata"
17 | ]
18 | },
19 | {
20 | "pick": "div.mw-parser-output"
21 | }
22 | ],
23 | "tags": [
24 | "knowledge",
25 | "wiki"
26 | ]
27 | },
28 | {
29 | "name": "Wikipedia Mobile View",
30 | "pattern": "https://*.m.wikipedia.org/*/",
31 | "version": 20240502,
32 | "contributors": [
33 | "Altair Wei (author)"
34 | ],
35 | "actions": [
36 | {
37 | "hide": [
38 | ".page-actions-menu",
39 | "#toc",
40 | ".mw-editsection",
41 | "h2 > div.mw-ui-icon",
42 | "table.box-Unreferenced",
43 | "table.box-More_citations_needed"
44 | ]
45 | },
46 | {
47 | "chAttr": {
48 | "type": "split2list.add",
49 | "pick": "h2.section-heading",
50 | "attr": "style",
51 | "value": "width: 100%",
52 | "sep": ";"
53 | }
54 | }
55 | ],
56 | "tags": [
57 | "knowledge",
58 | "wiki"
59 | ]
60 | }
61 | ]
--------------------------------------------------------------------------------
/plans/test/dev-pc.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "mx.pc/hide-except",
4 | "pattern": "http://*.pc/maoxian-web-clipper/**/test-hide-except.html",
5 | "version": 20240502,
6 | "actions": [
7 | {
8 | "hideExcept": {
9 | "inside": "pre",
10 | "except": [
11 | "code"
12 | ]
13 | }
14 | }
15 | ]
16 | },
17 | {
18 | "name": "mx.pc/hg-img",
19 | "pattern": "http://*.pc/maoxian-web-clipper/**/test-hq-img.html",
20 | "version": 20240502,
21 | "actions": [
22 | {
23 | "chAttr": {
24 | "type": "split2list.add",
25 | "pick": "div.t0",
26 | "attr": "class",
27 | "value": "actived"
28 | }
29 | },
30 | {
31 | "chAttr": {
32 | "type": "assign.from.self-attr",
33 | "pick": "div.t1 img",
34 | "attr": "src",
35 | "tAttr": "hq-src"
36 | }
37 | },
38 | {
39 | "chAttr": {
40 | "pick": "div.t2 img",
41 | "action": {
42 | "type": "replace.last-match",
43 | "attr": "src",
44 | "subStr": "thumb",
45 | "newStr": "hq"
46 | }
47 | }
48 | },
49 | {
50 | "chAttr": {
51 | "pick": "div.t3 img",
52 | "action": [{
53 | "type": "assign.from.parent-attr",
54 | "attr": "src",
55 | "tAttr": "href"
56 | }]
57 | }
58 | }
59 | ]
60 | },
61 | {
62 | "name": "mx.pc/iframe",
63 | "pattern": "http://*.pc/maoxian-web-clipper/**/iframe*",
64 | "version": 20240502,
65 | "actions": [
66 | {
67 | "chAttr": {
68 | "type": "assign.from.self-attr",
69 | "pick": "img",
70 | "attr": "src",
71 | "tAttr": "hq-src"
72 | }
73 | }
74 | ]
75 | },
76 | {
77 | "name": "mx.pc/mx-index",
78 | "pattern": "http://*.pc/maoxian-web-clipper/index*.html",
79 | "version": 20240502,
80 | "actions": [
81 | {
82 | "hide": "ul"
83 | },
84 | {
85 | "pick": ".main"
86 | }
87 | ]
88 | },
89 | {
90 | "name": "mx.pc/blog-post",
91 | "pattern": "http://*.pc/blog/**/*/*/*/*/*.html",
92 | "version": 20240502,
93 | "actions": [
94 | {
95 | "hide": [
96 | "header"
97 | ]
98 | },
99 | {
100 | "pick": [
101 | ".page-content"
102 | ]
103 | }
104 | ]
105 | },
106 | {
107 | "name": "mx.pc/test-tab",
108 | "pattern": "http://*.pc/maoxian-web-clipper/**/test-tab.html",
109 | "version": 20240502,
110 | "actions": [
111 | {
112 | "chAttr": {
113 | "type": "split2list.add",
114 | "attr": "class",
115 | "pick": ".tab-content",
116 | "value": "actived"
117 | }
118 | }
119 | ]
120 | }
121 | ]
122 |
--------------------------------------------------------------------------------
/plans/zh/36kr.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "36kr",
4 | "pattern": "https://36kr.com/p/$d",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "pick": ".articleDetailContent"
12 | }
13 | ],
14 | "tags": [
15 | "IT",
16 | "news",
17 | "tech"
18 | ]
19 | }
20 | ]
--------------------------------------------------------------------------------
/plans/zh/52pojie.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "52pojie",
4 | "pattern": "https://www.52pojie.cn/thread-*.html",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "hide": [
12 | "#toptb",
13 | "#hd",
14 | "#pt",
15 | "#pgt",
16 | "#postlist > table",
17 | "table.ad",
18 | ".psth",
19 | "#fj",
20 | "img[alt=回帖奖励]",
21 | "#threadstamp",
22 | "dl.rate",
23 | "ul.mbw",
24 | ".dnch_eo_pb",
25 | ".dnch_eo_pt",
26 | ".po.hin",
27 | ".favatar",
28 | ".authicn.vm",
29 | "#p_btn",
30 | ".pgbtn",
31 | "#diyfastposttop",
32 | "#fastpostform",
33 | ".res-footer-note",
34 | "#jz52top",
35 | "#ft",
36 | "X||//a[text() = '\n推荐\n']",
37 | "X||//a[text() = '推荐']"
38 | ]
39 | },
40 | {
41 | "chAttr": {
42 | "type": "assign.from.value",
43 | "pick": "#postlist table.plhin",
44 | "attr": "data-mx-layout-table",
45 | "value": "1"
46 | }
47 | }
48 | ],
49 | "tags": [
50 | "IT",
51 | "forum"
52 | ]
53 | }
54 | ]
--------------------------------------------------------------------------------
/plans/zh/bilibili.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Bilibili 专栏",
4 | "pattern": "https://www.bilibili.com/read/*",
5 | "version": 20240502,
6 | "contributors": [
7 | "yzqzss",
8 | "Mika"
9 | ],
10 | "actions": [
11 | {
12 | "hide": [
13 | ".img-box .n-img-mask"
14 | ]
15 | },
16 | {
17 | "chAttr": {
18 | "type": "assign.from.self-attr",
19 | "pick": ".img-box img",
20 | "attr": "src",
21 | "tAttr": "data-src"
22 | }
23 | }
24 | ]
25 | }
26 | ]
--------------------------------------------------------------------------------
/plans/zh/cnblogs.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "博客园",
4 | "pattern": "https://www.cnblogs.com/**/*/*.html",
5 | "version": 20240502,
6 | "contributors": [
7 | "CodingOX"
8 | ],
9 | "actions": [
10 | {
11 | "hide": [
12 | "div.cnblogs_code_toolbar",
13 | "div.cnblogs_code_copy"
14 | ]
15 | },
16 | {
17 | "pick": "div#cnblogs_post_body"
18 | }
19 | ],
20 | "tags": [
21 | "IT",
22 | "blog"
23 | ]
24 | }
25 | ]
--------------------------------------------------------------------------------
/plans/zh/coolshell.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "酷壳",
4 | "pattern": "https://coolshell.cn/articles/*",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "pick": ".post-content"
12 | }
13 | ],
14 | "tags": [
15 | "IT",
16 | "blog"
17 | ]
18 | }
19 | ]
--------------------------------------------------------------------------------
/plans/zh/csdn.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "CSDN-博客",
4 | "pattern": "https://blog.csdn.net/*/article/details/*",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "hide": [
12 | "pre > .signin",
13 | ".hide-article-box",
14 | "#blogColumnPayAdvert",
15 | "#toolBarBox",
16 | "#asidedirectory",
17 | ".recommend-box",
18 | ".template-box",
19 | "#copyright-box"
20 | ]
21 | },
22 | {
23 | "hideSibling": [
24 | ".unlogin-comment-model > .unlogin-comment-tit",
25 | ".main_father",
26 | "#mainBox > main"
27 | ]
28 | },
29 | {
30 | "chAttr": {
31 | "type": "split2list.add",
32 | "pick": "#article_content",
33 | "attr": "style",
34 | "value": "height: auto !important",
35 | "sep": ";"
36 | }
37 | },
38 | {
39 | "chAttr": {
40 | "type": "split2list.add",
41 | "pick": "#content_views pre code",
42 | "attr": "style",
43 | "value": "user-select: text !important",
44 | "sep": ";"
45 | }
46 | }
47 | ],
48 | "comment": "Removed code selection, Unfolded article content"
49 | }
50 | ]
--------------------------------------------------------------------------------
/plans/zh/guokr.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "果壳",
4 | "pattern": "http*://www.guokr.com/article/",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "pick": "div[class*=ArticleContent]"
12 | }
13 | ],
14 | "tags": [
15 | "science",
16 | "nature"
17 | ]
18 | }
19 | ]
--------------------------------------------------------------------------------
/plans/zh/ifeng.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "凤凰网/资讯",
4 | "pattern": "https://news.ifeng.com/c/*",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "hide": [
12 | "[class|=rightContent]",
13 | "[class|=infoboxLink] [class|=face]",
14 | "[class|=infoboxLink] [class|=follow]",
15 | "[class|=artical] [class|=info] > [class|=share_box]",
16 | "[class|=article] [class|=info] > [class|=share_box]",
17 | "[class|=vote_box] [class|=qr_box]",
18 | "[class|=vote_box] [class|=share_box]",
19 | "[class|=comment_textarea_head]",
20 | "[class|=comment_textarea_body]",
21 | "[class|=comment_textarea_bottom]",
22 | "[class|=showAllComment]"
23 | ]
24 | },
25 | {
26 | "hideSibling": [
27 | "[class|=artical]",
28 | "[class|=article]"
29 | ]
30 | },
31 | {
32 | "pick": [
33 | "[class|=main_content]",
34 | "[class|=artical]",
35 | "[class|=article]"
36 | ]
37 | }
38 | ],
39 | "tags": [
40 | "news"
41 | ]
42 | }
43 | ]
--------------------------------------------------------------------------------
/plans/zh/ipcfun.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "iPcfun",
4 | "pattern": "https://www.ipcfun.com/*.html",
5 | "version": 20240502,
6 | "actions": [
7 | {
8 | "hide": [
9 | ".post_detail > .entry-relate-links",
10 | "#sidebar"
11 | ]
12 | },
13 | {
14 | "pick": ".post"
15 | }
16 | ]
17 | }
18 | ]
--------------------------------------------------------------------------------
/plans/zh/iplaysoft.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "异次元软件世界",
4 | "pattern": "https://www.iplaysoft.com/*.html",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "hide": [
12 | "ul#link1111",
13 | "#socialshare",
14 | ".post_wechat_qr",
15 | ".donate",
16 | ".entry-recommend-posts",
17 | ".entry-relate-links",
18 | ".same-cat-post-head",
19 | ".same-cat-post",
20 | "#respond",
21 | "#sidebar"
22 | ]
23 | },
24 | {
25 | "chAttr": {
26 | "type": "assign.from.self-attr",
27 | "pick": "div.entry-content img.lazyload",
28 | "attr": "src",
29 | "tAttr": "data-src"
30 | }
31 | }
32 | ],
33 | "tags": [
34 | "IT",
35 | "software",
36 | "share"
37 | ]
38 | }
39 | ]
--------------------------------------------------------------------------------
/plans/zh/iteye.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "iteye",
4 | "pattern": "http*://*.iteye.com/blog/*",
5 | "version": 20240502,
6 | "actions": [
7 | {
8 | "hide": [
9 | "#bottoms",
10 | ".blog_bottom",
11 | ".blog_nav",
12 | ".blog_comment"
13 | ]
14 | },
15 | {
16 | "pick": "#blog_content"
17 | }
18 | ],
19 | "tags": [
20 | "IT",
21 | "blog"
22 | ]
23 | }
24 | ]
--------------------------------------------------------------------------------
/plans/zh/ithome.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "IT之家",
4 | "pattern": "https://www.ithome.com/**/*/*.htm",
5 | "version": 20240502,
6 | "contributors": [
7 | "yzqzss (author)",
8 | "Mika"
9 | ],
10 | "actions": [
11 | {
12 | "chAttr": {
13 | "type": "assign.from.self-attr",
14 | "pick": "img.lazy",
15 | "attr": "src",
16 | "tAttr": "data-original"
17 | }
18 | }
19 | ],
20 | "tags": [
21 | "IT",
22 | "news"
23 | ]
24 | }
25 | ]
--------------------------------------------------------------------------------
/plans/zh/jianshu.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "简书",
4 | "pattern": "https://www.jianshu.com/p/*",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "chAttr": {
12 | "type": "assign.from.self-attr",
13 | "pick": ".image-view img[data-original-src]",
14 | "attr": "src",
15 | "tAttr": "data-original-src"
16 | }
17 | }
18 | ],
19 | "tags": [
20 | "writing"
21 | ]
22 | }
23 | ]
--------------------------------------------------------------------------------
/plans/zh/juejin.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "掘金-文章/资讯",
4 | "pattern": [
5 | "https://juejin.cn/post/*"
6 | ],
7 | "version": 20240502,
8 | "contributors": [
9 | "CodingOX"
10 | ],
11 | "actions": [
12 | {
13 | "hideSibling": [
14 | ".article-content pre > code"
15 | ]
16 | },
17 | {
18 | "pick": ".article-content"
19 | }
20 | ],
21 | "tags": [
22 | "IT",
23 | "community",
24 | "frontend"
25 | ]
26 | },
27 | {
28 | "name": "掘金-资讯",
29 | "pattern": [
30 | "https://juejin.cn/news/*"
31 | ],
32 | "version": 20240502,
33 | "contributors": [
34 | "Mika"
35 | ],
36 | "actions": [
37 | {
38 | "hide": [
39 | ".article #commentListBox",
40 | ".article .jh-news-detail-action-area > .category",
41 | ".article .jh-news-detail-action-area > .viewcount"
42 | ]
43 | },
44 | {
45 | "pick": ".article"
46 | }
47 | ],
48 | "tags": [
49 | "IT",
50 | "community",
51 | "frontend"
52 | ]
53 | },
54 | {
55 | "name": "掘金-小册",
56 | "pattern": "https://juejin.cn/book/*/section/*",
57 | "version": 20240502,
58 | "contributors": [
59 | "Sularxx",
60 | "Mika"
61 | ],
62 | "actions": [
63 | {
64 | "form": {
65 | "title": ".article-content > .markdown-body > h1"
66 | }
67 | },
68 | {
69 | "pick": ".article-content > .markdown-body"
70 | }
71 | ],
72 | "tags": [
73 | "IT",
74 | "book"
75 | ]
76 | }
77 | ]
--------------------------------------------------------------------------------
/plans/zh/linux-china.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "linux.cn",
4 | "pattern": "https://linux.cn/article-*.html",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "pick": "div#article_content"
12 | }
13 | ],
14 | "tags": [
15 | "IT",
16 | "Linux"
17 | ]
18 | }
19 | ]
--------------------------------------------------------------------------------
/plans/zh/ruanyifeng.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "阮一峰的博客",
4 | "pattern": "http*://www.ruanyifeng.com/blog/**/*/*.html",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "pick": "article"
12 | }
13 | ],
14 | "tags": [
15 | "IT",
16 | "blog",
17 | "frontend"
18 | ]
19 | }
20 | ]
--------------------------------------------------------------------------------
/plans/zh/ruby-china.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "ruby-china.org",
4 | "pattern": "https://ruby-china.org/topics/*",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "pick": ".topic-detail"
12 | }
13 | ],
14 | "tags": [
15 | "IT",
16 | "community",
17 | "ruby"
18 | ]
19 | }
20 | ]
--------------------------------------------------------------------------------
/plans/zh/segmentfault.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "segmentfault.com",
4 | "pattern": "https://segmentfault.com/a/",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "pick": "article.article-content"
12 | }
13 | ],
14 | "tags": [
15 | "IT",
16 | "geek"
17 | ]
18 | }
19 | ]
--------------------------------------------------------------------------------
/plans/zh/solidot-cn.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Solidot",
4 | "pattern": "https://www.solidot.org/story",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "pick": "div.p_mainnew"
12 | }
13 | ],
14 | "tags": [
15 | "IT",
16 | "news",
17 | "geek"
18 | ]
19 | }
20 | ]
--------------------------------------------------------------------------------
/plans/zh/sspai.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "少数派",
4 | "pattern": "https://sspai.com/post/*",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "hide": [
12 | ".bgGrey",
13 | ".article-side"
14 | ]
15 | },
16 | {
17 | "chAttr": {
18 | "type": "assign.from.self-attr",
19 | "pick": ".content img",
20 | "attr": "src",
21 | "tAttr": "data-original"
22 | }
23 | }
24 | ],
25 | "tags": [
26 | "news"
27 | ]
28 | }
29 | ]
--------------------------------------------------------------------------------
/plans/zh/v2ex.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "v2ex",
4 | "pattern": "https://www.v2ex.com/t/",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "pick": "div.topic_content"
12 | }
13 | ],
14 | "tags": [
15 | "IT",
16 | "forum",
17 | "share",
18 | "creation"
19 | ]
20 | }
21 | ]
--------------------------------------------------------------------------------
/plans/zh/wangyi.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "wangyi open courses",
4 | "pattern": "https://open.163.com/newview/movie/free",
5 | "version": 20240502,
6 | "actions": [
7 | {
8 | "hideSibling": [
9 | ".video-js > video"
10 | ]
11 | },
12 | {
13 | "pick": ".video-module"
14 | }
15 | ]
16 | }
17 | ]
--------------------------------------------------------------------------------
/plans/zh/weibo.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "新浪微博",
4 | "pattern": "https://weibo.com/$d/*",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "hideSibling": [
12 | "video.wbpv-tech"
13 | ]
14 | },
15 | {
16 | "chAttr": {
17 | "type": "assign.from.value",
18 | "pick": ".vue-recycle-scroller__item-wrapper",
19 | "attr": "data-mx-sort-by-index",
20 | "value": "1"
21 | }
22 | },
23 | {
24 | "chAttr": {
25 | "type": "assign.from.value",
26 | "pick": ".vue-recycle-scroller__item-view",
27 | "attr": "data-mx-locked-style-position",
28 | "value": "static !important"
29 | }
30 | },
31 | {
32 | "chAttr": {
33 | "type": "assign.from.value",
34 | "pick": ".vue-recycle-scroller__item-view",
35 | "attr": "data-mx-locked-style-transform",
36 | "value": "translateY(0px)"
37 | }
38 | },
39 | {
40 | "chAttr": {
41 | "type": "assign.from.first-child-attr",
42 | "pick": ".vue-recycle-scroller__item-view",
43 | "attr": "data-mx-index",
44 | "tAttr": "data-index"
45 | }
46 | }
47 | ]
48 | }
49 | ]
--------------------------------------------------------------------------------
/plans/zh/weixin-gh.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "wixin-gh",
4 | "pattern": "https://mp.weixin.qq.com/s/*",
5 | "version": 20240508,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "hide": [ ".qr_code_pc" ]
12 | },
13 | {
14 | "chAttr": {
15 | "type": "assign.from.self-attr",
16 | "pick": ".wxw-img[data-src], img.img_loading[data-src]",
17 | "attr": "src",
18 | "tAttr": "data-src"
19 | }
20 | },
21 | {
22 | "formula": {
23 | "pick": "mjx-container",
24 | "attr": "data-formula"
25 | },
26 | "tag": "md-only"
27 | }
28 | ]
29 | }
30 | ]
31 |
--------------------------------------------------------------------------------
/plans/zh/zhihu.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "知乎专栏",
4 | "pattern": "https://zhuanlan.zhihu.com/p/**",
5 | "version": 20240502,
6 | "contributors": [
7 | "Mika"
8 | ],
9 | "actions": [
10 | {
11 | "hide": [
12 | "figure > noscript",
13 | ".GifPlayer-icon",
14 | ".GifPlayer > .GifPlayer-gif2mp4",
15 | ".PostIndex-Contributions",
16 | ".Recommendations-Main",
17 | ".CommentsV2-footer-wrapper",
18 | ".CommentTopbar .Topbar-options",
19 | ".ColumnPageHeader-Wrapper"
20 | ]
21 | },
22 | {
23 | "chAttr": {
24 | "type": "split2list.remove",
25 | "pick": ".RichContent-actions",
26 | "attr": "class",
27 | "value": [
28 | "Sticky",
29 | "is-fixed",
30 | "is-bottom"
31 | ]
32 | }
33 | },
34 | {
35 | "chAttr": {
36 | "type": "assign.from.self-attr",
37 | "pick": "figure img",
38 | "attr": "src",
39 | "tAttr": "data-actualsrc"
40 | }
41 | },
42 | {
43 | "chAttr": {
44 | "type": "replace.last-match",
45 | "pick": ".GifPlayer img",
46 | "attr": "src",
47 | "subStr": ".jpg",
48 | "newStr": ".gif"
49 | }
50 | }
51 | ],
52 | "tags": [
53 | "knowledge",
54 | "share"
55 | ]
56 | },
57 | {
58 | "name": "知乎回答",
59 | "pattern": "https://www.zhihu.com/question/*/answer/*",
60 | "version": 20240502,
61 | "contributors": [
62 | "Mika"
63 | ],
64 | "actions": [
65 | {
66 | "hide": [
67 | ".QuestionHeader-footer-main",
68 | ".Card.ViewAll",
69 | ".Question-mainColumnLogin",
70 | ".Topbar-options",
71 | ".List-header",
72 | ".ContentItem-expandButton",
73 | ".ContentItem-action.ContentItem-rightButton",
74 | ".GifPlayer-icon",
75 | ".GifPlayer > .GifPlayer-gif2mp4",
76 | ".CommentsV2-footer-wrapper"
77 | ]
78 | },
79 | {
80 | "chAttr": {
81 | "type": "split2list.remove",
82 | "pick": ".ContentItem .RichContent",
83 | "attr": "class",
84 | "value": [
85 | "is-collapsed"
86 | ]
87 | }
88 | },
89 | {
90 | "chAttr": {
91 | "type": "split2list.remove",
92 | "pick": ".ContentItem-actions",
93 | "attr": "class",
94 | "value": [
95 | "Sticky",
96 | "is-fixed",
97 | "is-bottom"
98 | ]
99 | }
100 | },
101 | {
102 | "chAttr": {
103 | "type": "assign.from.self-attr",
104 | "pick": "figure img",
105 | "attr": "src",
106 | "tAttr": "data-actualsrc"
107 | }
108 | },
109 | {
110 | "chAttr": {
111 | "type": "replace.last-match",
112 | "pick": ".GifPlayer img",
113 | "attr": "src",
114 | "subStr": ".jpg",
115 | "newStr": ".gif"
116 | }
117 | }
118 | ],
119 | "tags": [
120 | "QA",
121 | "answer",
122 | "share",
123 | "discuss"
124 | ]
125 | },
126 | {
127 | "name": "知乎问题",
128 | "pattern": "https://www.zhihu.com/question/*",
129 | "version": 20240502,
130 | "contributors": [
131 | "Mika"
132 | ],
133 | "actions": [
134 | {
135 | "hide": [
136 | ".QuestionHeader-footer-main",
137 | ".AnswersNavWrapper .List-header",
138 | ".ContentItem-expandButton",
139 | ".ContentItem-action.ContentItem-rightButton",
140 | ".GifPlayer-icon",
141 | ".GifPlayer > .GifPlayer-gif2mp4",
142 | ".CommentsV2-footer-wrapper",
143 | ".Question-sideColumn"
144 | ]
145 | },
146 | {
147 | "chAttr": {
148 | "type": "split2list.remove",
149 | "pick": ".ContentItem .RichContent",
150 | "attr": "class",
151 | "value": [
152 | "is-collapsed"
153 | ]
154 | }
155 | },
156 | {
157 | "chAttr": {
158 | "type": "split2list.remove",
159 | "pick": ".ContentItem-actions",
160 | "attr": "class",
161 | "value": [
162 | "Sticky",
163 | "is-fixed",
164 | "is-bottom"
165 | ],
166 | "sep": " "
167 | }
168 | },
169 | {
170 | "chAttr": {
171 | "type": "assign.from.self-attr",
172 | "pick": "figure img",
173 | "attr": "src",
174 | "tAttr": "data-actualsrc"
175 | }
176 | },
177 | {
178 | "chAttr": {
179 | "type": "replace.last-match",
180 | "pick": ".GifPlayer img",
181 | "attr": "src",
182 | "subStr": ".jpg",
183 | "newStr": ".gif"
184 | }
185 | }
186 | ],
187 | "tags": [
188 | "QA",
189 | "question",
190 | "share",
191 | "discuss"
192 | ]
193 | },
194 | {
195 | "name": "zhihu video",
196 | "pattern": "https://video.zhihu.com/video/*",
197 | "version": 20240502,
198 | "actions": [
199 | {
200 | "hideSibling": "X||//video/.."
201 | }
202 | ]
203 | }
204 | ]
--------------------------------------------------------------------------------
/tmp/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mika-cn/maoxian-assistant/f3acaefc2f0b0dccf955274501be51669453f459/tmp/.keep
--------------------------------------------------------------------------------