├── .editorconfig ├── .github └── workflows │ └── workflow.yaml ├── .gitignore ├── LICENSE ├── OWNERS ├── README.md ├── build.gradle ├── console ├── .editorconfig ├── .eslintrc.cjs ├── .gitignore ├── env.d.ts ├── package.json ├── pnpm-lock.yaml ├── src │ ├── api │ │ ├── pushlog │ │ │ └── pushlog.ts │ │ └── request.ts │ ├── components │ │ └── PushLogListItem.vue │ ├── index.ts │ ├── types │ │ └── index.ts │ ├── utils │ │ └── date.ts │ └── views │ │ └── PushLog.vue ├── tsconfig.app.json ├── tsconfig.config.json ├── tsconfig.json ├── tsconfig.vitest.json └── vite.config.ts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── com │ └── stonewu │ └── sitepush │ ├── GlobalCache.java │ ├── SiteHeadProcessor.java │ ├── SitePushEndpoint.java │ ├── SitePushPlugin.java │ ├── job │ └── CleanOldDataJob.java │ ├── reconciler │ ├── AbstractPushReconciler.java │ ├── PostPushReconciler.java │ └── SinglePagePushReconciler.java │ ├── router │ └── PushLogRouter.java │ ├── scheme │ ├── PushLog.java │ └── PushUnique.java │ ├── service │ ├── PushService.java │ └── PushServiceImpl.java │ ├── setting │ ├── BaiduPushSetting.java │ ├── BaiduSettingProvider.java │ ├── BasePushSetting.java │ ├── BingPushSetting.java │ ├── BingPushSettingProvider.java │ ├── GooglePushSetting.java │ ├── GooglePushSettingProvider.java │ └── PushSettingProvider.java │ ├── strategy │ ├── AbstractPushStrategy.java │ ├── BaiduPushStrategy.java │ ├── BingPushStrategy.java │ ├── GooglePushStrategy.java │ └── PushStrategy.java │ └── utils │ ├── AbstractHttpRequestSender.java │ ├── AuthProxyHttpRequestSender.java │ ├── DefaultHttpRequestSender.java │ ├── HttpRequestSender.java │ ├── HttpResponse.java │ ├── JWTSignUtil.java │ ├── PemReader.java │ ├── Proxy.java │ ├── ProxyHttpRequestSender.java │ └── SecurityUtil.java └── resources ├── extensions ├── roleTemplate.yaml └── settings.yaml └── plugin.yaml /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | indent_size = 4 5 | indent_style = space 6 | insert_final_newline = false 7 | max_line_length = 120 8 | tab_width = 4 9 | ij_continuation_indent_size = 8 10 | ij_formatter_off_tag = @formatter:off 11 | ij_formatter_on_tag = @formatter:on 12 | ij_formatter_tags_enabled = false 13 | ij_smart_tabs = false 14 | ij_wrap_on_typing = false 15 | 16 | [*.java] 17 | max_line_length = 100 18 | ij_continuation_indent_size = 4 19 | ij_java_align_consecutive_assignments = false 20 | ij_java_align_consecutive_variable_declarations = false 21 | ij_java_align_group_field_declarations = false 22 | ij_java_align_multiline_annotation_parameters = false 23 | ij_java_align_multiline_array_initializer_expression = false 24 | ij_java_align_multiline_assignment = false 25 | ij_java_align_multiline_binary_operation = false 26 | ij_java_align_multiline_chained_methods = false 27 | ij_java_align_multiline_extends_list = false 28 | ij_java_align_multiline_for = true 29 | ij_java_align_multiline_method_parentheses = false 30 | ij_java_align_multiline_parameters = false 31 | ij_java_align_multiline_parameters_in_calls = false 32 | ij_java_align_multiline_parenthesized_expression = false 33 | ij_java_align_multiline_records = true 34 | ij_java_align_multiline_resources = true 35 | ij_java_align_multiline_ternary_operation = false 36 | ij_java_align_multiline_text_blocks = false 37 | ij_java_align_multiline_throws_list = false 38 | ij_java_align_subsequent_simple_methods = false 39 | ij_java_align_throws_keyword = false 40 | ij_java_annotation_parameter_wrap = off 41 | ij_java_array_initializer_new_line_after_left_brace = false 42 | ij_java_array_initializer_right_brace_on_new_line = false 43 | ij_java_array_initializer_wrap = normal 44 | ij_java_assert_statement_colon_on_next_line = false 45 | ij_java_assert_statement_wrap = normal 46 | ij_java_assignment_wrap = normal 47 | ij_java_binary_operation_sign_on_next_line = true 48 | ij_java_binary_operation_wrap = normal 49 | ij_java_blank_lines_after_anonymous_class_header = 0 50 | ij_java_blank_lines_after_class_header = 0 51 | ij_java_blank_lines_after_imports = 1 52 | ij_java_blank_lines_after_package = 1 53 | ij_java_blank_lines_around_class = 1 54 | ij_java_blank_lines_around_field = 0 55 | ij_java_blank_lines_around_field_in_interface = 0 56 | ij_java_blank_lines_around_initializer = 1 57 | ij_java_blank_lines_around_method = 1 58 | ij_java_blank_lines_around_method_in_interface = 1 59 | ij_java_blank_lines_before_class_end = 0 60 | ij_java_blank_lines_before_imports = 0 61 | ij_java_blank_lines_before_method_body = 0 62 | ij_java_blank_lines_before_package = 1 63 | ij_java_block_brace_style = end_of_line 64 | ij_java_block_comment_at_first_column = false 65 | ij_java_call_parameters_new_line_after_left_paren = false 66 | ij_java_call_parameters_right_paren_on_new_line = false 67 | ij_java_call_parameters_wrap = normal 68 | ij_java_case_statement_on_separate_line = true 69 | ij_java_catch_on_new_line = false 70 | ij_java_class_annotation_wrap = split_into_lines 71 | ij_java_class_brace_style = end_of_line 72 | ij_java_class_count_to_use_import_on_demand = 999 73 | ij_java_class_names_in_javadoc = 1 74 | ij_java_do_not_indent_top_level_class_members = false 75 | ij_java_do_not_wrap_after_single_annotation = false 76 | ij_java_do_while_brace_force = always 77 | ij_java_doc_add_blank_line_after_description = true 78 | ij_java_doc_add_blank_line_after_param_comments = false 79 | ij_java_doc_add_blank_line_after_return = false 80 | ij_java_doc_add_p_tag_on_empty_lines = true 81 | ij_java_doc_align_exception_comments = true 82 | ij_java_doc_align_param_comments = false 83 | ij_java_doc_do_not_wrap_if_one_line = false 84 | ij_java_doc_enable_formatting = true 85 | ij_java_doc_enable_leading_asterisks = true 86 | ij_java_doc_indent_on_continuation = false 87 | ij_java_doc_keep_empty_lines = true 88 | ij_java_doc_keep_empty_parameter_tag = true 89 | ij_java_doc_keep_empty_return_tag = true 90 | ij_java_doc_keep_empty_throws_tag = true 91 | ij_java_doc_keep_invalid_tags = true 92 | ij_java_doc_param_description_on_new_line = false 93 | ij_java_doc_preserve_line_breaks = false 94 | ij_java_doc_use_throws_not_exception_tag = true 95 | ij_java_else_on_new_line = false 96 | ij_java_enum_constants_wrap = normal 97 | ij_java_extends_keyword_wrap = normal 98 | ij_java_extends_list_wrap = normal 99 | ij_java_field_annotation_wrap = split_into_lines 100 | ij_java_finally_on_new_line = false 101 | ij_java_for_brace_force = always 102 | ij_java_for_statement_new_line_after_left_paren = false 103 | ij_java_for_statement_right_paren_on_new_line = false 104 | ij_java_for_statement_wrap = normal 105 | ij_java_generate_final_locals = false 106 | ij_java_generate_final_parameters = false 107 | ij_java_if_brace_force = always 108 | ij_java_imports_layout = $*, |, *, |, * 109 | ij_java_indent_case_from_switch = true 110 | ij_java_insert_inner_class_imports = false 111 | ij_java_insert_override_annotation = true 112 | ij_java_keep_blank_lines_before_right_brace = 2 113 | ij_java_keep_blank_lines_between_package_declaration_and_header = 2 114 | ij_java_keep_blank_lines_in_code = 2 115 | ij_java_keep_blank_lines_in_declarations = 2 116 | ij_java_keep_control_statement_in_one_line = true 117 | ij_java_keep_first_column_comment = true 118 | ij_java_keep_indents_on_empty_lines = false 119 | ij_java_keep_line_breaks = true 120 | ij_java_keep_multiple_expressions_in_one_line = false 121 | ij_java_keep_simple_blocks_in_one_line = false 122 | ij_java_keep_simple_classes_in_one_line = false 123 | ij_java_keep_simple_lambdas_in_one_line = false 124 | ij_java_keep_simple_methods_in_one_line = false 125 | ij_java_label_indent_absolute = false 126 | ij_java_label_indent_size = 0 127 | ij_java_lambda_brace_style = end_of_line 128 | ij_java_layout_static_imports_separately = true 129 | ij_java_line_comment_add_space = true 130 | ij_java_line_comment_at_first_column = false 131 | ij_java_method_annotation_wrap = split_into_lines 132 | ij_java_method_brace_style = end_of_line 133 | ij_java_method_call_chain_wrap = normal 134 | ij_java_method_parameters_new_line_after_left_paren = false 135 | ij_java_method_parameters_right_paren_on_new_line = false 136 | ij_java_method_parameters_wrap = normal 137 | ij_java_modifier_list_wrap = false 138 | ij_java_names_count_to_use_import_on_demand = 999 139 | ij_java_new_line_after_lparen_in_record_header = false 140 | ij_java_parameter_annotation_wrap = normal 141 | ij_java_parentheses_expression_new_line_after_left_paren = false 142 | ij_java_parentheses_expression_right_paren_on_new_line = false 143 | ij_java_place_assignment_sign_on_next_line = false 144 | ij_java_prefer_longer_names = true 145 | ij_java_prefer_parameters_wrap = false 146 | ij_java_record_components_wrap = normal 147 | ij_java_repeat_synchronized = true 148 | ij_java_replace_instanceof_and_cast = false 149 | ij_java_replace_null_check = true 150 | ij_java_replace_sum_lambda_with_method_ref = true 151 | ij_java_resource_list_new_line_after_left_paren = false 152 | ij_java_resource_list_right_paren_on_new_line = false 153 | ij_java_resource_list_wrap = normal 154 | ij_java_rparen_on_new_line_in_record_header = false 155 | ij_java_space_after_closing_angle_bracket_in_type_argument = false 156 | ij_java_space_after_colon = true 157 | ij_java_space_after_comma = true 158 | ij_java_space_after_comma_in_type_arguments = true 159 | ij_java_space_after_for_semicolon = true 160 | ij_java_space_after_quest = true 161 | ij_java_space_after_type_cast = true 162 | ij_java_space_before_annotation_array_initializer_left_brace = false 163 | ij_java_space_before_annotation_parameter_list = false 164 | ij_java_space_before_array_initializer_left_brace = true 165 | ij_java_space_before_catch_keyword = true 166 | ij_java_space_before_catch_left_brace = true 167 | ij_java_space_before_catch_parentheses = true 168 | ij_java_space_before_class_left_brace = true 169 | ij_java_space_before_colon = true 170 | ij_java_space_before_colon_in_foreach = true 171 | ij_java_space_before_comma = false 172 | ij_java_space_before_do_left_brace = true 173 | ij_java_space_before_else_keyword = true 174 | ij_java_space_before_else_left_brace = true 175 | ij_java_space_before_finally_keyword = true 176 | ij_java_space_before_finally_left_brace = true 177 | ij_java_space_before_for_left_brace = true 178 | ij_java_space_before_for_parentheses = true 179 | ij_java_space_before_for_semicolon = false 180 | ij_java_space_before_if_left_brace = true 181 | ij_java_space_before_if_parentheses = true 182 | ij_java_space_before_method_call_parentheses = false 183 | ij_java_space_before_method_left_brace = true 184 | ij_java_space_before_method_parentheses = false 185 | ij_java_space_before_opening_angle_bracket_in_type_parameter = false 186 | ij_java_space_before_quest = true 187 | ij_java_space_before_switch_left_brace = true 188 | ij_java_space_before_switch_parentheses = true 189 | ij_java_space_before_synchronized_left_brace = true 190 | ij_java_space_before_synchronized_parentheses = true 191 | ij_java_space_before_try_left_brace = true 192 | ij_java_space_before_try_parentheses = true 193 | ij_java_space_before_type_parameter_list = false 194 | ij_java_space_before_while_keyword = true 195 | ij_java_space_before_while_left_brace = true 196 | ij_java_space_before_while_parentheses = true 197 | ij_java_space_inside_one_line_enum_braces = false 198 | ij_java_space_within_empty_array_initializer_braces = false 199 | ij_java_space_within_empty_method_call_parentheses = false 200 | ij_java_space_within_empty_method_parentheses = false 201 | ij_java_spaces_around_additive_operators = true 202 | ij_java_spaces_around_assignment_operators = true 203 | ij_java_spaces_around_bitwise_operators = true 204 | ij_java_spaces_around_equality_operators = true 205 | ij_java_spaces_around_lambda_arrow = true 206 | ij_java_spaces_around_logical_operators = true 207 | ij_java_spaces_around_method_ref_dbl_colon = false 208 | ij_java_spaces_around_multiplicative_operators = true 209 | ij_java_spaces_around_relational_operators = true 210 | ij_java_spaces_around_shift_operators = true 211 | ij_java_spaces_around_type_bounds_in_type_parameters = true 212 | ij_java_spaces_around_unary_operator = false 213 | ij_java_spaces_within_angle_brackets = false 214 | ij_java_spaces_within_annotation_parentheses = false 215 | ij_java_spaces_within_array_initializer_braces = false 216 | ij_java_spaces_within_braces = false 217 | ij_java_spaces_within_brackets = false 218 | ij_java_spaces_within_cast_parentheses = false 219 | ij_java_spaces_within_catch_parentheses = false 220 | ij_java_spaces_within_for_parentheses = false 221 | ij_java_spaces_within_if_parentheses = false 222 | ij_java_spaces_within_method_call_parentheses = false 223 | ij_java_spaces_within_method_parentheses = false 224 | ij_java_spaces_within_parentheses = false 225 | ij_java_spaces_within_switch_parentheses = false 226 | ij_java_spaces_within_synchronized_parentheses = false 227 | ij_java_spaces_within_try_parentheses = false 228 | ij_java_spaces_within_while_parentheses = false 229 | ij_java_special_else_if_treatment = true 230 | ij_java_subclass_name_suffix = Impl 231 | ij_java_ternary_operation_signs_on_next_line = true 232 | ij_java_ternary_operation_wrap = normal 233 | ij_java_test_name_suffix = Test 234 | ij_java_throws_keyword_wrap = normal 235 | ij_java_throws_list_wrap = normal 236 | ij_java_use_external_annotations = false 237 | ij_java_use_fq_class_names = false 238 | ij_java_use_relative_indents = false 239 | ij_java_use_single_class_imports = true 240 | ij_java_variable_annotation_wrap = normal 241 | ij_java_visibility = public 242 | ij_java_while_brace_force = always 243 | ij_java_while_on_new_line = false 244 | ij_java_wrap_comments = false 245 | ij_java_wrap_first_method_in_call_chain = false 246 | ij_java_wrap_long_lines = true 247 | 248 | [*.properties] 249 | ij_properties_align_group_field_declarations = false 250 | ij_properties_keep_blank_lines = false 251 | ij_properties_key_value_delimiter = equals 252 | ij_properties_spaces_around_key_value_delimiter = false 253 | 254 | [.editorconfig] 255 | ij_editorconfig_align_group_field_declarations = false 256 | ij_editorconfig_space_after_colon = false 257 | ij_editorconfig_space_after_comma = true 258 | ij_editorconfig_space_before_colon = false 259 | ij_editorconfig_space_before_comma = false 260 | ij_editorconfig_spaces_around_assignment_operators = true 261 | 262 | [{*.ant, *.fxml, *.jhm, *.jnlp, *.jrxml, *.jspx, *.pom, *.rng, *.tagx, *.tld, *.wsdl, *.xml, *.xsd, *.xsl, *.xslt, *.xul}] 263 | ij_xml_align_attributes = true 264 | ij_xml_align_text = false 265 | ij_xml_attribute_wrap = normal 266 | ij_xml_block_comment_at_first_column = true 267 | ij_xml_keep_blank_lines = 2 268 | ij_xml_keep_indents_on_empty_lines = false 269 | ij_xml_keep_line_breaks = true 270 | ij_xml_keep_line_breaks_in_text = true 271 | ij_xml_keep_whitespaces = false 272 | ij_xml_keep_whitespaces_around_cdata = preserve 273 | ij_xml_keep_whitespaces_inside_cdata = false 274 | ij_xml_line_comment_at_first_column = true 275 | ij_xml_space_after_tag_name = false 276 | ij_xml_space_around_equals_in_attribute = false 277 | ij_xml_space_inside_empty_tag = false 278 | ij_xml_text_wrap = normal 279 | 280 | [{*.bash, *.sh, *.zsh}] 281 | indent_size = 2 282 | tab_width = 2 283 | ij_shell_binary_ops_start_line = false 284 | ij_shell_keep_column_alignment_padding = false 285 | ij_shell_minify_program = false 286 | ij_shell_redirect_followed_by_space = false 287 | ij_shell_switch_cases_indented = false 288 | 289 | [{*.gant, *.gradle, *.groovy, *.gy}] 290 | ij_groovy_align_group_field_declarations = false 291 | ij_groovy_align_multiline_array_initializer_expression = false 292 | ij_groovy_align_multiline_assignment = false 293 | ij_groovy_align_multiline_binary_operation = false 294 | ij_groovy_align_multiline_chained_methods = false 295 | ij_groovy_align_multiline_extends_list = false 296 | ij_groovy_align_multiline_for = true 297 | ij_groovy_align_multiline_list_or_map = true 298 | ij_groovy_align_multiline_method_parentheses = false 299 | ij_groovy_align_multiline_parameters = true 300 | ij_groovy_align_multiline_parameters_in_calls = false 301 | ij_groovy_align_multiline_resources = true 302 | ij_groovy_align_multiline_ternary_operation = false 303 | ij_groovy_align_multiline_throws_list = false 304 | ij_groovy_align_named_args_in_map = true 305 | ij_groovy_align_throws_keyword = false 306 | ij_groovy_array_initializer_new_line_after_left_brace = false 307 | ij_groovy_array_initializer_right_brace_on_new_line = false 308 | ij_groovy_array_initializer_wrap = off 309 | ij_groovy_assert_statement_wrap = off 310 | ij_groovy_assignment_wrap = off 311 | ij_groovy_binary_operation_wrap = off 312 | ij_groovy_blank_lines_after_class_header = 0 313 | ij_groovy_blank_lines_after_imports = 1 314 | ij_groovy_blank_lines_after_package = 1 315 | ij_groovy_blank_lines_around_class = 1 316 | ij_groovy_blank_lines_around_field = 0 317 | ij_groovy_blank_lines_around_field_in_interface = 0 318 | ij_groovy_blank_lines_around_method = 1 319 | ij_groovy_blank_lines_around_method_in_interface = 1 320 | ij_groovy_blank_lines_before_imports = 1 321 | ij_groovy_blank_lines_before_method_body = 0 322 | ij_groovy_blank_lines_before_package = 0 323 | ij_groovy_block_brace_style = end_of_line 324 | ij_groovy_block_comment_at_first_column = true 325 | ij_groovy_call_parameters_new_line_after_left_paren = false 326 | ij_groovy_call_parameters_right_paren_on_new_line = false 327 | ij_groovy_call_parameters_wrap = off 328 | ij_groovy_catch_on_new_line = false 329 | ij_groovy_class_annotation_wrap = split_into_lines 330 | ij_groovy_class_brace_style = end_of_line 331 | ij_groovy_class_count_to_use_import_on_demand = 5 332 | ij_groovy_do_while_brace_force = never 333 | ij_groovy_else_on_new_line = false 334 | ij_groovy_enum_constants_wrap = off 335 | ij_groovy_extends_keyword_wrap = off 336 | ij_groovy_extends_list_wrap = off 337 | ij_groovy_field_annotation_wrap = split_into_lines 338 | ij_groovy_finally_on_new_line = false 339 | ij_groovy_for_brace_force = never 340 | ij_groovy_for_statement_new_line_after_left_paren = false 341 | ij_groovy_for_statement_right_paren_on_new_line = false 342 | ij_groovy_for_statement_wrap = off 343 | ij_groovy_if_brace_force = never 344 | ij_groovy_import_annotation_wrap = 2 345 | ij_groovy_indent_case_from_switch = true 346 | ij_groovy_indent_label_blocks = true 347 | ij_groovy_insert_inner_class_imports = false 348 | ij_groovy_keep_blank_lines_before_right_brace = 2 349 | ij_groovy_keep_blank_lines_in_code = 2 350 | ij_groovy_keep_blank_lines_in_declarations = 2 351 | ij_groovy_keep_control_statement_in_one_line = true 352 | ij_groovy_keep_first_column_comment = true 353 | ij_groovy_keep_indents_on_empty_lines = false 354 | ij_groovy_keep_line_breaks = true 355 | ij_groovy_keep_multiple_expressions_in_one_line = false 356 | ij_groovy_keep_simple_blocks_in_one_line = false 357 | ij_groovy_keep_simple_classes_in_one_line = true 358 | ij_groovy_keep_simple_lambdas_in_one_line = true 359 | ij_groovy_keep_simple_methods_in_one_line = true 360 | ij_groovy_label_indent_absolute = false 361 | ij_groovy_label_indent_size = 0 362 | ij_groovy_lambda_brace_style = end_of_line 363 | ij_groovy_layout_static_imports_separately = true 364 | ij_groovy_line_comment_add_space = false 365 | ij_groovy_line_comment_at_first_column = true 366 | ij_groovy_method_annotation_wrap = split_into_lines 367 | ij_groovy_method_brace_style = end_of_line 368 | ij_groovy_method_call_chain_wrap = off 369 | ij_groovy_method_parameters_new_line_after_left_paren = false 370 | ij_groovy_method_parameters_right_paren_on_new_line = false 371 | ij_groovy_method_parameters_wrap = off 372 | ij_groovy_modifier_list_wrap = false 373 | ij_groovy_names_count_to_use_import_on_demand = 3 374 | ij_groovy_parameter_annotation_wrap = off 375 | ij_groovy_parentheses_expression_new_line_after_left_paren = false 376 | ij_groovy_parentheses_expression_right_paren_on_new_line = false 377 | ij_groovy_prefer_parameters_wrap = false 378 | ij_groovy_resource_list_new_line_after_left_paren = false 379 | ij_groovy_resource_list_right_paren_on_new_line = false 380 | ij_groovy_resource_list_wrap = off 381 | ij_groovy_space_after_assert_separator = true 382 | ij_groovy_space_after_colon = true 383 | ij_groovy_space_after_comma = true 384 | ij_groovy_space_after_comma_in_type_arguments = true 385 | ij_groovy_space_after_for_semicolon = true 386 | ij_groovy_space_after_quest = true 387 | ij_groovy_space_after_type_cast = true 388 | ij_groovy_space_before_annotation_parameter_list = false 389 | ij_groovy_space_before_array_initializer_left_brace = false 390 | ij_groovy_space_before_assert_separator = false 391 | ij_groovy_space_before_catch_keyword = true 392 | ij_groovy_space_before_catch_left_brace = true 393 | ij_groovy_space_before_catch_parentheses = true 394 | ij_groovy_space_before_class_left_brace = true 395 | ij_groovy_space_before_closure_left_brace = true 396 | ij_groovy_space_before_colon = true 397 | ij_groovy_space_before_comma = false 398 | ij_groovy_space_before_do_left_brace = true 399 | ij_groovy_space_before_else_keyword = true 400 | ij_groovy_space_before_else_left_brace = true 401 | ij_groovy_space_before_finally_keyword = true 402 | ij_groovy_space_before_finally_left_brace = true 403 | ij_groovy_space_before_for_left_brace = true 404 | ij_groovy_space_before_for_parentheses = true 405 | ij_groovy_space_before_for_semicolon = false 406 | ij_groovy_space_before_if_left_brace = true 407 | ij_groovy_space_before_if_parentheses = true 408 | ij_groovy_space_before_method_call_parentheses = false 409 | ij_groovy_space_before_method_left_brace = true 410 | ij_groovy_space_before_method_parentheses = false 411 | ij_groovy_space_before_quest = true 412 | ij_groovy_space_before_switch_left_brace = true 413 | ij_groovy_space_before_switch_parentheses = true 414 | ij_groovy_space_before_synchronized_left_brace = true 415 | ij_groovy_space_before_synchronized_parentheses = true 416 | ij_groovy_space_before_try_left_brace = true 417 | ij_groovy_space_before_try_parentheses = true 418 | ij_groovy_space_before_while_keyword = true 419 | ij_groovy_space_before_while_left_brace = true 420 | ij_groovy_space_before_while_parentheses = true 421 | ij_groovy_space_in_named_argument = true 422 | ij_groovy_space_in_named_argument_before_colon = false 423 | ij_groovy_space_within_empty_array_initializer_braces = false 424 | ij_groovy_space_within_empty_method_call_parentheses = false 425 | ij_groovy_spaces_around_additive_operators = true 426 | ij_groovy_spaces_around_assignment_operators = true 427 | ij_groovy_spaces_around_bitwise_operators = true 428 | ij_groovy_spaces_around_equality_operators = true 429 | ij_groovy_spaces_around_lambda_arrow = true 430 | ij_groovy_spaces_around_logical_operators = true 431 | ij_groovy_spaces_around_multiplicative_operators = true 432 | ij_groovy_spaces_around_regex_operators = true 433 | ij_groovy_spaces_around_relational_operators = true 434 | ij_groovy_spaces_around_shift_operators = true 435 | ij_groovy_spaces_within_annotation_parentheses = false 436 | ij_groovy_spaces_within_array_initializer_braces = false 437 | ij_groovy_spaces_within_braces = true 438 | ij_groovy_spaces_within_brackets = false 439 | ij_groovy_spaces_within_cast_parentheses = false 440 | ij_groovy_spaces_within_catch_parentheses = false 441 | ij_groovy_spaces_within_for_parentheses = false 442 | ij_groovy_spaces_within_gstring_injection_braces = false 443 | ij_groovy_spaces_within_if_parentheses = false 444 | ij_groovy_spaces_within_list_or_map = false 445 | ij_groovy_spaces_within_method_call_parentheses = false 446 | ij_groovy_spaces_within_method_parentheses = false 447 | ij_groovy_spaces_within_parentheses = false 448 | ij_groovy_spaces_within_switch_parentheses = false 449 | ij_groovy_spaces_within_synchronized_parentheses = false 450 | ij_groovy_spaces_within_try_parentheses = false 451 | ij_groovy_spaces_within_tuple_expression = false 452 | ij_groovy_spaces_within_while_parentheses = false 453 | ij_groovy_special_else_if_treatment = true 454 | ij_groovy_ternary_operation_wrap = off 455 | ij_groovy_throws_keyword_wrap = off 456 | ij_groovy_throws_list_wrap = off 457 | ij_groovy_use_flying_geese_braces = false 458 | ij_groovy_use_fq_class_names = false 459 | ij_groovy_use_fq_class_names_in_javadoc = true 460 | ij_groovy_use_relative_indents = false 461 | ij_groovy_use_single_class_imports = true 462 | ij_groovy_variable_annotation_wrap = off 463 | ij_groovy_while_brace_force = never 464 | ij_groovy_while_on_new_line = false 465 | ij_groovy_wrap_long_lines = false 466 | 467 | [{*.har, *.json}] 468 | indent_size = 2 469 | ij_json_keep_blank_lines_in_code = 0 470 | ij_json_keep_indents_on_empty_lines = false 471 | ij_json_keep_line_breaks = true 472 | ij_json_space_after_colon = true 473 | ij_json_space_after_comma = true 474 | ij_json_space_before_colon = true 475 | ij_json_space_before_comma = false 476 | ij_json_spaces_within_braces = false 477 | ij_json_spaces_within_brackets = false 478 | ij_json_wrap_long_lines = false 479 | 480 | [{*.htm, *.html, *.sht, *.shtm, *.shtml}] 481 | ij_html_add_new_line_before_tags = body, div, p, form, h1, h2, h3 482 | ij_html_align_attributes = true 483 | ij_html_align_text = false 484 | ij_html_attribute_wrap = normal 485 | ij_html_block_comment_at_first_column = true 486 | ij_html_do_not_align_children_of_min_lines = 0 487 | ij_html_do_not_break_if_inline_tags = title, h1, h2, h3, h4, h5, h6, p 488 | ij_html_do_not_indent_children_of_tags = html, body, thead, tbody, tfoot 489 | ij_html_enforce_quotes = false 490 | ij_html_inline_tags = a, abbr, acronym, b, basefont, bdo, big, br, cite, cite, code, dfn, em, font, i, img, input, kbd, label, q, s, samp, select, small, span, strike, strong, sub, sup, textarea, tt, u, var 491 | ij_html_keep_blank_lines = 2 492 | ij_html_keep_indents_on_empty_lines = false 493 | ij_html_keep_line_breaks = true 494 | ij_html_keep_line_breaks_in_text = true 495 | ij_html_keep_whitespaces = false 496 | ij_html_keep_whitespaces_inside = span, pre, textarea 497 | ij_html_line_comment_at_first_column = true 498 | ij_html_new_line_after_last_attribute = never 499 | ij_html_new_line_before_first_attribute = never 500 | ij_html_quote_style = double 501 | ij_html_remove_new_line_before_tags = br 502 | ij_html_space_after_tag_name = false 503 | ij_html_space_around_equality_in_attribute = false 504 | ij_html_space_inside_empty_tag = false 505 | ij_html_text_wrap = normal 506 | 507 | [{*.yaml, *.yml}] 508 | indent_size = 2 509 | ij_yaml_keep_indents_on_empty_lines = false 510 | ij_yaml_keep_line_breaks = true 511 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yaml: -------------------------------------------------------------------------------- 1 | name: Build Plugin JAR File 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: 7 | - created 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | submodules: true 16 | - name: Set up JDK 17 17 | uses: actions/setup-java@v2 18 | with: 19 | distribution: 'temurin' 20 | cache: 'gradle' 21 | java-version: 17 22 | - name: Set up Node.js 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: 18 26 | - uses: pnpm/action-setup@v2.0.1 27 | name: Install pnpm 28 | id: pnpm-install 29 | with: 30 | version: 8 31 | run_install: false 32 | - name: Get pnpm store directory 33 | id: pnpm-cache 34 | run: | 35 | echo "::set-output name=pnpm_cache_dir::$(pnpm store path)" 36 | - uses: actions/cache@v3 37 | name: Setup pnpm cache 38 | with: 39 | path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} 40 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/console/pnpm-lock.yaml') }} 41 | restore-keys: | 42 | ${{ runner.os }}-pnpm-store- 43 | - name: Install Frontend Dependencies 44 | run: | 45 | ./gradlew pnpmInstall 46 | - name: Build with Gradle 47 | run: | 48 | # Set the version with tag name when releasing 49 | version=${{ github.event.release.tag_name }} 50 | version=${version#v} 51 | sed -i "s/version=.*-SNAPSHOT$/version=$version/1" gradle.properties 52 | ./gradlew clean build -x test 53 | - name: Archive halo-plugin-sitepush jar 54 | uses: actions/upload-artifact@v2 55 | with: 56 | name: halo-plugin-sitepush 57 | path: | 58 | build/libs/*.jar 59 | retention-days: 1 60 | 61 | github-release: 62 | runs-on: ubuntu-latest 63 | needs: build 64 | if: github.event_name == 'release' 65 | steps: 66 | - name: Download halo-plugin-sitepush jar 67 | uses: actions/download-artifact@v2 68 | with: 69 | name: halo-plugin-sitepush 70 | path: build/libs 71 | - name: Get Name of Artifact 72 | id: get_artifact 73 | run: | 74 | ARTIFACT_PATHNAME=$(ls build/libs/*.jar | head -n 1) 75 | ARTIFACT_NAME=$(basename ${ARTIFACT_PATHNAME}) 76 | echo "Artifact pathname: ${ARTIFACT_PATHNAME}" 77 | echo "Artifact name: ${ARTIFACT_NAME}" 78 | echo "ARTIFACT_PATHNAME=${ARTIFACT_PATHNAME}" >> $GITHUB_ENV 79 | echo "ARTIFACT_NAME=${ARTIFACT_NAME}" >> $GITHUB_ENV 80 | echo "RELEASE_ID=${{ github.event.release.id }}" >> $GITHUB_ENV 81 | - name: Upload a Release Asset 82 | uses: actions/github-script@v2 83 | if: github.event_name == 'release' 84 | with: 85 | github-token: ${{secrets.GITHUB_TOKEN}} 86 | script: | 87 | console.log('environment', process.versions); 88 | 89 | const fs = require('fs').promises; 90 | 91 | const { repo: { owner, repo }, sha } = context; 92 | console.log({ owner, repo, sha }); 93 | 94 | const releaseId = process.env.RELEASE_ID 95 | const artifactPathName = process.env.ARTIFACT_PATHNAME 96 | const artifactName = process.env.ARTIFACT_NAME 97 | console.log('Releasing', releaseId, artifactPathName, artifactName) 98 | 99 | await github.repos.uploadReleaseAsset({ 100 | owner, repo, 101 | release_id: releaseId, 102 | name: artifactName, 103 | data: await fs.readFile(artifactPathName) 104 | }); 105 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Maven 2 | target/ 3 | logs/ 4 | !.mvn/wrapper/maven-wrapper.jar 5 | 6 | ### Gradle 7 | .gradle 8 | /build/ 9 | /out/ 10 | !gradle/wrapper/gradle-wrapper.jar 11 | bin/ 12 | 13 | ### STS ### 14 | .apt_generated 15 | .classpath 16 | .factorypath 17 | .project 18 | .settings 19 | .springBeans 20 | .sts4-cache 21 | 22 | ### IntelliJ IDEA ### 23 | .idea 24 | *.iws 25 | *.iml 26 | *.ipr 27 | log/ 28 | 29 | ### NetBeans ### 30 | nbproject/private/ 31 | build/ 32 | nbbuild/ 33 | dist/ 34 | nbdist/ 35 | .nb-gradle/ 36 | 37 | ### Mac 38 | .DS_Store 39 | */.DS_Store 40 | 41 | ### VS Code ### 42 | *.project 43 | *.factorypath 44 | 45 | ### Compiled class file 46 | *.class 47 | 48 | ### Log file 49 | *.log 50 | 51 | ### BlueJ files 52 | *.ctxt 53 | 54 | ### Mobile Tools for Java (J2ME) 55 | .mtj.tmp/ 56 | 57 | ### Package Files 58 | *.war 59 | *.nar 60 | *.ear 61 | *.zip 62 | *.tar.gz 63 | *.rar 64 | 65 | ### VSCode 66 | .vscode 67 | 68 | ### Local file 69 | application-local.yml 70 | application-local.yaml 71 | application-local.properties 72 | 73 | /admin-frontend/node_modules/ 74 | /workplace/ 75 | /src/main/resources/console/ 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | reviewers: 2 | - ruibaby 3 | - guqing 4 | - JohnNiang 5 | - wangzhen-fit2cloud 6 | 7 | approvers: 8 | - ruibaby 9 | - guqing 10 | - JohnNiang 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # halo-plugin-sitepush 2 | 3 | Halo 2.x 搜索引擎主动推送插件 4 | 5 | ## 功能简介 6 | 该插件可将站点内文章模块、页面模块的链接,推送至各大搜索引擎收录平台 7 | 8 | ### 功能特性 9 | - 支持站点收录平台的验证 10 | - 支持推送站点链接至各大搜索引擎收录平台 11 | 12 | ### 当前支持的收录平台: 13 | - [x] [百度收录](https://ziyuan.baidu.com) 14 | - [x] [必应(bing)收录](https://www.bing.com/webmasters) 15 | - [x] [谷歌(google)收录](https://search.google.com/search-console/) 16 | - [ ] 更多 (欢迎PR) 17 | 18 | ## 安装方式 19 | - 在 [release页面](https://github.com/Stonewuu/halo-plugin-sitepush/releases) 下载最新的 JAR 文件。 20 | - 在 Halo 后台的插件管理上传 JAR 文件进行安装。 21 | 22 | ## 使用说明 23 | - 安装完成后,在 Halo 后台的 `插件` -> `站点推送插件` 配置页面,进行配置。 24 | - 推送的时机是:插件启动时、 页面与文章的发布时 25 | - 谷歌推送需要网络能访问谷歌,其他推送同理 26 | 27 | ![插件截图](https://github.com/Erzbir/halo-plugin-sitepush/assets/100007608/0f258f18-1e2d-4d6d-b7ca-7c8aee8ffc9f) 28 | 29 | ## 配置说明 30 | - 如果是通过 dns 记录或者其他方式已经认证,则不需要网站验证码 31 | - 此插件的推送支持代理(支持无认证和 basic auth),配置此项之后需确保代理可用 32 | 33 | 34 | 35 | ## 参与开发 36 | 37 | 插件开发的详细文档请查阅: 38 | 39 | ```bash 40 | git clone https://github.com/Stonewuu/halo-plugin-sitepush.git 41 | 42 | # 或者当你 fork 之后 43 | 44 | git clone https://github.com/{your_github_id}/halo-plugin-sitepush.git 45 | ``` 46 | 47 | ```bash 48 | cd path/to/halo-plugin-sitepush 49 | ``` 50 | 51 | ```bash 52 | # macOS / Linux 53 | ./gradlew build 54 | 55 | # Windows 56 | ./gradlew.bat build 57 | ``` 58 | 59 | 修改 Halo 配置文件: 60 | 61 | ```yaml 62 | halo: 63 | plugin: 64 | runtime-mode: development 65 | fixedPluginPath: 66 | - "/path/to/halo-plugin-sitepush" 67 | ``` 68 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id "com.github.node-gradle.node" version "5.0.0" 4 | id "io.freefair.lombok" version "8.0.1" 5 | id "run.halo.plugin.devtools" version "0.0.7" 6 | } 7 | 8 | group 'run.halo.sitepush' 9 | sourceCompatibility = JavaVersion.VERSION_17 10 | 11 | repositories { 12 | mavenCentral() 13 | maven { url 'https://s01.oss.sonatype.org/content/repositories/releases' } 14 | maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' } 15 | maven { url 'https://repo.spring.io/milestone' } 16 | } 17 | 18 | dependencies { 19 | implementation platform('run.halo.tools.platform:plugin:2.12.0-SNAPSHOT') 20 | compileOnly 'run.halo.app:api' 21 | testImplementation 'run.halo.app:api' 22 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 23 | } 24 | 25 | test { 26 | useJUnitPlatform() 27 | } 28 | 29 | node { 30 | nodeProjectDir = file("${project.projectDir}/console") 31 | } 32 | 33 | task buildFrontend(type: PnpmTask) { 34 | args = ['build'] 35 | } 36 | 37 | build { 38 | // build frontend before build 39 | tasks.getByName('compileJava').dependsOn('buildFrontend') 40 | tasks.withType(JavaCompile) { 41 | options.encoding = "UTF-8" 42 | } 43 | } 44 | 45 | halo { 46 | version = "2.12" 47 | } -------------------------------------------------------------------------------- /console/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = false 12 | insert_final_newline = true -------------------------------------------------------------------------------- /console/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require("@rushstack/eslint-patch/modern-module-resolution"); 3 | 4 | module.exports = { 5 | root: true, 6 | extends: [ 7 | "plugin:vue/vue3-recommended", 8 | "eslint:recommended", 9 | "@vue/eslint-config-typescript/recommended", 10 | "@vue/eslint-config-prettier", 11 | ], 12 | env: { 13 | "vue/setup-compiler-macros": true, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /console/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /console/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module "*.vue" { 4 | import Vue from "vue"; 5 | export default Vue; 6 | } 7 | -------------------------------------------------------------------------------- /console/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@halo-dev/halo-plugin-sitepush", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite build --watch --mode=development", 7 | "build": "vite build", 8 | "preview": "vite preview --port 4173", 9 | "test:unit": "vitest --environment jsdom", 10 | "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false", 11 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" 12 | }, 13 | "dependencies": { 14 | "@halo-dev/api-client": "^2.12.0", 15 | "@halo-dev/components": "^1.10.0", 16 | "@halo-dev/console-shared": "^2.12.0", 17 | "@tanstack/vue-query": "^4.29.1", 18 | "@vueuse/core": "^10.7.1", 19 | "@vueuse/router": "^10.7.1", 20 | "axios": "^1.6.3", 21 | "dayjs": "^1.11.10", 22 | "javascript-time-ago": "^2.5.9", 23 | "vue": "^3.4.3" 24 | }, 25 | "devDependencies": { 26 | "@halo-dev/ui-plugin-bundler-kit": "^2.12.0", 27 | "@iconify/json": "^2.2.164", 28 | "@rushstack/eslint-patch": "^1.6.1", 29 | "@types/jsdom": "^20.0.1", 30 | "@types/node": "^16.18.69", 31 | "@vitejs/plugin-vue": "^3.2.0", 32 | "@vitejs/plugin-vue-jsx": "^2.1.1", 33 | "@vue/eslint-config-prettier": "^7.1.0", 34 | "@vue/eslint-config-typescript": "^11.0.3", 35 | "@vue/test-utils": "^2.4.3", 36 | "@vue/tsconfig": "^0.1.3", 37 | "eslint": "^8.56.0", 38 | "eslint-plugin-vue": "^9.19.2", 39 | "jsdom": "^19.0.0", 40 | "npm-run-all": "^4.1.5", 41 | "prettier": "^2.8.8", 42 | "sass": "^1.69.6", 43 | "typescript": "~4.7.4", 44 | "unplugin-icons": "^0.15.3", 45 | "vite": "^3.2.7", 46 | "vitest": "^0.24.5", 47 | "vue-tsc": "^1.8.27" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /console/src/api/pushlog/pushlog.ts: -------------------------------------------------------------------------------- 1 | import request from "@/api/request"; 2 | import type { PushLogs, PushLogsList } from "@/types"; 3 | 4 | export const getLogListApi = (params: object) => { 5 | return request>({ 6 | url: "/apis/sitepush.halo.run/v1alpha1/pushLogs", 7 | method: "get", 8 | params, 9 | }); 10 | }; 11 | 12 | export const clearAllLogApi = () => { 13 | return request({ 14 | url: "/apis/api.plugin.halo.run/v1alpha1/plugins/PluginSitePush/pushLogs/clear", 15 | method: "delete", 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /console/src/api/request.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosInstance } from "axios"; 2 | import axios from "axios"; 3 | 4 | const service: AxiosInstance = axios.create({ 5 | baseURL: "/", 6 | timeout: 30 * 1000, 7 | }); 8 | 9 | export default service; 10 | -------------------------------------------------------------------------------- /console/src/components/PushLogListItem.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 118 | -------------------------------------------------------------------------------- /console/src/index.ts: -------------------------------------------------------------------------------- 1 | import { definePlugin } from "@halo-dev/console-shared"; 2 | import { IconUpload } from "@halo-dev/components"; 3 | import { markRaw } from "vue"; 4 | import PushLog from "@/views/PushLog.vue"; 5 | 6 | export default definePlugin({ 7 | components: {}, 8 | routes: [ 9 | { 10 | parentName: "ToolsRoot", 11 | route: { 12 | path: "site-push-log", 13 | name: "SitePushLog", 14 | component: PushLog, 15 | meta: { 16 | title: "站点收录推送", 17 | searchable: true, 18 | description: "查阅站点收录推送日志", 19 | permissions: ["plugin:sitepush:view"], 20 | menu: { 21 | name: "站点收录推送", 22 | group: "tool", 23 | icon: markRaw(IconUpload), 24 | priority: 0, 25 | }, 26 | }, 27 | }, 28 | }, 29 | ], 30 | extensionPoints: {}, 31 | }); 32 | -------------------------------------------------------------------------------- /console/src/types/index.ts: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "@halo-dev/api-client"; 2 | 3 | /** 4 | * 5 | */ 6 | export interface PushLogs { 7 | createTime: number; 8 | pushUrl: string; 9 | pushType: "baidu" | "bing" | "google"; 10 | pushStatus: number; 11 | apiVersion: "todo.plugin.halo.run/v1alpha1"; // apiVersion=自定义模型的 group/version 12 | kind: "PushLog"; 13 | metadata: Metadata; 14 | remark: string; 15 | } 16 | 17 | /** 18 | * 19 | */ 20 | export interface PushLogsList { 21 | page: number; 22 | size: number; 23 | total: number; 24 | items: Array; 25 | first: boolean; 26 | last: boolean; 27 | hasNext: boolean; 28 | hasPrevious: boolean; 29 | totalPages: number; 30 | } 31 | -------------------------------------------------------------------------------- /console/src/utils/date.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | import "dayjs/locale/zh-cn"; 3 | import timezone from "dayjs/plugin/timezone"; 4 | import TimeAgo from "javascript-time-ago"; 5 | import zh from "javascript-time-ago/locale/zh"; 6 | 7 | dayjs.extend(timezone); 8 | 9 | dayjs.locale("zh-cn"); 10 | 11 | TimeAgo.addDefaultLocale(zh); 12 | 13 | export function formatDatetime( 14 | date: string | number | Date | undefined | null 15 | ): string { 16 | if (!date) { 17 | return ""; 18 | } 19 | return dayjs(date).format("YYYY-MM-DD HH:mm"); 20 | } 21 | 22 | export function timeAgo( 23 | date: string | Date | number | undefined | null 24 | ): string { 25 | if (!date) { 26 | return ""; 27 | } 28 | 29 | const currentDate = new Date(); 30 | const inputDate = new Date(date); 31 | 32 | // 365天 * 24小时 * 60分钟 * 60秒 * 1000毫秒 33 | const oneYearInMilliseconds = 365 * 24 * 60 * 60 * 1000; 34 | 35 | if (currentDate.getTime() - inputDate.getTime() > oneYearInMilliseconds) { 36 | return dayjs(date).format("YYYY-MM-DD"); 37 | } 38 | 39 | const timeAgo = new TimeAgo("zh"); 40 | 41 | return timeAgo.format(new Date(date)); 42 | } 43 | -------------------------------------------------------------------------------- /console/src/views/PushLog.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 116 | -------------------------------------------------------------------------------- /console/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["./env.d.ts", "./src/**/*", "./src/**/*.vue"], 4 | "exclude": ["./src/**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "baseUrl": ".", 8 | "paths": { 9 | "@/*": ["./src/*"] 10 | }, 11 | "types": ["unplugin-icons/types/vue"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /console/tsconfig.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.node.json", 3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /console/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.config.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | }, 10 | { 11 | "path": "./tsconfig.vitest.json" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /console/tsconfig.vitest.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.app.json", 3 | "exclude": [], 4 | "compilerOptions": { 5 | "composite": true, 6 | "lib": [], 7 | "types": ["node", "jsdom"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /console/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from "url"; 2 | 3 | import { defineConfig } from "vite"; 4 | import Vue from "@vitejs/plugin-vue"; 5 | import VueJsx from "@vitejs/plugin-vue-jsx"; 6 | import Icons from "unplugin-icons/vite"; 7 | import { HaloUIPluginBundlerKit } from "@halo-dev/ui-plugin-bundler-kit"; 8 | 9 | export default defineConfig({ 10 | plugins: [ 11 | Vue(), 12 | VueJsx(), 13 | Icons({ compiler: "vue3" }), 14 | HaloUIPluginBundlerKit(), 15 | ], 16 | resolve: { 17 | alias: { 18 | "@": fileURLToPath(new URL("./src", import.meta.url)), 19 | }, 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=1.5.2 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stonewuu/halo-plugin-sitepush/8190e67136096b385390df4ba6c0c94149125bd1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed May 10 13:57:01 CST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | rootProject.name = 'halo-plugin-sitepush' 7 | 8 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/GlobalCache.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush; 2 | 3 | import com.stonewu.sitepush.scheme.PushUnique; 4 | import java.util.Map; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | public class GlobalCache { 8 | 9 | public static Map PUSH_CACHE = new ConcurrentHashMap<>(); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/SiteHeadProcessor.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush; 2 | 3 | import com.stonewu.sitepush.setting.BaiduPushSetting; 4 | import com.stonewu.sitepush.setting.BaiduSettingProvider; 5 | import com.stonewu.sitepush.setting.BingPushSetting; 6 | import com.stonewu.sitepush.setting.BingPushSettingProvider; 7 | import com.stonewu.sitepush.setting.GooglePushSetting; 8 | import com.stonewu.sitepush.setting.GooglePushSettingProvider; 9 | import com.stonewu.sitepush.setting.PushSettingProvider; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import lombok.Data; 13 | import org.springframework.scheduling.annotation.EnableScheduling; 14 | import org.springframework.scheduling.annotation.Scheduled; 15 | import org.springframework.stereotype.Component; 16 | import org.thymeleaf.context.ITemplateContext; 17 | import org.thymeleaf.model.IModel; 18 | import org.thymeleaf.model.IModelFactory; 19 | import org.thymeleaf.processor.element.IElementModelStructureHandler; 20 | import reactor.core.publisher.Flux; 21 | import reactor.core.publisher.Mono; 22 | import run.halo.app.plugin.ReactiveSettingFetcher; 23 | import run.halo.app.theme.dialect.TemplateHeadProcessor; 24 | 25 | 26 | @EnableScheduling 27 | @Component 28 | public class SiteHeadProcessor implements TemplateHeadProcessor { 29 | 30 | private final ReactiveSettingFetcher settingFetcher; 31 | private List settingProviders; 32 | 33 | public SiteHeadProcessor(ReactiveSettingFetcher settingFetcher) { 34 | this.settingFetcher = settingFetcher; 35 | initProvider(); 36 | } 37 | 38 | @Scheduled(cron = "0 0/1 * * * ?") 39 | private void initProvider() { 40 | settingProviders = Arrays.asList( 41 | new BaiduSettingProvider( 42 | settingFetcher.fetch(BaiduPushSetting.GROUP, BaiduPushSetting.class) 43 | .defaultIfEmpty(new BaiduPushSetting()).block()), 44 | new BingPushSettingProvider( 45 | settingFetcher.fetch(BingPushSetting.GROUP, BingPushSetting.class) 46 | .defaultIfEmpty(new BingPushSetting()).block()), 47 | new GooglePushSettingProvider( 48 | settingFetcher.fetch(GooglePushSetting.GROUP, GooglePushSetting.class) 49 | .defaultIfEmpty(new GooglePushSetting()).block()) 50 | ); 51 | } 52 | 53 | @Override 54 | public Mono process(ITemplateContext context, IModel model, 55 | IElementModelStructureHandler structureHandler) { 56 | final IModelFactory modelFactory = context.getModelFactory(); 57 | Flux fetchAndAddHeaders = Flux.fromIterable(settingProviders) 58 | .flatMap( 59 | provider -> { 60 | if (provider.isTagVerificationEnable()) { 61 | return settingFetcher.fetch(provider.getGroup(), provider.getSettingClass()) 62 | .map(config -> { 63 | model.add( 64 | modelFactory.createText( 65 | provider.getSiteVerificationMeta())); 66 | return Mono.empty(); 67 | }).defaultIfEmpty(Mono.empty()); 68 | } 69 | return Mono.empty(); 70 | } 71 | ); 72 | return Mono.when(fetchAndAddHeaders).then(); 73 | } 74 | 75 | @Data 76 | public static class BasicConfig { 77 | String siteVerification; 78 | } 79 | } -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/SitePushEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush; 2 | 3 | import com.stonewu.sitepush.service.PushService; 4 | import lombok.AllArgsConstructor; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | @AllArgsConstructor 9 | public class SitePushEndpoint { 10 | 11 | private PushService pushService; 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/SitePushPlugin.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush; 2 | 3 | import com.stonewu.sitepush.scheme.PushLog; 4 | import com.stonewu.sitepush.scheme.PushUnique; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.pf4j.PluginWrapper; 7 | import org.springframework.stereotype.Component; 8 | import reactor.core.publisher.Flux; 9 | import run.halo.app.extension.ReactiveExtensionClient; 10 | import run.halo.app.extension.Scheme; 11 | import run.halo.app.extension.SchemeManager; 12 | import run.halo.app.plugin.BasePlugin; 13 | 14 | /** 15 | * 站点收录推送插件 16 | * 17 | * @author stonewu 18 | * @since 1.0.0 19 | */ 20 | @Component 21 | @Slf4j 22 | public class SitePushPlugin extends BasePlugin { 23 | String pathPrefix = "/root/.halo2/"; 24 | private final SchemeManager schemeManager; 25 | private final ReactiveExtensionClient client; 26 | 27 | public SitePushPlugin(PluginWrapper wrapper, SchemeManager schemeManager, 28 | ReactiveExtensionClient client) { 29 | super(wrapper); 30 | this.schemeManager = schemeManager; 31 | this.client = client; 32 | } 33 | 34 | @Override 35 | public void start() { 36 | registerScheme(); 37 | loadPushedData(); 38 | } 39 | 40 | @Override 41 | public void stop() { 42 | GlobalCache.PUSH_CACHE.clear(); 43 | unregisterScheme(); 44 | } 45 | 46 | private void registerScheme() { 47 | schemeManager.register(PushLog.class); 48 | schemeManager.register(PushUnique.class); 49 | } 50 | 51 | private void unregisterScheme() { 52 | Scheme pushLogScheme = schemeManager.get(PushLog.class); 53 | Scheme pushUniqueScheme = schemeManager.get(PushUnique.class); 54 | schemeManager.unregister(pushLogScheme); 55 | schemeManager.unregister(pushUniqueScheme); 56 | } 57 | 58 | private void loadPushedData() { 59 | Flux fluxPushUnique = client.list(PushUnique.class, null, null); 60 | fluxPushUnique.doOnNext(pushUnique -> 61 | GlobalCache.PUSH_CACHE.put(pushUnique.getCacheKey(), pushUnique)) 62 | .subscribe(); 63 | log.info("成功读取已推送链接 {} 个", fluxPushUnique.count().block()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/job/CleanOldDataJob.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.job; 2 | 3 | import com.stonewu.sitepush.scheme.PushLog; 4 | import com.stonewu.sitepush.scheme.PushUnique; 5 | import com.stonewu.sitepush.setting.BasePushSetting; 6 | import lombok.AllArgsConstructor; 7 | import lombok.extern.log4j.Log4j; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.scheduling.annotation.EnableScheduling; 10 | import org.springframework.scheduling.annotation.Scheduled; 11 | import org.springframework.stereotype.Component; 12 | import reactor.core.publisher.Flux; 13 | import reactor.core.publisher.Mono; 14 | import run.halo.app.extension.ExtensionClient; 15 | import run.halo.app.extension.ListOptions; 16 | import run.halo.app.extension.ReactiveExtensionClient; 17 | import run.halo.app.extension.index.query.QueryFactory; 18 | import run.halo.app.extension.router.selector.FieldSelector; 19 | import run.halo.app.plugin.SettingFetcher; 20 | 21 | import java.time.Instant; 22 | import java.util.List; 23 | 24 | @Component 25 | @EnableScheduling 26 | @AllArgsConstructor 27 | @Slf4j 28 | public class CleanOldDataJob { 29 | private SettingFetcher settingFetcher; 30 | 31 | private ExtensionClient client; 32 | 33 | @Scheduled(cron = "0 0 0/1 * * ?") 34 | public void clean() { 35 | BasePushSetting basePushSetting = settingFetcher.fetch(BasePushSetting.GROUP, BasePushSetting.class) 36 | .orElseGet(BasePushSetting::new); 37 | Integer cleanOldDataDays = basePushSetting.getCleanOldLogDataDays(); 38 | Integer cleanOldUniqueDataDays = basePushSetting.getCleanOldUniqueDataDays(); 39 | if (cleanOldDataDays != null && cleanOldDataDays > 0) { 40 | long oldTime = Instant.now().getEpochSecond() - cleanOldDataDays * 24 * 60 * 60; 41 | Instant instant = Instant.ofEpochSecond(oldTime); 42 | ListOptions listOptions = getListOptions(instant); 43 | List pushLogs = client.listAll(PushLog.class, listOptions, null); 44 | pushLogs.forEach(log -> { 45 | client.delete(log); 46 | }); 47 | log.info("已清理 {} 之前的 {} 条log数据", instant, pushLogs.size()); 48 | } 49 | if (cleanOldUniqueDataDays != null && cleanOldUniqueDataDays > 0) { 50 | long oldTime = Instant.now().getEpochSecond() - cleanOldUniqueDataDays * 24 * 60 * 60; 51 | Instant instant = Instant.ofEpochSecond(oldTime); 52 | ListOptions listOptions = getListOptions(instant); 53 | List pushUniques = client.listAll(PushUnique.class, listOptions, null); 54 | pushUniques.forEach(unique -> { 55 | client.delete(unique); 56 | }); 57 | log.info("已清理 {} 之前的 {} 条key数据", instant, pushUniques.size()); 58 | } 59 | } 60 | 61 | private static ListOptions getListOptions(Instant instant) { 62 | FieldSelector fieldSelector = FieldSelector.of(QueryFactory.lessThan("metadata.creationTimestamp", String.valueOf(instant))); 63 | ListOptions listOptions = new ListOptions(); 64 | listOptions.setFieldSelector(fieldSelector); 65 | return listOptions; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/reconciler/AbstractPushReconciler.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.reconciler; 2 | 3 | import com.stonewu.sitepush.service.PushService; 4 | import com.stonewu.sitepush.setting.BasePushSetting; 5 | import java.time.Duration; 6 | import java.util.concurrent.atomic.AtomicBoolean; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.util.StringUtils; 10 | import run.halo.app.extension.ReactiveExtensionClient; 11 | import run.halo.app.extension.controller.Reconciler; 12 | import run.halo.app.plugin.SettingFetcher; 13 | 14 | /** 15 | * @author Erzbir 16 | * @Data: 2024/1/9 19:25 17 | */ 18 | @Slf4j 19 | public abstract class AbstractPushReconciler implements Reconciler { 20 | protected SettingFetcher settingFetcher; 21 | protected ReactiveExtensionClient client; 22 | protected PushService pushService; 23 | 24 | private final AtomicInteger currentReGetTimes = new AtomicInteger(0); 25 | private static final int MAX_GET_TIMES = 10; 26 | private final AtomicBoolean isFirst = new AtomicBoolean(true); 27 | 28 | public AbstractPushReconciler(SettingFetcher settingFetcher, ReactiveExtensionClient client, 29 | PushService pushService) { 30 | this.settingFetcher = settingFetcher; 31 | this.client = client; 32 | this.pushService = pushService; 33 | } 34 | 35 | public interface PublishExtension { 36 | boolean isPublished(); 37 | 38 | String getSlug(); 39 | 40 | String getPermalink(); 41 | 42 | String getKind(); 43 | 44 | boolean isVisible(); 45 | 46 | boolean isObserved(); 47 | } 48 | 49 | protected Result reconcile(PublishExtension extension) { 50 | 51 | if (extension == null) { 52 | return Result.doNotRetry(); 53 | } 54 | // 这里的写法为了保证引用的 SinglePage 或是其他 Extension 是完全初始化的 55 | if (extension.getPermalink() == null) { 56 | // 为了防止出现一直为 null 的情况 57 | if (currentReGetTimes.get() >= MAX_GET_TIMES) { 58 | return Result.doNotRetry(); 59 | } 60 | currentReGetTimes.addAndGet(1); 61 | return new Result(true, Duration.ofMillis(100)); 62 | } 63 | currentReGetTimes.set(0); 64 | 65 | BasePushSetting basePushSetting = getBasePushSetting(); 66 | if (shouldRetry(basePushSetting, extension) && basePushSetting.getRetryInterval() != 0) { 67 | return new Result(true, Duration.ofMinutes(basePushSetting.getRetryInterval())); 68 | } else { 69 | return Result.doNotRetry(); 70 | } 71 | } 72 | 73 | private BasePushSetting getBasePushSetting() { 74 | return settingFetcher.fetch(BasePushSetting.GROUP, BasePushSetting.class) 75 | .orElseGet(BasePushSetting::new); 76 | } 77 | 78 | private boolean shouldRetry(BasePushSetting basePushSetting, 79 | PublishExtension publishExtension) { 80 | if (basePushSetting.getEnable() && StringUtils.hasText(basePushSetting.getSiteUrl())) { 81 | try { 82 | if (publishExtension.isPublished() && publishExtension.isObserved() && publishExtension.isVisible()) { 83 | String slug = publishExtension.getSlug(); 84 | String permalink = publishExtension.getPermalink(); 85 | if (!checkIllegal(slug, permalink)) { 86 | return true; 87 | } 88 | String siteUrl = basePushSetting.getSiteUrl().trim(); 89 | boolean allPush = pushService.pushUseAllStrategy(siteUrl, 90 | publishExtension.getKind() + ":" + slug, permalink); 91 | return !allPush; 92 | } 93 | } catch (Throwable e) { 94 | log.error(e.getMessage()); 95 | return true; 96 | } 97 | } 98 | // 未启用不再重试,减少资源消耗 99 | return false; 100 | } 101 | 102 | private boolean checkIllegal(String key, String... pageLinks) { 103 | if (!StringUtils.hasText(key) || pageLinks == null || pageLinks.length == 0) { 104 | return false; 105 | } 106 | for (String pageLink : pageLinks) { 107 | if (!StringUtils.hasText(pageLink)) { 108 | return false; 109 | } 110 | } 111 | return true; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/reconciler/PostPushReconciler.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.reconciler; 2 | 3 | import com.stonewu.sitepush.service.PushService; 4 | import java.util.Optional; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Component; 7 | import run.halo.app.core.extension.content.Category; 8 | import run.halo.app.core.extension.content.Post; 9 | import run.halo.app.extension.ReactiveExtensionClient; 10 | import run.halo.app.extension.controller.Controller; 11 | import run.halo.app.extension.controller.ControllerBuilder; 12 | import run.halo.app.extension.controller.Reconciler; 13 | import run.halo.app.plugin.SettingFetcher; 14 | 15 | /** 16 | * Reconciler for {@link Category}. 17 | * 18 | * @author guqing 19 | * @since 2.0.0 20 | */ 21 | @Component 22 | @Slf4j 23 | public class PostPushReconciler extends AbstractPushReconciler 24 | implements Reconciler { 25 | 26 | public PostPushReconciler(SettingFetcher settingFetcher, ReactiveExtensionClient client, 27 | PushService pushService) { 28 | super(settingFetcher, client, pushService); 29 | } 30 | 31 | @Override 32 | public Result reconcile(Request request) { 33 | PublishExtension publishExtension = client.fetch(Post.class, request.name()) 34 | .blockOptional() 35 | .map(PostAdapter::new).orElseThrow(); 36 | return reconcile(publishExtension); 37 | } 38 | 39 | @Override 40 | public Controller setupWith(ControllerBuilder builder) { 41 | return builder 42 | .extension(new Post()) 43 | .build(); 44 | } 45 | 46 | private record PostAdapter(Post post) implements PublishExtension { 47 | 48 | @Override 49 | public boolean isPublished() { 50 | return post.isPublished(); 51 | } 52 | 53 | @Override 54 | public String getSlug() { 55 | return post.getSpec().getSlug(); 56 | } 57 | 58 | @Override 59 | public String getPermalink() { 60 | return post.getStatusOrDefault().getPermalink(); 61 | } 62 | 63 | @Override 64 | public String getKind() { 65 | return post.getKind(); 66 | } 67 | 68 | @Override 69 | public boolean isVisible() { 70 | return post.getSpec().getVisible().equals(Post.VisibleEnum.PUBLIC); 71 | } 72 | @Override 73 | public boolean isObserved() { 74 | if(post.getMetadata() != null && post.getStatusOrDefault() != null){ 75 | return post.getMetadata().getVersion().equals(post.getStatusOrDefault().getObservedVersion()); 76 | } 77 | return false; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/reconciler/SinglePagePushReconciler.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.reconciler; 2 | 3 | import com.stonewu.sitepush.service.PushService; 4 | import java.util.Optional; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Component; 7 | import run.halo.app.core.extension.content.Post; 8 | import run.halo.app.core.extension.content.SinglePage; 9 | import run.halo.app.extension.ReactiveExtensionClient; 10 | import run.halo.app.extension.controller.Controller; 11 | import run.halo.app.extension.controller.ControllerBuilder; 12 | import run.halo.app.extension.controller.Reconciler; 13 | import run.halo.app.plugin.SettingFetcher; 14 | 15 | /** 16 | * The {@link SinglePagePushReconciler} for route request to specific template page 17 | * .html. 18 | * 19 | * @author guqing 20 | * @since 2.0.0 21 | */ 22 | @Component 23 | @Slf4j 24 | public class SinglePagePushReconciler extends AbstractPushReconciler 25 | implements Reconciler { 26 | 27 | public SinglePagePushReconciler(SettingFetcher settingFetcher, ReactiveExtensionClient client, 28 | PushService pushService) { 29 | super(settingFetcher, client, pushService); 30 | } 31 | 32 | @Override 33 | public Controller setupWith(ControllerBuilder builder) { 34 | return builder 35 | .extension(new SinglePage()) 36 | .build(); 37 | } 38 | 39 | @Override 40 | public Result reconcile(Request request) { 41 | PublishExtension publishExtension = client.fetch(SinglePage.class, request.name()) 42 | .blockOptional() 43 | .map(SinglePageAdapter::new).orElseThrow(); 44 | return reconcile(publishExtension); 45 | } 46 | 47 | private record SinglePageAdapter(SinglePage singlePage) implements PublishExtension { 48 | 49 | @Override 50 | public boolean isPublished() { 51 | return singlePage.isPublished(); 52 | } 53 | 54 | @Override 55 | public String getSlug() { 56 | return singlePage.getSpec().getSlug(); 57 | } 58 | 59 | @Override 60 | public String getPermalink() { 61 | return singlePage.getStatusOrDefault().getPermalink(); 62 | } 63 | 64 | @Override 65 | public String getKind() { 66 | return singlePage.getKind(); 67 | } 68 | 69 | @Override 70 | public boolean isVisible() { 71 | return singlePage.getSpec().getVisible().equals(Post.VisibleEnum.PUBLIC); 72 | } 73 | 74 | @Override 75 | public boolean isObserved() { 76 | return singlePage.getMetadata().getVersion().equals(singlePage.getStatusOrDefault().getObservedVersion()); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/router/PushLogRouter.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.router; 2 | 3 | import com.stonewu.sitepush.scheme.PushLog; 4 | import lombok.AllArgsConstructor; 5 | import org.springframework.web.bind.annotation.DeleteMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | import reactor.core.publisher.Mono; 9 | import run.halo.app.extension.ReactiveExtensionClient; 10 | import run.halo.app.plugin.ApiVersion; 11 | 12 | /** 13 | * @author Erzbir 14 | * @Date 2023/10/26 15 | */ 16 | @ApiVersion("v1alpha1") 17 | @RestController 18 | @RequestMapping("/pushLogs") 19 | @AllArgsConstructor 20 | public class PushLogRouter { 21 | private ReactiveExtensionClient client; 22 | 23 | @DeleteMapping("/clear") 24 | public Mono clearLogs() { 25 | return client.list(PushLog.class, null, null).flatMap(pushLog -> client.delete(pushLog)) 26 | .then(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/scheme/PushLog.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.scheme; 2 | 3 | 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.NoArgsConstructor; 9 | import run.halo.app.extension.AbstractExtension; 10 | import run.halo.app.extension.GVK; 11 | 12 | import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; 13 | 14 | @Data 15 | @EqualsAndHashCode(callSuper = true) 16 | @GVK(kind = "PushLog", group = "sitepush.halo.run", 17 | version = "v1alpha1", singular = "pushLog", plural = "pushLogs") 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | public class PushLog extends AbstractExtension { 21 | 22 | @Schema() 23 | private Long createTime; 24 | 25 | @Schema(requiredMode = REQUIRED) 26 | private String pushUrl; 27 | 28 | @Schema(requiredMode = REQUIRED) 29 | private String pushType; 30 | 31 | @Schema() 32 | private int pushStatus; 33 | 34 | @Schema() 35 | private String remark; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/scheme/PushUnique.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.scheme; 2 | 3 | 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import lombok.ToString; 8 | import run.halo.app.extension.AbstractExtension; 9 | import run.halo.app.extension.GVK; 10 | 11 | import java.time.Instant; 12 | 13 | import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; 14 | 15 | @Data 16 | @EqualsAndHashCode(callSuper = true) 17 | @GVK(kind = "PushUnique", group = "sitepush.halo.run", 18 | version = "v1alpha1", singular = "pushUnique", plural = "pushUniques") 19 | @ToString 20 | public class PushUnique extends AbstractExtension { 21 | 22 | @Schema(requiredMode = REQUIRED) 23 | private Instant lastPushTime; 24 | 25 | @Schema(requiredMode = REQUIRED) 26 | private String pushType; 27 | 28 | @Schema(requiredMode = REQUIRED) 29 | private String pushUniqueKey; 30 | 31 | @Schema(requiredMode = REQUIRED, description = "1:推送成功,0:推送失败,-1:跳过推送") 32 | private Integer pushStatus; 33 | 34 | public String getCacheKey() { 35 | return this.pushType + ":" + this.pushUniqueKey; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/service/PushService.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.service; 2 | 3 | import com.stonewu.sitepush.strategy.PushStrategy; 4 | 5 | public interface PushService { 6 | boolean pushUseAllStrategy(String siteUrl, String slugKey, String... permalinks); 7 | 8 | boolean push(String siteUrl, PushStrategy pushStrategy, String slug, String... permalinks); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/service/PushServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.service; 2 | 3 | import com.stonewu.sitepush.GlobalCache; 4 | import com.stonewu.sitepush.scheme.PushUnique; 5 | import com.stonewu.sitepush.strategy.PushStrategy; 6 | import java.time.Instant; 7 | import java.util.Map; 8 | import java.util.Optional; 9 | import lombok.AllArgsConstructor; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.stereotype.Component; 12 | import run.halo.app.extension.Metadata; 13 | import run.halo.app.extension.ReactiveExtensionClient; 14 | 15 | @Component 16 | @Slf4j 17 | @AllArgsConstructor 18 | public class PushServiceImpl implements PushService { 19 | private final ReactiveExtensionClient client; 20 | 21 | private Map pushStrategyMap; 22 | 23 | @Override 24 | public boolean pushUseAllStrategy(String siteUrl, String slugKey, String... permalinks) { 25 | boolean allPush = true; 26 | for (Map.Entry entry : pushStrategyMap.entrySet()) { 27 | allPush = allPush && push(siteUrl, entry.getValue(), slugKey, permalinks); 28 | } 29 | return allPush; 30 | } 31 | 32 | 33 | @Override 34 | public boolean push(String siteUrl, PushStrategy pushStrategy, String slugKey, 35 | String... permalinks) { 36 | String cacheKey = pushStrategy.getPushType() + ":" + slugKey; 37 | // 设置默认值为成功 38 | boolean allSuccess = true; 39 | for (String permalink : permalinks) { 40 | // 没推送过或者推送过但是失败了的 41 | if (isNeedPush(cacheKey)) { 42 | int status = pushStrategy.push(siteUrl, slugKey, permalink); 43 | allSuccess = allSuccess && status == 1; 44 | recordPushResult(pushStrategy.getPushType(), slugKey, cacheKey, status); 45 | } 46 | } 47 | return allSuccess; 48 | } 49 | 50 | public void recordPushResult(String pushType, String slugKey, String cacheKey, int status) { 51 | PushUnique pushUnique = new PushUnique(); 52 | pushUnique.setLastPushTime(Instant.now()); 53 | pushUnique.setPushType(pushType); 54 | pushUnique.setPushUniqueKey(slugKey); 55 | pushUnique.setPushStatus(status); 56 | GlobalCache.PUSH_CACHE.put(cacheKey, pushUnique); 57 | Optional fetch = 58 | client.fetch(PushUnique.class, pushUnique.getCacheKey()).blockOptional(); 59 | if (fetch.isPresent()) { 60 | pushUnique = fetch.get(); 61 | client.update(pushUnique).subscribe(); 62 | } else { 63 | Metadata metadata = new Metadata(); 64 | metadata.setName(cacheKey); 65 | pushUnique.setMetadata(metadata); 66 | client.create(pushUnique).subscribe(); 67 | } 68 | } 69 | 70 | public boolean isNeedPush(String cacheKey) { 71 | var pushUnique = GlobalCache.PUSH_CACHE.get(cacheKey); 72 | return pushUnique == null 73 | || pushUnique.getPushStatus() == 0; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/setting/BaiduPushSetting.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.setting; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | @Data 7 | @ToString 8 | public class BaiduPushSetting { 9 | 10 | public static final String CONFIG_MAP_NAME = "plugin-sitepush-config"; 11 | 12 | public static final String GROUP = "baidu"; 13 | 14 | private String siteVerification = ""; 15 | 16 | private Boolean baiduEnable = Boolean.FALSE; 17 | 18 | private String token = ""; 19 | 20 | private Boolean baiduEnableTagVerification = Boolean.TRUE; 21 | 22 | private Boolean baiduProxyEnable = Boolean.FALSE; 23 | 24 | private String baiduProxyType = "HTTP"; 25 | 26 | private String baiduProxyAddress = ""; 27 | 28 | private Integer baiduProxyPort = 0; 29 | 30 | private Boolean baiduProxyAuthEnable = Boolean.FALSE; 31 | 32 | private String baiduProxyUsername = ""; 33 | 34 | private String baiduProxyPassword = ""; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/setting/BaiduSettingProvider.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.setting; 2 | 3 | import lombok.AllArgsConstructor; 4 | import org.apache.commons.lang3.StringUtils; 5 | import reactor.netty.transport.ProxyProvider; 6 | 7 | /** 8 | * @author Erzbir 9 | * @Date 2023/10/17 10 | */ 11 | @AllArgsConstructor 12 | public class BaiduSettingProvider implements PushSettingProvider { 13 | private BaiduPushSetting setting; 14 | 15 | @Override 16 | public String getConfigMapName() { 17 | return BaiduPushSetting.CONFIG_MAP_NAME; 18 | } 19 | 20 | @Override 21 | public String getGroup() { 22 | return BaiduPushSetting.GROUP; 23 | } 24 | 25 | @Override 26 | public Boolean isEnable() { 27 | return setting.getBaiduEnable(); 28 | } 29 | 30 | @Override 31 | public Boolean isTagVerificationEnable() { 32 | return setting.getBaiduEnableTagVerification(); 33 | } 34 | 35 | @Override 36 | public Class getSettingClass() { 37 | return setting.getClass(); 38 | } 39 | 40 | @Override 41 | public String getSiteVerification() { 42 | return setting.getSiteVerification(); 43 | } 44 | 45 | @Override 46 | public String getAccess() { 47 | return setting.getToken(); 48 | } 49 | 50 | @Override 51 | public String getSiteVerificationMeta() { 52 | String script = ""; 53 | if (StringUtils.isNotBlank(getSiteVerification())) { 54 | script = script + """ 55 | 56 | """.formatted(getSiteVerification()); 57 | } 58 | return script; 59 | } 60 | 61 | @Override 62 | public Boolean isUseProxy() { 63 | return setting.getBaiduProxyEnable(); 64 | } 65 | 66 | @Override 67 | public ProxyProvider.Proxy getProxyType() { 68 | if (setting.getBaiduProxyType().equals("SOCKS")) { 69 | return ProxyProvider.Proxy.SOCKS5; 70 | } 71 | return ProxyProvider.Proxy.valueOf(setting.getBaiduProxyType()); 72 | } 73 | 74 | @Override 75 | public String getProxyAddress() { 76 | return setting.getBaiduProxyAddress(); 77 | } 78 | 79 | @Override 80 | public Integer getProxyPort() { 81 | return setting.getBaiduProxyPort(); 82 | } 83 | 84 | @Override 85 | public Boolean proxyAuthEnable() { 86 | return setting.getBaiduProxyAuthEnable(); 87 | } 88 | 89 | @Override 90 | public String getProxyUsername() { 91 | return setting.getBaiduProxyUsername(); 92 | } 93 | 94 | @Override 95 | public String getProxyPassword() { 96 | return setting.getBaiduProxyPassword(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/setting/BasePushSetting.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.setting; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | @Data 7 | @ToString 8 | public class BasePushSetting { 9 | public static final String CONFIG_MAP_NAME = "plugin-sitepush-config"; 10 | 11 | public static final String GROUP = "basic"; 12 | 13 | private Boolean enable = Boolean.FALSE; 14 | 15 | private String siteUrl = ""; 16 | 17 | private Integer retryInterval = 360; 18 | 19 | private Integer cleanOldLogDataDays = 0; 20 | 21 | private Integer cleanOldUniqueDataDays = 0; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/setting/BingPushSetting.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.setting; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | @Data 7 | @ToString 8 | public class BingPushSetting { 9 | 10 | public static final String CONFIG_MAP_NAME = "plugin-sitepush-config"; 11 | 12 | public static final String GROUP = "bing"; 13 | 14 | private Boolean bingEnable = Boolean.FALSE; 15 | 16 | private String apikey = ""; 17 | 18 | private String bingSiteVerification = ""; 19 | 20 | private Boolean bingEnableTagVerification = Boolean.TRUE; 21 | 22 | private Boolean bingProxyEnable = Boolean.FALSE; 23 | 24 | private String bingProxyType = "HTTP"; 25 | 26 | private String bingProxyAddress = ""; 27 | 28 | private Integer bingProxyPort = 0; 29 | 30 | private Boolean bingProxyAuthEnable = Boolean.FALSE; 31 | 32 | private String bingProxyUsername = ""; 33 | 34 | private String bingProxyPassword = ""; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/setting/BingPushSettingProvider.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.setting; 2 | 3 | import lombok.AllArgsConstructor; 4 | import org.apache.commons.lang3.StringUtils; 5 | import reactor.netty.transport.ProxyProvider; 6 | 7 | /** 8 | * @author Erzbir 9 | * @Date 2023/10/17 10 | */ 11 | @AllArgsConstructor 12 | public class BingPushSettingProvider implements PushSettingProvider { 13 | private BingPushSetting setting; 14 | 15 | @Override 16 | public String getConfigMapName() { 17 | return BingPushSetting.CONFIG_MAP_NAME; 18 | } 19 | 20 | @Override 21 | public String getGroup() { 22 | return BingPushSetting.GROUP; 23 | } 24 | 25 | @Override 26 | public Boolean isEnable() { 27 | return setting.getBingEnable(); 28 | } 29 | 30 | @Override 31 | public Boolean isTagVerificationEnable() { 32 | return setting.getBingEnableTagVerification(); 33 | } 34 | 35 | @Override 36 | public Class getSettingClass() { 37 | return setting.getClass(); 38 | } 39 | 40 | @Override 41 | public String getSiteVerification() { 42 | return setting.getBingSiteVerification(); 43 | } 44 | 45 | @Override 46 | public String getAccess() { 47 | return setting.getApikey(); 48 | } 49 | 50 | @Override 51 | public String getSiteVerificationMeta() { 52 | String script = ""; 53 | 54 | if (StringUtils.isNotBlank(getSiteVerification())) { 55 | script = script + """ 56 | 57 | """.formatted(getSiteVerification()); 58 | } 59 | return script; 60 | } 61 | 62 | @Override 63 | public Boolean isUseProxy() { 64 | return setting.getBingProxyEnable(); 65 | } 66 | 67 | @Override 68 | public ProxyProvider.Proxy getProxyType() { 69 | if (setting.getBingProxyType().equals("SOCKS")) { 70 | return ProxyProvider.Proxy.SOCKS5; 71 | } 72 | return ProxyProvider.Proxy.valueOf(setting.getBingProxyType()); 73 | } 74 | 75 | @Override 76 | public String getProxyAddress() { 77 | return setting.getBingProxyAddress(); 78 | } 79 | 80 | @Override 81 | public Integer getProxyPort() { 82 | return setting.getBingProxyPort(); 83 | } 84 | 85 | @Override 86 | public Boolean proxyAuthEnable() { 87 | return setting.getBingProxyAuthEnable(); 88 | } 89 | 90 | @Override 91 | public String getProxyUsername() { 92 | return setting.getBingProxyUsername(); 93 | } 94 | 95 | @Override 96 | public String getProxyPassword() { 97 | return setting.getBingProxyPassword(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/setting/GooglePushSetting.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.setting; 2 | 3 | import lombok.Data; 4 | import lombok.ToString; 5 | 6 | /** 7 | * @author Erzbir 8 | * @Date 2023/10/12 9 | */ 10 | @Data 11 | @ToString 12 | public class GooglePushSetting { 13 | public static final String CONFIG_MAP_NAME = "plugin-sitepush-config"; 14 | 15 | public static final String GROUP = "google"; 16 | 17 | private Boolean googleEnable = Boolean.FALSE; 18 | 19 | private String credentialsJson = ""; 20 | 21 | private String googleSiteVerification = ""; 22 | 23 | private Boolean googleEnableTagVerification = Boolean.TRUE; 24 | 25 | private Boolean googleProxyEnable = Boolean.FALSE; 26 | 27 | private String googleProxyType = "HTTP"; 28 | 29 | private String googleProxyAddress = ""; 30 | 31 | private Integer googleProxyPort = 0; 32 | 33 | private Boolean googleProxyAuthEnable = Boolean.FALSE; 34 | 35 | private String googleProxyUsername = ""; 36 | 37 | private String googleProxyPassword = ""; 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/setting/GooglePushSettingProvider.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.setting; 2 | 3 | import lombok.AllArgsConstructor; 4 | import org.apache.commons.lang3.StringUtils; 5 | import reactor.netty.transport.ProxyProvider; 6 | 7 | /** 8 | * @author Erzbir 9 | * @Date 2023/10/17 10 | */ 11 | @AllArgsConstructor 12 | public class GooglePushSettingProvider implements PushSettingProvider { 13 | private GooglePushSetting setting; 14 | 15 | @Override 16 | public String getConfigMapName() { 17 | return GooglePushSetting.CONFIG_MAP_NAME; 18 | } 19 | 20 | @Override 21 | public String getGroup() { 22 | return GooglePushSetting.GROUP; 23 | } 24 | 25 | @Override 26 | public Boolean isEnable() { 27 | return setting.getGoogleEnable(); 28 | } 29 | 30 | @Override 31 | public Boolean isTagVerificationEnable() { 32 | return setting.getGoogleEnableTagVerification(); 33 | } 34 | 35 | @Override 36 | public Class getSettingClass() { 37 | return setting.getClass(); 38 | } 39 | 40 | @Override 41 | public String getSiteVerification() { 42 | return setting.getGoogleSiteVerification(); 43 | } 44 | 45 | @Override 46 | public String getAccess() { 47 | return setting.getCredentialsJson(); 48 | } 49 | 50 | @Override 51 | public String getSiteVerificationMeta() { 52 | String script = ""; 53 | 54 | if (StringUtils.isNotBlank(getSiteVerification())) { 55 | script = script + """ 56 | 57 | """.formatted(getSiteVerification()); 58 | } 59 | return script; 60 | } 61 | 62 | @Override 63 | public Boolean isUseProxy() { 64 | return setting.getGoogleProxyEnable(); 65 | } 66 | 67 | @Override 68 | public ProxyProvider.Proxy getProxyType() { 69 | if (setting.getGoogleProxyType().equals("SOCKS")) { 70 | return ProxyProvider.Proxy.SOCKS5; 71 | } 72 | return ProxyProvider.Proxy.valueOf(setting.getGoogleProxyType()); 73 | } 74 | 75 | @Override 76 | public String getProxyAddress() { 77 | return setting.getGoogleProxyAddress(); 78 | } 79 | 80 | @Override 81 | public Integer getProxyPort() { 82 | return setting.getGoogleProxyPort(); 83 | } 84 | 85 | @Override 86 | public Boolean proxyAuthEnable() { 87 | return setting.getGoogleProxyAuthEnable(); 88 | } 89 | 90 | @Override 91 | public String getProxyUsername() { 92 | return setting.getGoogleProxyUsername(); 93 | } 94 | 95 | @Override 96 | public String getProxyPassword() { 97 | return setting.getGoogleProxyPassword(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/setting/PushSettingProvider.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.setting; 2 | 3 | import reactor.netty.transport.ProxyProvider; 4 | 5 | /** 6 | * @author Erzbir 7 | * @Date 2023/10/17 8 | */ 9 | public interface PushSettingProvider { 10 | String getConfigMapName(); 11 | 12 | String getGroup(); 13 | 14 | Boolean isEnable(); 15 | 16 | /** 17 | * @return 是否启用标签验证 18 | */ 19 | Boolean isTagVerificationEnable(); 20 | 21 | /** 22 | * @return 设置的字节码 23 | */ 24 | Class getSettingClass(); 25 | 26 | /** 27 | * @return 站点验证码 28 | */ 29 | String getSiteVerification(); 30 | 31 | /** 32 | * @return 访问口令 33 | */ 34 | String getAccess(); 35 | 36 | /** 37 | * @return html tag 验证的 meta 标签 38 | */ 39 | String getSiteVerificationMeta(); 40 | 41 | Boolean isUseProxy(); 42 | 43 | ProxyProvider.Proxy getProxyType(); 44 | 45 | String getProxyAddress(); 46 | 47 | Integer getProxyPort(); 48 | 49 | Boolean proxyAuthEnable(); 50 | 51 | String getProxyUsername(); 52 | 53 | String getProxyPassword(); 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/strategy/AbstractPushStrategy.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.strategy; 2 | 3 | import com.stonewu.sitepush.scheme.PushLog; 4 | import com.stonewu.sitepush.setting.PushSettingProvider; 5 | import com.stonewu.sitepush.utils.AuthProxyHttpRequestSender; 6 | import com.stonewu.sitepush.utils.DefaultHttpRequestSender; 7 | import com.stonewu.sitepush.utils.HttpRequestSender; 8 | import com.stonewu.sitepush.utils.HttpResponse; 9 | import com.stonewu.sitepush.utils.Proxy; 10 | import com.stonewu.sitepush.utils.ProxyHttpRequestSender; 11 | import java.net.InetSocketAddress; 12 | import java.time.Instant; 13 | import java.util.UUID; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.util.StringUtils; 16 | import reactor.core.publisher.Mono; 17 | import run.halo.app.extension.Metadata; 18 | import run.halo.app.extension.ReactiveExtensionClient; 19 | import run.halo.app.plugin.SettingFetcher; 20 | 21 | /** 22 | * @author Erzbir 23 | * @Date 2023/10/23 24 | */ 25 | @Slf4j 26 | public abstract class AbstractPushStrategy implements PushStrategy { 27 | protected final SettingFetcher settingFetcher; 28 | protected final ReactiveExtensionClient client; 29 | 30 | protected HttpRequestSender httpRequestSender; 31 | 32 | public AbstractPushStrategy(SettingFetcher settingFetcher, ReactiveExtensionClient client) { 33 | this.settingFetcher = settingFetcher; 34 | this.client = client; 35 | } 36 | 37 | @Override 38 | public int push(String siteUrl, String key, String... pageLinks) { 39 | PushSettingProvider settingProvider = getSettingProvider(); 40 | updateHttpRequestSender(settingProvider); 41 | String token = settingProvider.getAccess(); 42 | if (settingProvider.isEnable() && StringUtils.hasText(token)) { 43 | HttpResponse response; 44 | try { 45 | response = request(settingProvider, siteUrl, pageLinks).block(); 46 | if (response == null) { 47 | throw new Exception("response is null"); 48 | } 49 | } catch (Exception e) { 50 | log.warn("Push exception: {} : {}", getPushType(), e.getMessage()); 51 | recordPushLogs(0, e.getMessage(), siteUrl, pageLinks); 52 | return 0; 53 | } 54 | String body = response.body(); 55 | log.info("Pushing to {} Result: {}", getPushType(), body); 56 | log.info("code: {}", response.code()); 57 | boolean status = response.code() == 200; 58 | recordPushLogs(status ? 1 : 0, body, siteUrl, pageLinks); 59 | return status ? 1 : 0; 60 | } 61 | return -1; 62 | 63 | } 64 | 65 | private void recordPushLogs(int status, String remark, String siteUrl, String... pageLinks) { 66 | for (String pageLink : pageLinks) { 67 | PushLog pushLog = new PushLog(Instant.now().getEpochSecond(), siteUrl + pageLink, 68 | getPushType(), status, remark); 69 | Metadata metadata = new Metadata(); 70 | metadata.setName(UUID.randomUUID().toString()); 71 | pushLog.setMetadata(metadata); 72 | client.create(pushLog).subscribe(); 73 | } 74 | } 75 | 76 | /** 77 | * 通过设置更新请求策略 78 | */ 79 | protected void updateHttpRequestSender(PushSettingProvider settingProvider) { 80 | if (settingProvider.isUseProxy()) { 81 | Proxy proxy = new Proxy(settingProvider.getProxyType(), new InetSocketAddress( 82 | settingProvider.getProxyAddress(), settingProvider.getProxyPort())); 83 | if (settingProvider.proxyAuthEnable()) { 84 | httpRequestSender = 85 | new AuthProxyHttpRequestSender(settingProvider.getProxyUsername(), 86 | settingProvider.getProxyPassword(), proxy); 87 | } else { 88 | httpRequestSender = new ProxyHttpRequestSender(proxy); 89 | } 90 | } else { 91 | httpRequestSender = new DefaultHttpRequestSender(); 92 | } 93 | } 94 | 95 | protected abstract PushSettingProvider getSettingProvider(); 96 | 97 | protected abstract Mono request(PushSettingProvider settingProvider, 98 | String siteUrl, String... pageLinks) throws Exception; 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/strategy/BaiduPushStrategy.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.strategy; 2 | 3 | import com.stonewu.sitepush.setting.BaiduPushSetting; 4 | import com.stonewu.sitepush.setting.BaiduSettingProvider; 5 | import com.stonewu.sitepush.setting.PushSettingProvider; 6 | import com.stonewu.sitepush.utils.HttpResponse; 7 | import io.netty.handler.codec.http.DefaultHttpHeaders; 8 | import io.netty.handler.codec.http.HttpHeaderNames; 9 | import io.netty.handler.codec.http.HttpHeaders; 10 | import io.netty.handler.codec.http.HttpMethod; 11 | import java.io.IOException; 12 | import java.util.concurrent.ExecutionException; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.springframework.stereotype.Component; 15 | import reactor.core.publisher.Mono; 16 | import run.halo.app.extension.ReactiveExtensionClient; 17 | import run.halo.app.plugin.SettingFetcher; 18 | 19 | @Component 20 | @Slf4j 21 | public class BaiduPushStrategy extends AbstractPushStrategy implements PushStrategy { 22 | public static final String PUSH_ENDPOINT = "http://data.zz.baidu.com/urls?site=%s&token=%s"; 23 | 24 | public BaiduPushStrategy(SettingFetcher settingFetcher, ReactiveExtensionClient client) { 25 | super(settingFetcher, client); 26 | } 27 | 28 | 29 | @Override 30 | public String getPushType() { 31 | return "baidu"; 32 | } 33 | 34 | 35 | @Override 36 | protected PushSettingProvider getSettingProvider() { 37 | BaiduPushSetting baiduPushSetting = 38 | settingFetcher.fetch(BaiduPushSetting.GROUP, 39 | BaiduPushSetting.class).orElseGet(BaiduPushSetting::new); 40 | 41 | return new BaiduSettingProvider(baiduPushSetting); 42 | } 43 | 44 | @Override 45 | protected Mono request(PushSettingProvider settingProvider, String siteUrl, 46 | String... pageLinks) 47 | throws IOException, ExecutionException, InterruptedException { 48 | String baiduPushUrl = String.format(PUSH_ENDPOINT, siteUrl, settingProvider.getAccess()); 49 | StringBuilder pushUrls = new StringBuilder(); 50 | for (String pageLink : pageLinks) { 51 | pushUrls.append(siteUrl).append(pageLink).append("\n"); 52 | } 53 | HttpHeaders httpHeaders = new DefaultHttpHeaders(); 54 | httpHeaders.add(HttpHeaderNames.CONTENT_TYPE, "text/plain"); 55 | String urls = pushUrls.toString(); 56 | log.info("Pushing to baidu webmasters: {}", urls); 57 | return httpRequestSender.request(baiduPushUrl, HttpMethod.POST, 58 | httpHeaders, 59 | urls); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/strategy/BingPushStrategy.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.strategy; 2 | 3 | import com.stonewu.sitepush.setting.BingPushSetting; 4 | import com.stonewu.sitepush.setting.BingPushSettingProvider; 5 | import com.stonewu.sitepush.setting.PushSettingProvider; 6 | import com.stonewu.sitepush.utils.HttpResponse; 7 | import io.netty.handler.codec.http.DefaultHttpHeaders; 8 | import io.netty.handler.codec.http.HttpHeaderNames; 9 | import io.netty.handler.codec.http.HttpHeaders; 10 | import io.netty.handler.codec.http.HttpMethod; 11 | import java.io.IOException; 12 | import java.util.Arrays; 13 | import java.util.List; 14 | import java.util.concurrent.ExecutionException; 15 | import lombok.Data; 16 | import lombok.extern.slf4j.Slf4j; 17 | import org.springframework.stereotype.Component; 18 | import reactor.core.publisher.Mono; 19 | import run.halo.app.extension.ReactiveExtensionClient; 20 | import run.halo.app.infra.utils.JsonUtils; 21 | import run.halo.app.plugin.SettingFetcher; 22 | 23 | @Component 24 | @Slf4j 25 | public class BingPushStrategy extends AbstractPushStrategy implements PushStrategy { 26 | public static final String PUSH_ENDPOINT = 27 | "https://ssl.bing.com/webmaster/api.svc/json/SubmitUrlbatch?apikey=%s"; 28 | 29 | public BingPushStrategy(SettingFetcher settingFetcher, ReactiveExtensionClient client) { 30 | super(settingFetcher, client); 31 | } 32 | 33 | 34 | @Override 35 | public String getPushType() { 36 | return "bing"; 37 | } 38 | 39 | @Override 40 | protected PushSettingProvider getSettingProvider() { 41 | BingPushSetting bingPushSetting = 42 | settingFetcher.fetch(BingPushSetting.GROUP, 43 | BingPushSetting.class).orElseGet(BingPushSetting::new); 44 | return new BingPushSettingProvider(bingPushSetting); 45 | } 46 | 47 | @Override 48 | protected Mono request(PushSettingProvider settingProvider, String siteUrl, 49 | String... pageLinks) 50 | throws IOException, ExecutionException, InterruptedException { 51 | String bingPushUrl = String.format(PUSH_ENDPOINT, settingProvider.getAccess()); 52 | String[] pushBodyUrls = new String[pageLinks.length]; 53 | for (int i = 0; i < pageLinks.length; i++) { 54 | pushBodyUrls[i] = siteUrl + pageLinks[i]; 55 | } 56 | String pushUrls = Arrays.toString(pushBodyUrls); 57 | log.info("Pushing to bing webmasters: {}", pushUrls); 58 | BingPushBody bingPushBody = new BingPushBody(); 59 | bingPushBody.setSiteUrl(siteUrl); 60 | bingPushBody.setUrlList(List.of(pushBodyUrls)); 61 | HttpHeaders httpHeaders = new DefaultHttpHeaders(); 62 | httpHeaders.add(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8"); 63 | return httpRequestSender.request(bingPushUrl, HttpMethod.POST, httpHeaders, 64 | JsonUtils.objectToJson(bingPushBody)); 65 | } 66 | 67 | @Data 68 | static 69 | class BingPushBody { 70 | private String siteUrl; 71 | private List urlList; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/strategy/GooglePushStrategy.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.strategy; 2 | 3 | 4 | import com.stonewu.sitepush.setting.GooglePushSetting; 5 | import com.stonewu.sitepush.setting.GooglePushSettingProvider; 6 | import com.stonewu.sitepush.setting.PushSettingProvider; 7 | import com.stonewu.sitepush.utils.HttpResponse; 8 | import com.stonewu.sitepush.utils.JWTSignUtil; 9 | import com.stonewu.sitepush.utils.PemReader; 10 | import io.netty.handler.codec.http.DefaultHttpHeaders; 11 | import io.netty.handler.codec.http.HttpHeaderNames; 12 | import io.netty.handler.codec.http.HttpHeaders; 13 | import io.netty.handler.codec.http.HttpMethod; 14 | import java.io.IOException; 15 | import java.net.URI; 16 | import java.net.URISyntaxException; 17 | import java.security.GeneralSecurityException; 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.HashMap; 21 | import java.util.List; 22 | import java.util.Map; 23 | import lombok.Data; 24 | import lombok.extern.slf4j.Slf4j; 25 | import org.springframework.stereotype.Component; 26 | import reactor.core.publisher.Mono; 27 | import run.halo.app.extension.ReactiveExtensionClient; 28 | import run.halo.app.infra.utils.JsonUtils; 29 | import run.halo.app.plugin.SettingFetcher; 30 | 31 | /** 32 | * @author Erzbir 33 | * @Date 2023/10/12 34 | */ 35 | @Component 36 | @Slf4j 37 | public class GooglePushStrategy extends AbstractPushStrategy implements PushStrategy { 38 | 39 | public static final String GLOBAL_ENDPOINT = 40 | "https://indexing.googleapis.com/batch"; 41 | 42 | public static final String API_URL = "/v3/urlNotifications:publish"; 43 | 44 | public static final String UPDATE_TYPE = "URL_UPDATED"; 45 | 46 | public GooglePushStrategy(SettingFetcher settingFetcher, ReactiveExtensionClient client) { 47 | super(settingFetcher, client); 48 | } 49 | 50 | @Override 51 | public String getPushType() { 52 | return "google"; 53 | } 54 | 55 | @Override 56 | protected PushSettingProvider getSettingProvider() { 57 | GooglePushSetting googlePushSetting = 58 | settingFetcher.fetch(GooglePushSetting.GROUP, GooglePushSetting.class) 59 | .orElseGet(GooglePushSetting::new); 60 | return new GooglePushSettingProvider(googlePushSetting); 61 | } 62 | 63 | @Override 64 | protected Mono request(PushSettingProvider settingProvider, String siteUrl, 65 | String... pageLinks) throws Exception { 66 | String[] pushBodyUrls = new String[pageLinks.length]; 67 | List> requests = new ArrayList<>(); 68 | for (int i = 0; i < pageLinks.length; i++) { 69 | String url = siteUrl + pageLinks[i]; 70 | pushBodyUrls[i] = url; 71 | Map map = new HashMap<>(); 72 | map.put("url", url); 73 | map.put("type", UPDATE_TYPE); 74 | requests.add(map); 75 | } 76 | String pushUrls = Arrays.toString(pushBodyUrls); 77 | log.info("Pushing to google webmasters: {}", pushUrls); 78 | String token = GooglePushTokenCreator.createToken( 79 | JsonUtils.jsonToObject(settingProvider.getAccess(), 80 | GooglePushTokenCreator.ServiceAccountCredentials.class), 81 | URI.create(GLOBAL_ENDPOINT)); 82 | String boundary = "===============7330845974216740156=="; 83 | String batchBody = buildBatchBody(requests, boundary); 84 | HttpHeaders headers = new DefaultHttpHeaders(); 85 | headers.add(HttpHeaderNames.AUTHORIZATION, "Bearer " + token); 86 | headers.add(HttpHeaderNames.CONTENT_TYPE, "multipart/mixed; boundary=" + boundary); 87 | headers.add(HttpHeaderNames.ACCEPT, "application/json"); 88 | return httpRequestSender.request(GLOBAL_ENDPOINT, HttpMethod.POST, headers, batchBody); 89 | } 90 | 91 | 92 | private String buildBatchBody(List> requests, String boundary) { 93 | StringBuilder batchBody = new StringBuilder(); 94 | int contentId = 1; 95 | for (Map request : requests) { 96 | String requestStr = JsonUtils.objectToJson(request); 97 | batchBody.append("--").append(boundary).append("\n") 98 | .append("Content-Type: application/http\n") 99 | .append("Content-Transfer-Encoding: binary\n") 100 | .append("Content-ID: ").append(contentId++).append("\n") 101 | .append("\n") 102 | .append("POST").append(" ").append(API_URL).append("\n") 103 | .append("Content-Type: application/json").append("\n") 104 | .append("accept: application/json").append("\n") 105 | .append("\n") 106 | .append(requestStr).append("\n"); 107 | } 108 | batchBody.append("--").append(boundary).append("--"); 109 | return batchBody.toString(); 110 | } 111 | 112 | private static class GooglePushTokenCreator { 113 | 114 | public static String createToken(ServiceAccountCredentials credentials, URI endpoint) 115 | throws IOException, 116 | GeneralSecurityException { 117 | String privateKeyId = credentials.private_key_id; 118 | String privateKey = credentials.private_key; 119 | Map header = new HashMap<>(); 120 | header.put("alg", "RS256"); 121 | header.put("kid", privateKeyId); 122 | header.put("typ", "JWT"); 123 | 124 | Map payload = new HashMap<>(); 125 | String clientEmail = credentials.client_email; 126 | long l = System.currentTimeMillis(); 127 | payload.put("iss", clientEmail); 128 | payload.put("aud", getUriForSelfSignedJWT(endpoint)); 129 | payload.put("sub", clientEmail); 130 | payload.put("iat", l / 1000); 131 | payload.put("exp", l / 1000 + 3600); 132 | return JWTSignUtil.getInstance() 133 | .signUsingRsaSha256(PemReader.getInstance().getPrivateKeyFromPEM(privateKey, "RSA"), 134 | header, 135 | payload); 136 | } 137 | 138 | private static URI getUriForSelfSignedJWT(URI uri) { 139 | if (uri == null || uri.getScheme() == null || uri.getHost() == null) { 140 | return uri; 141 | } 142 | try { 143 | return new URI(uri.getScheme(), uri.getHost(), "/", null); 144 | } catch (URISyntaxException unused) { 145 | return uri; 146 | } 147 | } 148 | 149 | @Data 150 | public static class ServiceAccountCredentials { 151 | private String type; 152 | private String project_id; 153 | private String private_key_id; 154 | private String private_key; 155 | private String client_email; 156 | private String client_id; 157 | private String auth_uri; 158 | private String token_uri; 159 | private String auth_provider_x509_cert_url; 160 | private String client_x509_cert_url; 161 | private String universe_domain; 162 | } 163 | } 164 | 165 | @Data 166 | static class GooglePushBody { 167 | private String url; 168 | private String type; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/strategy/PushStrategy.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.strategy; 2 | 3 | public interface PushStrategy { 4 | String getPushType(); 5 | 6 | /** 7 | * @param siteUrl 站点地址,不含/,例:https://www.stonewu.com 8 | * @param key 用于缓存已经推送的key 9 | * @param pageLinks 页面绝对访问路径,以/开头,例:/post/new-page 10 | * @return 返回 -1 为没有进行推送, 1 为推送成功, 0 为推送失败 11 | */ 12 | int push(String siteUrl, String key, String... pageLinks); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/utils/AbstractHttpRequestSender.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.utils; 2 | 3 | 4 | import io.netty.channel.ChannelOption; 5 | import io.netty.handler.codec.http.HttpHeaders; 6 | import io.netty.handler.codec.http.HttpMethod; 7 | import java.time.Duration; 8 | import lombok.Getter; 9 | import lombok.Setter; 10 | import reactor.core.publisher.Mono; 11 | import reactor.netty.ByteBufFlux; 12 | import reactor.netty.http.HttpProtocol; 13 | import reactor.netty.http.client.HttpClient; 14 | 15 | /** 16 | * @author Erzbir 17 | * @Date 2023/10/29 18 | */ 19 | @Getter 20 | @Setter 21 | public abstract class AbstractHttpRequestSender implements HttpRequestSender { 22 | protected int timeOut; 23 | protected HttpClient httpClient; 24 | 25 | protected AbstractHttpRequestSender(int timeOut) { 26 | this.timeOut = timeOut; 27 | httpClient = HttpClient.create().keepAlive(false) 28 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeOut); 29 | httpClient.responseTimeout(Duration.ofMillis(timeOut)); 30 | } 31 | 32 | protected AbstractHttpRequestSender() { 33 | this(5000); 34 | } 35 | 36 | @Override 37 | public Mono request(String requestUrl, HttpMethod httpMethod, 38 | HttpHeaders httpHeaders, String body) { 39 | httpClient = httpClient.protocol(HttpProtocol.HTTP11); 40 | return getRequestSender(httpMethod, httpHeaders) 41 | .uri(requestUrl) 42 | .send(ByteBufFlux.fromString(Mono.just(body))) 43 | .responseSingle((response, byteBufMono) -> byteBufMono.map( 44 | byteBuf -> new HttpResponse(response.status().code(), byteBuf))) 45 | .doOnError(throwable -> new HttpResponse(500, throwable.getMessage())); 46 | } 47 | 48 | protected abstract HttpClient.RequestSender getRequestSender(HttpMethod httpMethod, 49 | HttpHeaders httpHeaders); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/utils/AuthProxyHttpRequestSender.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.utils; 2 | 3 | import io.netty.handler.codec.http.HttpHeaderNames; 4 | import io.netty.handler.codec.http.HttpHeaders; 5 | import io.netty.handler.codec.http.HttpMethod; 6 | import java.nio.charset.StandardCharsets; 7 | import java.util.Base64; 8 | import lombok.Getter; 9 | import lombok.Setter; 10 | import reactor.netty.http.client.HttpClient; 11 | 12 | /** 13 | * @author Erzbir 14 | * @Date 2023/10/23 15 | */ 16 | @Getter 17 | @Setter 18 | public class AuthProxyHttpRequestSender extends AbstractHttpRequestSender 19 | implements HttpRequestSender { 20 | private final ProxyHttpRequestSender proxyHttpRequestSender; 21 | private String username; 22 | private String password; 23 | 24 | public AuthProxyHttpRequestSender(String username, String password, Proxy proxy) { 25 | proxyHttpRequestSender = new ProxyHttpRequestSender(proxy); 26 | this.username = username; 27 | this.password = password; 28 | } 29 | 30 | 31 | @Override 32 | public boolean isProxy() { 33 | return true; 34 | } 35 | 36 | @Override 37 | protected HttpClient.RequestSender getRequestSender(HttpMethod httpMethod, 38 | HttpHeaders httpHeaders) { 39 | final String data = username.concat(":").concat(password); 40 | String token = 41 | "Basic " + Base64.getEncoder().encodeToString(data.getBytes(StandardCharsets.UTF_8)); 42 | httpHeaders.add(HttpHeaderNames.PROXY_AUTHORIZATION, token); 43 | return proxyHttpRequestSender.getRequestSender(httpMethod, httpHeaders); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/utils/DefaultHttpRequestSender.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.utils; 2 | 3 | 4 | import io.netty.handler.codec.http.HttpHeaders; 5 | import io.netty.handler.codec.http.HttpMethod; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import lombok.extern.slf4j.Slf4j; 9 | import reactor.netty.http.client.HttpClient; 10 | 11 | /** 12 | * @author Erzbir 13 | * @Date 2023/10/23 14 | */ 15 | @Getter 16 | @Setter 17 | @Slf4j 18 | public class DefaultHttpRequestSender extends AbstractHttpRequestSender 19 | implements HttpRequestSender { 20 | 21 | @Override 22 | public boolean isProxy() { 23 | return false; 24 | } 25 | 26 | @Override 27 | public HttpClient.RequestSender getRequestSender(HttpMethod httpMethod, 28 | HttpHeaders httpHeaders) { 29 | return httpClient 30 | .headers(builder -> builder.add(httpHeaders)) 31 | .request(httpMethod); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/utils/HttpRequestSender.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.utils; 2 | 3 | import io.netty.handler.codec.http.HttpHeaders; 4 | import io.netty.handler.codec.http.HttpMethod; 5 | import reactor.core.publisher.Mono; 6 | 7 | /** 8 | * @author Erzbir 9 | * @Date 2023/10/23 10 | */ 11 | public interface HttpRequestSender { 12 | Mono request(String requestUrl, HttpMethod httpMethod, HttpHeaders httpHeaders, 13 | String body); 14 | 15 | boolean isProxy(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/utils/HttpResponse.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.utils; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import java.nio.charset.StandardCharsets; 5 | 6 | /** 7 | * @author Erzbir 8 | * @Date 2023/10/30 9 | */ 10 | public class HttpResponse { 11 | private final int code; 12 | private final String body; 13 | 14 | public HttpResponse(int code, ByteBuf byteBuf) { 15 | this(code, byteBuf.toString(StandardCharsets.UTF_8)); 16 | } 17 | 18 | public HttpResponse(int code, String body) { 19 | this.code = code; 20 | this.body = body; 21 | } 22 | 23 | public int code() { 24 | return code; 25 | } 26 | 27 | public String body() { 28 | return body; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/utils/JWTSignUtil.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.utils; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.security.GeneralSecurityException; 5 | import java.security.PrivateKey; 6 | import java.security.Signature; 7 | import java.util.Base64; 8 | import java.util.Map; 9 | import run.halo.app.infra.utils.JsonUtils; 10 | 11 | /** 12 | * @author Erzbir 13 | * @Date 2023/10/21 14 | */ 15 | public class JWTSignUtil { 16 | private JWTSignUtil() { 17 | 18 | } 19 | 20 | public static JWTSignUtil getInstance() { 21 | return SingletonHolder.INSTANCE; 22 | } 23 | 24 | public String signUsingRsaSha256( 25 | PrivateKey privateKey, 26 | Map header, 27 | Map payload) 28 | throws GeneralSecurityException { 29 | String headerBase64 = 30 | Base64.getUrlEncoder() 31 | .encodeToString(JsonUtils.objectToJson(header).getBytes(StandardCharsets.UTF_8)); 32 | String payloadBase64 = 33 | Base64.getUrlEncoder() 34 | .encodeToString(JsonUtils.objectToJson(payload).getBytes(StandardCharsets.UTF_8)); 35 | String content = headerBase64 + "." + payloadBase64; 36 | byte[] contentBytes = content.getBytes(); 37 | byte[] signature = SecurityUtil.getInstance() 38 | .sign(Signature.getInstance("SHA256withRSA"), contentBytes, privateKey); 39 | return content + "." + Base64.getUrlEncoder().encodeToString(signature); 40 | } 41 | 42 | 43 | private interface SingletonHolder { 44 | JWTSignUtil INSTANCE = new JWTSignUtil(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/utils/PemReader.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.utils; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.StringReader; 6 | import java.security.KeyFactory; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.security.PrivateKey; 9 | import java.security.spec.InvalidKeySpecException; 10 | import java.security.spec.PKCS8EncodedKeySpec; 11 | import java.util.Base64; 12 | 13 | /** 14 | * @author Erzbir 15 | * @Date 2023/10/21 16 | */ 17 | public class PemReader { 18 | private PemReader() { 19 | 20 | } 21 | 22 | public static PemReader getInstance() { 23 | return SingletonHolder.INSTANCE; 24 | } 25 | 26 | public PrivateKey getPrivateKeyFromPEM(String pem, String algorithm, String title) 27 | throws IOException { 28 | try (BufferedReader reader = new BufferedReader(new StringReader(pem))) { 29 | String line; 30 | StringBuilder keyContent = new StringBuilder(); 31 | boolean inKeyBlock = false; 32 | 33 | while ((line = reader.readLine()) != null) { 34 | if (line.contains(title)) { 35 | inKeyBlock = true; 36 | continue; 37 | } 38 | if (inKeyBlock) { 39 | keyContent.append(line); 40 | } 41 | } 42 | 43 | byte[] keyBytes = Base64.getDecoder().decode(keyContent.toString()); 44 | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); 45 | return KeyFactory.getInstance(algorithm).generatePrivate(keySpec); 46 | } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) { 47 | throw new IOException("Unexpected exception reading pem", e); 48 | } 49 | } 50 | 51 | public PrivateKey getPrivateKeyFromPEM(String pem, String algorithm) throws IOException { 52 | String title = "PRIVATE KEY"; 53 | return getPrivateKeyFromPEM(pem, algorithm, title); 54 | } 55 | 56 | 57 | private interface SingletonHolder { 58 | PemReader INSTANCE = new PemReader(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/utils/Proxy.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.utils; 2 | 3 | import java.net.SocketAddress; 4 | import reactor.netty.transport.ProxyProvider; 5 | 6 | /** 7 | * @author Erzbir 8 | * @Date 2023/10/29 9 | */ 10 | 11 | public record Proxy(ProxyProvider.Proxy type, SocketAddress address) { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/utils/ProxyHttpRequestSender.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.utils; 2 | 3 | import io.netty.handler.codec.http.HttpHeaders; 4 | import io.netty.handler.codec.http.HttpMethod; 5 | import java.net.InetSocketAddress; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import lombok.extern.slf4j.Slf4j; 9 | import reactor.netty.http.client.HttpClient; 10 | 11 | /** 12 | * @author Erzbir 13 | * @Date 2023/10/23 14 | */ 15 | @Getter 16 | @Setter 17 | @Slf4j 18 | public class ProxyHttpRequestSender extends AbstractHttpRequestSender implements HttpRequestSender { 19 | private Proxy proxy; 20 | 21 | public ProxyHttpRequestSender(Proxy proxy) { 22 | this.proxy = proxy; 23 | } 24 | 25 | @Override 26 | public HttpClient.RequestSender getRequestSender(HttpMethod httpMethod, 27 | HttpHeaders httpHeaders) { 28 | return httpClient 29 | .headers(builder -> builder.add(httpHeaders)) 30 | .proxy(proxyOptions -> proxyOptions.type(proxy.type()) 31 | .address((InetSocketAddress) proxy.address())) 32 | .request(httpMethod); 33 | } 34 | 35 | @Override 36 | public boolean isProxy() { 37 | return true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/stonewu/sitepush/utils/SecurityUtil.java: -------------------------------------------------------------------------------- 1 | package com.stonewu.sitepush.utils; 2 | 3 | import java.security.InvalidKeyException; 4 | import java.security.PrivateKey; 5 | import java.security.Signature; 6 | import java.security.SignatureException; 7 | 8 | /** 9 | * @author Erzbir 10 | * @Date 2023/10/21 11 | */ 12 | public class SecurityUtil { 13 | 14 | private SecurityUtil() { 15 | 16 | } 17 | 18 | public static SecurityUtil getInstance() { 19 | return SingletonHolder.INSTANCE; 20 | } 21 | 22 | public byte[] sign(Signature signature, byte[] contentBytes, PrivateKey privateKey) 23 | throws InvalidKeyException, SignatureException { 24 | signature.initSign(privateKey); 25 | signature.update(contentBytes); 26 | return signature.sign(); 27 | } 28 | 29 | private interface SingletonHolder { 30 | SecurityUtil INSTANCE = new SecurityUtil(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/extensions/roleTemplate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1alpha1 2 | kind: Role 3 | metadata: 4 | name: role-template-sitepush-view 5 | labels: 6 | halo.run/role-template: "true" 7 | annotations: 8 | rbac.authorization.halo.run/module: "SitePush Management" 9 | rbac.authorization.halo.run/display-name: "站点推送查看" 10 | rbac.authorization.halo.run/ui-permissions: | 11 | ["plugin:sitepush:view"] 12 | rules: 13 | - apiGroups: [ "sitepush.halo.run" ] 14 | resources: [ "pushLogs" , "pushUniques" ] 15 | verbs: [ "get", "list" ] 16 | --- 17 | apiVersion: v1alpha1 18 | kind: Role 19 | metadata: 20 | name: role-template-sitepush-manage 21 | labels: 22 | halo.run/role-template: "true" 23 | annotations: 24 | rbac.authorization.halo.run/module: "SitePush Management" 25 | rbac.authorization.halo.run/display-name: "站点推送管理" 26 | rbac.authorization.halo.run/ui-permissions: | 27 | ["plugin:sitepush:manage"] 28 | rbac.authorization.halo.run/dependencies: | 29 | ["role-template-sitepush-view"] 30 | rules: 31 | - apiGroups: [ "sitepush.halo.run" ] 32 | resources: [ "pushLogs" , "pushUniques" ] 33 | verbs: [ "create", "patch", "update", "delete", "deletecollection" ] -------------------------------------------------------------------------------- /src/main/resources/extensions/settings.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1alpha1 2 | kind: Setting 3 | metadata: 4 | name: plugin-sitepush-settings 5 | spec: 6 | forms: 7 | - group: basic 8 | label: 基本设置 9 | formSchema: 10 | - $formkit: radio 11 | name: enable 12 | id: enable 13 | key: enable 14 | label: 是否启用站点收录推送 15 | help: 禁用后右方的所有站点推送都将不生效 16 | value: false 17 | options: 18 | - label: 启用 19 | value: true 20 | - label: 关闭 21 | value: false 22 | 23 | - $formkit: text 24 | if: $get(enable).value 25 | name: siteUrl 26 | label: 站点地址 27 | placeholder: 请输入站点地址 28 | help: 启用后必填,格式:http(s)://(www.)example.com ,结尾无需加斜杠,未填写将无法推送 29 | 30 | - $formkit: number 31 | if: $get(enable).value 32 | name: retryInterval 33 | label: 重试间隔时间 (分钟) 34 | help: 默认为 360 分钟,设置为0代表不重试 35 | value: 360 36 | 37 | - $formkit: number 38 | if: $get(enable).value 39 | name: cleanOldLogDataDays 40 | label: 每 1 小时清理指定天数前的旧推送 log 数据 41 | help: 当值大于 0 才会清理,单位(天) 42 | value: 0 43 | - $formkit: number 44 | if: $get(enable).value 45 | name: cleanOldUniqueDataDays 46 | label: 每 1 小时清理指定天数前的旧推送 key 数据 47 | help: 当值大于 0 才会清理,单位(天),被清理的 url 后续会再次触发推送 48 | value: 0 49 | 50 | - group: baidu 51 | label: 百度收录推送 52 | formSchema: 53 | - $formkit: radio 54 | name: baiduEnableTagVerification 55 | id: baiduEnableTagVerification 56 | key: baiduEnableTagVerification 57 | label: 是否启用标签验证 58 | value: true 59 | options: 60 | - label: 是 61 | value: true 62 | - label: 否 63 | value: false 64 | 65 | - $formkit: text 66 | if: $get(baiduEnableTagVerification).value 67 | name: siteVerification 68 | label: 百度站点验证(验证码) 69 | placeholder: 格式:codeva-xxxxxxx 70 | help: 申请地址:https://ziyuan.baidu.com/site/siteadd,验证网站使用HTML标签验证,代码中的content就是需要填写的验证码,修改后1分钟生效 71 | 72 | - $formkit: radio 73 | name: baiduEnable 74 | id: baiduEnable 75 | key: baiduEnable 76 | label: 是否启用百度收录推送 77 | value: false 78 | options: 79 | - label: 启用 80 | value: true 81 | - label: 关闭 82 | value: false 83 | 84 | - $formkit: radio 85 | if: $get(baiduEnable).value 86 | name: baiduProxyEnable 87 | id: baiduProxyEnable 88 | key: baiduProxyEnable 89 | label: 是否启用代理 90 | value: false 91 | options: 92 | - label: 启用 93 | value: true 94 | - label: 关闭 95 | value: false 96 | 97 | - $formkit: radio 98 | if: $get(baiduProxyEnable).value 99 | name: baiduProxyType 100 | label: 百度代理类型 101 | value: HTTP 102 | options: 103 | - label: http 104 | value: HTTP 105 | - label: socks 106 | value: SOCKS 107 | 108 | - $formkit: text 109 | if: $get(baiduProxyEnable).value 110 | name: baiduProxyAddress 111 | label: 代理地址 112 | placeholder: 请输入代理地址 113 | 114 | - $formkit: text 115 | if: $get(baiduProxyEnable).value 116 | name: baiduProxyPort 117 | label: 代理端口 118 | placeholder: 请输入代理端口 119 | 120 | - $formkit: radio 121 | if: $get(baiduProxyEnable).value 122 | name: baiduProxyAuthEnable 123 | label: 是否启用代理验证 124 | id: baiduProxyAuthEnable 125 | key: baiduProxyAuthEnable 126 | value: false 127 | options: 128 | - label: 启用 129 | value: true 130 | - label: 关闭 131 | value: false 132 | 133 | - $formkit: text 134 | if: $get(baiduProxyAuthEnable).value 135 | name: baiduProxyUsername 136 | label: 代理用户名 137 | placeholder: 请输入代理用户名 138 | 139 | - $formkit: password 140 | if: $get(baiduProxyAuthEnable).value 141 | name: baiduProxyPassword 142 | label: 代理密码 143 | placeholder: 请输入代理密码 144 | 145 | - $formkit: text 146 | if: $get(baiduEnable).value 147 | name: token 148 | label: 百度推送token 149 | placeholder: 请输入token 150 | help: token(准入密钥)请前往 百度搜索资源平台(https://ziyuan.baidu.com/)-搜索服务-普通收录 获取,未填写将无法推送 151 | 152 | - group: bing 153 | label: 必应收录推送 154 | formSchema: 155 | - $formkit: radio 156 | name: bingEnableTagVerification 157 | id: bingEnableTagVerification 158 | key: bingEnableTagVerification 159 | label: 是否启用标签验证 160 | value: true 161 | options: 162 | - label: 是 163 | value: true 164 | - label: 否 165 | value: false 166 | 167 | - $formkit: text 168 | if: $get(bingEnableTagVerification).value 169 | name: bingSiteVerification 170 | label: bing站点验证(验证码) 171 | placeholder: 格式:FD2FCC31DFA40B22CF5FB9F88E8AC95A 172 | help: 申请地址:https://www.bing.com/webmasters,验证方式选择HTML Meta Tag,代码中的content就是需要填写的验证码,修改后1分钟生效 173 | 174 | - $formkit: radio 175 | name: bingEnable 176 | id: bingEnable 177 | key: bingEnable 178 | label: 是否启用必应(bing)收录推送 179 | value: false 180 | options: 181 | - label: 启用 182 | value: true 183 | - label: 关闭 184 | value: false 185 | 186 | - $formkit: radio 187 | if: $get(bingEnable).value 188 | name: bingProxyEnable 189 | id: bingProxyEnable 190 | key: bingProxyEnable 191 | label: 是否启用代理 192 | value: false 193 | options: 194 | - label: 启用 195 | value: true 196 | - label: 关闭 197 | value: false 198 | - $formkit: radio 199 | if: $get(bingProxyEnable).value 200 | name: bingProxyType 201 | label: 必应代理类型 202 | value: HTTP 203 | options: 204 | - label: http 205 | value: HTTP 206 | - label: socks 207 | value: SOCKS 208 | - $formkit: text 209 | if: $get(bingProxyEnable).value 210 | name: bingProxyAddress 211 | label: 代理地址 212 | placeholder: 请输入代理地址 213 | 214 | - $formkit: text 215 | if: $get(bingProxyEnable).value 216 | name: bingProxyPort 217 | label: 代理端口 218 | placeholder: 请输入代理端口 219 | 220 | - $formkit: radio 221 | if: $get(bingProxyEnable).value 222 | name: bingProxyAuthEnable 223 | label: 是否启用代理验证 224 | id: bingProxyAuthEnable 225 | key: bingProxyAuthEnable 226 | value: false 227 | options: 228 | - label: 启用 229 | value: true 230 | - label: 关闭 231 | value: false 232 | 233 | - $formkit: text 234 | if: $get(bingProxyAuthEnable).value 235 | name: bingProxyUsername 236 | label: 代理用户名 237 | placeholder: 请输入代理用户名 238 | 239 | - $formkit: password 240 | if: $get(bingProxyAuthEnable).value 241 | name: bingProxyPassword 242 | label: 代理密码 243 | placeholder: 请输入代理密码 244 | 245 | - $formkit: text 246 | if: $get(bingEnable).value 247 | name: apikey 248 | label: 必应apikey 249 | placeholder: 请输入apikey 250 | help: apikey请前往 Bing Webmaster Tools(https://www.bing.com/webmasters) -右上角齿轮图标设置 - API访问(管理凭据) - API密钥 获取,未填写将无法推送 251 | 252 | - group: google 253 | label: 谷歌收录推送 254 | formSchema: 255 | - $formkit: radio 256 | name: googleEnableTagVerification 257 | id: googleEnableTagVerification 258 | key: googleEnableTagVerification 259 | label: 是否启用标签验证 260 | value: true 261 | options: 262 | - label: 是 263 | value: true 264 | - label: 否 265 | value: false 266 | 267 | - $formkit: text 268 | if: $get(googleEnableTagVerification).value 269 | name: googleSiteVerification 270 | label: google站点验证(验证码) 271 | placeholder: 格式:3MKyENvsAfOqs9LL1hxJkxseEMZnwisZVaG8ezodv_E 272 | help: 申请地址:https://search.google.com/search-console,验证方式选择 HTML标记,代码中的content就是需要填写的验证码,修改后1分钟生效 273 | 274 | - $formkit: radio 275 | name: googleEnable 276 | id: googleEnable 277 | key: googleEnable 278 | label: 是否启用谷歌(google)收录推送 279 | value: false 280 | options: 281 | - label: 启用 282 | value: true 283 | - label: 关闭 284 | value: false 285 | 286 | - $formkit: radio 287 | if: $get(googleEnable).value 288 | name: googleProxyEnable 289 | id: googleProxyEnable 290 | key: googleProxyEnable 291 | label: 是否启用代理 292 | value: false 293 | options: 294 | - label: 启用 295 | value: true 296 | - label: 关闭 297 | value: false 298 | 299 | - $formkit: radio 300 | if: $get(googleProxyEnable).value 301 | name: googleProxyType 302 | label: 谷歌代理类型 303 | value: HTTP 304 | options: 305 | - label: http 306 | value: HTTP 307 | - label: socks 308 | value: SOCKS 309 | 310 | - $formkit: text 311 | if: $get(googleProxyEnable).value 312 | name: googleProxyAddress 313 | label: 谷歌代理地址 314 | placeholder: 请输入代理地址 315 | 316 | - $formkit: text 317 | if: $get(googleProxyEnable).value 318 | name: googleProxyPort 319 | label: 谷歌代理端口 320 | placeholder: 请输入代理端口 321 | 322 | - $formkit: radio 323 | if: $get(googleProxyEnable).value 324 | name: googleProxyAuthEnable 325 | label: 是否启用代理验证 326 | id: googleProxyAuthEnable 327 | key: googleProxyAuthEnable 328 | value: false 329 | options: 330 | - label: 启用 331 | value: true 332 | - label: 关闭 333 | value: false 334 | 335 | - $formkit: text 336 | if: $get(googleProxyAuthEnable).value 337 | name: googleProxyUsername 338 | label: 代理用户名 339 | placeholder: 请输入代理用户名 340 | 341 | - $formkit: password 342 | if: $get(googleProxyAuthEnable).value 343 | name: googleProxyPassword 344 | label: 代理密码 345 | placeholder: 请输入代理密码 346 | 347 | - $formkit: text 348 | if: $get(googleEnable).value 349 | name: credentialsJson 350 | label: google凭据json文件内容 351 | placeholder: 请将json文件的所有内容粘贴至此 352 | help: 教程地址:https://erzbir.com/archives/useGoogleIndexAPI 353 | 354 | 355 | 356 | 357 | -------------------------------------------------------------------------------- /src/main/resources/plugin.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: plugin.halo.run/v1alpha1 2 | kind: Plugin 3 | metadata: 4 | name: PluginSitePush 5 | spec: 6 | enabled: true 7 | requires: ">=2.12.0" 8 | author: 9 | name: StoneWu 10 | website: https://www.stonewu.com 11 | logo: https://source.stonewu.com/upload/site-push-logo.png 12 | # 'homepage' usually links to the GitHub repository of the plugin 13 | homepage: https://github.com/Stonewuu/halo-plugin-sitepush 14 | settingName: plugin-sitepush-settings 15 | configMapName: plugin-sitepush-config 16 | displayName: "站点推送插件" 17 | description: "该插件用于将站内页面主动推送至各大搜索引擎" 18 | license: 19 | - name: "GPL-3.0" 20 | url: "https://github.com/Stonewuu/halo-plugin-sitepush/blob/main/LICENSE" 21 | --------------------------------------------------------------------------------