From 6972f384c47405e251417fc8221008575e897c1c Mon Sep 17 00:00:00 2001 From: sose Date: Fri, 23 Apr 2021 16:43:02 -0700 Subject: [PATCH] first commit --- .gitignore | 2 + gen_cert.sh | 10 + hellgate.js | 130 ++++++ license | 674 +++++++++++++++++++++++++++++ mandatory-js-project-logo.png | Bin 0 -> 39576 bytes package-lock.json | 773 ++++++++++++++++++++++++++++++++++ package.json | 20 + preload.js | 82 ++++ readme.md | 50 +++ sigils/*.sr.ht | 104 +++++ sigils/default | 1 + sigils/github.com | 74 ++++ sigils/gitlab.com | 76 ++++ sigils/tildegit.org | 115 +++++ tls/cert.pem | 29 ++ tls/key.pem | 52 +++ writing_sigils.md | 79 ++++ 17 files changed, 2271 insertions(+) create mode 100644 .gitignore create mode 100755 gen_cert.sh create mode 100644 hellgate.js create mode 100644 license create mode 100644 mandatory-js-project-logo.png create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 preload.js create mode 100644 readme.md create mode 100644 sigils/*.sr.ht create mode 100644 sigils/default create mode 100644 sigils/github.com create mode 100644 sigils/gitlab.com create mode 100644 sigils/tildegit.org create mode 100644 tls/cert.pem create mode 100644 tls/key.pem create mode 100644 writing_sigils.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6ec74b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +*.swp diff --git a/gen_cert.sh b/gen_cert.sh new file mode 100755 index 0000000..6ba8202 --- /dev/null +++ b/gen_cert.sh @@ -0,0 +1,10 @@ +#!/bin/sh +mkdir tls +openssl req \ + -x509 \ + -newkey rsa:4096 \ + -keyout tls/key.pem \ + -out tls/cert.pem \ + -days 365 \ + -nodes \ + -subj '/CN=localhost' \ diff --git a/hellgate.js b/hellgate.js new file mode 100644 index 0000000..b6bc461 --- /dev/null +++ b/hellgate.js @@ -0,0 +1,130 @@ +const { ipcMain, app, BrowserWindow } = require("electron") +const gemini = require("gemini-server") +const fs = require("fs") +const url = require("url") +const path = require("path") + +const sigils_dir = "sigils" + +const electron_options = { + show: false, + webPreferences: { + sandbox: true, + contextIsolation: true, + nodeIntegration: false, + enableRemoteModule: false, + webgl: false, + disableDialogs: true, + preload: path.join(__dirname, "preload.js"), + }, +} + +const server_options = { + cert: fs.readFileSync('tls/cert.pem'), + key: fs.readFileSync('tls/key.pem') +} + +const server = gemini(server_options) + + +async function handle_request(request, response) { + if (!request.query) { + response.data("Please supply a url in the query string.") + return + } + const target_url = request.query + const win = new BrowserWindow(electron_options) + + const load_timeout_ms = 2000 + const exec_timeout_ms = 2000 + + async function timeout(timeout_ms, reason = "") { + return new Promise((resolve, reject) => { + setTimeout( + reject, + timeout_ms, + reason + " timed out after " + timeout_ms + " miliseconds" + ) + }) + } + + var error + var current_url + var page_sigil_name + var page_sigil + var data = "" + var available_sigils = fs.readdirSync(sigils_dir) + + await Promise.race([ + win.loadURL(target_url) + .catch((err) => { // there was an error loading the page + if (err.errno !== -3) { + error = err.message + } }), + timeout(load_timeout_ms) + ]).catch(() => { // loading timed out + win.webContents.stop() + }) + + + try { + current_url = new URL(win.webContents.getURL()) + } catch (err) { + error = err.code + response.data(error) + win.destroy() + return + } + + for (let sigil_name of available_sigils) { + if (current_url.host === sigil_name) { + page_sigil_name = sigil_name + break + } else if ( + sigil_name[0] === "*" + && current_url.host.endsWith(sigil_name.replace("*.", "")) + ){ + page_sigil_name = sigil_name + break + } + } + + if (! page_sigil_name) { + page_sigil = fs.readFileSync(sigils_dir + "/default") + } else { + page_sigil = fs.readFileSync(sigils_dir + "/" + page_sigil_name) + } + + ipcMain.on("page-gemtext", (event, arg) => { + data += arg + }) + + await Promise.race([ + win.webContents.executeJavaScript(page_sigil).catch( + (err) => error = err + ), + + timeout(exec_timeout_ms, "Script execution") + ]).catch((err) => error = err) + + ipcMain.removeAllListeners() + + win.destroy() + + if (error) { + response.data(error) + } else { + response.data(data, mimeType="text/gemini") + } +} + +app.on('window-all-closed', () => { + ; +}) + +app.whenReady().then(() => { + server.on("*", handle_request) + server.listen(() => { + console.log("listening on localhost:1965") + }) +}); diff --git a/license b/license new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/license @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/mandatory-js-project-logo.png b/mandatory-js-project-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4928314b12095fc4c4cc55c8a17f1e1922708878 GIT binary patch literal 39576 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%0DI* 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 V `3))4Szcg7K0cgpYb{{Yz4w#EPe literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..965b46e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,773 @@ +{ + "name": "hellgate", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@electron/get": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.12.4.tgz", + "integrity": "sha512-6nr9DbJPUR9Xujw6zD3y+rS95TyItEVM0NVjt1EehY2vUWfIgPiIPVHxCvaTS0xr2B+DRxovYVKbuOWqC35kjg==", + "dev": true, + "requires": { + "debug": "4.3.1", + "env-paths": "2.2.1", + "fs-extra": "8.1.0", + "global-agent": "2.2.0", + "global-tunnel-ng": "2.7.1", + "got": "9.6.0", + "progress": "2.0.3", + "semver": "6.3.0", + "sumchecker": "3.0.1" + } + }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "requires": { + "defer-to-connect": "1.1.3" + } + }, + "@types/node": { + "version": "14.14.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz", + "integrity": "sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g==", + "dev": true + }, + "boolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.3.tgz", + "integrity": "sha512-EqrTKXQX6Z3A2nRmMEIlAIfjQOgFnVO2nqZGpbcsPnYGWBwpFqzlrozU1dy+S2iqfYDLh26ef4KrgTxu9xQrxA==", + "dev": true + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "requires": { + "clone-response": "1.0.2", + "get-stream": "5.2.0", + "http-cache-semantics": "4.1.0", + "keyv": "3.1.0", + "lowercase-keys": "2.0.0", + "normalize-url": "4.5.0", + "responselike": "1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + } + } + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "requires": { + "mimic-response": "1.0.1" + } + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "1.1.1", + "inherits": "2.0.4", + "readable-stream": "2.3.7", + "typedarray": "0.0.6" + } + }, + "config-chain": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", + "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", + "dev": true, + "optional": true, + "requires": { + "ini": "1.3.8", + "proto-list": "1.2.4" + } + }, + "core-js": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.10.2.tgz", + "integrity": "sha512-W+2oVYeNghuBr3yTzZFQ5rfmjZtYB/Ubg87R5YOmlGrIb+Uw9f7qjUbhsj+/EkXhcV7eOD3jiM4+sgraX3FZUw==", + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "1.0.1" + } + }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "optional": true, + "requires": { + "object-keys": "1.1.1" + } + }, + "detect-node": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.5.tgz", + "integrity": "sha512-qi86tE6hRcFHy8jI1m2VG+LaPUR1LhqDa5G8tVjuUXmOrpuAgqsA1pN0+ldgr3aKUH+QLI9hCY/OcRYisERejw==", + "dev": true, + "optional": true + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "electron": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/electron/-/electron-12.0.5.tgz", + "integrity": "sha512-z0xYB3sPr0qZcDrHUUWqooPKe3yUzBDxQcgQe3f2TLstA84JIFXBoaIJCPh/fJW0+JdF/ZFVeK2SNgLhYtRV+Q==", + "dev": true, + "requires": { + "@electron/get": "1.12.4", + "@types/node": "14.14.41", + "extract-zip": "1.7.0" + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true, + "optional": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "optional": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "optional": true + }, + "extract-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "dev": true, + "requires": { + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.5", + "yauzl": "2.10.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "1.2.0" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "4.2.6", + "jsonfile": "4.0.0", + "universalify": "0.1.2" + } + }, + "gemini-server": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gemini-server/-/gemini-server-1.2.0.tgz", + "integrity": "sha512-yfsh4CoG+irW0ek6GK8VS2kf3hC5fUrp+iIruDwQv0KdEUe3S8pToS3XQ2wKOBDv43h5pBQfFJUzkLez3qRHgQ==", + "dev": true, + "requires": { + "mime": "2.5.2", + "path-to-regexp": "6.2.0", + "truncate-utf8-bytes": "1.0.2" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "3.0.0" + } + }, + "global-agent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.2.0.tgz", + "integrity": "sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==", + "dev": true, + "optional": true, + "requires": { + "boolean": "3.0.3", + "core-js": "3.10.2", + "es6-error": "4.1.1", + "matcher": "3.0.0", + "roarr": "2.15.4", + "semver": "7.3.5", + "serialize-error": "7.0.1" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "optional": true, + "requires": { + "lru-cache": "6.0.0" + } + } + } + }, + "global-tunnel-ng": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz", + "integrity": "sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==", + "dev": true, + "optional": true, + "requires": { + "encodeurl": "1.0.2", + "lodash": "4.17.21", + "npm-conf": "1.1.3", + "tunnel": "0.0.6" + } + }, + "globalthis": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.2.tgz", + "integrity": "sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ==", + "dev": true, + "optional": true, + "requires": { + "define-properties": "1.1.3" + } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "requires": { + "@sindresorhus/is": "0.14.0", + "@szmarczak/http-timer": "1.1.2", + "cacheable-request": "6.1.0", + "decompress-response": "3.3.0", + "duplexer3": "0.1.4", + "get-stream": "4.1.0", + "lowercase-keys": "1.0.1", + "mimic-response": "1.0.1", + "p-cancelable": "1.1.0", + "to-readable-stream": "1.0.0", + "url-parse-lax": "3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "dev": true + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true, + "optional": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "4.2.6" + } + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "optional": true + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "optional": true, + "requires": { + "yallist": "4.0.0" + } + }, + "matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "optional": true, + "requires": { + "escape-string-regexp": "4.0.0" + } + }, + "mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true + }, + "npm-conf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", + "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", + "dev": true, + "optional": true, + "requires": { + "config-chain": "1.1.12", + "pify": "3.0.0" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true + }, + "path-to-regexp": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.0.tgz", + "integrity": "sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg==", + "dev": true + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "optional": true + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "dev": true, + "optional": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "1.4.4", + "once": "1.4.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.4", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "requires": { + "lowercase-keys": "1.0.1" + } + }, + "roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "optional": true, + "requires": { + "boolean": "3.0.3", + "detect-node": "2.0.5", + "globalthis": "1.0.2", + "json-stringify-safe": "5.0.1", + "semver-compare": "1.0.0", + "sprintf-js": "1.1.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true, + "optional": true + }, + "serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "optional": true, + "requires": { + "type-fest": "0.13.1" + } + }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true, + "optional": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "requires": { + "debug": "4.3.1" + } + }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true + }, + "truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", + "dev": true, + "requires": { + "utf8-byte-length": "1.0.4" + } + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "optional": true + }, + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "requires": { + "prepend-http": "2.0.0" + } + }, + "utf8-byte-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", + "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "optional": true + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "0.2.13", + "fd-slicer": "1.1.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..02c592c --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "hellgate", + "version": "1.0.0", + "description": "", + "main": "hellgate.js", + "scripts": { + "start": "electron ." + }, + "keywords": [ + "hell", + "gate", + "hellgate" + ], + "author": "sose", + "license": "GPL3", + "devDependencies": { + "electron": "^12.0.5", + "gemini-server": "^1.2.0" + } +} diff --git a/preload.js b/preload.js new file mode 100644 index 0000000..47446d4 --- /dev/null +++ b/preload.js @@ -0,0 +1,82 @@ +const { contextBridge, ipcRenderer } = require("electron") + +window.stop() + +function md_parse(selector) { + var data = "" + var readme_elements = document.querySelector(selector).children + for (let i = 0; i < readme_elements.length; i++) { + let element = readme_elements[i] + + let tag_name = element.tagName + if (tag_name.startsWith("H") && tag_name.length === 2) { + let header_lvl = tag_name[1] + data += "\n" + "#".repeat(header_lvl) + " " + element.innerText + } else if (tag_name === "P") { + data += element.innerText + } else if (tag_name === "UL") { + let list_items = element.children + for (let j = 0; j < list_items.length ; j++) { + list_item = list_items[j] + data += "\n* " + list_item.innerText.replaceAll("\n", "\n\t - ") + } + } else if (tag_name === "PRE") { + data += "```\n" + element.innerText + "\n```\n" + } else { + data += "```\n" + element.innerText + "\n```\n" + } + data += "\n" + } + return data +} +function escape_gemtext(string) { + return string.replaceAll(/\n\*/g, "\n *") + .replaceAll(/\n>/g, "\n >") + .replaceAll(/\n#/g, "\n #") + .replaceAll(/^```/g, " ```") + .replaceAll(/\n```/g, "\n ```") +} + +function abs_href(href) { + var current_site = window.location.protocol + "//" + window.location.hostname + if (href.indexOf("http://") === 0 || href.indexOf("https://") === 0) { + return "?" + href + } else if (href.indexOf("gemini://") === 0) { + return href + } else if (href.startsWith("/")) { + return "?" + current_site + href + } else { + return "?" + window.location.href + href + } +} + +function simple_convert() { + var title = window.document.title + var link_elements = document.getElementsByTagName("a") + var pre_elements = document.getElementsByTagName("pre") + var gemtext + if (title) { title = "# " + title + "\n\n" } + for (let link_element of link_elements) { + let href = link_element.getAttribute("href") + if (href === null) { continue } + link_element.innerText = "\n=> " + abs_href(href) + " " + link_element.innerText + "\n" + } + gemtext = escape_gemtext(document.getRootNode().children[0].innerText) + return title + gemtext +} + +contextBridge.exposeInMainWorld( + "electron", + { + send_gemtext: (gemtext) => { + ipcRenderer.send("page-gemtext", gemtext) + }, + request_sigil: (sigil_name) => { + return ipcRenderer.invoke("request-sigil", sigil_name) + }, + default_sigil: simple_convert, + escape_gemtext: escape_gemtext, + abs_href: abs_href, + md_parse: md_parse, + } +) diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..87f95b2 --- /dev/null +++ b/readme.md @@ -0,0 +1,50 @@ +# hellgate +[!hellgate logo](mandatory-js-project-logo.png) +a complete web-to-gemini proxy + +## requirements +- nodejs +- npm +- electron +- openssh +- sh +- libgtk-2-0 +- libgconf-2-4 +- xvfb (if you're running it headless) + +## setup +- `sudo apt install libgtk2.0-0 libgconf-2-4` (if you're on debian, other + distros idk) +- `sudo apt install xvfb` (again, on debian) +- `git clone https://tildegit.org/sose/hellgate` +- `cd hellgate` +- `sudo npm install -g electron --unsafe-perm=true` +- `npm install` +- `./gen_cert` +- `npm start` or `xvfb-run npm start` + - the server will listen on localhost:1965 + - this is not configurable + +## what is this? +- hellgate is a web browser with a gemini server attached +- ask the server for a webpage and it will access it, render it, and send it to + you in gemtext format + +## why not just use `curl` and convert the html to gemtext? +- because hellgate runs a fully featured web browser, it has the ability to + execute javascript like a browser would +- this means websites that require javascript to function (like say, cnn.com) + can be made accessible over gemini + +## what is the `sigils` dir for? +- hellgate is extensible through the use of files called 'sigils' +- a sigil is a file containing user-created javascript that will execute inside + the browser sandbox whenever its corresponding page is accessed +- sigils are mainly used for generating nicer looking gemtext for specific + websites, but they can be used to automate any action within the browser's +javascript environment +- sigils are located in the `sigils` dir and are titled with the domain name of + their corresponding website +- see [writing sigils](writing_sigils.md) for more info on how to write a sigil +- if you write a new sigil for a specific website, don't hesitate to send it as + a pull request to this repo diff --git a/sigils/*.sr.ht b/sigils/*.sr.ht new file mode 100644 index 0000000..dc2f412 --- /dev/null +++ b/sigils/*.sr.ht @@ -0,0 +1,104 @@ +var data = "" +header = document.querySelector("h2") +if (header) { + data += "# " + header.innerText + "\n\n" + + var nav_links = document.querySelectorAll("li.nav-item > a.nav-link") + if (nav_links) { + var valid_links = ["summary", "tree", "source"] + nav_links.forEach((nav_link) => { + if (valid_links.includes(nav_link.innerText)){ + let href = nav_link.getAttribute("href") + if (href[0] === "/") { + data += "=> " + + window.electron.abs_href(href) + + " " + + nav_link.innerText + + "\n" + } else { + data += "=> ?" + + href + + " " + + nav_link.innerText + + "\n" + } + } + }) + } + var code_view = document.querySelector("div.col-md-12.code-view > div.highlight > pre") + if (code_view) { + var header = document.querySelector("div.header-extension").innerText.split(" ")[0] + data += "\n## " + header + "\n\n```\n" + code_view.innerText + "```\n" + } + + var tree_list = document.querySelector("div.tree-list") + if (tree_list) { + var header = document.querySelector("div.header-extension").innerText.split(" ")[0] + data += "\n## " + header + "\n\n" + var tree_items = tree_list.children + for (let tree_item of tree_items) { + if (tree_item.className.includes("name")) { + let item_link = tree_item.querySelector("a").getAttribute("href") + data += "=> " + window.electron.abs_href(item_link) + " " + tree_item.innerText + " " + } else if (tree_item.className.includes("size")) { + if (tree_item.innerText !== "") { + data += "(" + tree_item.innerText + ")" + "\n" + } else { + data += "\n" + } + } + } + } + + var latest_events = document.querySelectorAll("div.event-list > div.event") + if (latest_events.length !== 0) { + data += "\n## Latest events:\n\n----\n" + latest_events.forEach((event_el) => { + data += event_el.innerText + "\n----\n" + }) + } + var clone_links = document.querySelector("div.col-md-8 > dl") + if (clone_links) { + data += "\n## Clone:\n" + data += clone_links.innerText + "\n" + } + var readme_node = document.querySelector("div#readme") + var readme_selector = "div#readme" + if (! readme_node) { readme_node = document.querySelector("div.readme") } // don't ask me ask drew + + if (readme_node) { + var markdown_node = readme_node.querySelector("div.markdown") + data += "\n----------\n" + if (markdown_node) { + var readme_elements = markdown_node.children + for (let i = 0; i < readme_elements.length; i++) { + let element = readme_elements[i] + + let tag_name = element.tagName + if (tag_name.startsWith("H") && tag_name.length === 2) { + let header_lvl = tag_name[1] + data += "\n" + + "#".repeat(header_lvl) + + " " + + element.innerText.replace("\n", "").replaceAll("#", "") + } else if (tag_name === "P") { + data += element.innerText + } else if (tag_name === "UL") { + let list_items = element.children + for (let j = 0; j < list_items.length ; j++) { + list_item = list_items[j] + data += "\n* " + list_item.innerText.replaceAll("\n", "\n\t - ") + } + } else if (tag_name === "PRE") { + data += "```\n" + element.innerText + "```\n" + } + data += "\n" + } + } else { + data += readme_node.innerText + "\n" + } + } + window.electron.send_gemtext(data) +} else { + window.electron.send_gemtext(window.electron.default_sigil()) +} diff --git a/sigils/default b/sigils/default new file mode 100644 index 0000000..0949b3e --- /dev/null +++ b/sigils/default @@ -0,0 +1 @@ +window.electron.send_gemtext(window.electron.default_sigil()) diff --git a/sigils/github.com b/sigils/github.com new file mode 100644 index 0000000..504bb9f --- /dev/null +++ b/sigils/github.com @@ -0,0 +1,74 @@ +async function gen_gemtext() { + var data = "" + // wait for the page to actually finish loading + // ideally we would use a mutation observer here but i am lazy + await new Promise((resolve,reject) => { + setTimeout(resolve, 900) + }); + if (document.querySelector("span[itemprop='author']") === null) { + window.electron.send_gemtext(window.electron.default_sigil()) + return + } else { + var title = document.querySelector("span[itemprop='author']").innerText + + " / " + + document.querySelector("strong[itemprop='name']").innerText + data += "# " + title + "\n" + + var latest_commit = document.querySelector("div.Box-header") + if (latest_commit !== null) { + latest_commit = latest_commit.innerText.replaceAll("\n", " ").replace(/History$/, "") + data += "\n" + latest_commit + "\n" + } + var file_list = document.querySelector("div.repository-content") + if (file_list !== null) { + var file_elements = file_list.querySelectorAll("div.Box-row") + if (file_elements.length !== 0) { + data += "\n## Files\n\n" + for (let i = 0; i < file_elements.length; i++) { + let file_element = file_elements[i] + if (file_element.innerText == ". .") { continue } + let link_el = file_element.querySelector("a.js-navigation-open.Link--primary") + let file_title = link_el.innerText + let file_link = link_el.getAttribute("href") + let last_updated = file_element.querySelector("div.color-text-tertiary.text-right").innerText + data += "=> " + + window.electron.abs_href(file_link) + + " " + + file_title + + "\n" + + "* " + "Last updated " + last_updated.trim() + "\n" + } + } else { + var code_lines = document.querySelectorAll("td.blob-num[data-line-number]") + var num_lines = code_lines.length + var file_name = document.querySelector("strong.final-path") + var raw_link = document.querySelector("a#raw-url") + if (file_name) { + data += "\n## " + file_name.innerText + "\n\n" + } + if (num_lines !== 0) { + data += "```\n" + code_lines.forEach((line) => { + let line_num = line.getAttribute("data-line-number") + let padding = String(num_lines).length - String(line_num).length + let line_contents = line.nextElementSibling.innerText.replace(/^\n$/, "") + data += line_num + " ".repeat(padding) +"| " + line_contents + "\n" + }) + data += "```\n" + } + } + } + var readme_box = document.querySelector("div#readme") + if (readme_box) { + data += "\n----------\n" + var md_contents = document.querySelector("article.markdown-body.entry-content[itemprop='text']") + if (md_contents) { + data += window.electron.md_parse("article.markdown-body.entry-content[itemprop='text']") + } else { + data += readme_box.innerText + } + } + } + window.electron.send_gemtext(data) +} +gen_gemtext() diff --git a/sigils/gitlab.com b/sigils/gitlab.com new file mode 100644 index 0000000..eb7d2d2 --- /dev/null +++ b/sigils/gitlab.com @@ -0,0 +1,76 @@ +async function gen_gemtext() { + // wait for the page to actually finish loading + // ideally we would use a mutation observer here but i am lazy + await new Promise((resolve,reject) => { + setTimeout(resolve, 900) + }); + + var data = "" + var repo_node = document.getElementById("content-body") + if (repo_node !== null + && repo_node.getAttribute("itemtype") === "http://schema.org/SoftwareSourceCode" ) { + var repo_title = repo_node + .querySelector("h1[data-qa-selector='project_name_content']") + + if (repo_title !== null) { + data += "# " + repo_title.innerText + "\n\n" + } + + var project_stats = repo_node.querySelector("nav.project-stats") + if (project_stats !== null) { + project_stats = project_stats.innerText.replaceAll("\n ", " ").replaceAll("\n", " | ") + data += project_stats + "\n" + } + + var latest_commit = repo_node.querySelector("div.commit-detail.flex-list") + if (latest_commit !== null) { + latest_commit = latest_commit.innerText + data += "\n## Last updated: \n\n" + data += latest_commit + "\n" + } + + file_node_list = repo_node.querySelectorAll("tr.tree-item") + if (file_node_list.length !== 0) { + data += "\n## Files\n\n" + for (let file_node of file_node_list) { + if (file_node.innerText === "..") { continue } + let file_link = file_node.querySelector("a.tree-item-link") + if (file_link !== null) { + file_link = window.electron.abs_href(file_link.getAttribute("href")) + } + let file_info = file_node.innerText.replace(/\t.*\t/, "\n* Last updated ") + data += "=> " + file_link + " " + file_info + "\n" + } + } + + var readme_node = repo_node.querySelector("div.blob-viewer > div.file-content.md") + var code_node = repo_node.querySelector("div.blob-viewer > div.file-content.code") + if (readme_node !== null) { + data += "\n----------\n" + data += window.electron.md_parse("div.blob-viewer > div.file-content.md") + } else if (code_node !== null) { + var file_name = document.querySelector("div.file-header-content").innerText + var code_lines = code_node.querySelectorAll("span.line") + var num_lines = code_lines.length + var raw_link = document.querySelector("a[aria-label='Open raw'") + if (raw_link) { + raw_link = window.electron.abs_href(raw_link.getAttribute("href")) + data += "\n=> " + raw_link + " Raw" + } + if (num_lines !== 0) { + data += "\n\n# " + file_name.replace("\n", "").replace("\n", "|") + "\n\n" + data += "```\n" + for (let i = 0; i < code_lines.length; i++) { + let padding = String(num_lines).length - String(i + 1).length + let code_line = code_lines[i] + data += String(i + 1) + " ".repeat(padding) + "| " + code_line.innerText + "\n" + } + data += "```\n" + } + } + window.electron.send_gemtext(data) + } else { + window.electron.send_gemtext(window.electron.default_sigil()) + } +} +gen_gemtext() diff --git a/sigils/tildegit.org b/sigils/tildegit.org new file mode 100644 index 0000000..b98a758 --- /dev/null +++ b/sigils/tildegit.org @@ -0,0 +1,115 @@ +var data = "" +var repo_nodes = document.getElementsByClassName("page-content repository file list") + + +if (repo_nodes.length !== 0) { + var repo_node = repo_nodes[0] + var repo_title = repo_node.getElementsByClassName("repo-title")[0].innerText + repo_title = repo_title.replaceAll("\n", " ") + repo_title = "# " + repo_title + + data += repo_title + "\n" + + var fork_flag = repo_node.getElementsByClassName("fork-flag") + if (fork_flag.length !== 0) { + fork_flag = fork_flag[0] + .innerText + .replaceAll("\n", "") + + data += fork_flag + "\n" + } + + var repo_stats = repo_node + .getElementsByClassName("ui two horizontal center link list")[0] + .innerText + .replaceAll("\n", " |") + .trim() + + data += "\n" + repo_stats + "\n" + + var latest_commit_info = repo_node + .getElementsByClassName("commit-list") + if (latest_commit_info.length !== 0) { + latest_commit_info = latest_commit_info[0] + .innerText + .replaceAll("\t", "") + .trim() + data += "\nLatest commit:\n" + latest_commit_info + "\n" + } + + var code_view = document.querySelector("div.file-view.code-view") + if (code_view) { + var code_lines = code_view.querySelectorAll("td.lines-code") + var file_name = document.querySelector("span.ui.breadcrumb.repo-path") + var buttons = document.querySelectorAll("a.ui.mini.basic.button") + var raw_link + buttons.forEach((button) => { + if (button.innerText === "Raw") { + raw_link = window.electron.abs_href(button.getAttribute("href")) + } + }) + if (file_name) { file_name = file_name.innerText } + data += "\n## " + file_name + "\n\n" + + if (raw_link) { + data += "=> " + raw_link + " Raw\n\n" + } + var padding + data += "```\n" + code_lines.forEach((code_line) => { + let line_num = code_line.getAttribute("rel").replace(/^./, "") + let line_data = code_line.innerText.replace("\n", "") + let padding = String(code_lines.length).length - String(line_num.length) + data += line_num + " ".repeat(padding) + "| " + line_data + "\n" + }) + data += "\n```\n" + } + + var repo_files_table = document + .getElementById("repo-files-table") + + if (repo_files_table) { + repo_files_table = repo_files_table.getElementsByTagName("tr") + data += "\n## Files\n\n" + for (let i = 2; i < repo_files_table.length; i++) { + let table_element = repo_files_table[i] + let file_info = table_element.innerText.trim().replace(/\t.*$/, "") + let file_link = table_element.querySelector("a") + if (file_link) { + file_link = window.electron.abs_href(file_link.getAttribute("href")) + } + + data += "=> " + file_link + " " + file_info + "\n" + } + } + + var readme_nodes = repo_node + .getElementsByClassName("file-view markdown markdown") + if (readme_nodes.length !== 0) { + readme_nodes = readme_nodes[0].children + data += "\n----------\n" + + for (let i = 0; i < readme_nodes.length; i++) { + let readme_node = readme_nodes[i] + let tag_name = readme_node.tagName + if (tag_name.startsWith("H") && tag_name.length === 2) { + let header_lvl = tag_name[1] + data += "\n" + "#".repeat(header_lvl) + " " + readme_node.innerText + } else if (tag_name === "P") { + data += readme_node.innerText + } else if (tag_name === "UL") { + let list_items = readme_node.children + for (let j = 0; j < list_items.length ; j++) { + list_item = list_items[j] + data += "\n* " + list_item.innerText.replaceAll("\n", "\n\t - ") + } + } else if (tag_name === "PRE") { + data += "```\n" + readme_node.innerText + "```\n" + } + data += "\n" + } + } + window.electron.send_gemtext(data) +} else { + window.electron.send_gemtext(window.electron.default_sigil()) +} diff --git a/tls/cert.pem b/tls/cert.pem new file mode 100644 index 0000000..1a4c042 --- /dev/null +++ b/tls/cert.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFCTCCAvGgAwIBAgIUY5J0vNCb4udzvi9A7qe/DrlCnBQwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIxMDQyMjAzNDU1NloXDTIyMDQy +MjAzNDU1NlowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAyt0SljPnOj18Xkq74tuf/CAgOUFoa1DGEbJ9pyeDQ+68 +rfaGcQzvC1e3NPAoEqO8gLlsYgnxCK308ZeO34LkhA/yrbsgWx02WLYlzZ0fFYas ++0K+1xrhPsLURm0aAMVI60pIPoTBXi/fqfV21k32X5lS/c3PZpbmt0l+mVHzWy8+ +OpHt05qaBB/rB3LD2RGwJVZwBWWeJI1kb3wfpN0+fMoo/4cgpAVaZ2Uak8NoswEP +hVCcMo/WJF0+Ik9Mtl6V5OAwt1u2Pp5W4OzMYkQJhlV1w8ufhEYxBAOVXTxQ5Lu1 +iqztipyx1g8mpEyEi3e0MrysxKfkgEZxeSvHdIEwVf+sStPAFBPJjEcQJgCzQtns +EPMGqbghPU6Swpil3nKQ/93HEfhw4LjWrG9Mp9DptmPu9fzah028BgkLfWMRHhgp +lKQV6wSy+8AdlNDOb0Q8xawt7/RDfgPJF5lsPfi3lVToGAU/9mWhckaZlgEU20hC +PiH6O9PxMlZ62Ch3VEhR8XUSI3g0rqPHsJqDlZZl26pCLOvECpHhTln43/3mx75X +Rld04EpeZ9S2sJGXghdBn0NIEGlG7w1DulQLS9gkZGE8Y9PnLUrnWlLuyY99oLzL +xpAF37O2vYZGlFYt9BWZ2yav+QJU9PGP+18jOgHiS/QvfJ9mcEMKqp1UXeCIPSsC +AwEAAaNTMFEwHQYDVR0OBBYEFK5WqeRNQdkSBwv5j/FlzfxvN9qYMB8GA1UdIwQY +MBaAFK5WqeRNQdkSBwv5j/FlzfxvN9qYMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI +hvcNAQELBQADggIBAMYJ9rLpISXjgNuQ4VM8tXlikCeWVaPfUvQnhjW5UKFGR/Hj +d0KRpzNw0YlvVO3mTYy3mMAjws0k8kFqTkWftwg6C4B7FIuFpJK3eJnYXrhiyGSp +dp3l6ASSYyl6QeHruyIV9u+n8yyeV88gqjgK3T22OoZdmEXSkkMZ4KL4hIE8hYb6 +BV5aAYh+QlxyHBMuB1ZUa9d9K+0WUA/4ykp9kOrpUnUJ8WdaUKbWGHR0SrKXFDsV +W58VpWzymZwUxqIC8uMFSzXMEhwU87t23Azzob1vdp2cc5tOplzIo317wtdhyjro +/CjuNt6hqeduMjXz+jku2fwk51le0mhE3Qr+CqjVg8Qp0ffrmX69W+2St9tbxkmN +yOhyZw55W3j3/16+VZTaGMQhQwMxszMVBebKIaPXiBDbvWcDahPknESY6bBIicSY +W2a6l0Ao01+6/PwqKGGi353FWgn7Bm4coXuLPMseBuwBhc4/kUXMc80+XZyrEIUO +FNaEnbZ4YcP/Fqmi6BLRslUgpIpCkttr0XnuE3wA7gdH+pOS/Hxfk7xAJvNByR93 +nGkN3+fLGyQI+9fI241ce8MQevDgC9DSvZGw1UQEq+RX9DGjpK26Iyu+UrjMhIMW +X002hHQ0E/OZXbTUVwv3G3lPFlUBZbUH/BTjG8mnmn+RxJToe/JhqXI9qIS3 +-----END CERTIFICATE----- diff --git a/tls/key.pem b/tls/key.pem new file mode 100644 index 0000000..a83447c --- /dev/null +++ b/tls/key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +A+0xklCnyaSsl5sQBB8+KTiVvqEMysfo +-----END PRIVATE KEY----- diff --git a/writing_sigils.md b/writing_sigils.md new file mode 100644 index 0000000..81f0cea --- /dev/null +++ b/writing_sigils.md @@ -0,0 +1,79 @@ +# how to write a sigil +## requirements +- a text editor +- a browser +- some knowledge of javascript + +## getting started +Say we want to write a sigil for "https://time.is" to get the current times +around the world and serve them in gemtext format. Our first step is to create +the file `time.is` and place it in the `sigils` directory. + +## writing the sigil +The only necessary part of any sigil is the call to +`window.electron.send_gemtext()`. This function sends any data generated in +the browser to the web server. + +To start, we can write our sigil `time.is` to consist only of the +line: +``` +window.electron.send_gemtext("This is a test.") +``` + +Now, whenever you access "gemini://localhost?https://time.is" +you will be greeted with: +``` +This is a test. +``` +Great! This is not very useful however, as we aren't getting any data from the +page. + +So, lets add some data! If we navigate to "https://time.is" using a web +browser, we can see the current time for the user's location is displayed near +the top of the page. Using the "inspect element" feature we can see that the +current time is contained in a `time` element with the id `clock`. If we want +to use this data in our sigil, we can access it using +`document.querySelector()`, the same way we would in the browser console: +``` +var current_time = document.querySelector("time#clock").innerText +``` + +Now, if we want our sigil to send that data, we simply write: +``` +window.electron.send_gemtext(current_time) +``` + +So now our sigil `time.is` looks like this: +``` +var current_time = document.querySelector("time#clock").innerText +window.electron.send_gemtext(current_time) +``` + +And now, when we access "gemini://localhost?https://time.is", we see: +``` +4:29:36pm +``` +(or whatever the time is in your current location) + +If we want to display the current date as well, we can find it in the `div` element with id `dd` +``` +var current_date = document.querySelector("div#dd").innerText +``` + +And send the entire thing as a string back to the web server: +``` +var current_time = document.querySelector("time#clock").innerText +var current_date = document.querySelector("div#dd").innerText +window.electron.send_gemtext(current_time + "\n\n" + current_date) +``` + +Now, when we access "gemini://localhost?https://time.is" we see: +``` +4:38:02pm + +Friday, April 23, 2021 +``` + +Of course, sigils can be much more powerful than this. For a good example of a +more complex sigil, you can see the `*.sr.ht` sigil for viewing sourcehut +repos.