From 202760882ec866f747e25adae9442b45a4315ba7 Mon Sep 17 00:00:00 2001 From: kuroahna Date: Tue, 12 Sep 2023 03:18:09 -0400 Subject: [PATCH] Initial commit --- .cargo/config.toml | 2 + .gitignore | 2 + Cargo.lock | 447 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 12 ++ LICENSE-APACHE | 176 ++++++++++++++++++ LICENSE-MIT | 21 +++ README.md | 76 ++++++++ src/lib.rs | 62 +++++++ src/textractor.rs | 113 ++++++++++++ src/websocket.rs | 42 +++++ 10 files changed, 953 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.md create mode 100644 src/lib.rs create mode 100644 src/textractor.rs create mode 100644 src/websocket.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..2063bbd --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "i686-pc-windows-gnu" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6db043d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..45f3b71 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,447 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +dependencies = [ + "byteorder", + "iovec", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-extras" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" +dependencies = [ + "lazycell", + "log", + "mio", + "slab", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "net2" +version = "0.2.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "textractor_websocket" +version = "0.1.0" +dependencies = [ + "widestring", + "winapi 0.3.9", + "ws", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "ws" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fe90c75f236a0a00247d5900226aea4f2d7b05ccc34da9e7a8880ff59b5848" +dependencies = [ + "byteorder", + "bytes", + "httparse", + "log", + "mio", + "mio-extras", + "rand", + "sha-1", + "slab", + "url", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f49d0cb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "textractor_websocket" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +widestring = { version = "1.0.2", default-features = false, features = ["std"] } +winapi = { version = "0.3.9", default-features = false, features = ["winnt", "minwindef"] } +ws = { version = "0.9.2", default-features = false } diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..ded81b1 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 kuroahna + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f90799d --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +# textractor_websocket + +## Description + +An extension for [Textractor](https://github.com/Artikash/Textractor) written in +Rust that opens a WebSocket locally on port `6677` and sends the text from +Textractor to all the connected clients. + +A WebSocket client such as +[texthooker-ui](https://github.com/Renji-XD/texthooker-ui) +can stream the text by the server and display it to your browser. + +## Build + +If you want to build the DLL yourself, you can follow the instructions below. +Otherwise, skip to the [Install](#install) section. + +Ensure you have Rust installed. The installation instructions can be found +[here](https://www.rust-lang.org/learn/get-started). Then you can build the +DLL with + +```bash +# For x64 +cargo build --release --target i686-pc-windows-gnu + +# For x86 +cargo build --release --target x86_64-pc-windows-gnu +``` + +## Install + +Pre-compiled binaries are available in the +[Releases](https://github.com/kuroahna/textractor_websocket/releases) page + +1. Download, unzip, and copy the + [x86 DLL](https://github.com/kuroahna/textractor_websocket/releases/latest/download/textractor_websocket_x86.zip) + file into your `Textractor/x86` folder +2. Download, unzip, and copy the + [x64 DLL](https://github.com/kuroahna/textractor_websocket/releases/latest/download/textractor_websocket_x64.zip) + file into your `Textractor/x64` folder +3. Open up `Textractor/x86/Textractor.exe`, click `Extensions`, right click + inside the `Extensions` dialog box, click `Add extension`, change the file + extension in the file picker dialog from `*.xdll` to `*.dll`, and select + `textractor_websocket_x86.dll` inside the `Textractor/x86` folder from Step + 1. +4. Open up `Textractor/x64/Textractor.exe`, click `Extensions`, right click + inside the `Extensions` dialog box, click `Add extension`, change the file + extension in the file picker dialog from `*.xdll` to `*.dll`, and select + `textractor_websocket_x64.dll` inside the `Textractor/x64` folder from Step + 2. + +
Expected file structure + +``` +Textractor +├── x64 + └── Textractor.exe + └── textractor_websocket_x64.dll + └── ... +├── x86 + └── Textractor.exe + └── textractor_websocket_x86.dll + └── ... +``` + +
+ +## Usage + +After installing the extension, when you start Textractor and have selected a +text thread, Textractor will automatically start the server at +`ws://localhost:6677` + +You will need a WebSocket client such as +[texthooker-ui](https://github.com/Renji-XD/texthooker-ui) +to stream the text and display it to your browser diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..12cc9d6 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,62 @@ +use std::net::SocketAddr; +use std::sync::OnceLock; + +use widestring::U16CString; +use winapi::shared::minwindef::{BOOL, DWORD, HINSTANCE, LPVOID, TRUE}; +use winapi::um::winnt::DLL_PROCESS_ATTACH; + +use crate::textractor::{CurrentSelect, InfoForExtension, SentenceInfo, TextNumber}; + +mod textractor; +mod websocket; + +static SERVER: OnceLock = OnceLock::new(); + +fn start_websocket_server() -> websocket::ServerStarted { + println!("Starting websocket server at `0.0.0.0:6677`"); + websocket::Server::new(SocketAddr::from(([0, 0, 0, 0], 6677))).start() +} + +#[no_mangle] +pub extern "C" fn OnNewSentence( + sentence: *const u16, + sentence_info: *const InfoForExtension, +) -> *const u16 { + let sentence_as_cstring: U16CString; + // SAFETY: Constructing a `U16Cstring` from `*const u16` is safe because + // Textractor should return a valid pointer when this function is called + unsafe { + sentence_as_cstring = U16CString::from_ptr_str(sentence); + } + + // We cannot assume that the sentence will always be a valid UTF-8 string + // because the text hook might be bad and contain random bytes + let sentence_as_lossy_string = sentence_as_cstring.to_string_lossy(); + let sentence_info = SentenceInfo::new(sentence_info); + let current_select = sentence_info.get_current_select(); + let text_number = sentence_info.get_text_number(); + if let CurrentSelect::UserSelectedTextThread(_) = current_select { + if let TextNumber::TextThread(_) = text_number { + SERVER + .get_or_init(start_websocket_server) + .send_message(sentence_as_lossy_string); + } + } + + sentence +} + +#[no_mangle] +pub extern "system" fn DllMain( + _h_module: HINSTANCE, + fdw_reason: DWORD, + _lpv_reserved: LPVOID, +) -> BOOL { + if fdw_reason == DLL_PROCESS_ATTACH { + SERVER + .set(start_websocket_server()) + .unwrap_or_else(|_| panic!("Websocket server should not have started yet")); + } + + TRUE +} diff --git a/src/textractor.rs b/src/textractor.rs new file mode 100644 index 0000000..3915fc3 --- /dev/null +++ b/src/textractor.rs @@ -0,0 +1,113 @@ +use std::ffi::{c_char, CStr}; + +enum PropertyName { + CurrentSelect, + TextNumber, +} + +impl PropertyName { + fn as_str(&self) -> &'static str { + match self { + PropertyName::CurrentSelect => "current select", + PropertyName::TextNumber => "text number", + } + } +} + +#[derive(PartialEq)] +pub enum CurrentSelect { + NotUserSelectedTextThread, + UserSelectedTextThread(i64), +} + +#[derive(PartialEq)] +pub enum TextNumber { + Console, + Clipboard, + TextThread(i64), +} + +#[repr(C)] +pub struct InfoForExtension { + name: *mut c_char, + value: i64, +} + +pub struct SentenceInfo { + info_array: *const InfoForExtension, +} + +impl SentenceInfo { + pub fn new(info_array: *const InfoForExtension) -> Self { + Self { info_array } + } + + pub fn get_current_select(&self) -> CurrentSelect { + let value = self + .get_property_value(PropertyName::CurrentSelect) + .unwrap_or_else(|| { + panic!( + "The sentence info array should always contain the property `{}`", + PropertyName::CurrentSelect.as_str() + ) + }); + // "current select": always 0 unless the sentence is in the text thread + // selected by the user + match value { + 0 => CurrentSelect::NotUserSelectedTextThread, + value => CurrentSelect::UserSelectedTextThread(value), + } + } + + pub fn get_text_number(&self) -> TextNumber { + let value = self + .get_property_value(PropertyName::TextNumber) + .unwrap_or_else(|| { + panic!( + "The sentence info array should always contain the property `{}`", + PropertyName::TextNumber.as_str() + ) + }); + // "text number": number of the current text thread. Counts up one by one + // as text threads are created. 0 for console, 1 for clipboard + match value { + 0 => TextNumber::Console, + 1 => TextNumber::Clipboard, + value => TextNumber::TextThread(value), + } + } + + fn get_property_value(&self, property_name: PropertyName) -> Option { + let mut pointer = self.info_array; + + while !pointer.is_null() { + // SAFETY: The pointer dereference is safe because we checked that + // the pointer is not null, and Textractor should provide a name and + // a value for each element in the info array. Also, constructing a + // CStr is safe because Textractor should provide a string with a + // nul terminator at the end of the string + let name = unsafe { + let name = (*pointer).name; + CStr::from_ptr(name).to_str().unwrap_or_else(|e| { + panic!( + "The property name `{}` is not a valid UTF-8 string: {:?}", + CStr::from_ptr(name).to_string_lossy(), + e + ) + }) + }; + // SAFETY: The pointer dereference is safe because we checked + // that the pointer is not null, and Textractor should provide + // a name and a value for each element in the info array + let value = unsafe { (*pointer).value }; + if name == property_name.as_str() { + return Some(value); + } + // SAFETY: The pointer addition is safe because we are adding 1 byte + // past the end of the allocated object + pointer = unsafe { pointer.add(1) } + } + + None + } +} diff --git a/src/websocket.rs b/src/websocket.rs new file mode 100644 index 0000000..41dc3e5 --- /dev/null +++ b/src/websocket.rs @@ -0,0 +1,42 @@ +use std::net::SocketAddr; +use std::sync::mpsc; +use std::sync::mpsc::Sender; +use std::thread; + +pub struct Server { + address: SocketAddr, +} + +pub struct ServerStarted { + sender: Sender, +} + +impl Server { + pub fn new(address: SocketAddr) -> Self { + Self { address } + } + + pub fn start(self) -> ServerStarted { + let (sender, receiver) = mpsc::channel(); + let server = ws::WebSocket::new(move |_| move |_| Ok(())) + .expect("The WebSocket factory method returns Ok()"); + let broadcaster = server.broadcaster(); + thread::spawn(move || { + server + .listen(self.address) + .unwrap_or_else(|e| panic!("The address `{}` is in use: {:?}", self.address, e)); + }); + thread::spawn(move || loop { + let msg = receiver.recv().unwrap(); + broadcaster.broadcast(msg).unwrap(); + }); + + ServerStarted { sender } + } +} + +impl ServerStarted { + pub fn send_message(&self, message: String) { + self.sender.send(message).unwrap(); + } +}