ForcomeBot 2.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- forcomebot-2.2.4.dist-info/METADATA +342 -0
- forcomebot-2.2.4.dist-info/RECORD +36 -0
- forcomebot-2.2.4.dist-info/WHEEL +4 -0
- forcomebot-2.2.4.dist-info/entry_points.txt +4 -0
- src/__init__.py +68 -0
- src/__main__.py +487 -0
- src/api/__init__.py +21 -0
- src/api/routes.py +775 -0
- src/api/websocket.py +280 -0
- src/auth/__init__.py +33 -0
- src/auth/database.py +87 -0
- src/auth/dingtalk.py +373 -0
- src/auth/jwt_handler.py +129 -0
- src/auth/middleware.py +260 -0
- src/auth/models.py +107 -0
- src/auth/routes.py +385 -0
- src/clients/__init__.py +7 -0
- src/clients/langbot.py +710 -0
- src/clients/qianxun.py +388 -0
- src/core/__init__.py +19 -0
- src/core/config_manager.py +411 -0
- src/core/log_collector.py +167 -0
- src/core/message_queue.py +364 -0
- src/core/state_store.py +242 -0
- src/handlers/__init__.py +8 -0
- src/handlers/message_handler.py +833 -0
- src/handlers/message_parser.py +325 -0
- src/handlers/scheduler.py +822 -0
- src/models.py +77 -0
- src/static/assets/index-B4i68B5_.js +50 -0
- src/static/assets/index-BPXisDkw.css +2 -0
- src/static/index.html +14 -0
- src/static/vite.svg +1 -0
- src/utils/__init__.py +13 -0
- src/utils/text_processor.py +166 -0
- src/utils/xml_parser.py +215 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:#fef2f2;--color-red-100:#ffe2e2;--color-red-200:#ffcaca;--color-red-300:#ffa3a3;--color-red-500:#fb2c36;--color-red-600:#e40014;--color-red-700:#bf000f;--color-red-800:#9f0712;--color-yellow-50:#fefce8;--color-yellow-100:#fef9c2;--color-yellow-200:#fff085;--color-yellow-500:#edb200;--color-yellow-600:#cd8900;--color-yellow-800:#874b00;--color-green-50:#f0fdf4;--color-green-100:#dcfce7;--color-green-200:#b9f8cf;--color-green-500:#00c758;--color-green-600:#00a544;--color-green-700:#008138;--color-green-800:#016630;--color-blue-50:#eff6ff;--color-blue-100:#dbeafe;--color-blue-200:#bedbff;--color-blue-300:#90c5ff;--color-blue-400:#54a2ff;--color-blue-500:#3080ff;--color-blue-600:#155dfc;--color-blue-700:#1447e6;--color-blue-800:#193cb8;--color-purple-100:#f3e8ff;--color-gray-50:#f9fafb;--color-gray-100:#f3f4f6;--color-gray-200:#e5e7eb;--color-gray-300:#d1d5dc;--color-gray-400:#99a1af;--color-gray-500:#6a7282;--color-gray-600:#4a5565;--color-gray-700:#364153;--color-gray-800:#1e2939;--color-gray-900:#101828;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wider:.05em;--radius-md:.375rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0,0,.2,1)infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}@supports (color:lab(0% 0 0)){:root,:host{--color-red-50:lab(96.5005% 4.18511 1.52329);--color-red-100:lab(92.243% 10.2865 3.83865);--color-red-200:lab(86.017% 19.8815 7.75869);--color-red-300:lab(76.5514% 36.4219 15.5335);--color-red-500:lab(55.4814% 75.0732 48.8528);--color-red-600:lab(48.4493% 77.4328 61.5452);--color-red-700:lab(40.4273% 67.2623 53.7441);--color-red-800:lab(33.7174% 55.8993 41.0293);--color-yellow-50:lab(98.6846% -1.79058 9.77662);--color-yellow-100:lab(97.3564% -4.51407 27.344);--color-yellow-200:lab(94.3433% -5.00426 52.9663);--color-yellow-500:lab(76.3898% 14.5258 98.4589);--color-yellow-600:lab(62.7799% 22.4198 86.1544);--color-yellow-800:lab(38.7484% 23.5833 51.4916);--color-green-50:lab(98.1563% -5.60117 2.75913);--color-green-100:lab(96.186% -13.8464 6.52362);--color-green-200:lab(92.4222% -26.4702 12.9427);--color-green-500:lab(70.5521% -66.5147 45.8072);--color-green-600:lab(59.0978% -58.6621 41.2579);--color-green-700:lab(47.0329% -47.0239 31.4788);--color-green-800:lab(37.4616% -36.7971 22.9692);--color-blue-50:lab(96.492% -1.14647 -5.11479);--color-blue-100:lab(92.0301% -2.24757 -11.6453);--color-blue-200:lab(86.15% -4.04379 -21.0797);--color-blue-300:lab(77.5052% -6.4629 -36.42);--color-blue-400:lab(65.0361% -1.42062 -56.9803);--color-blue-500:lab(54.1736% 13.3368 -74.6839);--color-blue-600:lab(44.0605% 29.0279 -86.0352);--color-blue-700:lab(36.9089% 35.0961 -85.6872);--color-blue-800:lab(30.2514% 27.7854 -70.2699);--color-purple-100:lab(93.3333% 6.9744 -9.83434);--color-gray-50:lab(98.2596% -.247031 -.706708);--color-gray-100:lab(96.1596% -.082314 -1.13575);--color-gray-200:lab(91.6229% -.159085 -2.26791);--color-gray-300:lab(85.1236% -.612259 -3.7138);--color-gray-400:lab(65.9269% -.832707 -8.17474);--color-gray-500:lab(47.7841% -.393212 -10.0268);--color-gray-600:lab(35.6337% -1.58697 -10.8425);--color-gray-700:lab(27.1134% -.956401 -12.3224);--color-gray-800:lab(16.1051% -1.18239 -11.7533);--color-gray-900:lab(8.11897% .811279 -12.254)}}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.-top-1{top:calc(var(--spacing)*-1)}.top-4{top:calc(var(--spacing)*4)}.-right-1{right:calc(var(--spacing)*-1)}.right-4{right:calc(var(--spacing)*4)}.z-50{z-index:50}.col-span-1{grid-column:span 1/span 1}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-3{margin-right:calc(var(--spacing)*3)}.-mb-px{margin-bottom:-1px}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.-ml-1{margin-left:calc(var(--spacing)*-1)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-4{margin-left:calc(var(--spacing)*4)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-2{height:calc(var(--spacing)*2)}.h-3{height:calc(var(--spacing)*3)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-8{height:calc(var(--spacing)*8)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-16{height:calc(var(--spacing)*16)}.h-64{height:calc(var(--spacing)*64)}.h-full{height:100%}.max-h-32{max-height:calc(var(--spacing)*32)}.max-h-80{max-height:calc(var(--spacing)*80)}.max-h-\[600px\]{max-height:600px}.min-h-\[38px\]{min-height:38px}.min-h-\[60px\]{min-height:60px}.min-h-full{min-height:100%}.min-h-screen{min-height:100vh}.w-2{width:calc(var(--spacing)*2)}.w-3{width:calc(var(--spacing)*3)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-9{width:calc(var(--spacing)*9)}.w-10{width:calc(var(--spacing)*10)}.w-12{width:calc(var(--spacing)*12)}.w-16{width:calc(var(--spacing)*16)}.w-20{width:calc(var(--spacing)*20)}.w-28{width:calc(var(--spacing)*28)}.w-full{width:100%}.max-w-7xl{max-width:var(--container-7xl)}.max-w-\[200px\]{max-width:200px}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.translate-x-0{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-4{--tw-translate-x:calc(var(--spacing)*4);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.rotate-90{rotate:90deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-ping{animation:var(--animate-ping)}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*1)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*3)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*4)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*8)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*8)*calc(1 - var(--tw-space-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-b-lg{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-b-md{border-bottom-right-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-blue-200{border-color:var(--color-blue-200)}.border-blue-300{border-color:var(--color-blue-300)}.border-blue-500{border-color:var(--color-blue-500)}.border-blue-600{border-color:var(--color-blue-600)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-green-200{border-color:var(--color-green-200)}.border-red-200{border-color:var(--color-red-200)}.border-red-300{border-color:var(--color-red-300)}.border-transparent{border-color:#0000}.border-yellow-200{border-color:var(--color-yellow-200)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-400{background-color:var(--color-gray-400)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-500{background-color:var(--color-green-500)}.bg-purple-100{background-color:var(--color-purple-100)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-600{background-color:var(--color-red-600)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-yellow-500{background-color:var(--color-yellow-500)}.object-cover{object-fit:cover}.p-1{padding:calc(var(--spacing)*1)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.py-12{padding-block:calc(var(--spacing)*12)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-3{padding-top:calc(var(--spacing)*3)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-800{color:var(--color-blue-800)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-white{color:var(--color-white)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-800{color:var(--color-yellow-800)}.uppercase{text-transform:uppercase}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-60{opacity:.6}.opacity-75{opacity:.75}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}@media (hover:hover){.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.hover\:border-blue-400:hover{border-color:var(--color-blue-400)}.hover\:border-gray-300:hover{border-color:var(--color-gray-300)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-gray-200:hover{background-color:var(--color-gray-200)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-gray-500:hover{color:var(--color-gray-500)}.hover\:text-gray-600:hover{color:var(--color-gray-600)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-red-700:hover{color:var(--color-red-700)}.hover\:text-red-800:hover{color:var(--color-red-800)}}.focus\:border-blue-500:focus{border-color:var(--color-blue-500)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-gray-500:focus{--tw-ring-color:var(--color-gray-500)}.focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-gray-100:disabled{background-color:var(--color-gray-100)}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width:40rem){.sm\:px-6{padding-inline:calc(var(--spacing)*6)}.sm\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:48rem){.md\:flex{display:flex}.md\:hidden{display:none}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}}}:root{font-family:system-ui,Avenir,Helvetica,Arial,sans-serif;font-weight:400;line-height:1.5}body{min-width:320px;min-height:100vh;margin:0}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}
|
src/static/index.html
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/app/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>FORCOME 康康</title>
|
|
8
|
+
<script type="module" crossorigin src="/app/assets/index-B4i68B5_.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/app/assets/index-BPXisDkw.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="root"></div>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
src/static/vite.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
src/utils/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Utils layer
|
|
2
|
+
"""Utility modules for text processing and XML parsing."""
|
|
3
|
+
|
|
4
|
+
from .text_processor import TextProcessor
|
|
5
|
+
from .xml_parser import XMLParser, QuoteMessageResult, PatMessageResult, VoiceInfo
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'TextProcessor',
|
|
9
|
+
'XMLParser',
|
|
10
|
+
'QuoteMessageResult',
|
|
11
|
+
'PatMessageResult',
|
|
12
|
+
'VoiceInfo',
|
|
13
|
+
]
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""文本处理器 - 处理换行符和emoji编解码
|
|
2
|
+
|
|
3
|
+
保持与原有业务逻辑完全一致:
|
|
4
|
+
- 千寻框架 sendText API 的换行符处理:
|
|
5
|
+
- \n 会显示为两行(有空行/段落间距)
|
|
6
|
+
- \r 会显示为单行换行(无空行)
|
|
7
|
+
- Emoji 使用 \\uXXXX 格式编码(含 surrogate pair)
|
|
8
|
+
"""
|
|
9
|
+
import re
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TextProcessor:
|
|
16
|
+
"""文本处理器 - 保持原有逻辑"""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def encode_for_qianxun(text: str) -> str:
|
|
20
|
+
"""编码文本用于发送到千寻框架
|
|
21
|
+
|
|
22
|
+
换行符处理(保持原逻辑):
|
|
23
|
+
- \n 会显示为两行(有空行)
|
|
24
|
+
- \r 会显示为单行换行(无空行)
|
|
25
|
+
|
|
26
|
+
处理步骤:
|
|
27
|
+
1. 先统一所有换行符为 \n
|
|
28
|
+
2. 临时标记双换行(空行)
|
|
29
|
+
3. 单换行 → \r
|
|
30
|
+
4. 双换行(空行)→ \n
|
|
31
|
+
5. emoji编码
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
text: 原始文本
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
编码后的文本,可直接发送到千寻框架
|
|
38
|
+
"""
|
|
39
|
+
# 先统一为 \n
|
|
40
|
+
text = text.replace('\r\n', '\n').replace('\r', '\n')
|
|
41
|
+
# 临时标记双换行(空行)
|
|
42
|
+
text = text.replace('\n\n', '\x00')
|
|
43
|
+
# 单换行 → \r
|
|
44
|
+
text = text.replace('\n', '\r')
|
|
45
|
+
# 双换行(空行)→ \n(千寻会显示为两行)
|
|
46
|
+
text = text.replace('\x00', '\n')
|
|
47
|
+
# emoji编码
|
|
48
|
+
text = TextProcessor.encode_emoji(text)
|
|
49
|
+
return text
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def encode_emoji(text: str) -> str:
|
|
53
|
+
"""把emoji等非BMP字符转换为\\uXXXX格式
|
|
54
|
+
|
|
55
|
+
千寻框架需要 \\uXXXX 格式的 emoji 转义
|
|
56
|
+
非 BMP 字符(emoji 等)需要转换为 surrogate pair
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
text: 原始文本
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
编码后的文本
|
|
63
|
+
"""
|
|
64
|
+
result = []
|
|
65
|
+
for char in text:
|
|
66
|
+
code = ord(char)
|
|
67
|
+
# 非 BMP 字符(emoji 等)需要转换为 surrogate pair
|
|
68
|
+
if code > 0xFFFF:
|
|
69
|
+
code -= 0x10000
|
|
70
|
+
high = 0xD800 + (code >> 10)
|
|
71
|
+
low = 0xDC00 + (code & 0x3FF)
|
|
72
|
+
result.append(f'\\u{high:04X}\\u{low:04X}')
|
|
73
|
+
else:
|
|
74
|
+
result.append(char)
|
|
75
|
+
return ''.join(result)
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def decode_emoji(text: str) -> str:
|
|
79
|
+
"""解码\\uXXXX格式为真正的字符
|
|
80
|
+
|
|
81
|
+
处理两种情况:
|
|
82
|
+
1. surrogate pair 格式的 emoji: \\uD83D\\uDE00 -> 😀
|
|
83
|
+
2. 普通 Unicode 转义: \\u4F60 -> 你
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
text: 包含\\uXXXX格式的文本
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
解码后的文本
|
|
90
|
+
"""
|
|
91
|
+
if not text or '\\u' not in text:
|
|
92
|
+
return text
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
# 先处理 surrogate pair 格式的 emoji
|
|
96
|
+
def replace_surrogate(match):
|
|
97
|
+
try:
|
|
98
|
+
high = int(match.group(1), 16)
|
|
99
|
+
low = int(match.group(2), 16)
|
|
100
|
+
# 验证是否是有效的 surrogate pair
|
|
101
|
+
if 0xD800 <= high <= 0xDBFF and 0xDC00 <= low <= 0xDFFF:
|
|
102
|
+
# 转换为真正的 Unicode 字符
|
|
103
|
+
code_point = 0x10000 + ((high - 0xD800) << 10) + (low - 0xDC00)
|
|
104
|
+
return chr(code_point)
|
|
105
|
+
return match.group(0)
|
|
106
|
+
except:
|
|
107
|
+
return match.group(0)
|
|
108
|
+
|
|
109
|
+
# 匹配 \uXXXX\uXXXX 格式的 surrogate pair
|
|
110
|
+
surrogate_pattern = r'\\u([dD][89aAbB][0-9a-fA-F]{2})\\u([dD][cCdDeEfF][0-9a-fA-F]{2})'
|
|
111
|
+
result = re.sub(surrogate_pattern, replace_surrogate, text)
|
|
112
|
+
|
|
113
|
+
# 再处理剩余的普通 \uXXXX 格式(非 surrogate)
|
|
114
|
+
def replace_unicode(match):
|
|
115
|
+
try:
|
|
116
|
+
code = int(match.group(1), 16)
|
|
117
|
+
# 跳过 surrogate 范围(已经处理过了)
|
|
118
|
+
if 0xD800 <= code <= 0xDFFF:
|
|
119
|
+
return match.group(0)
|
|
120
|
+
return chr(code)
|
|
121
|
+
except:
|
|
122
|
+
return match.group(0)
|
|
123
|
+
|
|
124
|
+
unicode_pattern = r'\\u([0-9a-fA-F]{4})'
|
|
125
|
+
result = re.sub(unicode_pattern, replace_unicode, result)
|
|
126
|
+
|
|
127
|
+
return result
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.debug(f"解码 Unicode 转义失败: {e}")
|
|
130
|
+
return text
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def config_to_text(text: str) -> str:
|
|
134
|
+
"""配置文件中的\\n字符串转换为真正的换行符
|
|
135
|
+
|
|
136
|
+
配置文件中存储的是字面量 \\n(两个字符),
|
|
137
|
+
需要转换为真正的换行符 \n(一个字符)
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
text: 配置文件中的文本
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
转换后的文本
|
|
144
|
+
"""
|
|
145
|
+
return text.replace('\\n', '\n')
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def text_to_config(text: str) -> str:
|
|
149
|
+
"""文本中的换行符转换为配置文件格式
|
|
150
|
+
|
|
151
|
+
将真正的换行符转换为字面量 \\n,用于保存到配置文件
|
|
152
|
+
保留空行,只去除每行末尾空格
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
text: 原始文本
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
配置文件格式的文本
|
|
159
|
+
"""
|
|
160
|
+
# 统一换行符
|
|
161
|
+
text = text.replace('\r\n', '\n').replace('\r', '')
|
|
162
|
+
if '\n' in text:
|
|
163
|
+
# 保留空行,只去除每行末尾空格
|
|
164
|
+
lines = [line.rstrip() for line in text.split('\n')]
|
|
165
|
+
return '\\n'.join(lines)
|
|
166
|
+
return text
|
src/utils/xml_parser.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""XML解析工具 - 解析微信消息中的XML内容
|
|
2
|
+
|
|
3
|
+
支持解析:
|
|
4
|
+
- 引用消息(检查是否引用机器人)
|
|
5
|
+
- 拍一拍消息
|
|
6
|
+
- 语音消息
|
|
7
|
+
"""
|
|
8
|
+
import re
|
|
9
|
+
import logging
|
|
10
|
+
import xml.etree.ElementTree as ET
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class QuoteMessageResult:
|
|
19
|
+
"""引用消息解析结果"""
|
|
20
|
+
user_msg: str # 用户发送的新消息
|
|
21
|
+
quoted_text: str # 被引用的消息内容
|
|
22
|
+
sender_name: str # 被引用消息发送者的昵称
|
|
23
|
+
quoted_image_path: Optional[str] = None # 引用的图片路径(如果引用的是图片消息)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class PatMessageResult:
|
|
28
|
+
"""拍一拍消息解析结果"""
|
|
29
|
+
from_user: str # 发起拍一拍的用户
|
|
30
|
+
patted_user: str # 被拍的用户
|
|
31
|
+
is_pat_me: bool # 是否拍的是机器人
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class VoiceInfo:
|
|
36
|
+
"""语音消息信息"""
|
|
37
|
+
voicelength: int # 语音长度(毫秒)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class XMLParser:
|
|
41
|
+
"""XML解析工具"""
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def parse_quote_message(xml_content: str, robot_wxid: str) -> Optional[QuoteMessageResult]:
|
|
45
|
+
"""解析引用消息,如果引用的是机器人的消息则返回解析结果
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
xml_content: 引用消息的XML内容
|
|
49
|
+
robot_wxid: 机器人wxid,用于检查是否引用机器人
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
QuoteMessageResult 如果引用的是机器人消息,否则返回 None
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
root = ET.fromstring(xml_content)
|
|
56
|
+
|
|
57
|
+
# 获取引用信息
|
|
58
|
+
refermsg = root.find('.//refermsg')
|
|
59
|
+
if refermsg is None:
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
# 检查被引用消息的发送者是否是机器人
|
|
63
|
+
chatusr = refermsg.find('chatusr')
|
|
64
|
+
if chatusr is None or chatusr.text != robot_wxid:
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
# 获取用户发送的新消息(title标签)
|
|
68
|
+
title = root.find('.//title')
|
|
69
|
+
user_msg = title.text.strip() if title is not None and title.text else ""
|
|
70
|
+
|
|
71
|
+
# 获取被引用的消息内容
|
|
72
|
+
quote_content = refermsg.find('content')
|
|
73
|
+
quoted_text = quote_content.text.strip() if quote_content is not None and quote_content.text else ""
|
|
74
|
+
|
|
75
|
+
# 获取被引用消息发送者的昵称
|
|
76
|
+
displayname = refermsg.find('displayname')
|
|
77
|
+
sender_name = displayname.text.strip() if displayname is not None and displayname.text else "未知"
|
|
78
|
+
|
|
79
|
+
if user_msg:
|
|
80
|
+
return QuoteMessageResult(
|
|
81
|
+
user_msg=user_msg,
|
|
82
|
+
quoted_text=quoted_text,
|
|
83
|
+
sender_name=sender_name
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return None
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logger.error(f"解析引用消息XML失败: {e}")
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def parse_quote_message_any(xml_content: str) -> Optional[QuoteMessageResult]:
|
|
93
|
+
"""解析引用消息(不检查被引用者)
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
xml_content: 引用消息的XML内容
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
QuoteMessageResult 包含用户消息、引用内容、发送者昵称和可能的图片路径
|
|
100
|
+
"""
|
|
101
|
+
try:
|
|
102
|
+
root = ET.fromstring(xml_content)
|
|
103
|
+
|
|
104
|
+
# 获取用户发送的新消息
|
|
105
|
+
title = root.find('.//title')
|
|
106
|
+
user_msg = title.text.strip() if title is not None and title.text else ""
|
|
107
|
+
|
|
108
|
+
# 获取被引用的消息内容和发送者昵称
|
|
109
|
+
refermsg = root.find('.//refermsg')
|
|
110
|
+
quoted_text = ""
|
|
111
|
+
sender_name = "未知"
|
|
112
|
+
quoted_image_path = None
|
|
113
|
+
|
|
114
|
+
if refermsg is not None:
|
|
115
|
+
quote_content = refermsg.find('content')
|
|
116
|
+
quoted_text = quote_content.text.strip() if quote_content is not None and quote_content.text else ""
|
|
117
|
+
displayname = refermsg.find('displayname')
|
|
118
|
+
sender_name = displayname.text.strip() if displayname is not None and displayname.text else "未知"
|
|
119
|
+
|
|
120
|
+
# 检查引用的是否是图片消息
|
|
121
|
+
# 图片消息的 type 为 3,content 格式为 [pic=路径,isDecrypt=x]
|
|
122
|
+
msg_type = refermsg.find('type')
|
|
123
|
+
if msg_type is not None and msg_type.text == '3':
|
|
124
|
+
# 解析图片路径
|
|
125
|
+
match = re.search(r'\[pic=([^,\]]+)', quoted_text)
|
|
126
|
+
if match:
|
|
127
|
+
quoted_image_path = match.group(1)
|
|
128
|
+
quoted_text = "[图片]" # 替换为友好文本
|
|
129
|
+
|
|
130
|
+
if user_msg:
|
|
131
|
+
return QuoteMessageResult(
|
|
132
|
+
user_msg=user_msg,
|
|
133
|
+
quoted_text=quoted_text,
|
|
134
|
+
sender_name=sender_name,
|
|
135
|
+
quoted_image_path=quoted_image_path
|
|
136
|
+
)
|
|
137
|
+
return None
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.error(f"解析引用消息XML失败: {e}")
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def parse_pat_message(xml_content: str, robot_wxid: str) -> Optional[PatMessageResult]:
|
|
144
|
+
"""解析拍一拍消息(XML格式,私聊或部分群聊)
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
xml_content: 拍一拍消息的XML内容
|
|
148
|
+
robot_wxid: 机器人wxid,用于判断是否拍的是机器人
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
PatMessageResult 解析结果
|
|
152
|
+
"""
|
|
153
|
+
try:
|
|
154
|
+
root = ET.fromstring(xml_content)
|
|
155
|
+
|
|
156
|
+
pat = root.find('pat')
|
|
157
|
+
if pat is None:
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
from_user = pat.findtext('fromusername', '')
|
|
161
|
+
patted_user = pat.findtext('pattedusername', '')
|
|
162
|
+
|
|
163
|
+
return PatMessageResult(
|
|
164
|
+
from_user=from_user,
|
|
165
|
+
patted_user=patted_user,
|
|
166
|
+
is_pat_me=patted_user == robot_wxid
|
|
167
|
+
)
|
|
168
|
+
except Exception as e:
|
|
169
|
+
logger.error(f"解析拍一拍XML失败: {e}")
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def parse_pat_text(text: str, robot_wxid: str) -> Optional[PatMessageResult]:
|
|
174
|
+
"""解析文本格式的拍一拍消息(群聊)
|
|
175
|
+
|
|
176
|
+
群聊拍一拍文本格式: "xxx" 拍了拍 "yyy"
|
|
177
|
+
由于文本中没有wxid,无法精确判断是否拍的是机器人,
|
|
178
|
+
假设收到的拍一拍消息都是拍机器人的
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
text: 拍一拍消息文本
|
|
182
|
+
robot_wxid: 机器人wxid(用于判断,但文本中没有wxid)
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
PatMessageResult 解析结果
|
|
186
|
+
"""
|
|
187
|
+
if "拍了拍" in text:
|
|
188
|
+
return PatMessageResult(
|
|
189
|
+
from_user="", # 群聊文本格式无法获取wxid
|
|
190
|
+
patted_user="",
|
|
191
|
+
is_pat_me=True # 假设收到的拍一拍都是拍机器人的
|
|
192
|
+
)
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def parse_voice_info(xml_content: str) -> Optional[VoiceInfo]:
|
|
197
|
+
"""解析语音消息
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
xml_content: 语音消息的XML内容
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
VoiceInfo 语音信息
|
|
204
|
+
"""
|
|
205
|
+
try:
|
|
206
|
+
root = ET.fromstring(xml_content)
|
|
207
|
+
voicemsg = root.find('voicemsg')
|
|
208
|
+
if voicemsg is not None:
|
|
209
|
+
return VoiceInfo(
|
|
210
|
+
voicelength=int(voicemsg.get('voicelength', 0))
|
|
211
|
+
)
|
|
212
|
+
return None
|
|
213
|
+
except Exception as e:
|
|
214
|
+
logger.error(f"解析语音XML失败: {e}")
|
|
215
|
+
return None
|