コミットを比較
14 コミット
2b07354e5c
...
9f79050ccf
作成者 | SHA1 | 日付 |
---|---|---|
n9k | 9f79050ccf | |
n9k | f551a1263b | |
n9k | 829f3f004b | |
n9k | d6506ef9bf | |
n9k | 4bab173237 | |
n9k | 5bd5d7ff6d | |
n9k | 4cde4ea07a | |
n9k | ce5b7ba0ba | |
n9k | 55c16d7214 | |
n9k | 9c5fc4bc71 | |
n9k | f48a27525e | |
n9k | 0ce1902918 | |
n9k | 5153f5d112 | |
n9k | ade3ca4e9e |
|
@ -0,0 +1,660 @@
|
|||
### GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc.
|
||||
<https://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
### Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains
|
||||
free software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing
|
||||
under this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
### TERMS AND CONDITIONS
|
||||
|
||||
#### 0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public
|
||||
License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds
|
||||
of works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of
|
||||
an exact copy. The resulting work is called a "modified version" of
|
||||
the earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user
|
||||
through a computer network, with no transfer of a copy, is not
|
||||
conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices" to
|
||||
the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
#### 1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work for
|
||||
making modifications to it. "Object code" means any non-source form of
|
||||
a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users can
|
||||
regenerate automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that same
|
||||
work.
|
||||
|
||||
#### 2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not convey,
|
||||
without conditions so long as your license otherwise remains in force.
|
||||
You may convey covered works to others for the sole purpose of having
|
||||
them make modifications exclusively for you, or provide you with
|
||||
facilities for running those works, provided that you comply with the
|
||||
terms of this License in conveying all material for which you do not
|
||||
control copyright. Those thus making or running the covered works for
|
||||
you must do so exclusively on your behalf, under your direction and
|
||||
control, on terms that prohibit them from making any copies of your
|
||||
copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under the
|
||||
conditions stated below. Sublicensing is not allowed; section 10 makes
|
||||
it unnecessary.
|
||||
|
||||
#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such
|
||||
circumvention is effected by exercising rights under this License with
|
||||
respect to the covered work, and you disclaim any intention to limit
|
||||
operation or modification of the work as a means of enforcing, against
|
||||
the work's users, your or third parties' legal rights to forbid
|
||||
circumvention of technological measures.
|
||||
|
||||
#### 4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
#### 5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these
|
||||
conditions:
|
||||
|
||||
- a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
- b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under
|
||||
section 7. This requirement modifies the requirement in section 4
|
||||
to "keep intact all notices".
|
||||
- c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
- d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
#### 6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms of
|
||||
sections 4 and 5, provided that you also convey the machine-readable
|
||||
Corresponding Source under the terms of this License, in one of these
|
||||
ways:
|
||||
|
||||
- a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
- b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the Corresponding
|
||||
Source from a network server at no charge.
|
||||
- c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
- d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
- e) Convey the object code using peer-to-peer transmission,
|
||||
provided you inform other peers where the object code and
|
||||
Corresponding Source of the work are being offered to the general
|
||||
public at no charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal,
|
||||
family, or household purposes, or (2) anything designed or sold for
|
||||
incorporation into a dwelling. In determining whether a product is a
|
||||
consumer product, doubtful cases shall be resolved in favor of
|
||||
coverage. For a particular product received by a particular user,
|
||||
"normally used" refers to a typical or common use of that class of
|
||||
product, regardless of the status of the particular user or of the way
|
||||
in which the particular user actually uses, or expects or is expected
|
||||
to use, the product. A product is a consumer product regardless of
|
||||
whether the product has substantial commercial, industrial or
|
||||
non-consumer uses, unless such uses represent the only significant
|
||||
mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to
|
||||
install and execute modified versions of a covered work in that User
|
||||
Product from a modified version of its Corresponding Source. The
|
||||
information must suffice to ensure that the continued functioning of
|
||||
the modified object code is in no case prevented or interfered with
|
||||
solely because modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or
|
||||
updates for a work that has been modified or installed by the
|
||||
recipient, or for the User Product in which it has been modified or
|
||||
installed. Access to a network may be denied when the modification
|
||||
itself materially and adversely affects the operation of the network
|
||||
or violates the rules and protocols for communication across the
|
||||
network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
#### 7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders
|
||||
of that material) supplement the terms of this License with terms:
|
||||
|
||||
- a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
- b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
- c) Prohibiting misrepresentation of the origin of that material,
|
||||
or requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
- d) Limiting the use for publicity purposes of names of licensors
|
||||
or authors of the material; or
|
||||
- e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
- f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions
|
||||
of it) with contractual assumptions of liability to the recipient,
|
||||
for any liability that these contractual assumptions directly
|
||||
impose on those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions; the
|
||||
above requirements apply either way.
|
||||
|
||||
#### 8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your license
|
||||
from a particular copyright holder is reinstated (a) provisionally,
|
||||
unless and until the copyright holder explicitly and finally
|
||||
terminates your license, and (b) permanently, if the copyright holder
|
||||
fails to notify you of the violation by some reasonable means prior to
|
||||
60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
#### 9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or run
|
||||
a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
#### 10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
#### 11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims owned
|
||||
or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within the
|
||||
scope of its coverage, prohibits the exercise of, or is conditioned on
|
||||
the non-exercise of one or more of the rights that are specifically
|
||||
granted under this License. You may not convey a covered work if you
|
||||
are a party to an arrangement with a third party that is in the
|
||||
business of distributing software, under which you make payment to the
|
||||
third party based on the extent of your activity of conveying the
|
||||
work, and under which the third party grants, to any of the parties
|
||||
who would receive the covered work from you, a discriminatory patent
|
||||
license (a) in connection with copies of the covered work conveyed by
|
||||
you (or copies made from those copies), or (b) primarily for and in
|
||||
connection with specific products or compilations that contain the
|
||||
covered work, unless you entered into that arrangement, or that patent
|
||||
license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
#### 12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under
|
||||
this License and any other pertinent obligations, then as a
|
||||
consequence you may not convey it at all. For example, if you agree to
|
||||
terms that obligate you to collect a royalty for further conveying
|
||||
from those to whom you convey the Program, the only way you could
|
||||
satisfy both those terms and this License would be to refrain entirely
|
||||
from conveying the Program.
|
||||
|
||||
#### 13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your
|
||||
version supports such interaction) an opportunity to receive the
|
||||
Corresponding Source of your version by providing access to the
|
||||
Corresponding Source from a network server at no charge, through some
|
||||
standard or customary means of facilitating copying of software. This
|
||||
Corresponding Source shall include the Corresponding Source for any
|
||||
work covered by version 3 of the GNU General Public License that is
|
||||
incorporated pursuant to the following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
#### 14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Affero General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever
|
||||
published by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future versions
|
||||
of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
#### 15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
|
||||
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
|
||||
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
|
||||
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
|
||||
CORRECTION.
|
||||
|
||||
#### 16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
|
||||
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
|
||||
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
|
||||
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
|
||||
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
|
||||
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
|
||||
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
#### 17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
### How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these
|
||||
terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest to
|
||||
attach them to the start of each source file to most effectively state
|
||||
the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper
|
||||
mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for
|
||||
the specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. For more information on this, and how to apply and follow
|
||||
the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,342 @@
|
|||
### Creative Commons Attribution 3.0 Unported
|
||||
|
||||
> CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
> LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
|
||||
> ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
> INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO
|
||||
> WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS
|
||||
> LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
|
||||
|
||||
### *License*
|
||||
|
||||
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS
|
||||
CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS
|
||||
PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE
|
||||
WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS
|
||||
PROHIBITED.
|
||||
|
||||
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND
|
||||
AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS
|
||||
LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU
|
||||
THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH
|
||||
TERMS AND CONDITIONS.
|
||||
|
||||
#### 1. Definitions
|
||||
|
||||
- a. __"Adaptation"__ means a work based upon the Work, or upon the
|
||||
Work and other pre-existing works, such as a translation,
|
||||
adaptation, derivative work, arrangement of music or other
|
||||
alterations of a literary or artistic work, or phonogram or
|
||||
performance and includes cinematographic adaptations or any other
|
||||
form in which the Work may be recast, transformed, or adapted
|
||||
including in any form recognizably derived from the original,
|
||||
except that a work that constitutes a Collection will not be
|
||||
considered an Adaptation for the purpose of this License. For the
|
||||
avoidance of doubt, where the Work is a musical work, performance
|
||||
or phonogram, the synchronization of the Work in timed-relation
|
||||
with a moving image ("synching") will be considered an Adaptation
|
||||
for the purpose of this License.
|
||||
- b. __"Collection"__ means a collection of literary or artistic
|
||||
works, such as encyclopedias and anthologies, or performances,
|
||||
phonograms or broadcasts, or other works or subject matter other
|
||||
than works listed in Section 1(f) below, which, by reason of the
|
||||
selection and arrangement of their contents, constitute
|
||||
intellectual creations, in which the Work is included in its
|
||||
entirety in unmodified form along with one or more other
|
||||
contributions, each constituting separate and independent works in
|
||||
themselves, which together are assembled into a collective whole.
|
||||
A work that constitutes a Collection will not be considered an
|
||||
Adaptation (as defined above) for the purposes of this License.
|
||||
- c. __"Distribute"__ means to make available to the public the
|
||||
original and copies of the Work or Adaptation, as appropriate,
|
||||
through sale or other transfer of ownership.
|
||||
- d. __"Licensor"__ means the individual, individuals, entity or
|
||||
entities that offer(s) the Work under the terms of this License.
|
||||
- e. __"Original Author"__ means, in the case of a literary or
|
||||
artistic work, the individual, individuals, entity or entities who
|
||||
created the Work or if no individual or entity can be identified,
|
||||
the publisher; and in addition (i) in the case of a performance
|
||||
the actors, singers, musicians, dancers, and other persons who
|
||||
act, sing, deliver, declaim, play in, interpret or otherwise
|
||||
perform literary or artistic works or expressions of folklore;
|
||||
(ii) in the case of a phonogram the producer being the person or
|
||||
legal entity who first fixes the sounds of a performance or other
|
||||
sounds; and, (iii) in the case of broadcasts, the organization
|
||||
that transmits the broadcast.
|
||||
- f. __"Work"__ means the literary and/or artistic work offered
|
||||
under the terms of this License including without limitation any
|
||||
production in the literary, scientific and artistic domain,
|
||||
whatever may be the mode or form of its expression including
|
||||
digital form, such as a book, pamphlet and other writing; a
|
||||
lecture, address, sermon or other work of the same nature; a
|
||||
dramatic or dramatico- musical work; a choreographic work or
|
||||
entertainment in dumb show; a musical composition with or without
|
||||
words; a cinematographic work to which are assimilated works
|
||||
expressed by a process analogous to cinematography; a work of
|
||||
drawing, painting, architecture, sculpture, engraving or
|
||||
lithography; a photographic work to which are assimilated works
|
||||
expressed by a process analogous to photography; a work of applied
|
||||
art; an illustration, map, plan, sketch or three-dimensional work
|
||||
relative to geography, topography, architecture or science; a
|
||||
performance; a broadcast; a phonogram; a compilation of data to
|
||||
the extent it is protected as a copyrightable work; or a work
|
||||
performed by a variety or circus performer to the extent it is not
|
||||
otherwise considered a literary or artistic work.
|
||||
- g. __"You"__ means an individual or entity exercising rights under
|
||||
this License who has not previously violated the terms of this
|
||||
License with respect to the Work, or who has received express
|
||||
permission from the Licensor to exercise rights under this License
|
||||
despite a previous violation.
|
||||
- h. __"Publicly Perform"__ means to perform public recitations of the
|
||||
Work and to communicate to the public those public recitations, by
|
||||
any means or process, including by wire or wireless means or
|
||||
public digital performances; to make available to the public Works
|
||||
in such a way that members of the public may access these Works
|
||||
from a place and at a place individually chosen by them; to
|
||||
perform the Work to the public by any means or process and the
|
||||
communication to the public of the performances of the Work,
|
||||
including by public digital performance; to broadcast and
|
||||
rebroadcast the Work by any means including signs, sounds or
|
||||
images.
|
||||
- i. __"Reproduce"__ means to make copies of the Work by any means
|
||||
including without limitation by sound or visual recordings and the
|
||||
right of fixation and reproducing fixations of the Work, including
|
||||
storage of a protected performance or phonogram in digital form or
|
||||
other electronic medium.
|
||||
|
||||
#### 2. Fair Dealing Rights
|
||||
|
||||
Nothing in this License is intended to reduce, limit, or restrict any
|
||||
uses free from copyright or rights arising from limitations or
|
||||
exceptions that are provided for in connection with the copyright
|
||||
protection under copyright law or other applicable laws.
|
||||
|
||||
#### 3. License Grant
|
||||
|
||||
Subject to the terms and conditions of this License, Licensor hereby
|
||||
grants You a worldwide, royalty-free, non-exclusive, perpetual (for
|
||||
the duration of the applicable copyright) license to exercise the
|
||||
rights in the Work as stated below:
|
||||
|
||||
- a. to Reproduce the Work, to incorporate the Work into one or more
|
||||
Collections, and to Reproduce the Work as incorporated in the
|
||||
Collections;
|
||||
- b. to create and Reproduce Adaptations provided that any such
|
||||
Adaptation, including any translation in any medium, takes
|
||||
reasonable steps to clearly label, demarcate or otherwise identify
|
||||
that changes were made to the original Work. For example, a
|
||||
translation could be marked "The original work was translated from
|
||||
English to Spanish," or a modification could indicate "The
|
||||
original work has been modified.";
|
||||
- c. to Distribute and Publicly Perform the Work including as
|
||||
incorporated in Collections; and,
|
||||
- d. to Distribute and Publicly Perform Adaptations.
|
||||
- e. For the avoidance of doubt:
|
||||
- 1. __Non-waivable Compulsory License Schemes.__ In those
|
||||
jurisdictions in which the right to collect royalties through
|
||||
any statutory or compulsory licensing scheme cannot be waived,
|
||||
the Licensor reserves the exclusive right to collect such
|
||||
royalities for any exercise by You of the rights granted under
|
||||
this License;
|
||||
- 2. __Waivable Compulsory License Schemes.__ In those
|
||||
jurisdictions in which the right to collect royalties through
|
||||
any statutory or compulsory licensing scheme can be waived,
|
||||
the Licensor waives the exclusive right to collect such
|
||||
royalties for any exercise by You of the rights granted under
|
||||
this License; and,
|
||||
- 3. __Voluntary License Schemes.__ The Licensor waives the right
|
||||
to collect royalties, whether individually or, in the event
|
||||
that the Licensor is a member of a collecting society that
|
||||
administers voluntary licensing schemes, via that society,
|
||||
from any exercise by You of the rights granted under this
|
||||
License.
|
||||
|
||||
The above rights may be exercised in all media and formats whether now
|
||||
known or hereafter devised. The above rights include the right to make
|
||||
such modifications as are technically necessary to exercise the rights
|
||||
in other media and formats. Subject to Section 8(f), all rights not
|
||||
expressly granted by Licensor are hereby reserved.
|
||||
|
||||
#### 4. Restrictions
|
||||
|
||||
The license granted in Section 3 above is expressly made subject to
|
||||
and limited by the following restrictions:
|
||||
|
||||
- a. You may Distribute or Publicly Perform the Work only under the
|
||||
terms of this License. You must include a copy of, or the Uniform
|
||||
Resource Identifier (URI) for, this License with every copy of the
|
||||
Work You Distribute or Publicly Perform. You may not offer or
|
||||
impose any terms on the Work that restrict the terms of this
|
||||
License or the ability of the recipient of the Work to exercise
|
||||
the rights granted to that recipient under the terms of the
|
||||
License. You may not sublicense the Work. You must keep intact all
|
||||
notices that refer to this License and to the disclaimer of
|
||||
warranties with every copy of the Work You Distribute or Publicly
|
||||
Perform. When You Distribute or Publicly Perform the Work, You may
|
||||
not impose any effective technological measures on the Work that
|
||||
restrict the ability of a recipient of the Work from You to
|
||||
exercise the rights granted to that recipient under the terms of
|
||||
the License. This Section 4(a) applies to the Work as incorporated
|
||||
in a Collection, but this does not require the Collection apart
|
||||
from the Work itself to be made subject to the terms of this
|
||||
License. If You create a Collection, upon notice from any Licensor
|
||||
You must, to the extent practicable, remove from the Collection
|
||||
any credit as required by Section 4(b), as requested. If You
|
||||
create an Adaptation, upon notice from any Licensor You must, to
|
||||
the extent practicable, remove from the Adaptation any credit as
|
||||
required by Section 4(b), as requested.
|
||||
- b. If You Distribute, or Publicly Perform the Work or any
|
||||
Adaptations or Collections, You must, unless a request has been
|
||||
made pursuant to Section 4(a), keep intact all copyright notices
|
||||
for the Work and provide, reasonable to the medium or means You
|
||||
are utilizing: (i) the name of the Original Author (or pseudonym,
|
||||
if applicable) if supplied, and/or if the Original Author and/or
|
||||
Licensor designate another party or parties (e.g., a sponsor
|
||||
institute, publishing entity, journal) for attribution
|
||||
("Attribution Parties") in Licensor's copyright notice, terms of
|
||||
service or by other reasonable means, the name of such party or
|
||||
parties; (ii) the title of the Work if supplied; (iii) to the
|
||||
extent reasonably practicable, the URI, if any, that Licensor
|
||||
specifies to be associated with the Work, unless such URI does not
|
||||
refer to the copyright notice or licensing information for the
|
||||
Work; and (iv) , consistent with Section 3(b), in the case of an
|
||||
Adaptation, a credit identifying the use of the Work in the
|
||||
Adaptation (e.g., "French translation of the Work by Original
|
||||
Author," or "Screenplay based on original Work by Original
|
||||
Author"). The credit required by this Section 4 (b) may be
|
||||
implemented in any reasonable manner; provided, however, that in
|
||||
the case of a Adaptation or Collection, at a minimum such credit
|
||||
will appear, if a credit for all contributing authors of the
|
||||
Adaptation or Collection appears, then as part of these credits
|
||||
and in a manner at least as prominent as the credits for the other
|
||||
contributing authors. For the avoidance of doubt, You may only use
|
||||
the credit required by this Section for the purpose of attribution
|
||||
in the manner set out above and, by exercising Your rights under
|
||||
this License, You may not implicitly or explicitly assert or imply
|
||||
any connection with, sponsorship or endorsement by the Original
|
||||
Author, Licensor and/or Attribution Parties, as appropriate, of
|
||||
You or Your use of the Work, without the separate, express prior
|
||||
written permission of the Original Author, Licensor and/or
|
||||
Attribution Parties.
|
||||
- c. Except as otherwise agreed in writing by the Licensor or as may
|
||||
be otherwise permitted by applicable law, if You Reproduce,
|
||||
Distribute or Publicly Perform the Work either by itself or as
|
||||
part of any Adaptations or Collections, You must not distort,
|
||||
mutilate, modify or take other derogatory action in relation to
|
||||
the Work which would be prejudicial to the Original Author's honor
|
||||
or reputation. Licensor agrees that in those jurisdictions (e.g.
|
||||
Japan), in which any exercise of the right granted in Section 3(b)
|
||||
of this License (the right to make Adaptations) would be deemed to
|
||||
be a distortion, mutilation, modification or other derogatory
|
||||
action prejudicial to the Original Author's honor and reputation,
|
||||
the Licensor will waive or not assert, as appropriate, this
|
||||
Section, to the fullest extent permitted by the applicable
|
||||
national law, to enable You to reasonably exercise Your right
|
||||
under Section 3(b) of this License (right to make Adaptations) but
|
||||
not otherwise.
|
||||
|
||||
#### 5. Representations, Warranties and Disclaimer
|
||||
|
||||
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING,
|
||||
LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR
|
||||
WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED,
|
||||
STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF
|
||||
TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY,
|
||||
OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE.
|
||||
SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES,
|
||||
SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
|
||||
|
||||
#### 6. Limitation on Liability
|
||||
|
||||
EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL
|
||||
LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT
|
||||
OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
#### 7. Termination
|
||||
|
||||
- a. This License and the rights granted hereunder will terminate
|
||||
automatically upon any breach by You of the terms of this License.
|
||||
Individuals or entities who have received Adaptations or
|
||||
Collections from You under this License, however, will not have
|
||||
their licenses terminated provided such individuals or entities
|
||||
remain in full compliance with those licenses. Sections 1, 2, 5,
|
||||
6, 7, and 8 will survive any termination of this License.
|
||||
- b. Subject to the above terms and conditions, the license granted
|
||||
here is perpetual (for the duration of the applicable copyright in
|
||||
the Work). Notwithstanding the above, Licensor reserves the right
|
||||
to release the Work under different license terms or to stop
|
||||
distributing the Work at any time; provided, however that any such
|
||||
election will not serve to withdraw this License (or any other
|
||||
license that has been, or is required to be, granted under the
|
||||
terms of this License), and this License will continue in full
|
||||
force and effect unless terminated as stated above.
|
||||
|
||||
#### 8. Miscellaneous
|
||||
|
||||
- a. Each time You Distribute or Publicly Perform the Work or a
|
||||
Collection, the Licensor offers to the recipient a license to the
|
||||
Work on the same terms and conditions as the license granted to
|
||||
You under this License.
|
||||
- b. Each time You Distribute or Publicly Perform an Adaptation,
|
||||
Licensor offers to the recipient a license to the original Work on
|
||||
the same terms and conditions as the license granted to You under
|
||||
this License.
|
||||
- c. If any provision of this License is invalid or unenforceable
|
||||
under applicable law, it shall not affect the validity or
|
||||
enforceability of the remainder of the terms of this License, and
|
||||
without further action by the parties to this agreement, such
|
||||
provision shall be reformed to the minimum extent necessary to
|
||||
make such provision valid and enforceable.
|
||||
- d. No term or provision of this License shall be deemed waived and
|
||||
no breach consented to unless such waiver or consent shall be in
|
||||
writing and signed by the party to be charged with such waiver or
|
||||
consent.
|
||||
- e. This License constitutes the entire agreement between the
|
||||
parties with respect to the Work licensed here. There are no
|
||||
understandings, agreements or representations with respect to the
|
||||
Work not specified here. Licensor shall not be bound by any
|
||||
additional provisions that may appear in any communication from
|
||||
You. This License may not be modified without the mutual written
|
||||
agreement of the Licensor and You.
|
||||
- f. The rights granted under, and the subject matter referenced, in
|
||||
this License were drafted utilizing the terminology of the Berne
|
||||
Convention for the Protection of Literary and Artistic Works (as
|
||||
amended on September 28, 1979), the Rome Convention of 1961, the
|
||||
WIPO Copyright Treaty of 1996, the WIPO Performances and
|
||||
Phonograms Treaty of 1996 and the Universal Copyright Convention
|
||||
(as revised on July 24, 1971). These rights and subject matter
|
||||
take effect in the relevant jurisdiction in which the License
|
||||
terms are sought to be enforced according to the corresponding
|
||||
provisions of the implementation of those treaty provisions in the
|
||||
applicable national law. If the standard suite of rights granted
|
||||
under applicable copyright law includes additional rights not
|
||||
granted under this License, such additional rights are deemed to
|
||||
be included in the License; this License is not intended to
|
||||
restrict the license of any rights under applicable law.
|
||||
|
||||
> ### Creative Commons Notice
|
||||
>
|
||||
> Creative Commons is not a party to this License, and makes no
|
||||
> warranty whatsoever in connection with the Work. Creative Commons
|
||||
> will not be liable to You or any party on any legal theory for any
|
||||
> damages whatsoever, including without limitation any general,
|
||||
> special, incidental or consequential damages arising in connection
|
||||
> to this license. Notwithstanding the foregoing two (2) sentences, if
|
||||
> Creative Commons has expressly identified itself as the Licensor
|
||||
> hereunder, it shall have all rights and obligations of Licensor.
|
||||
>
|
||||
> Except for the limited purpose of indicating to the public that the
|
||||
> Work is licensed under the CCPL, Creative Commons does not authorize
|
||||
> the use by either party of the trademark "Creative Commons" or any
|
||||
> related trademark or logo of Creative Commons without the prior
|
||||
> written consent of Creative Commons. Any permitted use will be in
|
||||
> compliance with Creative Commons' then-current trademark usage
|
||||
> guidelines, as may be published on its website or otherwise made
|
||||
> available upon request from time to time. For the avoidance of
|
||||
> doubt, this trademark restriction does not form part of this
|
||||
> License.
|
||||
>
|
||||
> Creative Commons may be contacted at <https://creativecommons.org/>.
|
112
README.md
112
README.md
|
@ -4,8 +4,114 @@ Recipe for livestreaming over Tor
|
|||
|
||||
## Repo
|
||||
|
||||
The canonical location of this repo is https://git.076.ne.jp/ninya9k/anonstream.
|
||||
The canonical location of this repo is
|
||||
<https://git.076.ne.jp/ninya9k/anonstream>.
|
||||
|
||||
These mirrors also exist:
|
||||
* https://gitlab.com/ninya9k/anonstream
|
||||
* https://github.com/ninya9k/anonstream
|
||||
* <https://gitlab.com/ninya9k/anonstream>
|
||||
* <https://github.com/ninya9k/anonstream>
|
||||
|
||||
## Setup
|
||||
|
||||
You must have Python 3.10 at a minimum.
|
||||
|
||||
Clone the repo:
|
||||
```sh
|
||||
git clone https://git.076.ne.jp/ninya9k/anonstream.git
|
||||
cd anonstream
|
||||
```
|
||||
|
||||
Install dependencies in a virtual environment:
|
||||
```sh
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
python -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Before you run it might want to edit [/config.toml][config]:
|
||||
|
||||
* `secret_key`:
|
||||
used for cryptography, make it any long random string
|
||||
(e.g. `$ dd if=/dev/urandom bs=16 count=1 | base64`)
|
||||
|
||||
* `segments/directory`:
|
||||
directory containing stream segments, the default is `stream/` in
|
||||
the project root
|
||||
|
||||
* `title/file`:
|
||||
location of the stream title, the default is `title.txt` in the
|
||||
project root
|
||||
|
||||
* `captcha/fonts`:
|
||||
locations of fonts for the captcha, leaving it blank will use the
|
||||
default font
|
||||
|
||||
Run it:
|
||||
```sh
|
||||
python -m uvicorn app:app --port 5051
|
||||
```
|
||||
|
||||
This will start a webserver listening on localhost port 5051.
|
||||
|
||||
If you go to `http://localhost:5051` in a regular web browser now
|
||||
you should see the interface. When you started the webserver some
|
||||
credentials were printed in the terminal; you can log in with those at
|
||||
`http://localhost:5051/login` (requires cookies).
|
||||
|
||||
The only things left are (1) streaming, and (2) letting other people
|
||||
access your stream. [/STREAMING.md][streaming] has instructions for
|
||||
setting up OBS Studio and a Tor onion service. The instructions will
|
||||
be useful even if you want to use different streaming software and put
|
||||
your stream on the Internet some other way.
|
||||
|
||||
## Copying
|
||||
|
||||
anonstream is AGPL 3.0 or later, see
|
||||
[/LICENSES/AGPL-3.0-or-later.md][licence].
|
||||
|
||||
### Assets
|
||||
|
||||
* [/anonstream/static/settings.svg][settings.svg]:
|
||||
[setting](https://thenounproject.com/icon/setting-685325/) by
|
||||
[ulimicon](https://thenounproject.com/unlimicon/) is licensed under
|
||||
[CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
|
||||
|
||||
### Dependencies
|
||||
|
||||
* aiofiles <https://github.com/Tinche/aiofiles>
|
||||
([Apache 2.0][aiofiles])
|
||||
|
||||
* captcha <https://github.com/lepture/captcha>
|
||||
([BSD 3-Clause][captcha])
|
||||
|
||||
* itsdangerous <https://github.com/pallets/itsdangerous/>
|
||||
([BSD 3-Clause][itsdangerous])
|
||||
|
||||
* m3u8 <https://github.com/globocom/m3u8>
|
||||
([MIT][m3u8])
|
||||
|
||||
* quart <https://gitlab.com/pgjones/quart>
|
||||
([MIT][quart])
|
||||
|
||||
* toml <https://github.com/uiri/toml>
|
||||
([MIT][toml])
|
||||
|
||||
* uvicorn <https://github.com/encode/uvicorn>
|
||||
([BSD 3-Clause][uvicorn])
|
||||
|
||||
* werkzeug <https://github.com/pallets/werkzeug>
|
||||
([BSD 3-Clause][werkzeug])
|
||||
|
||||
[config]: https://git.076.ne.jp/ninya9k/anonstream/src/branch/master/config.toml
|
||||
[licence]: https://git.076.ne.jp/ninya9k/anonstream/src/branch/master/LICENSES/AGPL-3.0-or-later.md
|
||||
[settings.svg]: https://git.076.ne.jp/ninya9k/anonstream/src/branch/master/anonstream/static/settings.svg
|
||||
[streaming]: https://git.076.ne.jp/ninya9k/anonstream/src/branch/master/STREAMING.md
|
||||
|
||||
[aiofiles]: https://github.com/Tinche/aiofiles/blob/master/LICENSE
|
||||
[captcha]: https://github.com/lepture/captcha/blob/master/LICENSE
|
||||
[itsdangerous]: https://github.com/pallets/itsdangerous/blob/main/LICENSE.rst
|
||||
[m3u8]: https://github.com/globocom/m3u8/blob/master/LICENSE
|
||||
[quart]: https://gitlab.com/pgjones/quart/-/blob/main/LICENSE
|
||||
[toml]: https://github.com/uiri/toml/blob/master/LICENSE
|
||||
[uvicorn]: https://github.com/encode/uvicorn/blob/master/LICENSE.md
|
||||
[werkzeug]: https://github.com/pallets/werkzeug/blob/main/LICENSE.rst
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
### Tor
|
||||
|
||||
Install tor and include these lines in your [torrc][torrc]:
|
||||
|
||||
```
|
||||
HiddenServiceDir $PROJECT_ROOT/hidden_service
|
||||
HiddenServicePort 80 127.0.0.1:5051
|
||||
```
|
||||
but replace `$PROJECT_ROOT` with the folder you cloned the git repo
|
||||
into.
|
||||
|
||||
Then reload tor. If everything went well, the directory will have been
|
||||
created and your onion address will be in
|
||||
`$PROJECT_ROOT/hidden_service/hostname`.
|
||||
|
||||
### OBS Studio
|
||||
|
||||
Install OBS Studio. If the autoconfiguration wizard prompts you to
|
||||
choose a third-party service, ignore it since we're not gonna be doing
|
||||
that. Click `Settings`.
|
||||
|
||||
* Advanced
|
||||
* Recording
|
||||
* Filename Formatting: `stream`
|
||||
* Output
|
||||
* Output Mode: `Advanced`
|
||||
* Recording
|
||||
* Type: `Custom Output (FFmpeg)`
|
||||
* FFmpeg Output Type: `Output to File`
|
||||
* File path or URL: (`segments/directory` from the config, but should be an absolute path)
|
||||
* Container Format: `hls`
|
||||
* Muxer Settings (if any): `hls_init_time=0 hls_time=2 hls_list_size=120 hls_flags=delete_segments hls_segment_type=fmp4`
|
||||
* Video Bitrate: `400 Kbps`
|
||||
|
||||
[torrc]: https://support.torproject.org/#tbb-editing-torrc
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import os
|
||||
import secrets
|
||||
import toml
|
||||
|
@ -25,6 +28,7 @@ def create_app(config_file):
|
|||
'lstrip_blocks': True,
|
||||
})
|
||||
app.config.update({
|
||||
'SECRET_KEY_STRING': config['secret_key'],
|
||||
'SECRET_KEY': config['secret_key'].encode(),
|
||||
'AUTH_USERNAME': config['auth']['username'],
|
||||
'AUTH_PWHASH': auth_pwhash,
|
||||
|
@ -56,6 +60,7 @@ def create_app(config_file):
|
|||
'CHAT_NAME_MAX_LENGTH': config['chat']['max_name_length'],
|
||||
'CHAT_NAME_MIN_CONTRAST': config['chat']['min_name_contrast'],
|
||||
'CHAT_BACKGROUND_COLOUR': color_to_colour(config['chat']['background_color']),
|
||||
'CHAT_LEGACY_TRIPCODE_ALGORITHM': config['chat']['legacy_tripcode_algorithm'],
|
||||
'FLOOD_DURATION': config['flood']['duration'],
|
||||
'FLOOD_THRESHOLD': config['flood']['threshold'],
|
||||
'CAPTCHA_LIFETIME': config['captcha']['lifetime'],
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
from quart import current_app
|
||||
|
||||
from anonstream.utils.user import get_user_for_websocket
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import secrets
|
||||
|
||||
from quart import current_app
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import hashlib
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import hashlib
|
||||
|
||||
from quart import current_app
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
|
||||
|
@ -11,19 +14,27 @@ CONFIG = current_app.config
|
|||
def _generate_tripcode_digest_legacy(password):
|
||||
hexdigest, _ = werkzeug.security._hash_internal(
|
||||
'pbkdf2:sha256:150000',
|
||||
CONFIG['SECRET_KEY'],
|
||||
CONFIG['SECRET_KEY_STRING'],
|
||||
password,
|
||||
)
|
||||
digest = bytes.fromhex(hexdigest)
|
||||
return base64.b64encode(digest)[:8].decode()
|
||||
|
||||
def generate_tripcode_digest(password):
|
||||
def _generate_tripcode_digest(password):
|
||||
parts = CONFIG['SECRET_KEY'] + b'tripcode\0' + password.encode()
|
||||
digest = hashlib.sha256(parts).digest()
|
||||
return base64.b64encode(digest)[:8].decode()
|
||||
|
||||
def generate_tripcode(password, generate_digest=generate_tripcode_digest):
|
||||
digest = generate_digest(password)
|
||||
def generate_tripcode_digest(password):
|
||||
algorithm = (
|
||||
_generate_tripcode_digest_legacy
|
||||
if CONFIG['CHAT_LEGACY_TRIPCODE_ALGORITHM'] else
|
||||
_generate_tripcode_digest
|
||||
)
|
||||
return algorithm(password)
|
||||
|
||||
def generate_tripcode(password):
|
||||
digest = generate_tripcode_digest(password)
|
||||
background_colour = generate_colour(
|
||||
seed='tripcode-background\0' + digest,
|
||||
bg=CONFIG['CHAT_BACKGROUND_COLOUR'],
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import hashlib
|
||||
import base64
|
||||
from collections import OrderedDict
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import anonstream.routes.core
|
||||
import anonstream.routes.websocket
|
||||
import anonstream.routes.nojs
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
from quart import current_app, request, render_template, abort, make_response, redirect, url_for, abort
|
||||
|
||||
from anonstream.captcha import get_captcha_image
|
||||
|
@ -5,11 +8,16 @@ from anonstream.segments import segments
|
|||
from anonstream.stream import is_online, get_stream_uptime
|
||||
from anonstream.user import watched
|
||||
from anonstream.routes.wrappers import with_user_from, auth_required
|
||||
from anonstream.utils.security import generate_csp
|
||||
|
||||
@current_app.route('/')
|
||||
@with_user_from(request)
|
||||
async def home(user):
|
||||
return await render_template('home.html', user=user)
|
||||
return await render_template(
|
||||
'home.html',
|
||||
csp=generate_csp(),
|
||||
user=user,
|
||||
)
|
||||
|
||||
@current_app.route('/stream.mp4')
|
||||
@with_user_from(request)
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
from quart import current_app, request, render_template, redirect, url_for, escape, Markup
|
||||
|
||||
from anonstream.captcha import get_random_captcha_digest_for
|
||||
|
@ -8,6 +11,7 @@ from anonstream.routes.wrappers import with_user_from, render_template_with_etag
|
|||
from anonstream.helpers.chat import get_scrollback
|
||||
from anonstream.helpers.user import get_default_name
|
||||
from anonstream.utils.chat import generate_nonce
|
||||
from anonstream.utils.security import generate_csp
|
||||
from anonstream.utils.user import concatenate_for_notice
|
||||
|
||||
CONFIG = current_app.config
|
||||
|
@ -18,6 +22,7 @@ USERS_BY_TOKEN = current_app.users_by_token
|
|||
async def nojs_stream(user):
|
||||
return await render_template(
|
||||
'nojs_stream.html',
|
||||
csp=generate_csp(),
|
||||
user=user,
|
||||
)
|
||||
|
||||
|
@ -28,6 +33,7 @@ async def nojs_info(user):
|
|||
uptime, viewership = get_stream_uptime_and_viewership()
|
||||
return await render_template(
|
||||
'nojs_info.html',
|
||||
csp=generate_csp(),
|
||||
user=user,
|
||||
viewership=viewership,
|
||||
uptime=uptime,
|
||||
|
@ -40,6 +46,7 @@ async def nojs_info(user):
|
|||
async def nojs_chat_messages(user):
|
||||
return await render_template_with_etag(
|
||||
'nojs_chat_messages.html',
|
||||
{'csp': generate_csp()},
|
||||
user=user,
|
||||
users_by_token=USERS_BY_TOKEN,
|
||||
messages=get_scrollback(current_app.messages),
|
||||
|
@ -58,6 +65,7 @@ async def nojs_chat_users(user):
|
|||
users_by_presence = get_users_by_presence()
|
||||
return await render_template_with_etag(
|
||||
'nojs_chat_users.html',
|
||||
{'csp': generate_csp()},
|
||||
user=user,
|
||||
get_default_name=get_default_name,
|
||||
users_watching=users_by_presence[Presence.WATCHING],
|
||||
|
@ -73,6 +81,7 @@ async def nojs_chat_form(user):
|
|||
prefer_chat_form = request.args.get('landing') != 'appearance'
|
||||
return await render_template(
|
||||
'nojs_chat_form.html',
|
||||
csp=generate_csp(),
|
||||
user=user,
|
||||
state=state,
|
||||
prefer_chat_form=prefer_chat_form,
|
||||
|
@ -141,7 +150,7 @@ async def nojs_submit_appearance(user):
|
|||
|
||||
# Collect form data
|
||||
name = form.get('name', '').strip()
|
||||
if len(name) == 0 or name == get_default_name(user):
|
||||
if len(name) == 0:
|
||||
name = None
|
||||
|
||||
color = form.get('color', '')
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import asyncio
|
||||
|
||||
from quart import current_app, websocket
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import hashlib
|
||||
import time
|
||||
from functools import wraps
|
||||
|
@ -86,11 +89,16 @@ def with_user_from(context):
|
|||
|
||||
return with_user_from_context
|
||||
|
||||
async def render_template_with_etag(*args, **kwargs):
|
||||
rendered_template = await render_template(*args, **kwargs)
|
||||
tag = hashlib.sha256(rendered_template.encode()).hexdigest()
|
||||
async def render_template_with_etag(template, deferred_kwargs, **kwargs):
|
||||
render = await render_template(template, **kwargs)
|
||||
tag = hashlib.sha256(render.encode()).hexdigest()
|
||||
etag = f'W/"{tag}"'
|
||||
if request.if_none_match.contains_weak(tag):
|
||||
return '', 304, {'ETag': etag}
|
||||
else:
|
||||
rendered_template = await render_template(
|
||||
template,
|
||||
**deferred_kwargs,
|
||||
**kwargs,
|
||||
)
|
||||
return rendered_template, {'ETag': etag}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import time
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
**/
|
||||
|
||||
/* token */
|
||||
const TOKEN = document.body.dataset.token;
|
||||
const TOKEN_HASH = document.body.dataset.tokenHash;
|
||||
|
||||
/* Content Security Policy nonce */
|
||||
const CSP = document.body.dataset.csp;
|
||||
|
||||
/* insert js-only markup */
|
||||
const jsmarkup_style_color = '<style id="style-color"></style>'
|
||||
const jsmarkup_style_tripcode_display = '<style id="style-tripcode-display"></style>'
|
||||
const jsmarkup_style_tripcode_colors = '<style id="style-tripcode-colors"></style>'
|
||||
const jsmarkup_stream = `<video id="stream_js" src="/stream.mp4?token=${encodeURIComponent(TOKEN)}" autoplay controls></video>`
|
||||
const jsmarkup_info = '<div id="info_js" data-js="true"></div>';
|
||||
const jsmarkup_info_float = '<aside id="info_js__float"></aside>';
|
||||
|
@ -25,29 +30,56 @@ const jsmarkup_chat_users = `\
|
|||
const jsmarkup_chat_form = `\
|
||||
<form id="chat-form_js" data-js="true" action="/chat" method="post">
|
||||
<input id="chat-form_js__nonce" type="hidden" name="nonce" value="">
|
||||
<textarea id="chat-form_js__comment" name="comment" maxlength="512" required placeholder="Send a message..." rows="1"></textarea>
|
||||
<textarea id="chat-form_js__comment" name="comment" maxlength="512" required placeholder="Send a message..." rows="1" autofocus></textarea>
|
||||
<div id="chat-live">
|
||||
<span id="chat-live__ball"></span>
|
||||
<span id="chat-live__status"><span>Not connected<span data-verbose='true'> to chat</span></span></span>
|
||||
<span id="chat-live__status">
|
||||
<span data-verbose="true">Not connected to chat</span>
|
||||
<span data-verbose="false">×</span>
|
||||
</span>
|
||||
</div>
|
||||
<input id="chat-form_js__captcha-digest" type="hidden" name="captcha-digest" disabled>
|
||||
<img id="chat-form_js__captcha-image" width="72" height="30">
|
||||
<input id="chat-form_js__captcha-image" type="image" width="72" height="30">
|
||||
<input id="chat-form_js__captcha-answer" name="captcha-answer" placeholder="Captcha" disabled>
|
||||
<input id="chat-form_js__settings" type="image" src="/static/settings.svg" width="28" height="28" alt="Settings">
|
||||
<input id="chat-form_js__submit" type="submit" value="Chat" accesskey="p" disabled>
|
||||
<article id="chat-form_js__notice">
|
||||
<button id="chat-form_js__notice__button" type="button">
|
||||
<header id="chat-form_js__notice__button__header"></header>
|
||||
<small>Click to dismiss</small>
|
||||
</button>
|
||||
</article>
|
||||
</form>
|
||||
<form id="appearance-form_js" data-hidden="">
|
||||
<span id="appearance-form_js__label-name">Name:</span>
|
||||
<input id="appearance-form_js__name" name="name" maxlength="24">
|
||||
<input id="appearance-form_js__color" type="color" name="color">
|
||||
<span id="appearance-form_js__label-tripcode">Tripcode:</span>
|
||||
<input id="appearance-form_js__password" type="password" name="password" placeholder="(tripcode password)" maxlength="1024">
|
||||
<div id="appearance-form_js__row">
|
||||
<article id="appearance-form_js__row__result"></article>
|
||||
<input id="appearance-form_js__row__submit" type="submit" value="Update">
|
||||
</div>
|
||||
</form>`;
|
||||
|
||||
const insert_jsmarkup = () => {jsmarkup_info_float_viewership
|
||||
const insert_jsmarkup = () => {
|
||||
if (document.getElementById("style-color") === null) {
|
||||
const parent = document.head;
|
||||
parent.insertAdjacentHTML("beforeend", jsmarkup_style_color);
|
||||
const style_color = document.createElement("style");
|
||||
style_color.id = "style-color";
|
||||
style_color.nonce = CSP;
|
||||
document.head.insertAdjacentElement("beforeend", style_color);
|
||||
}
|
||||
if (document.getElementById("style-tripcode-display") === null) {
|
||||
const parent = document.head;
|
||||
parent.insertAdjacentHTML("beforeend", jsmarkup_style_tripcode_display);
|
||||
const style_tripcode_display = document.createElement("style");
|
||||
style_tripcode_display.id = "style-tripcode-display";
|
||||
style_tripcode_display.nonce = CSP;
|
||||
document.head.insertAdjacentElement("beforeend", style_tripcode_display);
|
||||
}
|
||||
if (document.getElementById("style-tripcode-colors") === null) {
|
||||
const parent = document.head;
|
||||
parent.insertAdjacentHTML("beforeend", jsmarkup_style_tripcode_colors);
|
||||
const style_tripcode_colors = document.createElement("style");
|
||||
style_tripcode_colors.id = "style-tripcode-colors";
|
||||
style_tripcode_colors.nonce = CSP;
|
||||
document.head.insertAdjacentElement("beforeend", style_tripcode_colors);
|
||||
}
|
||||
if (document.getElementById("stream_js") === null) {
|
||||
const parent = document.getElementById("stream");
|
||||
|
@ -96,6 +128,43 @@ const stylesheet_color = document.styleSheets[1];
|
|||
const stylesheet_tripcode_display = document.styleSheets[2];
|
||||
const stylesheet_tripcode_colors = document.styleSheets[3];
|
||||
|
||||
/* override chat form notice button */
|
||||
const chat_form = document.getElementById("chat-form_js");
|
||||
const chat_form_notice_button = document.getElementById("chat-form_js__notice__button");
|
||||
const chat_form_notice_header = document.getElementById("chat-form_js__notice__button__header");
|
||||
chat_form_notice_button.addEventListener("click", (event) => {
|
||||
chat_form.removeAttribute("data-notice");
|
||||
chat_form_notice_header.innerText = "";
|
||||
});
|
||||
const show_notice = (text) => {
|
||||
chat_form_notice_header.innerText = text;
|
||||
chat_form.dataset.notice = "";
|
||||
}
|
||||
|
||||
/* override chat form settings input */
|
||||
const chat_appearance_form = document.getElementById("appearance-form_js");
|
||||
const chat_appearance_form_result = document.getElementById("appearance-form_js__row__result");
|
||||
const chat_form_settings = document.getElementById("chat-form_js__settings");
|
||||
chat_form_settings.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
if (chat_appearance_form.dataset.hidden === undefined) {
|
||||
chat_appearance_form.dataset.hidden = "";
|
||||
chat_form_settings.style.backgroundColor = "";
|
||||
chat_appearance_form_result.innerText = "";
|
||||
if (!chat_appearance_form_submit.disabled) {
|
||||
chat_appearance_form.reset();
|
||||
}
|
||||
} else {
|
||||
chat_appearance_form.removeAttribute("data-hidden");
|
||||
chat_form_settings.style.backgroundColor = "#4f4f53";
|
||||
}
|
||||
});
|
||||
|
||||
/* appearance form */
|
||||
const chat_appearance_form_name = document.getElementById("appearance-form_js__name");
|
||||
const chat_appearance_form_color = document.getElementById("appearance-form_js__color");
|
||||
const chat_appearance_form_password = document.getElementById("appearance-form_js__password");
|
||||
|
||||
/* create websocket */
|
||||
const info_title = document.getElementById("info_js__title");
|
||||
const info_viewership = document.getElementById("info_js__float__viewership");
|
||||
|
@ -120,11 +189,7 @@ const create_chat_message = (object) => {
|
|||
chat_message_time.title = `${object.date} ${object.time_seconds}`;
|
||||
chat_message_time.innerText = object.time_minutes;
|
||||
|
||||
const [
|
||||
chat_message_name,
|
||||
chat_message_tripcode_nbsp,
|
||||
chat_message_tripcode,
|
||||
] = create_chat_user_components(user);
|
||||
const chat_message_user_components = create_chat_user_components(user);
|
||||
|
||||
const chat_message_markup = document.createElement("span");
|
||||
chat_message_markup.classList.add("chat-message__markup");
|
||||
|
@ -132,9 +197,9 @@ const create_chat_message = (object) => {
|
|||
|
||||
chat_message.insertAdjacentElement("beforeend", chat_message_time);
|
||||
chat_message.insertAdjacentHTML("beforeend", " ");
|
||||
chat_message.insertAdjacentElement("beforeend", chat_message_name);
|
||||
chat_message.insertAdjacentElement("beforeend", chat_message_tripcode_nbsp);
|
||||
chat_message.insertAdjacentElement("beforeend", chat_message_tripcode);
|
||||
for (const chat_message_user_component of chat_message_user_components) {
|
||||
chat_message.insertAdjacentElement("beforeend", chat_message_user_component);
|
||||
}
|
||||
chat_message.insertAdjacentHTML("beforeend", ": ");
|
||||
chat_message.insertAdjacentElement("beforeend", chat_message_markup);
|
||||
|
||||
|
@ -146,9 +211,11 @@ const create_chat_user_name = (user) => {
|
|||
chat_user_name.innerText = get_user_name({user});
|
||||
//chat_user_name.dataset.color = user.color; // not working in any browser
|
||||
if (!user.broadcaster && user.name === null) {
|
||||
const b = document.createElement("b");
|
||||
b.innerText = user.tag;
|
||||
const chat_user_name_tag = document.createElement("sup");
|
||||
chat_user_name_tag.classList.add("chat-name__tag");
|
||||
chat_user_name_tag.innerText = user.tag;
|
||||
chat_user_name_tag.innerHTML = b.outerHTML;
|
||||
chat_user_name.insertAdjacentElement("beforeend", chat_user_name_tag);
|
||||
}
|
||||
return chat_user_name;
|
||||
|
@ -167,7 +234,20 @@ const create_chat_user_components = (user) => {
|
|||
chat_user_tripcode.innerHTML = user.tripcode.digest;
|
||||
}
|
||||
|
||||
return [chat_user_name, chat_user_tripcode_nbsp, chat_user_tripcode];
|
||||
let result;
|
||||
if (!user.broadcaster) {
|
||||
result = [];
|
||||
} else {
|
||||
const chat_user_insignia = document.createElement("b");
|
||||
chat_user_insignia.classList.add("chat-insignia")
|
||||
chat_user_insignia.title = "Broadcaster";
|
||||
chat_user_insignia.innerText = "##";
|
||||
const chat_user_insignia_nbsp = document.createElement("span");
|
||||
chat_user_insignia_nbsp.innerHTML = " "
|
||||
result = [chat_user_insignia, chat_user_insignia_nbsp];
|
||||
}
|
||||
result.push(...[chat_user_name, chat_user_tripcode_nbsp, chat_user_tripcode]);
|
||||
return result;
|
||||
}
|
||||
const create_and_add_chat_message = (object) => {
|
||||
const chat_message = create_chat_message(object);
|
||||
|
@ -283,26 +363,26 @@ const update_user_tripcodes = (token_hash=null) => {
|
|||
for (const this_token_hash of token_hashes) {
|
||||
const tripcode = users[this_token_hash].tripcode;
|
||||
if (tripcode === null) {
|
||||
if (!to_ignore_display.has(token_hash)) {
|
||||
if (!to_ignore_display.has(this_token_hash)) {
|
||||
stylesheet_tripcode_display.insertRule(
|
||||
`[data-token-hash="${this_token_hash}"] > .for-tripcode { display: none; }`,
|
||||
stylesheet_tripcode_display.cssRules.length,
|
||||
);
|
||||
}
|
||||
if (!to_ignore_colors.has(token_hash)) {
|
||||
if (!to_ignore_colors.has(this_token_hash)) {
|
||||
stylesheet_tripcode_colors.insertRule(
|
||||
`[data-token-hash="${this_token_hash}"] > .tripcode { background-color: initial; color: initial; }`,
|
||||
stylesheet_tripcode_colors.cssRules.length,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (!to_ignore_display.has(token_hash)) {
|
||||
if (!to_ignore_display.has(this_token_hash)) {
|
||||
stylesheet_tripcode_display.insertRule(
|
||||
`[data-token-hash="${this_token_hash}"] > .for-tripcode { display: inline; }`,
|
||||
stylesheet_tripcode_display.cssRules.length,
|
||||
);
|
||||
}
|
||||
if (!to_ignore_colors.has(token_hash)) {
|
||||
if (!to_ignore_colors.has(this_token_hash)) {
|
||||
stylesheet_tripcode_colors.insertRule(
|
||||
`[data-token-hash="${this_token_hash}"] > .tripcode { background-color: ${tripcode.background_color}; color: ${tripcode.foreground_color}; }`,
|
||||
stylesheet_tripcode_colors.cssRules.length,
|
||||
|
@ -349,16 +429,16 @@ chat_form_captcha_image.addEventListener("error", (event) => {
|
|||
chat_form_captcha_image.title = "Click for a new captcha";
|
||||
});
|
||||
chat_form_captcha_image.addEventListener("click", (event) => {
|
||||
if (chat_form_captcha_image.dataset.reloadable === undefined) {
|
||||
return;
|
||||
event.preventDefault();
|
||||
if (chat_form_captcha_image.dataset.reloadable !== undefined) {
|
||||
chat_form_submit.disabled = true;
|
||||
chat_form_captcha_image.alt = "Waiting...";
|
||||
chat_form_captcha_image.removeAttribute("title");
|
||||
chat_form_captcha_image.removeAttribute("data-reloadable");
|
||||
chat_form_captcha_image.removeAttribute("src");
|
||||
const payload = {type: "captcha"};
|
||||
ws.send(JSON.stringify(payload));
|
||||
}
|
||||
chat_form_submit.disabled = true;
|
||||
chat_form_captcha_image.alt = "Waiting...";
|
||||
chat_form_captcha_image.removeAttribute("title");
|
||||
chat_form_captcha_image.removeAttribute("data-reloadable");
|
||||
chat_form_captcha_image.removeAttribute("src");
|
||||
const payload = {type: "captcha"};
|
||||
ws.send(JSON.stringify(payload));
|
||||
});
|
||||
const enable_captcha = (digest) => {
|
||||
chat_form_captcha_digest.value = digest;
|
||||
|
@ -386,9 +466,9 @@ const disable_captcha = () => {
|
|||
}
|
||||
|
||||
const set_title = (title) => {
|
||||
const element = document.createElement("h1");
|
||||
element.innerText = title.replaceAll(/\r?\n/g, " ");
|
||||
info_title.innerHTML = element.outerHTML;
|
||||
const h1 = document.createElement("h1");
|
||||
h1.innerText = title.replaceAll(/\r?\n/g, " ");
|
||||
info_title.innerHTML = h1.outerHTML;
|
||||
}
|
||||
|
||||
const update_uptime = () => {
|
||||
|
@ -493,6 +573,7 @@ const on_websocket_message = (event) => {
|
|||
case "error":
|
||||
console.log("ws error", receipt);
|
||||
chat_form_submit.disabled = false;
|
||||
chat_appearance_form_submit.disabled = false;
|
||||
break;
|
||||
|
||||
case "init":
|
||||
|
@ -546,6 +627,14 @@ const on_websocket_message = (event) => {
|
|||
update_user_tripcodes();
|
||||
update_users_list()
|
||||
|
||||
// appearance form default values
|
||||
const user = users[TOKEN_HASH];
|
||||
if (user.name !== null) {
|
||||
chat_appearance_form_name.setAttribute("value", user.name);
|
||||
}
|
||||
chat_appearance_form_name.setAttribute("placeholder", default_name[user.broadcaster]);
|
||||
chat_appearance_form_color.setAttribute("value", user.color);
|
||||
|
||||
// insert new messages
|
||||
const last = chat_messages.children.length == 0 ? null : chat_messages.children[chat_messages.children.length - 1];
|
||||
const last_seq = last === null ? null : parseInt(last.dataset.seq);
|
||||
|
@ -583,6 +672,11 @@ const on_websocket_message = (event) => {
|
|||
|
||||
case "ack":
|
||||
console.log("ws ack", receipt);
|
||||
|
||||
if (receipt.notice !== null) {
|
||||
show_notice(receipt.notice);
|
||||
}
|
||||
|
||||
const existing_nonce = chat_form_nonce.value;
|
||||
if (receipt.clear && receipt.nonce === existing_nonce) {
|
||||
chat_form_comment.value = "";
|
||||
|
@ -592,6 +686,7 @@ const on_websocket_message = (event) => {
|
|||
chat_form_nonce.value = receipt.next;
|
||||
receipt.digest === null ? disable_captcha() : enable_captcha(receipt.digest);
|
||||
chat_form_submit.disabled = false;
|
||||
|
||||
break;
|
||||
|
||||
case "message":
|
||||
|
@ -630,6 +725,41 @@ const on_websocket_message = (event) => {
|
|||
receipt.digest === null ? disable_captcha() : enable_captcha(receipt.digest);
|
||||
break;
|
||||
|
||||
case "appearance":
|
||||
console.log("ws appearance", receipt);
|
||||
|
||||
if (receipt.errors === undefined) {
|
||||
if (receipt.name !== null) {
|
||||
chat_appearance_form_name.setAttribute("value", receipt.name);
|
||||
}
|
||||
chat_appearance_form_color.setAttribute("value", receipt.color);
|
||||
chat_appearance_form_result.innerHTML = receipt.result;
|
||||
} else {
|
||||
const ul = document.createElement("ul");
|
||||
for (const error of receipt.errors) {
|
||||
const li = document.createElement("li");
|
||||
li.innerText = error[0];
|
||||
for (const tuple of error.slice(1)) {
|
||||
const mark = document.createElement("mark");
|
||||
mark.innerText = tuple[0];
|
||||
li.insertAdjacentText("beforeend", " ");
|
||||
li.insertAdjacentElement("beforeend", mark);
|
||||
li.insertAdjacentText("beforeend", tuple[1]);
|
||||
}
|
||||
ul.insertAdjacentElement("beforeend", li);
|
||||
}
|
||||
const result = document.createElement("div");
|
||||
result.innerText = "Errors:";
|
||||
result.insertAdjacentElement("beforeend", ul);
|
||||
chat_appearance_form_result.innerHTML = result.innerHTML;
|
||||
}
|
||||
|
||||
chat_appearance_form_submit.disabled = false;
|
||||
chat_appearance_form.removeAttribute("data-hidden");
|
||||
chat_form_settings.style.backgroundColor = "#4f4f53";
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("incomprehensible websocket message", receipt);
|
||||
}
|
||||
|
@ -644,13 +774,13 @@ const connect_websocket = () => {
|
|||
return;
|
||||
}
|
||||
chat_live_ball.style.borderColor = "gold";
|
||||
chat_live_status.innerHTML = "<span data-verbose='false'>Waiting...</span> <span data-verbose='true'>Connecting to chat...</span>";
|
||||
chat_live_status.innerHTML = "<span data-verbose='true'>Connecting to chat...</span><span data-verbose='false'>···</span>";
|
||||
ws = new WebSocket(`ws://${document.domain}:${location.port}/live?token=${encodeURIComponent(TOKEN)}`);
|
||||
ws.addEventListener("open", (event) => {
|
||||
console.log("websocket open", event);
|
||||
chat_form_submit.disabled = false;
|
||||
chat_live_ball.style.borderColor = "green";
|
||||
chat_live_status.innerHTML = "<span>Connected<span data-verbose='true'> to chat</span></span>";
|
||||
chat_live_status.innerHTML = "<span><span data-verbose='true'>Connected to chat</span><span data-verbose='false'>✓</span></span>";
|
||||
// When the server is offline, a newly opened websocket can take a second
|
||||
// to close. This timeout tries to ensure the backoff doesn't instantly
|
||||
// (erroneously) reset to 2 seconds in that case.
|
||||
|
@ -666,7 +796,7 @@ const connect_websocket = () => {
|
|||
console.log("websocket close", event);
|
||||
chat_form_submit.disabled = true;
|
||||
chat_live_ball.style.borderColor = "maroon";
|
||||
chat_live_status.innerHTML = "<span data-verbose='false'>Failed to connect</span> <span data-verbose='true'>Disconnected from chat</span>";
|
||||
chat_live_status.innerHTML = "<span data-verbose='true'>Disconnected from chat</span><span data-verbose='false'>×</span>";
|
||||
if (!ws.successor) {
|
||||
ws.successor = true;
|
||||
setTimeout(connect_websocket, websocket_backoff);
|
||||
|
@ -698,7 +828,6 @@ stream.addEventListener("error", (event) => {
|
|||
});
|
||||
|
||||
/* override js-only chat form */
|
||||
const chat_form = document.getElementById("chat-form_js");
|
||||
const chat_form_nonce = document.getElementById("chat-form_js__nonce");
|
||||
const chat_form_comment = document.getElementById("chat-form_js__comment");
|
||||
const chat_form_submit = document.getElementById("chat-form_js__submit");
|
||||
|
@ -710,6 +839,18 @@ chat_form.addEventListener("submit", (event) => {
|
|||
ws.send(JSON.stringify(payload));
|
||||
});
|
||||
|
||||
/* override js-only appearance form */
|
||||
const chat_appearance_form_submit = document.getElementById("appearance-form_js__row__submit");
|
||||
chat_appearance_form.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
const form = Object.fromEntries(new FormData(chat_appearance_form));
|
||||
const payload = {type: "appearance", form: form};
|
||||
chat_appearance_form_submit.disabled = true;
|
||||
chat_appearance_form_password.value = "";
|
||||
chat_appearance_form_result.innerText = "";
|
||||
ws.send(JSON.stringify(payload));
|
||||
});
|
||||
|
||||
/* when chat is being resized, peg its bottom in place (instead of its top) */
|
||||
const track_scroll = (element) => {
|
||||
chat_messages.dataset.scrollTop = chat_messages.scrollTop;
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2016 ulimicon [https://thenounproject.com/unlimicon/]
|
||||
SPDX-License-Identifier: CC-BY-3.0
|
||||
-->
|
||||
<svg width="243.55pt" height="243.55pt" version="1.1" viewBox="0 0 243.55 243.55" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m104.65 0-8.2461 29.598c-7.6914 2.1172-14.992 5.2305-21.777 9.0898l-27.062-15.012-23.891 23.891 15.012 27.062c-3.8594 6.7852-6.9727 14.086-9.0898 21.777l-29.598 8.2461v34.25l29.598 8.2461c2.1172 7.6914 5.2305 14.992 9.0898 21.777l-15.012 27.062 23.891 23.891 27.062-15.012c6.7852 3.8594 14.086 6.9727 21.777 9.0898l8.2461 29.598h34.25l8.2461-29.598c7.6914-2.1172 14.992-5.2305 21.777-9.0898l27.062 15.012 23.891-23.891-15.012-27.062c3.8594-6.7812 6.9727-14.086 9.0898-21.777l29.598-8.2461v-34.25l-29.598-8.2461c-2.1172-7.6914-5.2305-14.992-9.0898-21.777l15.012-27.062-23.891-23.891-27.062 15.012c-6.7852-3.8594-14.086-6.9727-21.777-9.0898l-8.2461-29.598zm17.125 74.418c26.156 0 47.359 21.203 47.359 47.359s-21.203 47.359-47.359 47.359-47.359-21.203-47.359-47.359 21.203-47.359 47.359-47.359z" fill="#bbbbbf"/>
|
||||
</svg>
|
変更後 幅: | 高さ: | サイズ: 1.1 KiB |
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
**/
|
||||
|
||||
:root {
|
||||
--text-color: #ddd;
|
||||
|
||||
|
@ -107,9 +112,7 @@ noscript {
|
|||
#chat__toggle {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: calc(0.5rem + 1px);
|
||||
left: calc(0.5rem + 4px);
|
||||
margin: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
#chat__toggle:checked ~ #chat__body > #chat__body__messages,
|
||||
#chat__toggle:not(:checked) ~ #chat__body > #chat__body__users {
|
||||
|
@ -192,6 +195,10 @@ noscript {
|
|||
.chat-message__time {
|
||||
color: #b2b2b3;
|
||||
font-size: 10pt;
|
||||
cursor: default;
|
||||
}
|
||||
.chat-insignia {
|
||||
text-shadow: 0 0 2px orangered;
|
||||
cursor: help;
|
||||
}
|
||||
.chat-name {
|
||||
|
@ -256,18 +263,19 @@ noscript {
|
|||
#chat-users_nojs {
|
||||
height: 100%;
|
||||
}
|
||||
#chat__form {
|
||||
position: relative;
|
||||
}
|
||||
#chat-form_js {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr min-content min-content 5rem;
|
||||
grid-template-columns: 1fr min-content min-content min-content 5rem;
|
||||
grid-template-rows: auto var(--button-height);
|
||||
grid-gap: 0.375rem;
|
||||
margin: 0 0.5rem 0.5rem 0.5rem;
|
||||
}
|
||||
#chat-form_js__submit {
|
||||
grid-column: 2 / span 1;
|
||||
padding: 0 0.5rem 0.5rem 0.5rem;
|
||||
position: relative;
|
||||
}
|
||||
#chat-form_js__comment {
|
||||
grid-column: 1 / span 4;
|
||||
grid-column: 1 / span 5;
|
||||
background-color: #434347;
|
||||
border-radius: 4px;
|
||||
border: 2px solid transparent;
|
||||
|
@ -296,16 +304,100 @@ noscript {
|
|||
#chat-form_js__captcha-answer {
|
||||
width: 8ch;
|
||||
}
|
||||
#chat-form_js__submit {
|
||||
#chat-form_js__settings {
|
||||
align-self: center;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
color: var(--text-color);
|
||||
grid-column: 4;
|
||||
}
|
||||
#chat-form_js__settings:hover {
|
||||
background-color: #434347;
|
||||
}
|
||||
#chat-form_js__submit {
|
||||
grid-column: 5;
|
||||
}
|
||||
#chat-form_js:not([data-captcha]) > #chat-form_js__captcha-image,
|
||||
#chat-form_js:not([data-captcha]) > #chat-form_js__captcha-answer {
|
||||
display: none;
|
||||
}
|
||||
#chat-form_js:not([data-notice]) > #chat-form_js__notice {
|
||||
display: none;
|
||||
}
|
||||
#chat-form_js__notice {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
background: linear-gradient(#23232700, #2323277f 8%, #232327);
|
||||
height: 100%;
|
||||
display: grid;
|
||||
z-index: 1;
|
||||
}
|
||||
#chat-form_js__notice__button {
|
||||
color: inherit;
|
||||
border-color: black;
|
||||
background-color: #232327;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
display: grid;
|
||||
grid-gap: 0.375rem;
|
||||
padding: 0.625rem 1.25rem;
|
||||
box-shadow: 0 0 12px black;
|
||||
cursor: pointer;
|
||||
}
|
||||
#chat-form_js__notice__button__header {
|
||||
font-size: 14pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
#chat-form_nojs {
|
||||
height: 13ch;
|
||||
}
|
||||
#appearance-form_js {
|
||||
position: absolute;
|
||||
bottom: 3rem;
|
||||
padding: 0.5rem;
|
||||
margin: 0 1rem;
|
||||
width: calc(100% - 2rem);
|
||||
box-sizing: border-box;
|
||||
background: #343437df;
|
||||
border: 2px outset #434347;
|
||||
border-radius: 4px;
|
||||
display: grid;
|
||||
grid-template-columns: min-content 1fr min-content;
|
||||
grid-template-rows: 1fr 1fr auto;
|
||||
grid-gap: 0.375rem;
|
||||
}
|
||||
#appearance-form_js[data-hidden] {
|
||||
display: none;
|
||||
}
|
||||
#appearance-form_js__label-name,
|
||||
#appearance-form_js__label-tripcode {
|
||||
align-self: center;
|
||||
}
|
||||
#appearance-form_js__name,
|
||||
#appearance-form_js__password {
|
||||
min-width: 12ch;
|
||||
}
|
||||
#appearance-form_js__row {
|
||||
grid-column: 1 / span 3;
|
||||
grid-row: 3;
|
||||
display: grid;
|
||||
grid-template-columns: auto 4rem;
|
||||
align-items: end;
|
||||
}
|
||||
#appearance-form_js__row__result {
|
||||
font-weight: bold;
|
||||
font-size: 11pt;
|
||||
}
|
||||
#appearance-form_js__row__result > ul {
|
||||
margin: 0;
|
||||
padding-left: 1.125rem;
|
||||
font-size: 10pt;
|
||||
}
|
||||
#appearance-form_js__row__submit {
|
||||
min-height: 1.75rem;
|
||||
}
|
||||
#chat-live {
|
||||
position: relative;
|
||||
font-size: 9pt;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import itertools
|
||||
import operator
|
||||
import time
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import asyncio
|
||||
import itertools
|
||||
from functools import wraps
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
{##
|
||||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
##}
|
||||
<!doctype html>
|
||||
<html id="nochat">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="content-security-policy" content="default-src 'none'; connect-src 'self'; img-src 'self'; frame-src 'self'; media-src 'self'; script-src 'self'; style-src 'self' 'nonce-{{ csp }}';">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css">
|
||||
</head>
|
||||
<body id="both" data-token="{{ user.token }}" data-token-hash="{{ user.token_hash }}">
|
||||
<body id="both" data-token="{{ user.token }}" data-token-hash="{{ user.token_hash }}" data-csp="{{ csp }}">
|
||||
<article id="stream">
|
||||
<noscript><iframe id="stream_nojs" name="stream_nojs" src="{{ url_for('nojs_stream', token=user.token) }}"></iframe></noscript>
|
||||
</article>
|
||||
|
@ -36,7 +41,7 @@
|
|||
<a href="#chat">chat</a>
|
||||
<a href="#both">both</a>
|
||||
</nav>
|
||||
<footer>anonstream pre-1.0.0 — <a href="https://git.076.ne.jp/ninya9k/anonstream" target="_blank">source</a></footer>
|
||||
<footer>anonstream 1.0.0 — <a href="https://git.076.ne.jp/ninya9k/anonstream" target="_blank">source</a></footer>
|
||||
<script src="{{ url_for('static', filename='anonstream.js') }}" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
{##
|
||||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
##}
|
||||
{%
|
||||
macro appearance(
|
||||
user,
|
||||
insignia_class,
|
||||
name_class,
|
||||
tag_class,
|
||||
tripcode_nbsp_class='for-tripcode',
|
||||
|
@ -8,15 +13,19 @@
|
|||
)
|
||||
%}
|
||||
{{- '' -}}
|
||||
<span class="{{ name_class }}" style="color:{{ user.color }};">
|
||||
{%- if user.broadcaster -%}
|
||||
<b class="{{ insignia_class }}" title="Broadcaster">##</b>
|
||||
{{- ' ' | safe -}}
|
||||
{%- endif -%}
|
||||
<span class="{{ name_class }}">
|
||||
{{- user.name or get_default_name(user) -}}
|
||||
{%- if not user.broadcaster and user.name is none -%}
|
||||
<sup class="{{ tag_class }}">{{ user.tag }}</sup>
|
||||
<sup class="{{ tag_class }}"><b>{{ user.tag }}</b></sup>
|
||||
{%- endif -%}
|
||||
</span>
|
||||
{%- if user.tripcode -%}
|
||||
<span class="{{ tripcode_nbsp_class }}"> </span>
|
||||
{{- '' -}}
|
||||
<span class="{{ tripcode_class }}" style="background-color:{{ user.tripcode.background_color }};color:{{ user.tripcode.foreground_color }};">{{ user.tripcode.digest }}</span>
|
||||
<span class="{{ tripcode_class }}">{{ user.tripcode.digest }}</span>
|
||||
{%- endif -%}
|
||||
{% endmacro %}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
{##
|
||||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
##}
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
<meta http-equiv="content-security-policy" content="default-src 'none'; img-src 'self'; style-src 'nonce-{{ csp }}';">
|
||||
<style nonce="{{ csp }}">
|
||||
:root {
|
||||
--link-color: #42a5d7;
|
||||
--padding-size: 0.5rem;
|
||||
|
@ -41,6 +46,8 @@
|
|||
padding: 0;
|
||||
}
|
||||
#tripcode {
|
||||
background-color: {{ user.tripcode.background_color }};
|
||||
color: {{ user.tripcode.foreground_color }};
|
||||
cursor: pointer;
|
||||
}
|
||||
.x {
|
||||
|
@ -56,6 +63,7 @@
|
|||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
#notice h2 {
|
||||
margin: 0;
|
||||
|
@ -73,7 +81,7 @@
|
|||
grid-gap: 0.375rem;
|
||||
}
|
||||
#chat-form__exit,
|
||||
#appearance-form__exit,
|
||||
#appearance-form__buttons__exit,
|
||||
#appearance-form__label-name,
|
||||
#appearance-form__label-password {
|
||||
font-size: 11pt;
|
||||
|
@ -120,6 +128,7 @@
|
|||
}
|
||||
|
||||
#appearance-form {
|
||||
display: grid;
|
||||
grid-auto-rows: 1fr 1fr 2rem;
|
||||
grid-auto-columns: min-content 1fr min-content;
|
||||
}
|
||||
|
@ -172,46 +181,48 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
#appearance-form {
|
||||
#toggle {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
#chat-form__exit > label,
|
||||
#appearance-form__buttons__exit > label {
|
||||
padding: 1px;
|
||||
}
|
||||
#toggle:focus-visible ~ #chat-form > #chat-form__exit > label,
|
||||
#toggle:focus-visible ~ #appearance-form #appearance-form__buttons__exit > label {
|
||||
padding: 0;
|
||||
border: 1px dotted;
|
||||
}
|
||||
#notice-radio {
|
||||
display: none;
|
||||
}
|
||||
#appearance:target ~ #appearance-form {
|
||||
display: grid;
|
||||
}
|
||||
#appearance:target ~ #chat-form {
|
||||
#toggle:checked ~ #chat-form,
|
||||
#toggle:not(:checked) ~ #appearance-form {
|
||||
display: none;
|
||||
}
|
||||
#chat:target ~ #appearance-form {
|
||||
#notice-radio:checked + #notice,
|
||||
#notice-radio:not(:checked) ~ #chat-form,
|
||||
#notice-radio:not(:checked) ~ #appearance-form {
|
||||
display: none;
|
||||
}
|
||||
{% if state.notice %}
|
||||
#chat-form {
|
||||
display: none;
|
||||
}
|
||||
#chat:target ~ #chat-form {
|
||||
display: grid;
|
||||
}
|
||||
#chat:target ~ #notice,
|
||||
#appearance:target ~ #notice {
|
||||
display: none;
|
||||
}
|
||||
{% endif %}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="chat"></div>
|
||||
<div id="appearance"></div>
|
||||
<input id="toggle" type="checkbox" {% if not prefer_chat_form %}checked {% endif %}accesskey="x">
|
||||
{% if state.notice %}
|
||||
<a id="notice" {% if state.verbose %}class="verbose" {% endif %}{% if prefer_chat_form %}href="#chat"{% else %}href="#appearance"{% endif %}>
|
||||
<input id="notice-radio" type="radio" accesskey="z">
|
||||
<label id="notice" for="notice-radio"{% if state.verbose %} class="verbose"{% endif %}>
|
||||
<header><h2>{{ state.notice }}</h2></header>
|
||||
<small>Click to dismiss</small>
|
||||
</a>
|
||||
</label>
|
||||
{% endif %}
|
||||
<form id="chat-form" action="{{ url_for('nojs_submit_message', token=user.token) }}" method="post">
|
||||
<input type="hidden" name="nonce" value="{{ nonce }}">
|
||||
<textarea id="chat-form__comment" name="comment" maxlength="512" {% if digest is none %}required {% endif %} placeholder="Send a message..." rows="1" tabindex="1">{{ state.comment }}</textarea>
|
||||
<textarea id="chat-form__comment" name="comment" maxlength="512" {% if digest is none %}required {% endif %} placeholder="Send a message..." rows="1" tabindex="1" autofocus accesskey="m">{{ state.comment }}</textarea>
|
||||
<input id="chat-form__submit" type="submit" value="Chat" tabindex="4" accesskey="p">
|
||||
<div id="chat-form__exit"><a href="#appearance">Settings</a></div>
|
||||
<div id="chat-form__exit"><label for="toggle" class="pseudolink">Settings</label></div>
|
||||
{% if digest %}
|
||||
<input type="hidden" name="captcha-digest" value="{{ digest }}">
|
||||
<input id="chat-form__captcha-image" type="image" formaction="{{ url_for('nojs_chat_form_redirect', token=user.token) }}" formnovalidate src="{{ url_for('captcha', token=user.token, digest=digest) }}" width="72" height="30" alt="Captcha failed to load" title="Click for a new captcha" tabindex="2">
|
||||
|
@ -226,11 +237,11 @@
|
|||
<input id="password-toggle" name="set-tripcode" type="checkbox" accesskey="s">
|
||||
<input id="cleared-toggle" name="clear-tripcode" type="checkbox"{% if user.tripcode != none %} accesskey="c"{% endif %}>
|
||||
<div id="password-column">
|
||||
{% if user.tripcode == none %}
|
||||
{% if user.tripcode is none %}
|
||||
<span class="tripcode">(no tripcode)</span>
|
||||
<label for="password-toggle" class="show-password pseudolink">set</label>
|
||||
{% else %}
|
||||
<label id="tripcode" for="password-toggle" class="show-password tripcode" style="background-color:{{ user.tripcode.background_color }};color:{{ user.tripcode.foreground_color }};">{{ user.tripcode.digest }}</label>
|
||||
<label id="tripcode" for="password-toggle" class="show-password tripcode">{{ user.tripcode.digest }}</label>
|
||||
<label id="show-cleared" for="cleared-toggle" class="pseudolink x">✗</label>
|
||||
<div id="cleared" class="tripcode">(cleared)</div>
|
||||
<label id="hide-cleared" for="cleared-toggle" class="pseudolink">undo</label>
|
||||
|
@ -239,7 +250,7 @@
|
|||
<input id="appearance-form__password" name="password" type="password" placeholder="(tripcode password)" maxlength="1024">
|
||||
<div id="hide-password"><label for="password-toggle" class="pseudolink x">✗</label></div>
|
||||
<div id="appearance-form__buttons">
|
||||
<div id="appearance-form__exit"><a href="#chat">Return to chat</a></div>
|
||||
<div id="appearance-form__buttons__exit"><label for="toggle" class="pseudolink">Return to chat</label></div>
|
||||
<input type="submit" value="Update">
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
{##
|
||||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
##}
|
||||
{% from 'macros/user.html' import appearance with context %}
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="content-security-policy" content="default-src 'none'; style-src 'nonce-{{ csp }}';">
|
||||
<meta http-equiv="refresh" content="4">
|
||||
<meta http-equiv="refresh" content="5; url={{ url_for('nojs_chat_messages_redirect', token=user.token) }}">
|
||||
<style>
|
||||
<style nonce="{{ csp }}">
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -100,6 +105,10 @@
|
|||
.chat-message__time {
|
||||
color: #b2b2b3;
|
||||
font-size: 10pt;
|
||||
cursor: default;
|
||||
}
|
||||
.chat-message__insignia {
|
||||
text-shadow: 0 0 2px orangered;
|
||||
cursor: help;
|
||||
}
|
||||
.chat-message__name {
|
||||
|
@ -124,6 +133,20 @@
|
|||
font-size: 9pt;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
{% for token in messages | map(attribute='token') | list | unique %}
|
||||
{% with user = users_by_token[token] %}
|
||||
[data-token-hash="{{ user.token_hash }}"] > .chat-message__name {
|
||||
color: {{ user.color }};
|
||||
}
|
||||
{% if user.tripcode %}
|
||||
[data-token-hash="{{ user.token_hash }}"] > .tripcode {
|
||||
background-color: {{ user.tripcode.background_color }};
|
||||
color: {{ user.tripcode.foreground_color }};
|
||||
}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -141,7 +164,7 @@
|
|||
{% with user = users_by_token[message.token] %}
|
||||
<time class="chat-message__time" datetime="{{ message.date }}T{{ message.time_seconds }}Z" title="{{ message.date }} {{ message.time_seconds }}">{{ message.time_minutes }}</time>
|
||||
{{- ' ' | safe -}}
|
||||
{{ appearance(user, name_class='chat-message__name', tag_class='chat-message__name__tag') }}
|
||||
{{ appearance(user, insignia_class='chat-message__insignia', name_class='chat-message__name', tag_class='chat-message__name__tag') }}
|
||||
{{- ': ' -}}
|
||||
<span class="chat-message__markup">{{ message.markup }}</span>
|
||||
{% endwith %}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
{##
|
||||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
##}
|
||||
{% from 'macros/user.html' import appearance with context %}
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="content-security-policy" content="default-src 'none'; style-src 'nonce-{{ csp }}';">
|
||||
<meta http-equiv="refresh" content="6">
|
||||
<style>
|
||||
<style nonce="{{ csp }}">
|
||||
html {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
@ -61,6 +66,9 @@
|
|||
.user {
|
||||
line-height: 1.4375;
|
||||
}
|
||||
.user__insignia {
|
||||
cursor: help;
|
||||
}
|
||||
.user__name {
|
||||
font-weight: bold;
|
||||
cursor: default;
|
||||
|
@ -77,6 +85,18 @@
|
|||
font-size: 9pt;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
{% for user in users_watching + users_notwatching %}
|
||||
[data-token-hash="{{ user.token_hash }}"] > .user__name {
|
||||
color: {{ user.color }};
|
||||
}
|
||||
{% if user.tripcode %}
|
||||
[data-token-hash="{{ user.token_hash }}"] > .tripcode {
|
||||
background-color: {{ user.tripcode.background_color }};
|
||||
color: {{ user.tripcode.foreground_color }};
|
||||
}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -90,8 +110,8 @@
|
|||
<h5>Watching ({{ users_watching | length }})</h5>
|
||||
<ul>
|
||||
{% for user_listed in users_watching %}
|
||||
<li class="user">
|
||||
{{- appearance(user_listed, name_class='user__name', tag_class='user__name__tag') -}}
|
||||
<li class="user" data-token-hash="{{ user.token_hash }}">
|
||||
{{- appearance(user_listed, insignia_class='user__insignia', name_class='user__name', tag_class='user__name__tag') -}}
|
||||
{%- if user.token == user_listed.token %} (You){% endif -%}
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
@ -100,8 +120,8 @@
|
|||
<h5>Not watching ({{ users_notwatching | length }})</h5>
|
||||
<ul>
|
||||
{% for user_listed in users_notwatching %}
|
||||
<li class="user">
|
||||
{{- appearance(user_listed, name_class='user__name', tag_class='user__name__tag') -}}
|
||||
<li class="user" data-token-hash="{{ user.token_hash }}">
|
||||
{{- appearance(user_listed, insignia_class='user__insignia', name_class='user__name', tag_class='user__name__tag') -}}
|
||||
{%- if user.token == user_listed.token %} (You){% endif -%}
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
{##
|
||||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
##}
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="content-security-policy" content="default-src 'none'; style-src 'nonce-{{ csp }}';">
|
||||
<meta http-equiv="refresh" content="6">
|
||||
<style>
|
||||
<style nonce="{{ csp }}">
|
||||
body {
|
||||
overflow-y: auto;
|
||||
margin: 0.75ch 1.25ch;
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
{##
|
||||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
##}
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
<meta http-equiv="content-security-policy" content="default-src 'none'; media-src 'self'; style-src 'nonce-{{ csp }}';">
|
||||
<style nonce="{{ csp }}">
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import time
|
||||
from math import inf
|
||||
|
||||
from quart import current_app
|
||||
|
||||
from anonstream.wrappers import try_except_log, with_timestamp
|
||||
from anonstream.helpers.user import get_presence, Presence
|
||||
from anonstream.helpers.user import get_default_name, get_presence, Presence
|
||||
from anonstream.helpers.captcha import check_captcha_digest, Answer
|
||||
from anonstream.helpers.tripcode import generate_tripcode
|
||||
from anonstream.utils.colour import color_to_colour, get_contrast, NotAColor
|
||||
|
@ -69,6 +72,8 @@ def try_change_appearance(user, name, color, password, want_tripcode):
|
|||
|
||||
def change_name(user, name, dry_run=False):
|
||||
if dry_run:
|
||||
if name == get_default_name(user):
|
||||
name = None
|
||||
if name is not None:
|
||||
if len(name) == 0:
|
||||
raise BadAppearance('Name was empty')
|
||||
|
@ -91,7 +96,7 @@ def change_color(user, color, dry_run=False):
|
|||
if contrast < min_contrast:
|
||||
raise BadAppearance(
|
||||
'Colour had insufficient contrast:',
|
||||
(f'{contrast:.2f}', f'/{min_contrast}'),
|
||||
(f'{contrast:.2f}', f'/{min_contrast:.2f}'),
|
||||
)
|
||||
else:
|
||||
user['color'] = color
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import hashlib
|
||||
|
||||
from captcha.image import ImageCaptcha
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import secrets
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import re
|
||||
import random
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import secrets
|
||||
|
||||
def generate_csp():
|
||||
'''
|
||||
Generate a random Content Secuity Policy nonce.
|
||||
'''
|
||||
return secrets.token_urlsafe(16)
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import secrets
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
from enum import Enum
|
||||
|
||||
WS = Enum('WS', names=('MESSAGE, CAPTCHA, APPEARANCE'))
|
||||
|
||||
class Malformed(Exception):
|
||||
pass
|
||||
|
||||
|
@ -19,13 +26,27 @@ def parse_websocket_data(receipt):
|
|||
comment = get(str, form, 'comment')
|
||||
digest = get(str, form, 'captcha-digest', '')
|
||||
answer = get(str, form, 'captcha-answer', '')
|
||||
return nonce, comment, digest, answer
|
||||
return WS.MESSAGE, (nonce, comment, digest, answer)
|
||||
|
||||
case 'appearance':
|
||||
raise NotImplemented
|
||||
form = get(dict, receipt, 'form')
|
||||
name = get(str, form, 'name').strip()
|
||||
if len(name) == 0:
|
||||
name = None
|
||||
color = get(str, form, 'color')
|
||||
password = get(str, form, 'password')
|
||||
#match get(str | None, form, 'want-tripcode'):
|
||||
# case '0':
|
||||
# want_tripcode = False
|
||||
# case '1':
|
||||
# want_tripcode = True
|
||||
# case _:
|
||||
# want_tripcode = None
|
||||
want_tripcode = bool(password)
|
||||
return WS.APPEARANCE, (name, color, password, want_tripcode)
|
||||
|
||||
case 'captcha':
|
||||
return None
|
||||
return WS.CAPTCHA, ()
|
||||
|
||||
case _:
|
||||
raise Malformed('malformed type')
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
|
@ -6,9 +9,9 @@ from quart import current_app, websocket
|
|||
from anonstream.stream import get_stream_title, get_stream_uptime_and_viewership
|
||||
from anonstream.captcha import get_random_captcha_digest_for
|
||||
from anonstream.chat import get_all_messages_for_websocket, add_chat_message, Rejected
|
||||
from anonstream.user import get_all_users_for_websocket, see, verify, deverify, BadCaptcha
|
||||
from anonstream.user import get_all_users_for_websocket, see, verify, deverify, BadCaptcha, try_change_appearance
|
||||
from anonstream.utils.chat import generate_nonce
|
||||
from anonstream.utils.websocket import parse_websocket_data, Malformed
|
||||
from anonstream.utils.websocket import parse_websocket_data, Malformed, WS
|
||||
|
||||
CONFIG = current_app.config
|
||||
|
||||
|
@ -41,7 +44,7 @@ async def websocket_inbound(queue, user):
|
|||
finally:
|
||||
see(user)
|
||||
try:
|
||||
parsed = parse_websocket_data(receipt)
|
||||
receipt_type, parsed = parse_websocket_data(receipt)
|
||||
except Malformed as e:
|
||||
error , *_ = e.args
|
||||
payload = {
|
||||
|
@ -49,12 +52,14 @@ async def websocket_inbound(queue, user):
|
|||
'because': error,
|
||||
}
|
||||
else:
|
||||
match parsed:
|
||||
case [nonce, comment, digest, answer]:
|
||||
payload = handle_inbound_message(user, *parsed)
|
||||
|
||||
case None:
|
||||
payload = handle_inbound_captcha(user)
|
||||
match receipt_type:
|
||||
case WS.MESSAGE:
|
||||
handle = handle_inbound_message
|
||||
case WS.APPEARANCE:
|
||||
handle = handle_inbound_appearance
|
||||
case WS.CAPTCHA:
|
||||
handle = handle_inbound_captcha
|
||||
payload = handle(user, *parsed)
|
||||
|
||||
queue.put_nowait(payload)
|
||||
|
||||
|
@ -64,6 +69,22 @@ def handle_inbound_captcha(user):
|
|||
'digest': get_random_captcha_digest_for(user),
|
||||
}
|
||||
|
||||
def handle_inbound_appearance(user, name, color, password, want_tripcode):
|
||||
errors = try_change_appearance(user, name, color, password, want_tripcode)
|
||||
if errors:
|
||||
return {
|
||||
'type': 'appearance',
|
||||
'errors': [error.args for error in errors],
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'type': 'appearance',
|
||||
'result': 'Changed appearance',
|
||||
'name': user['name'],
|
||||
'color': user['color'],
|
||||
#'tripcode': user['tripcode'],
|
||||
}
|
||||
|
||||
def handle_inbound_message(user, nonce, comment, digest, answer):
|
||||
try:
|
||||
verification_happened = verify(user, digest, answer)
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import time
|
||||
from functools import wraps
|
||||
|
||||
|
|
3
app.py
3
app.py
|
@ -1,3 +1,6 @@
|
|||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import os
|
||||
import anonstream
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
secret_key = "test"
|
||||
secret_key = "place actual secret key here"
|
||||
|
||||
[auth]
|
||||
username = "broadcaster"
|
||||
|
@ -45,6 +45,7 @@ max_comment_length = 512
|
|||
max_name_length = 24
|
||||
min_name_contrast = 3.0
|
||||
background_color = "#232327"
|
||||
legacy_tripcode_algorithm = false
|
||||
|
||||
[flood]
|
||||
duration = 20.0
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
aiofiles==0.8.0
|
||||
asgiref==3.5.0
|
||||
blinker==1.4
|
||||
git+https://github.com/lepture/captcha@27920681b86c27c990da484984f673dba1dd47e5#egg=captcha
|
||||
click==8.0.4
|
||||
h11==0.13.0
|
||||
h2==4.1.0
|
||||
hpack==4.0.0
|
||||
hypercorn==0.13.2
|
||||
hyperframe==6.0.1
|
||||
iso8601==1.0.2
|
||||
itsdangerous==2.1.0
|
||||
Jinja2==3.0.3
|
||||
m3u8==1.0.0
|
||||
MarkupSafe==2.1.0
|
||||
Pillow==9.0.1
|
||||
priority==2.0.0
|
||||
quart==0.16.3
|
||||
toml==0.10.2
|
||||
uvicorn==0.17.5
|
||||
Werkzeug==2.0.3
|
||||
wsproto==1.1.0
|
読み込み中…
新しいイシューから参照