initial commit

AuthorKonata <konata@posteo.jp>
Date
Commit311eb463e44edfb498a976065b21bafbc50bea39
Parent23bc73a
18 files changed, 2303 insertions(+), 109 deletions(-)
A.dockerignore
@@ -0,0 +1,44 @@
1+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+# dependencies
4+/node_modules
5+/.pnp
6+.pnp.js
7+
8+# testing
9+/coverage
10+
11+# next.js
12+/.next/
13+/out/
14+
15+# production
16+/build
17+
18+# misc
19+.DS_Store
20+*.pem
21+
22+# debug
23+npm-debug.log*
24+yarn-debug.log*
25+yarn-error.log*
26+
27+# local env files
28+.env.local
29+.env.development.local
30+.env.test.local
31+.env.production.local
32+
33+# vercel
34+.vercel
35+
36+**/*.trace
37+**/*.zip
38+**/*.tar.gz
39+**/*.tgz
40+**/*.log
41+package-lock.json
42+**/*.bun
43+*.sqlite
44+assets/dist
M.gitignore
@@ -39,4 +39,6 @@ yarn-error.log*
3939 **/*.tgz
4040 **/*.log
4141 package-lock.json
42-**/*.bun
42+**/*.bun
43+*.sqlite
44+assets/dist
ADockerfile
@@ -0,0 +1,8 @@
1+FROM debian:12-slim
2+RUN apt-get update && apt-get install -y curl unzip
3+RUN curl -fsSL https://bun.sh/install | bash
4+WORKDIR /app
5+COPY . .
6+RUN /root/.bun/bin/bun install
7+EXPOSE 3000
8+CMD ["/root/.bun/bin/bun", "run", "prod"]
ALICENSE
@@ -0,0 +1,661 @@
1+ GNU AFFERO GENERAL PUBLIC LICENSE
2+ Version 3, 19 November 2007
3+
4+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5+ Everyone is permitted to copy and distribute verbatim copies
6+ of this license document, but changing it is not allowed.
7+
8+ Preamble
9+
10+ The GNU Affero General Public License is a free, copyleft license for
11+software and other kinds of works, specifically designed to ensure
12+cooperation with the community in the case of network server software.
13+
14+ The licenses for most software and other practical works are designed
15+to take away your freedom to share and change the works. By contrast,
16+our General Public Licenses are intended to guarantee your freedom to
17+share and change all versions of a program--to make sure it remains free
18+software for all its users.
19+
20+ When we speak of free software, we are referring to freedom, not
21+price. Our General Public Licenses are designed to make sure that you
22+have the freedom to distribute copies of free software (and charge for
23+them if you wish), that you receive source code or can get it if you
24+want it, that you can change the software or use pieces of it in new
25+free programs, and that you know you can do these things.
26+
27+ Developers that use our General Public Licenses protect your rights
28+with two steps: (1) assert copyright on the software, and (2) offer
29+you this License which gives you legal permission to copy, distribute
30+and/or modify the software.
31+
32+ A secondary benefit of defending all users' freedom is that
33+improvements made in alternate versions of the program, if they
34+receive widespread use, become available for other developers to
35+incorporate. Many developers of free software are heartened and
36+encouraged by the resulting cooperation. However, in the case of
37+software used on network servers, this result may fail to come about.
38+The GNU General Public License permits making a modified version and
39+letting the public access it on a server without ever releasing its
40+source code to the public.
41+
42+ The GNU Affero General Public License is designed specifically to
43+ensure that, in such cases, the modified source code becomes available
44+to the community. It requires the operator of a network server to
45+provide the source code of the modified version running there to the
46+users of that server. Therefore, public use of a modified version, on
47+a publicly accessible server, gives the public access to the source
48+code of the modified version.
49+
50+ An older license, called the Affero General Public License and
51+published by Affero, was designed to accomplish similar goals. This is
52+a different license, not a version of the Affero GPL, but Affero has
53+released a new version of the Affero GPL which permits relicensing under
54+this license.
55+
56+ The precise terms and conditions for copying, distribution and
57+modification follow.
58+
59+ TERMS AND CONDITIONS
60+
61+ 0. Definitions.
62+
63+ "This License" refers to version 3 of the GNU Affero General Public License.
64+
65+ "Copyright" also means copyright-like laws that apply to other kinds of
66+works, such as semiconductor masks.
67+
68+ "The Program" refers to any copyrightable work licensed under this
69+License. Each licensee is addressed as "you". "Licensees" and
70+"recipients" may be individuals or organizations.
71+
72+ To "modify" a work means to copy from or adapt all or part of the work
73+in a fashion requiring copyright permission, other than the making of an
74+exact copy. The resulting work is called a "modified version" of the
75+earlier work or a work "based on" the earlier work.
76+
77+ A "covered work" means either the unmodified Program or a work based
78+on the Program.
79+
80+ To "propagate" a work means to do anything with it that, without
81+permission, would make you directly or secondarily liable for
82+infringement under applicable copyright law, except executing it on a
83+computer or modifying a private copy. Propagation includes copying,
84+distribution (with or without modification), making available to the
85+public, and in some countries other activities as well.
86+
87+ To "convey" a work means any kind of propagation that enables other
88+parties to make or receive copies. Mere interaction with a user through
89+a computer network, with no transfer of a copy, is not conveying.
90+
91+ An interactive user interface displays "Appropriate Legal Notices"
92+to the extent that it includes a convenient and prominently visible
93+feature that (1) displays an appropriate copyright notice, and (2)
94+tells the user that there is no warranty for the work (except to the
95+extent that warranties are provided), that licensees may convey the
96+work under this License, and how to view a copy of this License. If
97+the interface presents a list of user commands or options, such as a
98+menu, a prominent item in the list meets this criterion.
99+
100+ 1. Source Code.
101+
102+ The "source code" for a work means the preferred form of the work
103+for making modifications to it. "Object code" means any non-source
104+form of a work.
105+
106+ A "Standard Interface" means an interface that either is an official
107+standard defined by a recognized standards body, or, in the case of
108+interfaces specified for a particular programming language, one that
109+is widely used among developers working in that language.
110+
111+ The "System Libraries" of an executable work include anything, other
112+than the work as a whole, that (a) is included in the normal form of
113+packaging a Major Component, but which is not part of that Major
114+Component, and (b) serves only to enable use of the work with that
115+Major Component, or to implement a Standard Interface for which an
116+implementation is available to the public in source code form. A
117+"Major Component", in this context, means a major essential component
118+(kernel, window system, and so on) of the specific operating system
119+(if any) on which the executable work runs, or a compiler used to
120+produce the work, or an object code interpreter used to run it.
121+
122+ The "Corresponding Source" for a work in object code form means all
123+the source code needed to generate, install, and (for an executable
124+work) run the object code and to modify the work, including scripts to
125+control those activities. However, it does not include the work's
126+System Libraries, or general-purpose tools or generally available free
127+programs which are used unmodified in performing those activities but
128+which are not part of the work. For example, Corresponding Source
129+includes interface definition files associated with source files for
130+the work, and the source code for shared libraries and dynamically
131+linked subprograms that the work is specifically designed to require,
132+such as by intimate data communication or control flow between those
133+subprograms and other parts of the work.
134+
135+ The Corresponding Source need not include anything that users
136+can regenerate automatically from other parts of the Corresponding
137+Source.
138+
139+ The Corresponding Source for a work in source code form is that
140+same work.
141+
142+ 2. Basic Permissions.
143+
144+ All rights granted under this License are granted for the term of
145+copyright on the Program, and are irrevocable provided the stated
146+conditions are met. This License explicitly affirms your unlimited
147+permission to run the unmodified Program. The output from running a
148+covered work is covered by this License only if the output, given its
149+content, constitutes a covered work. This License acknowledges your
150+rights of fair use or other equivalent, as provided by copyright law.
151+
152+ You may make, run and propagate covered works that you do not
153+convey, without conditions so long as your license otherwise remains
154+in force. You may convey covered works to others for the sole purpose
155+of having them make modifications exclusively for you, or provide you
156+with facilities for running those works, provided that you comply with
157+the terms of this License in conveying all material for which you do
158+not control copyright. Those thus making or running the covered works
159+for you must do so exclusively on your behalf, under your direction
160+and control, on terms that prohibit them from making any copies of
161+your copyrighted material outside their relationship with you.
162+
163+ Conveying under any other circumstances is permitted solely under
164+the conditions stated below. Sublicensing is not allowed; section 10
165+makes it unnecessary.
166+
167+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168+
169+ No covered work shall be deemed part of an effective technological
170+measure under any applicable law fulfilling obligations under article
171+11 of the WIPO copyright treaty adopted on 20 December 1996, or
172+similar laws prohibiting or restricting circumvention of such
173+measures.
174+
175+ When you convey a covered work, you waive any legal power to forbid
176+circumvention of technological measures to the extent such circumvention
177+is effected by exercising rights under this License with respect to
178+the covered work, and you disclaim any intention to limit operation or
179+modification of the work as a means of enforcing, against the work's
180+users, your or third parties' legal rights to forbid circumvention of
181+technological measures.
182+
183+ 4. Conveying Verbatim Copies.
184+
185+ You may convey verbatim copies of the Program's source code as you
186+receive it, in any medium, provided that you conspicuously and
187+appropriately publish on each copy an appropriate copyright notice;
188+keep intact all notices stating that this License and any
189+non-permissive terms added in accord with section 7 apply to the code;
190+keep intact all notices of the absence of any warranty; and give all
191+recipients a copy of this License along with the Program.
192+
193+ You may charge any price or no price for each copy that you convey,
194+and you may offer support or warranty protection for a fee.
195+
196+ 5. Conveying Modified Source Versions.
197+
198+ You may convey a work based on the Program, or the modifications to
199+produce it from the Program, in the form of source code under the
200+terms of section 4, provided that you also meet all of these conditions:
201+
202+ a) The work must carry prominent notices stating that you modified
203+ it, and giving a relevant date.
204+
205+ b) The work must carry prominent notices stating that it is
206+ released under this License and any conditions added under section
207+ 7. This requirement modifies the requirement in section 4 to
208+ "keep intact all notices".
209+
210+ c) You must license the entire work, as a whole, under this
211+ License to anyone who comes into possession of a copy. This
212+ License will therefore apply, along with any applicable section 7
213+ additional terms, to the whole of the work, and all its parts,
214+ regardless of how they are packaged. This License gives no
215+ permission to license the work in any other way, but it does not
216+ invalidate such permission if you have separately received it.
217+
218+ d) If the work has interactive user interfaces, each must display
219+ Appropriate Legal Notices; however, if the Program has interactive
220+ interfaces that do not display Appropriate Legal Notices, your
221+ work need not make them do so.
222+
223+ A compilation of a covered work with other separate and independent
224+works, which are not by their nature extensions of the covered work,
225+and which are not combined with it such as to form a larger program,
226+in or on a volume of a storage or distribution medium, is called an
227+"aggregate" if the compilation and its resulting copyright are not
228+used to limit the access or legal rights of the compilation's users
229+beyond what the individual works permit. Inclusion of a covered work
230+in an aggregate does not cause this License to apply to the other
231+parts of the aggregate.
232+
233+ 6. Conveying Non-Source Forms.
234+
235+ You may convey a covered work in object code form under the terms
236+of sections 4 and 5, provided that you also convey the
237+machine-readable Corresponding Source under the terms of this License,
238+in one of these ways:
239+
240+ a) Convey the object code in, or embodied in, a physical product
241+ (including a physical distribution medium), accompanied by the
242+ Corresponding Source fixed on a durable physical medium
243+ customarily used for software interchange.
244+
245+ b) Convey the object code in, or embodied in, a physical product
246+ (including a physical distribution medium), accompanied by a
247+ written offer, valid for at least three years and valid for as
248+ long as you offer spare parts or customer support for that product
249+ model, to give anyone who possesses the object code either (1) a
250+ copy of the Corresponding Source for all the software in the
251+ product that is covered by this License, on a durable physical
252+ medium customarily used for software interchange, for a price no
253+ more than your reasonable cost of physically performing this
254+ conveying of source, or (2) access to copy the
255+ Corresponding Source from a network server at no charge.
256+
257+ c) Convey individual copies of the object code with a copy of the
258+ written offer to provide the Corresponding Source. This
259+ alternative is allowed only occasionally and noncommercially, and
260+ only if you received the object code with such an offer, in accord
261+ with subsection 6b.
262+
263+ d) Convey the object code by offering access from a designated
264+ place (gratis or for a charge), and offer equivalent access to the
265+ Corresponding Source in the same way through the same place at no
266+ further charge. You need not require recipients to copy the
267+ Corresponding Source along with the object code. If the place to
268+ copy the object code is a network server, the Corresponding Source
269+ may be on a different server (operated by you or a third party)
270+ that supports equivalent copying facilities, provided you maintain
271+ clear directions next to the object code saying where to find the
272+ Corresponding Source. Regardless of what server hosts the
273+ Corresponding Source, you remain obligated to ensure that it is
274+ available for as long as needed to satisfy these requirements.
275+
276+ e) Convey the object code using peer-to-peer transmission, provided
277+ you inform other peers where the object code and Corresponding
278+ Source of the work are being offered to the general public at no
279+ charge under subsection 6d.
280+
281+ A separable portion of the object code, whose source code is excluded
282+from the Corresponding Source as a System Library, need not be
283+included in conveying the object code work.
284+
285+ A "User Product" is either (1) a "consumer product", which means any
286+tangible personal property which is normally used for personal, family,
287+or household purposes, or (2) anything designed or sold for incorporation
288+into a dwelling. In determining whether a product is a consumer product,
289+doubtful cases shall be resolved in favor of coverage. For a particular
290+product received by a particular user, "normally used" refers to a
291+typical or common use of that class of product, regardless of the status
292+of the particular user or of the way in which the particular user
293+actually uses, or expects or is expected to use, the product. A product
294+is a consumer product regardless of whether the product has substantial
295+commercial, industrial or non-consumer uses, unless such uses represent
296+the only significant mode of use of the product.
297+
298+ "Installation Information" for a User Product means any methods,
299+procedures, authorization keys, or other information required to install
300+and execute modified versions of a covered work in that User Product from
301+a modified version of its Corresponding Source. The information must
302+suffice to ensure that the continued functioning of the modified object
303+code is in no case prevented or interfered with solely because
304+modification has been made.
305+
306+ If you convey an object code work under this section in, or with, or
307+specifically for use in, a User Product, and the conveying occurs as
308+part of a transaction in which the right of possession and use of the
309+User Product is transferred to the recipient in perpetuity or for a
310+fixed term (regardless of how the transaction is characterized), the
311+Corresponding Source conveyed under this section must be accompanied
312+by the Installation Information. But this requirement does not apply
313+if neither you nor any third party retains the ability to install
314+modified object code on the User Product (for example, the work has
315+been installed in ROM).
316+
317+ The requirement to provide Installation Information does not include a
318+requirement to continue to provide support service, warranty, or updates
319+for a work that has been modified or installed by the recipient, or for
320+the User Product in which it has been modified or installed. Access to a
321+network may be denied when the modification itself materially and
322+adversely affects the operation of the network or violates the rules and
323+protocols for communication across the network.
324+
325+ Corresponding Source conveyed, and Installation Information provided,
326+in accord with this section must be in a format that is publicly
327+documented (and with an implementation available to the public in
328+source code form), and must require no special password or key for
329+unpacking, reading or copying.
330+
331+ 7. Additional Terms.
332+
333+ "Additional permissions" are terms that supplement the terms of this
334+License by making exceptions from one or more of its conditions.
335+Additional permissions that are applicable to the entire Program shall
336+be treated as though they were included in this License, to the extent
337+that they are valid under applicable law. If additional permissions
338+apply only to part of the Program, that part may be used separately
339+under those permissions, but the entire Program remains governed by
340+this License without regard to the additional permissions.
341+
342+ When you convey a copy of a covered work, you may at your option
343+remove any additional permissions from that copy, or from any part of
344+it. (Additional permissions may be written to require their own
345+removal in certain cases when you modify the work.) You may place
346+additional permissions on material, added by you to a covered work,
347+for which you have or can give appropriate copyright permission.
348+
349+ Notwithstanding any other provision of this License, for material you
350+add to a covered work, you may (if authorized by the copyright holders of
351+that material) supplement the terms of this License with terms:
352+
353+ a) Disclaiming warranty or limiting liability differently from the
354+ terms of sections 15 and 16 of this License; or
355+
356+ b) Requiring preservation of specified reasonable legal notices or
357+ author attributions in that material or in the Appropriate Legal
358+ Notices displayed by works containing it; or
359+
360+ c) Prohibiting misrepresentation of the origin of that material, or
361+ requiring that modified versions of such material be marked in
362+ reasonable ways as different from the original version; or
363+
364+ d) Limiting the use for publicity purposes of names of licensors or
365+ authors of the material; or
366+
367+ e) Declining to grant rights under trademark law for use of some
368+ trade names, trademarks, or service marks; or
369+
370+ f) Requiring indemnification of licensors and authors of that
371+ material by anyone who conveys the material (or modified versions of
372+ it) with contractual assumptions of liability to the recipient, for
373+ any liability that these contractual assumptions directly impose on
374+ those licensors and authors.
375+
376+ All other non-permissive additional terms are considered "further
377+restrictions" within the meaning of section 10. If the Program as you
378+received it, or any part of it, contains a notice stating that it is
379+governed by this License along with a term that is a further
380+restriction, you may remove that term. If a license document contains
381+a further restriction but permits relicensing or conveying under this
382+License, you may add to a covered work material governed by the terms
383+of that license document, provided that the further restriction does
384+not survive such relicensing or conveying.
385+
386+ If you add terms to a covered work in accord with this section, you
387+must place, in the relevant source files, a statement of the
388+additional terms that apply to those files, or a notice indicating
389+where to find the applicable terms.
390+
391+ Additional terms, permissive or non-permissive, may be stated in the
392+form of a separately written license, or stated as exceptions;
393+the above requirements apply either way.
394+
395+ 8. Termination.
396+
397+ You may not propagate or modify a covered work except as expressly
398+provided under this License. Any attempt otherwise to propagate or
399+modify it is void, and will automatically terminate your rights under
400+this License (including any patent licenses granted under the third
401+paragraph of section 11).
402+
403+ However, if you cease all violation of this License, then your
404+license from a particular copyright holder is reinstated (a)
405+provisionally, unless and until the copyright holder explicitly and
406+finally terminates your license, and (b) permanently, if the copyright
407+holder fails to notify you of the violation by some reasonable means
408+prior to 60 days after the cessation.
409+
410+ Moreover, your license from a particular copyright holder is
411+reinstated permanently if the copyright holder notifies you of the
412+violation by some reasonable means, this is the first time you have
413+received notice of violation of this License (for any work) from that
414+copyright holder, and you cure the violation prior to 30 days after
415+your receipt of the notice.
416+
417+ Termination of your rights under this section does not terminate the
418+licenses of parties who have received copies or rights from you under
419+this License. If your rights have been terminated and not permanently
420+reinstated, you do not qualify to receive new licenses for the same
421+material under section 10.
422+
423+ 9. Acceptance Not Required for Having Copies.
424+
425+ You are not required to accept this License in order to receive or
426+run a copy of the Program. Ancillary propagation of a covered work
427+occurring solely as a consequence of using peer-to-peer transmission
428+to receive a copy likewise does not require acceptance. However,
429+nothing other than this License grants you permission to propagate or
430+modify any covered work. These actions infringe copyright if you do
431+not accept this License. Therefore, by modifying or propagating a
432+covered work, you indicate your acceptance of this License to do so.
433+
434+ 10. Automatic Licensing of Downstream Recipients.
435+
436+ Each time you convey a covered work, the recipient automatically
437+receives a license from the original licensors, to run, modify and
438+propagate that work, subject to this License. You are not responsible
439+for enforcing compliance by third parties with this License.
440+
441+ An "entity transaction" is a transaction transferring control of an
442+organization, or substantially all assets of one, or subdividing an
443+organization, or merging organizations. If propagation of a covered
444+work results from an entity transaction, each party to that
445+transaction who receives a copy of the work also receives whatever
446+licenses to the work the party's predecessor in interest had or could
447+give under the previous paragraph, plus a right to possession of the
448+Corresponding Source of the work from the predecessor in interest, if
449+the predecessor has it or can get it with reasonable efforts.
450+
451+ You may not impose any further restrictions on the exercise of the
452+rights granted or affirmed under this License. For example, you may
453+not impose a license fee, royalty, or other charge for exercise of
454+rights granted under this License, and you may not initiate litigation
455+(including a cross-claim or counterclaim in a lawsuit) alleging that
456+any patent claim is infringed by making, using, selling, offering for
457+sale, or importing the Program or any portion of it.
458+
459+ 11. Patents.
460+
461+ A "contributor" is a copyright holder who authorizes use under this
462+License of the Program or a work on which the Program is based. The
463+work thus licensed is called the contributor's "contributor version".
464+
465+ A contributor's "essential patent claims" are all patent claims
466+owned or controlled by the contributor, whether already acquired or
467+hereafter acquired, that would be infringed by some manner, permitted
468+by this License, of making, using, or selling its contributor version,
469+but do not include claims that would be infringed only as a
470+consequence of further modification of the contributor version. For
471+purposes of this definition, "control" includes the right to grant
472+patent sublicenses in a manner consistent with the requirements of
473+this License.
474+
475+ Each contributor grants you a non-exclusive, worldwide, royalty-free
476+patent license under the contributor's essential patent claims, to
477+make, use, sell, offer for sale, import and otherwise run, modify and
478+propagate the contents of its contributor version.
479+
480+ In the following three paragraphs, a "patent license" is any express
481+agreement or commitment, however denominated, not to enforce a patent
482+(such as an express permission to practice a patent or covenant not to
483+sue for patent infringement). To "grant" such a patent license to a
484+party means to make such an agreement or commitment not to enforce a
485+patent against the party.
486+
487+ If you convey a covered work, knowingly relying on a patent license,
488+and the Corresponding Source of the work is not available for anyone
489+to copy, free of charge and under the terms of this License, through a
490+publicly available network server or other readily accessible means,
491+then you must either (1) cause the Corresponding Source to be so
492+available, or (2) arrange to deprive yourself of the benefit of the
493+patent license for this particular work, or (3) arrange, in a manner
494+consistent with the requirements of this License, to extend the patent
495+license to downstream recipients. "Knowingly relying" means you have
496+actual knowledge that, but for the patent license, your conveying the
497+covered work in a country, or your recipient's use of the covered work
498+in a country, would infringe one or more identifiable patents in that
499+country that you have reason to believe are valid.
500+
501+ If, pursuant to or in connection with a single transaction or
502+arrangement, you convey, or propagate by procuring conveyance of, a
503+covered work, and grant a patent license to some of the parties
504+receiving the covered work authorizing them to use, propagate, modify
505+or convey a specific copy of the covered work, then the patent license
506+you grant is automatically extended to all recipients of the covered
507+work and works based on it.
508+
509+ A patent license is "discriminatory" if it does not include within
510+the scope of its coverage, prohibits the exercise of, or is
511+conditioned on the non-exercise of one or more of the rights that are
512+specifically granted under this License. You may not convey a covered
513+work if you are a party to an arrangement with a third party that is
514+in the business of distributing software, under which you make payment
515+to the third party based on the extent of your activity of conveying
516+the work, and under which the third party grants, to any of the
517+parties who would receive the covered work from you, a discriminatory
518+patent license (a) in connection with copies of the covered work
519+conveyed by you (or copies made from those copies), or (b) primarily
520+for and in connection with specific products or compilations that
521+contain the covered work, unless you entered into that arrangement,
522+or that patent license was granted, prior to 28 March 2007.
523+
524+ Nothing in this License shall be construed as excluding or limiting
525+any implied license or other defenses to infringement that may
526+otherwise be available to you under applicable patent law.
527+
528+ 12. No Surrender of Others' Freedom.
529+
530+ If conditions are imposed on you (whether by court order, agreement or
531+otherwise) that contradict the conditions of this License, they do not
532+excuse you from the conditions of this License. If you cannot convey a
533+covered work so as to satisfy simultaneously your obligations under this
534+License and any other pertinent obligations, then as a consequence you may
535+not convey it at all. For example, if you agree to terms that obligate you
536+to collect a royalty for further conveying from those to whom you convey
537+the Program, the only way you could satisfy both those terms and this
538+License would be to refrain entirely from conveying the Program.
539+
540+ 13. Remote Network Interaction; Use with the GNU General Public License.
541+
542+ Notwithstanding any other provision of this License, if you modify the
543+Program, your modified version must prominently offer all users
544+interacting with it remotely through a computer network (if your version
545+supports such interaction) an opportunity to receive the Corresponding
546+Source of your version by providing access to the Corresponding Source
547+from a network server at no charge, through some standard or customary
548+means of facilitating copying of software. This Corresponding Source
549+shall include the Corresponding Source for any work covered by version 3
550+of the GNU General Public License that is incorporated pursuant to the
551+following paragraph.
552+
553+ Notwithstanding any other provision of this License, you have
554+permission to link or combine any covered work with a work licensed
555+under version 3 of the GNU General Public License into a single
556+combined work, and to convey the resulting work. The terms of this
557+License will continue to apply to the part which is the covered work,
558+but the work with which it is combined will remain governed by version
559+3 of the GNU General Public License.
560+
561+ 14. Revised Versions of this License.
562+
563+ The Free Software Foundation may publish revised and/or new versions of
564+the GNU Affero General Public License from time to time. Such new versions
565+will be similar in spirit to the present version, but may differ in detail to
566+address new problems or concerns.
567+
568+ Each version is given a distinguishing version number. If the
569+Program specifies that a certain numbered version of the GNU Affero General
570+Public License "or any later version" applies to it, you have the
571+option of following the terms and conditions either of that numbered
572+version or of any later version published by the Free Software
573+Foundation. If the Program does not specify a version number of the
574+GNU Affero General Public License, you may choose any version ever published
575+by the Free Software Foundation.
576+
577+ If the Program specifies that a proxy can decide which future
578+versions of the GNU Affero General Public License can be used, that proxy's
579+public statement of acceptance of a version permanently authorizes you
580+to choose that version for the Program.
581+
582+ Later license versions may give you additional or different
583+permissions. However, no additional obligations are imposed on any
584+author or copyright holder as a result of your choosing to follow a
585+later version.
586+
587+ 15. Disclaimer of Warranty.
588+
589+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597+
598+ 16. Limitation of Liability.
599+
600+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608+SUCH DAMAGES.
609+
610+ 17. Interpretation of Sections 15 and 16.
611+
612+ If the disclaimer of warranty and limitation of liability provided
613+above cannot be given local legal effect according to their terms,
614+reviewing courts shall apply local law that most closely approximates
615+an absolute waiver of all civil liability in connection with the
616+Program, unless a warranty or assumption of liability accompanies a
617+copy of the Program in return for a fee.
618+
619+ END OF TERMS AND CONDITIONS
620+
621+ How to Apply These Terms to Your New Programs
622+
623+ If you develop a new program, and you want it to be of the greatest
624+possible use to the public, the best way to achieve this is to make it
625+free software which everyone can redistribute and change under these terms.
626+
627+ To do so, attach the following notices to the program. It is safest
628+to attach them to the start of each source file to most effectively
629+state the exclusion of warranty; and each file should have at least
630+the "copyright" line and a pointer to where the full notice is found.
631+
632+ <one line to give the program's name and a brief idea of what it does.>
633+ Copyright (C) <year> <name of author>
634+
635+ This program is free software: you can redistribute it and/or modify
636+ it under the terms of the GNU Affero General Public License as published
637+ by the Free Software Foundation, either version 3 of the License, or
638+ (at your option) any later version.
639+
640+ This program is distributed in the hope that it will be useful,
641+ but WITHOUT ANY WARRANTY; without even the implied warranty of
642+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643+ GNU Affero General Public License for more details.
644+
645+ You should have received a copy of the GNU Affero General Public License
646+ along with this program. If not, see <https://www.gnu.org/licenses/>.
647+
648+Also add information on how to contact you by electronic and paper mail.
649+
650+ If your software can interact with users remotely through a computer
651+network, you should also make sure that it provides a way for users to
652+get its source. For example, if your program is a web application, its
653+interface could display a "Source" link that leads users to an archive
654+of the code. There are many ways you could offer source, and different
655+solutions will be better for different programs; see section 13 for the
656+specific requirements.
657+
658+ You should also get your employer (if you work as a programmer) or school,
659+if any, to sign a "copyright disclaimer" for the program, if necessary.
660+For more information on this, and how to apply and follow the GNU AGPL, see
661+<https://www.gnu.org/licenses/>.
Aassets/default.css
@@ -0,0 +1,75 @@
1+@import "normalize.css";
2+@import "sakura-vader.css";
3+
4+#jsguard {
5+ display: none;
6+}
7+
8+body {
9+ display: flex;
10+ align-items: center;
11+ flex-direction: column;
12+ min-height: 100vh;
13+
14+ padding: 3rem;
15+ box-sizing: border-box;
16+ max-width: 80rem;
17+}
18+
19+a {
20+ text-wrap: nowrap;
21+}
22+
23+hr {
24+ width: 100%;
25+}
26+
27+h1 {
28+ margin-bottom: 4rem;
29+}
30+
31+form {
32+ display: flex;
33+ flex-direction: column;
34+ gap: 0.5rem;
35+ margin-bottom: 1.5rem;
36+ min-width: 35rem;
37+ width: 100%;
38+
39+ input {
40+ flex-grow: 1;
41+ }
42+
43+ input[type=checkbox] {
44+ flex-grow: 0;
45+ margin-bottom: 0.5rem;
46+ }
47+
48+ div {
49+ display: flex;
50+ flex-direction: row;
51+ align-items: center;
52+ gap: 1rem;
53+ }
54+}
55+
56+select {
57+ overflow: auto;
58+ flex-grow: 1;
59+}
60+
61+img {
62+ margin-bottom: 0;
63+}
64+
65+.help-icon {
66+ width: 2rem;
67+ height: 2rem;
68+ filter: invert();
69+ margin-left: 0.5rem;
70+}
71+
72+input[type=reset] {
73+ max-width: 25rem;
74+ align-self: center;
75+}
Aassets/encrypt.py
@@ -0,0 +1,54 @@
1+from cryptography.hazmat.primitives import hashes
2+from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
3+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
4+from cryptography.hazmat.primitives import padding
5+from cryptography.hazmat.backends import default_backend
6+import os
7+import sys
8+
9+def encrypt(content: bytes, password: str) -> bytes:
10+ """
11+ Encrypts the given content using the provided password.
12+
13+ Args:
14+ content (bytes): The content to be encrypted.
15+ password (str): The password used for encryption.
16+
17+ Returns:
18+ bytes: The encrypted content.
19+ """
20+
21+ # Generate a random initialization vector (IV) and salt
22+ iv = os.urandom(16)
23+ salt = os.urandom(16)
24+
25+ # Derive a key from the password using PBKDF2
26+ kdf = PBKDF2HMAC(
27+ algorithm=hashes.SHA512(),
28+ length=32,
29+ salt=salt,
30+ iterations=100000,
31+ backend=default_backend()
32+ )
33+ key = kdf.derive(password.encode())
34+
35+ # Create a cipher object with AES-256-CBC
36+ cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
37+ encryptor = cipher.encryptor()
38+
39+ # Pad the content to a multiple of the block length
40+ padder = padding.PKCS7(128).padder()
41+ padded_content = padder.update(content) + padder.finalize()
42+
43+ # Encrypt the padded content
44+ encrypted_content = encryptor.update(padded_content) + encryptor.finalize()
45+
46+ # Return the salt, IV, and encrypted content concatenated
47+ return salt + iv + encrypted_content
48+if __name__ == "__main__":
49+ if len(sys.argv) != 4:
50+ print("Usage: encrypt.py <file> <password> <outfile>")
51+ sys.exit(1)
52+ data = encrypt(open(sys.argv[1], "rb").read(), sys.argv[2])
53+ open(sys.argv[3], "wb").write(data)
54+
Aassets/highlight.css
@@ -0,0 +1 @@
1+pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#1d1f21;color:#c5c8c6}.hljs span::selection,.hljs::selection{background:#373b41}.hljs span::-moz-selection,.hljs::-moz-selection{background:#373b41}.hljs-name,.hljs-title{color:#f0c674}.hljs-comment,.hljs-meta,.hljs-meta .hljs-keyword{color:#707880}.hljs-deletion,.hljs-link,.hljs-literal,.hljs-number,.hljs-symbol{color:#c66}.hljs-addition,.hljs-doctag,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-string{color:#b5bd68}.hljs-attribute,.hljs-code,.hljs-selector-id{color:#b294bb}.hljs-bullet,.hljs-keyword,.hljs-selector-tag,.hljs-tag{color:#81a2be}.hljs-subst,.hljs-template-tag,.hljs-template-variable,.hljs-variable{color:#8abeb7}.hljs-built_in,.hljs-quote,.hljs-section,.hljs-selector-class,.hljs-type{color:#de935f}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
Aassets/normalize.css
@@ -0,0 +1,461 @@
1+/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
2+
3+/**
4+ * 1. Change the default font family in all browsers (opinionated).
5+ * 2. Correct the line height in all browsers.
6+ * 3. Prevent adjustments of font size after orientation changes in
7+ * IE on Windows Phone and in iOS.
8+ */
9+
10+/* Document
11+ ========================================================================== */
12+
13+html {
14+ font-family: sans-serif; /* 1 */
15+ line-height: 1.15; /* 2 */
16+ -ms-text-size-adjust: 100%; /* 3 */
17+ -webkit-text-size-adjust: 100%; /* 3 */
18+}
19+
20+/* Sections
21+ ========================================================================== */
22+
23+/**
24+ * Remove the margin in all browsers (opinionated).
25+ */
26+
27+body {
28+ margin: 0;
29+}
30+
31+/**
32+ * Add the correct display in IE 9-.
33+ */
34+
35+article,
36+aside,
37+footer,
38+header,
39+nav,
40+section {
41+ display: block;
42+}
43+
44+/**
45+ * Correct the font size and margin on `h1` elements within `section` and
46+ * `article` contexts in Chrome, Firefox, and Safari.
47+ */
48+
49+h1 {
50+ font-size: 2em;
51+ margin: 0.67em 0;
52+}
53+
54+/* Grouping content
55+ ========================================================================== */
56+
57+/**
58+ * Add the correct display in IE 9-.
59+ * 1. Add the correct display in IE.
60+ */
61+
62+figcaption,
63+figure,
64+main { /* 1 */
65+ display: block;
66+}
67+
68+/**
69+ * Add the correct margin in IE 8.
70+ */
71+
72+figure {
73+ margin: 1em 40px;
74+}
75+
76+/**
77+ * 1. Add the correct box sizing in Firefox.
78+ * 2. Show the overflow in Edge and IE.
79+ */
80+
81+hr {
82+ box-sizing: content-box; /* 1 */
83+ height: 0; /* 1 */
84+ overflow: visible; /* 2 */
85+}
86+
87+/**
88+ * 1. Correct the inheritance and scaling of font size in all browsers.
89+ * 2. Correct the odd `em` font sizing in all browsers.
90+ */
91+
92+pre {
93+ font-family: monospace, monospace; /* 1 */
94+ font-size: 1em; /* 2 */
95+}
96+
97+/* Text-level semantics
98+ ========================================================================== */
99+
100+/**
101+ * 1. Remove the gray background on active links in IE 10.
102+ * 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
103+ */
104+
105+a {
106+ background-color: transparent; /* 1 */
107+ -webkit-text-decoration-skip: objects; /* 2 */
108+}
109+
110+/**
111+ * Remove the outline on focused links when they are also active or hovered
112+ * in all browsers (opinionated).
113+ */
114+
115+a:active,
116+a:hover {
117+ outline-width: 0;
118+}
119+
120+/**
121+ * 1. Remove the bottom border in Firefox 39-.
122+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
123+ */
124+
125+abbr[title] {
126+ border-bottom: none; /* 1 */
127+ text-decoration: underline; /* 2 */
128+ text-decoration: underline dotted; /* 2 */
129+}
130+
131+/**
132+ * Prevent the duplicate application of `bolder` by the next rule in Safari 6.
133+ */
134+
135+b,
136+strong {
137+ font-weight: inherit;
138+}
139+
140+/**
141+ * Add the correct font weight in Chrome, Edge, and Safari.
142+ */
143+
144+b,
145+strong {
146+ font-weight: bolder;
147+}
148+
149+/**
150+ * 1. Correct the inheritance and scaling of font size in all browsers.
151+ * 2. Correct the odd `em` font sizing in all browsers.
152+ */
153+
154+code,
155+kbd,
156+samp {
157+ font-family: monospace, monospace; /* 1 */
158+ font-size: 1em; /* 2 */
159+}
160+
161+/**
162+ * Add the correct font style in Android 4.3-.
163+ */
164+
165+dfn {
166+ font-style: italic;
167+}
168+
169+/**
170+ * Add the correct background and color in IE 9-.
171+ */
172+
173+mark {
174+ background-color: #ff0;
175+ color: #000;
176+}
177+
178+/**
179+ * Add the correct font size in all browsers.
180+ */
181+
182+small {
183+ font-size: 80%;
184+}
185+
186+/**
187+ * Prevent `sub` and `sup` elements from affecting the line height in
188+ * all browsers.
189+ */
190+
191+sub,
192+sup {
193+ font-size: 75%;
194+ line-height: 0;
195+ position: relative;
196+ vertical-align: baseline;
197+}
198+
199+sub {
200+ bottom: -0.25em;
201+}
202+
203+sup {
204+ top: -0.5em;
205+}
206+
207+/* Embedded content
208+ ========================================================================== */
209+
210+/**
211+ * Add the correct display in IE 9-.
212+ */
213+
214+audio,
215+video {
216+ display: inline-block;
217+}
218+
219+/**
220+ * Add the correct display in iOS 4-7.
221+ */
222+
223+audio:not([controls]) {
224+ display: none;
225+ height: 0;
226+}
227+
228+/**
229+ * Remove the border on images inside links in IE 10-.
230+ */
231+
232+img {
233+ border-style: none;
234+}
235+
236+/**
237+ * Hide the overflow in IE.
238+ */
239+
240+svg:not(:root) {
241+ overflow: hidden;
242+}
243+
244+/* Forms
245+ ========================================================================== */
246+
247+/**
248+ * 1. Change the font styles in all browsers (opinionated).
249+ * 2. Remove the margin in Firefox and Safari.
250+ */
251+
252+button,
253+input,
254+optgroup,
255+select,
256+textarea {
257+ font-family: sans-serif; /* 1 */
258+ font-size: 100%; /* 1 */
259+ line-height: 1.15; /* 1 */
260+ margin: 0; /* 2 */
261+}
262+
263+/**
264+ * Show the overflow in IE.
265+ * 1. Show the overflow in Edge.
266+ */
267+
268+button,
269+input { /* 1 */
270+ overflow: visible;
271+}
272+
273+/**
274+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
275+ * 1. Remove the inheritance of text transform in Firefox.
276+ */
277+
278+button,
279+select { /* 1 */
280+ text-transform: none;
281+}
282+
283+/**
284+ * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
285+ * controls in Android 4.
286+ * 2. Correct the inability to style clickable types in iOS and Safari.
287+ */
288+
289+button,
290+html [type="button"], /* 1 */
291+[type="reset"],
292+[type="submit"] {
293+ -webkit-appearance: button; /* 2 */
294+}
295+
296+/**
297+ * Remove the inner border and padding in Firefox.
298+ */
299+
300+button::-moz-focus-inner,
301+[type="button"]::-moz-focus-inner,
302+[type="reset"]::-moz-focus-inner,
303+[type="submit"]::-moz-focus-inner {
304+ border-style: none;
305+ padding: 0;
306+}
307+
308+/**
309+ * Restore the focus styles unset by the previous rule.
310+ */
311+
312+button:-moz-focusring,
313+[type="button"]:-moz-focusring,
314+[type="reset"]:-moz-focusring,
315+[type="submit"]:-moz-focusring {
316+ outline: 1px dotted ButtonText;
317+}
318+
319+/**
320+ * Change the border, margin, and padding in all browsers (opinionated).
321+ */
322+
323+fieldset {
324+ border: 1px solid #c0c0c0;
325+ margin: 0 2px;
326+ padding: 0.35em 0.625em 0.75em;
327+}
328+
329+/**
330+ * 1. Correct the text wrapping in Edge and IE.
331+ * 2. Correct the color inheritance from `fieldset` elements in IE.
332+ * 3. Remove the padding so developers are not caught out when they zero out
333+ * `fieldset` elements in all browsers.
334+ */
335+
336+legend {
337+ box-sizing: border-box; /* 1 */
338+ color: inherit; /* 2 */
339+ display: table; /* 1 */
340+ max-width: 100%; /* 1 */
341+ padding: 0; /* 3 */
342+ white-space: normal; /* 1 */
343+}
344+
345+/**
346+ * 1. Add the correct display in IE 9-.
347+ * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
348+ */
349+
350+progress {
351+ display: inline-block; /* 1 */
352+ vertical-align: baseline; /* 2 */
353+}
354+
355+/**
356+ * Remove the default vertical scrollbar in IE.
357+ */
358+
359+textarea {
360+ overflow: auto;
361+}
362+
363+/**
364+ * 1. Add the correct box sizing in IE 10-.
365+ * 2. Remove the padding in IE 10-.
366+ */
367+
368+[type="checkbox"],
369+[type="radio"] {
370+ box-sizing: border-box; /* 1 */
371+ padding: 0; /* 2 */
372+}
373+
374+/**
375+ * Correct the cursor style of increment and decrement buttons in Chrome.
376+ */
377+
378+[type="number"]::-webkit-inner-spin-button,
379+[type="number"]::-webkit-outer-spin-button {
380+ height: auto;
381+}
382+
383+/**
384+ * 1. Correct the odd appearance in Chrome and Safari.
385+ * 2. Correct the outline style in Safari.
386+ */
387+
388+[type="search"] {
389+ -webkit-appearance: textfield; /* 1 */
390+ outline-offset: -2px; /* 2 */
391+}
392+
393+/**
394+ * Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
395+ */
396+
397+[type="search"]::-webkit-search-cancel-button,
398+[type="search"]::-webkit-search-decoration {
399+ -webkit-appearance: none;
400+}
401+
402+/**
403+ * 1. Correct the inability to style clickable types in iOS and Safari.
404+ * 2. Change font properties to `inherit` in Safari.
405+ */
406+
407+::-webkit-file-upload-button {
408+ -webkit-appearance: button; /* 1 */
409+ font: inherit; /* 2 */
410+}
411+
412+/* Interactive
413+ ========================================================================== */
414+
415+/*
416+ * Add the correct display in IE 9-.
417+ * 1. Add the correct display in Edge, IE, and Firefox.
418+ */
419+
420+details, /* 1 */
421+menu {
422+ display: block;
423+}
424+
425+/*
426+ * Add the correct display in all browsers.
427+ */
428+
429+summary {
430+ display: list-item;
431+}
432+
433+/* Scripting
434+ ========================================================================== */
435+
436+/**
437+ * Add the correct display in IE 9-.
438+ */
439+
440+canvas {
441+ display: inline-block;
442+}
443+
444+/**
445+ * Add the correct display in IE.
446+ */
447+
448+template {
449+ display: none;
450+}
451+
452+/* Hidden
453+ ========================================================================== */
454+
455+/**
456+ * Add the correct display in IE 10-.
457+ */
458+
459+[hidden] {
460+ display: none;
461+}
Aassets/sakura-vader.css
@@ -0,0 +1,227 @@
1+/* $color-text: #dedce5; */
2+/* Sakura.css v1.5.0
3+ * ================
4+ * Minimal css theme.
5+ * Project: https://github.com/oxalorg/sakura/
6+ */
7+/* Body */
8+html {
9+ font-size: 62.5%;
10+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
11+}
12+
13+body {
14+ font-size: 1.8rem;
15+ line-height: 1.618;
16+ max-width: 38em;
17+ margin: auto;
18+ color: #d9d8dc;
19+ background-color: #120c0e;
20+ padding: 13px;
21+}
22+
23+@media (max-width: 684px) {
24+ body {
25+ font-size: 1.53rem;
26+ }
27+}
28+@media (max-width: 382px) {
29+ body {
30+ font-size: 1.35rem;
31+ }
32+}
33+h1, h2, h3, h4, h5, h6 {
34+ line-height: 1.1;
35+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
36+ font-weight: 700;
37+ margin-top: 3rem;
38+ margin-bottom: 1.5rem;
39+ overflow-wrap: break-word;
40+ word-wrap: break-word;
41+ -ms-word-break: break-all;
42+ word-break: break-word;
43+}
44+
45+h1 {
46+ font-size: 2.35em;
47+}
48+
49+h2 {
50+ font-size: 2em;
51+}
52+
53+h3 {
54+ font-size: 1.75em;
55+}
56+
57+h4 {
58+ font-size: 1.5em;
59+}
60+
61+h5 {
62+ font-size: 1.25em;
63+}
64+
65+h6 {
66+ font-size: 1em;
67+}
68+
69+p {
70+ margin-top: 0px;
71+ margin-bottom: 2.5rem;
72+}
73+
74+small, sub, sup {
75+ font-size: 75%;
76+}
77+
78+hr {
79+ border-color: #eb99a1;
80+}
81+
82+a {
83+ text-decoration: none;
84+ color: #eb99a1;
85+}
86+a:visited {
87+ color: #e26f7a;
88+}
89+a:hover {
90+ color: #DA4453;
91+ border-bottom: 2px solid #d9d8dc;
92+}
93+
94+ul {
95+ padding-left: 1.4em;
96+ margin-top: 0px;
97+ margin-bottom: 2.5rem;
98+}
99+
100+li {
101+ margin-bottom: 0.4em;
102+}
103+
104+blockquote {
105+ margin-left: 0px;
106+ margin-right: 0px;
107+ padding-left: 1em;
108+ padding-top: 0.8em;
109+ padding-bottom: 0.8em;
110+ padding-right: 0.8em;
111+ border-left: 5px solid #eb99a1;
112+ margin-bottom: 2.5rem;
113+ background-color: #40363a;
114+}
115+
116+blockquote p {
117+ margin-bottom: 0;
118+}
119+
120+img, video {
121+ height: auto;
122+ max-width: 100%;
123+ margin-top: 0px;
124+ margin-bottom: 2.5rem;
125+}
126+
127+/* Pre and Code */
128+pre {
129+ background-color: #40363a;
130+ display: block;
131+ padding: 1em;
132+ overflow-x: auto;
133+ margin-top: 0px;
134+ margin-bottom: 2.5rem;
135+ font-size: 0.9em;
136+}
137+
138+code, kbd, samp {
139+ font-size: 0.9em;
140+ padding: 0 0.5em;
141+ background-color: #40363a;
142+ white-space: pre-wrap;
143+}
144+
145+pre > code {
146+ padding: 0;
147+ background-color: transparent;
148+ white-space: pre;
149+ font-size: 1em;
150+}
151+
152+/* Tables */
153+table {
154+ text-align: justify;
155+ width: 100%;
156+ border-collapse: collapse;
157+ margin-bottom: 2rem;
158+}
159+
160+td, th {
161+ padding: 0.5em;
162+ border-bottom: 1px solid #40363a;
163+}
164+
165+/* Buttons, forms and input */
166+input, textarea {
167+ border: 1px solid #d9d8dc;
168+}
169+input:focus, textarea:focus {
170+ border: 1px solid #eb99a1;
171+}
172+
173+textarea {
174+ width: 100%;
175+}
176+
177+.button, button, input[type=submit], input[type=reset], input[type=button], input[type=file]::file-selector-button {
178+ display: inline-block;
179+ padding: 5px 10px;
180+ text-align: center;
181+ text-decoration: none;
182+ white-space: nowrap;
183+ background-color: #eb99a1;
184+ color: #120c0e;
185+ border-radius: 1px;
186+ border: 1px solid #eb99a1;
187+ cursor: pointer;
188+ box-sizing: border-box;
189+}
190+.button[disabled], button[disabled], input[type=submit][disabled], input[type=reset][disabled], input[type=button][disabled], input[type=file]::file-selector-button[disabled] {
191+ cursor: default;
192+ opacity: 0.5;
193+}
194+.button:hover, button:hover, input[type=submit]:hover, input[type=reset]:hover, input[type=button]:hover, input[type=file]::file-selector-button:hover {
195+ background-color: #DA4453;
196+ color: #120c0e;
197+ outline: 0;
198+}
199+.button:focus-visible, button:focus-visible, input[type=submit]:focus-visible, input[type=reset]:focus-visible, input[type=button]:focus-visible, input[type=file]::file-selector-button:focus-visible {
200+ outline-style: solid;
201+ outline-width: 2px;
202+}
203+
204+textarea, select, input {
205+ color: #d9d8dc;
206+ padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
207+ margin-bottom: 10px;
208+ background-color: #40363a;
209+ border: 1px solid #40363a;
210+ border-radius: 4px;
211+ box-shadow: none;
212+ box-sizing: border-box;
213+}
214+textarea:focus, select:focus, input:focus {
215+ border: 1px solid #eb99a1;
216+ outline: 0;
217+}
218+
219+input[type=checkbox]:focus {
220+ outline: 1px dotted #eb99a1;
221+}
222+
223+label, legend, fieldset {
224+ display: block;
225+ margin-bottom: 0.5rem;
226+ font-weight: 600;
227+}
Aassets/show.css
@@ -0,0 +1,94 @@
1+@import "normalize.css";
2+@import "sakura-vader.css";
3+@import "highlight.css";
4+
5+hr {
6+ width: 100%;
7+}
8+
9+body {
10+ box-sizing: border-box;
11+ width: 100%;
12+ height: 100vh;
13+ max-width: 100%;
14+ max-height: 100vh;
15+
16+ display: flex;
17+ flex-direction: row;
18+
19+ #content {
20+ box-sizing: border-box;
21+ flex-grow: 1;
22+ height: 100%;
23+ overflow: auto;
24+ border: 1px solid #40363a;
25+ border-right: unset;
26+ display: flex;
27+ flex-direction: column;
28+
29+ #filename {
30+ padding-left: 1rem;
31+ }
32+
33+ pre {
34+ box-sizing: border-box;
35+ margin: 0;
36+ flex-grow: 1;
37+ overflow: auto;
38+ max-width: 100%;
39+ max-height: 100%;
40+ }
41+
42+ #mediabox {
43+ flex-grow: 1;
44+ display: flex;
45+ justify-content: center;
46+ }
47+ }
48+
49+ #sidebar {
50+ box-sizing: border-box;
51+ box-sizing: border-box;
52+ flex-shrink: 0;
53+ padding: 1rem;
54+ width: 30rem;
55+ height: 100%;
56+ border: 1px solid #40363a;
57+ display: flex;
58+ flex-direction: column;
59+
60+ h3 {
61+ margin-top: 0;
62+ text-align: center;
63+ }
64+
65+ form {
66+ align-self: center;
67+ }
68+
69+ }
70+}
71+
72+audio {
73+ max-width: 40rem;
74+ min-width: 0;
75+ flex-grow: 1;
76+ align-self: center;
77+}
78+
79+img,
80+video {
81+ object-fit: scale-down;
82+}
83+
84+dialog {
85+ background-color: #120c0e;
86+ color: #d9d8dc;
87+ border-color: #eb99a1;
88+
89+ form {
90+ display: flex;
91+ flex-direction: column;
92+ align-items: center;
93+ }
94+}
Abun.lock
@@ -0,0 +1,85 @@
1+{
2+ "lockfileVersion": 1,
3+ "workspaces": {
4+ "": {
5+ "name": "zigbin",
6+ "dependencies": {
7+ "@chneau/elysia-compression": "^1.0.11",
8+ "@elysiajs/static": "^1.2.0",
9+ "elysia": "latest",
10+ "file-type": "^20.0.1",
11+ "highlight.js": "^11.11.1",
12+ "pkcs7-padding": "^0.1.1",
13+ "utf-8-validate": "^6.0.5",
14+ },
15+ "devDependencies": {
16+ "@types/pkcs7-padding": "^0.1.3",
17+ "@types/utf-8-validate": "^5.0.2",
18+ "bun-types": "latest",
19+ },
20+ },
21+ },
22+ "packages": {
23+ "@chneau/elysia-compression": ["@chneau/elysia-compression@1.0.11", "", { "dependencies": { "elysia": "^1.1.9" } }, "sha512-J4wfz5Qs68p/DNvkg5DXxo8sxJKSibzaUBP4W56uvqgs9NtHG0LC6tnyv7sAPEjNKzh21l6kinMoXbtVDp/KSQ=="],
24+
25+ "@elysiajs/static": ["@elysiajs/static@1.2.0", "", { "dependencies": { "node-cache": "^5.1.2" }, "peerDependencies": { "elysia": ">= 1.2.0" } }, "sha512-oLpAi8c+maPpA0XhhK3BELaIjIG+nXg/K9p8cFfW4q5ayRD59a3MOMOOGgpiXZkHJzLPWcouhhyyLAYtaANW4g=="],
26+
27+ "@sinclair/typebox": ["@sinclair/typebox@0.34.15", "", {}, "sha512-xeIzl3h1Znn9w/LTITqpiwag0gXjA+ldi2ZkXIBxGEppGCW211Tza+eL6D4pKqs10bj5z2umBWk5WL6spQ2OCQ=="],
28+
29+ "@tokenizer/inflate": ["@tokenizer/inflate@0.2.6", "", { "dependencies": { "debug": "^4.3.7", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-SdR/i05U7Xhnsq36iyIq/ZiGGw4PKzw4ww3bOq80Pjj4wyXpqyTcgrgdDdGlcatnlvzNJx8CQw3hp6QZvkUwhA=="],
30+
31+ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
32+
33+ "@types/node": ["@types/node@22.13.0", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ClIbNe36lawluuvq3+YYhnIN2CELi+6q8NpnM7PYp4hBn/TatfboPgVSm2rwKRfnV2M+Ty9GWDFI64KEe+kysA=="],
34+
35+ "@types/pkcs7-padding": ["@types/pkcs7-padding@0.1.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-rh4Tsp96Rj4KRBkRMHTZNsyjMBYT7lmHPr9Q6iZiRP1rrXxFzZKj3CpWP15a9ZE5O6BO3KqL0WsHzc+FGYYF1g=="],
36+
37+ "@types/utf-8-validate": ["@types/utf-8-validate@5.0.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-ta7cOkEiNr0RGKARljNBaI7E1GBIr3VwS9RrSoQRmbdv1RVq7Q6VhjSGmQHYNt3nHn051qZBKKrpnw7cnEMDuQ=="],
38+
39+ "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
40+
41+ "bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="],
42+
43+ "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="],
44+
45+ "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
46+
47+ "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
48+
49+ "elysia": ["elysia@1.2.10", "", { "dependencies": { "@sinclair/typebox": "^0.34.13", "cookie": "^1.0.2", "memoirist": "^0.2.0", "openapi-types": "^12.1.3" }, "peerDependencies": { "typescript": ">= 5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-QcNl2FjhHFRpKaqy1NoMpyCjJ7OcKBnHwLUkqGu09QwIV84PFb82ILvYJG4GS1RbGv76OA50luaqBLrM3SLZ2w=="],
50+
51+ "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
52+
53+ "file-type": ["file-type@20.0.1", "", { "dependencies": { "@tokenizer/inflate": "^0.2.6", "strtok3": "^10.2.0", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-R80W+NZ+s1M8PsC8L7xn+J0N72vEyxstMH+4vwIyt5B7ojfG80h/nKu2Qw/NYJTIYyGUeaPNLnzfJd78SWnG3A=="],
54+
55+ "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="],
56+
57+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
58+
59+ "memoirist": ["memoirist@0.2.0", "", {}, "sha512-DA1V11OWsKmYjgYHfT1luus0FtTjUbILfI9s5M+ckK29tBLON6GDhH5GwxDz7E1ou4Bdzm9vhbeCaRAWxwG+0g=="],
60+
61+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
62+
63+ "node-cache": ["node-cache@5.1.2", "", { "dependencies": { "clone": "2.x" } }, "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg=="],
64+
65+ "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
66+
67+ "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
68+
69+ "peek-readable": ["peek-readable@6.1.0", "", {}, "sha512-1H5ECS+rPH35Foh4JD/XohQKWsx6Jzn37ESOVTFuCSoI8wMB9r2e2aDSLgHSiyucVrPfoc0DRiipBEP1gr9wLw=="],
70+
71+ "pkcs7-padding": ["pkcs7-padding@0.1.1", "", {}, "sha512-tM/sUOL5FdH7x6gSLHTNDFi1bAut/EXGFp/Ih8uRkVd2IdXwb5QWnxBgQXn6buadj1pLi3CYKCnrxb+XoTz+Ww=="],
72+
73+ "strtok3": ["strtok3@10.2.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^6.1.0" } }, "sha512-S884oIGzokq3LkL/6jXw5c5oJXRiGt4jB42cuWdaooJdxFMSn99snaShh3cQmJx3jALV2eoyW3rV/TJxwOaBPA=="],
74+
75+ "token-types": ["token-types@6.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="],
76+
77+ "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
78+
79+ "uint8array-extras": ["uint8array-extras@1.4.0", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="],
80+
81+ "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
82+
83+ "utf-8-validate": ["utf-8-validate@6.0.5", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-EYZR+OpIXp9Y1eG1iueg8KRsY8TuT8VNgnanZ0uA3STqhHQTLwbl+WX76/9X5OY12yQubymBpaBSmMPkSTQcKA=="],
84+ }
85+}
Mpackage.json
@@ -1,14 +1,24 @@
11 {
22 "name": "zigbin",
3- "version": "1.0.50",
3+ "version": "1.0.0",
44 "scripts": {
5- "test": "echo \"Error: no test specified\" && exit 1",
6- "dev": "bun run --watch src/index.ts"
5+ "build": "bun build src/client*.ts --outdir assets/dist",
6+ "dev": "bun run build && bun run --watch src/index.ts",
7+ "prod": "bun run build && bun run src/index.ts"
78 },
89 "dependencies": {
9- "elysia": "latest"
10+ "@chneau/elysia-compression": "^1.0.11",
11+ "@elysiajs/static": "^1.2.0",
12+ "@elysiajs/html": "^1.2.0",
13+ "elysia": "^1.2.0",
14+ "file-type": "^20.0.1",
15+ "highlight.js": "^11.11.1",
16+ "pkcs7-padding": "^0.1.1",
17+ "utf-8-validate": "^6.0.5"
1018 },
1119 "devDependencies": {
20+ "@types/pkcs7-padding": "^0.1.3",
21+ "@types/utf-8-validate": "^5.0.2",
1222 "bun-types": "latest"
1323 },
1424 "module": "src/index.js"
Asrc/client-index.ts
@@ -0,0 +1,76 @@
1+async function encrypt(content: Uint8Array, password: string): Promise<ArrayBuffer> {
2+ const iv = crypto.getRandomValues(new Uint8Array(16))
3+ const salt = crypto.getRandomValues(new Uint8Array(16))
4+ const keyMaterial = await crypto.subtle.importKey(
5+ "raw",
6+ new TextEncoder().encode(password),
7+ { name: "PBKDF2" },
8+ false,
9+ ["deriveBits", "deriveKey"]
10+ )
11+ const key = await crypto.subtle.deriveKey(
12+ {
13+ name: "PBKDF2",
14+ salt: salt,
15+ iterations: 100000,
16+ hash: "SHA-512"
17+ },
18+ keyMaterial,
19+ { name: "AES-CBC", length: 256 },
20+ false,
21+ ["encrypt"]
22+ )
23+ const encryptedContent = await crypto.subtle.encrypt(
24+ { name: "AES-CBC", iv: iv },
25+ key,
26+ content
27+ )
28+ return new Uint8Array([...salt, ...iv, ...new Uint8Array(encryptedContent)]).buffer
29+}
30+
31+async function uploadFile() {
32+ const passwordInput = document.getElementById("password") as HTMLInputElement | null
33+ const fileInput = document.getElementById("file") as HTMLInputElement | null
34+ const filenameInput = document.getElementById("filename") as HTMLInputElement | null
35+ const encryptedInput = document.getElementById("encrypted") as HTMLInputElement | null
36+ const form = document.getElementById("uploadForm") as HTMLFormElement | null
37+
38+ if (!passwordInput || !fileInput || !filenameInput || !encryptedInput || !form) {
39+ console.error("Missing required elements.")
40+ console.error(passwordInput, fileInput, filenameInput, encryptedInput, form)
41+ return
42+ }
43+
44+ const password = passwordInput.value;
45+ if (!password) {
46+ form.submit()
47+ return
48+ }
49+
50+ const file = fileInput.files?.[0];
51+ if (!file) {
52+ return
53+ }
54+
55+ const content = await file.bytes()
56+ const encryptedContent = await encrypt(content, password)
57+
58+ const myFile = new File([encryptedContent], file.name)
59+ const dataTransfer = new DataTransfer()
60+ dataTransfer.items.add(myFile)
61+
62+ fileInput.files = dataTransfer.files
63+ encryptedInput.checked = true
64+ console.log("encryption done")
65+ form.submit()
66+}
67+
68+(document.getElementById("uploadForm") as HTMLFormElement | null)?.reset()
69+
70+const jsguards = document.getElementsByClassName("jsguard")
71+for (let i = 0; i < jsguards.length; i++) {
72+ jsguards[i].remove()
73+}
74+
75+window.uploadFile = uploadFile
76+
Asrc/client-show.ts
@@ -0,0 +1,139 @@
1+import { fileTypeFromBuffer } from 'file-type'
2+import hljs from 'highlight.js'
3+import { humanFileSize } from './shared'
4+
5+async function decrypt(encryptedContent: ArrayBuffer, password: string): Promise<ArrayBuffer> {
6+ const encryptedArray = new Uint8Array(encryptedContent)
7+ const salt = encryptedArray.slice(0, 16)
8+ const iv = encryptedArray.slice(16, 32)
9+ const keyMaterial = await crypto.subtle.importKey(
10+ "raw",
11+ new TextEncoder().encode(password),
12+ { name: "PBKDF2" },
13+ false,
14+ ["deriveBits", "deriveKey"]
15+ )
16+ const key = await crypto.subtle.deriveKey(
17+ {
18+ name: "PBKDF2",
19+ salt: salt,
20+ iterations: 100000,
21+ hash: "SHA-512"
22+ },
23+ keyMaterial,
24+ { name: "AES-CBC", length: 256 },
25+ false,
26+ ["decrypt"]
27+ )
28+ const decryptedContent = await crypto.subtle.decrypt(
29+ { name: "AES-CBC", iv: iv },
30+ key,
31+ encryptedArray.slice(32)
32+ )
33+ return decryptedContent
34+}
35+
36+function isValidUTF8(buf: ArrayBuffer) {
37+ try {
38+ new TextDecoder("utf-8", { fatal: true }).decode(buf)
39+ return true
40+ } catch (e) {
41+ return false
42+ }
43+}
44+
45+async function showContent(content: ArrayBuffer, filetype: string) {
46+ const filesize = document.getElementById("filesize") as HTMLDivElement | null
47+ if (filesize) {
48+ filesize.textContent = `size: ${humanFileSize(content.byteLength)}`
49+ }
50+
51+ const mediaBox = document.getElementById("mediabox") as HTMLDivElement | null
52+ if (!mediaBox) {
53+ console.error("Missing mediaBox element.")
54+ return
55+ }
56+ let preview: HTMLElement = document.createElement("div")
57+ preview.textContent = "This file can't be previewed"
58+ if (isValidUTF8(content) && filetype !== "blob") {
59+ preview = document.createElement("pre")
60+ if (filetype === "none") {
61+ preview.textContent = new TextDecoder().decode(content)
62+ } else {
63+ preview.innerHTML = hljs.highlight(new TextDecoder("utf-8").decode(content), { language: filetype }).value
64+ }
65+ } else {
66+ const filetype = await fileTypeFromBuffer(content)
67+ if (filetype) {
68+ const blob = new Blob([content], { type: filetype.mime })
69+ const url = URL.createObjectURL(blob)
70+ if (filetype.mime.startsWith("audio/")) {
71+ preview = document.createElement("audio")
72+ preview.setAttribute("controls", "")
73+ preview.setAttribute("src", url)
74+ } else if (filetype.mime.startsWith("video/")) {
75+ preview = document.createElement("video")
76+ preview.setAttribute("controls", "")
77+ preview.setAttribute("src", url)
78+ } else if (filetype.mime.startsWith("image/")) {
79+ preview = document.createElement("img")
80+ preview.setAttribute("src", url)
81+ }
82+ }
83+ }
84+ mediaBox.innerHTML = ""
85+ mediaBox.appendChild(preview)
86+}
87+
88+let content: ArrayBuffer
89+let filetype: string
90+let filename: string
91+
92+async function onPasswordSubmit() {
93+ const passwordInput = document.getElementById("password") as HTMLInputElement | null
94+ const passwordLabel = document.getElementById("password-label") as HTMLLabelElement | null
95+ if (!passwordInput || !passwordLabel) {
96+ console.error("Missing passwordInput or passwordLabel element.")
97+ return
98+ }
99+ try {
100+ content = await decrypt(content, passwordInput.value)
101+ } catch (e) {
102+ passwordLabel.textContent = "Incorrect password"
103+ return
104+ }
105+ const dialog = document.getElementById("password-dialog") as HTMLDialogElement | null
106+ dialog?.close()
107+ const downloadForm = document.getElementById("download-form") as HTMLFormElement | null
108+ downloadForm?.setAttribute("action", "")
109+ downloadForm?.addEventListener("submit", (e) => {
110+ e.preventDefault()
111+ const blob = new Blob([content])
112+ const url = URL.createObjectURL(blob)
113+ const link = document.createElement("a")
114+ link.download = filename
115+ link.href = url
116+ link.click();
117+ })
118+ showContent(content, filetype)
119+}
120+
121+const uuid = window.location.pathname.split("/").filter((x) => x !== "").reverse()[0]
122+
123+fetch("/raw/" + uuid + "?ignore_password=true").then(async (response) => {
124+ content = await response.arrayBuffer()
125+ const mediaBox = document.getElementById("mediabox")
126+ if(mediaBox) {
127+ mediaBox.innerHTML = ""
128+ }
129+ filetype = response.headers.get("filetype") || "none"
130+ filename = response.headers.get("filename") || ""
131+ if (response.headers.get("encrypted") === "true") {
132+ const dialog = document.getElementById("password-dialog") as HTMLDialogElement | null
133+ dialog?.showModal()
134+ } else {
135+ showContent(content, filetype)
136+ }
137+});
138+
139+(window as any).onPasswordSubmit = onPasswordSubmit
Asrc/components.tsx
@@ -0,0 +1,201 @@
1+import { Html } from '@elysiajs/html'
2+import { PropsWithChildren } from '@kitajs/html'
3+import { escapeHTML } from 'bun'
4+import { fileTypeFromBuffer } from 'file-type'
5+import hljs from 'highlight.js'
6+import isValidUTF8 from 'utf-8-validate'
7+import { humanFileSize } from './shared'
8+
9+const filetypes = ["none", "blob"].concat(hljs.listLanguages().sort())
10+
11+function Template(props: PropsWithChildren<{ css: string }>) {
12+ return (
13+ <>
14+ {'<!doctype html>'}
15+ <html lang="en">
16+ <head>
17+ <title>⚡Zigbin⚡</title>
18+ <meta charset="UTF-8" />
19+ <meta name="viewport" content="width=device-width, initial-scale=1" />
20+ <link rel="stylesheet" href={props.css} />
21+ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📂</text></svg>" />
22+ </head>
23+ <body>{props.children}</body>
24+ </html>
25+ </>
26+ )
27+}
28+
29+export function NotFound() {
30+ return (
31+ <Template css="/default.css">
32+ <h1>404</h1>
33+ <p>File not found</p>
34+ </Template>
35+ )
36+}
37+
38+export function Index(hostname: string, withJs: boolean) {
39+ let body = (
40+ <>
41+ {withJs ?
42+ <div style="text-align: center">JS version with client-side encryption, for the version without js an with server-side encryption, go <a href="/">here</a>.</div>
43+ : <div style="text-align: center">JS-less version with server-side encryption, for the version with client-side encryption, go to <a href="/js">/js</a>.</div>
44+ }
45+ <hr />
46+ <form action={withJs ? "/upload?withJs=true" : "/upload"} id="uploadForm" method="post" enctype="multipart/form-data" onsubmit={withJs ? "uploadFile(); return false" : undefined}>
47+ <input type="reset" value="Reset form" />
48+ <input required={true} type="file" id="file" name="file" />
49+ <label for="filename">File name override:</label>
50+ <small>Optional. If empty, will use the uploaded file name.</small>
51+ <input type="text" id="filename" name="filename" placeholder="File name" />
52+ <label for="filetype">File type:</label>
53+ <small>Used for syntax highlighting, if the file is only containing valid UTF-8 characters. Otherwise it will always be stored as a binary blob. Can select <code>blob</code> to force this detection.</small>
54+ <select size="10" required={true} id="filetype" name="filetype">
55+ {
56+ filetypes.map((filetype, i) => (
57+ <option selected={i === 0} value={filetype}>{filetype}</option>
58+ ))
59+ }
60+ </select>
61+ <label for="password">Password:</label>
62+ <small>Optional. If set, file will be encrypted and requires the given password to open.</small>
63+ <input type="password" id="password" name="password" placeholder="Password" />
64+ <div>
65+ <label for="encrypted">Already encrypted:</label>
66+ <input type="checkbox" id="encrypted" name="encrypted" />
67+ </div>
68+ <small>If set, file is already encrypted with an algorithm like in the python file available below. Password is ignored in this case.</small>
69+ <input type="submit" value="Upload File" />
70+ <hr />
71+ <h3>curl guide</h3>
72+ You can use curl to upload and download files<br />
73+ Upload:
74+ <pre>curl {hostname}upload \{"\n"}
75+ -F file=@/path/to/file \{"\n"}
76+ -F filetype="plaintext" \{"\n"}
77+ # optional{"\n"}
78+ -F filename="file name" \{"\n"}
79+ # optional{"\n"}
80+ -F password="mypassword" \{"\n"}
81+ # optional{"\n"}
82+ -F encrypted="on"</pre>
83+ <p>
84+ If you upload an already encrypted file, you should set the <code>encrypted="on"</code> parameter and encrypt it in a compatible way, so the frontend/backend can also decrypt it.
85+ You can use this python script to encrypt a file locally: <a href="/encrypt.py">encrypt.py</a>
86+ </p>
87+ <br />
88+ Download:
89+ <pre>curl {hostname}raw/$uuid?ignore_password=true/false \{"\n"}
90+ # optional, if encrypted with password{"\n"}
91+ --cookie "password=mypassword"</pre>
92+ <p>
93+ If ignore_password is set to true, then encrypted files can be downloaded directly (in encrypted form) without supplying the password.
94+ </p>
95+ </form>
96+ </>
97+ )
98+ if (withJs) {
99+ body = (
100+ <>
101+ <div class="jsguard">JS seems to be disabled, enable it and refesh the page, or go back to<a href="/">here</a> for the JS-less version.</div>
102+ <div class="jsguard">
103+ {body}
104+ </div>
105+ <script src="/dist/client-index.js" />
106+ </>
107+ )
108+ }
109+ return (
110+ <Template css="/default.css">
111+ <h1>⚡Zigbin⚡</h1>
112+ {body}
113+ </Template>
114+ )
115+}
116+
117+
118+export function DecryptFile(filename: string, uuid: string) {
119+ return (
120+ <Template css="/default.css">
121+ <h1>Encrypted file: {filename}</h1>
122+ <form action={`/set-cookie/${uuid}`} method="post">
123+ <label for="password">Enter a Password to decrypt the file</label>
124+ <input type="password" name="password" />
125+ <input type="submit" value="Submit" />
126+ </form>
127+ </Template>
128+ )
129+}
130+
131+export function WrongPassword() {
132+ return (
133+ <Template css="/default.css">
134+ <h1>Incorrect password</h1>
135+ <p>Reload page to try again</p>
136+ </Template>
137+ )
138+}
139+
140+export async function ShowFile(filename: string, uuid: string, content: Uint8Array | null, filetype: string) {
141+ //if content is null, this is for the js frontend
142+ let preview;
143+ if (!content) {
144+ preview = <>Please wait for the file to load</>
145+ } else {
146+ preview = <>This file can't be previewed</>
147+ if (isValidUTF8(Buffer.from(content)) && filetype !== "blob") {
148+ if (filetype === "none") {
149+ preview = <pre>{escapeHTML(new TextDecoder("utf-8").decode(content))}</pre>
150+ } else {
151+ preview = <pre>{hljs.highlight(new TextDecoder("utf-8").decode(content), { language: filetype }).value}</pre>
152+ }
153+ } else {
154+ const filetype = await fileTypeFromBuffer(content)
155+ if (filetype) {
156+ const base64Content = `data:${filetype.mime};base64,${Buffer.from(content).toString("base64")}`;
157+ if (filetype.mime.startsWith("audio/")) {
158+ preview = <audio controls="" src={base64Content} />
159+ } else if (filetype.mime.startsWith("video/")) {
160+ preview = <video controls src={base64Content} />
161+ } else if (filetype.mime.startsWith("image/")) {
162+ preview = <img src={base64Content} />
163+ }
164+ }
165+ }
166+ }
167+
168+ return (
169+ <Template css="/show.css">
170+ <dialog id="password-dialog">
171+ <form onsubmit="onPasswordSubmit(); return false">
172+ <label id="password-label" for="password">File is encrypted, enter password to decrypt:</label>
173+ <input required type="password" id="password" />
174+ <input type="submit" value="Submit" />
175+ </form>
176+ </dialog>
177+ <div id="content">
178+ <div id="filename">{filename}</div>
179+ <div id="mediabox">{preview}</div>
180+ </div>
181+ <div id="sidebar">
182+ <h3>File info</h3>
183+ <hr />
184+ <p id="filesize">size: {content ? humanFileSize(content.byteLength) : "unknown"}</p>
185+ <p>declared type: {filetype}</p>
186+ <form id="download-form" action={`/raw/${uuid}`} method="get">
187+ <button>Download</button>
188+ </form>
189+ </div>
190+ {content ? "" : <script src="/dist/client-show.js" />}
191+ </Template>
192+ )
193+}
194+
195+export function SetCookie(uuid: string) {
196+ return (
197+ <Template css="/default.css">
198+ Redirecting you back to <a href={`/show/${uuid}`}>/show/{uuid}</a>
199+ </Template>
200+ )
201+}
Msrc/index.ts
@@ -1,7 +1,143 @@
1-import { Elysia } from "elysia";
1+import { Elysia, StatusMap, t } from "elysia"
2+import { Database } from "bun:sqlite"
3+import { randomUUIDv7 } from "bun"
4+import staticPlugin from "@elysiajs/static"
5+import { html } from '@elysiajs/html'
6+import crypto from 'crypto'
7+import { DecryptFile, Index, NotFound, SetCookie, ShowFile, WrongPassword } from "./components"
28
3-const app = new Elysia().get("/", () => "Hello Elysia").listen(3000);
49
5-console.log(
6- `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
7-);
10+const db = new Database("./db.sqlite")
11+db.exec("PRAGMA foreign_keys = ON")
12+db.exec("PRAGMA journal_mode = WAL2")
13+db.exec("CREATE TABLE IF NOT EXISTS files (uuid TEXT PRIMARY KEY, filename TEXT NOT NULL, content BLOB NOT NULL, filetype TEXT NOT NULL, encrypted INTEGER NOT NULL) STRICT")
14+db.exec("PRAGMA optimize")
15+
16+
17+function encrypt(content: Uint8Array, password: string): Buffer<ArrayBuffer> {
18+ let iv = crypto.randomBytes(16)
19+ const salt = crypto.randomBytes(16)
20+ const key = crypto.pbkdf2Sync(password, salt, 100000, 32, "sha512")
21+ let cipher = crypto.createCipheriv('aes-256-cbc', key, iv)
22+ let encrypted_content = Buffer.concat([salt, iv, cipher.update(content), cipher.final()])
23+ return encrypted_content
24+}
25+
26+function decrypt(encrypted_content: Uint8Array, password: string): Buffer {
27+ const salt = encrypted_content.slice(0, 16)
28+ const iv = encrypted_content.slice(16, 32)
29+ const key = crypto.pbkdf2Sync(password, salt, 100000, 32, "sha512")
30+ let decipher = crypto.createDecipheriv('aes-256-cbc', key, iv)
31+ let content = Buffer.concat([decipher.update(encrypted_content.slice(32)), decipher.final()])
32+ return content
33+}
34+
35+const app = new Elysia({
36+ serve: {
37+ maxRequestBodySize: 1024 * 1024 * 1024 // 1GB
38+ }
39+})
40+ .use(staticPlugin({ assets: "./assets", prefix: "/" }))
41+ .use(html())
42+ .get("/", ({ server }) => Index(server?.url.toString()!, false))
43+ .get("/js", ({ server }) => Index(server?.url.toString()!, true))
44+ .post("/upload", async ({ set, body, query }) => {
45+
46+ const uuid = randomUUIDv7()
47+ let content = Buffer.from(await body.file.bytes())
48+ let encrypted = false;
49+ if (body.encrypted === "on") {
50+ encrypted = true
51+ } else if (body.password) {
52+ content = encrypt(content, body.password)
53+ encrypted = true
54+ }
55+ db.exec("INSERT INTO files (uuid, filename, content, filetype, encrypted) VALUES (?, ?, ?, ?, ?)", [uuid, body.filename || body.file.name, content, body.filetype, encrypted])
56+ set.status = StatusMap["See Other"]
57+ if (query.withJs) {
58+ set.headers["location"] = `/show-js/${uuid}`
59+ } else {
60+ set.headers["location"] = `/show/${uuid}`
61+ }
62+ return `Created with id: ${uuid}`
63+
64+ }, {
65+ body: t.Object({ file: t.File(), filename: t.Optional(t.String()), filetype: t.String()/*todo:verifiy via t.Enum*/, password: t.Optional(t.String()), encrypted: t.Optional(t.String()) }),
66+ query: t.Object({ withJs: t.Optional(t.Boolean()) })
67+ })
68+ .get("/show/:uuid", async ({ set, params, cookie }) => {
69+
70+ const result = db.prepare("SELECT filename, content, filetype, encrypted FROM files WHERE uuid = ?").get(params.uuid) as { filename: string, content: Uint8Array, filetype: string, encrypted: boolean } || null
71+ if (!result) {
72+ set.status = StatusMap["Not Found"]
73+ return NotFound()
74+ }
75+ if (result.encrypted) {
76+ const password = cookie.password.value
77+ if (!password) {
78+ return DecryptFile(result.filename, params.uuid)
79+ } else {
80+ try {
81+ result.content = decrypt(result.content, password)
82+ } catch (e) {
83+ set.status = StatusMap["Forbidden"]
84+ set.headers["set-cookie"] = [
85+ `password=; Path=/show/${params.uuid}; SameSite=lax; HttpOnly; Expires=Thu, 01 Jan 1970 00:00:00 GMT`,
86+ `password=; Path=/raw/${params.uuid}; SameSite=lax; HttpOnly; Expires=Thu, 01 Jan 1970 00:00:00 GMT`
87+ ]
88+ return WrongPassword()
89+ }
90+ }
91+ }
92+ return ShowFile(result.filename, params.uuid, result.content, result.filetype)
93+
94+ }, { params: t.Object({ uuid: t.String() }) })
95+ .get("/show-js/:uuid", async ({ set, params, cookie }) => {
96+
97+ const result = db.prepare("SELECT filename, content, filetype, encrypted FROM files WHERE uuid = ?").get(params.uuid) as { filename: string, content: Uint8Array, filetype: string, encrypted: boolean } || null
98+ if (!result) {
99+ set.status = StatusMap["Not Found"]
100+ return NotFound()
101+ }
102+ return ShowFile(result.filename, params.uuid, null, result.filetype)
103+
104+ }, { params: t.Object({ uuid: t.String() }) })
105+ .post("/set-cookie/:uuid", ({ set, body, params }) => {
106+
107+ set.headers["set-cookie"] = [
108+ `password=${body.password}; Path=/show/${params.uuid}; SameSite=lax; HttpOnly`,
109+ `password=${body.password}; Path=/raw/${params.uuid}; SameSite=lax; HttpOnly`
110+ ]
111+ set.headers["location"] = `/show/${params.uuid}`
112+ set.status = StatusMap["See Other"]
113+ return SetCookie(params.uuid)
114+
115+ }, { body: t.Object({ password: t.String() }), params: t.Object({ uuid: t.String() }) })
116+ .get("/raw/:uuid", ({ set, params, cookie, query }) => {
117+ const result = db.prepare("SELECT content, filename, encrypted, filetype FROM files WHERE uuid = ?").get(params.uuid) as { content: Uint8Array, filename: string, encrypted: boolean, filetype: string } || null
118+ if (!result) {
119+ set.status = StatusMap["Not Found"]
120+ return "File not found"
121+ }
122+ if (result.encrypted && query.ignore_password !== "true") {
123+ if (!cookie.password.value) {
124+ set.status = StatusMap["Unauthorized"]
125+ return "This file is encrypted, set the cookie \"password\" with the correct password to allow the server to decrypt it"
126+ }
127+ try {
128+ result.content = decrypt(result.content, cookie.password.value)
129+ } catch (e) {
130+ set.status = StatusMap["Forbidden"]
131+ return "Incorrect password"
132+ }
133+ }
134+ set.headers["encrypted"] = result.encrypted ? "true" : "false"
135+ set.headers["filetype"] = result.filetype
136+ set.headers["filename"] = result.filename
137+ set.headers["content-disposition"] = `inline; filename=${result.filename}`
138+ return result.content
139+
140+ }, { params: t.Object({ uuid: t.String() }), cookie: t.Object({ password: t.Optional(t.String()) }), query: t.Object({ ignore_password: t.Optional(t.String()) }) })
141+ .listen(3000)
142+
143+console.log(`⚡ Zigbin is running at ${app.server?.hostname}:${app.server?.port} ⚡`)
Asrc/shared.ts
@@ -0,0 +1,5 @@
1+export function humanFileSize(size: number) {
2+ if (size < 1024) return `${size} B`
3+ let i = Math.min(4, Math.floor(Math.log(size) / Math.log(1024)))
4+ return `${(size / Math.pow(1024, i)).toFixed(2)} ${["B", "KiB", "MiB", "GiB", "TiB"][i]}`
5+}
Mtsconfig.json
@@ -1,103 +1,18 @@
11 {
22 "compilerOptions": {
33 /* Visit https://aka.ms/tsconfig to read more about this file */
4-
5- /* Projects */
6- // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7- // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8- // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9- // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10- // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11- // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12-
13- /* Language and Environment */
14- "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15- // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16- // "jsx": "preserve", /* Specify what JSX code is generated. */
17- // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18- // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19- // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
20- // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21- // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
22- // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
23- // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24- // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25- // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
26-
27- /* Modules */
28- "module": "ES2022", /* Specify what module code is generated. */
29- // "rootDir": "./", /* Specify the root folder within your source files. */
30- "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
31- // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
32- // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
33- // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34- // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35- "types": ["bun-types"], /* Specify type package names to be included without being referenced in a source file. */
36- // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
37- // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
38- // "resolveJsonModule": true, /* Enable importing .json files. */
39- // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
40-
41- /* JavaScript Support */
42- // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
43- // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
44- // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
45-
46- /* Emit */
47- // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
48- // "declarationMap": true, /* Create sourcemaps for d.ts files. */
49- // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
50- // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
51- // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
52- // "outDir": "./", /* Specify an output folder for all emitted files. */
53- // "removeComments": true, /* Disable emitting comments. */
54- // "noEmit": true, /* Disable emitting files from a compilation. */
55- // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
56- // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
57- // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
58- // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
59- // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
60- // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
61- // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
62- // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
63- // "newLine": "crlf", /* Set the newline character for emitting files. */
64- // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
65- // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
66- // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
67- // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
68- // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
69- // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
70-
71- /* Interop Constraints */
72- // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
73- // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
74- "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
75- // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
76- "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
77-
78- /* Type Checking */
79- "strict": true, /* Enable all strict type-checking options. */
80- // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
81- // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
82- // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
83- // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
84- // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
85- // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
86- // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
87- // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
88- // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
89- // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90- // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
91- // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
92- // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
93- // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
94- // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
95- // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
96- // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
97- // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
98-
99- /* Completeness */
100- // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
101- "skipLibCheck": true /* Skip type checking all .d.ts files. */
4+ "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
5+ "module": "ES2022", /* Specify what module code is generated. */
6+ "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
7+ "types": [
8+ "bun-types"
9+ ], /* Specify type package names to be included without being referenced in a source file. */
10+ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
11+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
12+ "strict": true, /* Enable all strict type-checking options. */
13+ "skipLibCheck": true, /* Skip type checking all .d.ts files. */
14+ "jsx": "react",
15+ "jsxFactory": "Html.createElement",
16+ "jsxFragmentFactory": "Html.Fragment"
10217 }
103-}
18+}