├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── customHtmlAfterFooter.example.html ├── package.json ├── public ├── css │ └── styles.css ├── files │ └── .gitkeep ├── history │ └── .gitkeep └── js │ └── little-library.js ├── routes ├── get_about.js ├── get_give.js ├── get_history.js ├── get_home.js ├── get_tools.js ├── middleware.js ├── post_give.js ├── post_tools.js └── socketio.js ├── server.js ├── settings.example.json ├── templates ├── Templater.js ├── elements │ ├── book.html │ ├── bookInfo.html │ ├── book_readable.html │ ├── messageBox.html │ ├── modal.html │ ├── modalCard.html │ └── takeConfirm.html ├── htmlContainer.html └── pages │ ├── about.html │ ├── tools.html │ └── uploadForm.html └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | public/files/*.epub 3 | public/files/*.mobi 4 | public/files/*.pdf 5 | public/files/*.json 6 | public/files/*.zip 7 | public/history/*.json 8 | .well-known/ 9 | 10 | settings.json 11 | customHtmlAfterFooter.html 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | Copyright (C) 2007 Free Software Foundation, Inc. 4 | Everyone is permitted to copy and distribute verbatim copies 5 | of this license document, but changing it is not allowed. 6 | Preamble 7 | The GNU Affero General Public License is a free, copyleft license for 8 | software and other kinds of works, specifically designed to ensure 9 | cooperation with the community in the case of network server software. 10 | The licenses for most software and other practical works are designed 11 | to take away your freedom to share and change the works. By contrast, 12 | our General Public Licenses are intended to guarantee your freedom to 13 | share and change all versions of a program--to make sure it remains free 14 | software for all its users. 15 | When we speak of free software, we are referring to freedom, not 16 | price. Our General Public Licenses are designed to make sure that you 17 | have the freedom to distribute copies of free software (and charge for 18 | them if you wish), that you receive source code or can get it if you 19 | want it, that you can change the software or use pieces of it in new 20 | free programs, and that you know you can do these things. 21 | Developers that use our General Public Licenses protect your rights 22 | with two steps: (1) assert copyright on the software, and (2) offer 23 | you this License which gives you legal permission to copy, distribute 24 | and/or modify the software. 25 | A secondary benefit of defending all users' freedom is that 26 | improvements made in alternate versions of the program, if they 27 | receive widespread use, become available for other developers to 28 | incorporate. Many developers of free software are heartened and 29 | encouraged by the resulting cooperation. However, in the case of 30 | software used on network servers, this result may fail to come about. 31 | The GNU General Public License permits making a modified version and 32 | letting the public access it on a server without ever releasing its 33 | source code to the public. 34 | The GNU Affero General Public License is designed specifically to 35 | ensure that, in such cases, the modified source code becomes available 36 | to the community. It requires the operator of a network server to 37 | provide the source code of the modified version running there to the 38 | users of that server. Therefore, public use of a modified version, on 39 | a publicly accessible server, gives the public access to the source 40 | code of the modified version. 41 | An older license, called the Affero General Public License and 42 | published by Affero, was designed to accomplish similar goals. This is 43 | a different license, not a version of the Affero GPL, but Affero has 44 | released a new version of the Affero GPL which permits relicensing under 45 | this license. 46 | The precise terms and conditions for copying, distribution and 47 | modification follow. 48 | TERMS AND CONDITIONS 49 | 0. Definitions. 50 | "This License" refers to version 3 of the GNU Affero General Public License. 51 | "Copyright" also means copyright-like laws that apply to other kinds of 52 | works, such as semiconductor masks. 53 | "The Program" refers to any copyrightable work licensed under this 54 | License. Each licensee is addressed as "you". "Licensees" and 55 | "recipients" may be individuals or organizations. 56 | To "modify" a work means to copy from or adapt all or part of the work 57 | in a fashion requiring copyright permission, other than the making of an 58 | exact copy. The resulting work is called a "modified version" of the 59 | earlier work or a work "based on" the earlier work. 60 | A "covered work" means either the unmodified Program or a work based 61 | on the Program. 62 | To "propagate" a work means to do anything with it that, without 63 | permission, would make you directly or secondarily liable for 64 | infringement under applicable copyright law, except executing it on a 65 | computer or modifying a private copy. Propagation includes copying, 66 | distribution (with or without modification), making available to the 67 | public, and in some countries other activities as well. 68 | To "convey" a work means any kind of propagation that enables other 69 | parties to make or receive copies. Mere interaction with a user through 70 | a computer network, with no transfer of a copy, is not conveying. 71 | An interactive user interface displays "Appropriate Legal Notices" 72 | to the extent that it includes a convenient and prominently visible 73 | feature that (1) displays an appropriate copyright notice, and (2) 74 | tells the user that there is no warranty for the work (except to the 75 | extent that warranties are provided), that licensees may convey the 76 | work under this License, and how to view a copy of this License. If 77 | the interface presents a list of user commands or options, such as a 78 | menu, a prominent item in the list meets this criterion. 79 | 1. Source Code. 80 | The "source code" for a work means the preferred form of the work 81 | for making modifications to it. "Object code" means any non-source 82 | form of a work. 83 | A "Standard Interface" means an interface that either is an official 84 | standard defined by a recognized standards body, or, in the case of 85 | interfaces specified for a particular programming language, one that 86 | is widely used among developers working in that language. 87 | The "System Libraries" of an executable work include anything, other 88 | than the work as a whole, that (a) is included in the normal form of 89 | packaging a Major Component, but which is not part of that Major 90 | Component, and (b) serves only to enable use of the work with that 91 | Major Component, or to implement a Standard Interface for which an 92 | implementation is available to the public in source code form. A 93 | "Major Component", in this context, means a major essential component 94 | (kernel, window system, and so on) of the specific operating system 95 | (if any) on which the executable work runs, or a compiler used to 96 | produce the work, or an object code interpreter used to run it. 97 | The "Corresponding Source" for a work in object code form means all 98 | the source code needed to generate, install, and (for an executable 99 | work) run the object code and to modify the work, including scripts to 100 | control those activities. However, it does not include the work's 101 | System Libraries, or general-purpose tools or generally available free 102 | programs which are used unmodified in performing those activities but 103 | which are not part of the work. For example, Corresponding Source 104 | includes interface definition files associated with source files for 105 | the work, and the source code for shared libraries and dynamically 106 | linked subprograms that the work is specifically designed to require, 107 | such as by intimate data communication or control flow between those 108 | subprograms and other parts of the work. 109 | The Corresponding Source need not include anything that users 110 | can regenerate automatically from other parts of the Corresponding 111 | Source. 112 | The Corresponding Source for a work in source code form is that 113 | same work. 114 | 2. Basic Permissions. 115 | All rights granted under this License are granted for the term of 116 | copyright on the Program, and are irrevocable provided the stated 117 | conditions are met. This License explicitly affirms your unlimited 118 | permission to run the unmodified Program. The output from running a 119 | covered work is covered by this License only if the output, given its 120 | content, constitutes a covered work. This License acknowledges your 121 | rights of fair use or other equivalent, as provided by copyright law. 122 | You may make, run and propagate covered works that you do not 123 | convey, without conditions so long as your license otherwise remains 124 | in force. You may convey covered works to others for the sole purpose 125 | of having them make modifications exclusively for you, or provide you 126 | with facilities for running those works, provided that you comply with 127 | the terms of this License in conveying all material for which you do 128 | not control copyright. Those thus making or running the covered works 129 | for you must do so exclusively on your behalf, under your direction 130 | and control, on terms that prohibit them from making any copies of 131 | your copyrighted material outside their relationship with you. 132 | Conveying under any other circumstances is permitted solely under 133 | the conditions stated below. Sublicensing is not allowed; section 10 134 | makes it unnecessary. 135 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 136 | No covered work shall be deemed part of an effective technological 137 | measure under any applicable law fulfilling obligations under article 138 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 139 | similar laws prohibiting or restricting circumvention of such 140 | measures. 141 | When you convey a covered work, you waive any legal power to forbid 142 | circumvention of technological measures to the extent such circumvention 143 | is effected by exercising rights under this License with respect to 144 | the covered work, and you disclaim any intention to limit operation or 145 | modification of the work as a means of enforcing, against the work's 146 | users, your or third parties' legal rights to forbid circumvention of 147 | technological measures. 148 | 4. Conveying Verbatim Copies. 149 | You may convey verbatim copies of the Program's source code as you 150 | receive it, in any medium, provided that you conspicuously and 151 | appropriately publish on each copy an appropriate copyright notice; 152 | keep intact all notices stating that this License and any 153 | non-permissive terms added in accord with section 7 apply to the code; 154 | keep intact all notices of the absence of any warranty; and give all 155 | recipients a copy of this License along with the Program. 156 | You may charge any price or no price for each copy that you convey, 157 | and you may offer support or warranty protection for a fee. 158 | 5. Conveying Modified Source Versions. 159 | You may convey a work based on the Program, or the modifications to 160 | produce it from the Program, in the form of source code under the 161 | terms of section 4, provided that you also meet all of these conditions: 162 | a) The work must carry prominent notices stating that you modified 163 | it, and giving a relevant date. 164 | b) The work must carry prominent notices stating that it is 165 | released under this License and any conditions added under section 166 | 7. This requirement modifies the requirement in section 4 to 167 | "keep intact all notices". 168 | c) You must license the entire work, as a whole, under this 169 | License to anyone who comes into possession of a copy. This 170 | License will therefore apply, along with any applicable section 7 171 | additional terms, to the whole of the work, and all its parts, 172 | regardless of how they are packaged. This License gives no 173 | permission to license the work in any other way, but it does not 174 | invalidate such permission if you have separately received it. 175 | d) If the work has interactive user interfaces, each must display 176 | Appropriate Legal Notices; however, if the Program has interactive 177 | interfaces that do not display Appropriate Legal Notices, your 178 | work need not make them do so. 179 | A compilation of a covered work with other separate and independent 180 | works, which are not by their nature extensions of the covered work, 181 | and which are not combined with it such as to form a larger program, 182 | in or on a volume of a storage or distribution medium, is called an 183 | "aggregate" if the compilation and its resulting copyright are not 184 | used to limit the access or legal rights of the compilation's users 185 | beyond what the individual works permit. Inclusion of a covered work 186 | in an aggregate does not cause this License to apply to the other 187 | parts of the aggregate. 188 | 6. Conveying Non-Source Forms. 189 | You may convey a covered work in object code form under the terms 190 | of sections 4 and 5, provided that you also convey the 191 | machine-readable Corresponding Source under the terms of this License, 192 | in one of these ways: 193 | a) Convey the object code in, or embodied in, a physical product 194 | (including a physical distribution medium), accompanied by the 195 | Corresponding Source fixed on a durable physical medium 196 | customarily used for software interchange. 197 | b) Convey the object code in, or embodied in, a physical product 198 | (including a physical distribution medium), accompanied by a 199 | written offer, valid for at least three years and valid for as 200 | long as you offer spare parts or customer support for that product 201 | model, to give anyone who possesses the object code either (1) a 202 | copy of the Corresponding Source for all the software in the 203 | product that is covered by this License, on a durable physical 204 | medium customarily used for software interchange, for a price no 205 | more than your reasonable cost of physically performing this 206 | conveying of source, or (2) access to copy the 207 | Corresponding Source from a network server at no charge. 208 | c) Convey individual copies of the object code with a copy of the 209 | written offer to provide the Corresponding Source. This 210 | alternative is allowed only occasionally and noncommercially, and 211 | only if you received the object code with such an offer, in accord 212 | with subsection 6b. 213 | d) Convey the object code by offering access from a designated 214 | place (gratis or for a charge), and offer equivalent access to the 215 | Corresponding Source in the same way through the same place at no 216 | further charge. You need not require recipients to copy the 217 | Corresponding Source along with the object code. If the place to 218 | copy the object code is a network server, the Corresponding Source 219 | may be on a different server (operated by you or a third party) 220 | that supports equivalent copying facilities, provided you maintain 221 | clear directions next to the object code saying where to find the 222 | Corresponding Source. Regardless of what server hosts the 223 | Corresponding Source, you remain obligated to ensure that it is 224 | available for as long as needed to satisfy these requirements. 225 | e) Convey the object code using peer-to-peer transmission, provided 226 | you inform other peers where the object code and Corresponding 227 | Source of the work are being offered to the general public at no 228 | charge under subsection 6d. 229 | A separable portion of the object code, whose source code is excluded 230 | from the Corresponding Source as a System Library, need not be 231 | included in conveying the object code work. 232 | A "User Product" is either (1) a "consumer product", which means any 233 | tangible personal property which is normally used for personal, family, 234 | or household purposes, or (2) anything designed or sold for incorporation 235 | into a dwelling. In determining whether a product is a consumer product, 236 | doubtful cases shall be resolved in favor of coverage. For a particular 237 | product received by a particular user, "normally used" refers to a 238 | typical or common use of that class of product, regardless of the status 239 | of the particular user or of the way in which the particular user 240 | actually uses, or expects or is expected to use, the product. A product 241 | is a consumer product regardless of whether the product has substantial 242 | commercial, industrial or non-consumer uses, unless such uses represent 243 | the only significant mode of use of the product. 244 | "Installation Information" for a User Product means any methods, 245 | procedures, authorization keys, or other information required to install 246 | and execute modified versions of a covered work in that User Product from 247 | a modified version of its Corresponding Source. The information must 248 | suffice to ensure that the continued functioning of the modified object 249 | code is in no case prevented or interfered with solely because 250 | modification has been made. 251 | If you convey an object code work under this section in, or with, or 252 | specifically for use in, a User Product, and the conveying occurs as 253 | part of a transaction in which the right of possession and use of the 254 | User Product is transferred to the recipient in perpetuity or for a 255 | fixed term (regardless of how the transaction is characterized), the 256 | Corresponding Source conveyed under this section must be accompanied 257 | by the Installation Information. But this requirement does not apply 258 | if neither you nor any third party retains the ability to install 259 | modified object code on the User Product (for example, the work has 260 | been installed in ROM). 261 | The requirement to provide Installation Information does not include a 262 | requirement to continue to provide support service, warranty, or updates 263 | for a work that has been modified or installed by the recipient, or for 264 | the User Product in which it has been modified or installed. Access to a 265 | network may be denied when the modification itself materially and 266 | adversely affects the operation of the network or violates the rules and 267 | protocols for communication across the network. 268 | Corresponding Source conveyed, and Installation Information provided, 269 | in accord with this section must be in a format that is publicly 270 | documented (and with an implementation available to the public in 271 | source code form), and must require no special password or key for 272 | unpacking, reading or copying. 273 | 7. Additional Terms. 274 | "Additional permissions" are terms that supplement the terms of this 275 | License by making exceptions from one or more of its conditions. 276 | Additional permissions that are applicable to the entire Program shall 277 | be treated as though they were included in this License, to the extent 278 | that they are valid under applicable law. If additional permissions 279 | apply only to part of the Program, that part may be used separately 280 | under those permissions, but the entire Program remains governed by 281 | this License without regard to the additional permissions. 282 | When you convey a copy of a covered work, you may at your option 283 | remove any additional permissions from that copy, or from any part of 284 | it. (Additional permissions may be written to require their own 285 | removal in certain cases when you modify the work.) You may place 286 | additional permissions on material, added by you to a covered work, 287 | for which you have or can give appropriate copyright permission. 288 | Notwithstanding any other provision of this License, for material you 289 | add to a covered work, you may (if authorized by the copyright holders of 290 | that material) supplement the terms of this License with terms: 291 | a) Disclaiming warranty or limiting liability differently from the 292 | terms of sections 15 and 16 of this License; or 293 | b) Requiring preservation of specified reasonable legal notices or 294 | author attributions in that material or in the Appropriate Legal 295 | Notices displayed by works containing it; or 296 | c) Prohibiting misrepresentation of the origin of that material, or 297 | requiring that modified versions of such material be marked in 298 | reasonable ways as different from the original version; or 299 | d) Limiting the use for publicity purposes of names of licensors or 300 | authors of the material; or 301 | e) Declining to grant rights under trademark law for use of some 302 | trade names, trademarks, or service marks; or 303 | f) Requiring indemnification of licensors and authors of that 304 | material by anyone who conveys the material (or modified versions of 305 | it) with contractual assumptions of liability to the recipient, for 306 | any liability that these contractual assumptions directly impose on 307 | those licensors and authors. 308 | All other non-permissive additional terms are considered "further 309 | restrictions" within the meaning of section 10. If the Program as you 310 | received it, or any part of it, contains a notice stating that it is 311 | governed by this License along with a term that is a further 312 | restriction, you may remove that term. If a license document contains 313 | a further restriction but permits relicensing or conveying under this 314 | License, you may add to a covered work material governed by the terms 315 | of that license document, provided that the further restriction does 316 | not survive such relicensing or conveying. 317 | If you add terms to a covered work in accord with this section, you 318 | must place, in the relevant source files, a statement of the 319 | additional terms that apply to those files, or a notice indicating 320 | where to find the applicable terms. 321 | Additional terms, permissive or non-permissive, may be stated in the 322 | form of a separately written license, or stated as exceptions; 323 | the above requirements apply either way. 324 | 8. Termination. 325 | You may not propagate or modify a covered work except as expressly 326 | provided under this License. Any attempt otherwise to propagate or 327 | modify it is void, and will automatically terminate your rights under 328 | this License (including any patent licenses granted under the third 329 | paragraph of section 11). 330 | However, if you cease all violation of this License, then your 331 | license from a particular copyright holder is reinstated (a) 332 | provisionally, unless and until the copyright holder explicitly and 333 | finally terminates your license, and (b) permanently, if the copyright 334 | holder fails to notify you of the violation by some reasonable means 335 | prior to 60 days after the cessation. 336 | Moreover, your license from a particular copyright holder is 337 | reinstated permanently if the copyright holder notifies you of the 338 | violation by some reasonable means, this is the first time you have 339 | received notice of violation of this License (for any work) from that 340 | copyright holder, and you cure the violation prior to 30 days after 341 | your receipt of the notice. 342 | Termination of your rights under this section does not terminate the 343 | licenses of parties who have received copies or rights from you under 344 | this License. If your rights have been terminated and not permanently 345 | reinstated, you do not qualify to receive new licenses for the same 346 | material under section 10. 347 | 9. Acceptance Not Required for Having Copies. 348 | You are not required to accept this License in order to receive or 349 | run a copy of the Program. Ancillary propagation of a covered work 350 | occurring solely as a consequence of using peer-to-peer transmission 351 | to receive a copy likewise does not require acceptance. However, 352 | nothing other than this License grants you permission to propagate or 353 | modify any covered work. These actions infringe copyright if you do 354 | not accept this License. Therefore, by modifying or propagating a 355 | covered work, you indicate your acceptance of this License to do so. 356 | 10. Automatic Licensing of Downstream Recipients. 357 | Each time you convey a covered work, the recipient automatically 358 | receives a license from the original licensors, to run, modify and 359 | propagate that work, subject to this License. You are not responsible 360 | for enforcing compliance by third parties with this License. 361 | An "entity transaction" is a transaction transferring control of an 362 | organization, or substantially all assets of one, or subdividing an 363 | organization, or merging organizations. If propagation of a covered 364 | work results from an entity transaction, each party to that 365 | transaction who receives a copy of the work also receives whatever 366 | licenses to the work the party's predecessor in interest had or could 367 | give under the previous paragraph, plus a right to possession of the 368 | Corresponding Source of the work from the predecessor in interest, if 369 | the predecessor has it or can get it with reasonable efforts. 370 | You may not impose any further restrictions on the exercise of the 371 | rights granted or affirmed under this License. For example, you may 372 | not impose a license fee, royalty, or other charge for exercise of 373 | rights granted under this License, and you may not initiate litigation 374 | (including a cross-claim or counterclaim in a lawsuit) alleging that 375 | any patent claim is infringed by making, using, selling, offering for 376 | sale, or importing the Program or any portion of it. 377 | 11. Patents. 378 | A "contributor" is a copyright holder who authorizes use under this 379 | License of the Program or a work on which the Program is based. The 380 | work thus licensed is called the contributor's "contributor version". 381 | A contributor's "essential patent claims" are all patent claims 382 | owned or controlled by the contributor, whether already acquired or 383 | hereafter acquired, that would be infringed by some manner, permitted 384 | by this License, of making, using, or selling its contributor version, 385 | but do not include claims that would be infringed only as a 386 | consequence of further modification of the contributor version. For 387 | purposes of this definition, "control" includes the right to grant 388 | patent sublicenses in a manner consistent with the requirements of 389 | this License. 390 | Each contributor grants you a non-exclusive, worldwide, royalty-free 391 | patent license under the contributor's essential patent claims, to 392 | make, use, sell, offer for sale, import and otherwise run, modify and 393 | propagate the contents of its contributor version. 394 | In the following three paragraphs, a "patent license" is any express 395 | agreement or commitment, however denominated, not to enforce a patent 396 | (such as an express permission to practice a patent or covenant not to 397 | sue for patent infringement). To "grant" such a patent license to a 398 | party means to make such an agreement or commitment not to enforce a 399 | patent against the party. 400 | If you convey a covered work, knowingly relying on a patent license, 401 | and the Corresponding Source of the work is not available for anyone 402 | to copy, free of charge and under the terms of this License, through a 403 | publicly available network server or other readily accessible means, 404 | then you must either (1) cause the Corresponding Source to be so 405 | available, or (2) arrange to deprive yourself of the benefit of the 406 | patent license for this particular work, or (3) arrange, in a manner 407 | consistent with the requirements of this License, to extend the patent 408 | license to downstream recipients. "Knowingly relying" means you have 409 | actual knowledge that, but for the patent license, your conveying the 410 | covered work in a country, or your recipient's use of the covered work 411 | in a country, would infringe one or more identifiable patents in that 412 | country that you have reason to believe are valid. 413 | If, pursuant to or in connection with a single transaction or 414 | arrangement, you convey, or propagate by procuring conveyance of, a 415 | covered work, and grant a patent license to some of the parties 416 | receiving the covered work authorizing them to use, propagate, modify 417 | or convey a specific copy of the covered work, then the patent license 418 | you grant is automatically extended to all recipients of the covered 419 | work and works based on it. 420 | A patent license is "discriminatory" if it does not include within 421 | the scope of its coverage, prohibits the exercise of, or is 422 | conditioned on the non-exercise of one or more of the rights that are 423 | specifically granted under this License. You may not convey a covered 424 | work if you are a party to an arrangement with a third party that is 425 | in the business of distributing software, under which you make payment 426 | to the third party based on the extent of your activity of conveying 427 | the work, and under which the third party grants, to any of the 428 | parties who would receive the covered work from you, a discriminatory 429 | patent license (a) in connection with copies of the covered work 430 | conveyed by you (or copies made from those copies), or (b) primarily 431 | for and in connection with specific products or compilations that 432 | contain the covered work, unless you entered into that arrangement, 433 | or that patent license was granted, prior to 28 March 2007. 434 | Nothing in this License shall be construed as excluding or limiting 435 | any implied license or other defenses to infringement that may 436 | otherwise be available to you under applicable patent law. 437 | 12. No Surrender of Others' Freedom. 438 | If conditions are imposed on you (whether by court order, agreement or 439 | otherwise) that contradict the conditions of this License, they do not 440 | excuse you from the conditions of this License. If you cannot convey a 441 | covered work so as to satisfy simultaneously your obligations under this 442 | License and any other pertinent obligations, then as a consequence you may 443 | not convey it at all. For example, if you agree to terms that obligate you 444 | to collect a royalty for further conveying from those to whom you convey 445 | the Program, the only way you could satisfy both those terms and this 446 | License would be to refrain entirely from conveying the Program. 447 | 13. Remote Network Interaction; Use with the GNU General Public License. 448 | Notwithstanding any other provision of this License, if you modify the 449 | Program, your modified version must prominently offer all users 450 | interacting with it remotely through a computer network (if your version 451 | supports such interaction) an opportunity to receive the Corresponding 452 | Source of your version by providing access to the Corresponding Source 453 | from a network server at no charge, through some standard or customary 454 | means of facilitating copying of software. This Corresponding Source 455 | shall include the Corresponding Source for any work covered by version 3 456 | of the GNU General Public License that is incorporated pursuant to the 457 | following paragraph. 458 | Notwithstanding any other provision of this License, you have 459 | permission to link or combine any covered work with a work licensed 460 | under version 3 of the GNU General Public License into a single 461 | combined work, and to convey the resulting work. The terms of this 462 | License will continue to apply to the part which is the covered work, 463 | but the work with which it is combined will remain governed by version 464 | 3 of the GNU General Public License. 465 | 14. Revised Versions of this License. 466 | The Free Software Foundation may publish revised and/or new versions of 467 | the GNU Affero General Public License from time to time. Such new versions 468 | will be similar in spirit to the present version, but may differ in detail to 469 | address new problems or concerns. 470 | Each version is given a distinguishing version number. If the 471 | Program specifies that a certain numbered version of the GNU Affero General 472 | Public License "or any later version" applies to it, you have the 473 | option of following the terms and conditions either of that numbered 474 | version or of any later version published by the Free Software 475 | Foundation. If the Program does not specify a version number of the 476 | GNU Affero General Public License, you may choose any version ever published 477 | by the Free Software Foundation. 478 | If the Program specifies that a proxy can decide which future 479 | versions of the GNU Affero General Public License can be used, that proxy's 480 | public statement of acceptance of a version permanently authorizes you 481 | to choose that version for the Program. 482 | Later license versions may give you additional or different 483 | permissions. However, no additional obligations are imposed on any 484 | author or copyright holder as a result of your choosing to follow a 485 | later version. 486 | 15. Disclaimer of Warranty. 487 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 488 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 489 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 490 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 491 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 492 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 493 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 494 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 495 | 16. Limitation of Liability. 496 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 497 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 498 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 499 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 500 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 501 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 502 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 503 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 504 | SUCH DAMAGES. 505 | 17. Interpretation of Sections 15 and 16. 506 | If the disclaimer of warranty and limitation of liability provided 507 | above cannot be given local legal effect according to their terms, 508 | reviewing courts shall apply local law that most closely approximates 509 | an absolute waiver of all civil liability in connection with the 510 | Program, unless a warranty or assumption of liability accompanies a 511 | copy of the Program in return for a fee. 512 | END OF TERMS AND CONDITIONS 513 | How to Apply These Terms to Your New Programs 514 | If you develop a new program, and you want it to be of the greatest 515 | possible use to the public, the best way to achieve this is to make it 516 | free software which everyone can redistribute and change under these terms. 517 | To do so, attach the following notices to the program. It is safest 518 | to attach them to the start of each source file to most effectively 519 | state the exclusion of warranty; and each file should have at least 520 | the "copyright" line and a pointer to where the full notice is found. 521 | Readlebee Copyright (C) 2019 Robbie Antenesse 522 | This program is free software: you can redistribute it and/or modify 523 | it under the terms of the GNU Affero General Public License as published by 524 | the Free Software Foundation, either version 3 of the License, or 525 | (at your option) any later version. 526 | This program is distributed in the hope that it will be useful, 527 | but WITHOUT ANY WARRANTY; without even the implied warranty of 528 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 529 | GNU Affero General Public License for more details. 530 | You should have received a copy of the GNU Affero General Public License 531 | along with this program. If not, see . 532 | Also add information on how to contact you by electronic and paper mail. 533 | If your software can interact with users remotely through a computer 534 | network, you should also make sure that it provides a way for users to 535 | get its source. For example, if your program is a web application, its 536 | interface could display a "Source" link that leads users to an archive 537 | of the code. There are many ways you could offer source, and different 538 | solutions will be better for different programs; see section 13 for the 539 | specific requirements. 540 | You should also get your employer (if you work as a programmer) or school, 541 | if any, to sign a "copyright disclaimer" for the program, if necessary. 542 | For more information on this, and how to apply and follow the GNU AGPL, see 543 | . 544 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Little Library 2 | 3 | A digital give-a-book, take-a-book library for ebooks. 4 | 5 | ### Features 6 | 7 | - **Free Contribution:** allows DRM-free ebooks to be uploaded by anyone 8 | - **Built-in Reviews:** requires a description/reason/note for uploading 9 | - **Small Footprint:** only allows ebook files and limited metadata and optional library size limit 10 | - **Digital Physicality:** know when you're not the only one visiting the little library 11 | - **Single-borrower:** removes ebook files from server when someone takes it 12 | - **Metadata history:** keeps a history of all books that have been on shelf 13 | 14 | ### Requirements 15 | 16 | - [Node](https://nodejs.org) 12+ 17 | - [Yarn](https://yarnpkg.com) 1.12+ 18 | 19 | ### Installation 20 | 21 | Clone the repo: 22 | 23 | ```bash 24 | > git clone https://github.com/Alamantus/little-library.git 25 | ``` 26 | 27 | Navigate to the folder: 28 | 29 | ```bash 30 | > cd path/to/little-library 31 | ``` 32 | 33 | Run `yarn` to install all the dependencies: 34 | 35 | ```bash 36 | > yarn 37 | ``` 38 | 39 | Then copy `settings.example.json` to `settings.json` and make sure everything is to your liking. Below is the default `settings.example.json` file: 40 | 41 | ```js 42 | { 43 | "port": 3000, // The server's port 44 | "siteTitle": "Little Library", // The name that appears in the site header 45 | "titleSeparator": " | ", // The separator for the browser bar's title between page and site titles 46 | "fileLocation": "./public/files/", // The relative path to where the ebook files will be served from 47 | "historyLocation": "./public/history/", // The relative path to where the history metadata files will be served from 48 | "maxLibrarySize": 0, // The maximum number of books that can be added to the library. 0 means unlimited 49 | "maxFileSize": 0, // The maximum file size of an ebook allowed to be uploaded. 0 means unlimited 50 | "maxHistory": 0, // The maximum number of history metadata files that will be saved on your server. 0 means unlimited 51 | "allowedFormats": [".epub", ".mobi", ".pdf"], // The file formats allowed to be uploaded 52 | "backupPassword": "password", // The plaintext password that allows you to access the /backup features. Be sure to change this before going live! 53 | "hideVisitors": false, // If true, the "Current Visitors" counter will not update on the front end 54 | "sslPort": 443, // The port to serve HTTPS content from if your private key and certificate are specified 55 | "sslPrivateKey": null, // The ssl private key received from your certificate authority for HTTPS support 56 | "sslCertificate": null, // The ssl certificate received from your certificate authority for HTTPS support 57 | "sslCertificateAuthority": null, // The ssl certificate authority (CA) received from Let's Encrypt for HTTPS support 58 | "forceHTTPS": false // Redirect all traffic for http to https (not sure why you wouldn't want this) 59 | } 60 | ``` 61 | 62 | You can optionally copy the `customHtmlAfterFooter.example.html` to `customHtmlAfterFooter.html` if you want to add additional HTML to the bottom of the content container's body. This is useful for adding ` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "little-library", 3 | "version": "0.2.0", 4 | "description": "A digital give-a-book, take-a-book library for ebooks", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "repository": "https://github.com/Alamantus/little-library.git", 10 | "author": "Robbie Antenesse ", 11 | "license": "MIT", 12 | "dependencies": { 13 | "bulma": "^0.9.2", 14 | "cash-dom": "^8.1.0", 15 | "cookie-parser": "^1.4.5", 16 | "express": "^4.17.1", 17 | "express-fileupload": "^1.2.1", 18 | "fecha": "^4.2.0", 19 | "filenamify": "^4.2.0", 20 | "helmet": "^4.4.1", 21 | "onezip": "^5.0.0", 22 | "snarkdown": "^2.0.0", 23 | "socket.io": "^4.0.0", 24 | "socket.io-client": "^4.0.0", 25 | "striptags": "^3.1.1", 26 | "tinycolor2": "^1.4.2", 27 | "unused-filename": "^2.1.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /public/css/styles.css: -------------------------------------------------------------------------------- 1 | .is-clickable, 2 | .modal-button { 3 | cursor: pointer; 4 | } 5 | 6 | /* Bookshelf Styling */ 7 | .bookshelf { 8 | border: 4px solid saddlebrown; 9 | background: sienna; 10 | } 11 | 12 | .book-slot { 13 | position: relative; 14 | overflow: visible; 15 | width: 100px; 16 | height: 300px; /* The tallest a book could be */ 17 | border-bottom: 4px solid saddlebrown; 18 | margin: 10px 1px -4px !important; 19 | } 20 | .book-slot.is-thin { 21 | width: 80px !important; 22 | } 23 | .book-slot.is-thick { 24 | width: 120px !important; 25 | } 26 | 27 | .book { 28 | position: absolute; 29 | overflow: visible; 30 | top: 30px; 31 | right: 0; 32 | bottom: 0; 33 | left: 0; 34 | } 35 | .book.is-short { 36 | top: 60px; 37 | } 38 | .book.is-tall { 39 | top: 0; 40 | } 41 | 42 | .book .spine { 43 | position: relative; 44 | box-sizing: border-box; 45 | background: #c0ffee; 46 | border: 1px solid #aaa; 47 | width: 100%; 48 | height: 100%; 49 | margin-bottom: 4px; 50 | z-index: 1; 51 | transition: all 0.25s; 52 | } 53 | .book:hover .spine { 54 | width: 120%; 55 | margin-left: -10%; 56 | height: 120%; 57 | margin-top: -20%; 58 | z-index: 2; 59 | } 60 | 61 | .spine .text-container { 62 | width: 260px; 63 | height: 100px; 64 | transform: rotate(90deg) translateX(82px) translateY(80px); 65 | overflow: hidden; 66 | word-break: break-all; 67 | } 68 | .spine .title { 69 | font-size: 1.3em; 70 | } 71 | .spine .subtitle { 72 | font-size: 1.1em; 73 | } 74 | 75 | .book-slot.is-thin .book .spine .text-container { 76 | height: 80px; 77 | transform: rotate(90deg) translateX(92px) translateY(92px); 78 | } 79 | .book-slot.is-thick .book .spine .text-container { 80 | height: 120px; 81 | transform: rotate(90deg) translateX(72px) translateY(72px); 82 | } 83 | 84 | .book.is-short .spine .text-container { 85 | width: 230px; 86 | transform: rotate(90deg) translateX(68px) translateY(66px); 87 | } 88 | .book-slot.is-thin .book.is-short .spine .text-container { 89 | transform: rotate(90deg) translateX(78px) translateY(76px); 90 | } 91 | .book-slot.is-thick .book.is-short .spine .text-container { 92 | transform: rotate(90deg) translateX(58px) translateY(56px); 93 | } 94 | .book.is-short .spine .title { 95 | font-size: 1.1em; 96 | } 97 | .book.is-short .spine .subtitle { 98 | font-size: 0.9em; 99 | } 100 | .book.is-tall .spine .text-container { 101 | width: 290px; 102 | transform: rotate(90deg) translateX(98px) translateY(95px); 103 | } 104 | .book-slot.is-thin .book.is-tall .spine .text-container { 105 | transform: rotate(90deg) translateX(108px) translateY(106px); 106 | } 107 | .book-slot.is-thick .book.is-tall .spine .text-container { 108 | transform: rotate(90deg) translateX(88px) translateY(86px); 109 | } 110 | .book.is-tall .spine .title { 111 | font-size: 1.3em; 112 | } 113 | .book.is-tall .spine .subtitle { 114 | font-size: 1.1em; 115 | } 116 | -------------------------------------------------------------------------------- /public/files/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alamantus/little-library/054eee3d69109ef09488fb1b6a2ecdbdc59e5fc2/public/files/.gitkeep -------------------------------------------------------------------------------- /public/history/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alamantus/little-library/054eee3d69109ef09488fb1b6a2ecdbdc59e5fc2/public/history/.gitkeep -------------------------------------------------------------------------------- /public/js/little-library.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | var socket = io(); 3 | 4 | var downloadButton; 5 | 6 | socket.on('update visitors', function(visitors) { 7 | $('#visitors').text(visitors); 8 | }); 9 | 10 | socket.on('get book', function(url) { 11 | $(downloadButton).replaceWith('Download'); 12 | }); 13 | 14 | socket.on('remove book', function(bookId) { 15 | var book = $('#book_' + bookId); 16 | var modal = $(''); 17 | modal.find('.modal-background, .modal-close').on('click', function() { 18 | modal.remove(); 19 | }); 20 | book.after(modal); 21 | book.remove(); 22 | }); 23 | 24 | $('.navbar-burger').on('click', function() { 25 | if ($(this).hasClass('is-active')) { 26 | $(this).removeClass('is-active'); 27 | $(this).attr('aria-expanded', 'false'); 28 | $('.navbar-menu').removeClass('is-active'); 29 | } else { 30 | $(this).addClass('is-active'); 31 | $(this).attr('aria-expanded', 'true'); 32 | $('.navbar-menu').addClass('is-active'); 33 | } 34 | }); 35 | 36 | $('#readableToggle').on('click', function() { 37 | var useReadable = getCookieValue('useReadable'); 38 | document.cookie = 'useReadable=' + (useReadable !== 'yes' ? 'yes' : 'no'); 39 | window.location.reload(); 40 | }); 41 | 42 | $('.modal-background, .modal-close, .modal-card-head .delete, .modal .close').on('click', function() { 43 | $(this).closest('.modal').removeClass('is-active'); 44 | downloadButton = undefined; 45 | }); 46 | 47 | $('.modal-button').on('click', function() { 48 | var modal = $(this).data('modal'); 49 | $('#' + modal).addClass('is-active'); 50 | }); 51 | 52 | $('.take-book').on('click', function() { 53 | var id = $(this).data('book'); 54 | $('#book_' + id).find('.box') 55 | .removeClass('box').addClass(['notification', 'is-success']) 56 | .attr('title', 'This can be downloaded until you leave this page'); 57 | socket.emit('take book', id); 58 | downloadButton = this; 59 | $(this).addClass('is-loading'); 60 | }); 61 | 62 | $('#book').on('change', function() { 63 | var fileName = $(this).val(); 64 | if (fileName) { 65 | const lastIndexOfSlash = fileName.lastIndexOf('\\'); 66 | if (lastIndexOfSlash < 0) { 67 | lastIndexOfSlash = fileName.lastIndexOf('/'); 68 | } 69 | fileName = fileName.substr(lastIndexOfSlash + 1); 70 | } 71 | $('#bookFileName').text(fileName ? fileName : 'None Selected'); 72 | }); 73 | }); 74 | 75 | function getCookieValue(key) { 76 | var matches = document.cookie.match('(^|;)\\s*' + key + '\\s*=\\s*([^;]+)'); 77 | return matches ? matches.pop() : '' 78 | } -------------------------------------------------------------------------------- /routes/get_about.js: -------------------------------------------------------------------------------- 1 | module.exports = function (app) { 2 | app.server.get('/about', (req, res) => { 3 | const resourcePath = (req.url.substr(-1) === '/' ? '../' : './'); 4 | const body = app.templater.fill('./templates/pages/about.html', { resourcePath }); 5 | const html = app.templater.fill('./templates/htmlContainer.html', { title: 'About', body }); 6 | if (html) { 7 | res.send(html); 8 | } else { 9 | res.send('Something went wrong!'); 10 | } 11 | }); 12 | } -------------------------------------------------------------------------------- /routes/get_give.js: -------------------------------------------------------------------------------- 1 | module.exports = function (app) { 2 | app.server.get('/give', (req, res) => { 3 | const resourcePath = (req.url.substr(-1) === '/' ? '../' : './'); 4 | let body = app.templater.fill('./templates/pages/uploadForm.html', { resourcePath }); 5 | body = app.replaceBodyWithTooManyBooksWarning(body); 6 | 7 | const html = app.templater.fill('./templates/htmlContainer.html', { title: 'Give a Book', resourcePath, body }); 8 | res.send(html); 9 | }); 10 | } -------------------------------------------------------------------------------- /routes/get_history.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const snarkdown = require('snarkdown'); 4 | const fecha = require('fecha'); 5 | 6 | module.exports = function (app) { 7 | app.server.get('/history', (req, res) => { 8 | const files = fs.readdirSync(app.historyLocation).filter(fileName => fileName.includes('.json')) 9 | .map(fileName => { // Cache the file data so sorting doesn't need to re-check each file 10 | return { name: fileName, time: fs.statSync(path.resolve(app.historyLocation, fileName)).mtime.getTime() }; 11 | }).sort((a, b) => b.time - a.time).map(v => v.name); // Sort from newest to oldest. 12 | 13 | let history = files.map(fileName => { 14 | const bookData = JSON.parse(fs.readFileSync(path.resolve(app.historyLocation, fileName), 'utf8')); 15 | bookData.author = bookData.author ? bookData.author : 'author not provided'; 16 | bookData.contributor = bookData.contributor ? bookData.contributor : 'Anonymous'; 17 | bookData.source = bookData.source ? `

Originally retrieved from ${bookData.source}

` : ''; 18 | 19 | const id = fileName.replace('.json', ''); 20 | const added = fecha.format(new Date(bookData.added), 'hh:mm:ssA on dddd MMMM Do, YYYY'); 21 | const removed = fecha.format(new Date(parseInt(id)), 'hh:mm:ssA on dddd MMMM Do, YYYY'); 22 | const removedTag = '
Taken' + removed + '
'; 23 | const modal = app.templater.fill('./templates/elements/modalCard.html', { 24 | id, 25 | header: '

' + bookData.title + '

' + bookData.author + '

', 26 | content: app.templater.fill('./templates/elements/bookInfo.html', { 27 | contributor: bookData.contributor, 28 | source: bookData.source, 29 | fileFormat: bookData.fileType, 30 | added, 31 | removedTag, 32 | summary: snarkdown(bookData.summary), 33 | }), 34 | footer: 'Close', 35 | }); 36 | return app.templater.fill('./templates/elements/book_readable.html', { 37 | id, 38 | title: bookData.title, 39 | author: bookData.author, 40 | fileType: bookData.fileType, 41 | modal, 42 | }); 43 | }).join(''); 44 | 45 | if (history == '') { 46 | history = '
No books have been taken yet. Would you like to take a book?
'; 47 | } 48 | 49 | const body = '

History

' + history + '
'; 50 | const html = app.templater.fill('./templates/htmlContainer.html', { 51 | title: 'History', 52 | resourcePath: (req.url.substr(-1) === '/' ? '../' : './'), 53 | body 54 | }); 55 | 56 | if (html) { 57 | res.send(html); 58 | } else { 59 | res.send('Something went wrong!'); 60 | } 61 | }); 62 | } -------------------------------------------------------------------------------- /routes/get_home.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const snarkdown = require('snarkdown'); 4 | const fecha = require('fecha'); 5 | const tinycolor = require('tinycolor2'); 6 | 7 | const settings = require('../settings.json'); 8 | 9 | module.exports = function (app) { 10 | app.server.get('/', (req, res) => { 11 | const useReadable = req.cookies['useReadable'] === 'yes'; 12 | const files = fs.readdirSync(app.fileLocation).filter(fileName => fileName.includes('.json')) 13 | .map(fileName => { // Cache the file data so sorting doesn't need to re-check each file 14 | const stats = fs.statSync(path.resolve(app.fileLocation, fileName)); 15 | return { 16 | name: fileName, 17 | size: stats.size / (1000 * 1000), 18 | time: stats.mtime.getTime(), 19 | }; 20 | }).sort((a, b) => a.time - b.time); // Sort from oldest to newest. 21 | 22 | let books = files.map(fileDetails => { 23 | const bookData = JSON.parse(fs.readFileSync(path.resolve(app.fileLocation, fileDetails.name), 'utf8')); 24 | if (bookData.hasOwnProperty('fileName')) return ''; 25 | bookData.author = bookData.author ? bookData.author : 'author not provided'; 26 | bookData.contributor = bookData.contributor ? bookData.contributor : 'Anonymous'; 27 | bookData.source = bookData.source ? `

Originally retrieved from ${bookData.source}

` : ''; 28 | 29 | const id = fileDetails.name.replace('.json', ''); 30 | const confirmId = 'confirm_' + id; 31 | const added = fecha.format(new Date(bookData.added), 'hh:mm:ssA on dddd MMMM Do, YYYY'); 32 | const modal = app.templater.fill('./templates/elements/modalCard.html', { 33 | id, 34 | header: '

' + bookData.title + '

' + bookData.author + '

', 35 | content: app.templater.fill('./templates/elements/bookInfo.html', { 36 | contributor: bookData.contributor, 37 | source: bookData.source, 38 | fileFormat: bookData.fileType, 39 | added, 40 | summary: snarkdown(bookData.summary), 41 | }) 42 | + app.templater.fill('./templates/elements/modal.html', { 43 | id: confirmId, 44 | content: app.templater.fill('./templates/elements/messageBox.html', { 45 | header: 'Download Your Book', 46 | message: app.templater.fill('./templates/elements/takeConfirm.html', { id }), 47 | }), 48 | }), 49 | footer: 'Close Take Book', 50 | }); 51 | const maxSize = settings.maxFileSize > 0 ? settings.maxFileSize : 10; 52 | let spineColor = tinycolor('#' + id.substr(0, 6)); 53 | if (!spineColor.isValid()) { 54 | spineColor = tinycolor.random(); 55 | } 56 | return app.templater.fill(useReadable ? './templates/elements/book_readable.html' : './templates/elements/book.html', { 57 | id, 58 | title: bookData.title, 59 | author: bookData.author, 60 | thickness: (fileDetails.size > (maxSize * 0.3)) || (bookData.title.length > 28) 61 | ? 'is-thick' : (fileDetails.size < (maxSize * 0.6) ? 'is-thin' : ''), 62 | tallness: bookData.title.length > 16 ? 'is-tall' : (bookData.title.length < 8 ? 'is-short' : ''), 63 | spineColor: spineColor.toString(), 64 | textColor: spineColor.isLight() ? '#000000' : '#ffffff', 65 | fileType: bookData.fileType, 66 | modal, 67 | }); 68 | }).join(''); 69 | 70 | if (books == '') { 71 | books = '
The shelf is empty. Would you like to add a book?
'; 72 | } 73 | 74 | const body = '

Available Books

' 75 | + '' + (!useReadable ? 'Make it readable' : 'Make it look cool') + '' 76 | + '
' 77 | + books 78 | + '
'; 79 | const html = app.templater.fill('./templates/htmlContainer.html', { 80 | title: 'View', 81 | resourcePath: (req.url.substr(-1) === '/' ? '../' : './'), 82 | body 83 | }); 84 | 85 | if (html) { 86 | res.send(html); 87 | } else { 88 | res.send('Something went wrong!'); 89 | } 90 | }); 91 | } -------------------------------------------------------------------------------- /routes/get_tools.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | const settings = require('../settings.json'); 5 | 6 | module.exports = function (app) { 7 | app.server.get('/tools', (req, res) => { 8 | if (req.query.pass === settings.toolsPassword) { 9 | const templateValues = {}; 10 | let html = app.templater.fill('./templates/pages/tools.html', templateValues); 11 | 12 | if (req.query.do && ['resetVisitors'].includes(req.query.do)) { 13 | app.connections = 0; 14 | templateValues.resetVisitors = 'Done!'; 15 | html = app.templater.fill('./templates/pages/tools.html', templateValues); 16 | res.send(html); 17 | } else if (req.query.dl && ['files', 'history'].includes(req.query.dl)) { 18 | const onezip = require('onezip'); 19 | const { dl } = req.query; 20 | const saveLocation = path.resolve(app.fileLocation, dl + 'Backup.zip'); 21 | const backupLocation = dl === 'history' ? app.historyLocation : app.fileLocation; 22 | const files = fs.readdirSync(backupLocation).filter(fileName => !fileName.includes('.zip')); 23 | onezip.pack(backupLocation, saveLocation, files) 24 | .on('start', () => { 25 | console.info('Starting a backup zip of ' + dl) 26 | }) 27 | .on('error', (error) => { 28 | console.error(error); 29 | templateValues[dl + 'Download'] = 'Something went wrong: ' + JSON.stringify(error); 30 | html = app.templater.fill('./templates/pages/tools.html', templateValues); 31 | res.send(html); 32 | }) 33 | .on('end', () => { 34 | console.log('Backup complete. Saved to ' + saveLocation); 35 | let backupLocation = saveLocation.replace(/\\/g, '/'); 36 | backupLocation = backupLocation.substr(backupLocation.lastIndexOf('/')); 37 | templateValues[dl + 'Download'] = 'Download (This will be removed from the server in 1 hour)'; 38 | html = app.templater.fill('./templates/pages/tools.html', templateValues); 39 | res.send(html); 40 | console.log('Will delete ' + saveLocation + ' in 1 hour'); 41 | setTimeout(() => { 42 | fs.unlink(saveLocation, (err) => { 43 | if (err) { 44 | console.error(err); 45 | } else { 46 | console.log('Deleted backup file ' + saveLocation); 47 | } 48 | }) 49 | }, 60 * 60 * 1000); 50 | }); 51 | } else { 52 | res.send(html); 53 | } 54 | } else { 55 | res.status(400).send(); 56 | } 57 | }); 58 | } -------------------------------------------------------------------------------- /routes/middleware.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const express = require('express'); 4 | const helmet = require('helmet'); 5 | const cookieParser = require('cookie-parser'); 6 | const fileUpload = require('express-fileupload'); 7 | 8 | const settings = require('../settings.json'); 9 | 10 | module.exports = function (app) { 11 | const directives = helmet.contentSecurityPolicy.getDefaultDirectives(); 12 | if (!(app.https && settings.forceHTTPS)) delete directives['upgrade-insecure-requests']; 13 | app.server.use(helmet({ 14 | contentSecurityPolicy: { 15 | directives, 16 | }, 17 | })); 18 | 19 | // Opt out of Google Chrome tracking everything you do. 20 | // Note: if you’re reading this, stop using Google Chrome. 21 | // It is ridiculous for web servers to essentially have to ask 22 | // “please do not violate the privacy of the people who are viewing 23 | // this site” with every request. 24 | // For more info, see: https://plausible.io/blog/google-floc 25 | app.server.use((request, response, next) => { 26 | response.set('Permissions-Policy', 'interest-cohort=()'); 27 | next(); 28 | }); 29 | 30 | app.server.use(cookieParser()); 31 | 32 | app.server.use(express.json()); // support json encoded bodies 33 | app.server.use(express.urlencoded({ extended: true })); // support encoded bodies 34 | 35 | app.server.use('/give', fileUpload({ // support file uploads 36 | limits: { 37 | fileSize: (settings.maxFileSize > 0 ? settings.maxFileSize * 1024 * 1024 : Infinity), // filesize in bytes (settings accepts MB) 38 | }, 39 | })); 40 | app.server.use('/tools', fileUpload()); // Allow file upload on backup with no limits. 41 | 42 | app.server.use('/files', express.static(path.resolve('./public/files/'))); 43 | app.server.use('/css', express.static(path.resolve('./node_modules/bulma/css/'))); 44 | app.server.use('/css', express.static(path.resolve('./public/css/'))); 45 | app.server.use('/js', express.static(path.resolve('./public/js/'))); 46 | app.server.use('/js', express.static(path.resolve('./node_modules/cash-dom/dist/'))); 47 | app.server.use('/js', express.static(path.resolve('./node_modules/socket.io-client/dist/'))); 48 | 49 | // If a `.well-known` directory exists, allow it to be used for things like Let's Encrypt challenges 50 | if (fs.existsSync(path.resolve('./.well-known'))) { 51 | app.server.use('/.well-known', express.static(path.resolve('./.well-known'))); 52 | } 53 | 54 | if (app.https && settings.forceHTTPS) { 55 | app.server.use(function (req, res, next) { 56 | if (!req.secure) { 57 | return res.redirect(['https://', req.get('Host'), req.baseUrl].join('')); 58 | } 59 | next(); 0 60 | }); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /routes/post_give.js: -------------------------------------------------------------------------------- 1 | module.exports = function (app) { 2 | app.server.post('/give', (req, res) => { 3 | const resourcePath = (req.url.substr(-1) === '/' ? '../' : './'); 4 | const { title, author, summary, contributor, source } = req.body; 5 | if (Object.keys(req.files).length > 0 6 | && req.body.hasOwnProperty('title') && title.trim() !== '' 7 | && req.body.hasOwnProperty('summary') && summary.trim() !== '') { 8 | const { book } = req.files; 9 | const fileType = book.name.substr(book.name.lastIndexOf('.')); 10 | app.addBook({ book, title, author, summary, contributor, source, fileType }, () => { 11 | const messageBox = app.templater.fill('./templates/elements/messageBox.html', { 12 | style: 'is-success', 13 | header: 'Upload Successful', 14 | message: 'Thank you for your contribution!' 15 | }); 16 | const modal = app.templater.fill('./templates/elements/modal.html', { 17 | isActive: 'is-active', 18 | content: messageBox, 19 | }); 20 | let body = app.templater.fill('./templates/pages/uploadForm.html', { resourcePath }); 21 | body = app.replaceBodyWithTooManyBooksWarning(body); 22 | const html = app.templater.fill('./templates/htmlContainer.html', { title: 'Give a Book', resourcePath, body, modal }); 23 | res.send(html); 24 | }, (err) => { 25 | const messageBox = app.templater.fill('./templates/elements/messageBox.html', { 26 | style: 'is-danger', 27 | header: 'Upload Failed', 28 | message: err, 29 | }); 30 | const modal = app.templater.fill('./templates/elements/modal.html', { 31 | isActive: 'is-active', 32 | content: messageBox, 33 | }); 34 | let body = app.templater.fill('./templates/pages/uploadForm.html', { resourcePath, title, author, summary, contributor, source }); 35 | body = app.replaceBodyWithTooManyBooksWarning(body); 36 | const html = app.templater.fill('./templates/htmlContainer.html', { title: 'Give a Book', resourcePath, body, modal }); 37 | res.send(html); 38 | }); 39 | } else { 40 | let errorMessage = ''; 41 | if (Object.keys(req.files).length <= 0) { 42 | errorMessage += 'You have not selected a file.'; 43 | } 44 | if (!req.body.hasOwnProperty('title') || req.body.title.trim() === '') { 45 | errorMessage += (errorMessage.length > 0 ? '
' : '') + 'You have not written a title.'; 46 | } 47 | if (!req.body.hasOwnProperty('summary') || req.body.summary.trim() === '') { 48 | errorMessage += (errorMessage.length > 0 ? '
' : '') + 'You have not written a summary.'; 49 | } 50 | const message = app.templater.fill('./templates/elements/messageBox.html', { 51 | style: 'is-danger', 52 | header: 'Missing Required Fields', 53 | message: errorMessage, 54 | }); 55 | let body = app.templater.fill('./templates/pages/uploadForm.html', { resourcePath, title, author, summary, contributor, source }); 56 | body = app.replaceBodyWithTooManyBooksWarning(body); 57 | const html = app.templater.fill('./templates/htmlContainer.html', { title: 'Give a Book', resourcePath, body, message }); 58 | res.send(html); 59 | } 60 | }); 61 | } -------------------------------------------------------------------------------- /routes/post_tools.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | const settings = require('../settings.json'); 5 | 6 | module.exports = function (app) { 7 | app.server.post('/tools', (req, res) => { 8 | if (req.query.pass === settings.toolsPassword) { 9 | const templateValues = {}; 10 | let html = app.templater.fill('./templates/pages/tools.html', templateValues); 11 | 12 | const { files } = req; 13 | if (Object.keys(files).length > 0) { 14 | const backupType = Object.keys(files)[0]; 15 | if (['files', 'history'].includes(backupType)) { 16 | const onezip = require('onezip'); 17 | const uploadPath = path.resolve('./', backupType + 'UploadedBackup.zip'); 18 | files[backupType].mv(uploadPath, (err) => { 19 | if (err) { 20 | console.error(error); 21 | templateValues[backupType + 'UploadSuccess'] = 'Could not upload the file.'; 22 | html = app.templater.fill('./templates/pages/tools.html', templateValues); 23 | res.send(html); 24 | } else { 25 | onezip.extract(uploadPath, path.resolve('./public', backupType)) 26 | .on('start', () => { 27 | console.info('Extracting file ' + uploadPath) 28 | }) 29 | .on('error', (error) => { 30 | console.error(error); 31 | templateValues[backupType + 'UploadSuccess'] = 'Something went wrong: ' + JSON.stringify(error); 32 | html = app.templater.fill('./templates/pages/tools.html', templateValues); 33 | res.send(html); 34 | }) 35 | .on('end', () => { 36 | templateValues[backupType + 'UploadSuccess'] = 'Uploaded Successfully!'; 37 | html = app.templater.fill('./templates/pages/tools.html', templateValues); 38 | res.send(html); 39 | fs.unlink(uploadPath, (err) => { 40 | if (err) { 41 | console.error(err); 42 | } else { 43 | console.log('Deleted backup file ' + uploadPath); 44 | } 45 | }) 46 | }); 47 | } 48 | }); 49 | } else { 50 | templateValues['generalError'] = '

' + backupType + ' is not a valid backup type.

'; 51 | html = app.templater.fill('./templates/pages/tools.html', templateValues); 52 | res.send(html); 53 | } 54 | } else { 55 | res.send(html); 56 | } 57 | } else { 58 | res.status(400).send(); 59 | } 60 | }); 61 | } -------------------------------------------------------------------------------- /routes/socketio.js: -------------------------------------------------------------------------------- 1 | const settings = require('../settings.json'); 2 | 3 | module.exports = function (app) { 4 | app.io.on('connection', socket => { 5 | if (!settings.hideVisitors) { 6 | app.connections++; 7 | app.io.emit('update visitors', app.connections); 8 | } 9 | 10 | socket.on('take book', bookId => { 11 | const fileLocation = app.takeBook(bookId, socket.id); 12 | if (fileLocation) { 13 | console.log(socket.id + ' removed ' + bookId); 14 | const downloadLocation = fileLocation.substr(fileLocation.lastIndexOf('/')); 15 | socket.emit('get book', encodeURI('./files' + downloadLocation)); 16 | socket.broadcast.emit('remove book', bookId); 17 | } 18 | }); 19 | 20 | socket.on('disconnect', () => { 21 | if (!settings.hideVisitors) { 22 | app.connections--; 23 | if (app.connections < 0) app.connections = 0; 24 | app.io.emit('update visitors', app.connections); 25 | } 26 | app.deleteBooks(socket.id); 27 | }); 28 | }); 29 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const express = require('express'); 4 | const http = require('http'); 5 | const https = require('https'); 6 | const SocketIoServer = require('socket.io'); 7 | const filenamify = require('filenamify'); 8 | const unusedFilename = require('unused-filename'); 9 | const striptags = require('striptags'); 10 | 11 | const settings = require('./settings.json'); 12 | const privateKey = settings.sslPrivateKey ? fs.readFileSync(settings.sslPrivateKey, 'utf8') : null; 13 | const certificate = settings.sslCertificate ? fs.readFileSync(settings.sslCertificate, 'utf8') : null; 14 | const ca = settings.sslCertificateAuthority ? fs.readFileSync(settings.sslCertificateAuthority, 'utf8') : null; 15 | 16 | const Templater = require('./templates/Templater'); 17 | 18 | function Server () { 19 | this.server = express(); 20 | this.http = http.Server(this.server); 21 | this.https = privateKey && certificate ? https.createServer({ key: privateKey, cert: certificate, ca }, this.server) : null; 22 | this.io = SocketIoServer(); 23 | if (!settings.forceHTTPS) { 24 | this.io.attach(this.http); 25 | } 26 | if (this.https) { 27 | this.io.attach(this.https); 28 | } 29 | 30 | this.fileLocation = path.resolve(settings.fileLocation); 31 | this.historyLocation = path.resolve(settings.historyLocation); 32 | 33 | this.templater = new Templater(this); 34 | 35 | this.connections = 0; 36 | this.takenBooks = []; 37 | 38 | require('./routes/middleware')(this); 39 | 40 | require('./routes/get_home')(this); 41 | 42 | require('./routes/get_give')(this); 43 | require('./routes/post_give')(this); 44 | 45 | require('./routes/get_history')(this); 46 | 47 | require('./routes/get_about')(this); 48 | 49 | require('./routes/get_tools')(this); 50 | require('./routes/post_tools')(this); 51 | 52 | require('./routes/socketio')(this); 53 | } 54 | 55 | Server.prototype.replaceBodyWithTooManyBooksWarning = function (body) { 56 | if (settings.maxLibrarySize > 0) { 57 | const numberOfBooks = fs.readdirSync(this.fileLocation).filter(fileName => fileName.includes('.json')).length; 58 | if (numberOfBooks >= settings.maxLibrarySize) { 59 | body = this.templater.fill('./templates/elements/messageBox.html', { 60 | style: 'is-danger', 61 | title: 'Library Full', 62 | message: 'Sorry, the library has reached its maximum capacity for books! You will need to wait until a book is taken before a new one can be added.', 63 | }); 64 | } 65 | } 66 | 67 | return body; 68 | } 69 | 70 | Server.prototype.addBook = function (uploadData = {}, success = () => {}, error = () => {}) { 71 | const { book } = uploadData; 72 | 73 | // If the file is too big, error out. 74 | if (book.truncated === true) { 75 | delete book; 76 | return error('The file provided is too big'); 77 | } 78 | 79 | const bookId = this.uuid4(); 80 | const bookPath = path.resolve(this.fileLocation, bookId); 81 | 82 | const bookData = { 83 | title: striptags(uploadData.title.trim()), 84 | author: striptags(uploadData.author.trim()), 85 | summary: striptags(uploadData.summary.trim().replace(/\r\n/g, '\n')), 86 | contributor: striptags(uploadData.contributor.trim()), 87 | source: striptags(uploadData.source.trim()), 88 | added: Date.now(), 89 | fileType: uploadData.fileType, 90 | } 91 | 92 | const bookFilePath = unusedFilename.sync(path.resolve(bookPath + bookData.fileType)); 93 | return book.mv(bookFilePath, function (err) { 94 | if (err) { 95 | console.log(err); 96 | error(err); 97 | } else { 98 | const bookDataPath = unusedFilename.sync(path.resolve(bookPath + '.json')); 99 | fs.writeFileSync(bookDataPath, JSON.stringify(bookData)); 100 | success(); 101 | // console.log('uploaded ' + bookData.title + ' to ' + bookFilePath + ', and saved metadata to ' + bookDataPath); 102 | } 103 | }); 104 | } 105 | 106 | Server.prototype.takeBook = function (bookId, socketId) { 107 | return this.checkId(bookId, (bookPath, bookDataPath, bookData) => { 108 | const bookName = filenamify(bookData.title); 109 | const newFileName = unusedFilename.sync(path.resolve(this.fileLocation, bookName + bookData.fileType)); 110 | bookData.fileName = newFileName; 111 | fs.renameSync(bookPath, newFileName); 112 | fs.writeFileSync(bookDataPath, JSON.stringify(bookData)); 113 | this.takenBooks.push({ socketId, bookId }); 114 | return newFileName.replace(/\\/g, '/'); 115 | }); 116 | } 117 | 118 | Server.prototype.checkId = function (bookId, callback = () => {}) { 119 | const bookDataPath = path.resolve(this.fileLocation, bookId + '.json'); 120 | if (fs.existsSync(bookDataPath)) { 121 | const bookDataRaw = fs.readFileSync(bookDataPath); 122 | if (bookDataRaw) { 123 | const bookData = JSON.parse(bookDataRaw); 124 | const bookPath = bookData.hasOwnProperty('fileName') ? bookData.fileName : path.resolve(this.fileLocation, bookId + bookData.fileType); 125 | if (fs.existsSync(bookPath)) { 126 | return callback(bookPath, bookDataPath, bookData); 127 | } 128 | } 129 | } 130 | 131 | return false; 132 | } 133 | 134 | Server.prototype.deleteBooks = function (socketId) { 135 | this.takenBooks.forEach(data => { 136 | if (data.socketId === socketId) { 137 | const check = this.checkId(data.bookId, (bookPath, bookDataPath) => { 138 | fs.unlinkSync(bookPath); 139 | // console.log('removed ' + bookPath); 140 | fs.renameSync(bookDataPath, unusedFilename.sync(path.resolve(this.historyLocation, Date.now() + '.json'))); 141 | this.removeHistoryBeyondLimit(); 142 | }); 143 | if (check === false) { 144 | console.log('couldn\'t find data.bookId'); 145 | } 146 | } 147 | }); 148 | this.takenBooks = this.takenBooks.filter(data => data.socketId === socketId); 149 | } 150 | 151 | Server.prototype.removeHistoryBeyondLimit = function () { 152 | if (settings.maxHistory > 0) { 153 | let files = fs.readdirSync(this.historyLocation).filter(fileName => fileName.includes('.json')) 154 | .map(fileName => { // Cache the file data so sorting doesn't need to re-check each file 155 | return { name: fileName, time: fs.statSync(path.resolve(this.historyLocation, fileName)).mtime.getTime() }; 156 | }).sort((a, b) => b.time - a.time).map(v => v.name); // Sort from newest to oldest. 157 | if (files.length > settings.maxHistory) { 158 | files.slice(settings.maxHistory).forEach(fileName => { 159 | const filePath = path.resolve(this.historyLocation, fileName); 160 | fs.unlink(filePath, err => { 161 | if (err) { 162 | console.error(err); 163 | } else { 164 | console.log('Deleted ' + filePath); 165 | } 166 | }) 167 | }); 168 | } 169 | } 170 | } 171 | 172 | Server.prototype.uuid4 = function () { 173 | // https://stackoverflow.com/a/2117523 174 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 175 | var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); 176 | return v.toString(16); 177 | }); 178 | } 179 | 180 | Server.prototype.start = function () { 181 | this.http.listen((process.env.PORT || settings.port), () => { 182 | console.log('Started server on port ' + (process.env.PORT || settings.port)); 183 | }); 184 | if (this.https) { 185 | this.https.listen(settings.sslPort, () => { 186 | console.log('Started SSL server on port ' + settings.sslPort); 187 | }); 188 | } 189 | } 190 | 191 | const server = new Server(); 192 | server.start(); 193 | -------------------------------------------------------------------------------- /settings.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 3000, 3 | "siteTitle": "Little Library", 4 | "titleSeparator": " | ", 5 | "fileLocation": "./public/files/", 6 | "historyLocation": "./public/history/", 7 | "maxLibrarySize": 0, 8 | "maxFileSize": 0, 9 | "maxHistory": 0, 10 | "allowedFormats": [".epub", ".mobi", ".pdf"], 11 | "toolsPassword": "password", 12 | "hideVisitors": false, 13 | "sslPort": 443, 14 | "sslPrivateKey": null, 15 | "sslCertificate": null, 16 | "sslCertificateAuthority": null, 17 | "forceHTTPS": false 18 | } -------------------------------------------------------------------------------- /templates/Templater.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | const settings = require('../settings.json'); 5 | 6 | module.exports = class { 7 | constructor (app) { 8 | this.app = app; 9 | this.cache = {}; 10 | } 11 | 12 | fill (file, templateVars = {}) { 13 | let data; 14 | if (this.cache.hasOwnProperty(file)) { 15 | data = this.cache[file]; 16 | } else { 17 | data = fs.readFileSync(path.resolve(file), 'utf8'); 18 | } 19 | if (data) { 20 | if (!this.cache.hasOwnProperty(file)) { 21 | this.cache[file] = data; 22 | } 23 | 24 | let filledTemplate = data.replace(/\{\{siteTitle\}\}/g, settings.siteTitle) 25 | .replace(/\{\{titleSeparator\}\}/g, settings.titleSeparator) 26 | .replace(/\{\{allowedFormats\}\}/g, settings.allowedFormats.join(',')) 27 | .replace(/\{\{maxFileSize\}\}/g, (settings.maxFileSize > 0 ? settings.maxFileSize + 'MB' : 'no')); 28 | 29 | if (fs.existsSync(path.resolve('./customHtmlAfterFooter.html'))) { 30 | const customHtmlAfterFooter = fs.readFileSync(path.resolve('./customHtmlAfterFooter.html')); 31 | filledTemplate = filledTemplate.replace(/\{\{customHtmlAfterFooter\}\}/g, customHtmlAfterFooter); 32 | } 33 | 34 | for (let templateVar in templateVars) { 35 | const regExp = new RegExp('\{\{' + templateVar + '\}\}', 'g') 36 | filledTemplate = filledTemplate.replace(regExp, templateVars[templateVar]); 37 | } 38 | 39 | // If any template variable is not provided, don't even render them. 40 | filledTemplate = filledTemplate.replace(/\{\{[a-zA-Z0-9\-_]+\}\}/g, ''); 41 | 42 | return filledTemplate; 43 | } 44 | 45 | return data; 46 | } 47 | } -------------------------------------------------------------------------------- /templates/elements/book.html: -------------------------------------------------------------------------------- 1 |
2 | 14 | 15 | {{modal}} 16 |
-------------------------------------------------------------------------------- /templates/elements/bookInfo.html: -------------------------------------------------------------------------------- 1 |
2 |

Contributed by {{contributor}}

3 | {{source}} 4 |
5 |
6 |
7 | File Format 8 | {{fileFormat}} 9 |
10 |
11 |
12 |
13 | Added 14 | {{added}} 15 |
16 |
17 | {{removedTag}} 18 |
19 | 20 |
21 |

{{contributor}} said:

22 | {{summary}} 23 |
24 | 25 |
-------------------------------------------------------------------------------- /templates/elements/book_readable.html: -------------------------------------------------------------------------------- 1 |
2 | 10 | 11 | {{modal}} 12 |
-------------------------------------------------------------------------------- /templates/elements/messageBox.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{header}}

4 |
5 |
6 | {{message}} 7 |
8 |
-------------------------------------------------------------------------------- /templates/elements/modal.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/elements/modalCard.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/elements/takeConfirm.html: -------------------------------------------------------------------------------- 1 |
2 |

Please ensure that you're using a device that can download and save the file correctly!

3 |
4 |
After you leave or refresh this page, it will no longer be accessible to anyone!
5 | -------------------------------------------------------------------------------- /templates/htmlContainer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{title}}{{titleSeparator}}{{siteTitle}} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 61 | 62 |
63 |
64 | 65 | {{message}} 66 | 67 | {{body}} 68 | 69 |
70 |
71 | 72 |
73 |
74 |

75 | Little Library by Robbie Antenesse is intended 76 | for personal use only. 77 | The source code is 78 | licensed MIT. 79 |

80 |

81 | Any files uploaded to the server maintain the copyright attributed to them on their original creation on a 82 | case-by-case basis—please review the rights of the original authors before using them. 83 |

84 |
85 |
86 | 87 | {{modal}} 88 | 89 | 90 | {{customHtmlAfterFooter}} 91 | 92 | 93 | -------------------------------------------------------------------------------- /templates/pages/about.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

About

4 |
5 |

6 | {{siteTitle}} is a digital give a book, take a book website for e-books. Books on this 7 | site are treated as though they were physical, meaning that when someone takes a book, 8 | it becomes unavailable to be downloaded. 9 |

10 |

11 | Books that can be given and are available here are in the following file formats:
12 | {{allowedFormats}}
13 | with {{maxFileSize}} maximum file size. 14 |

15 | 16 |

Browsing

17 |

18 | You can view the available books on the home page of the site in exactly the way that they were 19 | provided by others. Items listed show the title, author (if provided), and the file type of the 20 | book, and clicking the item will reveal more information about the book, including the contributor 21 | and the contributor's reason for adding the book. 22 |

23 |

24 | While browsing, you will notice that there is a number in the top left corner of the website that 25 | shows how many people are currently looking at the library, including you. This number updates in 26 | real time as people come and go. If you are looking at a book and someone takes it, you will be 27 | notified that it was taken and will not be able to take it yourself. Please note that you cannot 28 | see what another person might be looking at, so the same thing happens to others when you take a 29 | book they are looking at—it is not a personal sleight against you if a book is taken. 30 |

31 |

32 | You can also browse the history of the shelf and see what books had once been in the library as 33 | well as what the contributor wrote about it. 34 |

35 | 36 |

Taking

37 |

38 | When looking at a book's information, you will notice a "Take Book" button. Clicking that will 39 | allow you to start the download process. 40 |

41 |

42 | After confirming that you understand, a new button labeled "Download" link will appear. Click it 43 | to start the download to your device. As soon as you confirm that you understand, the book will 44 | instantly become unavailable to anyone else viewing the library! If you leave or even refresh the 45 | page before downloading the file, it will be unavailable. Once you leave the page after 46 | clicking the "I understand" button, the file is deleted from the server. 47 |

48 |

49 | Please be careful, considerate, and responsible so you do not accidentally lose the file forever. 50 |

51 | 52 |

Giving

53 |

54 | When you give a book, in addition to the file itself, you are required to include the title 55 | of the book and your reason for giving it. You are encouraged to include your thoughts about 56 | the book when giving your reason. 57 | While it is not required, you are encouraged to also include the author of the book and your own 58 | name. 59 |

60 |

61 | When your book is taken from the shelf, the data that you entered when giving it will remain in 62 | the History listing so that anyone can look at what books 63 | had been on the shelf at one time and see what the contributor thought about it. 64 |

65 |
66 |
67 |
-------------------------------------------------------------------------------- /templates/pages/tools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tools{{titleSeparator}}{{siteTitle}} 5 | 6 | 7 |

Tools

8 |

Management

9 |

10 | 11 | 12 | {{resetVisitors}} 13 |

14 |

Backup/Restore

15 |

16 | These tools allows you to download a .zip file of your Files folder and your History 17 | folder or re-import one of the zipped files you received from a backup. 18 |

19 | 20 |
Export
21 |

22 | 23 | 24 | {{filesDownload}} 25 |

26 |

27 | 28 | 29 | {{historyDownload}} 30 |

31 | 32 |
Import
33 | {{generalError}} 34 |
35 |

36 |
37 | 38 | {{filesUploadSuccess}} 39 |

40 |

41 | 42 |

43 |
44 |
45 |

46 |
47 | 48 | {{historyUploadSuccess}} 49 |

50 |

51 | 52 |

53 |
54 | 55 | 80 | 81 | -------------------------------------------------------------------------------- /templates/pages/uploadForm.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | Give a Book 5 |

6 | 7 |
8 |

9 | Use this form to add a book to the library! 10 |

11 |
12 | 13 |
14 |
15 |
16 | 30 |
31 |
32 | 33 |
34 | 35 |
36 | 37 |
38 |
39 |
40 | 41 |
42 | 43 |
44 |
45 |
46 | 47 |
48 |
Markdown Enabled
49 | 50 |
51 |
52 |
53 | 54 |
55 | 56 |
57 |
58 |
59 | 60 |
61 |
Where you got the file from
62 | 63 |
64 |
65 | 66 |
67 | 68 |
69 |
70 |
71 |
-------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/component-emitter@^1.2.10": 6 | version "1.2.10" 7 | resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.10.tgz#ef5b1589b9f16544642e473db5ea5639107ef3ea" 8 | integrity sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg== 9 | 10 | "@types/cookie@^0.4.0": 11 | version "0.4.0" 12 | resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.0.tgz#14f854c0f93d326e39da6e3b6f34f7d37513d108" 13 | integrity sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg== 14 | 15 | "@types/cors@^2.8.8": 16 | version "2.8.10" 17 | resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.10.tgz#61cc8469849e5bcdd0c7044122265c39cec10cf4" 18 | integrity sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ== 19 | 20 | "@types/node@>=10.0.0": 21 | version "14.14.35" 22 | resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.35.tgz#42c953a4e2b18ab931f72477e7012172f4ffa313" 23 | integrity sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag== 24 | 25 | accepts@~1.3.4, accepts@~1.3.7: 26 | version "1.3.7" 27 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" 28 | integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== 29 | dependencies: 30 | mime-types "~2.1.24" 31 | negotiator "0.6.2" 32 | 33 | array-flatten@1.1.1: 34 | version "1.1.1" 35 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 36 | integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= 37 | 38 | backo2@~1.0.2: 39 | version "1.0.2" 40 | resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" 41 | integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= 42 | 43 | balanced-match@^1.0.0: 44 | version "1.0.0" 45 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 46 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 47 | 48 | base64-arraybuffer@0.1.4: 49 | version "0.1.4" 50 | resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" 51 | integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI= 52 | 53 | base64id@2.0.0, base64id@~2.0.0: 54 | version "2.0.0" 55 | resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" 56 | integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== 57 | 58 | body-parser@1.19.0: 59 | version "1.19.0" 60 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" 61 | integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== 62 | dependencies: 63 | bytes "3.1.0" 64 | content-type "~1.0.4" 65 | debug "2.6.9" 66 | depd "~1.1.2" 67 | http-errors "1.7.2" 68 | iconv-lite "0.4.24" 69 | on-finished "~2.3.0" 70 | qs "6.7.0" 71 | raw-body "2.4.0" 72 | type-is "~1.6.17" 73 | 74 | brace-expansion@^1.1.7: 75 | version "1.1.11" 76 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 77 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 78 | dependencies: 79 | balanced-match "^1.0.0" 80 | concat-map "0.0.1" 81 | 82 | buffer-crc32@~0.2.3: 83 | version "0.2.13" 84 | resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" 85 | integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= 86 | 87 | bulma@^0.9.2: 88 | version "0.9.2" 89 | resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.9.2.tgz#340011e119c605f19b8ca886bfea595f1deaf23c" 90 | integrity sha512-e14EF+3VSZ488yL/lJH0tR8mFWiEQVCMi/BQUMi2TGMBOk+zrDg4wryuwm/+dRSHJw0gMawp2tsW7X1JYUCE3A== 91 | 92 | busboy@^0.3.1: 93 | version "0.3.1" 94 | resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b" 95 | integrity sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw== 96 | dependencies: 97 | dicer "0.3.0" 98 | 99 | bytes@3.1.0: 100 | version "3.1.0" 101 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" 102 | integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== 103 | 104 | cash-dom@^8.1.0: 105 | version "8.1.0" 106 | resolved "https://registry.yarnpkg.com/cash-dom/-/cash-dom-8.1.0.tgz#ed8e278231c071b6596618131bd4320c57d32a31" 107 | integrity sha512-QTa50rFuPaX8klEDEbwLr+jVutwpvZEBQ0NpMMyng+je7gNe9Bz/JsOLHIG24tvNSSSIN/Q1QD0bnF6PQzWKHA== 108 | 109 | component-emitter@~1.3.0: 110 | version "1.3.0" 111 | resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" 112 | integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== 113 | 114 | concat-map@0.0.1: 115 | version "0.0.1" 116 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 117 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 118 | 119 | content-disposition@0.5.3: 120 | version "0.5.3" 121 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" 122 | integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== 123 | dependencies: 124 | safe-buffer "5.1.2" 125 | 126 | content-type@~1.0.4: 127 | version "1.0.4" 128 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 129 | integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== 130 | 131 | cookie-parser@^1.4.5: 132 | version "1.4.5" 133 | resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.5.tgz#3e572d4b7c0c80f9c61daf604e4336831b5d1d49" 134 | integrity sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw== 135 | dependencies: 136 | cookie "0.4.0" 137 | cookie-signature "1.0.6" 138 | 139 | cookie-signature@1.0.6: 140 | version "1.0.6" 141 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 142 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= 143 | 144 | cookie@0.4.0: 145 | version "0.4.0" 146 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" 147 | integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== 148 | 149 | cookie@~0.4.1: 150 | version "0.4.1" 151 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" 152 | integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== 153 | 154 | cors@~2.8.5: 155 | version "2.8.5" 156 | resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" 157 | integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== 158 | dependencies: 159 | object-assign "^4" 160 | vary "^1" 161 | 162 | currify@^4.0.0: 163 | version "4.0.0" 164 | resolved "https://registry.yarnpkg.com/currify/-/currify-4.0.0.tgz#54637df9a9752de8a0d59efbfb5ce59384a1e306" 165 | integrity sha512-ABfH28PWp5oqqp31cLXJQdeMqoFNej9rJOu84wKhN3jPCH7FAZg3zY1MVI27PTFoqfPlxOyhGmh9PzOVv+yN2g== 166 | 167 | debug@2.6.9: 168 | version "2.6.9" 169 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 170 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 171 | dependencies: 172 | ms "2.0.0" 173 | 174 | debug@~4.3.1: 175 | version "4.3.1" 176 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" 177 | integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== 178 | dependencies: 179 | ms "2.1.2" 180 | 181 | depd@~1.1.2: 182 | version "1.1.2" 183 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 184 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 185 | 186 | destroy@~1.0.4: 187 | version "1.0.4" 188 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 189 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= 190 | 191 | dicer@0.3.0: 192 | version "0.3.0" 193 | resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" 194 | integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA== 195 | dependencies: 196 | streamsearch "0.1.2" 197 | 198 | ee-first@1.1.1: 199 | version "1.1.1" 200 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 201 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 202 | 203 | encodeurl@~1.0.2: 204 | version "1.0.2" 205 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 206 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 207 | 208 | engine.io-client@~5.0.0: 209 | version "5.0.0" 210 | resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-5.0.0.tgz#65733887c8999d280e1dd7f241779a2c66e9559e" 211 | integrity sha512-e6GK0Fqvq45Nu/j7YdIVqXtDPvlsggAcfml3QiEiGdJ1qeh7IQU6knxSN3+yy9BmbnXtIfjo1hK4MFyHKdc9mQ== 212 | dependencies: 213 | base64-arraybuffer "0.1.4" 214 | component-emitter "~1.3.0" 215 | debug "~4.3.1" 216 | engine.io-parser "~4.0.1" 217 | has-cors "1.1.0" 218 | parseqs "0.0.6" 219 | parseuri "0.0.6" 220 | ws "~7.4.2" 221 | yeast "0.1.2" 222 | 223 | engine.io-parser@~4.0.0, engine.io-parser@~4.0.1: 224 | version "4.0.2" 225 | resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-4.0.2.tgz#e41d0b3fb66f7bf4a3671d2038a154024edb501e" 226 | integrity sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg== 227 | dependencies: 228 | base64-arraybuffer "0.1.4" 229 | 230 | engine.io@~5.0.0: 231 | version "5.0.0" 232 | resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-5.0.0.tgz#470dc94a8a4907fa4d2cd1fa6611426afcee61bf" 233 | integrity sha512-BATIdDV3H1SrE9/u2BAotvsmjJg0t1P4+vGedImSs1lkFAtQdvk4Ev1y4LDiPF7BPWgXWEG+NDY+nLvW3UrMWw== 234 | dependencies: 235 | accepts "~1.3.4" 236 | base64id "2.0.0" 237 | cookie "~0.4.1" 238 | cors "~2.8.5" 239 | debug "~4.3.1" 240 | engine.io-parser "~4.0.0" 241 | ws "~7.4.2" 242 | 243 | escape-html@~1.0.3: 244 | version "1.0.3" 245 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 246 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 247 | 248 | escape-string-regexp@^1.0.2: 249 | version "1.0.5" 250 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 251 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 252 | 253 | etag@~1.8.1: 254 | version "1.8.1" 255 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 256 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 257 | 258 | express-fileupload@^1.2.1: 259 | version "1.2.1" 260 | resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.2.1.tgz#73ac798bd14247d510adb1e439af2420c8367ded" 261 | integrity sha512-fWPNAkBj+Azt9Itmcz/Reqdg3LeBfaXptDEev2JM8bCC0yDptglCnlizhf0YZauyU5X/g6v7v4Xxqhg8tmEfEA== 262 | dependencies: 263 | busboy "^0.3.1" 264 | 265 | express@^4.17.1: 266 | version "4.17.1" 267 | resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" 268 | integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== 269 | dependencies: 270 | accepts "~1.3.7" 271 | array-flatten "1.1.1" 272 | body-parser "1.19.0" 273 | content-disposition "0.5.3" 274 | content-type "~1.0.4" 275 | cookie "0.4.0" 276 | cookie-signature "1.0.6" 277 | debug "2.6.9" 278 | depd "~1.1.2" 279 | encodeurl "~1.0.2" 280 | escape-html "~1.0.3" 281 | etag "~1.8.1" 282 | finalhandler "~1.1.2" 283 | fresh "0.5.2" 284 | merge-descriptors "1.0.1" 285 | methods "~1.1.2" 286 | on-finished "~2.3.0" 287 | parseurl "~1.3.3" 288 | path-to-regexp "0.1.7" 289 | proxy-addr "~2.0.5" 290 | qs "6.7.0" 291 | range-parser "~1.2.1" 292 | safe-buffer "5.1.2" 293 | send "0.17.1" 294 | serve-static "1.14.1" 295 | setprototypeof "1.1.1" 296 | statuses "~1.5.0" 297 | type-is "~1.6.18" 298 | utils-merge "1.0.1" 299 | vary "~1.1.2" 300 | 301 | fd-slicer@~1.1.0: 302 | version "1.1.0" 303 | resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" 304 | integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= 305 | dependencies: 306 | pend "~1.2.0" 307 | 308 | fecha@^4.2.0: 309 | version "4.2.0" 310 | resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.0.tgz#3ffb6395453e3f3efff850404f0a59b6747f5f41" 311 | integrity sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg== 312 | 313 | filename-reserved-regex@^2.0.0: 314 | version "2.0.0" 315 | resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" 316 | integrity sha1-q/c9+rc10EVECr/qLZHzieu/oik= 317 | 318 | filenamify@^4.2.0: 319 | version "4.2.0" 320 | resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-4.2.0.tgz#c99716d676869585b3b5d328b3f06590d032e89f" 321 | integrity sha512-pkgE+4p7N1n7QieOopmn3TqJaefjdWXwEkj2XLZJLKfOgcQKkn11ahvGNgTD8mLggexLiDFQxeTs14xVU22XPA== 322 | dependencies: 323 | filename-reserved-regex "^2.0.0" 324 | strip-outer "^1.0.1" 325 | trim-repeated "^1.0.0" 326 | 327 | finalhandler@~1.1.2: 328 | version "1.1.2" 329 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" 330 | integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== 331 | dependencies: 332 | debug "2.6.9" 333 | encodeurl "~1.0.2" 334 | escape-html "~1.0.3" 335 | on-finished "~2.3.0" 336 | parseurl "~1.3.3" 337 | statuses "~1.5.0" 338 | unpipe "~1.0.0" 339 | 340 | findit2@^2.2.3: 341 | version "2.2.3" 342 | resolved "https://registry.yarnpkg.com/findit2/-/findit2-2.2.3.tgz#58a466697df8a6205cdfdbf395536b8bd777a5f6" 343 | integrity sha1-WKRmaX34piBc39vzlVNri9d3pfY= 344 | 345 | forwarded@~0.1.2: 346 | version "0.1.2" 347 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 348 | integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= 349 | 350 | fresh@0.5.2: 351 | version "0.5.2" 352 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 353 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 354 | 355 | fs.realpath@^1.0.0: 356 | version "1.0.0" 357 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 358 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 359 | 360 | glob@^7.0.0: 361 | version "7.1.6" 362 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 363 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 364 | dependencies: 365 | fs.realpath "^1.0.0" 366 | inflight "^1.0.4" 367 | inherits "2" 368 | minimatch "^3.0.4" 369 | once "^1.3.0" 370 | path-is-absolute "^1.0.0" 371 | 372 | has-cors@1.1.0: 373 | version "1.1.0" 374 | resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" 375 | integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk= 376 | 377 | helmet@^4.4.1: 378 | version "4.4.1" 379 | resolved "https://registry.yarnpkg.com/helmet/-/helmet-4.4.1.tgz#a17e1444d81d7a83ddc6e6f9bc6e2055b994efe7" 380 | integrity sha512-G8tp0wUMI7i8wkMk2xLcEvESg5PiCitFMYgGRc/PwULB0RVhTP5GFdxOwvJwp9XVha8CuS8mnhmE8I/8dx/pbw== 381 | 382 | http-errors@1.7.2: 383 | version "1.7.2" 384 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" 385 | integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== 386 | dependencies: 387 | depd "~1.1.2" 388 | inherits "2.0.3" 389 | setprototypeof "1.1.1" 390 | statuses ">= 1.5.0 < 2" 391 | toidentifier "1.0.0" 392 | 393 | http-errors@~1.7.2: 394 | version "1.7.3" 395 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" 396 | integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== 397 | dependencies: 398 | depd "~1.1.2" 399 | inherits "2.0.4" 400 | setprototypeof "1.1.1" 401 | statuses ">= 1.5.0 < 2" 402 | toidentifier "1.0.0" 403 | 404 | iconv-lite@0.4.24: 405 | version "0.4.24" 406 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 407 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 408 | dependencies: 409 | safer-buffer ">= 2.1.2 < 3" 410 | 411 | inflight@^1.0.4: 412 | version "1.0.6" 413 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 414 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 415 | dependencies: 416 | once "^1.3.0" 417 | wrappy "1" 418 | 419 | inherits@2, inherits@2.0.4: 420 | version "2.0.4" 421 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 422 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 423 | 424 | inherits@2.0.3: 425 | version "2.0.3" 426 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 427 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 428 | 429 | ipaddr.js@1.9.1: 430 | version "1.9.1" 431 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 432 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== 433 | 434 | media-typer@0.3.0: 435 | version "0.3.0" 436 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 437 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= 438 | 439 | merge-descriptors@1.0.1: 440 | version "1.0.1" 441 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 442 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= 443 | 444 | methods@~1.1.2: 445 | version "1.1.2" 446 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 447 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 448 | 449 | mime-db@1.46.0: 450 | version "1.46.0" 451 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" 452 | integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== 453 | 454 | mime-types@~2.1.24: 455 | version "2.1.29" 456 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" 457 | integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ== 458 | dependencies: 459 | mime-db "1.46.0" 460 | 461 | mime@1.6.0: 462 | version "1.6.0" 463 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 464 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 465 | 466 | minimatch@^3.0.4: 467 | version "3.0.4" 468 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 469 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 470 | dependencies: 471 | brace-expansion "^1.1.7" 472 | 473 | modify-filename@^1.1.0: 474 | version "1.1.0" 475 | resolved "https://registry.yarnpkg.com/modify-filename/-/modify-filename-1.1.0.tgz#9a2dec83806fbb2d975f22beec859ca26b393aa1" 476 | integrity sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE= 477 | 478 | ms@2.0.0: 479 | version "2.0.0" 480 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 481 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 482 | 483 | ms@2.1.1: 484 | version "2.1.1" 485 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 486 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== 487 | 488 | ms@2.1.2: 489 | version "2.1.2" 490 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 491 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 492 | 493 | negotiator@0.6.2: 494 | version "0.6.2" 495 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" 496 | integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== 497 | 498 | object-assign@^4: 499 | version "4.1.1" 500 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 501 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 502 | 503 | on-finished@~2.3.0: 504 | version "2.3.0" 505 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 506 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= 507 | dependencies: 508 | ee-first "1.1.1" 509 | 510 | once@^1.3.0: 511 | version "1.4.0" 512 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 513 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 514 | dependencies: 515 | wrappy "1" 516 | 517 | onezip@^5.0.0: 518 | version "5.0.0" 519 | resolved "https://registry.yarnpkg.com/onezip/-/onezip-5.0.0.tgz#bd6ce531e1a50fb21a097bfaafe262a7dfd6e51a" 520 | integrity sha512-meTfyEkAvZ/YV/pO/zPcytTS1bYsc6HA8IupsnD/kJP+YekmfqVhd2nT9+D1s6j8PAJKGaauTspXxi+ecae5LQ== 521 | dependencies: 522 | currify "^4.0.0" 523 | findit2 "^2.2.3" 524 | glob "^7.0.0" 525 | pipe-io "^4.0.0" 526 | try-to-catch "^3.0.0" 527 | yargs-parser "^20.2.4" 528 | yauzl "^2.6.0" 529 | yazl "^2.4.1" 530 | 531 | parseqs@0.0.6: 532 | version "0.0.6" 533 | resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5" 534 | integrity sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w== 535 | 536 | parseuri@0.0.6: 537 | version "0.0.6" 538 | resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a" 539 | integrity sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow== 540 | 541 | parseurl@~1.3.3: 542 | version "1.3.3" 543 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 544 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== 545 | 546 | path-exists@^4.0.0: 547 | version "4.0.0" 548 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 549 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 550 | 551 | path-is-absolute@^1.0.0: 552 | version "1.0.1" 553 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 554 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 555 | 556 | path-to-regexp@0.1.7: 557 | version "0.1.7" 558 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 559 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= 560 | 561 | pend@~1.2.0: 562 | version "1.2.0" 563 | resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" 564 | integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= 565 | 566 | pipe-io@^4.0.0: 567 | version "4.0.1" 568 | resolved "https://registry.yarnpkg.com/pipe-io/-/pipe-io-4.0.1.tgz#5604da004caa2e4a08ccc338884888ea618ff5cc" 569 | integrity sha512-Wj9G85wJCpIgHq7xd0g4/IDjrA51pxmd+m9AbTiC6zRmWzVC6jOJIUyf92r7/B2+NE6zwqZIz0BZr85xkc3/Sg== 570 | 571 | proxy-addr@~2.0.5: 572 | version "2.0.6" 573 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" 574 | integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== 575 | dependencies: 576 | forwarded "~0.1.2" 577 | ipaddr.js "1.9.1" 578 | 579 | qs@6.7.0: 580 | version "6.7.0" 581 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" 582 | integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== 583 | 584 | range-parser@~1.2.1: 585 | version "1.2.1" 586 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 587 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== 588 | 589 | raw-body@2.4.0: 590 | version "2.4.0" 591 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" 592 | integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== 593 | dependencies: 594 | bytes "3.1.0" 595 | http-errors "1.7.2" 596 | iconv-lite "0.4.24" 597 | unpipe "1.0.0" 598 | 599 | safe-buffer@5.1.2: 600 | version "5.1.2" 601 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 602 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 603 | 604 | "safer-buffer@>= 2.1.2 < 3": 605 | version "2.1.2" 606 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 607 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 608 | 609 | send@0.17.1: 610 | version "0.17.1" 611 | resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" 612 | integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== 613 | dependencies: 614 | debug "2.6.9" 615 | depd "~1.1.2" 616 | destroy "~1.0.4" 617 | encodeurl "~1.0.2" 618 | escape-html "~1.0.3" 619 | etag "~1.8.1" 620 | fresh "0.5.2" 621 | http-errors "~1.7.2" 622 | mime "1.6.0" 623 | ms "2.1.1" 624 | on-finished "~2.3.0" 625 | range-parser "~1.2.1" 626 | statuses "~1.5.0" 627 | 628 | serve-static@1.14.1: 629 | version "1.14.1" 630 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" 631 | integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== 632 | dependencies: 633 | encodeurl "~1.0.2" 634 | escape-html "~1.0.3" 635 | parseurl "~1.3.3" 636 | send "0.17.1" 637 | 638 | setprototypeof@1.1.1: 639 | version "1.1.1" 640 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" 641 | integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== 642 | 643 | snarkdown@^2.0.0: 644 | version "2.0.0" 645 | resolved "https://registry.yarnpkg.com/snarkdown/-/snarkdown-2.0.0.tgz#b1feb4db91b9f94a8ebbd7a50f3e99aee18b1e03" 646 | integrity sha512-MgL/7k/AZdXCTJiNgrO7chgDqaB9FGM/1Tvlcenenb7div6obaDATzs16JhFyHHBGodHT3B7RzRc5qk8pFhg3A== 647 | 648 | socket.io-adapter@~2.2.0: 649 | version "2.2.0" 650 | resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.2.0.tgz#43af9157c4609e74b8addc6867873ac7eb48fda2" 651 | integrity sha512-rG49L+FwaVEwuAdeBRq49M97YI3ElVabJPzvHT9S6a2CWhDKnjSFasvwAwSYPRhQzfn4NtDIbCaGYgOCOU/rlg== 652 | 653 | socket.io-client@^4.0.0: 654 | version "4.0.0" 655 | resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.0.0.tgz#643cc25e5b5bbe37be75ecd317156a3335bb495a" 656 | integrity sha512-27yQxmXJAEYF19Ygyl8FPJ0if0wegpSmkIIbrWJeI7n7ST1JyH8bbD5v3fjjGY5cfCanACJ3dARUAyiVFNrlTQ== 657 | dependencies: 658 | "@types/component-emitter" "^1.2.10" 659 | backo2 "~1.0.2" 660 | component-emitter "~1.3.0" 661 | debug "~4.3.1" 662 | engine.io-client "~5.0.0" 663 | parseuri "0.0.6" 664 | socket.io-parser "~4.0.4" 665 | 666 | socket.io-parser@~4.0.3, socket.io-parser@~4.0.4: 667 | version "4.0.4" 668 | resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.4.tgz#9ea21b0d61508d18196ef04a2c6b9ab630f4c2b0" 669 | integrity sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g== 670 | dependencies: 671 | "@types/component-emitter" "^1.2.10" 672 | component-emitter "~1.3.0" 673 | debug "~4.3.1" 674 | 675 | socket.io@^4.0.0: 676 | version "4.0.0" 677 | resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.0.0.tgz#ee484a95dc6a38698491aaf63b6ec1f3ceeac0a8" 678 | integrity sha512-/c1riZMV/4yz7KEpaMhDQbwhJDIoO55whXaRKgyEBQrLU9zUHXo9rzeTMvTOqwL9mbKfHKdrXcMoCeQ/1YtMsg== 679 | dependencies: 680 | "@types/cookie" "^0.4.0" 681 | "@types/cors" "^2.8.8" 682 | "@types/node" ">=10.0.0" 683 | accepts "~1.3.4" 684 | base64id "~2.0.0" 685 | debug "~4.3.1" 686 | engine.io "~5.0.0" 687 | socket.io-adapter "~2.2.0" 688 | socket.io-parser "~4.0.3" 689 | 690 | "statuses@>= 1.5.0 < 2", statuses@~1.5.0: 691 | version "1.5.0" 692 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 693 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 694 | 695 | streamsearch@0.1.2: 696 | version "0.1.2" 697 | resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" 698 | integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= 699 | 700 | strip-outer@^1.0.1: 701 | version "1.0.1" 702 | resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" 703 | integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg== 704 | dependencies: 705 | escape-string-regexp "^1.0.2" 706 | 707 | striptags@^3.1.1: 708 | version "3.1.1" 709 | resolved "https://registry.yarnpkg.com/striptags/-/striptags-3.1.1.tgz#c8c3e7fdd6fb4bb3a32a3b752e5b5e3e38093ebd" 710 | integrity sha1-yMPn/db7S7OjKjt1LltePjgJPr0= 711 | 712 | tinycolor2@^1.4.2: 713 | version "1.4.2" 714 | resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" 715 | integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== 716 | 717 | toidentifier@1.0.0: 718 | version "1.0.0" 719 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" 720 | integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== 721 | 722 | trim-repeated@^1.0.0: 723 | version "1.0.0" 724 | resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" 725 | integrity sha1-42RqLqTokTEr9+rObPsFOAvAHCE= 726 | dependencies: 727 | escape-string-regexp "^1.0.2" 728 | 729 | try-to-catch@^3.0.0: 730 | version "3.0.0" 731 | resolved "https://registry.yarnpkg.com/try-to-catch/-/try-to-catch-3.0.0.tgz#a1903b44d13d5124c54d14a461d22ec1f52ea14b" 732 | integrity sha512-eIm6ZXwR35jVF8By/HdbbkcaCDTBI5PpCPkejRKrYp0jyf/DbCCcRhHD7/O9jtFI3ewsqo9WctFEiJTS6i+CQA== 733 | 734 | type-is@~1.6.17, type-is@~1.6.18: 735 | version "1.6.18" 736 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 737 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== 738 | dependencies: 739 | media-typer "0.3.0" 740 | mime-types "~2.1.24" 741 | 742 | unpipe@1.0.0, unpipe@~1.0.0: 743 | version "1.0.0" 744 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 745 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= 746 | 747 | unused-filename@^2.1.0: 748 | version "2.1.0" 749 | resolved "https://registry.yarnpkg.com/unused-filename/-/unused-filename-2.1.0.tgz#33719c4e8d9644f32d2dec1bc8525c6aaeb4ba51" 750 | integrity sha512-BMiNwJbuWmqCpAM1FqxCTD7lXF97AvfQC8Kr/DIeA6VtvhJaMDupZ82+inbjl5yVP44PcxOuCSxye1QMS0wZyg== 751 | dependencies: 752 | modify-filename "^1.1.0" 753 | path-exists "^4.0.0" 754 | 755 | utils-merge@1.0.1: 756 | version "1.0.1" 757 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 758 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= 759 | 760 | vary@^1, vary@~1.1.2: 761 | version "1.1.2" 762 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 763 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 764 | 765 | wrappy@1: 766 | version "1.0.2" 767 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 768 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 769 | 770 | ws@~7.4.2: 771 | version "7.4.4" 772 | resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59" 773 | integrity sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw== 774 | 775 | yargs-parser@^20.2.4: 776 | version "20.2.7" 777 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a" 778 | integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw== 779 | 780 | yauzl@^2.6.0: 781 | version "2.10.0" 782 | resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" 783 | integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= 784 | dependencies: 785 | buffer-crc32 "~0.2.3" 786 | fd-slicer "~1.1.0" 787 | 788 | yazl@^2.4.1: 789 | version "2.5.1" 790 | resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" 791 | integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== 792 | dependencies: 793 | buffer-crc32 "~0.2.3" 794 | 795 | yeast@0.1.2: 796 | version "0.1.2" 797 | resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" 798 | integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= 799 | --------------------------------------------------------------------------------