Merge branch 'master' into regex-censor
# Conflicts: # .gitignore # docker-compose.yml # files/classes/comment.py # files/classes/submission.py # files/helpers/const.py # requirements.txtremotes/1693045480750635534/spooky-22
commit
a831a24aa4
|
@ -1,4 +1,4 @@
|
|||
*.css linguist-detectable=false
|
||||
*.js linguist-detectable=true
|
||||
*.html linguist-detectable=false
|
||||
*.py linguist-detectable=true
|
||||
*.css linguist-detectable=false
|
||||
*.js linguist-detectable=true
|
||||
*.html linguist-detectable=false
|
||||
*.py linguist-detectable=true
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
FROM ubuntu:20.04
|
||||
|
||||
COPY supervisord.conf /etc/supervisord.conf
|
||||
|
||||
RUN apt update && apt install -y python3.8 python3-pip supervisor
|
||||
|
||||
RUN mkdir -p ./service
|
||||
|
||||
COPY requirements.txt ./service/requirements.txt
|
||||
|
||||
RUN cd ./service && pip3 install -r requirements.txt
|
||||
|
||||
EXPOSE 80/tcp
|
||||
|
||||
CMD [ "/usr/bin/supervisord", "-c", "/etc/supervisord.conf" ]
|
||||
FROM ubuntu:20.04
|
||||
|
||||
COPY supervisord.conf /etc/supervisord.conf
|
||||
|
||||
RUN apt update && apt install -y python3.8 python3-pip supervisor
|
||||
|
||||
RUN mkdir -p ./service
|
||||
|
||||
COPY requirements.txt ./service/requirements.txt
|
||||
|
||||
RUN cd ./service && pip3 install -r requirements.txt
|
||||
|
||||
EXPOSE 80/tcp
|
||||
|
||||
CMD [ "/usr/bin/supervisord", "-c", "/etc/supervisord.conf" ]
|
||||
|
|
|
@ -1,373 +1,373 @@
|
|||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
version: 0.0
|
||||
os: linux
|
||||
files:
|
||||
- source: /
|
||||
destination: files
|
||||
permissions:
|
||||
- object: files/*
|
||||
mode: 4755
|
||||
hooks:
|
||||
AfterInstall:
|
||||
- location: scripts/install_pip
|
||||
ApplicationStart:
|
||||
version: 0.0
|
||||
os: linux
|
||||
files:
|
||||
- source: /
|
||||
destination: files
|
||||
permissions:
|
||||
- object: files/*
|
||||
mode: 4755
|
||||
hooks:
|
||||
AfterInstall:
|
||||
- location: scripts/install_pip
|
||||
ApplicationStart:
|
||||
- location: scripts/start_files
|
|
@ -1,8 +1,8 @@
|
|||
version: 0.2
|
||||
phases:
|
||||
install:
|
||||
runtime-versions:
|
||||
python: 3.7
|
||||
artifacts:
|
||||
files:
|
||||
version: 0.2
|
||||
phases:
|
||||
install:
|
||||
runtime-versions:
|
||||
python: 3.7
|
||||
artifacts:
|
||||
files:
|
||||
- '**/*'
|
|
@ -1,7 +0,0 @@
|
|||
for theme in ['transparent', 'win98', 'midnight', 'dark', 'light', 'coffee', 'tron', '4chan']:
|
||||
with open(f"./files/assets/css/{theme}_ff66ac.css", encoding='utf-8') as t:
|
||||
text = t.read()
|
||||
for color in ['ff66ac','805ad5','62ca56','38a169','80ffff','2a96f3','62ca56','eb4963','ff0000','f39731','30409f','3e98a7','e4432d','7b9ae4','ec72de','7f8fa6', 'f8db58']:
|
||||
newtext = text.replace("ff66ac", color).replace("ff4097", color).replace("ff1a83", color).replace("ff3390", color).replace("rgba(255, 102, 172, 0.25)", color)
|
||||
with open(f"./files/assets/css/{theme}_{color}.css", encoding='utf-8', mode='w') as nt:
|
||||
nt.write(newtext)
|
|
@ -1,6 +1,6 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
|
@ -7,10 +7,9 @@ services:
|
|||
volumes:
|
||||
- "./:/service"
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://postgres@postgres:5432/postgres
|
||||
- DATABASE_URL=postgresql://postgres@127.0.0.1:5432/postgres
|
||||
- MASTER_KEY=${MASTER_KEY:-KTVciAUQFpFh2WdJ/oiHJlxl6FvzRZp8kYzAAv3l2OA=}
|
||||
- REDIS_URL=redis://redis
|
||||
- DOMAIN=localhost
|
||||
- DOMAIN=localhost
|
||||
- SITE_NAME=Drama
|
||||
- GIPHY_KEY=3435tdfsdudebussylmaoxxt43
|
||||
- FORCE_HTTPS=0
|
||||
|
@ -32,7 +31,7 @@ services:
|
|||
- BOT_DISABLE=0
|
||||
- COINS_NAME=Dramacoins
|
||||
- DEFAULT_TIME_FILTER=all
|
||||
- DEFAULT_THEME=dark
|
||||
- DEFAULT_THEME=midnight
|
||||
- DEFAULT_COLOR=ff66ac #YOU HAVE TO PICK ONE OF THOSE COLORS OR SHIT WILL BREAK: ff66ac, 805ad5, 62ca56, 38a169, 80ffff, 2a96f3, eb4963, ff0000, f39731, 30409f, 3e98a7, e4432d, 7b9ae4, ec72de, 7f8fa6, f8db58
|
||||
- SLOGAN=Dude bussy lmao
|
||||
- GUMROAD_TOKEN=3435tdfsdudebussylmaoxxt43
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
export DATABASE_URL="postgresql://postgres@postgres:5432/postgres"
|
||||
export MASTER_KEY="-KTVciAUQFpFh2WdJ/oiHJlxl6FvzRZp8kYzAAv3l2OA="
|
||||
export DOMAIN="localhost"
|
||||
export SITE_NAME="Drama"
|
||||
export GIPHY_KEY="3435tdfsdudebussylmaoxxt43"
|
||||
export FORCE_HTTPS="0"
|
||||
export DISCORD_SERVER_ID="3435tdfsdudebussylmaoxxt43"
|
||||
export DISCORD_CLIENT_ID="3435tdfsdudebussylmaoxxt43"
|
||||
export DISCORD_CLIENT_SECRET="3435tdfsdudebussylmaoxxt43"
|
||||
export DISCORD_BOT_TOKEN="3435tdfsdudebussylmaoxxt43"
|
||||
export HCAPTCHA_SECRET="3435tdfsdudebussylmaoxxt43"
|
||||
export YOUTUBE_KEY="3435tdfsdudebussylmaoxxt43"
|
||||
export PUSHER_KEY="3435tdfsdudebussylmaoxxt43"
|
||||
export CATBOX_KEY="3435tdfsdudebussylmaoxxt43"
|
||||
export SPAM_SIMILARITY_THRESHOLD="0.5"
|
||||
export SPAM_SIMILAR_COUNT_THRESHOLD="5"
|
||||
export SPAM_URL_SIMILARITY_THRESHOLD="0.1"
|
||||
export COMMENT_SPAM_SIMILAR_THRESHOLD="0.5"
|
||||
export COMMENT_SPAM_COUNT_THRESHOLD="5"
|
||||
export READ_ONLY="0"
|
||||
export BOT_DISABLE="0"
|
||||
export COINS_NAME="Dramacoins"
|
||||
export DEFAULT_TIME_FILTER="all"
|
||||
export SLOGAN="Dude bussy lmao"
|
||||
export GUMROAD_TOKEN="3435tdfsdudebussylmaoxxt43"
|
||||
export GUMROAD_LINK="https://marsey1.gumroad.com/l/tfcvri"
|
||||
export CARD_VIEW="1"
|
||||
export DISABLE_DOWNVOTES="0"
|
||||
export DUES="0"
|
||||
export DEFAULT_THEME="dark"
|
||||
export DEFAULT_COLOR="ff66ac" # YOU HAVE TO PICK ONE OF THOSE COLORS OR SHIT WILL BREAK: ff66ac, 805ad5, 62ca56, 38a169, 80ffff, 2a96f3, eb4963, ff0000, f39731, 30409f, 3e98a7, e4432d, 7b9ae4, ec72de, 7f8fa6, f8db58
|
||||
export MAIL_USERNAME="blahblahblah@gmail.com"
|
||||
export DATABASE_URL="postgresql://postgres@127.0.0.1:5432/postgres"
|
||||
export MASTER_KEY="-KTVciAUQFpFh2WdJ/oiHJlxl6FvzRZp8kYzAAv3l2OA="
|
||||
export DOMAIN="localhost"
|
||||
export SITE_NAME="Drama"
|
||||
export GIPHY_KEY="3435tdfsdudebussylmaoxxt43"
|
||||
export FORCE_HTTPS="0"
|
||||
export DISCORD_SERVER_ID="3435tdfsdudebussylmaoxxt43"
|
||||
export DISCORD_CLIENT_ID="3435tdfsdudebussylmaoxxt43"
|
||||
export DISCORD_CLIENT_SECRET="3435tdfsdudebussylmaoxxt43"
|
||||
export DISCORD_BOT_TOKEN="3435tdfsdudebussylmaoxxt43"
|
||||
export HCAPTCHA_SECRET="3435tdfsdudebussylmaoxxt43"
|
||||
export YOUTUBE_KEY="3435tdfsdudebussylmaoxxt43"
|
||||
export PUSHER_KEY="3435tdfsdudebussylmaoxxt43"
|
||||
export CATBOX_KEY="3435tdfsdudebussylmaoxxt43"
|
||||
export SPAM_SIMILARITY_THRESHOLD="0.5"
|
||||
export SPAM_SIMILAR_COUNT_THRESHOLD="5"
|
||||
export SPAM_URL_SIMILARITY_THRESHOLD="0.1"
|
||||
export COMMENT_SPAM_SIMILAR_THRESHOLD="0.5"
|
||||
export COMMENT_SPAM_COUNT_THRESHOLD="5"
|
||||
export READ_ONLY="0"
|
||||
export BOT_DISABLE="0"
|
||||
export COINS_NAME="Dramacoins"
|
||||
export DEFAULT_TIME_FILTER="all"
|
||||
export SLOGAN="Dude bussy lmao"
|
||||
export GUMROAD_TOKEN="3435tdfsdudebussylmaoxxt43"
|
||||
export GUMROAD_LINK="https://marsey1.gumroad.com/l/tfcvri"
|
||||
export CARD_VIEW="1"
|
||||
export DISABLE_DOWNVOTES="0"
|
||||
export DUES="0"
|
||||
export DEFAULT_THEME="midnight"
|
||||
export DEFAULT_COLOR="ff66ac" # YOU HAVE TO PICK ONE OF THOSE COLORS OR SHIT WILL BREAK: ff66ac, 805ad5, 62ca56, 38a169, 80ffff, 2a96f3, eb4963, ff0000, f39731, 30409f, 3e98a7, e4432d, 7b9ae4, ec72de, 7f8fa6, f8db58
|
||||
export MAIL_USERNAME="blahblahblah@gmail.com"
|
||||
export MAIL_PASSWORD="3435tdfsdudebussylmaoxxt43"
|
|
@ -1,131 +1,131 @@
|
|||
import gevent.monkey
|
||||
gevent.monkey.patch_all()
|
||||
from os import environ
|
||||
import secrets
|
||||
from flask import *
|
||||
from flask_caching import Cache
|
||||
from flask_limiter import Limiter
|
||||
from flask_compress import Compress
|
||||
from flask_limiter.util import get_ipaddr
|
||||
from flask_mail import Mail
|
||||
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
from sqlalchemy import *
|
||||
import gevent
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
import redis
|
||||
|
||||
app = Flask(__name__, template_folder='./templates')
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=3)
|
||||
app.url_map.strict_slashes = False
|
||||
app.jinja_env.cache = {}
|
||||
import faulthandler
|
||||
faulthandler.enable()
|
||||
|
||||
app.config["SITE_NAME"]=environ.get("SITE_NAME").strip()
|
||||
app.config["COINS_NAME"]=environ.get("COINS_NAME").strip()
|
||||
app.config["GUMROAD_LINK"]=environ.get("GUMROAD_LINK", "https://marsey1.gumroad.com/l/tfcvri").strip()
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
app.config['DATABASE_URL'] = environ.get("DATABASE_URL")
|
||||
app.config['SECRET_KEY'] = environ.get('MASTER_KEY')
|
||||
app.config["SERVER_NAME"] = environ.get("DOMAIN").strip()
|
||||
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 86400
|
||||
app.config["SESSION_COOKIE_NAME"] = "session_" + environ.get("SITE_NAME").strip().lower()
|
||||
app.config["VERSION"] = "1.0.0"
|
||||
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
|
||||
app.config["SESSION_COOKIE_SECURE"] = bool(int(environ.get("FORCE_HTTPS", 1)))
|
||||
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
|
||||
app.config["PERMANENT_SESSION_LIFETIME"] = 60 * 60 * 24 * 365
|
||||
app.config["SESSION_REFRESH_EACH_REQUEST"] = True
|
||||
app.config["SLOGAN"] = environ.get("SLOGAN", "").strip()
|
||||
app.config["DEFAULT_COLOR"] = environ.get("DEFAULT_COLOR", "ff0000").strip()
|
||||
app.config["DEFAULT_THEME"] = environ.get("DEFAULT_THEME", "light").strip() + "_" + environ.get("DEFAULT_COLOR", "ff0000").strip()
|
||||
app.config["FORCE_HTTPS"] = int(environ.get("FORCE_HTTPS", 1)) if ("localhost" not in app.config["SERVER_NAME"] and "127.0.0.1" not in app.config["SERVER_NAME"]) else 0
|
||||
app.config["UserAgent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
|
||||
app.config["HCAPTCHA_SITEKEY"] = environ.get("HCAPTCHA_SITEKEY","").strip()
|
||||
app.config["HCAPTCHA_SECRET"] = environ.get("HCAPTCHA_SECRET","").strip()
|
||||
app.config["SPAM_SIMILARITY_THRESHOLD"] = float(environ.get("SPAM_SIMILARITY_THRESHOLD", 0.5))
|
||||
app.config["SPAM_SIMILAR_COUNT_THRESHOLD"] = int(environ.get("SPAM_SIMILAR_COUNT_THRESHOLD", 5))
|
||||
app.config["SPAM_URL_SIMILARITY_THRESHOLD"] = float(environ.get("SPAM_URL_SIMILARITY_THRESHOLD", 0.5))
|
||||
app.config["COMMENT_SPAM_SIMILAR_THRESHOLD"] = float(environ.get("COMMENT_SPAM_SIMILAR_THRESHOLD", 0.5))
|
||||
app.config["COMMENT_SPAM_COUNT_THRESHOLD"] = int(environ.get("COMMENT_SPAM_COUNT_THRESHOLD", 0.5))
|
||||
app.config["VIDEO_COIN_REQUIREMENT"] = int(environ.get("VIDEO_COIN_REQUIREMENT", 0))
|
||||
app.config["READ_ONLY"]=bool(int(environ.get("READ_ONLY", "0")))
|
||||
app.config["BOT_DISABLE"]=bool(int(environ.get("BOT_DISABLE", False)))
|
||||
app.config["RATELIMIT_KEY_PREFIX"] = "flask_limiting_"
|
||||
app.config["RATELIMIT_ENABLED"] = True
|
||||
app.config["RATELIMIT_DEFAULTS_DEDUCT_WHEN"]=lambda:True
|
||||
app.config["RATELIMIT_DEFAULTS_EXEMPT_WHEN"]=lambda:False
|
||||
app.config["RATELIMIT_HEADERS_ENABLED"]=True
|
||||
app.config["CACHE_TYPE"] = "filesystem"
|
||||
app.config["CACHE_DIR"] = "cache"
|
||||
app.config["RATELIMIT_STORAGE_URL"] = environ.get("REDIS_URL", "redis://127.0.0.1")
|
||||
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
|
||||
app.config['MAIL_PORT'] = 587
|
||||
app.config['MAIL_USE_TLS'] = True
|
||||
app.config['MAIL_USERNAME'] = environ.get("MAIL_USERNAME", "").strip()
|
||||
app.config['MAIL_PASSWORD'] = environ.get("MAIL_PASSWORD", "").strip()
|
||||
|
||||
r=redis.Redis(host=environ.get("REDIS_URL", "redis://127.0.0.1"), decode_responses=True, ssl_cert_reqs=None)
|
||||
|
||||
limiter = Limiter(
|
||||
app,
|
||||
key_func=get_ipaddr,
|
||||
default_limits=["50/minute"],
|
||||
headers_enabled=True,
|
||||
strategy="fixed-window"
|
||||
)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
engine = create_engine(app.config['DATABASE_URL'])
|
||||
|
||||
db_session = scoped_session(sessionmaker(bind=engine, autoflush=False))
|
||||
|
||||
cache = Cache(app)
|
||||
Compress(app)
|
||||
mail = Mail(app)
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
|
||||
if request.method.lower() != "get" and app.config["READ_ONLY"]: return {"error":f"{app.config['SITE_NAME']} is currently in read-only mode."}, 500
|
||||
|
||||
if app.config["BOT_DISABLE"] and request.headers.get("X-User-Type")=="Bot": abort(503)
|
||||
|
||||
g.db = db_session()
|
||||
|
||||
g.timestamp = int(time.time())
|
||||
|
||||
if not request.path.startswith("/assets") and not request.path.startswith("/images") and not request.path.startswith("/hostedimages"):
|
||||
session.permanent = True
|
||||
if not session.get("session_id"): session["session_id"] = secrets.token_hex(16)
|
||||
|
||||
if app.config["FORCE_HTTPS"] and request.url.startswith("http://") and "localhost" not in app.config["SERVER_NAME"]:
|
||||
url = request.url.replace("http://", "https://", 1)
|
||||
return redirect(url, code=301)
|
||||
|
||||
ua=request.headers.get("User-Agent","")
|
||||
if "CriOS/" in ua: g.system="ios/chrome"
|
||||
elif "Version/" in ua: g.system="android/webview"
|
||||
elif "Mobile Safari/" in ua: g.system="android/chrome"
|
||||
elif "Safari/" in ua: g.system="ios/safari"
|
||||
elif "Mobile/" in ua: g.system="ios/webview"
|
||||
else: g.system="other/other"
|
||||
|
||||
@app.teardown_appcontext
|
||||
def teardown_request(error):
|
||||
if hasattr(g, 'db') and g.db:
|
||||
g.db.close()
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
|
||||
response.headers.add("Strict-Transport-Security", "max-age=31536000")
|
||||
response.headers.add("Referrer-Policy", "same-origin")
|
||||
response.headers.add("X-Frame-Options", "deny")
|
||||
return response
|
||||
|
||||
|
||||
import gevent.monkey
|
||||
gevent.monkey.patch_all()
|
||||
from os import environ
|
||||
import secrets
|
||||
from flask import *
|
||||
from flask_caching import Cache
|
||||
from flask_limiter import Limiter
|
||||
from flask_compress import Compress
|
||||
from flask_limiter.util import get_ipaddr
|
||||
from flask_mail import Mail
|
||||
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
from sqlalchemy import *
|
||||
import gevent
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
import redis
|
||||
|
||||
app = Flask(__name__, template_folder='./templates')
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=3)
|
||||
app.url_map.strict_slashes = False
|
||||
app.jinja_env.cache = {}
|
||||
import faulthandler
|
||||
faulthandler.enable()
|
||||
|
||||
app.config["SITE_NAME"]=environ.get("SITE_NAME").strip()
|
||||
app.config["COINS_NAME"]=environ.get("COINS_NAME").strip()
|
||||
app.config["GUMROAD_LINK"]=environ.get("GUMROAD_LINK", "https://marsey1.gumroad.com/l/tfcvri").strip()
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
app.config['DATABASE_URL'] = environ.get("DATABASE_URL")
|
||||
app.config['SECRET_KEY'] = environ.get('MASTER_KEY')
|
||||
app.config["SERVER_NAME"] = environ.get("DOMAIN").strip()
|
||||
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 86400
|
||||
app.config["SESSION_COOKIE_NAME"] = "session_" + environ.get("SITE_NAME").strip().lower()
|
||||
app.config["VERSION"] = "1.0.0"
|
||||
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
|
||||
app.config["SESSION_COOKIE_SECURE"] = bool(int(environ.get("FORCE_HTTPS", 1)))
|
||||
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
|
||||
app.config["PERMANENT_SESSION_LIFETIME"] = 60 * 60 * 24 * 365
|
||||
app.config["SESSION_REFRESH_EACH_REQUEST"] = True
|
||||
app.config["SLOGAN"] = environ.get("SLOGAN", "").strip()
|
||||
app.config["DEFAULT_COLOR"] = environ.get("DEFAULT_COLOR", "ff0000").strip()
|
||||
app.config["DEFAULT_THEME"] = environ.get("DEFAULT_THEME", "midnight").strip()
|
||||
app.config["FORCE_HTTPS"] = int(environ.get("FORCE_HTTPS", 1)) if ("localhost" not in app.config["SERVER_NAME"] and "127.0.0.1" not in app.config["SERVER_NAME"]) else 0
|
||||
app.config["UserAgent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
|
||||
app.config["HCAPTCHA_SITEKEY"] = environ.get("HCAPTCHA_SITEKEY","").strip()
|
||||
app.config["HCAPTCHA_SECRET"] = environ.get("HCAPTCHA_SECRET","").strip()
|
||||
app.config["SPAM_SIMILARITY_THRESHOLD"] = float(environ.get("SPAM_SIMILARITY_THRESHOLD", 0.5))
|
||||
app.config["SPAM_SIMILAR_COUNT_THRESHOLD"] = int(environ.get("SPAM_SIMILAR_COUNT_THRESHOLD", 5))
|
||||
app.config["SPAM_URL_SIMILARITY_THRESHOLD"] = float(environ.get("SPAM_URL_SIMILARITY_THRESHOLD", 0.5))
|
||||
app.config["COMMENT_SPAM_SIMILAR_THRESHOLD"] = float(environ.get("COMMENT_SPAM_SIMILAR_THRESHOLD", 0.5))
|
||||
app.config["COMMENT_SPAM_COUNT_THRESHOLD"] = int(environ.get("COMMENT_SPAM_COUNT_THRESHOLD", 0.5))
|
||||
app.config["VIDEO_COIN_REQUIREMENT"] = int(environ.get("VIDEO_COIN_REQUIREMENT", 0))
|
||||
app.config["READ_ONLY"]=bool(int(environ.get("READ_ONLY", "0")))
|
||||
app.config["BOT_DISABLE"]=bool(int(environ.get("BOT_DISABLE", False)))
|
||||
app.config["RATELIMIT_KEY_PREFIX"] = "flask_limiting_"
|
||||
app.config["RATELIMIT_ENABLED"] = True
|
||||
app.config["RATELIMIT_DEFAULTS_DEDUCT_WHEN"]=lambda:True
|
||||
app.config["RATELIMIT_DEFAULTS_EXEMPT_WHEN"]=lambda:False
|
||||
app.config["RATELIMIT_HEADERS_ENABLED"]=True
|
||||
app.config["CACHE_TYPE"] = "filesystem"
|
||||
app.config["CACHE_DIR"] = "cache"
|
||||
app.config["RATELIMIT_STORAGE_URL"] = environ.get("REDIS_URL", "redis://127.0.0.1")
|
||||
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
|
||||
app.config['MAIL_PORT'] = 587
|
||||
app.config['MAIL_USE_TLS'] = True
|
||||
app.config['MAIL_USERNAME'] = environ.get("MAIL_USERNAME", "").strip()
|
||||
app.config['MAIL_PASSWORD'] = environ.get("MAIL_PASSWORD", "").strip()
|
||||
|
||||
r=redis.Redis(host=environ.get("REDIS_URL", "redis://127.0.0.1"), decode_responses=True, ssl_cert_reqs=None)
|
||||
|
||||
limiter = Limiter(
|
||||
app,
|
||||
key_func=get_ipaddr,
|
||||
default_limits=["50/minute"],
|
||||
headers_enabled=True,
|
||||
strategy="fixed-window"
|
||||
)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
engine = create_engine(app.config['DATABASE_URL'])
|
||||
|
||||
db_session = scoped_session(sessionmaker(bind=engine, autoflush=False))
|
||||
|
||||
cache = Cache(app)
|
||||
Compress(app)
|
||||
mail = Mail(app)
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
|
||||
if request.method.lower() != "get" and app.config["READ_ONLY"]: return {"error":f"{app.config['SITE_NAME']} is currently in read-only mode."}, 500
|
||||
|
||||
if app.config["BOT_DISABLE"] and request.headers.get("X-User-Type")=="Bot": abort(503)
|
||||
|
||||
g.db = db_session()
|
||||
|
||||
g.timestamp = int(time.time())
|
||||
|
||||
if not request.path.startswith("/assets") and not request.path.startswith("/images") and not request.path.startswith("/hostedimages"):
|
||||
session.permanent = True
|
||||
if not session.get("session_id"): session["session_id"] = secrets.token_hex(16)
|
||||
|
||||
if app.config["FORCE_HTTPS"] and request.url.startswith("http://") and "localhost" not in app.config["SERVER_NAME"]:
|
||||
url = request.url.replace("http://", "https://", 1)
|
||||
return redirect(url, code=301)
|
||||
|
||||
ua=request.headers.get("User-Agent","")
|
||||
if "CriOS/" in ua: g.system="ios/chrome"
|
||||
elif "Version/" in ua: g.system="android/webview"
|
||||
elif "Mobile Safari/" in ua: g.system="android/chrome"
|
||||
elif "Safari/" in ua: g.system="ios/safari"
|
||||
elif "Mobile/" in ua: g.system="ios/webview"
|
||||
else: g.system="other/other"
|
||||
|
||||
@app.teardown_appcontext
|
||||
def teardown_request(error):
|
||||
if hasattr(g, 'db') and g.db:
|
||||
g.db.close()
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
|
||||
response.headers.add("Strict-Transport-Security", "max-age=31536000")
|
||||
response.headers.add("Referrer-Policy", "same-origin")
|
||||
response.headers.add("X-Frame-Options", "deny")
|
||||
return response
|
||||
|
||||
|
||||
from files.routes import *
|
|
@ -1,15 +1,15 @@
|
|||
from .alts import *
|
||||
from .badges import *
|
||||
from .clients import *
|
||||
from .comment import *
|
||||
from .domains import *
|
||||
from .flags import *
|
||||
from .user import *
|
||||
from .userblock import *
|
||||
from .submission import *
|
||||
from .votes import *
|
||||
from .domains import *
|
||||
from .subscriptions import *
|
||||
from files.__main__ import app
|
||||
from .mod_logs import *
|
||||
from .alts import *
|
||||
from .badges import *
|
||||
from .clients import *
|
||||
from .comment import *
|
||||
from .domains import *
|
||||
from .flags import *
|
||||
from .user import *
|
||||
from .userblock import *
|
||||
from .submission import *
|
||||
from .votes import *
|
||||
from .domains import *
|
||||
from .subscriptions import *
|
||||
from files.__main__ import app
|
||||
from .mod_logs import *
|
||||
from .award import *
|
|
@ -1,15 +1,15 @@
|
|||
from sqlalchemy import *
|
||||
from files.__main__ import Base
|
||||
|
||||
|
||||
class Alt(Base):
|
||||
__tablename__ = "alts"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user1 = Column(Integer, ForeignKey("users.id"))
|
||||
user2 = Column(Integer, ForeignKey("users.id"))
|
||||
is_manual = Column(Boolean, default=False)
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
return f"<Alt(id={self.id})>"
|
||||
from sqlalchemy import *
|
||||
from files.__main__ import Base
|
||||
|
||||
|
||||
class Alt(Base):
|
||||
__tablename__ = "alts"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user1 = Column(Integer, ForeignKey("users.id"))
|
||||
user2 = Column(Integer, ForeignKey("users.id"))
|
||||
is_manual = Column(Boolean, default=False)
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
return f"<Alt(id={self.id})>"
|
||||
|
|
|
@ -1,87 +1,87 @@
|
|||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship
|
||||
from files.__main__ import Base
|
||||
from os import environ
|
||||
from files.helpers.lazy import lazy
|
||||
|
||||
site_name = environ.get("SITE_NAME").strip()
|
||||
|
||||
if site_name == "Drama":
|
||||
AWARDS = {
|
||||
"ban": {
|
||||
"kind": "ban",
|
||||
"title": "One-Day Ban",
|
||||
"description": "Bans the author for a day.",
|
||||
"icon": "fas fa-gavel",
|
||||
"color": "text-danger",
|
||||
"price": 5000
|
||||
},
|
||||
"shit": {
|
||||
"kind": "shit",
|
||||
"title": "Shit",
|
||||
"description": "Makes flies swarm a post.",
|
||||
"icon": "fas fa-poop",
|
||||
"color": "text-black-50",
|
||||
"price": 1000
|
||||
},
|
||||
"fireflies": {
|
||||
"kind": "fireflies",
|
||||
"title": "Fireflies",
|
||||
"description": "Puts stars on the post.",
|
||||
"icon": "fas fa-sparkles",
|
||||
"color": "text-warning",
|
||||
"price": 1000
|
||||
}
|
||||
}
|
||||
else:
|
||||
AWARDS = {
|
||||
"shit": {
|
||||
"kind": "shit",
|
||||
"title": "Shit",
|
||||
"description": "Makes flies swarm a post.",
|
||||
"icon": "fas fa-poop",
|
||||
"color": "text-black-50",
|
||||
"price": 1000
|
||||
},
|
||||
"fireflies": {
|
||||
"kind": "fireflies",
|
||||
"title": "Fireflies",
|
||||
"description": "Puts stars on the post.",
|
||||
"icon": "fas fa-sparkles",
|
||||
"color": "text-warning",
|
||||
"price": 1000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AwardRelationship(Base):
|
||||
|
||||
__tablename__ = "award_relationships"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
submission_id = Column(Integer, ForeignKey("submissions.id"))
|
||||
comment_id = Column(Integer, ForeignKey("comments.id"))
|
||||
kind = Column(String)
|
||||
|
||||
user = relationship("User", primaryjoin="AwardRelationship.user_id==User.id", viewonly=True)
|
||||
|
||||
post = relationship("Submission", primaryjoin="AwardRelationship.submission_id==Submission.id", viewonly=True)
|
||||
comment = relationship("Comment", primaryjoin="AwardRelationship.comment_id==Comment.id", viewonly=True)
|
||||
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def type(self):
|
||||
return AWARDS[self.kind]
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def title(self):
|
||||
return self.type['title']
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def class_list(self):
|
||||
return self.type['icon']+' '+self.type['color']
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship
|
||||
from files.__main__ import Base
|
||||
from os import environ
|
||||
from files.helpers.lazy import lazy
|
||||
|
||||
site_name = environ.get("SITE_NAME").strip()
|
||||
|
||||
if site_name == "Drama":
|
||||
AWARDS = {
|
||||
"ban": {
|
||||
"kind": "ban",
|
||||
"title": "One-Day Ban",
|
||||
"description": "Bans the author for a day.",
|
||||
"icon": "fas fa-gavel",
|
||||
"color": "text-danger",
|
||||
"price": 5000
|
||||
},
|
||||
"shit": {
|
||||
"kind": "shit",
|
||||
"title": "Shit",
|
||||
"description": "Makes flies swarm a post.",
|
||||
"icon": "fas fa-poop",
|
||||
"color": "text-black-50",
|
||||
"price": 500
|
||||
},
|
||||
"fireflies": {
|
||||
"kind": "fireflies",
|
||||
"title": "Fireflies",
|
||||
"description": "Puts stars on the post.",
|
||||
"icon": "fas fa-sparkles",
|
||||
"color": "text-warning",
|
||||
"price": 500
|
||||
}
|
||||
}
|
||||
else:
|
||||
AWARDS = {
|
||||
"shit": {
|
||||
"kind": "shit",
|
||||
"title": "Shit",
|
||||
"description": "Makes flies swarm a post.",
|
||||
"icon": "fas fa-poop",
|
||||
"color": "text-black-50",
|
||||
"price": 500
|
||||
},
|
||||
"fireflies": {
|
||||
"kind": "fireflies",
|
||||
"title": "Fireflies",
|
||||
"description": "Puts stars on the post.",
|
||||
"icon": "fas fa-sparkles",
|
||||
"color": "text-warning",
|
||||
"price": 500
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AwardRelationship(Base):
|
||||
|
||||
__tablename__ = "award_relationships"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
submission_id = Column(Integer, ForeignKey("submissions.id"))
|
||||
comment_id = Column(Integer, ForeignKey("comments.id"))
|
||||
kind = Column(String)
|
||||
|
||||
user = relationship("User", primaryjoin="AwardRelationship.user_id==User.id", viewonly=True)
|
||||
|
||||
post = relationship("Submission", primaryjoin="AwardRelationship.submission_id==Submission.id", viewonly=True)
|
||||
comment = relationship("Comment", primaryjoin="AwardRelationship.comment_id==Comment.id", viewonly=True)
|
||||
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def type(self):
|
||||
return AWARDS[self.kind]
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def title(self):
|
||||
return self.type['title']
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def class_list(self):
|
||||
return self.type['icon']+' '+self.type['color']
|
||||
|
|
|
@ -1,94 +1,93 @@
|
|||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship
|
||||
from files.__main__ import Base, app
|
||||
from os import environ
|
||||
from files.helpers.lazy import lazy
|
||||
|
||||
site_name = environ.get("SITE_NAME").strip()
|
||||
|
||||
class BadgeDef(Base):
|
||||
|
||||
__tablename__ = "badge_defs"
|
||||
|
||||
id = Column(BigInteger, primary_key=True)
|
||||
name = Column(String)
|
||||
description = Column(String)
|
||||
icon = Column(String)
|
||||
kind = Column(Integer, default=1)
|
||||
qualification_expr = Column(String)
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
return f"<BadgeDef(badge_id={self.id})>"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def path(self):
|
||||
|
||||
return f"/assets/images/badges/{self.icon}"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def json_core(self):
|
||||
return {
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"icon": self.icon
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Badge(Base):
|
||||
|
||||
__tablename__ = "badges"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
|
||||
user_id = Column(Integer, ForeignKey('users.id'))
|
||||
badge_id = Column(Integer, ForeignKey("badge_defs.id"))
|
||||
description = Column(String)
|
||||
url = Column(String)
|
||||
badge = relationship("BadgeDef", viewonly=True)
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
return f"<Badge(user_id={self.user_id}, badge_id={self.badge_id})>"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def text(self):
|
||||
if self.description:
|
||||
return self.description
|
||||
else:
|
||||
return self.badge.description
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def type(self):
|
||||
return self.badge.id
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def name(self):
|
||||
return self.badge.name
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def path(self):
|
||||
return self.badge.path
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def json_core(self):
|
||||
|
||||
return {'text': self.text,
|
||||
'name': self.name,
|
||||
'created_utc': self.created_utc,
|
||||
'url': self.url,
|
||||
'icon_url':f"https://{app.config['SERVER_NAME']}{self.path}"
|
||||
}
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def json(self):
|
||||
return self.json_core
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship
|
||||
from files.__main__ import Base, app
|
||||
from os import environ
|
||||
from files.helpers.lazy import lazy
|
||||
|
||||
site_name = environ.get("SITE_NAME").strip()
|
||||
|
||||
class BadgeDef(Base):
|
||||
|
||||
__tablename__ = "badge_defs"
|
||||
|
||||
id = Column(BigInteger, primary_key=True)
|
||||
name = Column(String)
|
||||
description = Column(String)
|
||||
icon = Column(String)
|
||||
kind = Column(Integer, default=1)
|
||||
qualification_expr = Column(String)
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
return f"<BadgeDef(badge_id={self.id})>"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def path(self):
|
||||
|
||||
return f"/assets/images/badges/{self.icon}.gif"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def json_core(self):
|
||||
return {
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"icon": self.icon
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Badge(Base):
|
||||
|
||||
__tablename__ = "badges"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
|
||||
user_id = Column(Integer, ForeignKey('users.id'))
|
||||
badge_id = Column(Integer, ForeignKey("badge_defs.id"))
|
||||
description = Column(String)
|
||||
url = Column(String)
|
||||
badge = relationship("BadgeDef", viewonly=True)
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
return f"<Badge(user_id={self.user_id}, badge_id={self.badge_id})>"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def text(self):
|
||||
if self.description:
|
||||
return self.description
|
||||
else:
|
||||
return self.badge.description
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def type(self):
|
||||
return self.badge.id
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def name(self):
|
||||
return self.badge.name
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def path(self):
|
||||
return self.badge.path
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def json_core(self):
|
||||
|
||||
return {'text': self.text,
|
||||
'name': self.name,
|
||||
'url': self.url,
|
||||
'icon_url':f"https://{app.config['SERVER_NAME']}{self.path}"
|
||||
}
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def json(self):
|
||||
return self.json_core
|
||||
|
|
|
@ -1,82 +1,82 @@
|
|||
from flask import *
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship, lazyload
|
||||
from .submission import Submission
|
||||
from .comment import Comment
|
||||
from files.__main__ import Base
|
||||
from files.helpers.lazy import lazy
|
||||
import time
|
||||
|
||||
class OauthApp(Base):
|
||||
|
||||
__tablename__ = "oauth_apps"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
client_id = Column(String)
|
||||
app_name = Column(String)
|
||||
redirect_uri = Column(String)
|
||||
description = Column(String)
|
||||
author_id = Column(Integer, ForeignKey("users.id"))
|
||||
author = relationship("User", viewonly=True)
|
||||
|
||||
def __repr__(self): return f"<OauthApp(id={self.id})>"
|
||||
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_date(self):
|
||||
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_datetime(self):
|
||||
return str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc)))
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def permalink(self): return f"/admin/app/{self.id}"
|
||||
|
||||
@lazy
|
||||
def idlist(self, page=1):
|
||||
|
||||
posts = g.db.query(Submission.id).options(lazyload('*')).filter_by(app_id=self.id)
|
||||
|
||||
posts=posts.order_by(Submission.created_utc.desc())
|
||||
|
||||
posts=posts.offset(100*(page-1)).limit(101)
|
||||
|
||||
return [x[0] for x in posts.all()]
|
||||
|
||||
@lazy
|
||||
def comments_idlist(self, page=1):
|
||||
|
||||
posts = g.db.query(Comment.id).options(lazyload('*')).filter_by(app_id=self.id)
|
||||
|
||||
posts=posts.order_by(Comment.created_utc.desc())
|
||||
|
||||
posts=posts.offset(100*(page-1)).limit(101)
|
||||
|
||||
return [x[0] for x in posts.all()]
|
||||
|
||||
|
||||
|
||||
class ClientAuth(Base):
|
||||
|
||||
__tablename__ = "client_auths"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
oauth_client = Column(Integer, ForeignKey("oauth_apps.id"))
|
||||
access_token = Column(String)
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
user = relationship("User", viewonly=True)
|
||||
application = relationship("OauthApp", viewonly=True)
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_date(self):
|
||||
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_datetime(self):
|
||||
from flask import *
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship, lazyload
|
||||
from .submission import Submission
|
||||
from .comment import Comment
|
||||
from files.__main__ import Base
|
||||
from files.helpers.lazy import lazy
|
||||
import time
|
||||
|
||||
class OauthApp(Base):
|
||||
|
||||
__tablename__ = "oauth_apps"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
client_id = Column(String)
|
||||
app_name = Column(String)
|
||||
redirect_uri = Column(String)
|
||||
description = Column(String)
|
||||
author_id = Column(Integer, ForeignKey("users.id"))
|
||||
author = relationship("User", viewonly=True)
|
||||
|
||||
def __repr__(self): return f"<OauthApp(id={self.id})>"
|
||||
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_date(self):
|
||||
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_datetime(self):
|
||||
return str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc)))
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def permalink(self): return f"/admin/app/{self.id}"
|
||||
|
||||
@lazy
|
||||
def idlist(self, page=1):
|
||||
|
||||
posts = g.db.query(Submission.id).options(lazyload('*')).filter_by(app_id=self.id)
|
||||
|
||||
posts=posts.order_by(Submission.created_utc.desc())
|
||||
|
||||
posts=posts.offset(100*(page-1)).limit(101)
|
||||
|
||||
return [x[0] for x in posts.all()]
|
||||
|
||||
@lazy
|
||||
def comments_idlist(self, page=1):
|
||||
|
||||
posts = g.db.query(Comment.id).options(lazyload('*')).filter_by(app_id=self.id)
|
||||
|
||||
posts=posts.order_by(Comment.created_utc.desc())
|
||||
|
||||
posts=posts.offset(100*(page-1)).limit(101)
|
||||
|
||||
return [x[0] for x in posts.all()]
|
||||
|
||||
|
||||
|
||||
class ClientAuth(Base):
|
||||
|
||||
__tablename__ = "client_auths"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
oauth_client = Column(Integer, ForeignKey("oauth_apps.id"))
|
||||
access_token = Column(String)
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
user = relationship("User", viewonly=True)
|
||||
application = relationship("OauthApp", viewonly=True)
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_date(self):
|
||||
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_datetime(self):
|
||||
return str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc)))
|
|
@ -348,16 +348,13 @@ class Comment(Base):
|
|||
@lazy
|
||||
def collapse_for_user(self, v):
|
||||
|
||||
if self.over_18 and not (v and v.over_18) and not self.post.over_18:
|
||||
return True
|
||||
if self.over_18 and not (v and v.over_18) and not self.post.over_18: return True
|
||||
|
||||
if not v:
|
||||
return False
|
||||
if not v: return False
|
||||
|
||||
if any([x in self.body for x in v.filter_words]):
|
||||
return True
|
||||
if v.filter_words and any([x in self.body for x in v.filter_words]): return True
|
||||
|
||||
if self.is_banned: return True
|
||||
if self.is_banned or (self.author and self.author.shadowbanned): return True
|
||||
|
||||
return False
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from sqlalchemy import *
|
||||
from files.__main__ import Base
|
||||
|
||||
class BannedDomain(Base):
|
||||
|
||||
__tablename__ = "banneddomains"
|
||||
id = Column(Integer, primary_key=True)
|
||||
domain = Column(String)
|
||||
from sqlalchemy import *
|
||||
from files.__main__ import Base
|
||||
|
||||
class BannedDomain(Base):
|
||||
|
||||
__tablename__ = "banneddomains"
|
||||
id = Column(Integer, primary_key=True)
|
||||
domain = Column(String)
|
||||
reason = Column(String)
|
|
@ -1,56 +1,56 @@
|
|||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship
|
||||
from files.__main__ import Base
|
||||
from files.helpers.lazy import lazy
|
||||
import time
|
||||
|
||||
class Flag(Base):
|
||||
|
||||
__tablename__ = "flags"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
post_id = Column(Integer, ForeignKey("submissions.id"))
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
reason = Column(String)
|
||||
|
||||
user = relationship("User", primaryjoin = "Flag.user_id == User.id", uselist = False, viewonly=True)
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
return f"<Flag(id={self.id})>"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_date(self):
|
||||
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_datetime(self):
|
||||
return str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc)))
|
||||
|
||||
|
||||
class CommentFlag(Base):
|
||||
|
||||
__tablename__ = "commentflags"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
comment_id = Column(Integer, ForeignKey("comments.id"))
|
||||
reason = Column(String)
|
||||
|
||||
user = relationship("User", primaryjoin = "CommentFlag.user_id == User.id", uselist = False, viewonly=True)
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
return f"<CommentFlag(id={self.id})>"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_date(self):
|
||||
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_datetime(self):
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship
|
||||
from files.__main__ import Base
|
||||
from files.helpers.lazy import lazy
|
||||
import time
|
||||
|
||||
class Flag(Base):
|
||||
|
||||
__tablename__ = "flags"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
post_id = Column(Integer, ForeignKey("submissions.id"))
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
reason = Column(String)
|
||||
|
||||
user = relationship("User", primaryjoin = "Flag.user_id == User.id", uselist = False, viewonly=True)
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
return f"<Flag(id={self.id})>"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_date(self):
|
||||
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_datetime(self):
|
||||
return str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc)))
|
||||
|
||||
|
||||
class CommentFlag(Base):
|
||||
|
||||
__tablename__ = "commentflags"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
comment_id = Column(Integer, ForeignKey("comments.id"))
|
||||
reason = Column(String)
|
||||
|
||||
user = relationship("User", primaryjoin = "CommentFlag.user_id == User.id", uselist = False, viewonly=True)
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
return f"<CommentFlag(id={self.id})>"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_date(self):
|
||||
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_datetime(self):
|
||||
return str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc)))
|
|
@ -1,236 +1,236 @@
|
|||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship
|
||||
from files.__main__ import Base
|
||||
import time
|
||||
from files.helpers.lazy import lazy
|
||||
|
||||
class ModAction(Base):
|
||||
__tablename__ = "modactions"
|
||||
id = Column(BigInteger, primary_key=True)
|
||||
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
kind = Column(String)
|
||||
target_user_id = Column(Integer, ForeignKey("users.id"), default=0)
|
||||
target_submission_id = Column(Integer, ForeignKey("submissions.id"), default=0)
|
||||
target_comment_id = Column(Integer, ForeignKey("comments.id"), default=0)
|
||||
_note=Column(String)
|
||||
created_utc = Column(Integer, default=0)
|
||||
|
||||
user = relationship("User", primaryjoin="User.id==ModAction.user_id", viewonly=True)
|
||||
target_user = relationship("User", primaryjoin="User.id==ModAction.target_user_id", viewonly=True)
|
||||
target_post = relationship("Submission", viewonly=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if "created_utc" not in kwargs:
|
||||
kwargs["created_utc"] = int(time.time())
|
||||
|
||||
if "note" in kwargs:
|
||||
kwargs["_note"]=kwargs["note"]
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ModAction(id={self.id})>"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def age_string(self):
|
||||
|
||||
age = int(time.time()) - self.created_utc
|
||||
|
||||
if age < 60:
|
||||
return "just now"
|
||||
elif age < 3600:
|
||||
minutes = int(age / 60)
|
||||
return f"{minutes}m ago"
|
||||
elif age < 86400:
|
||||
hours = int(age / 3600)
|
||||
return f"{hours}hr ago"
|
||||
elif age < 2678400:
|
||||
days = int(age / 86400)
|
||||
return f"{days}d ago"
|
||||
|
||||
now = time.gmtime()
|
||||
ctd = time.gmtime(self.created_utc)
|
||||
|
||||
months = now.tm_mon - ctd.tm_mon + 12 * (now.tm_year - ctd.tm_year)
|
||||
if now.tm_mday < ctd.tm_mday:
|
||||
months -= 1
|
||||
|
||||
if months < 12:
|
||||
return f"{months}mo ago"
|
||||
else:
|
||||
years = int(months / 12)
|
||||
return f"{years}yr ago"
|
||||
|
||||
|
||||
@property
|
||||
def note(self):
|
||||
|
||||
if self.kind=="ban_user":
|
||||
if self.target_post: return f'for <a href="{self.target_post.permalink}">post</a>'
|
||||
elif self.target_comment_id: return f'for <a href="/comment/{self.target_comment_id}">comment</a>'
|
||||
else: return self._note
|
||||
else:
|
||||
return self._note or ""
|
||||
|
||||
@note.setter
|
||||
def note(self, x):
|
||||
self._note=x
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def string(self):
|
||||
|
||||
output = ACTIONTYPES[self.kind]["str"].format(self=self)
|
||||
|
||||
if self.note: output += f" <i>({self.note})</i>"
|
||||
|
||||
return output
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def target_link(self):
|
||||
if self.target_user: return f'<a href="{self.target_user.url}">{self.target_user.username}</a>'
|
||||
elif self.target_post: return f'<a href="{self.target_post.permalink}">{self.target_post.title.replace("<","").replace(">","")}</a>'
|
||||
elif self.target_comment_id: return f'<a href="/comment/{self.target_comment_id}">comment</a>'
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def icon(self):
|
||||
return ACTIONTYPES[self.kind]['icon']
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def color(self):
|
||||
return ACTIONTYPES[self.kind]['color']
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def permalink(self):
|
||||
return f"/log/{self.id}"
|
||||
|
||||
|
||||
|
||||
ACTIONTYPES={
|
||||
"ban_user":{
|
||||
"str":'banned user {self.target_link}',
|
||||
"icon":"fa-user-slash",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"unban_user":{
|
||||
"str":'unbanned user {self.target_link}',
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"club_allow":{
|
||||
"str":'allowed user {self.target_link} into the country club',
|
||||
"icon":"fa-user-slash",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"club_ban":{
|
||||
"str":'disallowed user {self.target_link} from the country club',
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"nuke_user":{
|
||||
"str":'removed all content of {self.target_link}',
|
||||
"icon":"fa-user-slash",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"unnuke_user":{
|
||||
"str":'approved all content of {self.target_link}',
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"shadowban": {
|
||||
"str": 'shadowbanned {self.target_link}',
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"unshadowban": {
|
||||
"str": 'unshadowbanned {self.target_link}',
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"agendaposter": {
|
||||
"str": "set agendaposter theme on {self.target_link}",
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"unagendaposter": {
|
||||
"str": "removed agendaposter theme from {self.target_link}",
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"set_flair_locked":{
|
||||
"str":"set {self.target_link}'s flair (locked)",
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"set_flair_notlocked":{
|
||||
"str":"set {self.target_link}'s flair (not locked)",
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"pin_comment":{
|
||||
"str":'pinned a {self.target_link}',
|
||||
"icon":"fa-thumbtack fa-rotate--45",
|
||||
"color": "bg-info",
|
||||
},
|
||||
"unpin_comment":{
|
||||
"str":'un-pinned a {self.target_link}',
|
||||
"icon":"fa-thumbtack fa-rotate--45",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"pin_post":{
|
||||
"str":'pinned post {self.target_link}',
|
||||
"icon":"fa-thumbtack fa-rotate--45",
|
||||
"color": "bg-success",
|
||||
},
|
||||
"unpin_post":{
|
||||
"str":'un-pinned post {self.target_link}',
|
||||
"icon":"fa-thumbtack fa-rotate--45",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"set_nsfw":{
|
||||
"str":'set nsfw on post {self.target_link}',
|
||||
"icon":"fa-eye-evil",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"unset_nsfw":{
|
||||
"str":'un-set nsfw on post {self.target_link}',
|
||||
"icon":"fa-eye-evil",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"ban_post":{
|
||||
"str": 'removed post {self.target_link}',
|
||||
"icon":"fa-feather-alt",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"unban_post":{
|
||||
"str": 'reinstated post {self.target_link}',
|
||||
"icon":"fa-feather-alt",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"club":{
|
||||
"str": 'marked post {self.target_link} as club-only',
|
||||
"icon":"fa-eye-slash",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"unclub":{
|
||||
"str": 'unmarked post {self.target_link} as club-only',
|
||||
"icon":"fa-eye",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"ban_comment":{
|
||||
"str": 'removed {self.target_link}',
|
||||
"icon":"fa-comment",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"unban_comment":{
|
||||
"str": 'reinstated {self.target_link}',
|
||||
"icon":"fa-comment",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
}
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship
|
||||
from files.__main__ import Base
|
||||
import time
|
||||
from files.helpers.lazy import lazy
|
||||
|
||||
class ModAction(Base):
|
||||
__tablename__ = "modactions"
|
||||
id = Column(BigInteger, primary_key=True)
|
||||
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
kind = Column(String)
|
||||
target_user_id = Column(Integer, ForeignKey("users.id"), default=0)
|
||||
target_submission_id = Column(Integer, ForeignKey("submissions.id"), default=0)
|
||||
target_comment_id = Column(Integer, ForeignKey("comments.id"), default=0)
|
||||
_note=Column(String)
|
||||
created_utc = Column(Integer, default=0)
|
||||
|
||||
user = relationship("User", primaryjoin="User.id==ModAction.user_id", viewonly=True)
|
||||
target_user = relationship("User", primaryjoin="User.id==ModAction.target_user_id", viewonly=True)
|
||||
target_post = relationship("Submission", viewonly=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if "created_utc" not in kwargs:
|
||||
kwargs["created_utc"] = int(time.time())
|
||||
|
||||
if "note" in kwargs:
|
||||
kwargs["_note"]=kwargs["note"]
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ModAction(id={self.id})>"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def age_string(self):
|
||||
|
||||
age = int(time.time()) - self.created_utc
|
||||
|
||||
if age < 60:
|
||||
return "just now"
|
||||
elif age < 3600:
|
||||
minutes = int(age / 60)
|
||||
return f"{minutes}m ago"
|
||||
elif age < 86400:
|
||||
hours = int(age / 3600)
|
||||
return f"{hours}hr ago"
|
||||
elif age < 2678400:
|
||||
days = int(age / 86400)
|
||||
return f"{days}d ago"
|
||||
|
||||
now = time.gmtime()
|
||||
ctd = time.gmtime(self.created_utc)
|
||||
|
||||
months = now.tm_mon - ctd.tm_mon + 12 * (now.tm_year - ctd.tm_year)
|
||||
if now.tm_mday < ctd.tm_mday:
|
||||
months -= 1
|
||||
|
||||
if months < 12:
|
||||
return f"{months}mo ago"
|
||||
else:
|
||||
years = int(months / 12)
|
||||
return f"{years}yr ago"
|
||||
|
||||
|
||||
@property
|
||||
def note(self):
|
||||
|
||||
if self.kind=="ban_user":
|
||||
if self.target_post: return f'for <a href="{self.target_post.permalink}">post</a>'
|
||||
elif self.target_comment_id: return f'for <a href="/comment/{self.target_comment_id}">comment</a>'
|
||||
else: return self._note
|
||||
else:
|
||||
return self._note or ""
|
||||
|
||||
@note.setter
|
||||
def note(self, x):
|
||||
self._note=x
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def string(self):
|
||||
|
||||
output = ACTIONTYPES[self.kind]["str"].format(self=self)
|
||||
|
||||
if self.note: output += f" <i>({self.note})</i>"
|
||||
|
||||
return output
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def target_link(self):
|
||||
if self.target_user: return f'<a href="{self.target_user.url}">{self.target_user.username}</a>'
|
||||
elif self.target_post: return f'<a href="{self.target_post.permalink}">{self.target_post.title.replace("<","").replace(">","")}</a>'
|
||||
elif self.target_comment_id: return f'<a href="/comment/{self.target_comment_id}">comment</a>'
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def icon(self):
|
||||
return ACTIONTYPES[self.kind]['icon']
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def color(self):
|
||||
return ACTIONTYPES[self.kind]['color']
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def permalink(self):
|
||||
return f"/log/{self.id}"
|
||||
|
||||
|
||||
|
||||
ACTIONTYPES={
|
||||
"ban_user":{
|
||||
"str":'banned user {self.target_link}',
|
||||
"icon":"fa-user-slash",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"unban_user":{
|
||||
"str":'unbanned user {self.target_link}',
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"club_allow":{
|
||||
"str":'allowed user {self.target_link} into the country club',
|
||||
"icon":"fa-user-slash",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"club_ban":{
|
||||
"str":'disallowed user {self.target_link} from the country club',
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"nuke_user":{
|
||||
"str":'removed all content of {self.target_link}',
|
||||
"icon":"fa-user-slash",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"unnuke_user":{
|
||||
"str":'approved all content of {self.target_link}',
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"shadowban": {
|
||||
"str": 'shadowbanned {self.target_link}',
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"unshadowban": {
|
||||
"str": 'unshadowbanned {self.target_link}',
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"agendaposter": {
|
||||
"str": "set agendaposter theme on {self.target_link}",
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"unagendaposter": {
|
||||
"str": "removed agendaposter theme from {self.target_link}",
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"set_flair_locked":{
|
||||
"str":"set {self.target_link}'s flair (locked)",
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"set_flair_notlocked":{
|
||||
"str":"set {self.target_link}'s flair (not locked)",
|
||||
"icon": "fa-user-slash",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"pin_comment":{
|
||||
"str":'pinned a {self.target_link}',
|
||||
"icon":"fa-thumbtack fa-rotate--45",
|
||||
"color": "bg-info",
|
||||
},
|
||||
"unpin_comment":{
|
||||
"str":'un-pinned a {self.target_link}',
|
||||
"icon":"fa-thumbtack fa-rotate--45",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"pin_post":{
|
||||
"str":'pinned post {self.target_link}',
|
||||
"icon":"fa-thumbtack fa-rotate--45",
|
||||
"color": "bg-success",
|
||||
},
|
||||
"unpin_post":{
|
||||
"str":'un-pinned post {self.target_link}',
|
||||
"icon":"fa-thumbtack fa-rotate--45",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"set_nsfw":{
|
||||
"str":'set nsfw on post {self.target_link}',
|
||||
"icon":"fa-eye-evil",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"unset_nsfw":{
|
||||
"str":'un-set nsfw on post {self.target_link}',
|
||||
"icon":"fa-eye-evil",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"ban_post":{
|
||||
"str": 'removed post {self.target_link}',
|
||||
"icon":"fa-feather-alt",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"unban_post":{
|
||||
"str": 'reinstated post {self.target_link}',
|
||||
"icon":"fa-feather-alt",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"club":{
|
||||
"str": 'marked post {self.target_link} as club-only',
|
||||
"icon":"fa-eye-slash",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"unclub":{
|
||||
"str": 'unmarked post {self.target_link} as club-only',
|
||||
"icon":"fa-eye",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
"ban_comment":{
|
||||
"str": 'removed {self.target_link}',
|
||||
"icon":"fa-comment",
|
||||
"color": "bg-danger",
|
||||
},
|
||||
"unban_comment":{
|
||||
"str": 'reinstated {self.target_link}',
|
||||
"icon":"fa-comment",
|
||||
"color": "bg-muted",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -233,11 +233,11 @@ class Submission(Base):
|
|||
@property
|
||||
@lazy
|
||||
def thumb_url(self):
|
||||
if self.over_18: return f"https://{site}/assets/images/nsfw.webp"
|
||||
elif not self.url: return f"https://{site}/assets/images/{site_name}/default_thumb_text.webp"
|
||||
if self.over_18: return f"https://{site}/assets/images/nsfw.gif"
|
||||
elif not self.url: return f"https://{site}/assets/images/{site_name}/default_thumb_text.gif"
|
||||
elif self.thumburl: return self.thumburl
|
||||
elif "youtu.be" in self.domain or "youtube.com" in self.domain: return f"https://{site}/assets/images/default_thumb_yt.webp"
|
||||
else: return f"https://{site}/assets/images/default_thumb_link.webp"
|
||||
elif "youtu.be" in self.domain or "youtube.com" in self.domain: return f"https://{site}/assets/images/default_thumb_yt.gif"
|
||||
else: return f"https://{site}/assets/images/default_thumb_link.gif"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
|
@ -266,6 +266,7 @@ class Submission(Base):
|
|||
'upvotes': self.upvotes,
|
||||
'downvotes': self.downvotes,
|
||||
'stickied': self.stickied,
|
||||
'private' : self.private,
|
||||
'distinguish_level': self.distinguish_level,
|
||||
'voted': self.voted if hasattr(self, 'voted') else 0,
|
||||
'flags': flags,
|
||||
|
@ -398,7 +399,8 @@ class SaveRelationship(Base):
|
|||
|
||||
__tablename__="save_relationship"
|
||||
|
||||
id=Column(Integer, primary_key=true)
|
||||
user_id=Column(Integer, ForeignKey("users.id"))
|
||||
submission_id=Column(Integer, ForeignKey("submissions.id"))
|
||||
id=Column(Integer, primary_key=True)
|
||||
user_id=Column(Integer)
|
||||
submission_id=Column(Integer)
|
||||
comment_id=Column(Integer)
|
||||
type=Column(Integer)
|
|
@ -1,34 +1,34 @@
|
|||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship
|
||||
from files.__main__ import Base
|
||||
|
||||
|
||||
class Subscription(Base):
|
||||
__tablename__ = "subscriptions"
|
||||
id = Column(BigInteger, primary_key=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id"))
|
||||
submission_id = Column(BigInteger, default=0)
|
||||
|
||||
user = relationship("User", uselist=False, viewonly=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Subscription(id={self.id})>"
|
||||
|
||||
|
||||
class Follow(Base):
|
||||
__tablename__ = "follows"
|
||||
id = Column(BigInteger, primary_key=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id"))
|
||||
target_id = Column(BigInteger, ForeignKey("users.id"))
|
||||
|
||||
user = relationship("User", uselist=False, primaryjoin="User.id==Follow.user_id", viewonly=True)
|
||||
target = relationship("User", primaryjoin="User.id==Follow.target_id", viewonly=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship
|
||||
from files.__main__ import Base
|
||||
|
||||
|
||||
class Subscription(Base):
|
||||
__tablename__ = "subscriptions"
|
||||
id = Column(BigInteger, primary_key=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id"))
|
||||
submission_id = Column(BigInteger, default=0)
|
||||
|
||||
user = relationship("User", uselist=False, viewonly=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Subscription(id={self.id})>"
|
||||
|
||||
|
||||
class Follow(Base):
|
||||
__tablename__ = "follows"
|
||||
id = Column(BigInteger, primary_key=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id"))
|
||||
target_id = Column(BigInteger, ForeignKey("users.id"))
|
||||
|
||||
user = relationship("User", uselist=False, primaryjoin="User.id==Follow.user_id", viewonly=True)
|
||||
target = relationship("User", primaryjoin="User.id==Follow.target_id", viewonly=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Follow(id={self.id})>"
|
File diff suppressed because it is too large
Load Diff
|
@ -1,24 +1,16 @@
|
|||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship
|
||||
from files.__main__ import Base
|
||||
from files.helpers.lazy import lazy
|
||||
import time
|
||||
|
||||
class UserBlock(Base):
|
||||
|
||||
__tablename__ = "userblocks"
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
target_id = Column(Integer, ForeignKey("users.id"))
|
||||
|
||||
user = relationship("User", primaryjoin="User.id==UserBlock.user_id", viewonly=True)
|
||||
target = relationship("User", primaryjoin="User.id==UserBlock.target_id", viewonly=True)
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
return f"<UserBlock(user={user.username}, target={target.username})>"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def created_date(self):
|
||||
return time.strftime("%d %b %Y", time.gmtime(self.created_utc))
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship
|
||||
from files.__main__ import Base
|
||||
|
||||
class UserBlock(Base):
|
||||
|
||||
__tablename__ = "userblocks"
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
target_id = Column(Integer, ForeignKey("users.id"))
|
||||
|
||||
user = relationship("User", primaryjoin="User.id==UserBlock.user_id", viewonly=True)
|
||||
target = relationship("User", primaryjoin="User.id==UserBlock.target_id", viewonly=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<UserBlock(user={self.user_id}, target={self.target_id})>"
|
|
@ -1,84 +1,84 @@
|
|||
from flask import *
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship
|
||||
from files.__main__ import Base
|
||||
from files.helpers.lazy import lazy
|
||||
|
||||
class Vote(Base):
|
||||
|
||||
__tablename__ = "votes"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
vote_type = Column(Integer)
|
||||
submission_id = Column(Integer, ForeignKey("submissions.id"))
|
||||
app_id = Column(Integer, ForeignKey("oauth_apps.id"))
|
||||
|
||||
user = relationship("User", lazy="subquery", viewonly=True)
|
||||
post = relationship("Submission", lazy="subquery", viewonly=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Vote(id={self.id})>"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def json_core(self):
|
||||
data={
|
||||
"user_id": self.user_id,
|
||||
"submission_id":self.submission_id,
|
||||
"vote_type":self.vote_type
|
||||
}
|
||||
return data
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def json(self):
|
||||
data=self.json_core
|
||||
data["user"]=self.user.json_core
|
||||
data["post"]=self.post.json_core
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class CommentVote(Base):
|
||||
|
||||
__tablename__ = "commentvotes"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
vote_type = Column(Integer)
|
||||
comment_id = Column(Integer, ForeignKey("comments.id"))
|
||||
app_id = Column(Integer, ForeignKey("oauth_apps.id"))
|
||||
|
||||
user = relationship("User", lazy="subquery", viewonly=True)
|
||||
comment = relationship("Comment", lazy="subquery", viewonly=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<CommentVote(id={self.id})>"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def json_core(self):
|
||||
data={
|
||||
"user_id": self.user_id,
|
||||
"comment_id":self.comment_id,
|
||||
"vote_type":self.vote_type
|
||||
}
|
||||
return data
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def json(self):
|
||||
data=self.json_core
|
||||
data["user"]=self.user.json_core
|
||||
data["comment"]=self.comment.json_core
|
||||
|
||||
from flask import *
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import relationship
|
||||
from files.__main__ import Base
|
||||
from files.helpers.lazy import lazy
|
||||
|
||||
class Vote(Base):
|
||||
|
||||
__tablename__ = "votes"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
vote_type = Column(Integer)
|
||||
submission_id = Column(Integer, ForeignKey("submissions.id"))
|
||||
app_id = Column(Integer, ForeignKey("oauth_apps.id"))
|
||||
|
||||
user = relationship("User", lazy="subquery", viewonly=True)
|
||||
post = relationship("Submission", lazy="subquery", viewonly=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Vote(id={self.id})>"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def json_core(self):
|
||||
data={
|
||||
"user_id": self.user_id,
|
||||
"submission_id":self.submission_id,
|
||||
"vote_type":self.vote_type
|
||||
}
|
||||
return data
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def json(self):
|
||||
data=self.json_core
|
||||
data["user"]=self.user.json_core
|
||||
data["post"]=self.post.json_core
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class CommentVote(Base):
|
||||
|
||||
__tablename__ = "commentvotes"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
vote_type = Column(Integer)
|
||||
comment_id = Column(Integer, ForeignKey("comments.id"))
|
||||
app_id = Column(Integer, ForeignKey("oauth_apps.id"))
|
||||
|
||||
user = relationship("User", lazy="subquery", viewonly=True)
|
||||
comment = relationship("Comment", lazy="subquery", viewonly=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<CommentVote(id={self.id})>"
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def json_core(self):
|
||||
data={
|
||||
"user_id": self.user_id,
|
||||
"comment_id":self.comment_id,
|
||||
"vote_type":self.vote_type
|
||||
}
|
||||
return data
|
||||
|
||||
@property
|
||||
@lazy
|
||||
def json(self):
|
||||
data=self.json_core
|
||||
data["user"]=self.user.json_core
|
||||
data["comment"]=self.comment.json_core
|
||||
|
||||
return data
|
|
@ -1,137 +1,137 @@
|
|||
import mistletoe
|
||||
|
||||
from files.classes import *
|
||||
from flask import g
|
||||
from .markdown import *
|
||||
from .sanitize import *
|
||||
from .const import *
|
||||
|
||||
|
||||
def send_notification(vid, user, text):
|
||||
|
||||
if isinstance(user, int):
|
||||
uid = user
|
||||
else:
|
||||
uid = user.id
|
||||
|
||||
text = text.replace('r/', 'r\/').replace('u/', 'u\/')
|
||||
text_html = CustomRenderer().render(mistletoe.Document(text))
|
||||
|
||||
text_html = sanitize(text_html)
|
||||
|
||||
new_comment = Comment(author_id=vid,
|
||||
parent_submission=None,
|
||||
distinguish_level=6,
|
||||
body=text,
|
||||
body_html=text_html,
|
||||
notifiedto=uid
|
||||
)
|
||||
g.db.add(new_comment)
|
||||
|
||||
g.db.flush()
|
||||
|
||||
notif = Notification(comment_id=new_comment.id,
|
||||
user_id=uid)
|
||||
g.db.add(notif)
|
||||
|
||||
|
||||
def send_follow_notif(vid, user, text):
|
||||
|
||||
text_html = CustomRenderer().render(mistletoe.Document(text))
|
||||
text_html = sanitize(text_html)
|
||||
|
||||
new_comment = Comment(author_id=NOTIFICATIONS_ACCOUNT,
|
||||
parent_submission=None,
|
||||
distinguish_level=6,
|
||||
body=text,
|
||||
body_html=text_html,
|
||||
)
|
||||
g.db.add(new_comment)
|
||||
g.db.flush()
|
||||
|
||||
notif = Notification(comment_id=new_comment.id,
|
||||
user_id=user,
|
||||
followsender=vid)
|
||||
g.db.add(notif)
|
||||
|
||||
def send_unfollow_notif(vid, user, text):
|
||||
|
||||
text_html = CustomRenderer().render(mistletoe.Document(text))
|
||||
text_html = sanitize(text_html)
|
||||
|
||||
new_comment = Comment(author_id=NOTIFICATIONS_ACCOUNT,
|
||||
parent_submission=None,
|
||||
distinguish_level=6,
|
||||
body=text,
|
||||
body_html=text_html,
|
||||
)
|
||||
g.db.add(new_comment)
|
||||
g.db.flush()
|
||||
|
||||
notif = Notification(comment_id=new_comment.id,
|
||||
user_id=user,
|
||||
unfollowsender=vid)
|
||||
g.db.add(notif)
|
||||
|
||||
def send_block_notif(vid, user, text):
|
||||
|
||||
text_html = CustomRenderer().render(mistletoe.Document(text))
|
||||
text_html = sanitize(text_html)
|
||||
|
||||
new_comment = Comment(author_id=NOTIFICATIONS_ACCOUNT,
|
||||
parent_submission=None,
|
||||
distinguish_level=6,
|
||||
body=text,
|
||||
body_html=text_html,
|
||||
)
|
||||
g.db.add(new_comment)
|
||||
g.db.flush()
|
||||
|
||||
notif = Notification(comment_id=new_comment.id,
|
||||
user_id=user,
|
||||
blocksender=vid)
|
||||
g.db.add(notif)
|
||||
|
||||
def send_unblock_notif(vid, user, text):
|
||||
|
||||
text_html = CustomRenderer().render(mistletoe.Document(text))
|
||||
text_html = sanitize(text_html)
|
||||
|
||||
new_comment = Comment(author_id=NOTIFICATIONS_ACCOUNT,
|
||||
parent_submission=None,
|
||||
distinguish_level=6,
|
||||
body=text,
|
||||
body_html=text_html,
|
||||
)
|
||||
g.db.add(new_comment)
|
||||
g.db.flush()
|
||||
|
||||
notif = Notification(comment_id=new_comment.id,
|
||||
user_id=user,
|
||||
unblocksender=vid)
|
||||
g.db.add(notif)
|
||||
|
||||
|
||||
|
||||
def send_admin(vid, text):
|
||||
|
||||
text = re.sub('([^\n])\n([^\n])', r'\1\n\n\2', text)
|
||||
|
||||
text_html = Renderer().render(mistletoe.Document(text))
|
||||
|
||||
text_html = sanitize(text_html, True)
|
||||
|
||||
new_comment = Comment(author_id=vid,
|
||||
parent_submission=None,
|
||||
level=1,
|
||||
sentto=0,
|
||||
body=text,
|
||||
body_html=text_html,
|
||||
)
|
||||
g.db.add(new_comment)
|
||||
g.db.flush()
|
||||
|
||||
admins = g.db.query(User).options(lazyload('*')).filter(User.admin_level > 0).all()
|
||||
for admin in admins:
|
||||
notif = Notification(comment_id=new_comment.id, user_id=admin.id)
|
||||
g.db.add(notif)
|
||||
import mistletoe
|
||||
|
||||
from files.classes import *
|
||||
from flask import g
|
||||
from .markdown import *
|
||||
from .sanitize import *
|
||||
from .const import *
|
||||
|
||||
|
||||
def send_notification(vid, user, text):
|
||||
|
||||
if isinstance(user, int):
|
||||
uid = user
|
||||
else:
|
||||
uid = user.id
|
||||
|
||||
text = text.replace('r/', 'r\/').replace('u/', 'u\/')
|
||||
text_html = CustomRenderer().render(mistletoe.Document(text))
|
||||
|
||||
text_html = sanitize(text_html)
|
||||
|
||||
new_comment = Comment(author_id=vid,
|
||||
parent_submission=None,
|
||||
distinguish_level=6,
|
||||
body=text,
|
||||
body_html=text_html,
|
||||
notifiedto=uid
|
||||
)
|
||||
g.db.add(new_comment)
|
||||
|
||||
g.db.flush()
|
||||
|
||||
notif = Notification(comment_id=new_comment.id,
|
||||
user_id=uid)
|
||||
g.db.add(notif)
|
||||
|
||||
|
||||
def send_follow_notif(vid, user, text):
|
||||
|
||||
text_html = CustomRenderer().render(mistletoe.Document(text))
|
||||
text_html = sanitize(text_html)
|
||||
|
||||
new_comment = Comment(author_id=NOTIFICATIONS_ACCOUNT,
|
||||
parent_submission=None,
|
||||
distinguish_level=6,
|
||||
body=text,
|
||||
body_html=text_html,
|
||||
)
|
||||
g.db.add(new_comment)
|
||||
g.db.flush()
|
||||
|
||||
notif = Notification(comment_id=new_comment.id,
|
||||
user_id=user,
|
||||
followsender=vid)
|
||||
g.db.add(notif)
|
||||
|
||||
def send_unfollow_notif(vid, user, text):
|
||||
|
||||
text_html = CustomRenderer().render(mistletoe.Document(text))
|
||||
text_html = sanitize(text_html)
|
||||
|
||||
new_comment = Comment(author_id=NOTIFICATIONS_ACCOUNT,
|
||||
parent_submission=None,
|
||||
distinguish_level=6,
|
||||
body=text,
|
||||
body_html=text_html,
|
||||
)
|
||||
g.db.add(new_comment)
|
||||
g.db.flush()
|
||||
|
||||
notif = Notification(comment_id=new_comment.id,
|
||||
user_id=user,
|
||||
unfollowsender=vid)
|
||||
g.db.add(notif)
|
||||
|
||||
def send_block_notif(vid, user, text):
|
||||
|
||||
text_html = CustomRenderer().render(mistletoe.Document(text))
|
||||
text_html = sanitize(text_html)
|
||||
|
||||
new_comment = Comment(author_id=NOTIFICATIONS_ACCOUNT,
|
||||
parent_submission=None,
|
||||
distinguish_level=6,
|
||||
body=text,
|
||||
body_html=text_html,
|
||||
)
|
||||
g.db.add(new_comment)
|
||||
g.db.flush()
|
||||
|
||||
notif = Notification(comment_id=new_comment.id,
|
||||
user_id=user,
|
||||
blocksender=vid)
|
||||
g.db.add(notif)
|
||||
|
||||
def send_unblock_notif(vid, user, text):
|
||||
|
||||
text_html = CustomRenderer().render(mistletoe.Document(text))
|
||||
text_html = sanitize(text_html)
|
||||
|
||||
new_comment = Comment(author_id=NOTIFICATIONS_ACCOUNT,
|
||||
parent_submission=None,
|
||||
distinguish_level=6,
|
||||
body=text,
|
||||
body_html=text_html,
|
||||
)
|
||||
g.db.add(new_comment)
|
||||
g.db.flush()
|
||||
|
||||
notif = Notification(comment_id=new_comment.id,
|
||||
user_id=user,
|
||||
unblocksender=vid)
|
||||
g.db.add(notif)
|
||||
|
||||
|
||||
|
||||
def send_admin(vid, text):
|
||||
|
||||
text = re.sub('([^\n])\n([^\n])', r'\1\n\n\2', text)
|
||||
|
||||
text_html = Renderer().render(mistletoe.Document(text))
|
||||
|
||||
text_html = sanitize(text_html, True)
|
||||
|
||||
new_comment = Comment(author_id=vid,
|
||||
parent_submission=None,
|
||||
level=1,
|
||||
sentto=0,
|
||||
body=text,
|
||||
body_html=text_html,
|
||||
)
|
||||
g.db.add(new_comment)
|
||||
g.db.flush()
|
||||
|
||||
admins = g.db.query(User).options(lazyload('*')).filter(User.admin_level > 0).all()
|
||||
for admin in admins:
|
||||
notif = Notification(comment_id=new_comment.id, user_id=admin.id)
|
||||
g.db.add(notif)
|
||||
|
|
|
@ -4,9 +4,9 @@ site = environ.get("DOMAIN", '').strip()
|
|||
|
||||
SLURS = {
|
||||
"faggot": "cute twink",
|
||||
"fag": " cute twink",
|
||||
"fag": "cute twink",
|
||||
"pedophile": "libertarian",
|
||||
"pedo": " libertarian",
|
||||
"pedo": "libertarian",
|
||||
"kill yourself": "keep yourself safe",
|
||||
"nigger": "🏀",
|
||||
"rapist": "male feminist",
|
||||
|
@ -14,18 +14,139 @@ SLURS = {
|
|||
"trannie": "🚂🚃🚃",
|
||||
"tranny": "🚂🚃🚃",
|
||||
"troon": "🚂🚃🚃",
|
||||
"NoNewNormal": " HorseDewormerAddicts",
|
||||
"kike": " https://sciencedirect.com/science/article/abs/pii/S016028960600033X",
|
||||
"NoNewNormal": "HorseDewormerAddicts",
|
||||
"nonewnormal": "HorseDewormerAddicts",
|
||||
"kike": "https://sciencedirect.com/science/article/abs/pii/S016028960600033X",
|
||||
"retard": "r-slur",
|
||||
"janny": " j-slur",
|
||||
"jannie": " j-slur",
|
||||
"janny": " j-slur",
|
||||
"janny": "j-slur",
|
||||
"jannie": "j-slur",
|
||||
"janny": "j-slur",
|
||||
"latinos": "latinx",
|
||||
"latino": "latinx",
|
||||
"latinas": "latinx",
|
||||
"latina": "latinx",
|
||||
"hispanics": "latinx",
|
||||
"hispanic": "latinx",
|
||||
" uss liberty incident":" tragic accident aboard the USS Liberty",
|
||||
" USS Liberty Incident":" tragic accident aboard the USS Liberty",
|
||||
" USS Liberty incident":" tragic accident aboard the USS Liberty",
|
||||
" USS Liberty Incident":" tragic accident aboard the USS Liberty",
|
||||
" uss Liberty incident":" tragic accident aboard the USS Liberty",
|
||||
" uss liberty Incident":" tragic accident aboard the USS Liberty",
|
||||
" USS LIBERTY INCIDENT":" TRAGIC ACCIDENT ABOARD THE USS LIBERTY",
|
||||
" lavon affair":" Lavon Misunderstanding",
|
||||
" Lavon affair":" Lavon Misunderstanding",
|
||||
" Lavon Affair":" Lavon Misunderstanding",
|
||||
" lavon Affair":" Lavon Misunderstanding",
|
||||
" shylock":" Israeli friend",
|
||||
" Shylock":" Israeli friend",
|
||||
" SHYLOCK":" ISRAELI FRIEND",
|
||||
" yid":" Israeli friend",
|
||||
" Yid":" Israeli friend",
|
||||
" YID":" ISRAELI FRIEND",
|
||||
" heeb":" Israeli friend",
|
||||
" Heeb":" Israeli friend",
|
||||
" HEEB":" ISRAELI FRIEND",
|
||||
" sheeny":" Israeli friend",
|
||||
" Sheeny":" Israeli friend",
|
||||
" SHEENY":" ISRAELI FRIEND",
|
||||
" sheenies":" Israeli friends",
|
||||
" Sheenies":" Israeli friends",
|
||||
" SHEENIES":" ISRAELI FRIENDS",
|
||||
" hymie":" Israeli friend",
|
||||
" Hymie":" Israeli friend",
|
||||
" HYMIES":" ISRAELI FRIENDS",
|
||||
" allah":" Allah (SWT)",
|
||||
" Allah":" Allah (SWT)",
|
||||
" ALLAH":" ALLAH (SWT)",
|
||||
" Mohammad":" Mohammad (PBUH)",
|
||||
" Muhammad":" Mohammad (PBUH)",
|
||||
" Mohammed":" Mohammad (PBUH)",
|
||||
" Muhammed":" Mohammad (PBUH)",
|
||||
" mohammad":" Mohammad (PBUH)",
|
||||
" mohammed":" Mohammad (PBUH)",
|
||||
" muhammad":" Mohammad (PBUH)",
|
||||
" muhammed":" Mohammad (PBUH)",
|
||||
" I HATE MARSEY":" I LOVE MARSEY",
|
||||
" i hate marsey":" i love marsey",
|
||||
" I hate Marsey":" I love Marsey",
|
||||
" I hate marsey":" I love Marsey",
|
||||
" libertarian":" pedophile",
|
||||
" Libertarian":" Pedophile",
|
||||
" LIBERTARIAN":" PEDOPHILE",
|
||||
" Billie Eilish":" Billie Eilish (fat cow)",
|
||||
" billie eilish":" bilie eilish (fat cow)",
|
||||
" BILLIE EILISH":" BILIE EILISH (FAT COW)",
|
||||
" dancing Israelis":" I love Israel",
|
||||
" dancing israelis":" i love israel",
|
||||
" DANCING ISRAELIS":" I LOVE ISRAEL",
|
||||
" Dancing Israelis":" I love Israel",
|
||||
" sodomite":" total dreamboat",
|
||||
" Sodomite":" Total dreamboat",
|
||||
" pajeet":" sexy Indian dude",
|
||||
" Pajeet":" Sexy Indian dude",
|
||||
" PAJEET":" SEXY INDIAN DUDE",
|
||||
" female":" birthing person",
|
||||
" Female":" Womb-haver",
|
||||
" FEMALE":" birthing person",
|
||||
" landlord":" landchad",
|
||||
" Landlord":" Landchad",
|
||||
" LANDLORD":" LANDCHAD",
|
||||
" tenant":" renthog",
|
||||
" Tenant":" Renthog",
|
||||
" TENANT":" RENTHOG",
|
||||
" renter":" rentoid",
|
||||
" Renter":" Rentoid",
|
||||
" RENTER":" RENTOID",
|
||||
" autistic":" neurodivergent",
|
||||
" Autistic":" Neurodivergent",
|
||||
" AUTISTIC":" NEURODIVERGENT",
|
||||
" anime":" p-dophilic japanese cartoons",
|
||||
" Anime":" P-dophilic Japanese cartoons",
|
||||
" ANIME":" P-DOPHILIC JAPANESE CARTOONS",
|
||||
" holohoax":" I tried to claim the Holocaust didn't happen because I am a pencil-dicked imbecile and the word filter caught me lol",
|
||||
" Holohoax":" I tried to claim the Holocaust didn't happen because I am a pencil-dicked imbecile and the word filter caught me lol",
|
||||
" HOLOHOAX":" I tried to claim the Holocaust didn't happen because I am a pencil-dicked imbecile and the word filter caught me lol",
|
||||
" groomercord":" discord (actually a pretty cool service)",
|
||||
" Groomercord":" Discord (actually a pretty cool service)",
|
||||
" GROOMERCORD":" DISCORD (ACTUALLY A PRETTY COOL SERVICE)",
|
||||
" pedocord":" discord (actually a pretty cool service)",
|
||||
" Pedocord":" Discord (actually a pretty cool service)",
|
||||
" PEDOCORD":" DISCORD (ACTUALLY A PRETTY COOL SERVICE)",
|
||||
" i hate carp":" i love carp",
|
||||
" I hate carp":" I love carp",
|
||||
" I HATE CARP":" I LOVE CARP",
|
||||
" I hate Carp":" I love Carp",
|
||||
" manlet":" little king",
|
||||
" Manlet":" Little king",
|
||||
" MANLET":" LITTLE KING",
|
||||
" gamer":" g*mer",
|
||||
" Gamer":" G*mer",
|
||||
" GAMER":" G*MER",
|
||||
" journalist":" journ*list",
|
||||
" Journalist":" Journ*list",
|
||||
" JOURNALIST":" JOURN*LIST",
|
||||
" journalism":" journ*lism",
|
||||
" Journalism":" Journ*lism",
|
||||
" JOURNALISM":" JOURN*LISM",
|
||||
" buttcheeks":" bulva",
|
||||
" Buttcheeks":" Bulva",
|
||||
" BUTTCHEEKS":" BULVA",
|
||||
" asscheeks":" bulva",
|
||||
" Asscheeks":" bulva",
|
||||
" ASSCHEEKS":" BULVA",
|
||||
" wuhan flu":" SARS-CoV-2 syndemic",
|
||||
" Wuhan flu":" SARS-CoV-2 syndemic",
|
||||
" Wuhan Flu":" SARS-CoV-2 syndemic",
|
||||
" china flu":" SARS-CoV-2 syndemic",
|
||||
" China flu":" SARS-CoV-2 syndemic",
|
||||
" China Flu":" SARS-CoV-2 syndemic",
|
||||
" china virus":" SARS-CoV-2 syndemic",
|
||||
" China virus":" SARS-CoV-2 syndemic",
|
||||
" China Virus":" SARS-CoV-2 syndemic",
|
||||
" kung flu":" SARS-CoV-2 syndemic",
|
||||
" Kung flu":" SARS-CoV-2 syndemic",
|
||||
" Kung Flu":" SARS-CoV-2 syndemic",
|
||||
|
||||
# if the word has spaces in the beginning and the end it will only censor this word without prefixes or suffixes
|
||||
" nig ": "🏀",
|
||||
|
@ -53,12 +174,28 @@ Thank you."""
|
|||
|
||||
BASED_MSG = "@{username}'s Based Count has increased by 1. Their Based Count is now {basedcount}.\n\nPills: {pills}"
|
||||
|
||||
BASEDBOT_ACCOUNT = 800
|
||||
NOTIFICATIONS_ACCOUNT = 1046
|
||||
if site == "pcmemes.net": AUTOJANNY_ACCOUNT = 1050
|
||||
else: AUTOJANNY_ACCOUNT = 2360
|
||||
LONGPOSTBOT_ACCOUNT = 1832
|
||||
AUTOPOLLER_ACCOUNT = 3369
|
||||
if site == "pcmemes.net":
|
||||
BASEDBOT_ACCOUNT = 800
|
||||
NOTIFICATIONS_ACCOUNT = 1046
|
||||
AUTOJANNY_ACCOUNT = 1050
|
||||
SNAPPY_ACCOUNT = 261
|
||||
LONGPOSTBOT_ACCOUNT = 1832
|
||||
ZOZBOT_ACCOUNT = 1833
|
||||
AUTOPOLLER_ACCOUNT = 3369
|
||||
elif site == 'rdrama.net':
|
||||
NOTIFICATIONS_ACCOUNT = 1046
|
||||
AUTOJANNY_ACCOUNT = 2360
|
||||
SNAPPY_ACCOUNT = 261
|
||||
LONGPOSTBOT_ACCOUNT = 1832
|
||||
ZOZBOT_ACCOUNT = 1833
|
||||
AUTOPOLLER_ACCOUNT = 3369
|
||||
else:
|
||||
NOTIFICATIONS_ACCOUNT = 1
|
||||
AUTOJANNY_ACCOUNT = 2
|
||||
SNAPPY_ACCOUNT = 3
|
||||
LONGPOSTBOT_ACCOUNT = 4
|
||||
ZOZBOT_ACCOUNT = 5
|
||||
AUTOPOLLER_ACCOUNT = 6
|
||||
|
||||
PUSHER_INSTANCE_ID = '02ddcc80-b8db-42be-9022-44c546b4dce6'
|
||||
PUSHER_KEY = environ.get("PUSHER_KEY", "").strip()
|
||||
|
|
|
@ -1,64 +1,64 @@
|
|||
from os import environ
|
||||
import requests
|
||||
import threading
|
||||
|
||||
SERVER_ID = environ.get("DISCORD_SERVER_ID",'').strip()
|
||||
CLIENT_ID = environ.get("DISCORD_CLIENT_ID",'').strip()
|
||||
CLIENT_SECRET = environ.get("DISCORD_CLIENT_SECRET",'').strip()
|
||||
BOT_TOKEN = environ.get("DISCORD_BOT_TOKEN",'').strip()
|
||||
AUTH = environ.get("DISCORD_AUTH",'').strip()
|
||||
|
||||
ROLES={
|
||||
"shrigma": "864612849199480914",
|
||||
"admin": "879459632656048180" if environ.get("DOMAIN") == "pcmemes.net" else "846509661288267776",
|
||||
"linked": "890342909390520382",
|
||||
"1": "868129042346414132",
|
||||
"2": "875569477671067688",
|
||||
"3": "869434199575236649",
|
||||
"4": "868140288013664296",
|
||||
"5": "880445545771044884",
|
||||
"8": "886781932430565418",
|
||||
}
|
||||
|
||||
def discord_wrap(f):
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
user=args[0]
|
||||
if not user.discord_id:
|
||||
return
|
||||
|
||||
|
||||
thread=threading.Thread(target=f, args=args, kwargs=kwargs)
|
||||
thread.start()
|
||||
|
||||
wrapper.__name__=f.__name__
|
||||
return wrapper
|
||||
|
||||
|
||||
|
||||
@discord_wrap
|
||||
def add_role(user, role_name):
|
||||
role_id = ROLES[role_name]
|
||||
url = f"https://discordapp.com/api/guilds/{SERVER_ID}/members/{user.discord_id}/roles/{role_id}"
|
||||
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
|
||||
requests.put(url, headers=headers)
|
||||
|
||||
@discord_wrap
|
||||
def remove_user(user):
|
||||
url=f"https://discordapp.com/api/guilds/{SERVER_ID}/members/{user.discord_id}"
|
||||
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
|
||||
requests.delete(url, headers=headers)
|
||||
|
||||
@discord_wrap
|
||||
def set_nick(user, nick):
|
||||
url=f"https://discordapp.com/api/guilds/{SERVER_ID}/members/{user.discord_id}"
|
||||
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
|
||||
data={"nick": nick}
|
||||
requests.patch(url, headers=headers, json=data)
|
||||
|
||||
def send_message(message):
|
||||
url=f"https://discordapp.com/api/channels/851846904283267094/messages"
|
||||
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
|
||||
data={"content": message}
|
||||
from os import environ
|
||||
import requests
|
||||
import threading
|
||||
|
||||
SERVER_ID = environ.get("DISCORD_SERVER_ID",'').strip()
|
||||
CLIENT_ID = environ.get("DISCORD_CLIENT_ID",'').strip()
|
||||
CLIENT_SECRET = environ.get("DISCORD_CLIENT_SECRET",'').strip()
|
||||
BOT_TOKEN = environ.get("DISCORD_BOT_TOKEN",'').strip()
|
||||
AUTH = environ.get("DISCORD_AUTH",'').strip()
|
||||
|
||||
ROLES={
|
||||
"shrigma": "864612849199480914",
|
||||
"admin": "879459632656048180" if environ.get("DOMAIN") == "pcmemes.net" else "846509661288267776",
|
||||
"linked": "890342909390520382",
|
||||
"1": "868129042346414132",
|
||||
"2": "875569477671067688",
|
||||
"3": "869434199575236649",
|
||||
"4": "868140288013664296",
|
||||
"5": "880445545771044884",
|
||||
"8": "886781932430565418",
|
||||
}
|
||||
|
||||
def discord_wrap(f):
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
user=args[0]
|
||||
if not user.discord_id:
|
||||
return
|
||||
|
||||
|
||||
thread=threading.Thread(target=f, args=args, kwargs=kwargs)
|
||||
thread.start()
|
||||
|
||||
wrapper.__name__=f.__name__
|
||||
return wrapper
|
||||
|
||||
|
||||
|
||||
@discord_wrap
|
||||
def add_role(user, role_name):
|
||||
role_id = ROLES[role_name]
|
||||
url = f"https://discordapp.com/api/guilds/{SERVER_ID}/members/{user.discord_id}/roles/{role_id}"
|
||||
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
|
||||
requests.put(url, headers=headers)
|
||||
|
||||
@discord_wrap
|
||||
def remove_user(user):
|
||||
url=f"https://discordapp.com/api/guilds/{SERVER_ID}/members/{user.discord_id}"
|
||||
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
|
||||
requests.delete(url, headers=headers)
|
||||
|
||||
@discord_wrap
|
||||
def set_nick(user, nick):
|
||||
url=f"https://discordapp.com/api/guilds/{SERVER_ID}/members/{user.discord_id}"
|
||||
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
|
||||
data={"nick": nick}
|
||||
requests.patch(url, headers=headers, json=data)
|
||||
|
||||
def send_message(message):
|
||||
url=f"https://discordapp.com/api/channels/851846904283267094/messages"
|
||||
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
|
||||
data={"content": message}
|
||||
requests.post(url, headers=headers, data=data)
|
|
@ -1,36 +1,33 @@
|
|||
from bs4 import BeautifulSoup
|
||||
from flask import *
|
||||
from urllib.parse import urlparse
|
||||
from files.classes import BannedDomain
|
||||
from sqlalchemy.orm import lazyload
|
||||
|
||||
def filter_comment_html(html_text):
|
||||
|
||||
soup = BeautifulSoup(html_text, features="html.parser")
|
||||
|
||||
links = soup.find_all("a")
|
||||
|
||||
domain_list = set()
|
||||
|
||||
for link in links:
|
||||
|
||||
href=link.get("href", None)
|
||||
if not href:
|
||||
continue
|
||||
|
||||
domain = urlparse(href).netloc
|
||||
|
||||
parts = domain.split(".")
|
||||
for i in range(len(parts)):
|
||||
new_domain = parts[i]
|
||||
for j in range(i + 1, len(parts)):
|
||||
new_domain += "." + parts[j]
|
||||
|
||||
domain_list.add(new_domain)
|
||||
|
||||
bans = [x for x in g.db.query(BannedDomain).options(lazyload('*')).filter(BannedDomain.domain.in_(list(domain_list))).all()]
|
||||
|
||||
if bans:
|
||||
return bans
|
||||
else:
|
||||
return []
|
||||
from bs4 import BeautifulSoup
|
||||
from flask import *
|
||||
from urllib.parse import urlparse
|
||||
from files.classes import BannedDomain
|
||||
from sqlalchemy.orm import lazyload
|
||||
|
||||
def filter_comment_html(html_text):
|
||||
|
||||
soup = BeautifulSoup(html_text, features="html.parser")
|
||||
|
||||
links = soup.find_all("a")
|
||||
|
||||
domain_list = set()
|
||||
|
||||
for link in links:
|
||||
|
||||
href = link.get("href")
|
||||
if not href: continue
|
||||
|
||||
domain = urlparse(href).netloc
|
||||
|
||||
parts = domain.split(".")
|
||||
for i in range(len(parts)):
|
||||
new_domain = parts[i]
|
||||
for j in range(i + 1, len(parts)):
|
||||
new_domain += "." + parts[j]
|
||||
|
||||
domain_list.add(new_domain)
|
||||
|
||||
bans = [x for x in g.db.query(BannedDomain).options(lazyload('*')).filter(BannedDomain.domain.in_(list(domain_list))).all()]
|
||||
|
||||
if bans: return bans
|
||||
else: return []
|
||||
|
|
|
@ -1,271 +1,271 @@
|
|||
from files.classes import *
|
||||
from flask import g
|
||||
|
||||
def get_user(username, v=None, graceful=False):
|
||||
|
||||
username = username.replace('\\', '')
|
||||
username = username.replace('_', '\_')
|
||||
username = username.replace('%', '')
|
||||
|
||||
user = g.db.query(
|
||||
User
|
||||
).filter(
|
||||
or_(
|
||||
User.username.ilike(username),
|
||||
User.original_username.ilike(username)
|
||||
)
|
||||
).first()
|
||||
|
||||
if not user:
|
||||
if not graceful:
|
||||
abort(404)
|
||||
else:
|
||||
return None
|
||||
|
||||
if v:
|
||||
block = g.db.query(UserBlock).options(lazyload('*')).filter(
|
||||
or_(
|
||||
and_(
|
||||
UserBlock.user_id == v.id,
|
||||
UserBlock.target_id == user.id
|
||||
),
|
||||
and_(UserBlock.user_id == user.id,
|
||||
UserBlock.target_id == v.id
|
||||
)
|
||||
)
|
||||
).first()
|
||||
|
||||
user.is_blocking = block and block.user_id == v.id
|
||||
user.is_blocked = block and block.target_id == v.id
|
||||
|
||||
return user
|
||||
|
||||
def get_account(id, v=None):
|
||||
|
||||
user = g.db.query(User).options(lazyload('*')).filter_by(id = id).first()
|
||||
|
||||
if not user:
|
||||
try: id = int(str(id), 36)
|
||||
except: abort(404)
|
||||
user = g.db.query(User).options(lazyload('*')).filter_by(id = id).first()
|
||||
if not user: abort(404)
|
||||
|
||||
if v:
|
||||
block = g.db.query(UserBlock).options(lazyload('*')).filter(
|
||||
or_(
|
||||
and_(
|
||||
UserBlock.user_id == v.id,
|
||||
UserBlock.target_id == user.id
|
||||
),
|
||||
and_(UserBlock.user_id == user.id,
|
||||
UserBlock.target_id == v.id
|
||||
)
|
||||
)
|
||||
).first()
|
||||
|
||||
user.is_blocking = block and block.user_id == v.id
|
||||
user.is_blocked = block and block.target_id == v.id
|
||||
|
||||
return user
|
||||
|
||||
|
||||
def get_post(i, v=None, graceful=False):
|
||||
|
||||
if v:
|
||||
vt = g.db.query(Vote).options(lazyload('*')).filter_by(
|
||||
user_id=v.id, submission_id=i).subquery()
|
||||
blocking = v.blocking.subquery()
|
||||
|
||||
items = g.db.query(
|
||||
Submission,
|
||||
vt.c.vote_type,
|
||||
blocking.c.id,
|
||||
)
|
||||
|
||||
items=items.filter(Submission.id == i
|
||||
).join(
|
||||
vt,
|
||||
vt.c.submission_id == Submission.id,
|
||||
isouter=True
|
||||
).join(
|
||||
blocking,
|
||||
blocking.c.target_id == Submission.author_id,
|
||||
isouter=True
|
||||
)
|
||||
|
||||
items=items.first()
|
||||
|
||||
if not items and not graceful:
|
||||
abort(404)
|
||||
x = items[0]
|
||||
x.voted = items[1] or 0
|
||||
x.is_blocking = items[2] or 0
|
||||
else:
|
||||
items = g.db.query(
|
||||
Submission
|
||||
).filter(Submission.id == i).first()
|
||||
if not items and not graceful:
|
||||
abort(404)
|
||||
x=items
|
||||
|
||||
return x
|
||||
|
||||
|
||||
def get_posts(pids, v=None):
|
||||
|
||||
if not pids:
|
||||
return []
|
||||
|
||||
pids=tuple(pids)
|
||||
|
||||
if v:
|
||||
vt = g.db.query(Vote).options(lazyload('*')).filter(
|
||||
Vote.submission_id.in_(pids),
|
||||
Vote.user_id==v.id
|
||||
).subquery()
|
||||
|
||||
blocking = v.blocking.subquery()
|
||||
blocked = v.blocked.subquery()
|
||||
|
||||
query = g.db.query(
|
||||
Submission,
|
||||
vt.c.vote_type,
|
||||
blocking.c.id,
|
||||
blocked.c.id,
|
||||
).filter(
|
||||
Submission.id.in_(pids)
|
||||
).join(
|
||||
vt, vt.c.submission_id==Submission.id, isouter=True
|
||||
).join(
|
||||
blocking,
|
||||
blocking.c.target_id == Submission.author_id,
|
||||
isouter=True
|
||||
).join(
|
||||
blocked,
|
||||
blocked.c.user_id == Submission.author_id,
|
||||
isouter=True
|
||||
).all()
|
||||
|
||||
output = [p[0] for p in query]
|
||||
for i in range(len(output)):
|
||||
output[i].voted = query[i][1] or 0
|
||||
output[i].is_blocking = query[i][2] or 0
|
||||
output[i].is_blocked = query[i][3] or 0
|
||||
else:
|
||||
output = g.db.query(Submission,).options(lazyload('*')).filter(Submission.id.in_(pids)).all()
|
||||
|
||||
return sorted(output, key=lambda x: pids.index(x.id))
|
||||
|
||||
def get_comment(i, v=None, graceful=False):
|
||||
|
||||
if v:
|
||||
|
||||
comment=g.db.query(Comment).options(lazyload('*')).filter(Comment.id == i).first()
|
||||
|
||||
if not comment and not graceful: abort(404)
|
||||
|
||||
block = g.db.query(UserBlock).options(lazyload('*')).filter(
|
||||
or_(
|
||||
and_(
|
||||
UserBlock.user_id == v.id,
|
||||
UserBlock.target_id == comment.author_id
|
||||
),
|
||||
and_(UserBlock.user_id == comment.author_id,
|
||||
UserBlock.target_id == v.id
|
||||
)
|
||||
)
|
||||
).first()
|
||||
|
||||
vts = g.db.query(CommentVote).options(lazyload('*')).filter_by(user_id=v.id, comment_id=comment.id)
|
||||
vt = g.db.query(CommentVote).options(lazyload('*')).filter_by(user_id=v.id, comment_id=comment.id).first()
|
||||
comment.is_blocking = block and block.user_id == v.id
|
||||
comment.is_blocked = block and block.target_id == v.id
|
||||
comment.voted = vt.vote_type if vt else 0
|
||||
|
||||
else:
|
||||
comment = g.db.query(Comment).options(lazyload('*')).filter(Comment.id == i).first()
|
||||
if not comment and not graceful:abort(404)
|
||||
|
||||
return comment
|
||||
|
||||
|
||||
def get_comments(cids, v=None, load_parent=False):
|
||||
|
||||
if not cids: return []
|
||||
|
||||
cids=tuple(cids)
|
||||
|
||||
if v:
|
||||
votes = g.db.query(CommentVote).options(lazyload('*')).filter_by(user_id=v.id).subquery()
|
||||
|
||||
blocking = v.blocking.subquery()
|
||||
|
||||
blocked = v.blocked.subquery()
|
||||
|
||||
comments = g.db.query(
|
||||
Comment,
|
||||
votes.c.vote_type,
|
||||
blocking.c.id,
|
||||
blocked.c.id,
|
||||
).filter(Comment.id.in_(cids))
|
||||
|
||||
if not (v and v.shadowbanned) and not (v and v.admin_level == 6):
|
||||
shadowbanned = [x[0] for x in g.db.query(User.id).options(lazyload('*')).filter(User.shadowbanned != None).all()]
|
||||
comments = comments.filter(Comment.author_id.notin_(shadowbanned))
|
||||
|
||||
comments = comments.join(
|
||||
votes,
|
||||
votes.c.comment_id == Comment.id,
|
||||
isouter=True
|
||||
).join(
|
||||
blocking,
|
||||
blocking.c.target_id == Comment.author_id,
|
||||
isouter=True
|
||||
).join(
|
||||
blocked,
|
||||
blocked.c.user_id == Comment.author_id,
|
||||
isouter=True
|
||||
).all()
|
||||
|
||||
output = []
|
||||
for c in comments:
|
||||
comment = c[0]
|
||||
comment.voted = c[1] or 0
|
||||
comment.is_blocking = c[2] or 0
|
||||
comment.is_blocked = c[3] or 0
|
||||
output.append(comment)
|
||||
|
||||
else:
|
||||
shadowbanned = [x[0] for x in g.db.query(User.id).options(lazyload('*')).filter(User.shadowbanned != None).all()]
|
||||
output = g.db.query(Comment).options(lazyload('*')).filter(Comment.id.in_(cids), Comment.author_id.notin_(shadowbanned)).all()
|
||||
|
||||
if load_parent:
|
||||
parents = [x.parent_comment_id for x in output if x.parent_comment_id]
|
||||
parents = get_comments(parents, v=v)
|
||||
parents = {x.id: x for x in parents}
|
||||
for c in output: c.sex = parents.get(c.parent_comment_id)
|
||||
|
||||
return sorted(output, key=lambda x: cids.index(x.id))
|
||||
|
||||
|
||||
def get_domain(s):
|
||||
|
||||
parts = s.split(".")
|
||||
domain_list = set([])
|
||||
for i in range(len(parts)):
|
||||
new_domain = parts[i]
|
||||
for j in range(i + 1, len(parts)):
|
||||
new_domain += "." + parts[j]
|
||||
|
||||
domain_list.add(new_domain)
|
||||
|
||||
domain_list = tuple(list(domain_list))
|
||||
|
||||
doms = [x for x in g.db.query(BannedDomain).options(lazyload('*')).filter(BannedDomain.domain.in_(domain_list)).all()]
|
||||
|
||||
if not doms:
|
||||
return None
|
||||
|
||||
doms = sorted(doms, key=lambda x: len(x.domain), reverse=True)
|
||||
|
||||
from files.classes import *
|
||||
from flask import g
|
||||
|
||||
def get_user(username, v=None, graceful=False):
|
||||
|
||||
username = username.replace('\\', '')
|
||||
username = username.replace('_', '\_')
|
||||
username = username.replace('%', '')
|
||||
|
||||
user = g.db.query(
|
||||
User
|
||||
).filter(
|
||||
or_(
|
||||
User.username.ilike(username),
|
||||
User.original_username.ilike(username)
|
||||
)
|
||||
).first()
|
||||
|
||||
if not user:
|
||||
if not graceful:
|
||||
abort(404)
|
||||
else:
|
||||
return None
|
||||
|
||||
if v:
|
||||
block = g.db.query(UserBlock).options(lazyload('*')).filter(
|
||||
or_(
|
||||
and_(
|
||||
UserBlock.user_id == v.id,
|
||||
UserBlock.target_id == user.id
|
||||
),
|
||||
and_(UserBlock.user_id == user.id,
|
||||
UserBlock.target_id == v.id
|
||||
)
|
||||
)
|
||||
).first()
|
||||
|
||||
user.is_blocking = block and block.user_id == v.id
|
||||
user.is_blocked = block and block.target_id == v.id
|
||||
|
||||
return user
|
||||
|
||||
def get_account(id, v=None):
|
||||
|
||||
user = g.db.query(User).options(lazyload('*')).filter_by(id = id).first()
|
||||
|
||||
if not user:
|
||||
try: id = int(str(id), 36)
|
||||
except: abort(404)
|
||||
user = g.db.query(User).options(lazyload('*')).filter_by(id = id).first()
|
||||
if not user: abort(404)
|
||||
|
||||
if v:
|
||||
block = g.db.query(UserBlock).options(lazyload('*')).filter(
|
||||
or_(
|
||||
and_(
|
||||
UserBlock.user_id == v.id,
|
||||
UserBlock.target_id == user.id
|
||||
),
|
||||
and_(UserBlock.user_id == user.id,
|
||||
UserBlock.target_id == v.id
|
||||
)
|
||||
)
|
||||
).first()
|
||||
|
||||
user.is_blocking = block and block.user_id == v.id
|
||||
user.is_blocked = block and block.target_id == v.id
|
||||
|
||||
return user
|
||||
|
||||
|
||||
def get_post(i, v=None, graceful=False):
|
||||
|
||||
if v:
|
||||
vt = g.db.query(Vote).options(lazyload('*')).filter_by(
|
||||
user_id=v.id, submission_id=i).subquery()
|
||||
blocking = v.blocking.subquery()
|
||||
|
||||
items = g.db.query(
|
||||
Submission,
|
||||
vt.c.vote_type,
|
||||
blocking.c.id,
|
||||
)
|
||||
|
||||
items=items.filter(Submission.id == i
|
||||
).join(
|
||||
vt,
|
||||
vt.c.submission_id == Submission.id,
|
||||
isouter=True
|
||||
).join(
|
||||
blocking,
|
||||
blocking.c.target_id == Submission.author_id,
|
||||
isouter=True
|
||||
)
|
||||
|
||||
items=items.first()
|
||||
|
||||
if not items and not graceful:
|
||||
abort(404)
|
||||
x = items[0]
|
||||
x.voted = items[1] or 0
|
||||
x.is_blocking = items[2] or 0
|
||||
else:
|
||||
items = g.db.query(
|
||||
Submission
|
||||
).filter(Submission.id == i).first()
|
||||
if not items and not graceful:
|
||||
abort(404)
|
||||
x=items
|
||||
|
||||
return x
|
||||
|
||||
|
||||
def get_posts(pids, v=None):
|
||||
|
||||
if not pids:
|
||||
return []
|
||||
|
||||
pids=tuple(pids)
|
||||
|
||||
if v:
|
||||
vt = g.db.query(Vote).options(lazyload('*')).filter(
|
||||
Vote.submission_id.in_(pids),
|
||||
Vote.user_id==v.id
|
||||
).subquery()
|
||||
|
||||
blocking = v.blocking.subquery()
|
||||
blocked = v.blocked.subquery()
|
||||
|
||||
query = g.db.query(
|
||||
Submission,
|
||||
vt.c.vote_type,
|
||||
blocking.c.id,
|
||||
blocked.c.id,
|
||||
).filter(
|
||||
Submission.id.in_(pids)
|
||||
).join(
|
||||
vt, vt.c.submission_id==Submission.id, isouter=True
|
||||
).join(
|
||||
blocking,
|
||||
blocking.c.target_id == Submission.author_id,
|
||||
isouter=True
|
||||
).join(
|
||||
blocked,
|
||||
blocked.c.user_id == Submission.author_id,
|
||||
isouter=True
|
||||
).all()
|
||||
|
||||
output = [p[0] for p in query]
|
||||
for i in range(len(output)):
|
||||
output[i].voted = query[i][1] or 0
|
||||
output[i].is_blocking = query[i][2] or 0
|
||||
output[i].is_blocked = query[i][3] or 0
|
||||
else:
|
||||
output = g.db.query(Submission,).options(lazyload('*')).filter(Submission.id.in_(pids)).all()
|
||||
|
||||
return sorted(output, key=lambda x: pids.index(x.id))
|
||||
|
||||
def get_comment(i, v=None, graceful=False):
|
||||
|
||||
if v:
|
||||
|
||||
comment=g.db.query(Comment).options(lazyload('*')).filter(Comment.id == i).first()
|
||||
|
||||
if not comment and not graceful: abort(404)
|
||||
|
||||
block = g.db.query(UserBlock).options(lazyload('*')).filter(
|
||||
or_(
|
||||
and_(
|
||||
UserBlock.user_id == v.id,
|
||||
UserBlock.target_id == comment.author_id
|
||||
),
|
||||
and_(UserBlock.user_id == comment.author_id,
|
||||
UserBlock.target_id == v.id
|
||||
)
|
||||
)
|
||||
).first()
|
||||
|
||||
vts = g.db.query(CommentVote).options(lazyload('*')).filter_by(user_id=v.id, comment_id=comment.id)
|
||||
vt = g.db.query(CommentVote).options(lazyload('*')).filter_by(user_id=v.id, comment_id=comment.id).first()
|
||||
comment.is_blocking = block and block.user_id == v.id
|
||||
comment.is_blocked = block and block.target_id == v.id
|
||||
comment.voted = vt.vote_type if vt else 0
|
||||
|
||||
else:
|
||||
comment = g.db.query(Comment).options(lazyload('*')).filter(Comment.id == i).first()
|
||||
if not comment and not graceful:abort(404)
|
||||
|
||||
return comment
|
||||
|
||||
|
||||
def get_comments(cids, v=None, load_parent=False):
|
||||
|
||||
if not cids: return []
|
||||
|
||||
cids=tuple(cids)
|
||||
|
||||
if v:
|
||||
votes = g.db.query(CommentVote).options(lazyload('*')).filter_by(user_id=v.id).subquery()
|
||||
|
||||
blocking = v.blocking.subquery()
|
||||
|
||||
blocked = v.blocked.subquery()
|
||||
|
||||
comments = g.db.query(
|
||||
Comment,
|
||||
votes.c.vote_type,
|
||||
blocking.c.id,
|
||||
blocked.c.id,
|
||||
).filter(Comment.id.in_(cids))
|
||||
|
||||
if not (v and v.shadowbanned) and not (v and v.admin_level == 6):
|
||||
shadowbanned = [x[0] for x in g.db.query(User.id).options(lazyload('*')).filter(User.shadowbanned != None).all()]
|
||||
comments = comments.filter(Comment.author_id.notin_(shadowbanned))
|
||||
|
||||
comments = comments.join(
|
||||
votes,
|
||||
votes.c.comment_id == Comment.id,
|
||||
isouter=True
|
||||
).join(
|
||||
blocking,
|
||||
blocking.c.target_id == Comment.author_id,
|
||||
isouter=True
|
||||
).join(
|
||||
blocked,
|
||||
blocked.c.user_id == Comment.author_id,
|
||||
isouter=True
|
||||
).all()
|
||||
|
||||
output = []
|
||||
for c in comments:
|
||||
comment = c[0]
|
||||
comment.voted = c[1] or 0
|
||||
comment.is_blocking = c[2] or 0
|
||||
comment.is_blocked = c[3] or 0
|
||||
output.append(comment)
|
||||
|
||||
else:
|
||||
shadowbanned = [x[0] for x in g.db.query(User.id).options(lazyload('*')).filter(User.shadowbanned != None).all()]
|
||||
output = g.db.query(Comment).options(lazyload('*')).filter(Comment.id.in_(cids), Comment.author_id.notin_(shadowbanned)).all()
|
||||
|
||||
if load_parent:
|
||||
parents = [x.parent_comment_id for x in output if x.parent_comment_id]
|
||||
parents = get_comments(parents, v=v)
|
||||
parents = {x.id: x for x in parents}
|
||||
for c in output: c.sex = parents.get(c.parent_comment_id)
|
||||
|
||||
return sorted(output, key=lambda x: cids.index(x.id))
|
||||
|
||||
|
||||
def get_domain(s):
|
||||
|
||||
parts = s.split(".")
|
||||
domain_list = set([])
|
||||
for i in range(len(parts)):
|
||||
new_domain = parts[i]
|
||||
for j in range(i + 1, len(parts)):
|
||||
new_domain += "." + parts[j]
|
||||
|
||||
domain_list.add(new_domain)
|
||||
|
||||
domain_list = tuple(list(domain_list))
|
||||
|
||||
doms = [x for x in g.db.query(BannedDomain).options(lazyload('*')).filter(BannedDomain.domain.in_(domain_list)).all()]
|
||||
|
||||
if not doms:
|
||||
return None
|
||||
|
||||
doms = sorted(doms, key=lambda x: len(x.domain), reverse=True)
|
||||
|
||||
return doms[0]
|
|
@ -1,27 +1,27 @@
|
|||
from PIL import Image as IImage, ImageSequence
|
||||
from webptools import gifwebp
|
||||
|
||||
def process_image(filename=None, resize=False):
|
||||
|
||||
i = IImage.open(filename)
|
||||
|
||||
if resize:
|
||||
size = 100, 100
|
||||
frames = ImageSequence.Iterator(i)
|
||||
|
||||
def thumbnails(frames):
|
||||
for frame in frames:
|
||||
thumbnail = frame.copy()
|
||||
thumbnail.thumbnail(size)
|
||||
yield thumbnail
|
||||
|
||||
frames = thumbnails(frames)
|
||||
|
||||
om = next(frames)
|
||||
om.info = i.info
|
||||
om.save(filename, format="WEBP", save_all=True, append_images=list(frames), loop=0)
|
||||
elif i.format.lower() != "webp":
|
||||
if i.format.lower() == "gif": gifwebp(input_image=filename, output_image=filename, option="-q 80")
|
||||
else: i.save(filename, format="WEBP")
|
||||
|
||||
from PIL import Image as IImage, ImageSequence
|
||||
from webptools import gifwebp
|
||||
|
||||
def process_image(filename=None, resize=False):
|
||||
|
||||
i = IImage.open(filename)
|
||||
|
||||
if resize:
|
||||
size = 100, 100
|
||||
frames = ImageSequence.Iterator(i)
|
||||
|
||||
def thumbnails(frames):
|
||||
for frame in frames:
|
||||
thumbnail = frame.copy()
|
||||
thumbnail.thumbnail(size)
|
||||
yield thumbnail
|
||||
|
||||
frames = thumbnails(frames)
|
||||
|
||||
om = next(frames)
|
||||
om.info = i.info
|
||||
om.save(filename, format="WEBP", save_all=True, append_images=list(frames), loop=0)
|
||||
elif i.format.lower() != "webp":
|
||||
if i.format.lower() == "gif": gifwebp(input_image=filename, output_image=filename, option="-q 80")
|
||||
else: i.save(filename, format="WEBP")
|
||||
|
||||
return filename
|
|
@ -1,36 +1,36 @@
|
|||
from files.__main__ import app
|
||||
from .get import *
|
||||
from files.helpers import const
|
||||
|
||||
|
||||
@app.template_filter("full_link")
|
||||
def full_link(url):
|
||||
|
||||
return f"https://{app.config['SERVER_NAME']}{url}"
|
||||
|
||||
@app.template_filter("app_config")
|
||||
def app_config(x):
|
||||
return app.config.get(x)
|
||||
|
||||
@app.template_filter("post_embed")
|
||||
def post_embed(id, v):
|
||||
|
||||
try: id = int(id)
|
||||
except: return None
|
||||
|
||||
p = get_post(id, v, graceful=True)
|
||||
|
||||
return render_template("submission_listing.html", listing=[p], v=v)
|
||||
|
||||
@app.template_filter("favorite_emojis")
|
||||
def favorite_emojis(x):
|
||||
str = ""
|
||||
emojis = sorted(x.items(), key=lambda x: x[1], reverse=True)[:25]
|
||||
for k, v in emojis:
|
||||
str += f'<button class="btn m-1 px-0 emoji2" onclick="getEmoji(\'{k}\')" style="background: None!important; width:60px; overflow: hidden; border: none;" data-bs-toggle="tooltip" title=":{k}:" delay:="0"><img loading="lazy" width=50 src="/assets/images/emojis/{k}.webp" alt="{k}-emoji"/></button>'
|
||||
return str
|
||||
|
||||
@app.context_processor
|
||||
def inject_constants():
|
||||
constants = [c for c in dir(const) if not c.startswith("_")]
|
||||
from files.__main__ import app
|
||||
from .get import *
|
||||
from files.helpers import const
|
||||
|
||||
|
||||
@app.template_filter("full_link")
|
||||
def full_link(url):
|
||||
|
||||
return f"https://{app.config['SERVER_NAME']}{url}"
|
||||
|
||||
@app.template_filter("app_config")
|
||||
def app_config(x):
|
||||
return app.config.get(x)
|
||||
|
||||
@app.template_filter("post_embed")
|
||||
def post_embed(id, v):
|
||||
|
||||
try: id = int(id)
|
||||
except: return None
|
||||
|
||||
p = get_post(id, v, graceful=True)
|
||||
|
||||
return render_template("submission_listing.html", listing=[p], v=v)
|
||||
|
||||
@app.template_filter("favorite_emojis")
|
||||
def favorite_emojis(x):
|
||||
str = ""
|
||||
emojis = sorted(x.items(), key=lambda x: x[1], reverse=True)[:25]
|
||||
for k, v in emojis:
|
||||
str += f'<button class="btn m-1 px-0 emoji2" onclick="getEmoji(\'{k}\')" style="background: None!important; width:60px; overflow: hidden; border: none;" data-bs-toggle="tooltip" title=":{k}:" delay:="0"><img loading="lazy" width=50 src="/assets/images/emojis/{k}.webp" alt="{k}-emoji"/></button>'
|
||||
return str
|
||||
|
||||
@app.context_processor
|
||||
def inject_constants():
|
||||
constants = [c for c in dir(const) if not c.startswith("_")]
|
||||
return {c:getattr(const, c) for c in constants}
|
|
@ -1,18 +1,18 @@
|
|||
# Prevents certain properties from having to be recomputed each time they
|
||||
# are referenced
|
||||
|
||||
|
||||
def lazy(f):
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
o = args[0]
|
||||
|
||||
if "_lazy" not in o.__dict__: o.__dict__["_lazy"] = {}
|
||||
|
||||
if f.__name__ not in o.__dict__["_lazy"]: o.__dict__["_lazy"][f.__name__] = f(*args, **kwargs)
|
||||
|
||||
return o.__dict__["_lazy"][f.__name__]
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
return wrapper
|
||||
# Prevents certain properties from having to be recomputed each time they
|
||||
# are referenced
|
||||
|
||||
|
||||
def lazy(f):
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
o = args[0]
|
||||
|
||||
if "_lazy" not in o.__dict__: o.__dict__["_lazy"] = {}
|
||||
|
||||
if f.__name__ not in o.__dict__["_lazy"]: o.__dict__["_lazy"][f.__name__] = f(*args, **kwargs)
|
||||
|
||||
return o.__dict__["_lazy"][f.__name__]
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
return wrapper
|
||||
|
|
|
@ -1,136 +1,136 @@
|
|||
from .get import *
|
||||
|
||||
from mistletoe.span_token import SpanToken
|
||||
from mistletoe.html_renderer import HTMLRenderer
|
||||
import re
|
||||
|
||||
from flask import g
|
||||
|
||||
|
||||
# add token/rendering for @username mentions
|
||||
|
||||
|
||||
class UserMention(SpanToken):
|
||||
|
||||
pattern = re.compile("(^|\s|\n)@((\w|-){1,25})")
|
||||
parse_inner = False
|
||||
|
||||
def __init__(self, match_obj):
|
||||
self.target = (match_obj.group(1), match_obj.group(2))
|
||||
|
||||
class SubMention(SpanToken):
|
||||
|
||||
pattern = re.compile("(^|\s|\n)r/(\w{3,25})")
|
||||
parse_inner = False
|
||||
|
||||
def __init__(self, match_obj):
|
||||
|
||||
self.target = (match_obj.group(1), match_obj.group(2))
|
||||
|
||||
class RedditorMention(SpanToken):
|
||||
|
||||
pattern = re.compile("(^|\s|\n)u/((\w|-){3,25})")
|
||||
parse_inner = False
|
||||
|
||||
def __init__(self, match_obj):
|
||||
|
||||
self.target = (match_obj.group(1), match_obj.group(2))
|
||||
|
||||
class SubMention2(SpanToken):
|
||||
|
||||
pattern = re.compile("(^|\s|\n)/r/(\w{3,25})")
|
||||
parse_inner = False
|
||||
|
||||
def __init__(self, match_obj):
|
||||
|
||||
self.target = (match_obj.group(1), match_obj.group(2))
|
||||
|
||||
class RedditorMention2(SpanToken):
|
||||
|
||||
pattern = re.compile("(^|\s|\n)/u/((\w|-){3,25})")
|
||||
parse_inner = False
|
||||
|
||||
def __init__(self, match_obj):
|
||||
|
||||
self.target = (match_obj.group(1), match_obj.group(2))
|
||||
|
||||
class CustomRenderer(HTMLRenderer):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(UserMention,
|
||||
SubMention,
|
||||
RedditorMention,
|
||||
SubMention2,
|
||||
RedditorMention2,
|
||||
)
|
||||
|
||||
for i in kwargs:
|
||||
self.__dict__[i] = kwargs[i]
|
||||
|
||||
def render_user_mention(self, token):
|
||||
space = token.target[0]
|
||||
target = token.target[1]
|
||||
|
||||
user = get_user(target, graceful=True)
|
||||
|
||||
|
||||
try:
|
||||
if g.v.admin_level == 0 and g.v.any_block_exists(user):
|
||||
return f"{space}@{target}"
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
if not user: return f"{space}@{target}"
|
||||
|
||||
return f'{space}<a href="{user.url}" class="d-inline-block mention-user" data-bs-original-name="{user.original_username}"><img loading="lazy" src="/uid/{user.id}/pic/profile" class="profile-pic-20 mr-1">@{user.username}</a>'
|
||||
|
||||
def render_sub_mention(self, token):
|
||||
space = token.target[0]
|
||||
target = token.target[1]
|
||||
return f'{space}<a href="https://old.reddit.com/r/{target}" rel="nofollow noopener noreferrer" class="d-inline-block">r/{target}</a>'
|
||||
|
||||
def render_redditor_mention(self, token):
|
||||
space = token.target[0]
|
||||
target = token.target[1]
|
||||
return f'{space}<a href="https://old.reddit.com/u/{target}" rel="nofollow noopener noreferrer" class="d-inline-block">u/{target}</a>'
|
||||
|
||||
|
||||
class Renderer(HTMLRenderer):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(UserMention,
|
||||
SubMention,
|
||||
RedditorMention,
|
||||
SubMention2,
|
||||
RedditorMention2,
|
||||
)
|
||||
|
||||
for i in kwargs:
|
||||
self.__dict__[i] = kwargs[i]
|
||||
|
||||
def render_user_mention(self, token):
|
||||
space = token.target[0]
|
||||
target = token.target[1]
|
||||
|
||||
user = get_user(target, graceful=True)
|
||||
|
||||
|
||||
try:
|
||||
if g.v.admin_level == 0 and g.v.any_block_exists(user):
|
||||
return f"{space}@{target}"
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
if not user: return f"{space}@{target}"
|
||||
|
||||
return f'{space}<a href="{user.url}" class="d-inline-block mention-user" data-bs-original-name="{user.original_username}">@{user.username}</a>'
|
||||
|
||||
def render_sub_mention(self, token):
|
||||
space = token.target[0]
|
||||
target = token.target[1]
|
||||
return f'{space}<a href="https://old.reddit.com/r/{target}" rel="nofollow noopener noreferrer" class="d-inline-block">r/{target}</a>'
|
||||
|
||||
def render_redditor_mention(self, token):
|
||||
space = token.target[0]
|
||||
target = token.target[1]
|
||||
from .get import *
|
||||
|
||||
from mistletoe.span_token import SpanToken
|
||||
from mistletoe.html_renderer import HTMLRenderer
|
||||
import re
|
||||
|
||||
from flask import g
|
||||
|
||||
|
||||
# add token/rendering for @username mentions
|
||||
|
||||
|
||||
class UserMention(SpanToken):
|
||||
|
||||
pattern = re.compile("(^|\s|\n)@((\w|-){1,25})")
|
||||
parse_inner = False
|
||||
|
||||
def __init__(self, match_obj):
|
||||
self.target = (match_obj.group(1), match_obj.group(2))
|
||||
|
||||
class SubMention(SpanToken):
|
||||
|
||||
pattern = re.compile("(^|\s|\n)r/(\w{3,25})")
|
||||
parse_inner = False
|
||||
|
||||
def __init__(self, match_obj):
|
||||
|
||||
self.target = (match_obj.group(1), match_obj.group(2))
|
||||
|
||||
class RedditorMention(SpanToken):
|
||||
|
||||
pattern = re.compile("(^|\s|\n)u/((\w|-){3,25})")
|
||||
parse_inner = False
|
||||
|
||||
def __init__(self, match_obj):
|
||||
|
||||
self.target = (match_obj.group(1), match_obj.group(2))
|
||||
|
||||
class SubMention2(SpanToken):
|
||||
|
||||
pattern = re.compile("(^|\s|\n)/r/(\w{3,25})")
|
||||
parse_inner = False
|
||||
|
||||
def __init__(self, match_obj):
|
||||
|
||||
self.target = (match_obj.group(1), match_obj.group(2))
|
||||
|
||||
class RedditorMention2(SpanToken):
|
||||
|
||||
pattern = re.compile("(^|\s|\n)/u/((\w|-){3,25})")
|
||||
parse_inner = False
|
||||
|
||||
def __init__(self, match_obj):
|
||||
|
||||
self.target = (match_obj.group(1), match_obj.group(2))
|
||||
|
||||
class CustomRenderer(HTMLRenderer):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(UserMention,
|
||||
SubMention,
|
||||
RedditorMention,
|
||||
SubMention2,
|
||||
RedditorMention2,
|
||||
)
|
||||
|
||||
for i in kwargs:
|
||||
self.__dict__[i] = kwargs[i]
|
||||
|
||||
def render_user_mention(self, token):
|
||||
space = token.target[0]
|
||||
target = token.target[1]
|
||||
|
||||
user = get_user(target, graceful=True)
|
||||
|
||||
|
||||
try:
|
||||
if g.v.admin_level == 0 and g.v.any_block_exists(user):
|
||||
return f"{space}@{target}"
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
if not user: return f"{space}@{target}"
|
||||
|
||||
return f'{space}<a href="{user.url}" class="d-inline-block mention-user" data-bs-original-name="{user.original_username}"><img loading="lazy" src="/uid/{user.id}/pic/profile" class="profile-pic-20 mr-1">@{user.username}</a>'
|
||||
|
||||
def render_sub_mention(self, token):
|
||||
space = token.target[0]
|
||||
target = token.target[1]
|
||||
return f'{space}<a href="https://old.reddit.com/r/{target}" rel="nofollow noopener noreferrer" class="d-inline-block">r/{target}</a>'
|
||||
|
||||
def render_redditor_mention(self, token):
|
||||
space = token.target[0]
|
||||
target = token.target[1]
|
||||
return f'{space}<a href="https://old.reddit.com/u/{target}" rel="nofollow noopener noreferrer" class="d-inline-block">u/{target}</a>'
|
||||
|
||||
|
||||
class Renderer(HTMLRenderer):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(UserMention,
|
||||
SubMention,
|
||||
RedditorMention,
|
||||
SubMention2,
|
||||
RedditorMention2,
|
||||
)
|
||||
|
||||
for i in kwargs:
|
||||
self.__dict__[i] = kwargs[i]
|
||||
|
||||
def render_user_mention(self, token):
|
||||
space = token.target[0]
|
||||
target = token.target[1]
|
||||
|
||||
user = get_user(target, graceful=True)
|
||||
|
||||
|
||||
try:
|
||||
if g.v.admin_level == 0 and g.v.any_block_exists(user):
|
||||
return f"{space}@{target}"
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
if not user: return f"{space}@{target}"
|
||||
|
||||
return f'{space}<a href="{user.url}" class="d-inline-block mention-user" data-bs-original-name="{user.original_username}">@{user.username}</a>'
|
||||
|
||||
def render_sub_mention(self, token):
|
||||
space = token.target[0]
|
||||
target = token.target[1]
|
||||
return f'{space}<a href="https://old.reddit.com/r/{target}" rel="nofollow noopener noreferrer" class="d-inline-block">r/{target}</a>'
|
||||
|
||||
def render_redditor_mention(self, token):
|
||||
space = token.target[0]
|
||||
target = token.target[1]
|
||||
return f'{space}<a href="https://old.reddit.com/u/{target}" rel="nofollow noopener noreferrer" class="d-inline-block">u/{target}</a>'
|
|
@ -1,228 +1,228 @@
|
|||
import bleach
|
||||
from bs4 import BeautifulSoup
|
||||
from bleach.linkifier import LinkifyFilter
|
||||
from functools import partial
|
||||
from .get import *
|
||||
from os import path, environ
|
||||
import re
|
||||
|
||||
site = environ.get("DOMAIN").strip()
|
||||
|
||||
allowed_tags = tags = ['b',
|
||||
'blockquote',
|
||||
'br',
|
||||
'code',
|
||||
'del',
|
||||
'em',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'hr',
|
||||
'i',
|
||||
'li',
|
||||
'ol',
|
||||
'p',
|
||||
'pre',
|
||||
'strong',
|
||||
'sub',
|
||||
'sup',
|
||||
'table',
|
||||
'tbody',
|
||||
'th',
|
||||
'thead',
|
||||
'td',
|
||||
'tr',
|
||||
'ul',
|
||||
'marquee',
|
||||
'a',
|
||||
'img',
|
||||
'span',
|
||||
]
|
||||
|
||||
no_images = ['b',
|
||||
'blockquote',
|
||||
'br',
|
||||
'code',
|
||||
'del',
|
||||
'em',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'hr',
|
||||
'i',
|
||||
'li',
|
||||
'ol',
|
||||
'p',
|
||||
'pre',
|
||||
'strong',
|
||||
'sub',
|
||||
'sup',
|
||||
'table',
|
||||
'tbody',
|
||||
'th',
|
||||
'thead',
|
||||
'td',
|
||||
'tr',
|
||||
'ul',
|
||||
'marquee',
|
||||
'a',
|
||||
'span',
|
||||
]
|
||||
|
||||
allowed_attributes = {'*': ['href', 'style', 'src', 'class', 'title', 'rel', 'data-bs-original-name', 'direction']}
|
||||
|
||||
allowed_protocols = ['http', 'https']
|
||||
|
||||
allowed_styles =['color', 'font-weight', 'transform', '-webkit-transform']
|
||||
|
||||
def sanitize(sanitized, noimages=False):
|
||||
|
||||
sanitized = sanitized.replace("\ufeff", "").replace("m.youtube.com", "youtube.com")
|
||||
|
||||
for i in re.finditer('https://i.imgur.com/(([^_]*?)\.(jpg|png|jpeg))', sanitized):
|
||||
sanitized = sanitized.replace(i.group(1), i.group(2) + "_d." + i.group(3) + "?maxwidth=9999")
|
||||
|
||||
if noimages:
|
||||
sanitized = bleach.Cleaner(tags=no_images,
|
||||
attributes=allowed_attributes,
|
||||
protocols=allowed_protocols,
|
||||
styles=allowed_styles,
|
||||
filters=[partial(LinkifyFilter,
|
||||
skip_tags=["pre"],
|
||||
parse_email=False,
|
||||
)
|
||||
]
|
||||
).clean(sanitized)
|
||||
else:
|
||||
sanitized = bleach.Cleaner(tags=allowed_tags,
|
||||
attributes=allowed_attributes,
|
||||
protocols=['http', 'https'],
|
||||
styles=['color','font-weight','transform','-webkit-transform'],
|
||||
filters=[partial(LinkifyFilter,
|
||||
skip_tags=["pre"],
|
||||
parse_email=False,
|
||||
)
|
||||
]
|
||||
).clean(sanitized)
|
||||
|
||||
soup = BeautifulSoup(sanitized, features="html.parser")
|
||||
|
||||
for tag in soup.find_all("img"):
|
||||
|
||||
if tag.get("src") and "profile-pic-20" not in tag.get("class", ""):
|
||||
|
||||
tag["rel"] = "nofollow noopener noreferrer"
|
||||
tag["class"] = "in-comment-image"
|
||||
tag["loading"] = "lazy"
|
||||
tag["data-src"] = tag["src"]
|
||||
tag["src"] = ""
|
||||
|
||||
link = soup.new_tag("a")
|
||||
link["href"] = tag["data-src"]
|
||||
link["rel"] = "nofollow noopener noreferrer"
|
||||
link["target"] = "_blank"
|
||||
link["onclick"] = f"expandDesktopImage('{tag['data-src']}');"
|
||||
link["data-bs-toggle"] = "modal"
|
||||
link["data-bs-target"] = "#expandImageModal"
|
||||
|
||||
tag.wrap(link)
|
||||
|
||||
for tag in soup.find_all("a"):
|
||||
if tag["href"]:
|
||||
tag["target"] = "_blank"
|
||||
if site not in tag["href"]: tag["rel"] = "nofollow noopener noreferrer"
|
||||
|
||||
if re.match("https?://\S+", str(tag.string)):
|
||||
try: tag.string = tag["href"]
|
||||
except: tag.string = ""
|
||||
|
||||
|
||||
sanitized = str(soup)
|
||||
|
||||
start = '<s>'
|
||||
end = '</s>'
|
||||
|
||||
try:
|
||||
if not session.get("favorite_emojis"): session["favorite_emojis"] = {}
|
||||
except:
|
||||
pass
|
||||
|
||||
if start in sanitized and end in sanitized and start in sanitized.split(end)[0] and end in sanitized.split(start)[1]: sanitized = sanitized.replace(start, '<span class="spoiler">').replace(end, '</span>')
|
||||
|
||||
for i in re.finditer("[^a]>\s*(:!?\w+:\s*)+<\/", sanitized):
|
||||
old = i.group(0)
|
||||
if 'marseylong1' in old or 'marseylong2' in old: new = old.lower().replace(">", " class='mb-0'>")
|
||||
else: new = old.lower()
|
||||
for i in re.finditer('(?<!"):([^ ]{1,30}?):', new):
|
||||
emoji = i.group(1).lower()
|
||||
if emoji.startswith("!"):
|
||||
emoji = emoji[1:]
|
||||
if path.isfile(f'./files/assets/images/emojis/{emoji}.webp'):
|
||||
new = re.sub(f'(?<!"):!{emoji}:', f'<img loading="lazy" data-bs-toggle="tooltip" alt=":!{emoji}:" title=":!{emoji}:" delay="0" class="bigemoji mirrored" src="https://{site}/assets/images/emojis/{emoji}.webp" >', new)
|
||||
|
||||
if emoji in session["favorite_emojis"]: session["favorite_emojis"][emoji] += 1
|
||||
else: session["favorite_emojis"][emoji] = 1
|
||||
|
||||
elif path.isfile(f'./files/assets/images/emojis/{emoji}.webp'):
|
||||
new = re.sub(f'(?<!"):{emoji}:', f'<img loading="lazy" data-bs-toggle="tooltip" alt=":{emoji}:" title=":{emoji}:" delay="0" class="bigemoji" src="https://{site}/assets/images/emojis/{emoji}.webp" >', new)
|
||||
|
||||
if emoji in session["favorite_emojis"]: session["favorite_emojis"][emoji] += 1
|
||||
else: session["favorite_emojis"][emoji] = 1
|
||||
|
||||
sanitized = sanitized.replace(old, new)
|
||||
|
||||
|
||||
for i in re.finditer('(?<!"):([^ ]{1,30}?):', sanitized):
|
||||
emoji = i.group(1).lower()
|
||||
if emoji.startswith("!"):
|
||||
emoji = emoji[1:]
|
||||
if path.isfile(f'./files/assets/images/emojis/{emoji}.webp'):
|
||||
sanitized = re.sub(f'(?<!"):!{emoji}:', f'<img loading="lazy" data-bs-toggle="tooltip" alt=":!{emoji}:" title=":!{emoji}:" delay="0" class="emoji mirrored" src="https://{site}/assets/images/emojis/{emoji}.webp">', sanitized)
|
||||
|
||||
if emoji in session["favorite_emojis"]: session["favorite_emojis"][emoji] += 1
|
||||
else: session["favorite_emojis"][emoji] = 1
|
||||
|
||||
elif path.isfile(f'./files/assets/images/emojis/{emoji}.webp'):
|
||||
sanitized = re.sub(f'(?<!"):{emoji}:', f'<img loading="lazy" data-bs-toggle="tooltip" alt=":{emoji}:" title=":{emoji}:" delay="0" class="emoji" src="https://{site}/assets/images/emojis/{emoji}.webp">', sanitized)
|
||||
|
||||
if emoji in session["favorite_emojis"]: session["favorite_emojis"][emoji] += 1
|
||||
else: session["favorite_emojis"][emoji] = 1
|
||||
|
||||
|
||||
sanitized = sanitized.replace("https://www.", "https://").replace("https://youtu.be/", "https://youtube.com/watch?v=").replace("https://music.youtube.com/watch?v=", "https://youtube.com/watch?v=").replace("https://open.spotify.com/", "https://open.spotify.com/embed/").replace("https://streamable.com/", "https://streamable.com/e/").replace("https://youtube.com/shorts/", "https://youtube.com/watch?v=").replace("https://mobile.twitter", "https://twitter").replace("https://m.facebook", "https://facebook").replace("https://m.wikipedia", "https://wikipedia").replace("https://m.youtube", "https://youtube")
|
||||
|
||||
|
||||
for i in re.finditer('" target="_blank">(https://youtube.com/watch\?v\=.*?)</a>', sanitized):
|
||||
url = i.group(1)
|
||||
replacing = f'<a href="{url}" rel="nofollow noopener noreferrer" target="_blank">{url}</a>'
|
||||
htmlsource = f'<iframe loading="lazy" data-src="{url}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>'
|
||||
sanitized = sanitized.replace(replacing, htmlsource.replace("watch?v=", "embed/"))
|
||||
|
||||
for i in re.finditer('<a href="(https://streamable.com/e/.*?)"', sanitized):
|
||||
url = i.group(1)
|
||||
replacing = f'<a href="{url}" rel="nofollow noopener noreferrer" target="_blank">{url}</a>'
|
||||
htmlsource = f'<iframe loading="lazy" data-src="{url}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>'
|
||||
sanitized = sanitized.replace(replacing, htmlsource)
|
||||
|
||||
for i in re.finditer('<p>(https:.*?\.mp4)</p>', sanitized):
|
||||
sanitized = sanitized.replace(i.group(0), f'<p><video controls loop preload="metadata" class="embedvid"><source data-src="{i.group(1)}" type="video/mp4"></video>')
|
||||
|
||||
for i in re.finditer('<a href="(https://open.spotify.com/embed/.*?)"', sanitized):
|
||||
url = i.group(1)
|
||||
replacing = f'<a href="{url}" rel="nofollow noopener noreferrer" target="_blank">{url}</a>'
|
||||
htmlsource = f'<iframe data-src="{url}" class="spotify" frameBorder="0" allowtransparency="true" allow="encrypted-media"></iframe>'
|
||||
sanitized = sanitized.replace(replacing, htmlsource)
|
||||
|
||||
for rd in ["https://reddit.com/", "https://new.reddit.com/", "https://www.reddit.com/", "https://redd.it/"]:
|
||||
sanitized = sanitized.replace(rd, "https://old.reddit.com/")
|
||||
|
||||
sanitized = re.sub(' (https:\/\/[^ <>]*)', r' <a target="_blank" rel="nofollow noopener noreferrer" href="\1">\1</a>', sanitized)
|
||||
sanitized = re.sub('<p>(https:\/\/[^ <>]*)', r'<p><a target="_blank" rel="nofollow noopener noreferrer" href="\1">\1</a></p>', sanitized)
|
||||
|
||||
return sanitized
|
||||
import bleach
|
||||
from bs4 import BeautifulSoup
|
||||
from bleach.linkifier import LinkifyFilter
|
||||
from functools import partial
|
||||
from .get import *
|
||||
from os import path, environ
|
||||
import re
|
||||
|
||||
site = environ.get("DOMAIN").strip()
|
||||
|
||||
allowed_tags = tags = ['b',
|
||||
'blockquote',
|
||||
'br',
|
||||
'code',
|
||||
'del',
|
||||
'em',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'hr',
|
||||
'i',
|
||||
'li',
|
||||
'ol',
|
||||
'p',
|
||||
'pre',
|
||||
'strong',
|
||||
'sub',
|
||||
'sup',
|
||||
'table',
|
||||
'tbody',
|
||||
'th',
|
||||
'thead',
|
||||
'td',
|
||||
'tr',
|
||||
'ul',
|
||||
'marquee',
|
||||
'a',
|
||||
'img',
|
||||
'span',
|
||||
]
|
||||
|
||||
no_images = ['b',
|
||||
'blockquote',
|
||||
'br',
|
||||
'code',
|
||||
'del',
|
||||
'em',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'hr',
|
||||
'i',
|
||||
'li',
|
||||
'ol',
|
||||
'p',
|
||||
'pre',
|
||||
'strong',
|
||||
'sub',
|
||||
'sup',
|
||||
'table',
|
||||
'tbody',
|
||||
'th',
|
||||
'thead',
|
||||
'td',
|
||||
'tr',
|
||||
'ul',
|
||||
'marquee',
|
||||
'a',
|
||||
'span',
|
||||
]
|
||||
|
||||
allowed_attributes = {'*': ['href', 'style', 'src', 'class', 'title', 'rel', 'data-bs-original-name', 'direction']}
|
||||
|
||||
allowed_protocols = ['http', 'https']
|
||||
|
||||
allowed_styles = ['color', 'font-weight', 'transform', '-webkit-transform']
|
||||
|
||||
def sanitize(sanitized, noimages=False):
|
||||
|
||||
sanitized = sanitized.replace("\ufeff", "").replace("m.youtube.com", "youtube.com")
|
||||
|
||||
for i in re.finditer('https://i.imgur.com/(([^_]*?)\.(jpg|png|jpeg))', sanitized):
|
||||
sanitized = sanitized.replace(i.group(1), i.group(2) + "_d." + i.group(3) + "?maxwidth=9999")
|
||||
|
||||
if noimages:
|
||||
sanitized = bleach.Cleaner(tags=no_images,
|
||||
attributes=allowed_attributes,
|
||||
protocols=allowed_protocols,
|
||||
styles=allowed_styles,
|
||||
filters=[partial(LinkifyFilter,
|
||||
skip_tags=["pre"],
|
||||
parse_email=False,
|
||||
)
|
||||
]
|
||||
).clean(sanitized)
|
||||
else:
|
||||
sanitized = bleach.Cleaner(tags=allowed_tags,
|
||||
attributes=allowed_attributes,
|
||||
protocols=['http', 'https'],
|
||||
styles=['color','font-weight','transform','-webkit-transform'],
|
||||
filters=[partial(LinkifyFilter,
|
||||
skip_tags=["pre"],
|
||||
parse_email=False,
|
||||
)
|
||||
]
|
||||
).clean(sanitized)
|
||||
|
||||
soup = BeautifulSoup(sanitized, features="html.parser")
|
||||
|
||||
for tag in soup.find_all("img"):
|
||||
|
||||
if tag.get("src") and "profile-pic-20" not in tag.get("class", ""):
|
||||
|
||||
tag["rel"] = "nofollow noopener noreferrer"
|
||||
tag["class"] = "in-comment-image"
|
||||
tag["loading"] = "lazy"
|
||||
tag["data-src"] = tag["src"]
|
||||
tag["src"] = "/assets/images/loading.gif"
|
||||
|
||||
link = soup.new_tag("a")
|
||||
link["href"] = tag["data-src"]
|
||||
link["rel"] = "nofollow noopener noreferrer"
|
||||
link["target"] = "_blank"
|
||||
link["onclick"] = f"expandDesktopImage('{tag['data-src']}');"
|
||||
link["data-bs-toggle"] = "modal"
|
||||
link["data-bs-target"] = "#expandImageModal"
|
||||
|
||||
tag.wrap(link)
|
||||
|
||||
for tag in soup.find_all("a"):
|
||||
if tag["href"]:
|
||||
tag["target"] = "_blank"
|
||||
if site not in tag["href"]: tag["rel"] = "nofollow noopener noreferrer"
|
||||
|
||||
if re.match("https?://\S+", str(tag.string)):
|
||||
try: tag.string = tag["href"]
|
||||
except: tag.string = ""
|
||||
|
||||
|
||||
sanitized = str(soup)
|
||||
|
||||
start = '<s>'
|
||||
end = '</s>'
|
||||
|
||||
try:
|
||||
if not session.get("favorite_emojis"): session["favorite_emojis"] = {}
|
||||
except:
|
||||
pass
|
||||
|
||||
if start in sanitized and end in sanitized and start in sanitized.split(end)[0] and end in sanitized.split(start)[1]: sanitized = sanitized.replace(start, '<span class="spoiler">').replace(end, '</span>')
|
||||
|
||||
for i in re.finditer("[^a]>\s*(:!?\w+:\s*)+<\/", sanitized):
|
||||
old = i.group(0)
|
||||
if 'marseylong1' in old or 'marseylong2' in old: new = old.lower().replace(">", " class='mb-0'>")
|
||||
else: new = old.lower()
|
||||
for i in re.finditer('(?<!"):([^ ]{1,30}?):', new):
|
||||
emoji = i.group(1).lower()
|
||||
if emoji.startswith("!"):
|
||||
emoji = emoji[1:]
|
||||
if path.isfile(f'./files/assets/images/emojis/{emoji}.webp'):
|
||||
new = re.sub(f'(?<!"):!{emoji}:', f'<img loading="lazy" data-bs-toggle="tooltip" alt=":!{emoji}:" title=":!{emoji}:" delay="0" class="bigemoji mirrored" src="https://{site}/assets/images/emojis/{emoji}.webp" >', new)
|
||||
|
||||
if emoji in session["favorite_emojis"]: session["favorite_emojis"][emoji] += 1
|
||||
else: session["favorite_emojis"][emoji] = 1
|
||||
|
||||
elif path.isfile(f'./files/assets/images/emojis/{emoji}.webp'):
|
||||
new = re.sub(f'(?<!"):{emoji}:', f'<img loading="lazy" data-bs-toggle="tooltip" alt=":{emoji}:" title=":{emoji}:" delay="0" class="bigemoji" src="https://{site}/assets/images/emojis/{emoji}.webp" >', new)
|
||||
|
||||
if emoji in session["favorite_emojis"]: session["favorite_emojis"][emoji] += 1
|
||||
else: session["favorite_emojis"][emoji] = 1
|
||||
|
||||
sanitized = sanitized.replace(old, new)
|
||||
|
||||
|
||||
for i in re.finditer('(?<!"):([^ ]{1,30}?):', sanitized):
|
||||
emoji = i.group(1).lower()
|
||||
if emoji.startswith("!"):
|
||||
emoji = emoji[1:]
|
||||
if path.isfile(f'./files/assets/images/emojis/{emoji}.webp'):
|
||||
sanitized = re.sub(f'(?<!"):!{emoji}:', f'<img loading="lazy" data-bs-toggle="tooltip" alt=":!{emoji}:" title=":!{emoji}:" delay="0" class="emoji mirrored" src="https://{site}/assets/images/emojis/{emoji}.webp">', sanitized)
|
||||
|
||||
if emoji in session["favorite_emojis"]: session["favorite_emojis"][emoji] += 1
|
||||
else: session["favorite_emojis"][emoji] = 1
|
||||
|
||||
elif path.isfile(f'./files/assets/images/emojis/{emoji}.webp'):
|
||||
sanitized = re.sub(f'(?<!"):{emoji}:', f'<img loading="lazy" data-bs-toggle="tooltip" alt=":{emoji}:" title=":{emoji}:" delay="0" class="emoji" src="https://{site}/assets/images/emojis/{emoji}.webp">', sanitized)
|
||||
|
||||
if emoji in session["favorite_emojis"]: session["favorite_emojis"][emoji] += 1
|
||||
else: session["favorite_emojis"][emoji] = 1
|
||||
|
||||
|
||||
sanitized = sanitized.replace("https://www.", "https://").replace("https://youtu.be/", "https://youtube.com/watch?v=").replace("https://music.youtube.com/watch?v=", "https://youtube.com/watch?v=").replace("https://open.spotify.com/", "https://open.spotify.com/embed/").replace("https://streamable.com/", "https://streamable.com/e/").replace("https://youtube.com/shorts/", "https://youtube.com/watch?v=").replace("https://mobile.twitter", "https://twitter").replace("https://m.facebook", "https://facebook").replace("https://m.wikipedia", "https://wikipedia").replace("https://m.youtube", "https://youtube")
|
||||
|
||||
|
||||
for i in re.finditer('" target="_blank">(https://youtube.com/watch\?v\=.*?)</a>', sanitized):
|
||||
url = i.group(1)
|
||||
replacing = f'<a href="{url}" rel="nofollow noopener noreferrer" target="_blank">{url}</a>'
|
||||
htmlsource = f'<iframe class="embedvid" loading="lazy" src="/assets/images/loading.gif" data-src="{url}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>'
|
||||
sanitized = sanitized.replace(replacing, htmlsource.replace("watch?v=", "embed/"))
|
||||
|
||||
for i in re.finditer('<a href="(https://streamable.com/e/.*?)"', sanitized):
|
||||
url = i.group(1)
|
||||
replacing = f'<a href="{url}" rel="nofollow noopener noreferrer" target="_blank">{url}</a>'
|
||||
htmlsource = f'<iframe class="embedvid" loading="lazy" src="/assets/images/loading.gif" data-src="{url}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>'
|
||||
sanitized = sanitized.replace(replacing, htmlsource)
|
||||
|
||||
for i in re.finditer('<p>(https:.*?\.mp4)</p>', sanitized):
|
||||
sanitized = sanitized.replace(i.group(0), f'<p><video controls loop preload="metadata" class="embedvid"><source src="/assets/images/loading.gif" data-src="{i.group(1)}" type="video/mp4"></video>')
|
||||
|
||||
for i in re.finditer('<a href="(https://open.spotify.com/embed/.*?)"', sanitized):
|
||||
url = i.group(1)
|
||||
replacing = f'<a href="{url}" rel="nofollow noopener noreferrer" target="_blank">{url}</a>'
|
||||
htmlsource = f'<iframe src="/assets/images/loading.gif" data-src="{url}" class="spotify" frameBorder="0" allowtransparency="true" allow="encrypted-media"></iframe>'
|
||||
sanitized = sanitized.replace(replacing, htmlsource)
|
||||
|
||||
for rd in ["https://reddit.com/", "https://new.reddit.com/", "https://www.reddit.com/", "https://redd.it/"]:
|
||||
sanitized = sanitized.replace(rd, "https://old.reddit.com/")
|
||||
|
||||
sanitized = re.sub(' (https:\/\/[^ <>]*)', r' <a target="_blank" rel="nofollow noopener noreferrer" href="\1">\1</a>', sanitized)
|
||||
sanitized = re.sub('<p>(https:\/\/[^ <>]*)', r'<p><a target="_blank" rel="nofollow noopener noreferrer" href="\1">\1</a></p>', sanitized)
|
||||
|
||||
return sanitized
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
from werkzeug.security import *
|
||||
from os import environ
|
||||
|
||||
|
||||
def generate_hash(string):
|
||||
|
||||
msg = bytes(string, "utf-16")
|
||||
|
||||
return hmac.new(key=bytes(environ.get("MASTER_KEY"), "utf-16"),
|
||||
msg=msg,
|
||||
digestmod='md5'
|
||||
).hexdigest()
|
||||
|
||||
|
||||
def validate_hash(string, hashstr):
|
||||
|
||||
return hmac.compare_digest(hashstr, generate_hash(string))
|
||||
|
||||
|
||||
def hash_password(password):
|
||||
|
||||
return generate_password_hash(
|
||||
password, method='pbkdf2:sha512', salt_length=8)
|
||||
from werkzeug.security import *
|
||||
from os import environ
|
||||
|
||||
|
||||
def generate_hash(string):
|
||||
|
||||
msg = bytes(string, "utf-16")
|
||||
|
||||
return hmac.new(key=bytes(environ.get("MASTER_KEY"), "utf-16"),
|
||||
msg=msg,
|
||||
digestmod='md5'
|
||||
).hexdigest()
|
||||
|
||||
|
||||
def validate_hash(string, hashstr):
|
||||
|
||||
return hmac.compare_digest(hashstr, generate_hash(string))
|
||||
|
||||
|
||||
def hash_password(password):
|
||||
|
||||
return generate_password_hash(
|
||||
password, method='pbkdf2:sha512', salt_length=8)
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
from flask import *
|
||||
import time
|
||||
from .security import *
|
||||
|
||||
def make_logged_out_formkey(t):
|
||||
|
||||
s = f"{t}+{session['session_id']}"
|
||||
|
||||
return generate_hash(s)
|
||||
|
||||
|
||||
def validate_logged_out_formkey(t, k):
|
||||
|
||||
now = int(time.time())
|
||||
if now - t > 3600:
|
||||
return False
|
||||
|
||||
s = f"{t}+{session['session_id']}"
|
||||
|
||||
return validate_hash(s, k)
|
||||
from flask import *
|
||||
import time
|
||||
from .security import *
|
||||
|
||||
def make_logged_out_formkey(t):
|
||||
|
||||
s = f"{t}+{session['session_id']}"
|
||||
|
||||
return generate_hash(s)
|
||||
|
||||
|
||||
def validate_logged_out_formkey(t, k):
|
||||
|
||||
now = int(time.time())
|
||||
if now - t > 3600:
|
||||
return False
|
||||
|
||||
s = f"{t}+{session['session_id']}"
|
||||
|
||||
return validate_hash(s, k)
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
from sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.sql.expression import FromClause
|
||||
|
||||
|
||||
class values(FromClause):
|
||||
named_with_column = True
|
||||
|
||||
def __init__(self, columns, *args, **kwargs):
|
||||
self._column_args = columns
|
||||
self.list = args
|
||||
self.alias_name = self.name = kwargs.pop('alias_name', None)
|
||||
|
||||
def _populate_column_collection(self):
|
||||
for c in self._column_args:
|
||||
c._make_proxy(self)
|
||||
|
||||
|
||||
@compiles(values)
|
||||
def compile_values(element, compiler, asfrom=False):
|
||||
columns = element.columns
|
||||
v = "VALUES %s" % ", ".join(
|
||||
"(%s)" % ", ".join(
|
||||
compiler.render_literal_value(elem, column.type)
|
||||
for elem, column in zip(tup, columns))
|
||||
for tup in element.list
|
||||
)
|
||||
if asfrom:
|
||||
if element.alias_name:
|
||||
v = "(%s) AS %s (%s)" % (v, element.alias_name,
|
||||
(", ".join(c.name for c in element.columns)))
|
||||
else:
|
||||
v = "(%s)" % v
|
||||
return v
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.sql.expression import FromClause
|
||||
|
||||
|
||||
class values(FromClause):
|
||||
named_with_column = True
|
||||
|
||||
def __init__(self, columns, *args, **kwargs):
|
||||
self._column_args = columns
|
||||
self.list = args
|
||||
self.alias_name = self.name = kwargs.pop('alias_name', None)
|
||||
|
||||
def _populate_column_collection(self):
|
||||
for c in self._column_args:
|
||||
c._make_proxy(self)
|
||||
|
||||
|
||||
@compiles(values)
|
||||
def compile_values(element, compiler, asfrom=False):
|
||||
columns = element.columns
|
||||
v = "VALUES %s" % ", ".join(
|
||||
"(%s)" % ", ".join(
|
||||
compiler.render_literal_value(elem, column.type)
|
||||
for elem, column in zip(tup, columns))
|
||||
for tup in element.list
|
||||
)
|
||||
if asfrom:
|
||||
if element.alias_name:
|
||||
v = "(%s) AS %s (%s)" % (v, element.alias_name,
|
||||
(", ".join(c.name for c in element.columns)))
|
||||
else:
|
||||
v = "(%s)" % v
|
||||
return v
|
||||
|
|
|
@ -1,200 +1,190 @@
|
|||
from .get import *
|
||||
from .alerts import send_notification
|
||||
from files.helpers.const import *
|
||||
|
||||
|
||||
def get_logged_in_user():
|
||||
|
||||
if request.headers.get("Authorization"):
|
||||
token = request.headers.get("Authorization")
|
||||
if not token: return None
|
||||
|
||||
client = g.db.query(ClientAuth).options(lazyload('*')).filter(ClientAuth.access_token == token).first()
|
||||
|
||||
x = (client.user, client) if client else (None, None)
|
||||
|
||||
|
||||
else:
|
||||
|
||||
uid = session.get("user_id")
|
||||
nonce = session.get("login_nonce", 0)
|
||||
if not uid: x= (None, None)
|
||||
try:
|
||||
if g.db: v = g.db.query(User).options(lazyload('*')).filter_by(id=uid).first()
|
||||
else: v = None
|
||||
except: v = None
|
||||
|
||||
if v and v.agendaposter_expires_utc and v.agendaposter_expires_utc < g.timestamp:
|
||||
v.agendaposter_expires_utc = 0
|
||||
v.agendaposter = False
|
||||
|
||||
g.db.add(v)
|
||||
|
||||
if v and (nonce < v.login_nonce):
|
||||
x= (None, None)
|
||||
else:
|
||||
x=(v, None)
|
||||
|
||||
|
||||
if x[0]: x[0].client=x[1]
|
||||
|
||||
return x[0]
|
||||
|
||||
|
||||
def check_ban_evade(v):
|
||||
|
||||
if not v or not v.ban_evade or v.admin_level > 0:
|
||||
return
|
||||
|
||||
if random.randint(0,30) < v.ban_evade:
|
||||
v.ban(reason="permaban evasion")
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, v, "Your account has been permanently suspended for the following reason:\n\n> permaban evasion")
|
||||
|
||||
for post in g.db.query(Submission).options(lazyload('*')).filter_by(author_id=v.id).all():
|
||||
if post.is_banned:
|
||||
continue
|
||||
|
||||
post.is_banned=True
|
||||
post.ban_reason="permaban evasion"
|
||||
g.db.add(post)
|
||||
|
||||
ma=ModAction(
|
||||
kind="ban_post",
|
||||
user_id=AUTOJANNY_ACCOUNT,
|
||||
target_submission_id=post.id,
|
||||
note="permaban evasion"
|
||||
)
|
||||
g.db.add(ma)
|
||||
|
||||
for comment in g.db.query(Comment).options(lazyload('*')).filter_by(author_id=v.id).all():
|
||||
if comment.is_banned:
|
||||
continue
|
||||
|
||||
comment.is_banned=True
|
||||
comment.ban_reason="permaban evasion"
|
||||
g.db.add(comment)
|
||||
|
||||
try:
|
||||
ma=ModAction(
|
||||
kind="ban_comment",
|
||||
user_id=AUTOJANNY_ACCOUNT,
|
||||
target_comment_id=comment.id,
|
||||
note="ban evasion"
|
||||
)
|
||||
g.db.add(ma)
|
||||
except: pass
|
||||
|
||||
else:
|
||||
v.ban_evade +=1
|
||||
g.db.add(v)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
|
||||
|
||||
# Wrappers
|
||||
def auth_desired(f):
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
v = get_logged_in_user()
|
||||
check_ban_evade(v)
|
||||
|
||||
resp = make_response(f(*args, v=v, **kwargs))
|
||||
return resp
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
return wrapper
|
||||
|
||||
|
||||
def auth_required(f):
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
v = get_logged_in_user()
|
||||
|
||||
if not v:
|
||||
abort(401)
|
||||
|
||||
check_ban_evade(v)
|
||||
|
||||
g.v = v
|
||||
|
||||
resp = make_response(f(*args, v=v, **kwargs))
|
||||
return resp
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
return wrapper
|
||||
|
||||
|
||||
def is_not_banned(f):
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
v = get_logged_in_user()
|
||||
|
||||
if not v:
|
||||
abort(401)
|
||||
|
||||
check_ban_evade(v)
|
||||
|
||||
if v.is_suspended:
|
||||
abort(403)
|
||||
|
||||
g.v = v
|
||||
|
||||
resp = make_response(f(*args, v=v, **kwargs))
|
||||
return resp
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
return wrapper
|
||||
|
||||
|
||||
# this wrapper takes args and is a bit more complicated
|
||||
def admin_level_required(x):
|
||||
|
||||
def wrapper_maker(f):
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
v = get_logged_in_user()
|
||||
|
||||
if not v:
|
||||
abort(401)
|
||||
|
||||
if v.admin_level < x:
|
||||
abort(403)
|
||||
|
||||
g.v = v
|
||||
|
||||
response = f(*args, v=v, **kwargs)
|
||||
|
||||
if isinstance(response, tuple):
|
||||
resp = make_response(response[0])
|
||||
else:
|
||||
resp = make_response(response)
|
||||
|
||||
return resp
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
return wrapper
|
||||
|
||||
return wrapper_maker
|
||||
|
||||
|
||||
def validate_formkey(f):
|
||||
"""Always use @auth_required or @admin_level_required above @validate_form"""
|
||||
|
||||
def wrapper(*args, v, **kwargs):
|
||||
|
||||
if not request.headers.get("Authorization"):
|
||||
|
||||
submitted_key = request.values.get("formkey", None)
|
||||
|
||||
if not submitted_key: abort(401)
|
||||
|
||||
elif not v.validate_formkey(submitted_key): abort(401)
|
||||
|
||||
return f(*args, v=v, **kwargs)
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
from .get import *
|
||||
from .alerts import send_notification
|
||||
from files.helpers.const import *
|
||||
|
||||
|
||||
def get_logged_in_user():
|
||||
|
||||
if request.headers.get("Authorization"):
|
||||
token = request.headers.get("Authorization")
|
||||
if not token: return None
|
||||
|
||||
client = g.db.query(ClientAuth).options(lazyload('*')).filter(ClientAuth.access_token == token).first()
|
||||
|
||||
x = (client.user, client) if client else (None, None)
|
||||
|
||||
|
||||
else:
|
||||
|
||||
uid = session.get("user_id")
|
||||
nonce = session.get("login_nonce", 0)
|
||||
if not uid: x= (None, None)
|
||||
try:
|
||||
if g.db: v = g.db.query(User).options(lazyload('*')).filter_by(id=uid).first()
|
||||
else: v = None
|
||||
except: v = None
|
||||
|
||||
if v and v.agendaposter_expires_utc and v.agendaposter_expires_utc < g.timestamp:
|
||||
v.agendaposter_expires_utc = 0
|
||||
v.agendaposter = False
|
||||
|
||||
g.db.add(v)
|
||||
|
||||
if v and (nonce < v.login_nonce):
|
||||
x= (None, None)
|
||||
else:
|
||||
x=(v, None)
|
||||
|
||||
|
||||
if x[0]: x[0].client=x[1]
|
||||
|
||||
return x[0]
|
||||
|
||||
|
||||
def check_ban_evade(v):
|
||||
|
||||
if not v or not v.ban_evade or v.admin_level > 0:
|
||||
return
|
||||
|
||||
if random.randint(0,30) < v.ban_evade:
|
||||
v.ban(reason="permaban evasion")
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, v, "Your account has been permanently suspended for the following reason:\n\n> permaban evasion")
|
||||
|
||||
for post in g.db.query(Submission).options(lazyload('*')).filter_by(author_id=v.id).all():
|
||||
if post.is_banned:
|
||||
continue
|
||||
|
||||
post.is_banned=True
|
||||
post.ban_reason="permaban evasion"
|
||||
g.db.add(post)
|
||||
|
||||
ma=ModAction(
|
||||
kind="ban_post",
|
||||
user_id=AUTOJANNY_ACCOUNT,
|
||||
target_submission_id=post.id,
|
||||
note="permaban evasion"
|
||||
)
|
||||
g.db.add(ma)
|
||||
|
||||
for comment in g.db.query(Comment).options(lazyload('*')).filter_by(author_id=v.id).all():
|
||||
if comment.is_banned:
|
||||
continue
|
||||
|
||||
comment.is_banned=True
|
||||
comment.ban_reason="permaban evasion"
|
||||
g.db.add(comment)
|
||||
|
||||
try:
|
||||
ma=ModAction(
|
||||
kind="ban_comment",
|
||||
user_id=AUTOJANNY_ACCOUNT,
|
||||
target_comment_id=comment.id,
|
||||
note="ban evasion"
|
||||
)
|
||||
g.db.add(ma)
|
||||
except: pass
|
||||
|
||||
else:
|
||||
v.ban_evade +=1
|
||||
g.db.add(v)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
|
||||
|
||||
def auth_desired(f):
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
v = get_logged_in_user()
|
||||
check_ban_evade(v)
|
||||
|
||||
resp = make_response(f(*args, v=v, **kwargs))
|
||||
return resp
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
return wrapper
|
||||
|
||||
|
||||
def auth_required(f):
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
v = get_logged_in_user()
|
||||
|
||||
if not v: abort(401)
|
||||
|
||||
check_ban_evade(v)
|
||||
|
||||
g.v = v
|
||||
|
||||
resp = make_response(f(*args, v=v, **kwargs))
|
||||
return resp
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
return wrapper
|
||||
|
||||
|
||||
def is_not_banned(f):
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
v = get_logged_in_user()
|
||||
|
||||
if not v: abort(401)
|
||||
|
||||
check_ban_evade(v)
|
||||
|
||||
if v.is_suspended:
|
||||
abort(403)
|
||||
|
||||
g.v = v
|
||||
|
||||
resp = make_response(f(*args, v=v, **kwargs))
|
||||
return resp
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
return wrapper
|
||||
|
||||
|
||||
def admin_level_required(x):
|
||||
|
||||
def wrapper_maker(f):
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
v = get_logged_in_user()
|
||||
|
||||
if not v: abort(401)
|
||||
|
||||
if v.admin_level < x: abort(403)
|
||||
|
||||
g.v = v
|
||||
|
||||
response = f(*args, v=v, **kwargs)
|
||||
|
||||
if isinstance(response, tuple): resp = make_response(response[0])
|
||||
else: resp = make_response(response)
|
||||
|
||||
return resp
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
return wrapper
|
||||
|
||||
return wrapper_maker
|
||||
|
||||
|
||||
def validate_formkey(f):
|
||||
def wrapper(*args, v, **kwargs):
|
||||
|
||||
if not request.headers.get("Authorization"):
|
||||
|
||||
submitted_key = request.values.get("formkey", None)
|
||||
|
||||
if not submitted_key: abort(401)
|
||||
|
||||
elif not v.validate_formkey(submitted_key): abort(401)
|
||||
|
||||
return f(*args, v=v, **kwargs)
|
||||
|
||||
wrapper.__name__ = f.__name__
|
||||
return wrapper
|
|
@ -1,88 +1,88 @@
|
|||
from os import environ
|
||||
import time
|
||||
from flask import *
|
||||
from urllib.parse import quote
|
||||
|
||||
from files.helpers.security import *
|
||||
from files.helpers.wrappers import *
|
||||
from files.classes import *
|
||||
from files.__main__ import app, mail, limiter
|
||||
from flask_mail import Message
|
||||
|
||||
site = environ.get("DOMAIN").strip()
|
||||
name = environ.get("SITE_NAME").strip()
|
||||
|
||||
|
||||
def send_mail(to_address, subject, html):
|
||||
|
||||
msg = Message(html=html, subject=subject, sender=f"{name}@{site}", recipients=[to_address])
|
||||
mail.send(msg)
|
||||
|
||||
|
||||
def send_verification_email(user, email=None):
|
||||
|
||||
if not email:
|
||||
email = user.email
|
||||
|
||||
url = f"https://{app.config['SERVER_NAME']}/activate"
|
||||
now = int(time.time())
|
||||
|
||||
token = generate_hash(f"{email}+{user.id}+{now}")
|
||||
params = f"?email={quote(email)}&id={user.id}&time={now}&token={token}"
|
||||
|
||||
link = url + params
|
||||
|
||||
send_mail(to_address=email,
|
||||
html=render_template("email/email_verify.html",
|
||||
action_url=link,
|
||||
v=user),
|
||||
subject=f"Validate your {name} account email."
|
||||
)
|
||||
|
||||
|
||||
@app.post("/verify_email")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
def api_verify_email(v):
|
||||
|
||||
send_verification_email(v)
|
||||
|
||||
return {"message": "Email has been sent (ETA ~5 minutes)"}
|
||||
|
||||
|
||||
@app.get("/activate")
|
||||
@auth_desired
|
||||
def activate(v):
|
||||
|
||||
email = request.values.get("email", "")
|
||||
id = request.values.get("id", "")
|
||||
timestamp = int(request.values.get("time", "0"))
|
||||
token = request.values.get("token", "")
|
||||
|
||||
if int(time.time()) - timestamp > 3600:
|
||||
return render_template("message.html", v=v, title="Verification link expired.",
|
||||
message="That link has expired. Visit your settings to send yourself another verification email."), 410
|
||||
|
||||
if not validate_hash(f"{email}+{id}+{timestamp}", token):
|
||||
abort(403)
|
||||
|
||||
user = g.db.query(User).options(lazyload('*')).filter_by(id=id).first()
|
||||
if not user:
|
||||
abort(404)
|
||||
|
||||
if user.is_activated and user.email == email:
|
||||
return render_template("message_success.html", v=v,
|
||||
title="Email already verified.", message="Email already verified."), 404
|
||||
|
||||
user.email = email
|
||||
user.is_activated = True
|
||||
|
||||
if not any([b.badge_id == 2 for b in user.badges]):
|
||||
mail_badge = Badge(user_id=user.id,
|
||||
badge_id=2)
|
||||
g.db.add(mail_badge)
|
||||
|
||||
g.db.add(user)
|
||||
g.db.commit()
|
||||
|
||||
return render_template("message_success.html", v=v, title="Email verified.", message=f"Your email {email} has been verified. Thank you.")
|
||||
from os import environ
|
||||
import time
|
||||
from flask import *
|
||||
from urllib.parse import quote
|
||||
|
||||
from files.helpers.security import *
|
||||
from files.helpers.wrappers import *
|
||||
from files.classes import *
|
||||
from files.__main__ import app, mail, limiter
|
||||
from flask_mail import Message
|
||||
|
||||
site = environ.get("DOMAIN").strip()
|
||||
name = environ.get("SITE_NAME").strip()
|
||||
|
||||
|
||||
def send_mail(to_address, subject, html):
|
||||
|
||||
msg = Message(html=html, subject=subject, sender=f"{name}@{site}", recipients=[to_address])
|
||||
mail.send(msg)
|
||||
|
||||
|
||||
def send_verification_email(user, email=None):
|
||||
|
||||
if not email:
|
||||
email = user.email
|
||||
|
||||
url = f"https://{app.config['SERVER_NAME']}/activate"
|
||||
now = int(time.time())
|
||||
|
||||
token = generate_hash(f"{email}+{user.id}+{now}")
|
||||
params = f"?email={quote(email)}&id={user.id}&time={now}&token={token}"
|
||||
|
||||
link = url + params
|
||||
|
||||
send_mail(to_address=email,
|
||||
html=render_template("email/email_verify.html",
|
||||
action_url=link,
|
||||
v=user),
|
||||
subject=f"Validate your {name} account email."
|
||||
)
|
||||
|
||||
|
||||
@app.post("/verify_email")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
def api_verify_email(v):
|
||||
|
||||
send_verification_email(v)
|
||||
|
||||
return {"message": "Email has been sent (ETA ~5 minutes)"}
|
||||
|
||||
|
||||
@app.get("/activate")
|
||||
@auth_desired
|
||||
def activate(v):
|
||||
|
||||
email = request.values.get("email", "").strip()
|
||||
id = request.values.get("id", "").strip()
|
||||
timestamp = int(request.values.get("time", "0"))
|
||||
token = request.values.get("token", "").strip()
|
||||
|
||||
if int(time.time()) - timestamp > 3600:
|
||||
return render_template("message.html", v=v, title="Verification link expired.",
|
||||
message="That link has expired. Visit your settings to send yourself another verification email."), 410
|
||||
|
||||
if not validate_hash(f"{email}+{id}+{timestamp}", token):
|
||||
abort(403)
|
||||
|
||||
user = g.db.query(User).options(lazyload('*')).filter_by(id=id).first()
|
||||
if not user:
|
||||
abort(404)
|
||||
|
||||
if user.is_activated and user.email == email:
|
||||
return render_template("message_success.html", v=v,
|
||||
title="Email already verified.", message="Email already verified."), 404
|
||||
|
||||
user.email = email
|
||||
user.is_activated = True
|
||||
|
||||
if not any([b.badge_id == 2 for b in user.badges]):
|
||||
mail_badge = Badge(user_id=user.id,
|
||||
badge_id=2)
|
||||
g.db.add(mail_badge)
|
||||
|
||||
g.db.add(user)
|
||||
g.db.commit()
|
||||
|
||||
return render_template("message_success.html", v=v, title="Email verified.", message=f"Your email {email} has been verified. Thank you.")
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
from .admin import *
|
||||
from .comments import *
|
||||
from .discord import *
|
||||
from .errors import *
|
||||
from .reporting import *
|
||||
from .front import *
|
||||
from .login import *
|
||||
from .oauth import *
|
||||
from .posts import *
|
||||
from .search import *
|
||||
from .settings import *
|
||||
from .static import *
|
||||
from .users import *
|
||||
from .votes import *
|
||||
from .feeds import *
|
||||
from .awards import *
|
||||
from .admin import *
|
||||
from .comments import *
|
||||
from .discord import *
|
||||
from .errors import *
|
||||
from .reporting import *
|
||||
from .front import *
|
||||
from .login import *
|
||||
from .oauth import *
|
||||
from .posts import *
|
||||
from .search import *
|
||||
from .settings import *
|
||||
from .static import *
|
||||
from .users import *
|
||||
from .votes import *
|
||||
from .feeds import *
|
||||
from .awards import *
|
||||
from .giphy import *
|
File diff suppressed because it is too large
Load Diff
|
@ -1,372 +1,373 @@
|
|||
from files.__main__ import app, limiter
|
||||
from files.helpers.wrappers import *
|
||||
from files.helpers.alerts import *
|
||||
from files.helpers.get import *
|
||||
from files.helpers.const import *
|
||||
from files.classes.award import *
|
||||
from flask import g, request
|
||||
|
||||
@app.get("/shop")
|
||||
@app.get("/settings/shop")
|
||||
@auth_required
|
||||
def shop(v):
|
||||
if site_name == "Drama":
|
||||
AWARDS = {
|
||||
"ban": {
|
||||
"kind": "ban",
|
||||
"title": "One-Day Ban",
|
||||
"description": "Bans the author for a day.",
|
||||
"icon": "fas fa-gavel",
|
||||
"color": "text-danger",
|
||||
"price": 5000
|
||||
},
|
||||
"shit": {
|
||||
"kind": "shit",
|
||||
"title": "Shit",
|
||||
"description": "Makes flies swarm a post.",
|
||||
"icon": "fas fa-poop",
|
||||
"color": "text-black-50",
|
||||
"price": 1000
|
||||
},
|
||||
"fireflies": {
|
||||
"kind": "fireflies",
|
||||
"title": "Fireflies",
|
||||
"description": "Puts stars on the post.",
|
||||
"icon": "fas fa-sparkles",
|
||||
"color": "text-warning",
|
||||
"price": 1000
|
||||
}
|
||||
}
|
||||
else:
|
||||
AWARDS = {
|
||||
"shit": {
|
||||
"kind": "shit",
|
||||
"title": "Shit",
|
||||
"description": "Makes flies swarm a post.",
|
||||
"icon": "fas fa-poop",
|
||||
"color": "text-black-50",
|
||||
"price": 1000
|
||||
},
|
||||
"fireflies": {
|
||||
"kind": "fireflies",
|
||||
"title": "Fireflies",
|
||||
"description": "Puts stars on the post.",
|
||||
"icon": "fas fa-sparkles",
|
||||
"color": "text-warning",
|
||||
"price": 1000
|
||||
}
|
||||
}
|
||||
|
||||
query = g.db.query(
|
||||
User.id, User.username, User.patron, User.namecolor,
|
||||
AwardRelationship.kind.label('last_award_kind'), func.count(AwardRelationship.id).label('last_award_count')
|
||||
).filter(AwardRelationship.submission_id==None, AwardRelationship.comment_id==None, User.patron > 0) \
|
||||
.group_by(User.username, User.patron, User.id, User.namecolor, AwardRelationship.kind) \
|
||||
.order_by(User.patron.desc(), AwardRelationship.kind.desc()) \
|
||||
.join(User).filter(User.id == v.id).all()
|
||||
|
||||
owned = []
|
||||
for row in (r._asdict() for r in query):
|
||||
kind = row['last_award_kind']
|
||||
if kind in AWARDS.keys():
|
||||
award = AWARDS[kind]
|
||||
award["owned_num"] = row['last_award_count']
|
||||
owned.append(award)
|
||||
|
||||
if v.patron:
|
||||
for val in AWARDS.values():
|
||||
if v.patron == 1: val["price"] = int(val["price"]*0.90)
|
||||
elif v.patron == 2: val["price"] = int(val["price"]*0.85)
|
||||
elif v.patron == 3: val["price"] = int(val["price"]*0.80)
|
||||
elif v.patron == 4: val["price"] = int(val["price"]*0.75)
|
||||
else: val["price"] = int(val["price"]*0.70)
|
||||
|
||||
return render_template("settings_shop.html", owned=owned, awards=list(AWARDS.values()), v=v)
|
||||
|
||||
|
||||
@app.post("/buy/<award>")
|
||||
@auth_required
|
||||
def buy(v, award):
|
||||
if site_name == "Drama":
|
||||
AWARDS = {
|
||||
"ban": {
|
||||
"kind": "ban",
|
||||
"title": "One-Day Ban",
|
||||
"description": "Bans the author for a day.",
|
||||
"icon": "fas fa-gavel",
|
||||
"color": "text-danger",
|
||||
"price": 5000
|
||||
},
|
||||
"shit": {
|
||||
"kind": "shit",
|
||||
"title": "Shit",
|
||||
"description": "Makes flies swarm a post.",
|
||||
"icon": "fas fa-poop",
|
||||
"color": "text-black-50",
|
||||
"price": 1000
|
||||
},
|
||||
"fireflies": {
|
||||
"kind": "fireflies",
|
||||
"title": "Fireflies",
|
||||
"description": "Puts stars on the post.",
|
||||
"icon": "fas fa-sparkles",
|
||||
"color": "text-warning",
|
||||
"price": 1000
|
||||
}
|
||||
}
|
||||
else:
|
||||
AWARDS = {
|
||||
"shit": {
|
||||
"kind": "shit",
|
||||
"title": "Shit",
|
||||
"description": "Makes flies swarm a post.",
|
||||
"icon": "fas fa-poop",
|
||||
"color": "text-black-50",
|
||||
"price": 1000
|
||||
},
|
||||
"fireflies": {
|
||||
"kind": "fireflies",
|
||||
"title": "Fireflies",
|
||||
"description": "Puts stars on the post.",
|
||||
"icon": "fas fa-sparkles",
|
||||
"color": "text-warning",
|
||||
"price": 1000
|
||||
}
|
||||
}
|
||||
|
||||
if award not in AWARDS: abort(400)
|
||||
price = AWARDS[award]["price"]
|
||||
if v.patron:
|
||||
if v.patron == 1: price = int(price*0.90)
|
||||
elif v.patron == 2: price = int(price*0.85)
|
||||
elif v.patron == 3: price = int(price*0.80)
|
||||
elif v.patron == 4: price = int(price*0.75)
|
||||
else: price = int(price*0.70)
|
||||
|
||||
if v.coins < price: return {"error": "Not enough coins."}, 400
|
||||
v.coins -= price
|
||||
g.db.add(v)
|
||||
|
||||
thing = g.db.query(AwardRelationship).order_by(AwardRelationship.id.desc()).first().id
|
||||
thing += 1
|
||||
|
||||
award = AwardRelationship(id=thing, user_id=v.id, kind=award)
|
||||
g.db.add(award)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return {"message": "Award bought!"}
|
||||
|
||||
|
||||
def banaward_trigger(post=None, comment=None):
|
||||
|
||||
author = post.author if post else comment.author
|
||||
link = f"[this post]({post.permalink})" if post else f"[this comment]({comment.permalink})"
|
||||
|
||||
if not author.is_suspended:
|
||||
author.ban(reason="one-day ban award used", days=1)
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, author, f"Your account has been suspended for a day for {link}. It sucked and you should feel bad.")
|
||||
elif author.unban_utc > 0:
|
||||
author.unban_utc += 24*60*60
|
||||
g.db.add(author)
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, author, f"Your account has been suspended for yet another day for {link}. Seriously man?")
|
||||
|
||||
|
||||
ACTIONS = {
|
||||
"ban": banaward_trigger
|
||||
}
|
||||
|
||||
ALLOW_MULTIPLE = (
|
||||
"ban",
|
||||
"shit",
|
||||
"fireflies"
|
||||
)
|
||||
|
||||
@app.post("/post/<pid>/awards")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
def award_post(pid, v):
|
||||
|
||||
if v.is_suspended and v.unban_utc == 0: return {"error": "forbidden."}, 403
|
||||
|
||||
kind = request.values.get("kind", "")
|
||||
|
||||
if kind not in AWARDS:
|
||||
return {"error": "That award doesn't exist."}, 404
|
||||
|
||||
post_award = g.db.query(AwardRelationship).options(lazyload('*')).filter(
|
||||
and_(
|
||||
AwardRelationship.kind == kind,
|
||||
AwardRelationship.user_id == v.id,
|
||||
AwardRelationship.submission_id == None,
|
||||
AwardRelationship.comment_id == None
|
||||
)
|
||||
).first()
|
||||
|
||||
if not post_award:
|
||||
return {"error": "You don't have that award."}, 404
|
||||
|
||||
post = g.db.query(Submission).options(lazyload('*')).filter_by(id=pid).first()
|
||||
|
||||
if not post or post.is_banned or post.deleted_utc > 0:
|
||||
return {"error": "That post doesn't exist or has been deleted or removed."}, 404
|
||||
|
||||
if post.author_id == v.id:
|
||||
return {"error": "You can't award yourself."}, 403
|
||||
|
||||
existing_award = g.db.query(AwardRelationship).options(lazyload('*')).filter(
|
||||
and_(
|
||||
AwardRelationship.submission_id == post.id,
|
||||
AwardRelationship.user_id == v.id,
|
||||
AwardRelationship.kind == kind
|
||||
)
|
||||
).first()
|
||||
|
||||
if existing_award and kind not in ALLOW_MULTIPLE:
|
||||
return {"error": "You can't give that award multiple times to the same post."}, 409
|
||||
|
||||
post_award.submission_id = post.id
|
||||
g.db.add(post_award)
|
||||
|
||||
msg = f"@{v.username} has given your [post]({post.permalink}) the {AWARDS[kind]['title']} Award!"
|
||||
|
||||
note = request.values.get("note", "")
|
||||
if note:
|
||||
msg += f"\n\n> {note}"
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, post.author, msg)
|
||||
|
||||
if kind in ACTIONS: ACTIONS[kind](post=post)
|
||||
|
||||
post.author.received_award_count += 1
|
||||
g.db.add(post.author)
|
||||
|
||||
g.db.commit()
|
||||
if request.referrer and len(request.referrer) > 1: return redirect(request.referrer)
|
||||
else: return redirect("/")
|
||||
|
||||
|
||||
@app.post("/comment/<cid>/awards")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
def award_comment(cid, v):
|
||||
|
||||
if v.is_suspended and v.unban_utc == 0: return {"error": "forbidden"}, 403
|
||||
|
||||
kind = request.values.get("kind", "")
|
||||
|
||||
if kind not in AWARDS:
|
||||
return {"error": "That award doesn't exist."}, 404
|
||||
|
||||
comment_award = g.db.query(AwardRelationship).options(lazyload('*')).filter(
|
||||
and_(
|
||||
AwardRelationship.kind == kind,
|
||||
AwardRelationship.user_id == v.id,
|
||||
AwardRelationship.submission_id == None,
|
||||
AwardRelationship.comment_id == None
|
||||
)
|
||||
).first()
|
||||
|
||||
if not comment_award:
|
||||
return {"error": "You don't have that award."}, 404
|
||||
|
||||
c = g.db.query(Comment).options(lazyload('*')).filter_by(id=cid).first()
|
||||
|
||||
if not c or c.is_banned or c.deleted_utc > 0:
|
||||
return {"error": "That comment doesn't exist or has been deleted or removed."}, 404
|
||||
|
||||
if c.author_id == v.id:
|
||||
return {"error": "You can't award yourself."}, 403
|
||||
|
||||
existing_award = g.db.query(AwardRelationship).options(lazyload('*')).filter(
|
||||
and_(
|
||||
AwardRelationship.comment_id == c.id,
|
||||
AwardRelationship.user_id == v.id,
|
||||
AwardRelationship.kind == kind
|
||||
)
|
||||
).first()
|
||||
|
||||
if existing_award and kind not in ALLOW_MULTIPLE:
|
||||
return {"error": "You can't give that award multiple times to the same comment."}, 409
|
||||
|
||||
comment_award.comment_id = c.id
|
||||
g.db.add(comment_award)
|
||||
|
||||
msg = f"@{v.username} has given your [comment]({c.permalink}) the {AWARDS[kind]['title']} Award!"
|
||||
|
||||
note = request.values.get("note", "")
|
||||
if note:
|
||||
msg += f"\n\n> {note}"
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, c.author, msg)
|
||||
|
||||
if kind in ACTIONS:
|
||||
ACTIONS[kind](comment=c)
|
||||
|
||||
c.author.received_award_count += 1
|
||||
g.db.add(c.author)
|
||||
|
||||
g.db.commit()
|
||||
if request.referrer and len(request.referrer) > 1: return redirect(request.referrer)
|
||||
else: return redirect("/")
|
||||
|
||||
@app.get("/admin/user_award")
|
||||
@auth_required
|
||||
def admin_userawards_get(v):
|
||||
|
||||
if v.admin_level < 6:
|
||||
abort(403)
|
||||
|
||||
return render_template("admin/user_award.html", awards=list(AWARDS.values()), v=v)
|
||||
|
||||
@app.post("/admin/user_award")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
@validate_formkey
|
||||
def admin_userawards_post(v):
|
||||
|
||||
if v.admin_level < 6:
|
||||
abort(403)
|
||||
|
||||
try: u = request.values.get("username").strip()
|
||||
except: abort(404)
|
||||
|
||||
u = get_user(u, graceful=False, v=v)
|
||||
|
||||
notify_awards = {}
|
||||
|
||||
latest = g.db.query(AwardRelationship).order_by(AwardRelationship.id.desc()).first()
|
||||
thing = latest.id
|
||||
|
||||
for key, value in request.values.items():
|
||||
if key not in AWARDS:
|
||||
continue
|
||||
|
||||
if value:
|
||||
|
||||
if int(value) > 0:
|
||||
notify_awards[key] = int(value)
|
||||
|
||||
for x in range(int(value)):
|
||||
thing += 1
|
||||
|
||||
award = AwardRelationship(
|
||||
id=thing,
|
||||
user_id=u.id,
|
||||
kind=key
|
||||
)
|
||||
|
||||
g.db.add(award)
|
||||
|
||||
text = "You were given the following awards:\n\n"
|
||||
|
||||
for key, value in notify_awards.items():
|
||||
text += f" - **{value}** {AWARDS[key]['title']} {'Awards' if value != 1 else 'Award'}\n"
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, u, text)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
from files.__main__ import app, limiter
|
||||
from files.helpers.wrappers import *
|
||||
from files.helpers.alerts import *
|
||||
from files.helpers.get import *
|
||||
from files.helpers.const import *
|
||||
from files.classes.award import *
|
||||
from flask import g, request
|
||||
|
||||
@app.get("/shop")
|
||||
@app.get("/settings/shop")
|
||||
@auth_required
|
||||
def shop(v):
|
||||
if site_name == "Drama":
|
||||
AWARDS = {
|
||||
"ban": {
|
||||
"kind": "ban",
|
||||
"title": "One-Day Ban",
|
||||
"description": "Bans the author for a day.",
|
||||
"icon": "fas fa-gavel",
|
||||
"color": "text-danger",
|
||||
"price": 5000
|
||||
},
|
||||
"shit": {
|
||||
"kind": "shit",
|
||||
"title": "Shit",
|
||||
"description": "Makes flies swarm a post.",
|
||||
"icon": "fas fa-poop",
|
||||
"color": "text-black-50",
|
||||
"price": 500
|
||||
},
|
||||
"fireflies": {
|
||||
"kind": "fireflies",
|
||||
"title": "Fireflies",
|
||||
"description": "Puts stars on the post.",
|
||||
"icon": "fas fa-sparkles",
|
||||
"color": "text-warning",
|
||||
"price": 500
|
||||
}
|
||||
}
|
||||
else:
|
||||
AWARDS = {
|
||||
"shit": {
|
||||
"kind": "shit",
|
||||
"title": "Shit",
|
||||
"description": "Makes flies swarm a post.",
|
||||
"icon": "fas fa-poop",
|
||||
"color": "text-black-50",
|
||||
"price": 500
|
||||
},
|
||||
"fireflies": {
|
||||
"kind": "fireflies",
|
||||
"title": "Fireflies",
|
||||
"description": "Puts stars on the post.",
|
||||
"icon": "fas fa-sparkles",
|
||||
"color": "text-warning",
|
||||
"price": 500
|
||||
}
|
||||
}
|
||||
|
||||
query = g.db.query(
|
||||
User.id, User.username, User.patron, User.namecolor,
|
||||
AwardRelationship.kind.label('last_award_kind'), func.count(AwardRelationship.id).label('last_award_count')
|
||||
).filter(AwardRelationship.submission_id==None, AwardRelationship.comment_id==None, User.patron > 0) \
|
||||
.group_by(User.username, User.patron, User.id, User.namecolor, AwardRelationship.kind) \
|
||||
.order_by(User.patron.desc(), AwardRelationship.kind.desc()) \
|
||||
.join(User).filter(User.id == v.id).all()
|
||||
|
||||
owned = []
|
||||
for row in (r._asdict() for r in query):
|
||||
kind = row['last_award_kind']
|
||||
if kind in AWARDS.keys():
|
||||
award = AWARDS[kind]
|
||||
award["owned_num"] = row['last_award_count']
|
||||
owned.append(award)
|
||||
|
||||
if v.patron:
|
||||
for val in AWARDS.values():
|
||||
if v.patron == 1: val["price"] = int(val["price"]*0.90)
|
||||
elif v.patron == 2: val["price"] = int(val["price"]*0.85)
|
||||
elif v.patron == 3: val["price"] = int(val["price"]*0.80)
|
||||
elif v.patron == 4: val["price"] = int(val["price"]*0.75)
|
||||
else: val["price"] = int(val["price"]*0.70)
|
||||
|
||||
return render_template("settings_shop.html", owned=owned, awards=list(AWARDS.values()), v=v)
|
||||
|
||||
|
||||
@app.post("/buy/<award>")
|
||||
@auth_required
|
||||
def buy(v, award):
|
||||
if site_name == "Drama":
|
||||
AWARDS = {
|
||||
"ban": {
|
||||
"kind": "ban",
|
||||
"title": "One-Day Ban",
|
||||
"description": "Bans the author for a day.",
|
||||
"icon": "fas fa-gavel",
|
||||
"color": "text-danger",
|
||||
"price": 5000
|
||||
},
|
||||
"shit": {
|
||||
"kind": "shit",
|
||||
"title": "Shit",
|
||||
"description": "Makes flies swarm a post.",
|
||||
"icon": "fas fa-poop",
|
||||
"color": "text-black-50",
|
||||
"price": 500
|
||||
},
|
||||
"fireflies": {
|
||||
"kind": "fireflies",
|
||||
"title": "Fireflies",
|
||||
"description": "Puts stars on the post.",
|
||||
"icon": "fas fa-sparkles",
|
||||
"color": "text-warning",
|
||||
"price": 500
|
||||
}
|
||||
}
|
||||
else:
|
||||
AWARDS = {
|
||||
"shit": {
|
||||
"kind": "shit",
|
||||
"title": "Shit",
|
||||
"description": "Makes flies swarm a post.",
|
||||
"icon": "fas fa-poop",
|
||||
"color": "text-black-50",
|
||||
"price": 500
|
||||
},
|
||||
"fireflies": {
|
||||
"kind": "fireflies",
|
||||
"title": "Fireflies",
|
||||
"description": "Puts stars on the post.",
|
||||
"icon": "fas fa-sparkles",
|
||||
"color": "text-warning",
|
||||
"price": 500
|
||||
}
|
||||
}
|
||||
|
||||
if award not in AWARDS: abort(400)
|
||||
price = AWARDS[award]["price"]
|
||||
if v.patron:
|
||||
if v.patron == 1: price = int(price*0.90)
|
||||
elif v.patron == 2: price = int(price*0.85)
|
||||
elif v.patron == 3: price = int(price*0.80)
|
||||
elif v.patron == 4: price = int(price*0.75)
|
||||
else: price = int(price*0.70)
|
||||
|
||||
if v.coins < price: return {"error": "Not enough coins."}, 400
|
||||
v.coins -= price
|
||||
g.db.add(v)
|
||||
|
||||
g.db.flush()
|
||||
thing = g.db.query(AwardRelationship).order_by(AwardRelationship.id.desc()).first().id
|
||||
thing += 1
|
||||
|
||||
award = AwardRelationship(id=thing, user_id=v.id, kind=award)
|
||||
g.db.add(award)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return {"message": "Award bought!"}
|
||||
|
||||
|
||||
def banaward_trigger(post=None, comment=None):
|
||||
|
||||
author = post.author if post else comment.author
|
||||
link = f"[this post]({post.permalink})" if post else f"[this comment]({comment.permalink})"
|
||||
|
||||
if not author.is_suspended:
|
||||
author.ban(reason="one-day ban award used", days=1)
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, author, f"Your account has been suspended for a day for {link}. It sucked and you should feel bad.")
|
||||
elif author.unban_utc > 0:
|
||||
author.unban_utc += 24*60*60
|
||||
g.db.add(author)
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, author, f"Your account has been suspended for yet another day for {link}. Seriously man?")
|
||||
|
||||
|
||||
ACTIONS = {
|
||||
"ban": banaward_trigger
|
||||
}
|
||||
|
||||
ALLOW_MULTIPLE = (
|
||||
"ban",
|
||||
"shit",
|
||||
"fireflies"
|
||||
)
|
||||
|
||||
@app.post("/post/<pid>/awards")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
def award_post(pid, v):
|
||||
|
||||
if v.is_suspended and v.unban_utc == 0: return {"error": "forbidden."}, 403
|
||||
|
||||
kind = request.values.get("kind", "").strip()
|
||||
|
||||
if kind not in AWARDS:
|
||||
return {"error": "That award doesn't exist."}, 404
|
||||
|
||||
post_award = g.db.query(AwardRelationship).options(lazyload('*')).filter(
|
||||
and_(
|
||||
AwardRelationship.kind == kind,
|
||||
AwardRelationship.user_id == v.id,
|
||||
AwardRelationship.submission_id == None,
|
||||
AwardRelationship.comment_id == None
|
||||
)
|
||||
).first()
|
||||
|
||||
if not post_award:
|
||||
return {"error": "You don't have that award."}, 404
|
||||
|
||||
post = g.db.query(Submission).options(lazyload('*')).filter_by(id=pid).first()
|
||||
|
||||
if not post or post.is_banned or post.deleted_utc > 0:
|
||||
return {"error": "That post doesn't exist or has been deleted or removed."}, 404
|
||||
|
||||
if post.author_id == v.id:
|
||||
return {"error": "You can't award yourself."}, 403
|
||||
|
||||
existing_award = g.db.query(AwardRelationship).options(lazyload('*')).filter(
|
||||
and_(
|
||||
AwardRelationship.submission_id == post.id,
|
||||
AwardRelationship.user_id == v.id,
|
||||
AwardRelationship.kind == kind
|
||||
)
|
||||
).first()
|
||||
|
||||
if existing_award and kind not in ALLOW_MULTIPLE:
|
||||
return {"error": "You can't give that award multiple times to the same post."}, 409
|
||||
|
||||
post_award.submission_id = post.id
|
||||
g.db.add(post_award)
|
||||
|
||||
msg = f"@{v.username} has given your [post]({post.permalink}) the {AWARDS[kind]['title']} Award!"
|
||||
|
||||
note = request.values.get("note", "").strip()
|
||||
if note:
|
||||
msg += f"\n\n> {note}"
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, post.author, msg)
|
||||
|
||||
if kind in ACTIONS: ACTIONS[kind](post=post)
|
||||
|
||||
post.author.received_award_count += 1
|
||||
g.db.add(post.author)
|
||||
|
||||
g.db.commit()
|
||||
if request.referrer and len(request.referrer) > 1: return redirect(request.referrer)
|
||||
else: return redirect("/")
|
||||
|
||||
|
||||
@app.post("/comment/<cid>/awards")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
def award_comment(cid, v):
|
||||
|
||||
if v.is_suspended and v.unban_utc == 0: return {"error": "forbidden"}, 403
|
||||
|
||||
kind = request.values.get("kind", "").strip()
|
||||
|
||||
if kind not in AWARDS:
|
||||
return {"error": "That award doesn't exist."}, 404
|
||||
|
||||
comment_award = g.db.query(AwardRelationship).options(lazyload('*')).filter(
|
||||
and_(
|
||||
AwardRelationship.kind == kind,
|
||||
AwardRelationship.user_id == v.id,
|
||||
AwardRelationship.submission_id == None,
|
||||
AwardRelationship.comment_id == None
|
||||
)
|
||||
).first()
|
||||
|
||||
if not comment_award:
|
||||
return {"error": "You don't have that award."}, 404
|
||||
|
||||
c = g.db.query(Comment).options(lazyload('*')).filter_by(id=cid).first()
|
||||
|
||||
if not c or c.is_banned or c.deleted_utc > 0:
|
||||
return {"error": "That comment doesn't exist or has been deleted or removed."}, 404
|
||||
|
||||
if c.author_id == v.id:
|
||||
return {"error": "You can't award yourself."}, 403
|
||||
|
||||
existing_award = g.db.query(AwardRelationship).options(lazyload('*')).filter(
|
||||
and_(
|
||||
AwardRelationship.comment_id == c.id,
|
||||
AwardRelationship.user_id == v.id,
|
||||
AwardRelationship.kind == kind
|
||||
)
|
||||
).first()
|
||||
|
||||
if existing_award and kind not in ALLOW_MULTIPLE:
|
||||
return {"error": "You can't give that award multiple times to the same comment."}, 409
|
||||
|
||||
comment_award.comment_id = c.id
|
||||
g.db.add(comment_award)
|
||||
|
||||
msg = f"@{v.username} has given your [comment]({c.permalink}) the {AWARDS[kind]['title']} Award!"
|
||||
|
||||
note = request.values.get("note", "").strip()
|
||||
if note:
|
||||
msg += f"\n\n> {note}"
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, c.author, msg)
|
||||
|
||||
if kind in ACTIONS:
|
||||
ACTIONS[kind](comment=c)
|
||||
|
||||
c.author.received_award_count += 1
|
||||
g.db.add(c.author)
|
||||
|
||||
g.db.commit()
|
||||
if request.referrer and len(request.referrer) > 1: return redirect(request.referrer)
|
||||
else: return redirect("/")
|
||||
|
||||
@app.get("/admin/user_award")
|
||||
@auth_required
|
||||
def admin_userawards_get(v):
|
||||
|
||||
if v.admin_level < 6:
|
||||
abort(403)
|
||||
|
||||
return render_template("admin/user_award.html", awards=list(AWARDS.values()), v=v)
|
||||
|
||||
@app.post("/admin/user_award")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
@validate_formkey
|
||||
def admin_userawards_post(v):
|
||||
|
||||
if v.admin_level < 6:
|
||||
abort(403)
|
||||
|
||||
try: u = request.values.get("username").strip()
|
||||
except: abort(404)
|
||||
|
||||
u = get_user(u, graceful=False, v=v)
|
||||
|
||||
notify_awards = {}
|
||||
|
||||
latest = g.db.query(AwardRelationship).order_by(AwardRelationship.id.desc()).first()
|
||||
thing = latest.id
|
||||
|
||||
for key, value in request.values.items():
|
||||
if key not in AWARDS:
|
||||
continue
|
||||
|
||||
if value:
|
||||
|
||||
if int(value) > 0:
|
||||
notify_awards[key] = int(value)
|
||||
|
||||
for x in range(int(value)):
|
||||
thing += 1
|
||||
|
||||
award = AwardRelationship(
|
||||
id=thing,
|
||||
user_id=u.id,
|
||||
kind=key
|
||||
)
|
||||
|
||||
g.db.add(award)
|
||||
|
||||
text = "You were given the following awards:\n\n"
|
||||
|
||||
for key, value in notify_awards.items():
|
||||
text += f" - **{value}** {AWARDS[key]['title']} {'Awards' if value != 1 else 'Award'}\n"
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, u, text)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return render_template("admin/user_award.html", awards=list(AWARDS.values()), v=v)
|
File diff suppressed because it is too large
Load Diff
|
@ -1,146 +1,146 @@
|
|||
from files.helpers.wrappers import *
|
||||
from files.helpers.security import *
|
||||
from files.helpers.discord import add_role
|
||||
from files.__main__ import app
|
||||
import requests
|
||||
|
||||
SERVER_ID = environ.get("DISCORD_SERVER_ID",'').strip()
|
||||
CLIENT_ID = environ.get("DISCORD_CLIENT_ID",'').strip()
|
||||
CLIENT_SECRET = environ.get("DISCORD_CLIENT_SECRET",'').strip()
|
||||
BOT_TOKEN = environ.get("DISCORD_BOT_TOKEN").strip()
|
||||
COINS_NAME = environ.get("COINS_NAME").strip()
|
||||
DISCORD_ENDPOINT = "https://discordapp.com/api/v6"
|
||||
WELCOME_CHANNEL="846509313941700618"
|
||||
|
||||
@app.get("/discord")
|
||||
@auth_required
|
||||
def join_discord(v):
|
||||
|
||||
if v.is_suspended != 0: return "You're banned"
|
||||
|
||||
if 'rdrama' in request.host and v.admin_level == 0 and v.patron == 0 and v.truecoins < 150: return f"You must earn 150 {COINS_NAME} before entering the Discord server. You earn {COINS_NAME} by making posts/comments and getting upvoted."
|
||||
|
||||
if v.shadowbanned or v.agendaposter: return ""
|
||||
|
||||
now=int(time.time())
|
||||
|
||||
state=generate_hash(f"{now}+{v.id}+discord")
|
||||
|
||||
state=f"{now}.{state}"
|
||||
|
||||
return redirect(f"https://discord.com/api/oauth2/authorize?client_id={CLIENT_ID}&redirect_uri=https%3A%2F%2F{app.config['SERVER_NAME']}%2Fdiscord_redirect&response_type=code&scope=identify%20guilds.join&state={state}")
|
||||
|
||||
|
||||
@app.get("/discord_redirect")
|
||||
@auth_required
|
||||
def discord_redirect(v):
|
||||
|
||||
|
||||
now=int(time.time())
|
||||
state=request.values.get('state','').split('.')
|
||||
|
||||
timestamp=state[0]
|
||||
|
||||
state=state[1]
|
||||
|
||||
if int(timestamp) < now-600:
|
||||
abort(400)
|
||||
|
||||
if not validate_hash(f"{timestamp}+{v.id}+discord", state):
|
||||
abort(400)
|
||||
|
||||
code = request.values.get("code","")
|
||||
if not code:
|
||||
abort(400)
|
||||
|
||||
data={
|
||||
"client_id":CLIENT_ID,
|
||||
'client_secret': CLIENT_SECRET,
|
||||
'grant_type': 'authorization_code',
|
||||
'code': code,
|
||||
'redirect_uri': f"https://{app.config['SERVER_NAME']}/discord_redirect",
|
||||
'scope': 'identify guilds.join'
|
||||
}
|
||||
headers={
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
url="https://discord.com/api/oauth2/token"
|
||||
|
||||
x=requests.post(url, headers=headers, data=data)
|
||||
|
||||
x=x.json()
|
||||
|
||||
|
||||
try:
|
||||
token=x["access_token"]
|
||||
except KeyError:
|
||||
abort(403)
|
||||
|
||||
|
||||
url="https://discord.com/api/users/@me"
|
||||
headers={
|
||||
'Authorization': f"Bearer {token}"
|
||||
}
|
||||
x=requests.get(url, headers=headers)
|
||||
|
||||
x=x.json()
|
||||
|
||||
|
||||
|
||||
headers={
|
||||
'Authorization': f"Bot {BOT_TOKEN}",
|
||||
'Content-Type': "application/json"
|
||||
}
|
||||
|
||||
if v.discord_id and v.discord_id != x['id']:
|
||||
url=f"https://discord.com/api/guilds/{SERVER_ID}/members/{v.discord_id}"
|
||||
requests.delete(url, headers=headers)
|
||||
|
||||
if g.db.query(User).options(lazyload('*')).filter(User.id!=v.id, User.discord_id==x["id"]).first():
|
||||
return render_template("message.html", title="Discord account already linked.", error="That Discord account is already in use by another user.", v=v)
|
||||
|
||||
v.discord_id=x["id"]
|
||||
g.db.add(v)
|
||||
|
||||
url=f"https://discord.com/api/guilds/{SERVER_ID}/members/{x['id']}"
|
||||
|
||||
name=v.username
|
||||
|
||||
data={
|
||||
"access_token":token,
|
||||
"nick":name,
|
||||
}
|
||||
|
||||
x=requests.put(url, headers=headers, json=data)
|
||||
|
||||
if x.status_code in [201, 204]:
|
||||
|
||||
if v.id == 1:
|
||||
add_role(v, "shrigma")
|
||||
time.sleep(0.1)
|
||||
|
||||
if v.admin_level > 0: add_role(v, "admin")
|
||||
|
||||
time.sleep(0.1)
|
||||
add_role(v, "linked")
|
||||
|
||||
if v.patron:
|
||||
time.sleep(0.1)
|
||||
add_role(v, str(v.patron))
|
||||
|
||||
else:
|
||||
return x.json()
|
||||
|
||||
|
||||
if x.status_code==204:
|
||||
|
||||
url=f"https://discord.com/api/guilds/{SERVER_ID}/members/{v.discord_id}"
|
||||
data={
|
||||
"nick": name
|
||||
}
|
||||
|
||||
requests.patch(url, headers=headers, json=data)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
from files.helpers.wrappers import *
|
||||
from files.helpers.security import *
|
||||
from files.helpers.discord import add_role
|
||||
from files.__main__ import app
|
||||
import requests
|
||||
|
||||
SERVER_ID = environ.get("DISCORD_SERVER_ID",'').strip()
|
||||
CLIENT_ID = environ.get("DISCORD_CLIENT_ID",'').strip()
|
||||
CLIENT_SECRET = environ.get("DISCORD_CLIENT_SECRET",'').strip()
|
||||
BOT_TOKEN = environ.get("DISCORD_BOT_TOKEN").strip()
|
||||
COINS_NAME = environ.get("COINS_NAME").strip()
|
||||
DISCORD_ENDPOINT = "https://discordapp.com/api/v6"
|
||||
WELCOME_CHANNEL="846509313941700618"
|
||||
|
||||
@app.get("/discord")
|
||||
@auth_required
|
||||
def join_discord(v):
|
||||
|
||||
if v.is_suspended != 0: return "You're banned"
|
||||
|
||||
if 'rama' in request.host and v.admin_level == 0 and v.patron == 0 and v.truecoins < 150: return f"You must earn 150 {COINS_NAME} before entering the Discord server. You earn {COINS_NAME} by making posts/comments and getting upvoted."
|
||||
|
||||
if v.shadowbanned or v.agendaposter: return ""
|
||||
|
||||
now=int(time.time())
|
||||
|
||||
state=generate_hash(f"{now}+{v.id}+discord")
|
||||
|
||||
state=f"{now}.{state}"
|
||||
|
||||
return redirect(f"https://discord.com/api/oauth2/authorize?client_id={CLIENT_ID}&redirect_uri=https%3A%2F%2F{app.config['SERVER_NAME']}%2Fdiscord_redirect&response_type=code&scope=identify%20guilds.join&state={state}")
|
||||
|
||||
|
||||
@app.get("/discord_redirect")
|
||||
@auth_required
|
||||
def discord_redirect(v):
|
||||
|
||||
|
||||
now=int(time.time())
|
||||
state=request.values.get('state','').split('.')
|
||||
|
||||
timestamp=state[0]
|
||||
|
||||
state=state[1]
|
||||
|
||||
if int(timestamp) < now-600:
|
||||
abort(400)
|
||||
|
||||
if not validate_hash(f"{timestamp}+{v.id}+discord", state):
|
||||
abort(400)
|
||||
|
||||
code = request.values.get("code","")
|
||||
if not code:
|
||||
abort(400)
|
||||
|
||||
data={
|
||||
"client_id":CLIENT_ID,
|
||||
'client_secret': CLIENT_SECRET,
|
||||
'grant_type': 'authorization_code',
|
||||
'code': code,
|
||||
'redirect_uri': f"https://{app.config['SERVER_NAME']}/discord_redirect",
|
||||
'scope': 'identify guilds.join'
|
||||
}
|
||||
headers={
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
url="https://discord.com/api/oauth2/token"
|
||||
|
||||
x=requests.post(url, headers=headers, data=data)
|
||||
|
||||
x=x.json()
|
||||
|
||||
|
||||
try:
|
||||
token=x["access_token"]
|
||||
except KeyError:
|
||||
abort(403)
|
||||
|
||||
|
||||
url="https://discord.com/api/users/@me"
|
||||
headers={
|
||||
'Authorization': f"Bearer {token}"
|
||||
}
|
||||
x=requests.get(url, headers=headers)
|
||||
|
||||
x=x.json()
|
||||
|
||||
|
||||
|
||||
headers={
|
||||
'Authorization': f"Bot {BOT_TOKEN}",
|
||||
'Content-Type': "application/json"
|
||||
}
|
||||
|
||||
if v.discord_id and v.discord_id != x['id']:
|
||||
url=f"https://discord.com/api/guilds/{SERVER_ID}/members/{v.discord_id}"
|
||||
requests.delete(url, headers=headers)
|
||||
|
||||
if g.db.query(User).options(lazyload('*')).filter(User.id!=v.id, User.discord_id==x["id"]).first():
|
||||
return render_template("message.html", title="Discord account already linked.", error="That Discord account is already in use by another user.", v=v)
|
||||
|
||||
v.discord_id=x["id"]
|
||||
g.db.add(v)
|
||||
|
||||
url=f"https://discord.com/api/guilds/{SERVER_ID}/members/{x['id']}"
|
||||
|
||||
name=v.username
|
||||
|
||||
data={
|
||||
"access_token":token,
|
||||
"nick":name,
|
||||
}
|
||||
|
||||
x=requests.put(url, headers=headers, json=data)
|
||||
|
||||
if x.status_code in [201, 204]:
|
||||
|
||||
if v.id == 1:
|
||||
add_role(v, "shrigma")
|
||||
time.sleep(0.1)
|
||||
|
||||
if v.admin_level > 0: add_role(v, "admin")
|
||||
|
||||
time.sleep(0.1)
|
||||
add_role(v, "linked")
|
||||
|
||||
if v.patron:
|
||||
time.sleep(0.1)
|
||||
add_role(v, str(v.patron))
|
||||
|
||||
else:
|
||||
return x.json()
|
||||
|
||||
|
||||
if x.status_code==204:
|
||||
|
||||
url=f"https://discord.com/api/guilds/{SERVER_ID}/members/{v.discord_id}"
|
||||
data={
|
||||
"nick": name
|
||||
}
|
||||
|
||||
requests.patch(url, headers=headers, json=data)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return redirect(f"https://discord.com/channels/{SERVER_ID}/{WELCOME_CHANNEL}")
|
|
@ -1,84 +1,84 @@
|
|||
import jinja2.exceptions
|
||||
|
||||
from files.helpers.wrappers import *
|
||||
from files.helpers.session import *
|
||||
from flask import *
|
||||
from urllib.parse import quote, urlencode
|
||||
import time
|
||||
from files.__main__ import app, limiter
|
||||
|
||||
# Errors
|
||||
|
||||
|
||||
|
||||
@app.errorhandler(400)
|
||||
@auth_desired
|
||||
def error_400(e, v):
|
||||
if request.headers.get("Authorization"): return {"error": "400 Bad Request"}, 400
|
||||
else: return render_template('errors/400.html', v=v), 400
|
||||
|
||||
@app.errorhandler(401)
|
||||
def error_401(e):
|
||||
|
||||
path = request.path
|
||||
qs = urlencode(dict(request.values))
|
||||
argval = quote(f"{path}?{qs}", safe='')
|
||||
output = f"/login?redirect={argval}"
|
||||
|
||||
if request.headers.get("Authorization"): return {"error": "401 Not Authorized"}, 401
|
||||
else: return redirect(output)
|
||||
|
||||
|
||||
@app.errorhandler(403)
|
||||
@auth_desired
|
||||
def error_403(e, v):
|
||||
if request.headers.get("Authorization"): return {"error": "403 Forbidden"}, 403
|
||||
else: return render_template('errors/403.html', v=v), 403
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
@auth_desired
|
||||
def error_404(e, v):
|
||||
if request.headers.get("Authorization"): return {"error": "404 Not Found"}, 404
|
||||
else: return render_template('errors/404.html', v=v), 404
|
||||
|
||||
|
||||
@app.errorhandler(405)
|
||||
@auth_desired
|
||||
def error_405(e, v):
|
||||
if request.headers.get("Authorization"): return {"error": "405 Method Not Allowed"}, 405
|
||||
else: return render_template('errors/405.html', v=v), 405
|
||||
|
||||
|
||||
@app.errorhandler(429)
|
||||
@auth_desired
|
||||
def error_429(e, v):
|
||||
if request.headers.get("Authorization"): return {"error": "429 Too Many Requests"}, 429
|
||||
else: return render_template('errors/429.html', v=v), 429
|
||||
|
||||
|
||||
@app.errorhandler(500)
|
||||
@auth_desired
|
||||
def error_500(e, v):
|
||||
g.db.rollback()
|
||||
|
||||
if request.headers.get("Authorization"): return {"error": "500 Internal Server Error"}, 500
|
||||
else: return render_template('errors/500.html', v=v), 500
|
||||
|
||||
|
||||
@app.post("/allow_nsfw")
|
||||
def allow_nsfw():
|
||||
|
||||
session["over_18"] = int(time.time()) + 3600
|
||||
return redirect(request.values.get("redir", "/"))
|
||||
|
||||
|
||||
@app.get("/error/<error>")
|
||||
@auth_desired
|
||||
def error_all_preview(error, v):
|
||||
|
||||
try:
|
||||
return render_template(f"errors/{error}.html", v=v)
|
||||
except jinja2.exceptions.TemplateNotFound:
|
||||
abort(400)
|
||||
|
||||
import jinja2.exceptions
|
||||
|
||||
from files.helpers.wrappers import *
|
||||
from files.helpers.session import *
|
||||
from flask import *
|
||||
from urllib.parse import quote, urlencode
|
||||
import time
|
||||
from files.__main__ import app, limiter
|
||||
|
||||
# Errors
|
||||
|
||||
|
||||
|
||||
@app.errorhandler(400)
|
||||
@auth_desired
|
||||
def error_400(e, v):
|
||||
if request.headers.get("Authorization"): return {"error": "400 Bad Request"}, 400
|
||||
else: return render_template('errors/400.html', v=v), 400
|
||||
|
||||
@app.errorhandler(401)
|
||||
def error_401(e):
|
||||
|
||||
path = request.path
|
||||
qs = urlencode(dict(request.values))
|
||||
argval = quote(f"{path}?{qs}", safe='')
|
||||
output = f"/login?redirect={argval}"
|
||||
|
||||
if request.headers.get("Authorization"): return {"error": "401 Not Authorized"}, 401
|
||||
else: return redirect(output)
|
||||
|
||||
|
||||
@app.errorhandler(403)
|
||||
@auth_desired
|
||||
def error_403(e, v):
|
||||
if request.headers.get("Authorization"): return {"error": "403 Forbidden"}, 403
|
||||
else: return render_template('errors/403.html', v=v), 403
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
@auth_desired
|
||||
def error_404(e, v):
|
||||
if request.headers.get("Authorization"): return {"error": "404 Not Found"}, 404
|
||||
else: return render_template('errors/404.html', v=v), 404
|
||||
|
||||
|
||||
@app.errorhandler(405)
|
||||
@auth_desired
|
||||
def error_405(e, v):
|
||||
if request.headers.get("Authorization"): return {"error": "405 Method Not Allowed"}, 405
|
||||
else: return render_template('errors/405.html', v=v), 405
|
||||
|
||||
|
||||
@app.errorhandler(429)
|
||||
@auth_desired
|
||||
def error_429(e, v):
|
||||
if request.headers.get("Authorization"): return {"error": "429 Too Many Requests"}, 429
|
||||
else: return render_template('errors/429.html', v=v), 429
|
||||
|
||||
|
||||
@app.errorhandler(500)
|
||||
@auth_desired
|
||||
def error_500(e, v):
|
||||
g.db.rollback()
|
||||
|
||||
if request.headers.get("Authorization"): return {"error": "500 Internal Server Error"}, 500
|
||||
else: return render_template('errors/500.html', v=v), 500
|
||||
|
||||
|
||||
@app.post("/allow_nsfw")
|
||||
def allow_nsfw():
|
||||
|
||||
session["over_18"] = int(time.time()) + 3600
|
||||
return redirect(request.values.get("redir", "/"))
|
||||
|
||||
|
||||
@app.get("/error/<error>")
|
||||
@auth_desired
|
||||
def error_all_preview(error, v):
|
||||
|
||||
try:
|
||||
return render_template(f"errors/{error}.html", v=v)
|
||||
except jinja2.exceptions.TemplateNotFound:
|
||||
abort(400)
|
||||
|
||||
|
|
|
@ -1,66 +1,66 @@
|
|||
import html
|
||||
from .front import frontlist
|
||||
from datetime import datetime
|
||||
from files.helpers.jinja2 import full_link
|
||||
from files.helpers.get import *
|
||||
from yattag import Doc
|
||||
|
||||
from files.__main__ import app
|
||||
|
||||
@app.get('/rss/<sort>/<t>')
|
||||
def feeds_user(sort='hot', t='all'):
|
||||
|
||||
page = int(request.values.get("page", 1))
|
||||
|
||||
ids, next_exists = frontlist(
|
||||
sort=sort,
|
||||
page=page,
|
||||
t=t,
|
||||
v=None,
|
||||
)
|
||||
|
||||
posts = get_posts(ids)
|
||||
|
||||
domain = environ.get("DOMAIN").strip()
|
||||
|
||||
doc, tag, text = Doc().tagtext()
|
||||
|
||||
with tag("feed", ("xmlns:media","http://search.yahoo.com/mrss/"), xmlns="http://www.w3.org/2005/Atom",):
|
||||
with tag("title", type="text"):
|
||||
text(f"{sort} posts from {domain}")
|
||||
|
||||
doc.stag("link", href=request.url)
|
||||
doc.stag("link", href=request.url_root)
|
||||
|
||||
for post in posts:
|
||||
with tag("entry", ("xml:base", request.url)):
|
||||
with tag("title", type="text"):
|
||||
text(post.title)
|
||||
|
||||
with tag("id"):
|
||||
text(post.fullname)
|
||||
|
||||
if (post.edited_utc > 0):
|
||||
with tag("updated"):
|
||||
text(datetime.utcfromtimestamp(post.edited_utc).isoformat())
|
||||
|
||||
with tag("published"):
|
||||
text(datetime.utcfromtimestamp(post.created_utc).isoformat())
|
||||
|
||||
with tag("author"):
|
||||
with tag("name"):
|
||||
text(post.author.username)
|
||||
with tag("uri"):
|
||||
text(f'https://{site}/@{post.author.username}')
|
||||
|
||||
doc.stag("link", href=full_link(post.permalink))
|
||||
|
||||
image_url = post.thumb_url or post.embed_url or post.url
|
||||
|
||||
doc.stag("media:thumbnail", url=image_url)
|
||||
|
||||
if len(post.body_html) > 0:
|
||||
with tag("content", type="html"):
|
||||
doc.cdata(f'<img loading="lazy" src={image_url}/><br/>{post.body_html}')
|
||||
|
||||
import html
|
||||
from .front import frontlist
|
||||
from datetime import datetime
|
||||
from files.helpers.jinja2 import full_link
|
||||
from files.helpers.get import *
|
||||
from yattag import Doc
|
||||
|
||||
from files.__main__ import app
|
||||
|
||||
@app.get('/rss/<sort>/<t>')
|
||||
def feeds_user(sort='hot', t='all'):
|
||||
|
||||
page = int(request.values.get("page", 1))
|
||||
|
||||
ids, next_exists = frontlist(
|
||||
sort=sort,
|
||||
page=page,
|
||||
t=t,
|
||||
v=None,
|
||||
)
|
||||
|
||||
posts = get_posts(ids)
|
||||
|
||||
domain = environ.get("DOMAIN").strip()
|
||||
|
||||
doc, tag, text = Doc().tagtext()
|
||||
|
||||
with tag("feed", ("xmlns:media","http://search.yahoo.com/mrss/"), xmlns="http://www.w3.org/2005/Atom",):
|
||||
with tag("title", type="text"):
|
||||
text(f"{sort} posts from {domain}")
|
||||
|
||||
doc.stag("link", href=request.url)
|
||||
doc.stag("link", href=request.url_root)
|
||||
|
||||
for post in posts:
|
||||
with tag("entry", ("xml:base", request.url)):
|
||||
with tag("title", type="text"):
|
||||
text(post.title)
|
||||
|
||||
with tag("id"):
|
||||
text(post.fullname)
|
||||
|
||||
if (post.edited_utc > 0):
|
||||
with tag("updated"):
|
||||
text(datetime.utcfromtimestamp(post.edited_utc).isoformat())
|
||||
|
||||
with tag("published"):
|
||||
text(datetime.utcfromtimestamp(post.created_utc).isoformat())
|
||||
|
||||
with tag("author"):
|
||||
with tag("name"):
|
||||
text(post.author.username)
|
||||
with tag("uri"):
|
||||
text(f'https://{site}/@{post.author.username}')
|
||||
|
||||
doc.stag("link", href=full_link(post.permalink))
|
||||
|
||||
image_url = post.thumb_url or post.embed_url or post.url
|
||||
|
||||
doc.stag("media:thumbnail", url=image_url)
|
||||
|
||||
if len(post.body_html) > 0:
|
||||
with tag("content", type="html"):
|
||||
doc.cdata(f'<img loading="lazy" src={image_url}/><br/>{post.body_html}')
|
||||
|
||||
return Response( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+ doc.getvalue(), mimetype="application/xml")
|
|
@ -1,396 +1,398 @@
|
|||
from files.helpers.wrappers import *
|
||||
from files.helpers.get import *
|
||||
|
||||
from files.__main__ import app, cache
|
||||
from files.classes.submission import Submission
|
||||
|
||||
defaulttimefilter = environ.get("DEFAULT_TIME_FILTER", "all").strip()
|
||||
|
||||
@app.get("/post/")
|
||||
def slash_post():
|
||||
return redirect("/")
|
||||
|
||||
@app.get("/notifications")
|
||||
@auth_required
|
||||
def notifications(v):
|
||||
try: page = int(request.values.get('page', 1))
|
||||
except: page = 1
|
||||
messages = request.values.get('messages', False)
|
||||
modmail = request.values.get('modmail', False)
|
||||
posts = request.values.get('posts', False)
|
||||
if modmail and v.admin_level == 6:
|
||||
comments = g.db.query(Comment).filter(Comment.sentto==0).order_by(Comment.created_utc.desc()).offset(25*(page-1)).limit(26).all()
|
||||
next_exists = (len(comments) > 25)
|
||||
comments = comments[:25]
|
||||
elif messages:
|
||||
comments = g.db.query(Comment).filter(or_(Comment.author_id==v.id, Comment.sentto==v.id), Comment.parent_submission == None).order_by(Comment.created_utc.desc(), not_(Comment.child_comments.any())).offset(25*(page-1)).limit(26).all()
|
||||
next_exists = (len(comments) > 25)
|
||||
comments = comments[:25]
|
||||
elif posts:
|
||||
notifications = v.notifications.join(Notification.comment).filter(Comment.author_id == AUTOJANNY_ACCOUNT).order_by(Notification.id.desc()).offset(25 * (page - 1)).limit(100).all()
|
||||
|
||||
comments = []
|
||||
|
||||
for index, x in enumerate(notifications):
|
||||
c = x.comment
|
||||
if x.read and index > 26: break
|
||||
elif not x.read:
|
||||
c.unread = True
|
||||
x.read = True
|
||||
g.db.add(x)
|
||||
comments.append(c)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
next_exists = (len(comments) > 25)
|
||||
listing = comments[:25]
|
||||
else:
|
||||
|
||||
notifications = v.notifications.join(Notification.comment).filter(
|
||||
Comment.is_banned == False,
|
||||
Comment.deleted_utc == 0,
|
||||
Comment.author_id != AUTOJANNY_ACCOUNT,
|
||||
).order_by(Notification.id.desc()).offset(50 * (page - 1)).limit(51).all()
|
||||
|
||||
next_exists = (len(notifications) > 50)
|
||||
notifications = notifications[:50]
|
||||
cids = [x.comment_id for x in notifications]
|
||||
comments = get_comments(cids, v=v, load_parent=True)
|
||||
|
||||
i = 0
|
||||
for x in notifications:
|
||||
try:
|
||||
if not x.read:
|
||||
comments[i].unread = True
|
||||
x.read = True
|
||||
g.db.add(x)
|
||||
except: continue
|
||||
i += 1
|
||||
|
||||
g.db.commit()
|
||||
|
||||
if not posts:
|
||||
listing = []
|
||||
for c in comments:
|
||||
c.is_blocked = False
|
||||
c.is_blocking = False
|
||||
if c.parent_submission and c.parent_comment and c.parent_comment.author_id == v.id:
|
||||
c.replies = []
|
||||
while c.parent_comment and c.parent_comment.author_id == v.id:
|
||||
parent = c.parent_comment
|
||||
if c not in parent.replies2:
|
||||
parent.replies2 = parent.replies2 + [c]
|
||||
parent.replies = parent.replies2
|
||||
c = parent
|
||||
if c not in listing:
|
||||
listing.append(c)
|
||||
c.replies = c.replies2
|
||||
elif c.parent_submission:
|
||||
c.replies = []
|
||||
if c not in listing:
|
||||
listing.append(c)
|
||||
else:
|
||||
if c.parent_comment:
|
||||
while c.level > 1:
|
||||
c = c.parent_comment
|
||||
|
||||
if c not in listing:
|
||||
listing.append(c)
|
||||
|
||||
|
||||
return render_template("notifications.html",
|
||||
v=v,
|
||||
notifications=listing,
|
||||
next_exists=next_exists,
|
||||
page=page,
|
||||
standalone=True,
|
||||
render_replies=True
|
||||
)
|
||||
|
||||
|
||||
|
||||
@app.get("/")
|
||||
@app.get("/logged_out")
|
||||
@auth_desired
|
||||
def front_all(v):
|
||||
|
||||
if not v and request.path == "/" and not request.headers.get("Authorization"): return redirect(f"/logged_out{request.full_path}")
|
||||
|
||||
if v and request.path.startswith('/logged_out'): v = None
|
||||
|
||||
try: page = int(request.values.get("page") or 1)
|
||||
except: abort(400)
|
||||
|
||||
page = max(page, 1)
|
||||
|
||||
if v:
|
||||
defaultsorting = v.defaultsorting
|
||||
defaulttime = v.defaulttime
|
||||
else:
|
||||
defaultsorting = "hot"
|
||||
defaulttime = defaulttimefilter
|
||||
|
||||
sort=request.values.get("sort", defaultsorting)
|
||||
t=request.values.get('t', defaulttime)
|
||||
|
||||
ids, next_exists = frontlist(sort=sort,
|
||||
page=page,
|
||||
t=t,
|
||||
v=v,
|
||||
filter_words=v.filter_words if v else [],
|
||||
)
|
||||
|
||||
posts = get_posts(ids, v=v)
|
||||
|
||||
if v and v.hidevotedon: posts = [x for x in posts if not hasattr(x, 'voted') or not x.voted]
|
||||
|
||||
if request.headers.get("Authorization"): return {"data": [x.json for x in posts], "next_exists": next_exists}
|
||||
else: return render_template("home.html", v=v, listing=posts, next_exists=next_exists, sort=sort, t=t, page=page)
|
||||
|
||||
|
||||
|
||||
@cache.memoize(timeout=86400)
|
||||
def frontlist(v=None, sort="hot", page=1, t="all", ids_only=True, filter_words=''):
|
||||
|
||||
posts = g.db.query(Submission.id).options(lazyload('*'))
|
||||
|
||||
if 'rdrama' in request.host and sort == "hot":
|
||||
cutoff = int(time.time()) - 86400
|
||||
posts = posts.filter(Submission.created_utc >= cutoff)
|
||||
elif t != 'all':
|
||||
now = int(time.time())
|
||||
if t == 'hour': cutoff = now - 3600
|
||||
elif t == 'week': cutoff = now - 604800
|
||||
elif t == 'month': cutoff = now - 2592000
|
||||
elif t == 'year': cutoff = now - 31536000
|
||||
else: cutoff = now - 86400
|
||||
posts = posts.filter(Submission.created_utc >= cutoff)
|
||||
|
||||
posts = posts.filter_by(is_banned=False, stickied=None, private=False, deleted_utc = 0)
|
||||
|
||||
if v and v.admin_level == 0:
|
||||
blocking = [x[0] for x in g.db.query(
|
||||
UserBlock.target_id).filter_by(
|
||||
user_id=v.id).all()]
|
||||
blocked = [x[0] for x in g.db.query(
|
||||
UserBlock.user_id).filter_by(
|
||||
target_id=v.id).all()]
|
||||
posts = posts.filter(
|
||||
Submission.author_id.notin_(blocking),
|
||||
Submission.author_id.notin_(blocked)
|
||||
)
|
||||
|
||||
if not (v and v.changelogsub):
|
||||
posts=posts.filter(not_(Submission.title.ilike(f'[changelog]%')))
|
||||
|
||||
if v and filter_words:
|
||||
for word in filter_words:
|
||||
posts=posts.filter(not_(Submission.title.ilike(f'%{word}%')))
|
||||
|
||||
if not (v and v.shadowbanned):
|
||||
shadowbanned = [x[0] for x in g.db.query(User.id).options(lazyload('*')).filter(User.shadowbanned != None).all()]
|
||||
posts = posts.filter(Submission.author_id.notin_(shadowbanned))
|
||||
|
||||
if sort == "hot":
|
||||
ti = int(time.time()) + 3600
|
||||
posts = posts.order_by(-1000000*(Submission.upvotes - Submission.downvotes + 1 + Submission.comment_count/5)/(func.power(((ti - Submission.created_utc)/1000), 1.35)))
|
||||
elif sort == "new":
|
||||
posts = posts.order_by(Submission.created_utc.desc())
|
||||
elif sort == "old":
|
||||
posts = posts.order_by(Submission.created_utc.asc())
|
||||
elif sort == "controversial":
|
||||
posts = posts.order_by(-1 * Submission.upvotes * (Submission.downvotes+1))
|
||||
elif sort == "top":
|
||||
posts = posts.order_by(Submission.downvotes - Submission.upvotes)
|
||||
elif sort == "bottom":
|
||||
posts = posts.order_by(Submission.upvotes - Submission.downvotes)
|
||||
elif sort == "comments":
|
||||
posts = posts.order_by(Submission.comment_count.desc())
|
||||
|
||||
if v:
|
||||
if v.agendaposter: size = 5
|
||||
else: size = v.frontsize
|
||||
else: size = 25
|
||||
|
||||
posts = posts.offset(size * (page - 1)).limit(size+1).all()
|
||||
|
||||
next_exists = (len(posts) > size)
|
||||
|
||||
posts = posts[:size]
|
||||
|
||||
if page == 1: posts = g.db.query(Submission.id).options(lazyload('*')).filter(Submission.stickied != None).all() + posts
|
||||
|
||||
if ids_only: posts = [x[0] for x in posts]
|
||||
|
||||
return posts, next_exists
|
||||
|
||||
|
||||
@app.get("/changelog")
|
||||
@auth_desired
|
||||
def changelog(v):
|
||||
|
||||
|
||||
page = int(request.values.get("page") or 1)
|
||||
page = max(page, 1)
|
||||
|
||||
sort=request.values.get("sort", "new")
|
||||
t=request.values.get('t', "all")
|
||||
|
||||
ids = changeloglist(sort=sort,
|
||||
page=page,
|
||||
t=t,
|
||||
v=v,
|
||||
)
|
||||
|
||||
next_exists = (len(ids) > 25)
|
||||
ids = ids[:25]
|
||||
|
||||
posts = get_posts(ids, v=v)
|
||||
|
||||
if request.headers.get("Authorization"): return {"data": [x.json for x in posts], "next_exists": next_exists}
|
||||
else: return render_template("changelog.html", v=v, listing=posts, next_exists=next_exists, sort=sort, t=t, page=page)
|
||||
|
||||
|
||||
@cache.memoize(timeout=86400)
|
||||
def changeloglist(v=None, sort="new", page=1 ,t="all"):
|
||||
|
||||
posts = g.db.query(Submission.id).options(lazyload('*')).filter_by(is_banned=False, private=False,).filter(Submission.deleted_utc == 0)
|
||||
|
||||
if v and v.admin_level == 0:
|
||||
blocking = [x[0] for x in g.db.query(
|
||||
UserBlock.target_id).filter_by(
|
||||
user_id=v.id).all()]
|
||||
blocked = [x[0] for x in g.db.query(
|
||||
UserBlock.user_id).filter_by(
|
||||
target_id=v.id).all()]
|
||||
posts = posts.filter(
|
||||
Submission.author_id.notin_(blocking),
|
||||
Submission.author_id.notin_(blocked)
|
||||
)
|
||||
|
||||
admins = [x[0] for x in g.db.query(User.id).options(lazyload('*')).filter(User.admin_level == 6).all()]
|
||||
posts = posts.filter(Submission.title.ilike('_changelog%'), Submission.author_id.in_(admins))
|
||||
|
||||
if t != 'all':
|
||||
cutoff = 0
|
||||
now = int(time.time())
|
||||
if t == 'hour':
|
||||
cutoff = now - 3600
|
||||
elif t == 'day':
|
||||
cutoff = now - 86400
|
||||
elif t == 'week':
|
||||
cutoff = now - 604800
|
||||
elif t == 'month':
|
||||
cutoff = now - 2592000
|
||||
elif t == 'year':
|
||||
cutoff = now - 31536000
|
||||
posts = posts.filter(Submission.created_utc >= cutoff)
|
||||
|
||||
if sort == "new":
|
||||
posts = posts.order_by(Submission.created_utc.desc())
|
||||
elif sort == "old":
|
||||
posts = posts.order_by(Submission.created_utc.asc())
|
||||
elif sort == "controversial":
|
||||
posts = posts.order_by(-1 * Submission.upvotes * (Submission.downvotes+1))
|
||||
elif sort == "top":
|
||||
posts = posts.order_by(Submission.downvotes - Submission.upvotes)
|
||||
elif sort == "bottom":
|
||||
posts = posts.order_by(Submission.upvotes - Submission.downvotes)
|
||||
elif sort == "comments":
|
||||
posts = posts.order_by(Submission.comment_count.desc())
|
||||
|
||||
posts = posts.offset(25 * (page - 1)).limit(26).all()
|
||||
|
||||
return [x[0] for x in posts]
|
||||
|
||||
|
||||
@app.get("/random")
|
||||
@auth_desired
|
||||
def random_post(v):
|
||||
|
||||
x = g.db.query(Submission).options(lazyload('*')).filter(Submission.deleted_utc == 0, Submission.is_banned == False)
|
||||
total = x.count()
|
||||
n = random.randint(1, total - 2)
|
||||
|
||||
post = x.offset(n).limit(1).first()
|
||||
return redirect(f"/post/{post.id}")
|
||||
|
||||
@cache.memoize(timeout=86400)
|
||||
def comment_idlist(page=1, v=None, nsfw=False, sort="new", t="all"):
|
||||
|
||||
posts = g.db.query(Submission).options(lazyload('*'))
|
||||
cc_idlist = [x[0] for x in g.db.query(Submission.id).options(lazyload('*')).filter(Submission.club == True).all()]
|
||||
|
||||
posts = posts.subquery()
|
||||
|
||||
comments = g.db.query(Comment.id).options(lazyload('*')).filter(Comment.parent_submission.notin_(cc_idlist))
|
||||
|
||||
if v and v.admin_level <= 3:
|
||||
blocking = [x[0] for x in g.db.query(
|
||||
UserBlock.target_id).filter_by(
|
||||
user_id=v.id).all()]
|
||||
blocked = [x[0] for x in g.db.query(
|
||||
UserBlock.user_id).filter_by(
|
||||
target_id=v.id).all()]
|
||||
|
||||
comments = comments.filter(
|
||||
Comment.author_id.notin_(blocking),
|
||||
Comment.author_id.notin_(blocked)
|
||||
)
|
||||
|
||||
if not v or not v.admin_level >= 3:
|
||||
comments = comments.filter_by(is_banned=False).filter(Comment.deleted_utc == 0)
|
||||
|
||||
now = int(time.time())
|
||||
if t == 'hour':
|
||||
cutoff = now - 3600
|
||||
elif t == 'day':
|
||||
cutoff = now - 86400
|
||||
elif t == 'week':
|
||||
cutoff = now - 604800
|
||||
elif t == 'month':
|
||||
cutoff = now - 2592000
|
||||
elif t == 'year':
|
||||
cutoff = now - 31536000
|
||||
else:
|
||||
cutoff = 0
|
||||
comments = comments.filter(Comment.created_utc >= cutoff)
|
||||
|
||||
if sort == "new":
|
||||
comments = comments.order_by(Comment.created_utc.desc())
|
||||
elif sort == "old":
|
||||
comments = comments.order_by(Comment.created_utc.asc())
|
||||
elif sort == "controversial":
|
||||
comments = comments.order_by(-1 * Comment.upvotes * (Comment.downvotes+1))
|
||||
elif sort == "top":
|
||||
comments = comments.order_by(Comment.downvotes - Comment.upvotes)
|
||||
elif sort == "bottom":
|
||||
comments = comments.order_by(Comment.upvotes - Comment.downvotes)
|
||||
|
||||
comments = comments.offset(25 * (page - 1)).limit(26).all()
|
||||
return [x[0] for x in comments]
|
||||
|
||||
@app.get("/comments")
|
||||
@auth_desired
|
||||
def all_comments(v):
|
||||
|
||||
|
||||
page = int(request.values.get("page", 1))
|
||||
|
||||
sort=request.values.get("sort", "new")
|
||||
t=request.values.get("t", defaulttimefilter)
|
||||
|
||||
idlist = comment_idlist(v=v,
|
||||
page=page,
|
||||
sort=sort,
|
||||
t=t,
|
||||
)
|
||||
|
||||
comments = get_comments(idlist, v=v)
|
||||
|
||||
next_exists = len(idlist) > 25
|
||||
|
||||
idlist = idlist[:25]
|
||||
|
||||
if request.headers.get("Authorization"): return {"data": [x.json for x in comments]}
|
||||
else: return render_template("home_comments.html", v=v, sort=sort, t=t, page=page, comments=comments, standalone=True, next_exists=next_exists)
|
||||
from files.helpers.wrappers import *
|
||||
from files.helpers.get import *
|
||||
|
||||
from files.__main__ import app, cache
|
||||
from files.classes.submission import Submission
|
||||
|
||||
defaulttimefilter = environ.get("DEFAULT_TIME_FILTER", "all").strip()
|
||||
|
||||
@app.get("/post/")
|
||||
def slash_post():
|
||||
return redirect("/")
|
||||
|
||||
@app.get("/notifications")
|
||||
@auth_required
|
||||
def notifications(v):
|
||||
try: page = int(request.values.get('page', 1))
|
||||
except: page = 1
|
||||
messages = request.values.get('messages', False)
|
||||
modmail = request.values.get('modmail', False)
|
||||
posts = request.values.get('posts', False)
|
||||
if modmail and v.admin_level == 6:
|
||||
comments = g.db.query(Comment).filter(Comment.sentto==0).order_by(Comment.created_utc.desc()).offset(25*(page-1)).limit(26).all()
|
||||
next_exists = (len(comments) > 25)
|
||||
comments = comments[:25]
|
||||
elif messages:
|
||||
comments = g.db.query(Comment).filter(or_(Comment.author_id==v.id, Comment.sentto==v.id), Comment.parent_submission == None).order_by(Comment.created_utc.desc(), not_(Comment.child_comments.any())).offset(25*(page-1)).limit(26).all()
|
||||
next_exists = (len(comments) > 25)
|
||||
comments = comments[:25]
|
||||
elif posts:
|
||||
notifications = v.notifications.join(Notification.comment).filter(Comment.author_id == AUTOJANNY_ACCOUNT).order_by(Notification.id.desc()).offset(25 * (page - 1)).limit(101).all()
|
||||
|
||||
listing = []
|
||||
|
||||
for index, x in enumerate(notifications[:100]):
|
||||
c = x.comment
|
||||
if x.read and index > 25: break
|
||||
elif not x.read:
|
||||
x.read = True
|
||||
c.unread = True
|
||||
g.db.add(x)
|
||||
listing.append(c)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
next_exists = (len(notifications) > len(listing))
|
||||
|
||||
else:
|
||||
notifications = v.notifications.join(Notification.comment).filter(Comment.author_id != AUTOJANNY_ACCOUNT).order_by(Notification.id.desc()).offset(25 * (page - 1)).limit(101).all()
|
||||
|
||||
listing = []
|
||||
|
||||
for index, x in enumerate(notifications[:100]):
|
||||
c = x.comment
|
||||
if x.read and index > 25: break
|
||||
elif not x.read:
|
||||
x.read = True
|
||||
g.db.add(x)
|
||||
listing.append(c.id)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
comments = get_comments(listing, v=v, load_parent=True)
|
||||
next_exists = (len(notifications) > len(comments))
|
||||
|
||||
if not posts:
|
||||
listing = []
|
||||
for c in comments:
|
||||
c.is_blocked = False
|
||||
c.is_blocking = False
|
||||
if c.parent_submission and c.parent_comment and c.parent_comment.author_id == v.id:
|
||||
c.replies = []
|
||||
while c.parent_comment and c.parent_comment.author_id == v.id:
|
||||
parent = c.parent_comment
|
||||
if c not in parent.replies2:
|
||||
parent.replies2 = parent.replies2 + [c]
|
||||
parent.replies = parent.replies2
|
||||
c = parent
|
||||
if c not in listing:
|
||||
listing.append(c)
|
||||
c.replies = c.replies2
|
||||
elif c.parent_submission:
|
||||
c.replies = []
|
||||
if c not in listing:
|
||||
listing.append(c)
|
||||
else:
|
||||
if c.parent_comment:
|
||||
while c.level > 1:
|
||||
c = c.parent_comment
|
||||
|
||||
if c not in listing:
|
||||
listing.append(c)
|
||||
|
||||
|
||||
return render_template("notifications.html",
|
||||
v=v,
|
||||
notifications=listing,
|
||||
next_exists=next_exists,
|
||||
page=page,
|
||||
standalone=True,
|
||||
render_replies=True
|
||||
)
|
||||
|
||||
|
||||
|
||||
@app.get("/")
|
||||
@app.get("/logged_out")
|
||||
@auth_desired
|
||||
def front_all(v):
|
||||
|
||||
if not v and request.path == "/" and not request.headers.get("Authorization"): return redirect(f"/logged_out{request.full_path}")
|
||||
|
||||
if v and request.path.startswith('/logged_out'): v = None
|
||||
|
||||
try: page = max(int(request.values.get("page", 1)), 1)
|
||||
except: abort(400)
|
||||
|
||||
if v:
|
||||
defaultsorting = v.defaultsorting
|
||||
defaulttime = v.defaulttime
|
||||
else:
|
||||
defaultsorting = "hot"
|
||||
defaulttime = defaulttimefilter
|
||||
|
||||
sort=request.values.get("sort", defaultsorting)
|
||||
t=request.values.get('t', defaulttime)
|
||||
|
||||
ids, next_exists = frontlist(sort=sort,
|
||||
page=page,
|
||||
t=t,
|
||||
v=v,
|
||||
filter_words=v.filter_words if v else [],
|
||||
gt=int(request.values.get("utc_greater_than", 0)),
|
||||
lt=int(request.values.get("utc_less_than", 0)),
|
||||
)
|
||||
|
||||
posts = get_posts(ids, v=v)
|
||||
|
||||
if v and v.hidevotedon: posts = [x for x in posts if not hasattr(x, 'voted') or not x.voted]
|
||||
|
||||
if request.headers.get("Authorization"): return {"data": [x.json for x in posts], "next_exists": next_exists}
|
||||
else: return render_template("home.html", v=v, listing=posts, next_exists=next_exists, sort=sort, t=t, page=page)
|
||||
|
||||
|
||||
|
||||
@cache.memoize(timeout=86400)
|
||||
def frontlist(v=None, sort="hot", page=1, t="all", ids_only=True, filter_words='', gt=None, lt=None):
|
||||
|
||||
posts = g.db.query(Submission.id).options(lazyload('*'))
|
||||
|
||||
if 'rama' in request.host and sort == "hot":
|
||||
cutoff = int(time.time()) - 86400
|
||||
posts = posts.filter(Submission.created_utc >= cutoff)
|
||||
elif t != 'all':
|
||||
now = int(time.time())
|
||||
if t == 'hour': cutoff = now - 3600
|
||||
elif t == 'week': cutoff = now - 604800
|
||||
elif t == 'month': cutoff = now - 2592000
|
||||
elif t == 'year': cutoff = now - 31536000
|
||||
else: cutoff = now - 86400
|
||||
posts = posts.filter(Submission.created_utc >= cutoff)
|
||||
|
||||
posts = posts.filter_by(is_banned=False, stickied=None, private=False, deleted_utc = 0)
|
||||
|
||||
if v and v.admin_level == 0:
|
||||
blocking = [x[0] for x in g.db.query(
|
||||
UserBlock.target_id).filter_by(
|
||||
user_id=v.id).all()]
|
||||
blocked = [x[0] for x in g.db.query(
|
||||
UserBlock.user_id).filter_by(
|
||||
target_id=v.id).all()]
|
||||
posts = posts.filter(
|
||||
Submission.author_id.notin_(blocking),
|
||||
Submission.author_id.notin_(blocked)
|
||||
)
|
||||
|
||||
if not (v and v.changelogsub):
|
||||
posts=posts.filter(not_(Submission.title.ilike(f'[changelog]%')))
|
||||
|
||||
if v and filter_words:
|
||||
for word in filter_words:
|
||||
posts=posts.filter(not_(Submission.title.ilike(f'%{word}%')))
|
||||
|
||||
if gt: posts = posts.filter(Submission.created_utc > gt)
|
||||
if lt: posts = posts.filter(Submission.created_utc < lt)
|
||||
|
||||
if not (v and v.shadowbanned):
|
||||
shadowbanned = [x[0] for x in g.db.query(User.id).options(lazyload('*')).filter(User.shadowbanned != None).all()]
|
||||
posts = posts.filter(Submission.author_id.notin_(shadowbanned))
|
||||
|
||||
if sort == "hot":
|
||||
ti = int(time.time()) + 3600
|
||||
posts = posts.order_by(-1000000*(Submission.upvotes + Submission.downvotes + 1 + Submission.comment_count/5)/(func.power(((ti - Submission.created_utc)/1000), 1.35)))
|
||||
elif sort == "new":
|
||||
posts = posts.order_by(Submission.created_utc.desc())
|
||||
elif sort == "old":
|
||||
posts = posts.order_by(Submission.created_utc.asc())
|
||||
elif sort == "controversial":
|
||||
posts = posts.order_by(-1 * Submission.upvotes * Submission.downvotes * Submission.downvotes)
|
||||
elif sort == "top":
|
||||
posts = posts.order_by(Submission.downvotes - Submission.upvotes)
|
||||
elif sort == "bottom":
|
||||
posts = posts.order_by(Submission.upvotes - Submission.downvotes)
|
||||
elif sort == "comments":
|
||||
posts = posts.order_by(Submission.comment_count.desc())
|
||||
|
||||
if v:
|
||||
if v.agendaposter: size = 5
|
||||
else: size = v.frontsize
|
||||
else: size = 25
|
||||
|
||||
posts = posts.offset(size * (page - 1)).limit(size+1).all()
|
||||
|
||||
next_exists = (len(posts) > size)
|
||||
|
||||
posts = posts[:size]
|
||||
|
||||
pins = g.db.query(Submission.id).options(lazyload('*')).filter(Submission.stickied != None)
|
||||
if v and v.admin_level == 0:
|
||||
blocking = [x[0] for x in g.db.query(UserBlock.target_id).filter_by(user_id=v.id).all()]
|
||||
blocked = [x[0] for x in g.db.query(UserBlock.user_id).filter_by(target_id=v.id).all()]
|
||||
pins = pins.filter(Submission.author_id.notin_(blocking), Submission.author_id.notin_(blocked))
|
||||
|
||||
if page == 1 and not gt and not lt: posts = pins.all() + posts
|
||||
|
||||
if ids_only: posts = [x[0] for x in posts]
|
||||
|
||||
return posts, next_exists
|
||||
|
||||
|
||||
@app.get("/changelog")
|
||||
@auth_desired
|
||||
def changelog(v):
|
||||
|
||||
|
||||
page = int(request.values.get("page") or 1)
|
||||
page = max(page, 1)
|
||||
|
||||
sort=request.values.get("sort", "new")
|
||||
t=request.values.get('t', "all")
|
||||
|
||||
ids = changeloglist(sort=sort,
|
||||
page=page,
|
||||
t=t,
|
||||
v=v,
|
||||
)
|
||||
|
||||
next_exists = (len(ids) > 25)
|
||||
ids = ids[:25]
|
||||
|
||||
posts = get_posts(ids, v=v)
|
||||
|
||||
if request.headers.get("Authorization"): return {"data": [x.json for x in posts], "next_exists": next_exists}
|
||||
else: return render_template("changelog.html", v=v, listing=posts, next_exists=next_exists, sort=sort, t=t, page=page)
|
||||
|
||||
|
||||
@cache.memoize(timeout=86400)
|
||||
def changeloglist(v=None, sort="new", page=1 ,t="all"):
|
||||
|
||||
posts = g.db.query(Submission.id).options(lazyload('*')).filter_by(is_banned=False, private=False,).filter(Submission.deleted_utc == 0)
|
||||
|
||||
if v and v.admin_level == 0:
|
||||
blocking = [x[0] for x in g.db.query(
|
||||
UserBlock.target_id).filter_by(
|
||||
user_id=v.id).all()]
|
||||
blocked = [x[0] for x in g.db.query(
|
||||
UserBlock.user_id).filter_by(
|
||||
target_id=v.id).all()]
|
||||
posts = posts.filter(
|
||||
Submission.author_id.notin_(blocking),
|
||||
Submission.author_id.notin_(blocked)
|
||||
)
|
||||
|
||||
admins = [x[0] for x in g.db.query(User.id).options(lazyload('*')).filter(User.admin_level == 6).all()]
|
||||
posts = posts.filter(Submission.title.ilike('_changelog%'), Submission.author_id.in_(admins))
|
||||
|
||||
if t != 'all':
|
||||
cutoff = 0
|
||||
now = int(time.time())
|
||||
if t == 'hour':
|
||||
cutoff = now - 3600
|
||||
elif t == 'day':
|
||||
cutoff = now - 86400
|
||||
elif t == 'week':
|
||||
cutoff = now - 604800
|
||||
elif t == 'month':
|
||||
cutoff = now - 2592000
|
||||
elif t == 'year':
|
||||
cutoff = now - 31536000
|
||||
posts = posts.filter(Submission.created_utc >= cutoff)
|
||||
|
||||
if sort == "new":
|
||||
posts = posts.order_by(Submission.created_utc.desc())
|
||||
elif sort == "old":
|
||||
posts = posts.order_by(Submission.created_utc.asc())
|
||||
elif sort == "controversial":
|
||||
posts = posts.order_by(-1 * Submission.upvotes * Submission.downvotes * Submission.downvotes)
|
||||
elif sort == "top":
|
||||
posts = posts.order_by(Submission.downvotes - Submission.upvotes)
|
||||
elif sort == "bottom":
|
||||
posts = posts.order_by(Submission.upvotes - Submission.downvotes)
|
||||
elif sort == "comments":
|
||||
posts = posts.order_by(Submission.comment_count.desc())
|
||||
|
||||
posts = posts.offset(25 * (page - 1)).limit(26).all()
|
||||
|
||||
return [x[0] for x in posts]
|
||||
|
||||
|
||||
@app.get("/random")
|
||||
@auth_desired
|
||||
def random_post(v):
|
||||
|
||||
x = g.db.query(Submission).options(lazyload('*')).filter(Submission.deleted_utc == 0, Submission.is_banned == False)
|
||||
total = x.count()
|
||||
n = random.randint(1, total - 2)
|
||||
|
||||
post = x.offset(n).limit(1).first()
|
||||
return redirect(f"/post/{post.id}")
|
||||
|
||||
@cache.memoize(timeout=86400)
|
||||
def comment_idlist(page=1, v=None, nsfw=False, sort="new", t="all"):
|
||||
|
||||
posts = g.db.query(Submission).options(lazyload('*'))
|
||||
cc_idlist = [x[0] for x in g.db.query(Submission.id).options(lazyload('*')).filter(Submission.club == True).all()]
|
||||
|
||||
posts = posts.subquery()
|
||||
|
||||
comments = g.db.query(Comment.id).options(lazyload('*')).filter(Comment.parent_submission.notin_(cc_idlist))
|
||||
|
||||
if v and v.admin_level <= 3:
|
||||
blocking = [x[0] for x in g.db.query(
|
||||
UserBlock.target_id).filter_by(
|
||||
user_id=v.id).all()]
|
||||
blocked = [x[0] for x in g.db.query(
|
||||
UserBlock.user_id).filter_by(
|
||||
target_id=v.id).all()]
|
||||
|
||||
comments = comments.filter(
|
||||
Comment.author_id.notin_(blocking),
|
||||
Comment.author_id.notin_(blocked)
|
||||
)
|
||||
|
||||
if not v or not v.admin_level >= 3:
|
||||
comments = comments.filter_by(is_banned=False).filter(Comment.deleted_utc == 0)
|
||||
|
||||
now = int(time.time())
|
||||
if t == 'hour':
|
||||
cutoff = now - 3600
|
||||
elif t == 'day':
|
||||
cutoff = now - 86400
|
||||
elif t == 'week':
|
||||
cutoff = now - 604800
|
||||
elif t == 'month':
|
||||
cutoff = now - 2592000
|
||||
elif t == 'year':
|
||||
cutoff = now - 31536000
|
||||
else:
|
||||
cutoff = 0
|
||||
comments = comments.filter(Comment.created_utc >= cutoff)
|
||||
|
||||
if sort == "new":
|
||||
comments = comments.order_by(Comment.created_utc.desc())
|
||||
elif sort == "old":
|
||||
comments = comments.order_by(Comment.created_utc.asc())
|
||||
elif sort == "controversial":
|
||||
comments = comments.order_by(-1 * Comment.upvotes * Comment.downvotes * Comment.downvotes)
|
||||
elif sort == "top":
|
||||
comments = comments.order_by(Comment.downvotes - Comment.upvotes)
|
||||
elif sort == "bottom":
|
||||
comments = comments.order_by(Comment.upvotes - Comment.downvotes)
|
||||
|
||||
comments = comments.offset(25 * (page - 1)).limit(26).all()
|
||||
return [x[0] for x in comments]
|
||||
|
||||
@app.get("/comments")
|
||||
@auth_desired
|
||||
def all_comments(v):
|
||||
|
||||
|
||||
page = int(request.values.get("page", 1))
|
||||
|
||||
sort=request.values.get("sort", "new")
|
||||
t=request.values.get("t", defaulttimefilter)
|
||||
|
||||
idlist = comment_idlist(v=v,
|
||||
page=page,
|
||||
sort=sort,
|
||||
t=t,
|
||||
)
|
||||
|
||||
comments = get_comments(idlist, v=v)
|
||||
|
||||
next_exists = len(idlist) > 25
|
||||
|
||||
idlist = idlist[:25]
|
||||
|
||||
if request.headers.get("Authorization"): return {"data": [x.json for x in comments]}
|
||||
else: return render_template("home_comments.html", v=v, sort=sort, t=t, page=page, comments=comments, standalone=True, next_exists=next_exists)
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
from flask import *
|
||||
from os import environ
|
||||
import requests
|
||||
|
||||
from files.__main__ import app
|
||||
|
||||
GIPHY_KEY = environ.get('GIPHY_KEY').rstrip()
|
||||
|
||||
|
||||
@app.get("/giphy")
|
||||
@app.get("/giphy<path>")
|
||||
def giphy(path=None):
|
||||
|
||||
searchTerm = request.values.get("searchTerm", "")
|
||||
limit = int(request.values.get("limit", 48))
|
||||
if searchTerm and limit:
|
||||
url = f"https://api.giphy.com/v1/gifs/search?q={searchTerm}&api_key={GIPHY_KEY}&limit={limit}"
|
||||
elif searchTerm and not limit:
|
||||
url = f"https://api.giphy.com/v1/gifs/search?q={searchTerm}&api_key={GIPHY_KEY}&limit=48"
|
||||
else:
|
||||
url = f"https://api.giphy.com/v1/gifs?api_key={GIPHY_KEY}&limit=48"
|
||||
return jsonify(requests.get(url).json())
|
||||
from flask import *
|
||||
from os import environ
|
||||
import requests
|
||||
|
||||
from files.__main__ import app
|
||||
|
||||
GIPHY_KEY = environ.get('GIPHY_KEY').rstrip()
|
||||
|
||||
|
||||
@app.get("/giphy")
|
||||
@app.get("/giphy<path>")
|
||||
def giphy(path=None):
|
||||
|
||||
searchTerm = request.values.get("searchTerm", "").strip()
|
||||
limit = int(request.values.get("limit", 48))
|
||||
if searchTerm and limit:
|
||||
url = f"https://api.giphy.com/v1/gifs/search?q={searchTerm}&api_key={GIPHY_KEY}&limit={limit}"
|
||||
elif searchTerm and not limit:
|
||||
url = f"https://api.giphy.com/v1/gifs/search?q={searchTerm}&api_key={GIPHY_KEY}&limit=48"
|
||||
else:
|
||||
url = f"https://api.giphy.com/v1/gifs?api_key={GIPHY_KEY}&limit=48"
|
||||
return jsonify(requests.get(url).json())
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,248 +1,248 @@
|
|||
from files.helpers.wrappers import *
|
||||
from files.helpers.alerts import *
|
||||
from files.helpers.get import *
|
||||
from files.helpers.const import *
|
||||
from files.classes import *
|
||||
from flask import *
|
||||
from files.__main__ import app, limiter
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
@app.get("/authorize")
|
||||
@auth_required
|
||||
def authorize_prompt(v):
|
||||
client_id = request.values.get("client_id")
|
||||
application = g.db.query(OauthApp).options(lazyload('*')).filter_by(client_id=client_id).first()
|
||||
if not application: return {"oauth_error": "Invalid `client_id`"}, 401
|
||||
return render_template("oauth.html", v=v, application=application)
|
||||
|
||||
|
||||
@app.post("/authorize")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
@validate_formkey
|
||||
def authorize(v):
|
||||
|
||||
client_id = request.values.get("client_id")
|
||||
application = g.db.query(OauthApp).options(lazyload('*')).filter_by(client_id=client_id).first()
|
||||
if not application: return {"oauth_error": "Invalid `client_id`"}, 401
|
||||
access_token = secrets.token_urlsafe(128)[:128]
|
||||
new_auth = ClientAuth(
|
||||
oauth_client = application.id,
|
||||
user_id = v.id,
|
||||
access_token=access_token
|
||||
)
|
||||
|
||||
g.db.add(new_auth)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return redirect(f"{application.redirect_uri}?token={access_token}")
|
||||
|
||||
|
||||
@app.post("/api_keys")
|
||||
@limiter.limit("1/second")
|
||||
@is_not_banned
|
||||
def request_api_keys(v):
|
||||
|
||||
new_app = OauthApp(
|
||||
app_name=request.values.get('name'),
|
||||
redirect_uri=request.values.get('redirect_uri'),
|
||||
author_id=v.id,
|
||||
description=request.values.get("description")[:256]
|
||||
)
|
||||
|
||||
g.db.add(new_app)
|
||||
|
||||
send_admin(NOTIFICATIONS_ACCOUNT, f"{v.username} has requested API keys for `{request.values.get('name')}`. You can approve or deny the request [here](/admin/apps).")
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return redirect('/settings/apps')
|
||||
|
||||
|
||||
@app.post("/delete_app/<aid>")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
@validate_formkey
|
||||
def delete_oauth_app(v, aid):
|
||||
|
||||
aid = int(aid)
|
||||
app = g.db.query(OauthApp).options(lazyload('*')).filter_by(id=aid).first()
|
||||
|
||||
for auth in g.db.query(ClientAuth).options(lazyload('*')).filter_by(oauth_client=app.id).all():
|
||||
g.db.delete(auth)
|
||||
|
||||
g.db.delete(app)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return redirect('/apps')
|
||||
|
||||
|
||||
@app.post("/edit_app/<aid>")
|
||||
@limiter.limit("1/second")
|
||||
@is_not_banned
|
||||
@validate_formkey
|
||||
def edit_oauth_app(v, aid):
|
||||
|
||||
aid = int(aid)
|
||||
app = g.db.query(OauthApp).options(lazyload('*')).filter_by(id=aid).first()
|
||||
|
||||
app.redirect_uri = request.values.get('redirect_uri')
|
||||
app.app_name = request.values.get('name')
|
||||
app.description = request.values.get("description")[:256]
|
||||
|
||||
g.db.add(app)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return redirect('/settings/apps')
|
||||
|
||||
|
||||
@app.post("/admin/app/approve/<aid>")
|
||||
@limiter.limit("1/second")
|
||||
@admin_level_required(3)
|
||||
@validate_formkey
|
||||
def admin_app_approve(v, aid):
|
||||
|
||||
app = g.db.query(OauthApp).options(lazyload('*')).filter_by(id=aid).first()
|
||||
user = app.author
|
||||
|
||||
app.client_id = secrets.token_urlsafe(64)[:64]
|
||||
g.db.add(app)
|
||||
|
||||
access_token = secrets.token_urlsafe(128)[:128]
|
||||
new_auth = ClientAuth(
|
||||
oauth_client = app.id,
|
||||
user_id = user.id,
|
||||
access_token=access_token
|
||||
)
|
||||
|
||||
g.db.add(new_auth)
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, user, f"Your application `{app.app_name}` has been approved. Here's your access token: `{access_token}`\nPlease check the guide [here](/api) if you don't know what to do next.")
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return {"message": f"{app.app_name} approved"}
|
||||
|
||||
|
||||
@app.post("/admin/app/revoke/<aid>")
|
||||
@limiter.limit("1/second")
|
||||
@admin_level_required(3)
|
||||
@validate_formkey
|
||||
def admin_app_revoke(v, aid):
|
||||
|
||||
app = g.db.query(OauthApp).options(lazyload('*')).filter_by(id=aid).first()
|
||||
if app.id:
|
||||
for auth in g.db.query(ClientAuth).options(lazyload('*')).filter_by(oauth_client=app.id).all(): g.db.delete(auth)
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, app.author, f"Your application `{app.app_name}` has been revoked.")
|
||||
|
||||
g.db.delete(app)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return {"message": f"App revoked"}
|
||||
|
||||
|
||||
@app.post("/admin/app/reject/<aid>")
|
||||
@limiter.limit("1/second")
|
||||
@admin_level_required(3)
|
||||
@validate_formkey
|
||||
def admin_app_reject(v, aid):
|
||||
|
||||
app = g.db.query(OauthApp).options(lazyload('*')).filter_by(id=aid).first()
|
||||
|
||||
for auth in g.db.query(ClientAuth).options(lazyload('*')).filter_by(oauth_client=app.id).all(): g.db.delete(auth)
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, app.author, f"Your application `{app.app_name}` has been rejected.")
|
||||
|
||||
g.db.delete(app)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return {"message": f"App rejected"}
|
||||
|
||||
|
||||
@app.get("/admin/app/<aid>")
|
||||
@admin_level_required(3)
|
||||
def admin_app_id(v, aid):
|
||||
|
||||
aid=aid
|
||||
|
||||
oauth = g.db.query(OauthApp).options(
|
||||
joinedload(
|
||||
OauthApp.author)).filter_by(
|
||||
id=aid).first()
|
||||
|
||||
pids=oauth.idlist(page=int(request.values.get("page",1)),
|
||||
)
|
||||
|
||||
next_exists=len(pids)==101
|
||||
pids=pids[:100]
|
||||
|
||||
posts=get_posts(pids, v=v)
|
||||
|
||||
return render_template("admin/app.html",
|
||||
v=v,
|
||||
app=oauth,
|
||||
listing=posts,
|
||||
next_exists=next_exists
|
||||
)
|
||||
|
||||
@app.get("/admin/app/<aid>/comments")
|
||||
@admin_level_required(3)
|
||||
def admin_app_id_comments(v, aid):
|
||||
|
||||
aid=aid
|
||||
|
||||
oauth = g.db.query(OauthApp).options(
|
||||
joinedload(
|
||||
OauthApp.author)).filter_by(
|
||||
id=aid).first()
|
||||
|
||||
cids=oauth.comments_idlist(page=int(request.values.get("page",1)),
|
||||
)
|
||||
|
||||
next_exists=len(cids)==101
|
||||
cids=cids[:100]
|
||||
|
||||
comments=get_comments(cids, v=v)
|
||||
|
||||
|
||||
return render_template("admin/app.html",
|
||||
v=v,
|
||||
app=oauth,
|
||||
comments=comments,
|
||||
next_exists=next_exists,
|
||||
standalone=True
|
||||
)
|
||||
|
||||
|
||||
@app.get("/admin/apps")
|
||||
@admin_level_required(3)
|
||||
def admin_apps_list(v):
|
||||
|
||||
apps = g.db.query(OauthApp).all()
|
||||
|
||||
return render_template("admin/apps.html", v=v, apps=apps)
|
||||
|
||||
|
||||
@app.post("/oauth/reroll/<aid>")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
def reroll_oauth_tokens(aid, v):
|
||||
|
||||
aid = aid
|
||||
|
||||
a = g.db.query(OauthApp).options(lazyload('*')).filter_by(id=aid).first()
|
||||
|
||||
if a.author_id != v.id: abort(403)
|
||||
|
||||
a.client_id = secrets.token_urlsafe(64)[:64]
|
||||
g.db.add(a)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return {"message": "Client ID Rerolled", "id": a.client_id}
|
||||
from files.helpers.wrappers import *
|
||||
from files.helpers.alerts import *
|
||||
from files.helpers.get import *
|
||||
from files.helpers.const import *
|
||||
from files.classes import *
|
||||
from flask import *
|
||||
from files.__main__ import app, limiter
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
@app.get("/authorize")
|
||||
@auth_required
|
||||
def authorize_prompt(v):
|
||||
client_id = request.values.get("client_id")
|
||||
application = g.db.query(OauthApp).options(lazyload('*')).filter_by(client_id=client_id).first()
|
||||
if not application: return {"oauth_error": "Invalid `client_id`"}, 401
|
||||
return render_template("oauth.html", v=v, application=application)
|
||||
|
||||
|
||||
@app.post("/authorize")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
@validate_formkey
|
||||
def authorize(v):
|
||||
|
||||
client_id = request.values.get("client_id")
|
||||
application = g.db.query(OauthApp).options(lazyload('*')).filter_by(client_id=client_id).first()
|
||||
if not application: return {"oauth_error": "Invalid `client_id`"}, 401
|
||||
access_token = secrets.token_urlsafe(128)[:128]
|
||||
new_auth = ClientAuth(oauth_client = application.id, user_id = v.id, access_token=access_token)
|
||||
|
||||
g.db.add(new_auth)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return redirect(f"{application.redirect_uri}?token={access_token}")
|
||||
|
||||
|
||||
@app.post("/api_keys")
|
||||
@limiter.limit("1/second")
|
||||
@is_not_banned
|
||||
def request_api_keys(v):
|
||||
|
||||
new_app = OauthApp(
|
||||
app_name=request.values.get('name'),
|
||||
redirect_uri=request.values.get('redirect_uri'),
|
||||
author_id=v.id,
|
||||
description=request.values.get("description")[:256]
|
||||
)
|
||||
|
||||
g.db.add(new_app)
|
||||
|
||||
send_admin(NOTIFICATIONS_ACCOUNT, f"{v.username} has requested API keys for `{request.values.get('name')}`. You can approve or deny the request [here](/admin/apps).")
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return redirect('/settings/apps')
|
||||
|
||||
|
||||
@app.post("/delete_app/<aid>")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
@validate_formkey
|
||||
def delete_oauth_app(v, aid):
|
||||
|
||||
aid = int(aid)
|
||||
app = g.db.query(OauthApp).options(lazyload('*')).filter_by(id=aid).first()
|
||||
|
||||
if app.author_id != v.id: abort(403)
|
||||
|
||||
for auth in g.db.query(ClientAuth).options(lazyload('*')).filter_by(oauth_client=app.id).all():
|
||||
g.db.delete(auth)
|
||||
|
||||
g.db.delete(app)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return redirect('/apps')
|
||||
|
||||
|
||||
@app.post("/edit_app/<aid>")
|
||||
@limiter.limit("1/second")
|
||||
@is_not_banned
|
||||
@validate_formkey
|
||||
def edit_oauth_app(v, aid):
|
||||
|
||||
aid = int(aid)
|
||||
app = g.db.query(OauthApp).options(lazyload('*')).filter_by(id=aid).first()
|
||||
|
||||
if app.author_id != v.id: abort(403)
|
||||
|
||||
app.redirect_uri = request.values.get('redirect_uri')
|
||||
app.app_name = request.values.get('name')
|
||||
app.description = request.values.get("description")[:256]
|
||||
|
||||
g.db.add(app)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return redirect('/settings/apps')
|
||||
|
||||
|
||||
@app.post("/admin/app/approve/<aid>")
|
||||
@limiter.limit("1/second")
|
||||
@admin_level_required(3)
|
||||
@validate_formkey
|
||||
def admin_app_approve(v, aid):
|
||||
|
||||
app = g.db.query(OauthApp).options(lazyload('*')).filter_by(id=aid).first()
|
||||
user = app.author
|
||||
|
||||
app.client_id = secrets.token_urlsafe(64)[:64]
|
||||
g.db.add(app)
|
||||
|
||||
access_token = secrets.token_urlsafe(128)[:128]
|
||||
new_auth = ClientAuth(
|
||||
oauth_client = app.id,
|
||||
user_id = user.id,
|
||||
access_token=access_token
|
||||
)
|
||||
|
||||
g.db.add(new_auth)
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, user, f"Your application `{app.app_name}` has been approved. Here's your access token: `{access_token}`\nPlease check the guide [here](/api) if you don't know what to do next.")
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return {"message": f"{app.app_name} approved"}
|
||||
|
||||
|
||||
@app.post("/admin/app/revoke/<aid>")
|
||||
@limiter.limit("1/second")
|
||||
@admin_level_required(3)
|
||||
@validate_formkey
|
||||
def admin_app_revoke(v, aid):
|
||||
|
||||
app = g.db.query(OauthApp).options(lazyload('*')).filter_by(id=aid).first()
|
||||
if app.id:
|
||||
for auth in g.db.query(ClientAuth).options(lazyload('*')).filter_by(oauth_client=app.id).all(): g.db.delete(auth)
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, app.author, f"Your application `{app.app_name}` has been revoked.")
|
||||
|
||||
g.db.delete(app)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return {"message": f"App revoked"}
|
||||
|
||||
|
||||
@app.post("/admin/app/reject/<aid>")
|
||||
@limiter.limit("1/second")
|
||||
@admin_level_required(3)
|
||||
@validate_formkey
|
||||
def admin_app_reject(v, aid):
|
||||
|
||||
app = g.db.query(OauthApp).options(lazyload('*')).filter_by(id=aid).first()
|
||||
|
||||
for auth in g.db.query(ClientAuth).options(lazyload('*')).filter_by(oauth_client=app.id).all(): g.db.delete(auth)
|
||||
|
||||
send_notification(NOTIFICATIONS_ACCOUNT, app.author, f"Your application `{app.app_name}` has been rejected.")
|
||||
|
||||
g.db.delete(app)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return {"message": f"App rejected"}
|
||||
|
||||
|
||||
@app.get("/admin/app/<aid>")
|
||||
@admin_level_required(3)
|
||||
def admin_app_id(v, aid):
|
||||
|
||||
aid=aid
|
||||
|
||||
oauth = g.db.query(OauthApp).options(
|
||||
joinedload(
|
||||
OauthApp.author)).filter_by(
|
||||
id=aid).first()
|
||||
|
||||
pids=oauth.idlist(page=int(request.values.get("page",1)),
|
||||
)
|
||||
|
||||
next_exists=len(pids)==101
|
||||
pids=pids[:100]
|
||||
|
||||
posts=get_posts(pids, v=v)
|
||||
|
||||
return render_template("admin/app.html",
|
||||
v=v,
|
||||
app=oauth,
|
||||
listing=posts,
|
||||
next_exists=next_exists
|
||||
)
|
||||
|
||||
@app.get("/admin/app/<aid>/comments")
|
||||
@admin_level_required(3)
|
||||
def admin_app_id_comments(v, aid):
|
||||
|
||||
aid=aid
|
||||
|
||||
oauth = g.db.query(OauthApp).options(
|
||||
joinedload(
|
||||
OauthApp.author)).filter_by(
|
||||
id=aid).first()
|
||||
|
||||
cids=oauth.comments_idlist(page=int(request.values.get("page",1)),
|
||||
)
|
||||
|
||||
next_exists=len(cids)==101
|
||||
cids=cids[:100]
|
||||
|
||||
comments=get_comments(cids, v=v)
|
||||
|
||||
|
||||
return render_template("admin/app.html",
|
||||
v=v,
|
||||
app=oauth,
|
||||
comments=comments,
|
||||
next_exists=next_exists,
|
||||
standalone=True
|
||||
)
|
||||
|
||||
|
||||
@app.get("/admin/apps")
|
||||
@admin_level_required(3)
|
||||
def admin_apps_list(v):
|
||||
|
||||
apps = g.db.query(OauthApp).all()
|
||||
|
||||
return render_template("admin/apps.html", v=v, apps=apps)
|
||||
|
||||
|
||||
@app.post("/oauth/reroll/<aid>")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
def reroll_oauth_tokens(aid, v):
|
||||
|
||||
aid = aid
|
||||
|
||||
a = g.db.query(OauthApp).options(lazyload('*')).filter_by(id=aid).first()
|
||||
|
||||
if a.author_id != v.id: abort(403)
|
||||
|
||||
a.client_id = secrets.token_urlsafe(64)[:64]
|
||||
g.db.add(a)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return {"message": "Client ID Rerolled", "id": a.client_id}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,90 +1,90 @@
|
|||
from files.helpers.wrappers import *
|
||||
from files.helpers.get import *
|
||||
from flask import g
|
||||
from files.__main__ import app, limiter
|
||||
from os import path
|
||||
|
||||
@app.post("/flag/post/<pid>")
|
||||
@limiter.limit("1/second")
|
||||
@auth_desired
|
||||
def api_flag_post(pid, v):
|
||||
|
||||
post = get_post(pid)
|
||||
|
||||
if v and not v.shadowbanned:
|
||||
existing = g.db.query(Flag).options(lazyload('*')).filter_by(user_id=v.id, post_id=post.id).first()
|
||||
|
||||
if existing: return "", 409
|
||||
|
||||
reason = request.values.get("reason", "").strip()[:100]
|
||||
if "<" in reason: return {"error": f"Reasons can't contain <"}
|
||||
|
||||
for i in re.finditer(':(.{1,30}?):', reason):
|
||||
if path.isfile(f'./files/assets/images/emojis/{i.group(1)}.webp'):
|
||||
reason = reason.replace(f':{i.group(1)}:', f'<img loading="lazy" data-bs-toggle="tooltip" title="{i.group(1)}" delay="0" height=20 src="https://{site}/assets/images/emojis/{i.group(1)}.webp">')
|
||||
|
||||
flag = Flag(post_id=post.id,
|
||||
user_id=v.id,
|
||||
reason=reason,
|
||||
)
|
||||
|
||||
|
||||
g.db.add(flag)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return {"message": "Post reported!"}
|
||||
|
||||
|
||||
@app.post("/flag/comment/<cid>")
|
||||
@limiter.limit("1/second")
|
||||
@auth_desired
|
||||
def api_flag_comment(cid, v):
|
||||
|
||||
comment = get_comment(cid)
|
||||
|
||||
if v and not v.shadowbanned:
|
||||
existing = g.db.query(CommentFlag).options(lazyload('*')).filter_by(
|
||||
user_id=v.id, comment_id=comment.id).first()
|
||||
|
||||
if existing: return "", 409
|
||||
reason = request.values.get("reason", "").strip()[:100]
|
||||
if "<" in reason: return {"error": f"Reasons can't contain <"}
|
||||
|
||||
for i in re.finditer(':(.{1,30}?):', reason):
|
||||
if path.isfile(f'./files/assets/images/emojis/{i.group(1)}.webp'):
|
||||
reason = reason.replace(f':{i.group(1)}:', f'<img loading="lazy" data-bs-toggle="tooltip" title="{i.group(1)}" delay="0" height=20 src="https://{site}/assets/images/emojis/{i.group(1)}.webp">')
|
||||
|
||||
flag = CommentFlag(comment_id=comment.id,
|
||||
user_id=v.id,
|
||||
reason=reason,
|
||||
)
|
||||
|
||||
g.db.add(flag)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return {"message": "Comment reported!"}
|
||||
|
||||
|
||||
@app.post('/del_report/<report_fn>')
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
@validate_formkey
|
||||
def remove_report(report_fn, v):
|
||||
|
||||
if v.admin_level < 6:
|
||||
return {"error": "go outside"}, 403
|
||||
|
||||
if report_fn.startswith('c'):
|
||||
report = g.db.query(CommentFlag).options(lazyload('*')).filter_by(id=int(report_fn.lstrip('c'))).first()
|
||||
elif report_fn.startswith('p'):
|
||||
report = g.db.query(Flag).options(lazyload('*')).filter_by(id=int(report_fn.lstrip('p'))).first()
|
||||
else:
|
||||
return {"error": "Invalid report ID"}, 400
|
||||
|
||||
g.db.delete(report)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
from files.helpers.wrappers import *
|
||||
from files.helpers.get import *
|
||||
from flask import g
|
||||
from files.__main__ import app, limiter
|
||||
from os import path
|
||||
|
||||
@app.post("/flag/post/<pid>")
|
||||
@limiter.limit("1/second")
|
||||
@auth_desired
|
||||
def api_flag_post(pid, v):
|
||||
|
||||
post = get_post(pid)
|
||||
|
||||
if v and not v.shadowbanned:
|
||||
existing = g.db.query(Flag).options(lazyload('*')).filter_by(user_id=v.id, post_id=post.id).first()
|
||||
|
||||
if existing: return "", 409
|
||||
|
||||
reason = request.values.get("reason", "").strip()[:100]
|
||||
if "<" in reason: return {"error": f"Reasons can't contain <"}
|
||||
|
||||
for i in re.finditer(':(.{1,30}?):', reason):
|
||||
if path.isfile(f'./files/assets/images/emojis/{i.group(1)}.webp'):
|
||||
reason = reason.replace(f':{i.group(1)}:', f'<img loading="lazy" data-bs-toggle="tooltip" title="{i.group(1)}" delay="0" height=20 src="https://{site}/assets/images/emojis/{i.group(1)}.webp">')
|
||||
|
||||
flag = Flag(post_id=post.id,
|
||||
user_id=v.id,
|
||||
reason=reason,
|
||||
)
|
||||
|
||||
|
||||
g.db.add(flag)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return {"message": "Post reported!"}
|
||||
|
||||
|
||||
@app.post("/flag/comment/<cid>")
|
||||
@limiter.limit("1/second")
|
||||
@auth_desired
|
||||
def api_flag_comment(cid, v):
|
||||
|
||||
comment = get_comment(cid)
|
||||
|
||||
if v and not v.shadowbanned:
|
||||
existing = g.db.query(CommentFlag).options(lazyload('*')).filter_by(
|
||||
user_id=v.id, comment_id=comment.id).first()
|
||||
|
||||
if existing: return "", 409
|
||||
reason = request.values.get("reason", "").strip()[:100]
|
||||
if "<" in reason: return {"error": f"Reasons can't contain <"}
|
||||
|
||||
for i in re.finditer(':(.{1,30}?):', reason):
|
||||
if path.isfile(f'./files/assets/images/emojis/{i.group(1)}.webp'):
|
||||
reason = reason.replace(f':{i.group(1)}:', f'<img loading="lazy" data-bs-toggle="tooltip" title="{i.group(1)}" delay="0" height=20 src="https://{site}/assets/images/emojis/{i.group(1)}.webp">')
|
||||
|
||||
flag = CommentFlag(comment_id=comment.id,
|
||||
user_id=v.id,
|
||||
reason=reason,
|
||||
)
|
||||
|
||||
g.db.add(flag)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return {"message": "Comment reported!"}
|
||||
|
||||
|
||||
@app.post('/del_report/<report_fn>')
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
@validate_formkey
|
||||
def remove_report(report_fn, v):
|
||||
|
||||
if v.admin_level < 6:
|
||||
return {"error": "go outside"}, 403
|
||||
|
||||
if report_fn.startswith('c'):
|
||||
report = g.db.query(CommentFlag).options(lazyload('*')).filter_by(id=int(report_fn.lstrip('c'))).first()
|
||||
elif report_fn.startswith('p'):
|
||||
report = g.db.query(Flag).options(lazyload('*')).filter_by(id=int(report_fn.lstrip('p'))).first()
|
||||
else:
|
||||
return {"error": "Invalid report ID"}, 400
|
||||
|
||||
g.db.delete(report)
|
||||
|
||||
g.db.commit()
|
||||
|
||||
return {"message": "Removed report"}
|
|
@ -1,283 +1,287 @@
|
|||
from files.helpers.wrappers import *
|
||||
import re
|
||||
from sqlalchemy import *
|
||||
from flask import *
|
||||
from files.__main__ import app
|
||||
|
||||
|
||||
query_regex=re.compile("(\w+):(\S+)")
|
||||
valid_params=[
|
||||
'author',
|
||||
'domain',
|
||||
'over18'
|
||||
]
|
||||
|
||||
def searchparse(text):
|
||||
|
||||
|
||||
criteria = {x[0]:x[1] for x in query_regex.findall(text)}
|
||||
|
||||
for x in criteria:
|
||||
if x in valid_params:
|
||||
text = text.replace(f"{x}:{criteria[x]}", "")
|
||||
|
||||
text=text.strip()
|
||||
|
||||
if text:
|
||||
criteria['q']=text
|
||||
|
||||
return criteria
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@app.get("/search/posts")
|
||||
@auth_desired
|
||||
def searchposts(v):
|
||||
|
||||
|
||||
query = request.values.get("q", '').strip()
|
||||
|
||||
page = max(1, int(request.values.get("page", 1)))
|
||||
|
||||
sort = request.values.get("sort", "top").lower()
|
||||
t = request.values.get('t', 'all').lower()
|
||||
|
||||
criteria=searchparse(query)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
posts = g.db.query(Submission.id).options(lazyload('*'))
|
||||
|
||||
if not (v and v.admin_level == 6): posts = posts.filter(Submission.private == False)
|
||||
|
||||
if 'q' in criteria:
|
||||
words=criteria['q'].split()
|
||||
words=[Submission.title.ilike('%'+x+'%') for x in words]
|
||||
words=tuple(words)
|
||||
posts=posts.filter(*words)
|
||||
|
||||
if 'over18' in criteria: posts = posts.filter(Submission.over_18==True)
|
||||
|
||||
if 'author' in criteria: posts = posts.filter(Submission.author_id == get_user(criteria['author']).id)
|
||||
|
||||
if 'domain' in criteria:
|
||||
domain=criteria['domain']
|
||||
posts=posts.filter(
|
||||
or_(
|
||||
Submission.url.ilike("https://"+domain+'/%'),
|
||||
Submission.url.ilike("https://"+domain+'/%'),
|
||||
Submission.url.ilike("https://"+domain),
|
||||
Submission.url.ilike("https://"+domain),
|
||||
Submission.url.ilike("https://www."+domain+'/%'),
|
||||
Submission.url.ilike("https://www."+domain+'/%'),
|
||||
Submission.url.ilike("https://www."+domain),
|
||||
Submission.url.ilike("https://www."+domain),
|
||||
Submission.url.ilike("https://old." + domain + '/%'),
|
||||
Submission.url.ilike("https://old." + domain + '/%'),
|
||||
Submission.url.ilike("https://old." + domain),
|
||||
Submission.url.ilike("https://old." + domain)
|
||||
)
|
||||
)
|
||||
|
||||
if not(v and v.admin_level >= 3):
|
||||
posts = posts.filter(
|
||||
Submission.deleted_utc == 0,
|
||||
Submission.is_banned == False,
|
||||
)
|
||||
|
||||
if v and v.admin_level >= 4:
|
||||
pass
|
||||
elif v:
|
||||
blocking = [x[0] for x in g.db.query(
|
||||
UserBlock.target_id).filter_by(
|
||||
user_id=v.id).all()]
|
||||
blocked = [x[0] for x in g.db.query(
|
||||
UserBlock.user_id).filter_by(
|
||||
target_id=v.id).all()]
|
||||
|
||||
posts = posts.filter(
|
||||
Submission.author_id.notin_(blocking),
|
||||
Submission.author_id.notin_(blocked),
|
||||
)
|
||||
|
||||
if t:
|
||||
now = int(time.time())
|
||||
if t == 'hour':
|
||||
cutoff = now - 3600
|
||||
elif t == 'day':
|
||||
cutoff = now - 86400
|
||||
elif t == 'week':
|
||||
cutoff = now - 604800
|
||||
elif t == 'month':
|
||||
cutoff = now - 2592000
|
||||
elif t == 'year':
|
||||
cutoff = now - 31536000
|
||||
else:
|
||||
cutoff = 0
|
||||
posts = posts.filter(Submission.created_utc >= cutoff)
|
||||
|
||||
if sort == "new":
|
||||
posts = posts.order_by(Submission.created_utc.desc())
|
||||
elif sort == "old":
|
||||
posts = posts.order_by(Submission.created_utc.asc())
|
||||
elif sort == "controversial":
|
||||
posts = posts.order_by(-1 * Submission.upvotes * (Submission.downvotes+1))
|
||||
elif sort == "top":
|
||||
posts = posts.order_by(Submission.downvotes - Submission.upvotes)
|
||||
elif sort == "bottom":
|
||||
posts = posts.order_by(Submission.upvotes - Submission.downvotes)
|
||||
elif sort == "comments":
|
||||
posts = posts.order_by(Submission.comment_count.desc())
|
||||
|
||||
total = posts.count()
|
||||
|
||||
posts = posts.offset(25 * (page - 1)).limit(26).all()
|
||||
|
||||
ids = [x[0] for x in posts]
|
||||
|
||||
|
||||
|
||||
|
||||
next_exists = (len(ids) > 25)
|
||||
ids = ids[:25]
|
||||
|
||||
posts = get_posts(ids, v=v)
|
||||
|
||||
if v and v.admin_level>3 and "domain" in criteria:
|
||||
domain=criteria['domain']
|
||||
domain_obj=get_domain(domain)
|
||||
else:
|
||||
domain=None
|
||||
domain_obj=None
|
||||
|
||||
if request.headers.get("Authorization"): return {"data":[x.json for x in posts]}
|
||||
else: return render_template("search.html",
|
||||
v=v,
|
||||
query=query,
|
||||
total=total,
|
||||
page=page,
|
||||
listing=posts,
|
||||
sort=sort,
|
||||
t=t,
|
||||
next_exists=next_exists,
|
||||
domain=domain,
|
||||
domain_obj=domain_obj
|
||||
)
|
||||
|
||||
@app.get("/search/comments")
|
||||
@auth_desired
|
||||
def searchcomments(v):
|
||||
|
||||
|
||||
query = request.values.get("q", '').strip()
|
||||
|
||||
try: page = max(1, int(request.values.get("page", 1)))
|
||||
except: page = 1
|
||||
|
||||
sort = request.values.get("sort", "top").lower()
|
||||
t = request.values.get('t', 'all').lower()
|
||||
|
||||
criteria=searchparse(query)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
comments = g.db.query(Comment.id).options(lazyload('*')).filter(Comment.parent_submission != None)
|
||||
|
||||
if 'q' in criteria:
|
||||
words=criteria['q'].split()
|
||||
words=[Comment.body.ilike('%'+x+'%') for x in words]
|
||||
words=tuple(words)
|
||||
comments=comments.filter(*words)
|
||||
|
||||
if not(v and v.admin_level >= 3):
|
||||
comments = comments.filter(
|
||||
Comment.deleted_utc == 0,
|
||||
Comment.is_banned == False)
|
||||
|
||||
if t:
|
||||
now = int(time.time())
|
||||
if t == 'hour':
|
||||
cutoff = now - 3600
|
||||
elif t == 'day':
|
||||
cutoff = now - 86400
|
||||
elif t == 'week':
|
||||
cutoff = now - 604800
|
||||
elif t == 'month':
|
||||
cutoff = now - 2592000
|
||||
elif t == 'year':
|
||||
cutoff = now - 31536000
|
||||
else:
|
||||
cutoff = 0
|
||||
comments = comments.filter(Comment.created_utc >= cutoff)
|
||||
|
||||
|
||||
|
||||
if sort == "new":
|
||||
comments = comments.order_by(Comment.created_utc.desc())
|
||||
elif sort == "old":
|
||||
comments = comments.order_by(Comment.created_utc.asc())
|
||||
elif sort == "controversial":
|
||||
comments = comments.order_by(-1 * Comment.upvotes * (Comment.downvotes+1))
|
||||
elif sort == "top":
|
||||
comments = comments.order_by(Comment.downvotes - Comment.upvotes)
|
||||
elif sort == "bottom":
|
||||
comments = comments.order_by(Comment.upvotes - Comment.downvotes)
|
||||
|
||||
total = comments.count()
|
||||
|
||||
comments = comments.offset(25 * (page - 1)).limit(26).all()
|
||||
ids = [x[0] for x in comments]
|
||||
|
||||
|
||||
|
||||
|
||||
next_exists = (len(ids) > 25)
|
||||
ids = ids[:25]
|
||||
|
||||
comments = get_comments(ids, v=v)
|
||||
|
||||
if request.headers.get("Authorization"): return [x.json for x in comments]
|
||||
else: return render_template("search_comments.html", v=v, query=query, total=total, page=page, comments=comments, sort=sort, t=t, next_exists=next_exists)
|
||||
|
||||
|
||||
@app.get("/search/users")
|
||||
@auth_desired
|
||||
def searchusers(v):
|
||||
|
||||
|
||||
query = request.values.get("q", '').strip()
|
||||
|
||||
page = max(1, int(request.values.get("page", 1)))
|
||||
sort = request.values.get("sort", "top").lower()
|
||||
t = request.values.get('t', 'all').lower()
|
||||
term=query.lstrip('@')
|
||||
term=term.replace('\\','')
|
||||
term=term.replace('_','\_')
|
||||
|
||||
users=g.db.query(User).options(lazyload('*')).filter(User.username.ilike(f'%{term}%'))
|
||||
|
||||
users=users.order_by(User.username.ilike(term).desc(), User.stored_subscriber_count.desc())
|
||||
|
||||
total=users.count()
|
||||
|
||||
users=[x for x in users.offset(25 * (page-1)).limit(26)]
|
||||
next_exists=(len(users)==26)
|
||||
users=users[:25]
|
||||
|
||||
|
||||
if request.headers.get("Authorization"): return [x.json for x in users]
|
||||
from files.helpers.wrappers import *
|
||||
import re
|
||||
from sqlalchemy import *
|
||||
from flask import *
|
||||
from files.__main__ import app
|
||||
|
||||
|
||||
query_regex=re.compile("(\w+):(\S+)")
|
||||
valid_params=[
|
||||
'author',
|
||||
'domain',
|
||||
'over18'
|
||||
]
|
||||
|
||||
def searchparse(text):
|
||||
|
||||
|
||||
criteria = {x[0]:x[1] for x in query_regex.findall(text)}
|
||||
|
||||
for x in criteria:
|
||||
if x in valid_params:
|
||||
text = text.replace(f"{x}:{criteria[x]}", "")
|
||||
|
||||
text=text.strip()
|
||||
|
||||
if text:
|
||||
criteria['q']=text
|
||||
|
||||
return criteria
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@app.get("/search/posts")
|
||||
@auth_desired
|
||||
def searchposts(v):
|
||||
|
||||
|
||||
query = request.values.get("q", '').strip()
|
||||
|
||||
page = max(1, int(request.values.get("page", 1)))
|
||||
|
||||
sort = request.values.get("sort", "new").lower()
|
||||
t = request.values.get('t', 'all').lower()
|
||||
|
||||
criteria=searchparse(query)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
posts = g.db.query(Submission.id).options(lazyload('*'))
|
||||
|
||||
if not (v and v.admin_level == 6): posts = posts.filter(Submission.private == False)
|
||||
|
||||
if 'q' in criteria:
|
||||
words=criteria['q'].split()
|
||||
words=[Submission.title.ilike('%'+x+'%') for x in words]
|
||||
words=tuple(words)
|
||||
posts=posts.filter(*words)
|
||||
|
||||
if 'over18' in criteria: posts = posts.filter(Submission.over_18==True)
|
||||
|
||||
if 'author' in criteria: posts = posts.filter(Submission.author_id == get_user(criteria['author']).id)
|
||||
|
||||
if 'domain' in criteria:
|
||||
domain=criteria['domain']
|
||||
posts=posts.filter(
|
||||
or_(
|
||||
Submission.url.ilike("https://"+domain+'/%'),
|
||||
Submission.url.ilike("https://"+domain+'/%'),
|
||||
Submission.url.ilike("https://"+domain),
|
||||
Submission.url.ilike("https://"+domain),
|
||||
Submission.url.ilike("https://www."+domain+'/%'),
|
||||
Submission.url.ilike("https://www."+domain+'/%'),
|
||||
Submission.url.ilike("https://www."+domain),
|
||||
Submission.url.ilike("https://www."+domain),
|
||||
Submission.url.ilike("https://old." + domain + '/%'),
|
||||
Submission.url.ilike("https://old." + domain + '/%'),
|
||||
Submission.url.ilike("https://old." + domain),
|
||||
Submission.url.ilike("https://old." + domain)
|
||||
)
|
||||
)
|
||||
|
||||
if not(v and v.admin_level >= 3):
|
||||
posts = posts.filter(
|
||||
Submission.deleted_utc == 0,
|
||||
Submission.is_banned == False,
|
||||
)
|
||||
|
||||
if v and v.admin_level >= 4:
|
||||
pass
|
||||
elif v:
|
||||
blocking = [x[0] for x in g.db.query(
|
||||
UserBlock.target_id).filter_by(
|
||||
user_id=v.id).all()]
|
||||
blocked = [x[0] for x in g.db.query(
|
||||
UserBlock.user_id).filter_by(
|
||||
target_id=v.id).all()]
|
||||
|
||||
posts = posts.filter(
|
||||
Submission.author_id.notin_(blocking),
|
||||
Submission.author_id.notin_(blocked),
|
||||
)
|
||||
|
||||
if t:
|
||||
now = int(time.time())
|
||||
if t == 'hour':
|
||||
cutoff = now - 3600
|
||||
elif t == 'day':
|
||||
cutoff = now - 86400
|
||||
elif t == 'week':
|
||||
cutoff = now - 604800
|
||||
elif t == 'month':
|
||||
cutoff = now - 2592000
|
||||
elif t == 'year':
|
||||
cutoff = now - 31536000
|
||||
else:
|
||||
cutoff = 0
|
||||
posts = posts.filter(Submission.created_utc >= cutoff)
|
||||
|
||||
if sort == "new":
|
||||
posts = posts.order_by(Submission.created_utc.desc())
|
||||
elif sort == "old":
|
||||
posts = posts.order_by(Submission.created_utc.asc())
|
||||
elif sort == "controversial":
|
||||
posts = posts.order_by(-1 * Submission.upvotes * Submission.downvotes * Submission.downvotes)
|
||||
elif sort == "top":
|
||||
posts = posts.order_by(Submission.downvotes - Submission.upvotes)
|
||||
elif sort == "bottom":
|
||||
posts = posts.order_by(Submission.upvotes - Submission.downvotes)
|
||||
elif sort == "comments":
|
||||
posts = posts.order_by(Submission.comment_count.desc())
|
||||
|
||||
total = posts.count()
|
||||
|
||||
posts = posts.offset(25 * (page - 1)).limit(26).all()
|
||||
|
||||
ids = [x[0] for x in posts]
|
||||
|
||||
|
||||
|
||||
|
||||
next_exists = (len(ids) > 25)
|
||||
ids = ids[:25]
|
||||
|
||||
posts = get_posts(ids, v=v)
|
||||
|
||||
if v and v.admin_level>3 and "domain" in criteria:
|
||||
domain=criteria['domain']
|
||||
domain_obj=get_domain(domain)
|
||||
else:
|
||||
domain=None
|
||||
domain_obj=None
|
||||
|
||||
if request.headers.get("Authorization"): return {"data":[x.json for x in posts]}
|
||||
else: return render_template("search.html",
|
||||
v=v,
|
||||
query=query,
|
||||
total=total,
|
||||
page=page,
|
||||
listing=posts,
|
||||
sort=sort,
|
||||
t=t,
|
||||
next_exists=next_exists,
|
||||
domain=domain,
|
||||
domain_obj=domain_obj
|
||||
)
|
||||
|
||||
@app.get("/search/comments")
|
||||
@auth_desired
|
||||
def searchcomments(v):
|
||||
|
||||
|
||||
query = request.values.get("q", '').strip()
|
||||
|
||||
try: page = max(1, int(request.values.get("page", 1)))
|
||||
except: page = 1
|
||||
|
||||
sort = request.values.get("sort", "new").lower()
|
||||
t = request.values.get('t', 'all').lower()
|
||||
|
||||
criteria=searchparse(query)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
comments = g.db.query(Comment.id).options(lazyload('*')).filter(Comment.parent_submission != None)
|
||||
|
||||
if 'q' in criteria:
|
||||
words=criteria['q'].split()
|
||||
words=[Comment.body.ilike('%'+x+'%') for x in words]
|
||||
words=tuple(words)
|
||||
comments=comments.filter(*words)
|
||||
|
||||
if 'over18' in criteria: comments = comments.filter(Comment.over_18==True)
|
||||
|
||||
if 'author' in criteria: comments = comments.filter(Comment.author_id == get_user(criteria['author']).id)
|
||||
|
||||
if not(v and v.admin_level >= 3):
|
||||
comments = comments.filter(
|
||||
Comment.deleted_utc == 0,
|
||||
Comment.is_banned == False)
|
||||
|
||||
if t:
|
||||
now = int(time.time())
|
||||
if t == 'hour':
|
||||
cutoff = now - 3600
|
||||
elif t == 'day':
|
||||
cutoff = now - 86400
|
||||
elif t == 'week':
|
||||
cutoff = now - 604800
|
||||
elif t == 'month':
|
||||
cutoff = now - 2592000
|
||||
elif t == 'year':
|
||||
cutoff = now - 31536000
|
||||
else:
|
||||
cutoff = 0
|
||||
comments = comments.filter(Comment.created_utc >= cutoff)
|
||||
|
||||
|
||||
|
||||
if sort == "new":
|
||||
comments = comments.order_by(Comment.created_utc.desc())
|
||||
elif sort == "old":
|
||||
comments = comments.order_by(Comment.created_utc.asc())
|
||||
elif sort == "controversial":
|
||||
comments = comments.order_by(-1 * Comment.upvotes * Comment.downvotes * Comment.downvotes)
|
||||
elif sort == "top":
|
||||
comments = comments.order_by(Comment.downvotes - Comment.upvotes)
|
||||
elif sort == "bottom":
|
||||
comments = comments.order_by(Comment.upvotes - Comment.downvotes)
|
||||
|
||||
total = comments.count()
|
||||
|
||||
comments = comments.offset(25 * (page - 1)).limit(26).all()
|
||||
ids = [x[0] for x in comments]
|
||||
|
||||
|
||||
|
||||
|
||||
next_exists = (len(ids) > 25)
|
||||
ids = ids[:25]
|
||||
|
||||
comments = get_comments(ids, v=v)
|
||||
|
||||
if request.headers.get("Authorization"): return [x.json for x in comments]
|
||||
else: return render_template("search_comments.html", v=v, query=query, total=total, page=page, comments=comments, sort=sort, t=t, next_exists=next_exists)
|
||||
|
||||
|
||||
@app.get("/search/users")
|
||||
@auth_desired
|
||||
def searchusers(v):
|
||||
|
||||
|
||||
query = request.values.get("q", '').strip()
|
||||
|
||||
page = max(1, int(request.values.get("page", 1)))
|
||||
sort = request.values.get("sort", "new").lower()
|
||||
t = request.values.get('t', 'all').lower()
|
||||
term=query.lstrip('@')
|
||||
term=term.replace('\\','')
|
||||
term=term.replace('_','\_')
|
||||
|
||||
users=g.db.query(User).options(lazyload('*')).filter(User.username.ilike(f'%{term}%'))
|
||||
|
||||
users=users.order_by(User.username.ilike(term).desc(), User.stored_subscriber_count.desc())
|
||||
|
||||
total=users.count()
|
||||
|
||||
users=[x for x in users.offset(25 * (page-1)).limit(26)]
|
||||
next_exists=(len(users)>25)
|
||||
users=users[:25]
|
||||
|
||||
|
||||
if request.headers.get("Authorization"): return [x.json for x in users]
|
||||
else: return render_template("search_users.html", v=v, query=query, total=total, page=page, users=users, sort=sort, t=t, next_exists=next_exists)
|
File diff suppressed because it is too large
Load Diff
|
@ -1,341 +1,341 @@
|
|||
from files.mail import *
|
||||
from files.__main__ import app, limiter, mail
|
||||
from files.helpers.alerts import *
|
||||
from files.classes.award import AWARDS
|
||||
from sqlalchemy import func
|
||||
from os import path
|
||||
import calendar
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
site = environ.get("DOMAIN").strip()
|
||||
site_name = environ.get("SITE_NAME").strip()
|
||||
|
||||
@app.get('/rules')
|
||||
@auth_desired
|
||||
def static_rules(v):
|
||||
|
||||
if not path.exists(f'./{site_name} rules.html'):
|
||||
if v and v.admin_level == 6:
|
||||
return render_template('norules.html', v=v)
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
with open(f'./{site_name} rules.html', 'r') as f:
|
||||
rules = f.read()
|
||||
|
||||
return render_template('rules.html', rules=rules, v=v)
|
||||
|
||||
|
||||
@app.get("/stats")
|
||||
@auth_required
|
||||
def participation_stats(v):
|
||||
|
||||
now = int(time.time())
|
||||
|
||||
day = now - 86400
|
||||
|
||||
data = {"valid_users": g.db.query(User.id).count(),
|
||||
"private_users": g.db.query(User.id).options(lazyload('*')).filter_by(is_private=True).count(),
|
||||
"banned_users": g.db.query(User.id).options(lazyload('*')).filter(User.is_banned > 0).count(),
|
||||
"verified_email_users": g.db.query(User.id).options(lazyload('*')).filter_by(is_activated=True).count(),
|
||||
"total_coins": g.db.query(func.sum(User.coins)).scalar(),
|
||||
"signups_last_24h": g.db.query(User.id).options(lazyload('*')).filter(User.created_utc > day).count(),
|
||||
"total_posts": g.db.query(Submission.id).count(),
|
||||
"posting_users": g.db.query(Submission.author_id).distinct().count(),
|
||||
"listed_posts": g.db.query(Submission.id).options(lazyload('*')).filter_by(is_banned=False).filter(Submission.deleted_utc == 0).count(),
|
||||
"removed_posts": g.db.query(Submission.id).options(lazyload('*')).filter_by(is_banned=True).count(),
|
||||
"deleted_posts": g.db.query(Submission.id).options(lazyload('*')).filter(Submission.deleted_utc > 0).count(),
|
||||
"posts_last_24h": g.db.query(Submission.id).options(lazyload('*')).filter(Submission.created_utc > day).count(),
|
||||
"total_comments": g.db.query(Comment.id).count(),
|
||||
"commenting_users": g.db.query(Comment.author_id).distinct().count(),
|
||||
"removed_comments": g.db.query(Comment.id).options(lazyload('*')).filter_by(is_banned=True).count(),
|
||||
"deleted_comments": g.db.query(Comment.id).options(lazyload('*')).filter(Comment.deleted_utc>0).count(),
|
||||
"comments_last_24h": g.db.query(Comment.id).options(lazyload('*')).filter(Comment.created_utc > day).count(),
|
||||
"post_votes": g.db.query(Vote.id).count(),
|
||||
"post_voting_users": g.db.query(Vote.user_id).distinct().count(),
|
||||
"comment_votes": g.db.query(CommentVote.id).count(),
|
||||
"comment_voting_users": g.db.query(CommentVote.user_id).distinct().count(),
|
||||
"total_awards": g.db.query(AwardRelationship.id).count(),
|
||||
"awards_given": g.db.query(AwardRelationship.id).options(lazyload('*')).filter(or_(AwardRelationship.submission_id != None, AwardRelationship.comment_id != None)).count()
|
||||
}
|
||||
|
||||
|
||||
return render_template("admin/content_stats.html", v=v, title="Content Statistics", data=data)
|
||||
|
||||
|
||||
@app.get("/chart")
|
||||
@auth_required
|
||||
def chart(v):
|
||||
file = cached_chart()
|
||||
return send_file(f"../{file}")
|
||||
|
||||
|
||||
@cache.memoize(timeout=86400)
|
||||
def cached_chart():
|
||||
days = int(request.values.get("days", 25))
|
||||
|
||||
now = time.gmtime()
|
||||
midnight_this_morning = time.struct_time((now.tm_year,
|
||||
now.tm_mon,
|
||||
now.tm_mday,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
now.tm_wday,
|
||||
now.tm_yday,
|
||||
0)
|
||||
)
|
||||
today_cutoff = calendar.timegm(midnight_this_morning)
|
||||
|
||||
day = 3600 * 24
|
||||
|
||||
day_cutoffs = [today_cutoff - day * i for i in range(days)]
|
||||
day_cutoffs.insert(0, calendar.timegm(now))
|
||||
|
||||
daily_times = [time.strftime("%d", time.gmtime(day_cutoffs[i + 1])) for i in range(len(day_cutoffs) - 1)][2:][::-1]
|
||||
|
||||
daily_signups = [g.db.query(User.id).options(lazyload('*')).filter(User.created_utc < day_cutoffs[i], User.created_utc > day_cutoffs[i + 1]).count() for i in range(len(day_cutoffs) - 1)][2:][::-1]
|
||||
|
||||
post_stats = [g.db.query(Submission.id).options(lazyload('*')).filter(Submission.created_utc < day_cutoffs[i], Submission.created_utc > day_cutoffs[i + 1], Submission.is_banned == False).count() for i in range(len(day_cutoffs) - 1)][2:][::-1]
|
||||
|
||||
comment_stats = [g.db.query(Comment.id).options(lazyload('*')).filter(Comment.created_utc < day_cutoffs[i], Comment.created_utc > day_cutoffs[i + 1],Comment.is_banned == False, Comment.author_id != 1).count() for i in range(len(day_cutoffs) - 1)][2:][::-1]
|
||||
|
||||
signup_chart = plt.subplot2grid((20, 4), (0, 0), rowspan=5, colspan=4)
|
||||
posts_chart = plt.subplot2grid((20, 4), (7, 0), rowspan=5, colspan=4)
|
||||
comments_chart = plt.subplot2grid((20, 4), (14, 0), rowspan=5, colspan=4)
|
||||
|
||||
signup_chart.grid(), posts_chart.grid(), comments_chart.grid()
|
||||
|
||||
signup_chart.plot(
|
||||
daily_times,
|
||||
daily_signups,
|
||||
color='red')
|
||||
posts_chart.plot(
|
||||
daily_times,
|
||||
post_stats,
|
||||
color='green')
|
||||
comments_chart.plot(
|
||||
daily_times,
|
||||
comment_stats,
|
||||
color='gold')
|
||||
|
||||
signup_chart.set_ylabel("Signups")
|
||||
posts_chart.set_ylabel("Posts")
|
||||
comments_chart.set_ylabel("Comments")
|
||||
comments_chart.set_xlabel("Time (UTC)")
|
||||
|
||||
signup_chart.legend(loc='upper left', frameon=True)
|
||||
posts_chart.legend(loc='upper left', frameon=True)
|
||||
comments_chart.legend(loc='upper left', frameon=True)
|
||||
|
||||
file = "chart.png"
|
||||
plt.savefig(file)
|
||||
plt.clf()
|
||||
return file
|
||||
|
||||
|
||||
@app.get("/patrons")
|
||||
@app.get("/paypigs")
|
||||
@auth_desired
|
||||
def patrons(v):
|
||||
query = g.db.query(
|
||||
User.id, User.username, User.patron, User.namecolor,
|
||||
AwardRelationship.kind.label('last_award_kind'), func.count(AwardRelationship.id).label('last_award_count')
|
||||
).filter(AwardRelationship.submission_id==None, AwardRelationship.comment_id==None, User.patron > 0) \
|
||||
.group_by(User.username, User.patron, User.id, User.namecolor, AwardRelationship.kind) \
|
||||
.order_by(User.patron.desc(), AwardRelationship.kind.desc()) \
|
||||
.join(User).all()
|
||||
|
||||
result = {}
|
||||
for row in (r._asdict() for r in query):
|
||||
user_id = row['id']
|
||||
if user_id not in result:
|
||||
result[user_id] = row
|
||||
result[user_id]['awards'] = {}
|
||||
|
||||
kind = row['last_award_kind']
|
||||
if kind in AWARDS.keys():
|
||||
result[user_id]['awards'][kind] = (AWARDS[kind], row['last_award_count'])
|
||||
|
||||
return render_template("patrons.html", v=v, result=result)
|
||||
|
||||
@app.get("/admins")
|
||||
@auth_desired
|
||||
def admins(v):
|
||||
admins = g.db.query(User).options(lazyload('*')).filter_by(admin_level=6).order_by(User.coins.desc()).all()
|
||||
return render_template("admins.html", v=v, admins=admins)
|
||||
|
||||
|
||||
@app.get("/log")
|
||||
@auth_desired
|
||||
def log(v):
|
||||
|
||||
page=int(request.args.get("page",1))
|
||||
|
||||
if v and v.admin_level == 6: actions = g.db.query(ModAction).order_by(ModAction.id.desc()).offset(25 * (page - 1)).limit(26).all()
|
||||
else: actions=g.db.query(ModAction).filter(ModAction.kind!="shadowban", ModAction.kind!="unshadowban", ModAction.kind!="club", ModAction.kind!="unclub").order_by(ModAction.id.desc()).offset(25*(page-1)).limit(26).all()
|
||||
|
||||
next_exists=len(actions)==26
|
||||
actions=actions[:25]
|
||||
|
||||
return render_template("log.html", v=v, actions=actions, next_exists=next_exists, page=page)
|
||||
|
||||
@app.get("/log/<id>")
|
||||
@auth_desired
|
||||
def log_item(id, v):
|
||||
|
||||
try: id = int(id)
|
||||
except:
|
||||
try: id = int(id, 36)
|
||||
except: abort(404)
|
||||
|
||||
action=g.db.query(ModAction).options(lazyload('*')).filter_by(id=id).first()
|
||||
|
||||
if not action:
|
||||
abort(404)
|
||||
|
||||
if request.path != action.permalink:
|
||||
return redirect(action.permalink)
|
||||
|
||||
return render_template("log.html",
|
||||
v=v,
|
||||
actions=[action],
|
||||
next_exists=False,
|
||||
page=1,
|
||||
action=action
|
||||
)
|
||||
|
||||
@app.get("/assets/favicon.ico")
|
||||
def favicon():
|
||||
return send_file(f"./assets/images/{site_name}/icon.webp")
|
||||
|
||||
@app.get("/api")
|
||||
@auth_desired
|
||||
def api(v):
|
||||
return render_template("api.html", v=v)
|
||||
|
||||
@app.get("/contact")
|
||||
@auth_required
|
||||
def contact(v):
|
||||
|
||||
return render_template("contact.html", v=v)
|
||||
|
||||
@app.post("/contact")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
def submit_contact(v):
|
||||
message = f'This message has been sent automatically to all admins via https://{site}/contact, user email is "{v.email}"\n\nMessage:\n\n' + request.values.get("message", "")
|
||||
send_admin(v.id, message)
|
||||
g.db.commit()
|
||||
return render_template("contact.html", v=v, msg="Your message has been sent.")
|
||||
|
||||
@app.get('/archives')
|
||||
def archivesindex():
|
||||
return redirect("/archives/index.html")
|
||||
|
||||
@app.get('/archives/<path:path>')
|
||||
def archives(path):
|
||||
resp = make_response(send_from_directory('/archives', path))
|
||||
if request.path.endswith('.css'): resp.headers.add("Content-Type", "text/css")
|
||||
return resp
|
||||
|
||||
@app.get('/assets/<path:path>')
|
||||
@limiter.exempt
|
||||
def static_service(path):
|
||||
resp = make_response(send_from_directory('./assets', path))
|
||||
if request.path.endswith('.webp') or request.path.endswith('.gif') or request.path.endswith('.ttf') or request.path.endswith('.woff') or request.path.endswith('.woff2'):
|
||||
resp.headers.remove("Cache-Control")
|
||||
resp.headers.add("Cache-Control", "public, max-age=2628000")
|
||||
|
||||
return resp
|
||||
|
||||
@app.get('/images/<path:path>')
|
||||
@app.get('/hostedimages/<path:path>')
|
||||
@limiter.exempt
|
||||
def images(path):
|
||||
resp = make_response(send_from_directory('/images', path))
|
||||
resp.headers.remove("Cache-Control")
|
||||
resp.headers.add("Cache-Control", "public, max-age=2628000")
|
||||
return resp
|
||||
|
||||
@app.get("/robots.txt")
|
||||
def robots_txt():
|
||||
return send_file("./assets/robots.txt")
|
||||
|
||||
@app.get("/settings")
|
||||
@auth_required
|
||||
def settings(v):
|
||||
|
||||
|
||||
return redirect("/settings/profile")
|
||||
|
||||
|
||||
@app.get("/settings/profile")
|
||||
@auth_required
|
||||
def settings_profile(v):
|
||||
|
||||
|
||||
return render_template("settings_profile.html",
|
||||
v=v)
|
||||
|
||||
@app.get("/badges")
|
||||
@auth_desired
|
||||
def badges(v):
|
||||
|
||||
|
||||
badges = g.db.query(BadgeDef).all()
|
||||
return render_template("badges.html", v=v, badges=badges)
|
||||
|
||||
@app.get("/blocks")
|
||||
@auth_desired
|
||||
def blocks(v):
|
||||
|
||||
|
||||
blocks=g.db.query(UserBlock).all()
|
||||
users = []
|
||||
targets = []
|
||||
for x in blocks:
|
||||
users.append(get_account(x.user_id))
|
||||
targets.append(get_account(x.target_id))
|
||||
|
||||
return render_template("blocks.html", v=v, users=users, targets=targets)
|
||||
|
||||
@app.get("/banned")
|
||||
@auth_desired
|
||||
def banned(v):
|
||||
|
||||
|
||||
users = [x for x in g.db.query(User).options(lazyload('*')).filter(User.is_banned > 0, User.unban_utc == 0).all()]
|
||||
return render_template("banned.html", v=v, users=users)
|
||||
|
||||
@app.get("/formatting")
|
||||
@auth_desired
|
||||
def formatting(v):
|
||||
|
||||
|
||||
return render_template("formatting.html", v=v)
|
||||
|
||||
@app.get("/service-worker.js")
|
||||
def serviceworker():
|
||||
with open("files/assets/js/service-worker.js", "r") as f: return Response(f.read(), mimetype='application/javascript')
|
||||
|
||||
@app.get("/settings/security")
|
||||
@auth_required
|
||||
def settings_security(v):
|
||||
|
||||
|
||||
return render_template("settings_security.html",
|
||||
v=v,
|
||||
mfa_secret=pyotp.random_base32() if not v.mfa_secret else None,
|
||||
error=request.values.get("error") or None,
|
||||
msg=request.values.get("msg") or None
|
||||
)
|
||||
|
||||
@app.post("/dismiss_mobile_tip")
|
||||
@limiter.limit("1/second")
|
||||
def dismiss_mobile_tip():
|
||||
|
||||
session["tooltip_last_dismissed"]=int(time.time())
|
||||
session.modified=True
|
||||
|
||||
return "", 204
|
||||
from files.mail import *
|
||||
from files.__main__ import app, limiter, mail
|
||||
from files.helpers.alerts import *
|
||||
from files.classes.award import AWARDS
|
||||
from sqlalchemy import func
|
||||
from os import path
|
||||
import calendar
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
site = environ.get("DOMAIN").strip()
|
||||
site_name = environ.get("SITE_NAME").strip()
|
||||
|
||||
@app.get('/rules')
|
||||
@auth_desired
|
||||
def static_rules(v):
|
||||
|
||||
if not path.exists(f'./{site_name} rules.html'):
|
||||
if v and v.admin_level == 6:
|
||||
return render_template('norules.html', v=v)
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
with open(f'./{site_name} rules.html', 'r') as f:
|
||||
rules = f.read()
|
||||
|
||||
return render_template('rules.html', rules=rules, v=v)
|
||||
|
||||
|
||||
@app.get("/stats")
|
||||
@auth_required
|
||||
def participation_stats(v):
|
||||
|
||||
now = int(time.time())
|
||||
|
||||
day = now - 86400
|
||||
|
||||
data = {"valid_users": g.db.query(User.id).count(),
|
||||
"private_users": g.db.query(User.id).options(lazyload('*')).filter_by(is_private=True).count(),
|
||||
"banned_users": g.db.query(User.id).options(lazyload('*')).filter(User.is_banned > 0).count(),
|
||||
"verified_email_users": g.db.query(User.id).options(lazyload('*')).filter_by(is_activated=True).count(),
|
||||
"total_coins": g.db.query(func.sum(User.coins)).scalar(),
|
||||
"signups_last_24h": g.db.query(User.id).options(lazyload('*')).filter(User.created_utc > day).count(),
|
||||
"total_posts": g.db.query(Submission.id).count(),
|
||||
"posting_users": g.db.query(Submission.author_id).distinct().count(),
|
||||
"listed_posts": g.db.query(Submission.id).options(lazyload('*')).filter_by(is_banned=False).filter(Submission.deleted_utc == 0).count(),
|
||||
"removed_posts": g.db.query(Submission.id).options(lazyload('*')).filter_by(is_banned=True).count(),
|
||||
"deleted_posts": g.db.query(Submission.id).options(lazyload('*')).filter(Submission.deleted_utc > 0).count(),
|
||||
"posts_last_24h": g.db.query(Submission.id).options(lazyload('*')).filter(Submission.created_utc > day).count(),
|
||||
"total_comments": g.db.query(Comment.id).count(),
|
||||
"commenting_users": g.db.query(Comment.author_id).distinct().count(),
|
||||
"removed_comments": g.db.query(Comment.id).options(lazyload('*')).filter_by(is_banned=True).count(),
|
||||
"deleted_comments": g.db.query(Comment.id).options(lazyload('*')).filter(Comment.deleted_utc>0).count(),
|
||||
"comments_last_24h": g.db.query(Comment.id).options(lazyload('*')).filter(Comment.created_utc > day).count(),
|
||||
"post_votes": g.db.query(Vote.id).count(),
|
||||
"post_voting_users": g.db.query(Vote.user_id).distinct().count(),
|
||||
"comment_votes": g.db.query(CommentVote.id).count(),
|
||||
"comment_voting_users": g.db.query(CommentVote.user_id).distinct().count(),
|
||||
"total_awards": g.db.query(AwardRelationship.id).count(),
|
||||
"awards_given": g.db.query(AwardRelationship.id).options(lazyload('*')).filter(or_(AwardRelationship.submission_id != None, AwardRelationship.comment_id != None)).count()
|
||||
}
|
||||
|
||||
|
||||
return render_template("admin/content_stats.html", v=v, title="Content Statistics", data=data)
|
||||
|
||||
|
||||
@app.get("/chart")
|
||||
@auth_required
|
||||
def chart(v):
|
||||
file = cached_chart()
|
||||
return send_file(f"../{file}")
|
||||
|
||||
|
||||
@cache.memoize(timeout=86400)
|
||||
def cached_chart():
|
||||
days = int(request.values.get("days", 25))
|
||||
|
||||
now = time.gmtime()
|
||||
midnight_this_morning = time.struct_time((now.tm_year,
|
||||
now.tm_mon,
|
||||
now.tm_mday,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
now.tm_wday,
|
||||
now.tm_yday,
|
||||
0)
|
||||
)
|
||||
today_cutoff = calendar.timegm(midnight_this_morning)
|
||||
|
||||
day = 3600 * 24
|
||||
|
||||
day_cutoffs = [today_cutoff - day * i for i in range(days)]
|
||||
day_cutoffs.insert(0, calendar.timegm(now))
|
||||
|
||||
daily_times = [time.strftime("%d", time.gmtime(day_cutoffs[i + 1])) for i in range(len(day_cutoffs) - 1)][2:][::-1]
|
||||
|
||||
daily_signups = [g.db.query(User.id).options(lazyload('*')).filter(User.created_utc < day_cutoffs[i], User.created_utc > day_cutoffs[i + 1]).count() for i in range(len(day_cutoffs) - 1)][2:][::-1]
|
||||
|
||||
post_stats = [g.db.query(Submission.id).options(lazyload('*')).filter(Submission.created_utc < day_cutoffs[i], Submission.created_utc > day_cutoffs[i + 1], Submission.is_banned == False).count() for i in range(len(day_cutoffs) - 1)][2:][::-1]
|
||||
|
||||
comment_stats = [g.db.query(Comment.id).options(lazyload('*')).filter(Comment.created_utc < day_cutoffs[i], Comment.created_utc > day_cutoffs[i + 1],Comment.is_banned == False, Comment.author_id != 1).count() for i in range(len(day_cutoffs) - 1)][2:][::-1]
|
||||
|
||||
signup_chart = plt.subplot2grid((20, 4), (0, 0), rowspan=5, colspan=4)
|
||||
posts_chart = plt.subplot2grid((20, 4), (7, 0), rowspan=5, colspan=4)
|
||||
comments_chart = plt.subplot2grid((20, 4), (14, 0), rowspan=5, colspan=4)
|
||||
|
||||
signup_chart.grid(), posts_chart.grid(), comments_chart.grid()
|
||||
|
||||
signup_chart.plot(
|
||||
daily_times,
|
||||
daily_signups,
|
||||
color='red')
|
||||
posts_chart.plot(
|
||||
daily_times,
|
||||
post_stats,
|
||||
color='green')
|
||||
comments_chart.plot(
|
||||
daily_times,
|
||||
comment_stats,
|
||||
color='gold')
|
||||
|
||||
signup_chart.set_ylabel("Signups")
|
||||
posts_chart.set_ylabel("Posts")
|
||||
comments_chart.set_ylabel("Comments")
|
||||
comments_chart.set_xlabel("Time (UTC)")
|
||||
|
||||
signup_chart.legend(loc='upper left', frameon=True)
|
||||
posts_chart.legend(loc='upper left', frameon=True)
|
||||
comments_chart.legend(loc='upper left', frameon=True)
|
||||
|
||||
file = "chart.png"
|
||||
plt.savefig(file)
|
||||
plt.clf()
|
||||
return file
|
||||
|
||||
|
||||
@app.get("/patrons")
|
||||
@app.get("/paypigs")
|
||||
@auth_desired
|
||||
def patrons(v):
|
||||
query = g.db.query(
|
||||
User.id, User.username, User.patron, User.namecolor,
|
||||
AwardRelationship.kind.label('last_award_kind'), func.count(AwardRelationship.id).label('last_award_count')
|
||||
).filter(AwardRelationship.submission_id==None, AwardRelationship.comment_id==None, User.patron > 0) \
|
||||
.group_by(User.username, User.patron, User.id, User.namecolor, AwardRelationship.kind) \
|
||||
.order_by(User.patron.desc(), AwardRelationship.kind.desc()) \
|
||||
.join(User).all()
|
||||
|
||||
result = {}
|
||||
for row in (r._asdict() for r in query):
|
||||
user_id = row['id']
|
||||
if user_id not in result:
|
||||
result[user_id] = row
|
||||
result[user_id]['awards'] = {}
|
||||
|
||||
kind = row['last_award_kind']
|
||||
if kind in AWARDS.keys():
|
||||
result[user_id]['awards'][kind] = (AWARDS[kind], row['last_award_count'])
|
||||
|
||||
return render_template("patrons.html", v=v, result=result)
|
||||
|
||||
@app.get("/admins")
|
||||
@auth_desired
|
||||
def admins(v):
|
||||
admins = g.db.query(User).options(lazyload('*')).filter_by(admin_level=6).order_by(User.coins.desc()).all()
|
||||
return render_template("admins.html", v=v, admins=admins)
|
||||
|
||||
|
||||
@app.get("/log")
|
||||
@auth_desired
|
||||
def log(v):
|
||||
|
||||
page=int(request.args.get("page",1))
|
||||
|
||||
if v and v.admin_level == 6: actions = g.db.query(ModAction).order_by(ModAction.id.desc()).offset(25 * (page - 1)).limit(26).all()
|
||||
else: actions=g.db.query(ModAction).filter(ModAction.kind!="shadowban", ModAction.kind!="unshadowban", ModAction.kind!="club", ModAction.kind!="unclub").order_by(ModAction.id.desc()).offset(25*(page-1)).limit(26).all()
|
||||
|
||||
next_exists=len(actions)>25
|
||||
actions=actions[:25]
|
||||
|
||||
return render_template("log.html", v=v, actions=actions, next_exists=next_exists, page=page)
|
||||
|
||||
@app.get("/log/<id>")
|
||||
@auth_desired
|
||||
def log_item(id, v):
|
||||
|
||||
try: id = int(id)
|
||||
except:
|
||||
try: id = int(id, 36)
|
||||
except: abort(404)
|
||||
|
||||
action=g.db.query(ModAction).options(lazyload('*')).filter_by(id=id).first()
|
||||
|
||||
if not action:
|
||||
abort(404)
|
||||
|
||||
if request.path != action.permalink:
|
||||
return redirect(action.permalink)
|
||||
|
||||
return render_template("log.html",
|
||||
v=v,
|
||||
actions=[action],
|
||||
next_exists=False,
|
||||
page=1,
|
||||
action=action
|
||||
)
|
||||
|
||||
@app.get("/assets/favicon.ico")
|
||||
def favicon():
|
||||
return send_file(f"./assets/images/{site_name}/icon.gif")
|
||||
|
||||
@app.get("/api")
|
||||
@auth_desired
|
||||
def api(v):
|
||||
return render_template("api.html", v=v)
|
||||
|
||||
@app.get("/contact")
|
||||
@auth_required
|
||||
def contact(v):
|
||||
|
||||
return render_template("contact.html", v=v)
|
||||
|
||||
@app.post("/contact")
|
||||
@limiter.limit("1/second")
|
||||
@auth_required
|
||||
def submit_contact(v):
|
||||
message = f'This message has been sent automatically to all admins via https://{site}/contact, user email is "{v.email}"\n\nMessage:\n\n' + request.values.get("message", "")
|
||||
send_admin(v.id, message)
|
||||
g.db.commit()
|
||||
return render_template("contact.html", v=v, msg="Your message has been sent.")
|
||||
|
||||
@app.get('/archives')
|
||||
def archivesindex():
|
||||
return redirect("/archives/index.html")
|
||||
|
||||
@app.get('/archives/<path:path>')
|
||||
def archives(path):
|
||||
resp = make_response(send_from_directory('/archives', path))
|
||||
if request.path.endswith('.css'): resp.headers.add("Content-Type", "text/css")
|
||||
return resp
|
||||
|
||||
@app.get('/assets/<path:path>')
|
||||
@limiter.exempt
|
||||
def static_service(path):
|
||||
resp = make_response(send_from_directory('./assets', path))
|
||||
if request.path.endswith('.webp') or request.path.endswith('.gif') or request.path.endswith('.ttf') or request.path.endswith('.woff') or request.path.endswith('.woff2'):
|
||||
resp.headers.remove("Cache-Control")
|
||||
resp.headers.add("Cache-Control", "public, max-age=2628000")
|
||||
|
||||
return resp
|
||||
|
||||
@app.get('/images/<path:path>')
|
||||
@app.get('/hostedimages/<path:path>')
|
||||
@limiter.exempt
|
||||
def images(path):
|
||||
resp = make_response(send_from_directory('/images', path))
|
||||
resp.headers.remove("Cache-Control")
|
||||
resp.headers.add("Cache-Control", "public, max-age=2628000")
|
||||
return resp
|
||||
|
||||
@app.get("/robots.txt")
|
||||
def robots_txt():
|
||||
return send_file("./assets/robots.txt")
|
||||
|
||||
@app.get("/settings")
|
||||
@auth_required
|
||||
def settings(v):
|
||||
|
||||
|
||||
return redirect("/settings/profile")
|
||||
|
||||
|
||||
@app.get("/settings/profile")
|
||||
@auth_required
|
||||
def settings_profile(v):
|
||||
|
||||
|
||||
return render_template("settings_profile.html",
|
||||
v=v)
|
||||
|
||||
@app.get("/badges")
|
||||
@auth_desired
|
||||
def badges(v):
|
||||
|
||||
|
||||
badges = g.db.query(BadgeDef).all()
|
||||
return render_template("badges.html", v=v, badges=badges)
|
||||
|
||||
@app.get("/blocks")
|
||||
@auth_desired
|
||||
def blocks(v):
|
||||
|
||||
|
||||
blocks=g.db.query(UserBlock).all()
|
||||
users = []
|
||||
targets = []
|
||||
for x in blocks:
|
||||
users.append(get_account(x.user_id))
|
||||
targets.append(get_account(x.target_id))
|
||||
|
||||
return render_template("blocks.html", v=v, users=users, targets=targets)
|
||||
|
||||
@app.get("/banned")
|
||||
@auth_desired
|
||||
def banned(v):
|
||||
|
||||
|
||||
users = [x for x in g.db.query(User).options(lazyload('*')).filter(User.is_banned > 0, User.unban_utc == 0).all()]
|
||||
return render_template("banned.html", v=v, users=users)
|
||||
|
||||
@app.get("/formatting")
|
||||
@auth_desired
|
||||
def formatting(v):
|
||||
|
||||
|
||||
return render_template("formatting.html", v=v)
|
||||
|
||||
@app.get("/service-worker.js")
|
||||
def serviceworker():
|
||||
with open("files/assets/js/service-worker.js", "r") as f: return Response(f.read(), mimetype='application/javascript')
|
||||
|
||||
@app.get("/settings/security")
|
||||
@auth_required
|
||||
def settings_security(v):
|
||||
|
||||
|
||||
return render_template("settings_security.html",
|
||||
v=v,
|
||||
mfa_secret=pyotp.random_base32() if not v.mfa_secret else None,
|
||||
error=request.values.get("error") or None,
|
||||
msg=request.values.get("msg") or None
|
||||
)
|
||||
|
||||
@app.post("/dismiss_mobile_tip")
|
||||
@limiter.limit("1/second")
|
||||
def dismiss_mobile_tip():
|
||||
|
||||
session["tooltip_last_dismissed"]=int(time.time())
|
||||
session.modified=True
|
||||
|
||||
return "", 204
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,200 +1,199 @@
|
|||
from files.helpers.wrappers import *
|
||||
from files.helpers.get import *
|
||||
from files.classes import *
|
||||
from flask import *
|
||||
from files.__main__ import app, limiter
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
|
||||
@app.get("/votes")
|
||||
@auth_desired
|
||||
def admin_vote_info_get(v):
|
||||
|
||||
|
||||
link = request.values.get("link")
|
||||
if not link: return render_template("votes.html", v=v)
|
||||
|
||||
try:
|
||||
if "t2_" in link: thing = get_post(int(link.split("t2_")[1]), v=v)
|
||||
elif "t3_" in link: thing = get_comment(int(link.split("t3_")[1]), v=v)
|
||||
else: abort(400)
|
||||
except: abort(400)
|
||||
|
||||
if isinstance(thing, Submission):
|
||||
|
||||
ups = g.db.query(Vote
|
||||
).options(joinedload(Vote.user)
|
||||
).filter_by(submission_id=thing.id, vote_type=1
|
||||
).all()
|
||||
|
||||
downs = g.db.query(Vote
|
||||
).options(joinedload(Vote.user)
|
||||
).filter_by(submission_id=thing.id, vote_type=-1
|
||||
).all()
|
||||
|
||||
elif isinstance(thing, Comment):
|
||||
|
||||
ups = g.db.query(CommentVote
|
||||
).options(joinedload(CommentVote.user)
|
||||
).filter_by(comment_id=thing.id, vote_type=1
|
||||
).all()
|
||||
|
||||
downs = g.db.query(CommentVote
|
||||
).options(joinedload(CommentVote.user)
|
||||
).filter_by(comment_id=thing.id, vote_type=-1
|
||||
).all()
|
||||
|
||||
else:
|
||||
abort(400)
|
||||
|
||||
return render_template("votes.html",
|
||||
v=v,
|
||||
thing=thing,
|
||||
ups=ups,
|
||||
downs=downs,)
|
||||
|
||||
|
||||
|
||||
@app.post("/vote/post/<post_id>/<new>")
|
||||
@auth_required
|
||||
@validate_formkey
|
||||
def api_vote_post(post_id, new, v):
|
||||
|
||||
if new not in ["-1", "0", "1"]: abort(400)
|
||||
|
||||
if request.headers.get("X-User-Type","") == "Bot": abort(403)
|
||||
|
||||
new = int(new)
|
||||
|
||||
post = get_post(post_id)
|
||||
|
||||
existing = g.db.query(Vote).options(lazyload('*')).filter_by(user_id=v.id, submission_id=post.id).first()
|
||||
|
||||
if existing and existing.vote_type == new: return "", 204
|
||||
|
||||
if existing:
|
||||
if existing.vote_type == 0 and new != 0:
|
||||
post.author.coins += 1
|
||||
post.author.truecoins += 1
|
||||
g.db.add(post.author)
|
||||
existing.vote_type = new
|
||||
g.db.add(existing)
|
||||
elif existing.vote_type != 0 and new == 0:
|
||||
post.author.coins -= 1
|
||||
post.author.truecoins -= 1
|
||||
g.db.add(post.author)
|
||||
g.db.delete(existing)
|
||||
else:
|
||||
existing.vote_type = new
|
||||
g.db.add(existing)
|
||||
elif new != 0:
|
||||
post.author.coins += 1
|
||||
post.author.truecoins += 1
|
||||
g.db.add(post.author)
|
||||
vote = Vote(user_id=v.id,
|
||||
vote_type=new,
|
||||
submission_id=post_id,
|
||||
app_id=v.client.application.id if v.client else None
|
||||
)
|
||||
g.db.add(vote)
|
||||
|
||||
try:
|
||||
g.db.flush()
|
||||
post.upvotes = g.db.query(Vote.id).options(lazyload('*')).filter_by(submission_id=post.id, vote_type=1).count()
|
||||
post.downvotes = g.db.query(Vote.id).options(lazyload('*')).filter_by(submission_id=post.id, vote_type=-1).count()
|
||||
g.db.add(post)
|
||||
g.db.commit()
|
||||
except: g.db.rollback()
|
||||
return "", 204
|
||||
|
||||
@app.post("/vote/comment/<comment_id>/<new>")
|
||||
@auth_required
|
||||
@validate_formkey
|
||||
def api_vote_comment(comment_id, new, v):
|
||||
|
||||
if new not in ["-1", "0", "1"]: abort(400)
|
||||
|
||||
if request.headers.get("X-User-Type","") == "Bot": abort(403)
|
||||
|
||||
new = int(new)
|
||||
|
||||
try: comment_id = int(comment_id)
|
||||
except:
|
||||
try: comment_id = int(comment_id, 36)
|
||||
except: abort(404)
|
||||
|
||||
comment = get_comment(comment_id)
|
||||
|
||||
existing = g.db.query(CommentVote).options(lazyload('*')).filter_by(user_id=v.id, comment_id=comment.id).first()
|
||||
|
||||
if existing and existing.vote_type == new: return "", 204
|
||||
|
||||
if existing:
|
||||
if existing.vote_type == 0 and new != 0:
|
||||
comment.author.coins += 1
|
||||
comment.author.truecoins += 1
|
||||
g.db.add(comment.author)
|
||||
existing.vote_type = new
|
||||
g.db.add(existing)
|
||||
elif existing.vote_type != 0 and new == 0:
|
||||
comment.author.coins -= 1
|
||||
comment.author.truecoins -= 1
|
||||
g.db.add(comment.author)
|
||||
g.db.delete(existing)
|
||||
else:
|
||||
existing.vote_type = new
|
||||
g.db.add(existing)
|
||||
elif new != 0:
|
||||
comment.author.coins += 1
|
||||
comment.author.truecoins += 1
|
||||
g.db.add(comment.author)
|
||||
vote = CommentVote(user_id=v.id,
|
||||
vote_type=new,
|
||||
comment_id=comment_id,
|
||||
app_id=v.client.application.id if v.client else None
|
||||
)
|
||||
|
||||
g.db.add(vote)
|
||||
|
||||
try:
|
||||
g.db.flush()
|
||||
comment.upvotes = g.db.query(CommentVote.id).options(lazyload('*')).filter_by(comment_id=comment.id, vote_type=1).count()
|
||||
comment.downvotes = g.db.query(CommentVote.id).options(lazyload('*')).filter_by(comment_id=comment.id, vote_type=-1).count()
|
||||
g.db.add(comment)
|
||||
g.db.commit()
|
||||
except: g.db.rollback()
|
||||
return "", 204
|
||||
|
||||
|
||||
@app.post("/vote/poll/<comment_id>")
|
||||
@auth_required
|
||||
def api_vote_poll(comment_id, v):
|
||||
|
||||
vote = request.values.get("vote")
|
||||
if vote == "true": new = 1
|
||||
elif vote == "false": new = 0
|
||||
else: abort(400)
|
||||
|
||||
comment_id = int(comment_id)
|
||||
comment = get_comment(comment_id)
|
||||
|
||||
existing = g.db.query(CommentVote).options(lazyload('*')).filter_by(user_id=v.id, comment_id=comment.id).first()
|
||||
|
||||
if existing and existing.vote_type == new: return "", 204
|
||||
|
||||
if existing:
|
||||
if new == 1:
|
||||
existing.vote_type = new
|
||||
g.db.add(existing)
|
||||
else: g.db.delete(existing)
|
||||
elif new == 1:
|
||||
vote = CommentVote(user_id=v.id, vote_type=new, comment_id=comment.id)
|
||||
g.db.add(vote)
|
||||
|
||||
try:
|
||||
g.db.flush()
|
||||
comment.upvotes = g.db.query(CommentVote.id).options(lazyload('*')).filter_by(comment_id=comment.id, vote_type=1).count()
|
||||
g.db.add(comment)
|
||||
g.db.commit()
|
||||
except: g.db.rollback()
|
||||
from files.helpers.wrappers import *
|
||||
from files.helpers.get import *
|
||||
from files.classes import *
|
||||
from flask import *
|
||||
from files.__main__ import app, limiter
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
|
||||
@app.get("/votes")
|
||||
@auth_desired
|
||||
def admin_vote_info_get(v):
|
||||
|
||||
|
||||
link = request.values.get("link")
|
||||
if not link: return render_template("votes.html", v=v)
|
||||
|
||||
try:
|
||||
if "t2_" in link: thing = get_post(int(link.split("t2_")[1]), v=v)
|
||||
elif "t3_" in link: thing = get_comment(int(link.split("t3_")[1]), v=v)
|
||||
else: abort(400)
|
||||
except: abort(400)
|
||||
|
||||
if isinstance(thing, Submission):
|
||||
|
||||
ups = g.db.query(Vote
|
||||
).options(joinedload(Vote.user)
|
||||
).filter_by(submission_id=thing.id, vote_type=1
|
||||
).order_by(Vote.id).all()
|
||||
|
||||
downs = g.db.query(Vote
|
||||
).options(joinedload(Vote.user)
|
||||
).filter_by(submission_id=thing.id, vote_type=-1
|
||||
).order_by(Vote.id).all()
|
||||
|
||||
elif isinstance(thing, Comment):
|
||||
|
||||
ups = g.db.query(CommentVote
|
||||
).options(joinedload(CommentVote.user)
|
||||
).filter_by(comment_id=thing.id, vote_type=1
|
||||
).order_by(CommentVote.id).all()
|
||||
|
||||
downs = g.db.query(CommentVote
|
||||
).options(joinedload(CommentVote.user)
|
||||
).filter_by(comment_id=thing.id, vote_type=-1
|
||||
).order_by(CommentVote.id).all()
|
||||
|
||||
else: abort(400)
|
||||
|
||||
return render_template("votes.html",
|
||||
v=v,
|
||||
thing=thing,
|
||||
ups=ups,
|
||||
downs=downs,)
|
||||
|
||||
|
||||
|
||||
@app.post("/vote/post/<post_id>/<new>")
|
||||
@auth_required
|
||||
@validate_formkey
|
||||
def api_vote_post(post_id, new, v):
|
||||
|
||||
if new not in ["-1", "0", "1"]: abort(400)
|
||||
|
||||
if request.headers.get("X-User-Type","") == "Bot": abort(403)
|
||||
|
||||
new = int(new)
|
||||
|
||||
post = get_post(post_id)
|
||||
|
||||
existing = g.db.query(Vote).options(lazyload('*')).filter_by(user_id=v.id, submission_id=post.id).first()
|
||||
|
||||
if existing and existing.vote_type == new: return "", 204
|
||||
|
||||
if existing:
|
||||
if existing.vote_type == 0 and new != 0:
|
||||
post.author.coins += 1
|
||||
post.author.truecoins += 1
|
||||
g.db.add(post.author)
|
||||
existing.vote_type = new
|
||||
g.db.add(existing)
|
||||
elif existing.vote_type != 0 and new == 0:
|
||||
post.author.coins -= 1
|
||||
post.author.truecoins -= 1
|
||||
g.db.add(post.author)
|
||||
g.db.delete(existing)
|
||||
else:
|
||||
existing.vote_type = new
|
||||
g.db.add(existing)
|
||||
elif new != 0:
|
||||
post.author.coins += 1
|
||||
post.author.truecoins += 1
|
||||
g.db.add(post.author)
|
||||
vote = Vote(user_id=v.id,
|
||||
vote_type=new,
|
||||
submission_id=post_id,
|
||||
app_id=v.client.application.id if v.client else None
|
||||
)
|
||||
g.db.add(vote)
|
||||
|
||||
try:
|
||||
g.db.flush()
|
||||
post.upvotes = g.db.query(Vote.id).options(lazyload('*')).filter_by(submission_id=post.id, vote_type=1).count()
|
||||
post.downvotes = g.db.query(Vote.id).options(lazyload('*')).filter_by(submission_id=post.id, vote_type=-1).count()
|
||||
g.db.add(post)
|
||||
g.db.commit()
|
||||
except: g.db.rollback()
|
||||
return "", 204
|
||||
|
||||
@app.post("/vote/comment/<comment_id>/<new>")
|
||||
@auth_required
|
||||
@validate_formkey
|
||||
def api_vote_comment(comment_id, new, v):
|
||||
|
||||
if new not in ["-1", "0", "1"]: abort(400)
|
||||
|
||||
if request.headers.get("X-User-Type","") == "Bot": abort(403)
|
||||
|
||||
new = int(new)
|
||||
|
||||
try: comment_id = int(comment_id)
|
||||
except:
|
||||
try: comment_id = int(comment_id, 36)
|
||||
except: abort(404)
|
||||
|
||||
comment = get_comment(comment_id)
|
||||
|
||||
existing = g.db.query(CommentVote).options(lazyload('*')).filter_by(user_id=v.id, comment_id=comment.id).first()
|
||||
|
||||
if existing and existing.vote_type == new: return "", 204
|
||||
|
||||
if existing:
|
||||
if existing.vote_type == 0 and new != 0:
|
||||
comment.author.coins += 1
|
||||
comment.author.truecoins += 1
|
||||
g.db.add(comment.author)
|
||||
existing.vote_type = new
|
||||
g.db.add(existing)
|
||||
elif existing.vote_type != 0 and new == 0:
|
||||
comment.author.coins -= 1
|
||||
comment.author.truecoins -= 1
|
||||
g.db.add(comment.author)
|
||||
g.db.delete(existing)
|
||||
else:
|
||||
existing.vote_type = new
|
||||
g.db.add(existing)
|
||||
elif new != 0:
|
||||
comment.author.coins += 1
|
||||
comment.author.truecoins += 1
|
||||
g.db.add(comment.author)
|
||||
vote = CommentVote(user_id=v.id,
|
||||
vote_type=new,
|
||||
comment_id=comment_id,
|
||||
app_id=v.client.application.id if v.client else None
|
||||
)
|
||||
|
||||
g.db.add(vote)
|
||||
|
||||
try:
|
||||
g.db.flush()
|
||||
comment.upvotes = g.db.query(CommentVote.id).options(lazyload('*')).filter_by(comment_id=comment.id, vote_type=1).count()
|
||||
comment.downvotes = g.db.query(CommentVote.id).options(lazyload('*')).filter_by(comment_id=comment.id, vote_type=-1).count()
|
||||
g.db.add(comment)
|
||||
g.db.commit()
|
||||
except: g.db.rollback()
|
||||
return "", 204
|
||||
|
||||
|
||||
@app.post("/vote/poll/<comment_id>")
|
||||
@auth_required
|
||||
def api_vote_poll(comment_id, v):
|
||||
|
||||
vote = request.values.get("vote")
|
||||
if vote == "true": new = 1
|
||||
elif vote == "false": new = 0
|
||||
else: abort(400)
|
||||
|
||||
comment_id = int(comment_id)
|
||||
comment = get_comment(comment_id)
|
||||
|
||||
existing = g.db.query(CommentVote).options(lazyload('*')).filter_by(user_id=v.id, comment_id=comment.id).first()
|
||||
|
||||
if existing and existing.vote_type == new: return "", 204
|
||||
|
||||
if existing:
|
||||
if new == 1:
|
||||
existing.vote_type = new
|
||||
g.db.add(existing)
|
||||
else: g.db.delete(existing)
|
||||
elif new == 1:
|
||||
vote = CommentVote(user_id=v.id, vote_type=new, comment_id=comment.id)
|
||||
g.db.add(vote)
|
||||
|
||||
try:
|
||||
g.db.flush()
|
||||
comment.upvotes = g.db.query(CommentVote.id).options(lazyload('*')).filter_by(comment_id=comment.id, vote_type=1).count()
|
||||
g.db.add(comment)
|
||||
g.db.commit()
|
||||
except: g.db.rollback()
|
||||
return "", 204
|
|
@ -1,62 +1,61 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>{{'SITE_NAME' | app_config}}</title>
|
||||
<meta name="description" content="{{'SITE_NAME' | app_config}} Help">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<pre></pre>
|
||||
<pre></pre>
|
||||
<h3> Admin Tools</h3>
|
||||
|
||||
<h4>Content</h4>
|
||||
<ul>
|
||||
<li><a href="/admin/reported/posts">Reported Posts</a></li>
|
||||
<li><a href="/admin/reported/comments">Reported Comments</a></li>
|
||||
<li><a href="/admin/image_posts">Image Posts</a></li>
|
||||
<li><a href="/admin/removed">Removed Posts</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Users</h4>
|
||||
<ul>
|
||||
<li><a href="/admin/shadowbanned">Shadowbanned Users</a></li>
|
||||
<li><a href="/admin/agendaposters">Users with Agendaposter Theme</a></li>
|
||||
<li><a href="/admin/users">Users Feed</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Safety</h4>
|
||||
<ul>
|
||||
<li><a href="/admin/banned_domains">Banned Domains</a></li>
|
||||
<li><a href="/admin/image_ban">Perceptive Hash Image Ban</a></li>
|
||||
<li><a href="/admin/alt_votes">Multi Vote Analysis</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Grant</h4>
|
||||
<ul>
|
||||
<li><a href="/admin/user_award">Give User Award</a></li>
|
||||
<li><a href="/admin/badge_grant">Badges</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>API Access Control</h4>
|
||||
<ul>
|
||||
<li><a href="/admin/apps">Apps</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Statistics</h4>
|
||||
<ul>
|
||||
<li><a href="/stats">Content Stats</a></li>
|
||||
<li><a href="/chart">Stat Chart</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Configuration</h4>
|
||||
<ul>
|
||||
<li><a href="/admin/rules">Site Rules</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="disablesignups" name="disablesignups" {% if x == "yes" %}checked{% endif %} onchange="post_toast('/admin/disablesignups');">
|
||||
<label class="custom-control-label" for="disablesignups">Disable signups</label>
|
||||
</div>
|
||||
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>{{'SITE_NAME' | app_config}}</title>
|
||||
<meta name="description" content="{{'SITE_NAME' | app_config}} Help">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<pre></pre>
|
||||
<pre></pre>
|
||||
<h3> Admin Tools</h3>
|
||||
|
||||
<h4>Content</h4>
|
||||
<ul>
|
||||
<li><a href="/admin/reported/posts">Reported Posts</a></li>
|
||||
<li><a href="/admin/reported/comments">Reported Comments</a></li>
|
||||
<li><a href="/admin/image_posts">Image Posts</a></li>
|
||||
<li><a href="/admin/removed">Removed Posts</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Users</h4>
|
||||
<ul>
|
||||
<li><a href="/admin/shadowbanned">Shadowbanned Users</a></li>
|
||||
<li><a href="/admin/agendaposters">Users with Agendaposter Theme</a></li>
|
||||
<li><a href="/admin/users">Users Feed</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Safety</h4>
|
||||
<ul>
|
||||
<li><a href="/admin/banned_domains">Banned Domains</a></li>
|
||||
<li><a href="/admin/alt_votes">Multi Vote Analysis</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Grant</h4>
|
||||
<ul>
|
||||
<li><a href="/admin/user_award">Give User Award</a></li>
|
||||
<li><a href="/admin/badge_grant">Badges</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>API Access Control</h4>
|
||||
<ul>
|
||||
<li><a href="/admin/apps">Apps</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Statistics</h4>
|
||||
<ul>
|
||||
<li><a href="/stats">Content Stats</a></li>
|
||||
<li><a href="/chart">Stat Chart</a></li>
|
||||
</ul>
|
||||
|
||||
<h4>Configuration</h4>
|
||||
<ul>
|
||||
<li><a href="/admin/rules">Site Rules</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="disablesignups" name="disablesignups" {% if x == "yes" %}checked{% endif %} onchange="post_toast('/admin/disablesignups');">
|
||||
<label class="custom-control-label" for="disablesignups">Disable signups</label>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -1,88 +1,88 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>{{'SITE_NAME' | app_config}}</title>
|
||||
<meta name="description" content="{{'SITE_NAME' | app_config}} Help">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<pre>
|
||||
|
||||
|
||||
|
||||
</pre>
|
||||
<h5>Vote Info</h5>
|
||||
|
||||
<form action="/admin/alt_votes" method="get" class="mb-6">
|
||||
<label for="link-input">Usernames</label>
|
||||
<input id="link-input" type="text" class="form-control mb-2" name="u1" value="{{u1.username if u1 else ''}}" placeholder="User 1">
|
||||
<input id="link-input" type="text" class="form-control mb-2" name="u2" value="{{u2.username if u2 else ''}}" placeholder="User 2">
|
||||
<input type="submit" value="Submit" class="btn btn-primary">
|
||||
</form>
|
||||
|
||||
{% if u1 and u2 %}
|
||||
|
||||
|
||||
<h2>Analysis</h2>
|
||||
|
||||
|
||||
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>@{{u1.username}} only(% unique)</th>
|
||||
<th>Both</th>
|
||||
<th>@{{u2.username}} only (% unique)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr>
|
||||
<td><b>Post Upvotes</b></td>
|
||||
<td>{{data['u1_only_post_ups']}} ({{data['u1_post_ups_unique']}}%)</td>
|
||||
<td>{{data['both_post_ups']}}</td>
|
||||
<td>{{data['u2_only_post_ups']}} ({{data['u2_post_ups_unique']}}%)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Post Downvotes</b></td>
|
||||
<td>{{data['u1_only_post_downs']}} ({{data['u1_post_downs_unique']}}%)</td>
|
||||
<td>{{data['both_post_downs']}}</td>
|
||||
<td>{{data['u2_only_post_downs']}} ({{data['u2_post_downs_unique']}}%)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Comment Upvotes</b></td>
|
||||
<td>{{data['u1_only_comment_ups']}} ({{data['u1_comment_ups_unique']}}%)</td>
|
||||
<td>{{data['both_comment_ups']}}</td>
|
||||
<td>{{data['u2_only_comment_ups']}} ({{data['u2_comment_ups_unique']}}%)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Comment Downvotes</b></td>
|
||||
<td>{{data['u1_only_comment_downs']}} ({{data['u1_comment_downs_unique']}}%)</td>
|
||||
<td>{{data['both_comment_downs']}}</td>
|
||||
<td>{{data['u2_only_comment_downs']}} ({{data['u2_comment_downs_unique']}}%)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Link Accounts</h2>
|
||||
|
||||
{% if u2 in u1.alts %}
|
||||
<p>Accounts are known alts of eachother.</p>
|
||||
{% else %}
|
||||
|
||||
<p>Two accounts controlled by different people should have most uniqueness percentages at or above 70-80%</p>
|
||||
<p>A sockpuppet account will have its uniqueness percentages significantly lower.</p>
|
||||
|
||||
<a href="javascript:void(0)" class="btn btn-secondary" onclick="document.getElementById('linkbtn').classList.toggle('d-none');">Link Accounts</a>
|
||||
<form action="/admin/link_accounts" method="post">
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
<input type="hidden" name="u1" value="{{u1.id}}">
|
||||
<input type="hidden" name="u2" value="{{u2.id}}">
|
||||
<input type="submit" id="linkbtn" class="btn btn-primary d-none" value="Confirm Link: {{u1.username}} and {{u2.username}}">
|
||||
</form>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>{{'SITE_NAME' | app_config}}</title>
|
||||
<meta name="description" content="{{'SITE_NAME' | app_config}} Help">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<pre>
|
||||
|
||||
|
||||
|
||||
</pre>
|
||||
<h5>Vote Info</h5>
|
||||
|
||||
<form action="/admin/alt_votes" method="get" class="mb-6">
|
||||
<label for="link-input">Usernames</label>
|
||||
<input id="link-input" type="text" class="form-control mb-2" name="u1" value="{{u1.username if u1 else ''}}" placeholder="User 1">
|
||||
<input id="link-input" type="text" class="form-control mb-2" name="u2" value="{{u2.username if u2 else ''}}" placeholder="User 2">
|
||||
<input type="submit" value="Submit" class="btn btn-primary">
|
||||
</form>
|
||||
|
||||
{% if u1 and u2 %}
|
||||
|
||||
|
||||
<h2>Analysis</h2>
|
||||
|
||||
|
||||
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>@{{u1.username}} only(% unique)</th>
|
||||
<th>Both</th>
|
||||
<th>@{{u2.username}} only (% unique)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tr>
|
||||
<td><b>Post Upvotes</b></td>
|
||||
<td>{{data['u1_only_post_ups']}} ({{data['u1_post_ups_unique']}}%)</td>
|
||||
<td>{{data['both_post_ups']}}</td>
|
||||
<td>{{data['u2_only_post_ups']}} ({{data['u2_post_ups_unique']}}%)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Post Downvotes</b></td>
|
||||
<td>{{data['u1_only_post_downs']}} ({{data['u1_post_downs_unique']}}%)</td>
|
||||
<td>{{data['both_post_downs']}}</td>
|
||||
<td>{{data['u2_only_post_downs']}} ({{data['u2_post_downs_unique']}}%)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Comment Upvotes</b></td>
|
||||
<td>{{data['u1_only_comment_ups']}} ({{data['u1_comment_ups_unique']}}%)</td>
|
||||
<td>{{data['both_comment_ups']}}</td>
|
||||
<td>{{data['u2_only_comment_ups']}} ({{data['u2_comment_ups_unique']}}%)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Comment Downvotes</b></td>
|
||||
<td>{{data['u1_only_comment_downs']}} ({{data['u1_comment_downs_unique']}}%)</td>
|
||||
<td>{{data['both_comment_downs']}}</td>
|
||||
<td>{{data['u2_only_comment_downs']}} ({{data['u2_comment_downs_unique']}}%)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Link Accounts</h2>
|
||||
|
||||
{% if u2 in u1.alts %}
|
||||
<p>Accounts are known alts of eachother.</p>
|
||||
{% else %}
|
||||
|
||||
<p>Two accounts controlled by different people should have most uniqueness percentages at or above 70-80%</p>
|
||||
<p>A sockpuppet account will have its uniqueness percentages significantly lower.</p>
|
||||
|
||||
<a href="javascript:void(0)" class="btn btn-secondary" onclick="document.getElementById('linkbtn').classList.toggle('d-none');">Link Accounts</a>
|
||||
<form action="/admin/link_accounts" method="post">
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
<input type="hidden" name="u1" value="{{u1.id}}">
|
||||
<input type="hidden" name="u2" value="{{u2.id}}">
|
||||
<input type="submit" id="linkbtn" class="btn btn-primary d-none" value="Confirm Link: {{u1.username}} and {{u2.username}}">
|
||||
</form>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% endblock %}
|
|
@ -1,72 +1,72 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>API App Administration</title>
|
||||
<meta name="description" content="{{'SITE_NAME' | app_config}} Help">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-lg-8">
|
||||
<div class="settings">
|
||||
|
||||
<div class="settings-section rounded">
|
||||
<div class="d-lg-flex">
|
||||
<div class="title w-lg-25">
|
||||
<label for="over18">{{app.app_name}}</label>
|
||||
</div>
|
||||
<div class="body w-lg-100">
|
||||
<label for="edit-{{app.id}}-author" class="mb-0 w-lg-25">User</label>
|
||||
<input id="edit-{{app.id}}-author" class="form-control" type="text" name="name" value="{{app.author.username}}" readonly=readonly>
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
<label for="edit-{{app.id}}-name" class="mb-0 w-lg-25">App Name</label>
|
||||
<input id="edit-{{app.id}}-name" class="form-control" type="text" name="name" value="{{app.app_name}}" readonly=readonly>
|
||||
|
||||
|
||||
<label for="edit-{{app.id}}-redirect" class="mb-0 w-lg-25">Redirect URI</label>
|
||||
<input id="edit-{{app.id}}-redirect" class="form-control" type="text" name="redirect_uri" value="{{app.redirect_uri}}" readonly="readonly">
|
||||
<label for="edit-{{app.id}}-desc" class="mb-0 w-lg-25">Description</label>
|
||||
<textarea form="edit-app-{{app.id}}" class="form-control" name="description" id="edit-{{app.id}}-desc" maxlength="256" readonly="readonly">{{app.description}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="d-flex">
|
||||
{% if not app.client_id%}
|
||||
|
||||
<a href="javascript:void(0)" class="btn btn-primary ml-auto" onclick="post_toast('/admin/app/approve/{{app.id}}')">Approve</a>
|
||||
<a href="javascript:void(0)" class="btn btn-secondary mr-0" onclick="post_toast('/admin/app/reject/{{app.id}}')">Reject</a>
|
||||
|
||||
{% else %}
|
||||
|
||||
<a href="javascript:void(0)" class="btn btn-primary ml-auto" onclick="post_toast('/admin/app/revoke/{{app.id}}')">Revoke</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% if listing %}
|
||||
{% include "submission_listing.html" %}
|
||||
{% elif comments %}
|
||||
{% include "comments.html" %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast-post-success" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
|
||||
<div class="toast-body bg-success text-center text-white">
|
||||
<i class="fas fa-comment-alt-smile mr-2"></i><span id="toast-post-success-text"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toast" id="toast-post-error" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
|
||||
<div class="toast-body bg-danger text-center text-white">
|
||||
<i class="fas fa-exclamation-circle mr-2"></i><span id="toast-post-error-text"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>API App Administration</title>
|
||||
<meta name="description" content="{{'SITE_NAME' | app_config}} Help">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-lg-8">
|
||||
<div class="settings">
|
||||
|
||||
<div class="settings-section rounded">
|
||||
<div class="d-lg-flex">
|
||||
<div class="title w-lg-25">
|
||||
<label for="over18">{{app.app_name}}</label>
|
||||
</div>
|
||||
<div class="body w-lg-100">
|
||||
<label for="edit-{{app.id}}-author" class="mb-0 w-lg-25">User</label>
|
||||
<input id="edit-{{app.id}}-author" class="form-control" type="text" name="name" value="{{app.author.username}}" readonly=readonly>
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
<label for="edit-{{app.id}}-name" class="mb-0 w-lg-25">App Name</label>
|
||||
<input id="edit-{{app.id}}-name" class="form-control" type="text" name="name" value="{{app.app_name}}" readonly=readonly>
|
||||
|
||||
|
||||
<label for="edit-{{app.id}}-redirect" class="mb-0 w-lg-25">Redirect URI</label>
|
||||
<input id="edit-{{app.id}}-redirect" class="form-control" type="text" name="redirect_uri" value="{{app.redirect_uri}}" readonly="readonly">
|
||||
<label for="edit-{{app.id}}-desc" class="mb-0 w-lg-25">Description</label>
|
||||
<textarea form="edit-app-{{app.id}}" class="form-control" name="description" id="edit-{{app.id}}-desc" maxlength="256" readonly="readonly">{{app.description}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="d-flex">
|
||||
{% if not app.client_id%}
|
||||
|
||||
<a href="javascript:void(0)" class="btn btn-primary ml-auto" onclick="post_toast('/admin/app/approve/{{app.id}}')">Approve</a>
|
||||
<a href="javascript:void(0)" class="btn btn-secondary mr-0" onclick="post_toast('/admin/app/reject/{{app.id}}')">Reject</a>
|
||||
|
||||
{% else %}
|
||||
|
||||
<a href="javascript:void(0)" class="btn btn-primary ml-auto" onclick="post_toast('/admin/app/revoke/{{app.id}}')">Revoke</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% if listing %}
|
||||
{% include "submission_listing.html" %}
|
||||
{% elif comments %}
|
||||
{% include "comments.html" %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast-post-success" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
|
||||
<div class="toast-body bg-success text-center text-white">
|
||||
<i class="fas fa-comment-alt-smile mr-2"></i><span id="toast-post-success-text"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toast" id="toast-post-error" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
|
||||
<div class="toast-body bg-danger text-center text-white">
|
||||
<i class="fas fa-exclamation-circle mr-2"></i><span id="toast-post-error-text">Error, please try again later.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
|
@ -1,72 +1,72 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>API App Administration</title>
|
||||
<meta name="description" content="{{'SITE_NAME' | app_config}} Help">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-lg-8">
|
||||
<div class="settings">
|
||||
{% for app in apps %}
|
||||
<div class="settings-section rounded">
|
||||
<div class="d-lg-flex">
|
||||
<div class="title w-lg-25">
|
||||
<label for="over18"><a href="{{app.permalink}}" target="_blank">{{app.app_name}}</a></label>
|
||||
</div>
|
||||
<div class="body w-lg-100">
|
||||
<label for="edit-{{app.id}}-author" class="mb-0 w-lg-25">User</label>
|
||||
<input id="edit-{{app.id}}-author" class="form-control" type="text" name="name" value="{{app.author.username}}" readonly=readonly>
|
||||
|
||||
<label for="edit-{{app.id}}-name" class="mb-0 w-lg-25">App Name</label>
|
||||
<input id="edit-{{app.id}}-name" class="form-control" type="text" name="name" value="{{app.app_name}}" readonly=readonly>
|
||||
|
||||
{% if app.client_id %}
|
||||
<label for="edit-{{app.id}}-client-id" class="mb-0 w-lg-25">Client ID</label>
|
||||
<input id="edit-{{app.id}}-client-id" class="form-control" type="text" name="name" value="{{app.client_id}}" readonly="readonly">
|
||||
{% endif %}
|
||||
|
||||
|
||||
<label for="edit-{{app.id}}-redirect" class="mb-0 w-lg-25">Redirect URI</label>
|
||||
<input id="edit-{{app.id}}-redirect" class="form-control" type="text" name="redirect_uri" value="{{app.redirect_uri}}" readonly="readonly">
|
||||
<label for="edit-{{app.id}}-desc" class="mb-0 w-lg-25">Description</label>
|
||||
<textarea form="edit-app-{{app.id}}" class="form-control" name="description" id="edit-{{app.id}}-desc" maxlength="256" readonly="readonly">{{app.description}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="d-flex">
|
||||
{% if not app.client_id %}
|
||||
|
||||
<a href="javascript:void(0)" class="btn btn-primary ml-auto" onclick="post_toast('/admin/app/approve/{{app.id}}')">Approve</a>
|
||||
<a href="javascript:void(0)" class="btn btn-secondary mr-0" onclick="post_toast('/admin/app/reject/{{app.id}}')">Reject</a>
|
||||
|
||||
{% else %}
|
||||
|
||||
<a href="javascript:void(0)" class="btn btn-primary ml-auto" onclick="post_toast('/admin/app/revoke/{{app.id}}')">Revoke</a>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast-post-success" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
|
||||
<div class="toast-body bg-success text-center text-white">
|
||||
<i class="fas fa-comment-alt-smile mr-2"></i><span id="toast-post-success-text"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toast" id="toast-post-error" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
|
||||
<div class="toast-body bg-danger text-center text-white">
|
||||
<i class="fas fa-exclamation-circle mr-2"></i><span id="toast-post-error-text"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>API App Administration</title>
|
||||
<meta name="description" content="{{'SITE_NAME' | app_config}} Help">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-lg-8">
|
||||
<div class="settings">
|
||||
{% for app in apps %}
|
||||
<div class="settings-section rounded">
|
||||
<div class="d-lg-flex">
|
||||
<div class="title w-lg-25">
|
||||
<label for="over18"><a href="{{app.permalink}}" target="_blank">{{app.app_name}}</a></label>
|
||||
</div>
|
||||
<div class="body w-lg-100">
|
||||
<label for="edit-{{app.id}}-author" class="mb-0 w-lg-25">User</label>
|
||||
<input id="edit-{{app.id}}-author" class="form-control" type="text" name="name" value="{{app.author.username}}" readonly=readonly>
|
||||
|
||||
<label for="edit-{{app.id}}-name" class="mb-0 w-lg-25">App Name</label>
|
||||
<input id="edit-{{app.id}}-name" class="form-control" type="text" name="name" value="{{app.app_name}}" readonly=readonly>
|
||||
|
||||
{% if app.client_id %}
|
||||
<label for="edit-{{app.id}}-client-id" class="mb-0 w-lg-25">Client ID</label>
|
||||
<input id="edit-{{app.id}}-client-id" class="form-control" type="text" name="name" value="{{app.client_id}}" readonly="readonly">
|
||||
{% endif %}
|
||||
|
||||
|
||||
<label for="edit-{{app.id}}-redirect" class="mb-0 w-lg-25">Redirect URI</label>
|
||||
<input id="edit-{{app.id}}-redirect" class="form-control" type="text" name="redirect_uri" value="{{app.redirect_uri}}" readonly="readonly">
|
||||
<label for="edit-{{app.id}}-desc" class="mb-0 w-lg-25">Description</label>
|
||||
<textarea form="edit-app-{{app.id}}" class="form-control" name="description" id="edit-{{app.id}}-desc" maxlength="256" readonly="readonly">{{app.description}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="d-flex">
|
||||
{% if not app.client_id %}
|
||||
|
||||
<a href="javascript:void(0)" class="btn btn-primary ml-auto" onclick="post_toast('/admin/app/approve/{{app.id}}')">Approve</a>
|
||||
<a href="javascript:void(0)" class="btn btn-secondary mr-0" onclick="post_toast('/admin/app/reject/{{app.id}}')">Reject</a>
|
||||
|
||||
{% else %}
|
||||
|
||||
<a href="javascript:void(0)" class="btn btn-primary ml-auto" onclick="post_toast('/admin/app/revoke/{{app.id}}')">Revoke</a>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast-post-success" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
|
||||
<div class="toast-body bg-success text-center text-white">
|
||||
<i class="fas fa-comment-alt-smile mr-2"></i><span id="toast-post-success-text"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toast" id="toast-post-error" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
|
||||
<div class="toast-body bg-danger text-center text-white">
|
||||
<i class="fas fa-exclamation-circle mr-2"></i><span id="toast-post-error-text">Error, please try again later.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
|
@ -1,74 +1,74 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>Badge Grant</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagetype %}message{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger alert-dismissible fade show my-3" role="alert">
|
||||
<i class="fas fa-exclamation-circle my-auto"></i>
|
||||
<span>
|
||||
{{error}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if msg %}
|
||||
<div class="alert alert-success alert-dismissible fade show my-3" role="alert">
|
||||
<i class="fas fa-check-circle my-auto" aria-hidden="true"></i>
|
||||
<span>
|
||||
{{msg}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<pre></pre>
|
||||
<pre></pre>
|
||||
<h5>Badge Grant</h5>
|
||||
|
||||
<form action="/admin/badge_grant", method="post">
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
|
||||
|
||||
<label for="input-username">Username</label><br>
|
||||
<input id="input-username" class="form-control" type="text" name="username" required>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th scope="col">Select</th>
|
||||
<th scope="col">Image</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Default Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for badge in badge_types %}
|
||||
<tr>
|
||||
<td><input type="radio" id="badge-{{badge.id}}" name="badge_id" value="{{badge.id}}"></td>
|
||||
<td><label for="badge-{{badge.id}}"><img loading="lazy" src="{{badge.path}}" width="70px" height="70px"></label></td>
|
||||
<td>{{badge.name}}</td>
|
||||
<td>{{badge.description}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<label for="input-url">URL</label><br>
|
||||
<input id="input-url" class="form-control" type="text" name="url" placeholder="Optional">
|
||||
|
||||
<label for="input-description">Custom description</label><br>
|
||||
<input id="input-description" class="form-control" type="text" name="description" placeholder="Leave blank for badge default">
|
||||
|
||||
<input class="btn btn-primary" type="submit">
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>Badge Grant</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagetype %}message{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger alert-dismissible fade show my-3" role="alert">
|
||||
<i class="fas fa-exclamation-circle my-auto"></i>
|
||||
<span>
|
||||
{{error}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if msg %}
|
||||
<div class="alert alert-success alert-dismissible fade show my-3" role="alert">
|
||||
<i class="fas fa-check-circle my-auto" aria-hidden="true"></i>
|
||||
<span>
|
||||
{{msg}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<pre></pre>
|
||||
<pre></pre>
|
||||
<h5>Badge Grant</h5>
|
||||
|
||||
<form action="/admin/badge_grant", method="post">
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
|
||||
|
||||
<label for="input-username">Username</label><br>
|
||||
<input id="input-username" class="form-control" type="text" name="username" required>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th scope="col">Select</th>
|
||||
<th scope="col">Image</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Default Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for badge in badge_types %}
|
||||
<tr>
|
||||
<td><input type="radio" id="badge-{{badge.id}}" name="badge_id" value="{{badge.id}}"></td>
|
||||
<td><label for="badge-{{badge.id}}"><img loading="lazy" src="{{badge.path}}" width="70px" height="70px"></label></td>
|
||||
<td>{{badge.name}}</td>
|
||||
<td>{{badge.description}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<label for="input-url">URL</label><br>
|
||||
<input id="input-url" class="form-control" type="text" name="url" placeholder="Optional">
|
||||
|
||||
<label for="input-description">Custom description</label><br>
|
||||
<input id="input-description" class="form-control" type="text" name="description" placeholder="Leave blank for badge default">
|
||||
|
||||
<input class="btn btn-primary" type="submit">
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>Banned Domains</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<pre>
|
||||
|
||||
</pre>
|
||||
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th style="font-weight:bold;">Domain</th>
|
||||
<th style="font-weight:bold;">Ban reason</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{% for domain in banned_domains %}
|
||||
<tr>
|
||||
<td style="font-weight:bold;">{{domain.domain}}</td>
|
||||
<td>{{domain.reason}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
<form action="/admin/banned_domains" method="post">
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
<input name="domain" placeholder="Enter domain here.." class="form-control" required>
|
||||
<input name="reason" placeholder="Enter ban reason here.." class="form-control">
|
||||
<input id="ban-submit" type="submit" class="btn btn-primary" value="Toggle ban">
|
||||
</form>
|
||||
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>Banned Domains</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<pre>
|
||||
|
||||
</pre>
|
||||
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th style="font-weight:bold;">Domain</th>
|
||||
<th style="font-weight:bold;">Ban reason</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{% for domain in banned_domains %}
|
||||
<tr>
|
||||
<td style="font-weight:bold;">{{domain.domain}}</td>
|
||||
<td>{{domain.reason}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
<form action="/admin/banned_domains" method="post">
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
<input name="domain" placeholder="Enter domain here.." class="form-control" required>
|
||||
<input name="reason" placeholder="Enter ban reason here.." onchange="document.getElementById('ban-submit').disabled=false" class="form-control">
|
||||
<input id="ban-submit" type="submit" class="btn btn-primary" value="Toggle ban" disabled>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
|
@ -1,25 +1,25 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>{{'SITE_NAME' | app_config}}</title>
|
||||
<meta name="description" content="{{'SITE_NAME' | app_config}} Help">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<pre></pre>
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th>Statistic</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for entry in data %}
|
||||
<tr>
|
||||
<td>{{entry}}</td>
|
||||
<td>{{data[entry]}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>{{'SITE_NAME' | app_config}}</title>
|
||||
<meta name="description" content="{{'SITE_NAME' | app_config}} Help">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<pre></pre>
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th>Statistic</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for entry in data %}
|
||||
<tr>
|
||||
<td>{{entry}}</td>
|
||||
<td>{{data[entry]}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
|
@ -1,42 +0,0 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>Image Ban</title>
|
||||
<meta name="description" content="Image Ban">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if existing %}
|
||||
<p class="text-danger">Image already banned for: {{existing.ban_reason}}</p>
|
||||
{% elif success %}
|
||||
<p class="text-success">Image banned.</p>
|
||||
{% endif %}
|
||||
|
||||
<pre>
|
||||
|
||||
|
||||
|
||||
</pre>
|
||||
<h5>Perceptive Hash Image Ban</h5>
|
||||
<p>Upload an image to add its hash to the ban list.</p>
|
||||
|
||||
<form action="/admin/image_ban" method="post" class="mb-6" enctype="multipart/form-data">
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
<label for="img-input">Image Upload</label>
|
||||
<input id="img-input" type="file" class="form-control-file mb-2" name="file">
|
||||
<label for="img-input" class="mt-3">Ban Reason</label>
|
||||
<select name="ban_reason" class="form-control" id="ban_reason">
|
||||
<option disabled selected>Select Ban Reason</option>
|
||||
<option value="Bestiality">Bestiality</option>
|
||||
<option value="Child Sexual Abuse Material">CSAM</option>
|
||||
<option value="Involuntary Pornography">Involuntary Pornography</option>
|
||||
</select>
|
||||
<label for="time-input" class="mt-3">Penalty</label>
|
||||
<small>Enter the number of days to ban a user who attempts to upload this image</small>
|
||||
<input id="time-input" class="form-control" type="text" name="ban_length" placeholder="Enter 0 for permanent ban" required>
|
||||
<input type="submit" value="Ban Image" class="btn btn-primary mt-3">
|
||||
</form>
|
||||
|
||||
|
||||
{% endblock %}
|
|
@ -1,77 +1,77 @@
|
|||
{% extends "userpage.html" %}
|
||||
|
||||
{% block adminpanel %}{% endblock %}
|
||||
{% block pagetype %}userpage{% endblock %}
|
||||
{% block banner %}{% endblock %}
|
||||
{% block mobileBanner %}{% endblock %}
|
||||
{% block desktopBanner %}{% endblock %}
|
||||
{% block desktopUserBanner %}{% endblock %}
|
||||
{% block mobileUserBanner %}{% endblock %}
|
||||
|
||||
{% block postNav %}{% endblock %}
|
||||
|
||||
{% block fixedMobileBarJS %}
|
||||
<script>
|
||||
var prevScrollpos = window.pageYOffset;
|
||||
window.onscroll = function () {
|
||||
var currentScrollPos = window.pageYOffset;
|
||||
if (prevScrollpos > currentScrollPos) {
|
||||
document.getElementById("fixed-bar-mobile").style.top = "48px";
|
||||
document.getElementById("navbar").classList.remove("shadow");
|
||||
}
|
||||
else if (currentScrollPos <= 125) {
|
||||
document.getElementById("fixed-bar-mobile").style.top = "48px";
|
||||
document.getElementById("navbar").classList.remove("shadow");
|
||||
}
|
||||
else {
|
||||
document.getElementById("fixed-bar-mobile").style.top = "-48px";
|
||||
document.getElementById("dropdownMenuSortBy").classList.remove('show');
|
||||
document.getElementById("dropdownMenuFrom").classList.remove('show');
|
||||
document.getElementById("navbar").classList.add("shadow");
|
||||
}
|
||||
prevScrollpos = currentScrollPos;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
<title>Image feed</title>
|
||||
<meta name="description" content="on {{'SITE_NAME' | app_config}}">
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row no-gutters">
|
||||
|
||||
<div class="col">
|
||||
|
||||
{% block listing %}
|
||||
<div class="posts">
|
||||
{% include "submission_listing.html" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagenav %}
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination pagination-sm py-3 pl-3 mb-0">
|
||||
{% if page>1 %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?page={{page-1}}" tabindex="-1">Prev</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Prev</span></li>
|
||||
{% endif %}
|
||||
{% if next_exists %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?page={{page+1}}">Next</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Next</span></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
{% extends "userpage.html" %}
|
||||
|
||||
{% block adminpanel %}{% endblock %}
|
||||
{% block pagetype %}userpage{% endblock %}
|
||||
{% block banner %}{% endblock %}
|
||||
{% block mobileBanner %}{% endblock %}
|
||||
{% block desktopBanner %}{% endblock %}
|
||||
{% block desktopUserBanner %}{% endblock %}
|
||||
{% block mobileUserBanner %}{% endblock %}
|
||||
|
||||
{% block postNav %}{% endblock %}
|
||||
|
||||
{% block fixedMobileBarJS %}
|
||||
<script>
|
||||
var prevScrollpos = window.pageYOffset;
|
||||
window.onscroll = function () {
|
||||
var currentScrollPos = window.pageYOffset;
|
||||
if (prevScrollpos > currentScrollPos) {
|
||||
document.getElementById("fixed-bar-mobile").style.top = "48px";
|
||||
document.getElementById("navbar").classList.remove("shadow");
|
||||
}
|
||||
else if (currentScrollPos <= 125) {
|
||||
document.getElementById("fixed-bar-mobile").style.top = "48px";
|
||||
document.getElementById("navbar").classList.remove("shadow");
|
||||
}
|
||||
else {
|
||||
document.getElementById("fixed-bar-mobile").style.top = "-48px";
|
||||
document.getElementById("dropdownMenuSortBy").classList.remove('show');
|
||||
document.getElementById("dropdownMenuFrom").classList.remove('show');
|
||||
document.getElementById("navbar").classList.add("shadow");
|
||||
}
|
||||
prevScrollpos = currentScrollPos;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
<title>Image feed</title>
|
||||
<meta name="description" content="on {{'SITE_NAME' | app_config}}">
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row no-gutters">
|
||||
|
||||
<div class="col">
|
||||
|
||||
{% block listing %}
|
||||
<div class="posts">
|
||||
{% include "submission_listing.html" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagenav %}
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination pagination-sm py-3 pl-3 mb-0">
|
||||
{% if page>1 %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?page={{page-1}}" tabindex="-1">Prev</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Prev</span></li>
|
||||
{% endif %}
|
||||
{% if next_exists %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?page={{page+1}}">Next</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Next</span></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{% extends "mine.html" %}
|
||||
|
||||
{% block maincontent %}
|
||||
<img loading="lazy" src="{{single_plot}}">
|
||||
<img loading="lazy" src="{{multi_plot}}">
|
||||
{% include "user_listing.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% extends "mine.html" %}
|
||||
|
||||
{% block maincontent %}
|
||||
<img loading="lazy" src="{{single_plot}}">
|
||||
<img loading="lazy" src="{{multi_plot}}">
|
||||
{% include "user_listing.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block navbar %}{% endblock %}
|
|
@ -1,59 +1,59 @@
|
|||
{% extends "admin/image_posts.html" %}
|
||||
|
||||
|
||||
{% block title %}
|
||||
<title>Removed Content</title>
|
||||
<meta name="description" content="on {{'SITE_NAME' | app_config}}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row justify-content-around mx-lg-5 d-lg-none no-gutters">
|
||||
<div class="col bg-light border-bottom rounded-md p-3">
|
||||
<div class="profile-details">
|
||||
<div class="media">
|
||||
<div class="media-body">
|
||||
<pre></pre>
|
||||
<h5 class="h6 d-inline-block">Removed Posts</h5>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters">
|
||||
|
||||
<div class="col">
|
||||
|
||||
{% block listing %}
|
||||
<div class="posts">
|
||||
{% include "submission_listing.html" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagenav %}
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination pagination-sm py-3 pl-3 mb-0">
|
||||
{% if page>1 %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?page={{page-1}}" tabindex="-1">Prev</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Prev</span></li>
|
||||
{% endif %}
|
||||
{% if next_exists %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?page={{page+1}}">Next</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Next</span></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
{% extends "admin/image_posts.html" %}
|
||||
|
||||
|
||||
{% block title %}
|
||||
<title>Removed Content</title>
|
||||
<meta name="description" content="on {{'SITE_NAME' | app_config}}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row justify-content-around mx-lg-5 d-lg-none no-gutters">
|
||||
<div class="col bg-light border-bottom rounded-md p-3">
|
||||
<div class="profile-details">
|
||||
<div class="media">
|
||||
<div class="media-body">
|
||||
<pre></pre>
|
||||
<h5 class="h6 d-inline-block">Removed Posts</h5>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row no-gutters">
|
||||
|
||||
<div class="col">
|
||||
|
||||
{% block listing %}
|
||||
<div class="posts">
|
||||
{% include "submission_listing.html" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagenav %}
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination pagination-sm py-3 pl-3 mb-0">
|
||||
{% if page>1 %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?page={{page-1}}" tabindex="-1">Prev</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Prev</span></li>
|
||||
{% endif %}
|
||||
{% if next_exists %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?page={{page+1}}">Next</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Next</span></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
{% extends "admin/reported_posts.html" %}
|
||||
|
||||
|
||||
|
||||
{% block listing %}
|
||||
|
||||
|
||||
<div class="posts">
|
||||
{% with comments=listing %}
|
||||
{% include "comments.html" %}
|
||||
{% endwith %}
|
||||
{% if not listing %}
|
||||
<div class="row no-gutters">
|
||||
<div class="col">
|
||||
<div class="text-center py-7">
|
||||
<div class="h4 p-2">There are no comments here (yet).</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% extends "admin/reported_posts.html" %}
|
||||
|
||||
|
||||
|
||||
{% block listing %}
|
||||
|
||||
|
||||
<div class="posts">
|
||||
{% with comments=listing %}
|
||||
{% include "comments.html" %}
|
||||
{% endwith %}
|
||||
{% if not listing %}
|
||||
<div class="row no-gutters">
|
||||
<div class="col">
|
||||
<div class="text-center py-7">
|
||||
<div class="h4 p-2">There are no comments here (yet).</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -1,104 +1,104 @@
|
|||
{% extends "userpage.html" %}
|
||||
|
||||
{% block adminpanel %}{% endblock %}
|
||||
{% block pagetype %}userpage{% endblock %}
|
||||
{% block banner %}{% endblock %}
|
||||
{% block mobileBanner %}{% endblock %}
|
||||
{% block desktopBanner %}{% endblock %}
|
||||
{% block desktopUserBanner %}{% endblock %}
|
||||
{% block mobileUserBanner %}{% endblock %}
|
||||
|
||||
{% block postNav %}
|
||||
<div class="container-fluid bg-white sticky">
|
||||
<div class="row border-bottom">
|
||||
<div class="col">
|
||||
<div class="container">
|
||||
<div class="row bg-white">
|
||||
<div class="col">
|
||||
<div class="d-flex">
|
||||
<ul class="nav post-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.path=="/admin/reported/posts" %} active{% endif %}" href="/admin/reported/posts">
|
||||
<div>Posts</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.path=="/admin/reported/comments" %} active{% endif %}" href="/admin/reported/comments">
|
||||
<div>Comments</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block fixedMobileBarJS %}
|
||||
<script>
|
||||
var prevScrollpos = window.pageYOffset;
|
||||
window.onscroll = function () {
|
||||
var currentScrollPos = window.pageYOffset;
|
||||
if (prevScrollpos > currentScrollPos) {
|
||||
document.getElementById("fixed-bar-mobile").style.top = "48px";
|
||||
document.getElementById("navbar").classList.remove("shadow");
|
||||
}
|
||||
else if (currentScrollPos <= 125) {
|
||||
document.getElementById("fixed-bar-mobile").style.top = "48px";
|
||||
document.getElementById("navbar").classList.remove("shadow");
|
||||
}
|
||||
else {
|
||||
document.getElementById("fixed-bar-mobile").style.top = "-48px";
|
||||
document.getElementById("dropdownMenuSortBy").classList.remove('show');
|
||||
document.getElementById("dropdownMenuFrom").classList.remove('show');
|
||||
document.getElementById("navbar").classList.add("shadow");
|
||||
}
|
||||
prevScrollpos = currentScrollPos;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
<title>Reported Posts</title>
|
||||
<meta name="description" content="on {{'SITE_NAME' | app_config}}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row no-gutters">
|
||||
|
||||
<div class="col">
|
||||
|
||||
{% block listing %}
|
||||
<div class="posts">
|
||||
{% include "submission_listing.html" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagenav %}
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination pagination-sm py-3 pl-3 mb-0">
|
||||
{% if page>1 %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?page={{page-1}}" tabindex="-1">Prev</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Prev</span></li>
|
||||
{% endif %}
|
||||
{% if next_exists %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?page={{page+1}}">Next</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Next</span></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
{% extends "userpage.html" %}
|
||||
|
||||
{% block adminpanel %}{% endblock %}
|
||||
{% block pagetype %}userpage{% endblock %}
|
||||
{% block banner %}{% endblock %}
|
||||
{% block mobileBanner %}{% endblock %}
|
||||
{% block desktopBanner %}{% endblock %}
|
||||
{% block desktopUserBanner %}{% endblock %}
|
||||
{% block mobileUserBanner %}{% endblock %}
|
||||
|
||||
{% block postNav %}
|
||||
<div class="container-fluid bg-white sticky">
|
||||
<div class="row border-bottom">
|
||||
<div class="col">
|
||||
<div class="container">
|
||||
<div class="row bg-white">
|
||||
<div class="col">
|
||||
<div class="d-flex">
|
||||
<ul class="nav post-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.path=="/admin/reported/posts" %} active{% endif %}" href="/admin/reported/posts">
|
||||
<div>Posts</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.path=="/admin/reported/comments" %} active{% endif %}" href="/admin/reported/comments">
|
||||
<div>Comments</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block fixedMobileBarJS %}
|
||||
<script>
|
||||
var prevScrollpos = window.pageYOffset;
|
||||
window.onscroll = function () {
|
||||
var currentScrollPos = window.pageYOffset;
|
||||
if (prevScrollpos > currentScrollPos) {
|
||||
document.getElementById("fixed-bar-mobile").style.top = "48px";
|
||||
document.getElementById("navbar").classList.remove("shadow");
|
||||
}
|
||||
else if (currentScrollPos <= 125) {
|
||||
document.getElementById("fixed-bar-mobile").style.top = "48px";
|
||||
document.getElementById("navbar").classList.remove("shadow");
|
||||
}
|
||||
else {
|
||||
document.getElementById("fixed-bar-mobile").style.top = "-48px";
|
||||
document.getElementById("dropdownMenuSortBy").classList.remove('show');
|
||||
document.getElementById("dropdownMenuFrom").classList.remove('show');
|
||||
document.getElementById("navbar").classList.add("shadow");
|
||||
}
|
||||
prevScrollpos = currentScrollPos;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
<title>Reported Posts</title>
|
||||
<meta name="description" content="on {{'SITE_NAME' | app_config}}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row no-gutters">
|
||||
|
||||
<div class="col">
|
||||
|
||||
{% block listing %}
|
||||
<div class="posts">
|
||||
{% include "submission_listing.html" %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagenav %}
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination pagination-sm py-3 pl-3 mb-0">
|
||||
{% if page>1 %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?page={{page-1}}" tabindex="-1">Prev</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Prev</span></li>
|
||||
{% endif %}
|
||||
{% if next_exists %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?page={{page+1}}">Next</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Next</span></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block pagetitle %}Edit {{'SITE_NAME' | app_config}} rules{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row my-5">
|
||||
<div class="col col-md-8">
|
||||
<div class="settings">
|
||||
<div id="description">
|
||||
<h2>Edit rules</h2>
|
||||
<p>Your rules page will be publicly visible at <a href="/rules">{{'/rules'|full_link}}</a>.</p>
|
||||
<p class="text-small text-muted">Supports <a href="https://www.markdownguide.org/basic-syntax">markdown syntax</a>.</p>
|
||||
</div>
|
||||
<div class="body d-lg-flex">
|
||||
<div class="w-lg-100">
|
||||
<form id="profile-settings" action="/admin/rules" method="post">
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
<textarea class="form-control rounded" id="bio-text" aria-label="With textarea" placeholder="Site rules" rows="50" name="rules" form="profile-settings">{% if rules %}{{ rules }}{% endif %}</textarea>
|
||||
|
||||
<div class="d-flex mt-2">
|
||||
<input class="btn btn-primary ml-auto" type="submit" value="Save">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block pagetitle %}Edit {{'SITE_NAME' | app_config}} rules{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row my-5">
|
||||
<div class="col col-md-8">
|
||||
<div class="settings">
|
||||
<div id="description">
|
||||
<h2>Edit rules</h2>
|
||||
<p>Your rules page will be publicly visible at <a href="/rules">{{'/rules'|full_link}}</a>.</p>
|
||||
<p class="text-small text-muted">Supports <a href="https://www.markdownguide.org/basic-syntax">markdown syntax</a>.</p>
|
||||
</div>
|
||||
<div class="body d-lg-flex">
|
||||
<div class="w-lg-100">
|
||||
<form id="profile-settings" action="/admin/rules" method="post">
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
<textarea class="form-control rounded" id="bio-text" aria-label="With textarea" placeholder="Site rules" rows="50" name="rules" form="profile-settings">{% if rules %}{{ rules }}{% endif %}</textarea>
|
||||
|
||||
<div class="d-flex mt-2">
|
||||
<input class="btn btn-primary ml-auto" type="submit" value="Save">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -1,71 +1,71 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>Grant User Award</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagetype %}message{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger alert-dismissible fade show my-3" role="alert">
|
||||
<i class="fas fa-exclamation-circle my-auto"></i>
|
||||
<span>
|
||||
{{error}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if msg %}
|
||||
<div class="alert alert-success alert-dismissible fade show my-3" role="alert">
|
||||
<i class="fas fa-check-circle my-auto" aria-hidden="true"></i>
|
||||
<span>
|
||||
{{msg}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<pre></pre>
|
||||
<pre></pre>
|
||||
<h5>User Award Grant</h5>
|
||||
|
||||
<form action="/admin/user_award", method="post">
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
|
||||
|
||||
<label for="input-username">Username</label><br>
|
||||
<input id="input-username" class="form-control mb-3" type="text" name="username" required>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th scope="col">Icon</th>
|
||||
<th scope="col">Title</th>
|
||||
<th scope="col">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for a in awards %}
|
||||
<tr>
|
||||
<td><i class="{{a['icon']}} {{a['color']}}" style="font-size: 30px"></i></td>
|
||||
<td style="font-weight: bold">{{a['title']}}</td>
|
||||
<td><input type="number" class="form-control" name="{{a['kind']}}" value="0" placeholder="enter amount" /></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<input class="btn btn-primary mt-3" type="submit" value="Grant Awards">
|
||||
|
||||
</form>
|
||||
|
||||
<pre></pre>
|
||||
{% if v.id in [1,12,28,29,747,995,1480] %}
|
||||
<div><a class="btn btn-success" href="javascript:void(0)" onclick="post_toast('/admin/monthly')">Grant Monthly Awards</a></div>
|
||||
{% endif %}
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>Grant User Award</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagetype %}message{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger alert-dismissible fade show my-3" role="alert">
|
||||
<i class="fas fa-exclamation-circle my-auto"></i>
|
||||
<span>
|
||||
{{error}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if msg %}
|
||||
<div class="alert alert-success alert-dismissible fade show my-3" role="alert">
|
||||
<i class="fas fa-check-circle my-auto" aria-hidden="true"></i>
|
||||
<span>
|
||||
{{msg}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<pre></pre>
|
||||
<pre></pre>
|
||||
<h5>User Award Grant</h5>
|
||||
|
||||
<form action="/admin/user_award", method="post">
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
|
||||
|
||||
<label for="input-username">Username</label><br>
|
||||
<input id="input-username" class="form-control mb-3" type="text" name="username" required>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th scope="col">Icon</th>
|
||||
<th scope="col">Title</th>
|
||||
<th scope="col">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for a in awards %}
|
||||
<tr>
|
||||
<td><i class="{{a['icon']}} {{a['color']}}" style="font-size: 30px"></i></td>
|
||||
<td style="font-weight: bold">{{a['title']}}</td>
|
||||
<td><input type="number" class="form-control" name="{{a['kind']}}" value="0" placeholder="enter amount" /></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<input class="btn btn-primary mt-3" type="submit" value="Grant Awards">
|
||||
|
||||
</form>
|
||||
|
||||
<pre></pre>
|
||||
{% if v.id in [1,12,28,29,747,995,1480] %}
|
||||
<div><a class="btn btn-success" href="javascript:void(0)" onclick="post_toast('/admin/monthly')">Grant Monthly Awards</a></div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,23 +1,23 @@
|
|||
{% extends "settings2.html" %}
|
||||
|
||||
{% block pagetitle %}Admins{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<pre class="d-none d-md-inline-block"></pre>
|
||||
<h5 style="font-weight:bold;">Admins</h5>
|
||||
<pre></pre>
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th style="font-weight:bold;">Name</th>
|
||||
<th style="font-weight:bold; text-align:right;">Coins</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in admins %}
|
||||
<tr>
|
||||
<td><a style="color:#{{user.namecolor}}; font-weight:bold;" href="/@{{user.username}}"><img loading="lazy" src="/uid/{{user.id}}/pic/profile" class="profile-pic-20 mr-1"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}};"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td style="font-weight:bold; text-align:right;">{{user.coins}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% extends "settings2.html" %}
|
||||
|
||||
{% block pagetitle %}Admins{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<pre class="d-none d-md-inline-block"></pre>
|
||||
<h5 style="font-weight:bold;">Admins</h5>
|
||||
<pre></pre>
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th style="font-weight:bold;">Name</th>
|
||||
<th style="font-weight:bold; text-align:right;">Coins</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in admins %}
|
||||
<tr>
|
||||
<td><a style="color:#{{user.namecolor}}; font-weight:bold;" href="/@{{user.username}}"><img loading="lazy" src="/uid/{{user.id}}/pic/profile" class="profile-pic-20 mr-1"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}};"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td style="font-weight:bold; text-align:right;">{{user.coins}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
|
@ -1,84 +1,82 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>{{'SITE_NAME' | app_config}} - API</title>
|
||||
<meta name="description" content="{{'SITE_NAME' | app_config}} API Guide">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<img class="in-comment-image rounded-sm my-2" data-src="https://media.giphy.com/media/c6Wwc9cT05vMdhyTcM/giphy.webp" loading="lazy" height="100px" rel="nofollow noopener noreferrer" data-placeholder-background="red" style="max-height: 100px; max-width: 100%;">
|
||||
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h1>API Guide for Bots</h1>
|
||||
<pre></pre>
|
||||
<p>This page explains how to obtain and use an access token. </p>
|
||||
<h2>Step 1: Create your Application</h2>
|
||||
<p>In the <a href="/settings/apps">apps tab of Drama settings</a>, fill in and submit the form to request an access token. You will need:</p>
|
||||
<ul>
|
||||
<li>an application name</li>
|
||||
<li>a Redirect URI. May not use HTTP unless using localhost (use HTTPS instead).</li>
|
||||
<li>a brief description of what your bot is intended to do</li>
|
||||
</ul>
|
||||
<p>Don't worry too much about accuracy; you will be able to change all of these later.</p>
|
||||
<p>Drama administrators will review and approve or deny your request for an access token. You'll know when your request has been approved when you get a private message with an access token tied to your account.</p>
|
||||
<p>DO NOT reveal your Client ID or Access Token. Anyone with these information will be able to pretend to be you. You are responsible for keeping them a secret!</p>
|
||||
<h2>Step 2: Using the Access Token</h2>
|
||||
<p>To use the access token, include the following header in subsequent API requests to Drama: <code>Authorization: access_token_goes_here</code></p>
|
||||
<p>Python example:</p>
|
||||
<pre> import requests
|
||||
|
||||
headers={"Authorization": "access_token_goes_here", "User-Agent": "sex"}
|
||||
|
||||
url="https://rdrama.net/@carpathianflorist"
|
||||
|
||||
r=requests.get(url, headers=headers)
|
||||
|
||||
print(r.json())
|
||||
</pre>
|
||||
<p>The expected result of this would be a large JSON representation of the posts posted by @carpathianflorist</p>
|
||||
<pre>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</pre>
|
||||
<h1>API Guide for Applications</h1>
|
||||
<pre></pre>
|
||||
<p>The OAuth2 authorization flow is used to enable users to authorize third-party applications to access their Drama account without having to provide their login information to the application.</p>
|
||||
<p>This page explains how to obtain API application keys, how to prompt a user for authorization, and how to obtain and use access tokens. </p>
|
||||
<h2>Step 1: Create your Application</h2>
|
||||
<p>In the <a href="/settings/apps">apps tab of Drama settings</a>, fill in and submit the form to request new API keys. You will need:</p>
|
||||
<ul>
|
||||
<li>an application name</li>
|
||||
<li>a Redirect URI. May not use HTTP unless using localhost (use HTTPS instead).</li>
|
||||
<li>a brief description of what your application is intended to do</li>
|
||||
</ul>
|
||||
<p>Don't worry too much about accuracy; you will be able to change all of these later.</p>
|
||||
<p>Drama administrators will review and approve or deny your request for API keys. You'll know when your request has been approved when you get a private message with an access token tied to your account.</p>
|
||||
<p>DO NOT reveal your Client ID or Access Token. Anyone with these information will be able to pretend to be you. You are responsible for keeping them a secret!</p>
|
||||
<h2>Step 2: Prompt Your User for Authorization</h2>
|
||||
<p>Send your user to <code>https://rdrama.net/authorize/?client_id=YOUR_CLIENT_ID</code></p>
|
||||
<p>If done correctly, the user will see that your application wants to access their Drama account, and be prompted to approve or deny the request.</p>
|
||||
<h2>Step 3: Catch the redirect</h2>
|
||||
<p>The user clicks "Authorize". Drama will redirect the user's browser to GET the designated redirect URI. The access token URL parameter will be included in the redirect, which your server should process.</p>
|
||||
<h2>Step 4: Using the Access Token</h2>
|
||||
<p>To use the access token, include the following header in subsequent API requests to Drama: <code>Authorization: access_token_goes_here</code></p>
|
||||
<p>Python example:</p>
|
||||
<pre> import requests
|
||||
|
||||
headers={"Authorization": "access_token_goes_here", "User-Agent": "sex"}
|
||||
|
||||
url="https://rdrama.net/@carpathianflorist"
|
||||
|
||||
r=requests.get(url, headers=headers)
|
||||
|
||||
print(r.json())
|
||||
</pre>
|
||||
<p>The expected result of this would be a large JSON representation of the submissions submitted by @carpathianflorist</p>
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>{{'SITE_NAME' | app_config}} - API</title>
|
||||
<meta name="description" content="{{'SITE_NAME' | app_config}} API Guide">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h1>API Guide for Bots</h1>
|
||||
<pre></pre>
|
||||
<p>This page explains how to obtain and use an access token. </p>
|
||||
<h2>Step 1: Create your Application</h2>
|
||||
<p>In the <a href="/settings/apps">apps tab of Drama settings</a>, fill in and submit the form to request an access token. You will need:</p>
|
||||
<ul>
|
||||
<li>an application name</li>
|
||||
<li>a Redirect URI. May not use HTTP unless using localhost (use HTTPS instead).</li>
|
||||
<li>a brief description of what your bot is intended to do</li>
|
||||
</ul>
|
||||
<p>Don't worry too much about accuracy; you will be able to change all of these later.</p>
|
||||
<p>Drama administrators will review and approve or deny your request for an access token. You'll know when your request has been approved when you get a private message with an access token tied to your account.</p>
|
||||
<p>DO NOT reveal your Client ID or Access Token. Anyone with these information will be able to pretend to be you. You are responsible for keeping them a secret!</p>
|
||||
<h2>Step 2: Using the Access Token</h2>
|
||||
<p>To use the access token, include the following header in subsequent API requests to Drama: <code>Authorization: access_token_goes_here</code></p>
|
||||
<p>Python example:</p>
|
||||
<pre> import requests
|
||||
|
||||
headers={"Authorization": "access_token_goes_here"}
|
||||
|
||||
url="https://rdrama.net/@carpathianflorist"
|
||||
|
||||
r=requests.get(url, headers=headers)
|
||||
|
||||
print(r.json())
|
||||
</pre>
|
||||
<p>The expected result of this would be a large JSON representation of the posts posted by @carpathianflorist</p>
|
||||
<pre>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</pre>
|
||||
<h1>API Guide for Applications</h1>
|
||||
<pre></pre>
|
||||
<p>The OAuth2 authorization flow is used to enable users to authorize third-party applications to access their Drama account without having to provide their login information to the application.</p>
|
||||
<p>This page explains how to obtain API application keys, how to prompt a user for authorization, and how to obtain and use access tokens. </p>
|
||||
<h2>Step 1: Create your Application</h2>
|
||||
<p>In the <a href="/settings/apps">apps tab of Drama settings</a>, fill in and submit the form to request new API keys. You will need:</p>
|
||||
<ul>
|
||||
<li>an application name</li>
|
||||
<li>a Redirect URI. May not use HTTP unless using localhost (use HTTPS instead).</li>
|
||||
<li>a brief description of what your application is intended to do</li>
|
||||
</ul>
|
||||
<p>Don't worry too much about accuracy; you will be able to change all of these later.</p>
|
||||
<p>Drama administrators will review and approve or deny your request for API keys. You'll know when your request has been approved when you get a private message with an access token tied to your account.</p>
|
||||
<p>DO NOT reveal your Client ID or Access Token. Anyone with these information will be able to pretend to be you. You are responsible for keeping them a secret!</p>
|
||||
<h2>Step 2: Prompt Your User for Authorization</h2>
|
||||
<p>Send your user to <code>https://rdrama.net/authorize/?client_id=YOUR_CLIENT_ID</code></p>
|
||||
<p>If done correctly, the user will see that your application wants to access their Drama account, and be prompted to approve or deny the request.</p>
|
||||
<h2>Step 3: Catch the redirect</h2>
|
||||
<p>The user clicks "Authorize". Drama will redirect the user's browser to GET the designated redirect URI. The access token URL parameter will be included in the redirect, which your server should process.</p>
|
||||
<h2>Step 4: Using the Access Token</h2>
|
||||
<p>To use the access token, include the following header in subsequent API requests to Drama: <code>Authorization: access_token_goes_here</code></p>
|
||||
<p>Python example:</p>
|
||||
<pre> import requests
|
||||
|
||||
headers={"Authorization": "access_token_goes_here"}
|
||||
|
||||
url="https://rdrama.net/@carpathianflorist"
|
||||
|
||||
r=requests.get(url, headers=headers)
|
||||
|
||||
print(r.json())
|
||||
</pre>
|
||||
<p>The expected result of this would be a large JSON representation of the submissions submitted by @carpathianflorist</p>
|
||||
{% endblock %}
|
|
@ -1,102 +1,104 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="{% block pagedesc %}{{'SITE_NAME' | app_config}}{% endblock %}">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>{% block pagetitle %}{{'SITE_NAME' | app_config}}{% endblock %}</title>
|
||||
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600&display=swap" rel="stylesheet">
|
||||
|
||||
|
||||
{% if v %}
|
||||
<link rel="stylesheet" href="/assets/css/{{v.theme}}_{{v.themecolor}}.css?v=61">
|
||||
{% if v.agendaposter %}<link rel="stylesheet" href="/assets/css/agendaposter.css?v=61">{% endif %}
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="/assets/css/{{'DEFAULT_THEME' | app_config}}.css?v=61">
|
||||
{% endif %}
|
||||
|
||||
</head>
|
||||
|
||||
<body id="login">
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-transparent fixed-top border-0">
|
||||
<div class="container-fluid">
|
||||
<button class="navbar-toggler d-none" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive"
|
||||
aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
<div class="container-fluid position-absolute h-100 p-0">
|
||||
<div class="row no-gutters h-100">
|
||||
|
||||
<div class="col-12 col-md-6 my-auto p-3">
|
||||
|
||||
<div class="row justify-content-center">
|
||||
|
||||
<div class="col-10 col-md-7">
|
||||
|
||||
<div class="mb-5">
|
||||
<a href="/" class="text-decoration-none"><span class="h3 text-primary">{{'SITE_NAME' | app_config}}</span></a>
|
||||
</div>
|
||||
|
||||
<h1 class="h2">{% block authtitle %}{% endblock %}</h1>
|
||||
|
||||
<p class="text-muted mb-md-5">{% block authtext %}{% endblock %}</p>
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger alert-dismissible fade show d-flex my-3" role="alert">
|
||||
<i class="fas fa-exclamation-circle my-auto"></i>
|
||||
<span>
|
||||
{{error}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if msg %}
|
||||
<div class="alert alert-success alert-dismissible fade show d-flex my-3" role="alert">
|
||||
<i class="fas fa-info-circle my-auto" aria-hidden="true"></i>
|
||||
<span>
|
||||
{{msg}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6 d-none d-md-block">
|
||||
|
||||
<div class="splash-wrapper">
|
||||
|
||||
<div class="splash-overlay"></div>
|
||||
|
||||
<img loading="lazy" class="splash-img" src="/assets/images/{{'SITE_NAME' | app_config}}/cover.webp"></img>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="{% block pagedesc %}{{'SITE_NAME' | app_config}}{% endblock %}">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>{% block pagetitle %}{{'SITE_NAME' | app_config}}{% endblock %}</title>
|
||||
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600&display=swap" rel="stylesheet">
|
||||
|
||||
|
||||
{% if v %}
|
||||
<style>:root{--primary:#{{v.themecolor}}}</style>
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=80"><link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=80">
|
||||
{% if v.agendaposter %}<link rel="stylesheet" href="/assets/css/agendaposter.css?v=80">{% elif v.css %}<link rel="stylesheet" href="/@{{v.username}}/css">{% endif %}
|
||||
{% else %}
|
||||
<style>:root{--primary:#{{'DEFAULT_COLOR' | app_config}}</style>
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=80"><link rel="stylesheet" href="/assets/css/{{'DEFAULT_THEME' | app_config}}.css?v=80">
|
||||
{% endif %}
|
||||
|
||||
</head>
|
||||
|
||||
<body id="login">
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-transparent fixed-top border-0">
|
||||
<div class="container-fluid">
|
||||
<button class="navbar-toggler d-none" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive"
|
||||
aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
<div class="container-fluid position-absolute h-100 p-0">
|
||||
<div class="row no-gutters h-100">
|
||||
|
||||
<div class="col-12 col-md-6 my-auto p-3">
|
||||
|
||||
<div class="row justify-content-center">
|
||||
|
||||
<div class="col-10 col-md-7">
|
||||
|
||||
<div class="mb-5">
|
||||
<a href="/" class="text-decoration-none"><span class="h3 text-primary">{{'SITE_NAME' | app_config}}</span></a>
|
||||
</div>
|
||||
|
||||
<h1 class="h2">{% block authtitle %}{% endblock %}</h1>
|
||||
|
||||
<p class="text-muted mb-md-5">{% block authtext %}{% endblock %}</p>
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-danger alert-dismissible fade show d-flex my-3" role="alert">
|
||||
<i class="fas fa-exclamation-circle my-auto"></i>
|
||||
<span>
|
||||
{{error}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if msg %}
|
||||
<div class="alert alert-success alert-dismissible fade show d-flex my-3" role="alert">
|
||||
<i class="fas fa-info-circle my-auto" aria-hidden="true"></i>
|
||||
<span>
|
||||
{{msg}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6 d-none d-md-block">
|
||||
|
||||
<div class="splash-wrapper">
|
||||
|
||||
<div class="splash-overlay"></div>
|
||||
|
||||
<img loading="lazy" class="splash-img" src="/assets/images/{{'SITE_NAME' | app_config}}/cover.gif"></img>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,70 +1,70 @@
|
|||
<script src="/assets/js/award_modal.js?v=50"></script>
|
||||
|
||||
<div class="modal fade" id="awardModal" tabindex="-1" role="dialog" aria-labelledby="awardModalTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Give Award</h5>
|
||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="awardModalBody" class="modal-body">
|
||||
<form id="awardTarget" class="pt-3 pb-0" action="" method="post">
|
||||
<div class="card-columns award-columns awards-wrapper">
|
||||
{% for award in v.user_awards %}
|
||||
{% if award.owned %}
|
||||
<a href="javascript:void(0)" id="{{award.kind}}" class="card" onclick="bruh('{{award.kind}}')">
|
||||
{% else %}
|
||||
<a href="javascript:void(0)" id="{{award.kind}}" class="card disabled">
|
||||
{% endif %}
|
||||
<i class="{{award.icon}} {{award.color}}"></i>
|
||||
<div class="pt-2" style="font-weight: bold; font-size: 14px; color:#E1E1E1">{{award.title}}</div>
|
||||
<div class="text-muted">{{award.owned}} owned</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<label for="note" class="pt-4">Note (optional):</label>
|
||||
<input id="kind" name="kind" value="" hidden>
|
||||
<textarea id="note" name="note" class="form-control" placeholder="Note to include in award notification"></textarea>
|
||||
<input id="giveaward" class="btn btn-primary" style="float:right" type="submit" value="Give Award" disabled>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.awards-wrapper input[type="radio"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.awards-wrapper a {
|
||||
cursor: pointer;
|
||||
padding: 15px !important;
|
||||
text-align: center;
|
||||
text-transform: none!important;
|
||||
}
|
||||
|
||||
.awards-wrapper a i {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.awards-wrapper a.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.awards-wrapper a:hover, .picked {
|
||||
background-color: var(--primary)!important;
|
||||
}
|
||||
|
||||
.awards-wrapper input[type="radio"]:checked+a {
|
||||
background-color: var(--primary)!important;
|
||||
}
|
||||
|
||||
@media (min-width: 767.98px) {
|
||||
.award-columns {
|
||||
column-count: 3 !important;
|
||||
}
|
||||
}
|
||||
<script src="/assets/js/award_modal.js?v=53"></script>
|
||||
|
||||
<div class="modal fade" id="awardModal" tabindex="-1" role="dialog" aria-labelledby="awardModalTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Give Award</h5>
|
||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="awardModalBody" class="modal-body">
|
||||
<form id="awardTarget" class="pt-3 pb-0" action="" method="post">
|
||||
<div class="card-columns award-columns awards-wrapper">
|
||||
{% for award in v.user_awards %}
|
||||
{% if award.owned %}
|
||||
<a href="javascript:void(0)" id="{{award.kind}}" class="card" onclick="bruh('{{award.kind}}')">
|
||||
{% else %}
|
||||
<a href="javascript:void(0)" id="{{award.kind}}" class="card disabled">
|
||||
{% endif %}
|
||||
<i class="{{award.icon}} {{award.color}}"></i>
|
||||
<div class="pt-2" style="font-weight: bold; font-size: 14px; color:#E1E1E1">{{award.title}}</div>
|
||||
<div class="text-muted">{{award.owned}} owned</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<label for="note" class="pt-4">Note (optional):</label>
|
||||
<input id="kind" name="kind" value="" hidden>
|
||||
<textarea id="note" name="note" class="form-control" placeholder="Note to include in award notification"></textarea>
|
||||
<input id="giveaward" class="btn btn-primary" style="float:right" type="submit" value="Give Award" disabled>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.awards-wrapper input[type="radio"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.awards-wrapper a {
|
||||
cursor: pointer;
|
||||
padding: 15px !important;
|
||||
text-align: center;
|
||||
text-transform: none!important;
|
||||
}
|
||||
|
||||
.awards-wrapper a i {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.awards-wrapper a.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.awards-wrapper a:hover, .picked {
|
||||
background-color: var(--primary)!important;
|
||||
}
|
||||
|
||||
.awards-wrapper input[type="radio"]:checked+a {
|
||||
background-color: var(--primary)!important;
|
||||
}
|
||||
|
||||
@media (min-width: 767.98px) {
|
||||
.award-columns {
|
||||
column-count: 3 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,72 +1,72 @@
|
|||
{% extends "default.html" %}
|
||||
{% block content %}
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h1>User Badges</h1>
|
||||
<div>This page describes the requirements for obtaining all profile badges.</div>
|
||||
<div>Badges are sorted into bronze, silver, gold, and diamond tiers, based on the relative difficulty of obtaining them.</div>
|
||||
|
||||
<h2 class="mt-3">Unlockable Badges</h2>
|
||||
<div>These badges are automatically granted through different kinds of activity on {{'SITE_NAME' | app_config}}.</div>
|
||||
<pre></pre>
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Image</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for badge in badges if badge.kind==1 %}
|
||||
<tr>
|
||||
<td>{{badge.name}}</td>
|
||||
<td><img loading="lazy" src="{{badge.path}}" style="width:50px;height:50px">
|
||||
<td>{{badge.description}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
<h2 class="mt-3">Granted Badges</h2>
|
||||
<div>These badges can be granted by admins.</div>
|
||||
<pre></pre>
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Image</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for badge in badges if badge.kind==3 %}
|
||||
<tr>
|
||||
<td>{{badge.name}}</td>
|
||||
<td><img loading="lazy" src="{{badge.path}}" style="width:50px;height:50px">
|
||||
<td>{{badge.description}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<h2 class="mt-3">Unobtainable Badges</h2>
|
||||
<div>There is no way to acquire these badges if you don't already have them.</div>
|
||||
<pre></pre>
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Image</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for badge in badges if badge.kind==4 %}
|
||||
<tr>
|
||||
<td>{{badge.name}}</td>
|
||||
<td><img loading="lazy" src="{{badge.path}}" style="width:50px;height:50px">
|
||||
<td>{{badge.description}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% extends "default.html" %}
|
||||
{% block content %}
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
<h1>User Badges</h1>
|
||||
<div>This page describes the requirements for obtaining all profile badges.</div>
|
||||
<div>Badges are sorted into bronze, silver, gold, and diamond tiers, based on the relative difficulty of obtaining them.</div>
|
||||
|
||||
<h2 class="mt-3">Unlockable Badges</h2>
|
||||
<div>These badges are automatically granted through different kinds of activity on {{'SITE_NAME' | app_config}}.</div>
|
||||
<pre></pre>
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Image</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for badge in badges if badge.kind==1 %}
|
||||
<tr>
|
||||
<td>{{badge.name}}</td>
|
||||
<td><img loading="lazy" src="{{badge.path}}" width=50 height=50>
|
||||
<td>{{badge.description}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
<h2 class="mt-3">Granted Badges</h2>
|
||||
<div>These badges can be granted by admins.</div>
|
||||
<pre></pre>
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Image</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for badge in badges if badge.kind==3 %}
|
||||
<tr>
|
||||
<td>{{badge.name}}</td>
|
||||
<td><img loading="lazy" src="{{badge.path}}" width=50 height=50>
|
||||
<td>{{badge.description}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<h2 class="mt-3">Unobtainable Badges</h2>
|
||||
<div>There is no way to acquire these badges if you don't already have them.</div>
|
||||
<pre></pre>
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Image</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for badge in badges if badge.kind==4 %}
|
||||
<tr>
|
||||
<td>{{badge.name}}</td>
|
||||
<td><img loading="lazy" src="{{badge.path}}" width=50 height=50>
|
||||
<td>{{badge.description}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
|
@ -1,61 +1,61 @@
|
|||
|
||||
<script>
|
||||
const banModal = function(link, id, name) {
|
||||
document.getElementById("banModalTitle").innerHTML = `Ban @${name}`;
|
||||
document.getElementById("ban-modal-link").value = link;
|
||||
document.getElementById("banUserButton").innerHTML = `Ban @${name}`;
|
||||
|
||||
document.getElementById("banUserButton").onclick = function() {
|
||||
let fd = new FormData(document.getElementById("banModalForm"));
|
||||
fd.append("formkey", formkey());
|
||||
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", `/ban_user/${id}?form`, true);
|
||||
xhr.withCredentials = true;
|
||||
|
||||
xhr.onload = function(){
|
||||
var myToast = new bootstrap.Toast(document.getElementById('toast-post-success'));
|
||||
myToast.show();
|
||||
document.getElementById('toast-post-success-text').innerHTML = `@${name} banned`;
|
||||
}
|
||||
|
||||
xhr.send(fd);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="modal fade" id="banModal" tabindex="-1" role="dialog" aria-labelledby="banModalTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header pt-3">
|
||||
<h5 id="banModalTitle"></h5>
|
||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" id="ban-modal-body">
|
||||
|
||||
<form id="banModalForm">
|
||||
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}" />
|
||||
|
||||
<label for="ban-modal-link">Public ban reason (optional)</label>
|
||||
<textarea name="reason" form="banModalForm" class="form-control" id="ban-modal-link" aria-label="With textarea" placeholder="Enter reason"></textarea>
|
||||
|
||||
<label for="days" class="mt-3">Duration days</label>
|
||||
<input type="number" step="any" name="days" id="days" class="form-control" placeholder="leave blank for permanent" />
|
||||
|
||||
<div class="custom-control custom-switch mt-3">
|
||||
<input type="checkbox" class="custom-control-input" id="alts" name="alts">
|
||||
<label class="custom-control-label" for="alts">Ban known alts</label>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-link text-muted" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" id="banUserButton" class="btn btn-danger" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const banModal = function(link, id, name) {
|
||||
document.getElementById("banModalTitle").innerHTML = `Ban @${name}`;
|
||||
document.getElementById("ban-modal-link").value = link;
|
||||
document.getElementById("banUserButton").innerHTML = `Ban @${name}`;
|
||||
|
||||
document.getElementById("banUserButton").onclick = function() {
|
||||
let fd = new FormData(document.getElementById("banModalForm"));
|
||||
fd.append("formkey", formkey());
|
||||
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", `/ban_user/${id}?form`, true);
|
||||
xhr.withCredentials = true;
|
||||
|
||||
xhr.onload = function(){
|
||||
var myToast = new bootstrap.Toast(document.getElementById('toast-post-success'));
|
||||
myToast.show();
|
||||
document.getElementById('toast-post-success-text').innerHTML = `@${name} banned`;
|
||||
}
|
||||
|
||||
xhr.send(fd);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="modal fade" id="banModal" tabindex="-1" role="dialog" aria-labelledby="banModalTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header pt-3">
|
||||
<h5 id="banModalTitle"></h5>
|
||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" id="ban-modal-body">
|
||||
|
||||
<form id="banModalForm">
|
||||
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}" />
|
||||
|
||||
<label for="ban-modal-link">Public ban reason (optional)</label>
|
||||
<textarea name="reason" form="banModalForm" class="form-control" id="ban-modal-link" aria-label="With textarea" placeholder="Enter reason"></textarea>
|
||||
|
||||
<label for="days" class="mt-3">Duration days</label>
|
||||
<input type="number" step="any" name="days" id="days" class="form-control" placeholder="leave blank for permanent" />
|
||||
|
||||
<div class="custom-control custom-switch mt-3">
|
||||
<input type="checkbox" class="custom-control-input" id="alts" name="alts">
|
||||
<label class="custom-control-label" for="alts">Ban known alts</label>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-link text-muted" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" id="banUserButton" class="btn btn-danger" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,23 +1,23 @@
|
|||
{% extends "settings2.html" %}
|
||||
|
||||
{% block content %}
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th style="font-weight:bold;">#</th>
|
||||
<th style="font-weight:bold;">Name</th>
|
||||
<th style="font-weight:bold;">Ban reason</th>
|
||||
<th style="font-weight:bold;">Banned by</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td style="font-weight:bold;">{{users.index(user)+1}}</td>
|
||||
<td><a style="color:#{{user.namecolor}}; font-weight:bold;" href="/@{{user.username}}"><img loading="lazy" src="/uid/{{user.id}}/pic/profile" class="profile-pic-20 mr-1"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}};"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td style="font-weight:bold;">{% if user.ban_reason %}{{user.ban_reason}}{% endif %}</td>
|
||||
<td style="font-weight:bold;" href="/@{{user.banned_by.username}}"><img loading="lazy" src="/uid/{{user.banned_by.id}}/pic/profile" class="profile-pic-20 mr-1"><span {% if user.banned_by.patron %}class="patron" style="background-color:#{{user.banned_by.namecolor}};"{% endif %}>{{user.banned_by.username}}</span></a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
{% extends "settings2.html" %}
|
||||
|
||||
{% block content %}
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th style="font-weight:bold;">#</th>
|
||||
<th style="font-weight:bold;">Name</th>
|
||||
<th style="font-weight:bold;">Ban reason</th>
|
||||
<th style="font-weight:bold;">Banned by</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td style="font-weight:bold;">{{users.index(user)+1}}</td>
|
||||
<td><a style="color:#{{user.namecolor}}; font-weight:bold;" href="/@{{user.username}}"><img loading="lazy" src="/uid/{{user.id}}/pic/profile" class="profile-pic-20 mr-1"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}};"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td style="font-weight:bold;">{% if user.ban_reason %}{{user.ban_reason}}{% endif %}</td>
|
||||
<td style="font-weight:bold;" href="/@{{user.banned_by.username}}"><img loading="lazy" src="/uid/{{user.banned_by.id}}/pic/profile" class="profile-pic-20 mr-1"><span {% if user.banned_by.patron %}class="patron" style="background-color:#{{user.banned_by.namecolor}};"{% endif %}>{{user.banned_by.username}}</span></a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
{% extends "settings2.html" %}
|
||||
|
||||
{% block pagetitle %}Blocks{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1> Blocks</h1>
|
||||
<pre></pre>
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th style="font-weight:bold;">User</th>
|
||||
<th style="font-weight:bold;">Target</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td><a style="font-weight:bold;color:#{{user.namecolor}}; " href="/@{{user.username}}"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}};"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td><a style="font-weight:bold;color:#{{targets[loop.index-1].namecolor}}; " href="/@{{targets[loop.index-1].username}}"><span {% if targets[loop.index-1].patron %}class="patron" style="background-color:#{{targets[loop.index-1].namecolor}};"{% endif %}>{{targets[loop.index-1].username}}</span></a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% extends "settings2.html" %}
|
||||
|
||||
{% block pagetitle %}Blocks{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1> Blocks</h1>
|
||||
<pre></pre>
|
||||
<table class="table table-striped mb-5">
|
||||
<thead class="bg-primary text-white">
|
||||
<tr>
|
||||
<th style="font-weight:bold;">User</th>
|
||||
<th style="font-weight:bold;">Target</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td><a style="font-weight:bold;color:#{{user.namecolor}}; " href="/@{{user.username}}"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}};"{% endif %}>{{user.username}}</span></a></td>
|
||||
<td><a style="font-weight:bold;color:#{{targets[loop.index-1].namecolor}}; " href="/@{{targets[loop.index-1].username}}"><span {% if targets[loop.index-1].patron %}class="patron" style="background-color:#{{targets[loop.index-1].namecolor}};"{% endif %}>{{targets[loop.index-1].username}}</span></a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,108 +1,108 @@
|
|||
{% extends "settings2.html" %}
|
||||
|
||||
{% block pagetitle %}Changelog{% endblock %}
|
||||
|
||||
{% block desktopBanner %}
|
||||
<script src="/assets/js/changelog.js?v=50"></script>
|
||||
|
||||
<div class="row" style="overflow: visible;padding-top:5px;">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
|
||||
{% block navbar %}
|
||||
<div class="font-weight-bold py-3"></div>
|
||||
|
||||
<div class="d-flex align-items-center sortingbarmargin">
|
||||
<div class="text-small font-weight-bold mr-2"></div>
|
||||
<div class="dropdown dropdown-actions">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{% if t=="day" %}<i class="fas fa-calendar-day mr-1"></i>{% endif %}
|
||||
{% if t=="week" %}<i class="fas fa-calendar-week mr-1"></i>{% endif %}
|
||||
{% if t=="month" %}<i class="fas fa-calendar-alt mr-1"></i>{% endif %}
|
||||
{% if t=="year" %}<i class="fas fa-calendar mr-1"></i>{% endif %}
|
||||
{% if t=="all" %}<i class="fas fa-infinity mr-1"></i>{% endif %}
|
||||
{{t | capitalize}}
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 31px, 0px);">
|
||||
{% if not t=="hour" %}<a class="dropdown-item" href="?sort={{sort}}&t=hour"><i class="fas fa-clock mr-2"></i>Hour</a>{% endif %}
|
||||
{% if not t=="day" %}<a class="dropdown-item" href="?sort={{sort}}&t=day"><i class="fas fa-calendar-day mr-2"></i>Day</a>{% endif %}
|
||||
{% if not t=="week" %}<a class="dropdown-item" href="?sort={{sort}}&t=week"><i class="fas fa-calendar-week mr-2"></i>Week</a>{% endif %}
|
||||
{% if not t=="month" %}<a class="dropdown-item" href="?sort={{sort}}&t=month"><i class="fas fa-calendar-alt mr-2"></i>Month</a>{% endif %}
|
||||
{% if not t=="year" %}<a class="dropdown-item" href="?sort={{sort}}&t=year"><i class="fas fa-calendar mr-2"></i>Year</a>{% endif %}
|
||||
{% if not t=="all" %}<a class="dropdown-item" href="?sort={{sort}}&t=all"><i class="fas fa-infinity mr-2"></i>All</a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-small font-weight-bold ml-3 mr-2"></div>
|
||||
<div class="dropdown dropdown-actions">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{% if sort=="hot" %}<i class="fas fa-fire mr-1"></i>{% endif %}
|
||||
{% if sort=="top" %}<i class="fas fa-arrow-alt-circle-up mr-1"></i>{% endif %}
|
||||
{% if sort=="bottom" %}<i class="fas fa-arrow-alt-circle-down mr-1"></i>{% endif %}
|
||||
{% if sort=="new" %}<i class="fas fa-sparkles mr-1"></i>{% endif %}
|
||||
{% if sort=="old" %}<i class="fas fa-book mr-1"></i>{% endif %}
|
||||
{% if sort=="controversial" %}<i class="fas fa-bullhorn mr-1"></i>{% endif %}
|
||||
{% if sort=="comments" %}<i class="fas fa-comments mr-1"></i>{% endif %}
|
||||
{{sort | capitalize}}
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 31px, 0px);">
|
||||
{% if sort != "hot" %}<a class="dropdown-item" href="?sort=hot&t={{t}}"><i class="fas fa-fire mr-2"></i>Hot</a>{% endif %}
|
||||
{% if sort != "top" %}<a class="dropdown-item" href="?sort=top&t={{t}}"><i class="fas fa-arrow-alt-circle-up mr-2"></i>Top</a>{% endif %}
|
||||
{% if sort != "bottom" %}<a class="dropdown-item" href="?sort=bottom&t={{t}}"><i class="fas fa-arrow-alt-circle-down mr-2"></i>Bottom</a>{% endif %}
|
||||
{% if sort != "new" %}<a class="dropdown-item" href="?sort=new&t={{t}}"><i class="fas fa-sparkles mr-2"></i>New</a>{% endif %}
|
||||
{% if sort != "old" %}<a class="dropdown-item" href="?sort=old&t={{t}}"><i class="fas fa-book mr-2"></i>Old</a>{% endif %}
|
||||
{% if sort != "controversial" %}<a class="dropdown-item" href="?sort=controversial&t={{t}}"><i class="fas fa-bullhorn mr-2"></i>Controversial</a>{% endif %}
|
||||
{% if sort != "comments" %}<a class="dropdown-item" href="?sort=comments&t={{t}}"><i class="fas fa-comments mr-2"></i>Comments</a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if v %}
|
||||
<a id="subscribe" class="{% if v.changelogsub %}d-none{% endif %} btn btn-primary followbutton " href="javascript:void(0)" onclick="post_toast2('/changelogsub','subscribe','unsubscribe')">Subscribe</a>
|
||||
<a id="unsubscribe" class="{% if not v.changelogsub %}d-none{% endif %} btn btn-primary followbutton " href="javascript:void(0)" onclick="post_toast2('/changelogsub','subscribe','unsubscribe')">Unsubscribe</a>
|
||||
{% endif %}
|
||||
|
||||
<div class="row no-gutters {% if listing %}mt-md-3{% elif not listing %}my-md-3{% endif %}">
|
||||
|
||||
<div class="col-12">
|
||||
|
||||
<div class="posts" id="posts">
|
||||
|
||||
{% include "submission_listing.html" %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block pagenav %}
|
||||
{% if listing %}
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination pagination-sm mb-0">
|
||||
{% if page>1 %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?sort={{sort}}&page={{page-1}}&t={{t}}{% if only %}&only={{only}}{% endif %}" tabindex="-1">Prev</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Prev</span></li>
|
||||
{% endif %}
|
||||
{% if next_exists %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?sort={{sort}}&page={{page+1}}&t={{t}}{% if only %}&only={{only}}{% endif %}">Next</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Next</span></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% extends "settings2.html" %}
|
||||
|
||||
{% block pagetitle %}Changelog{% endblock %}
|
||||
|
||||
{% block desktopBanner %}
|
||||
<script src="/assets/js/changelog.js?v=54"></script>
|
||||
|
||||
<div class="row" style="overflow: visible;padding-top:5px;">
|
||||
<div class="col">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
|
||||
{% block navbar %}
|
||||
<div class="font-weight-bold py-3"></div>
|
||||
|
||||
<div class="d-flex align-items-center sortingbarmargin">
|
||||
<div class="text-small font-weight-bold mr-2"></div>
|
||||
<div class="dropdown dropdown-actions">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{% if t=="day" %}<i class="fas fa-calendar-day mr-1"></i>{% endif %}
|
||||
{% if t=="week" %}<i class="fas fa-calendar-week mr-1"></i>{% endif %}
|
||||
{% if t=="month" %}<i class="fas fa-calendar-alt mr-1"></i>{% endif %}
|
||||
{% if t=="year" %}<i class="fas fa-calendar mr-1"></i>{% endif %}
|
||||
{% if t=="all" %}<i class="fas fa-infinity mr-1"></i>{% endif %}
|
||||
{{t | capitalize}}
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 31px, 0px);">
|
||||
{% if not t=="hour" %}<a class="dropdown-item" href="?sort={{sort}}&t=hour"><i class="fas fa-clock mr-2"></i>Hour</a>{% endif %}
|
||||
{% if not t=="day" %}<a class="dropdown-item" href="?sort={{sort}}&t=day"><i class="fas fa-calendar-day mr-2"></i>Day</a>{% endif %}
|
||||
{% if not t=="week" %}<a class="dropdown-item" href="?sort={{sort}}&t=week"><i class="fas fa-calendar-week mr-2"></i>Week</a>{% endif %}
|
||||
{% if not t=="month" %}<a class="dropdown-item" href="?sort={{sort}}&t=month"><i class="fas fa-calendar-alt mr-2"></i>Month</a>{% endif %}
|
||||
{% if not t=="year" %}<a class="dropdown-item" href="?sort={{sort}}&t=year"><i class="fas fa-calendar mr-2"></i>Year</a>{% endif %}
|
||||
{% if not t=="all" %}<a class="dropdown-item" href="?sort={{sort}}&t=all"><i class="fas fa-infinity mr-2"></i>All</a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-small font-weight-bold ml-3 mr-2"></div>
|
||||
<div class="dropdown dropdown-actions">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{% if sort=="hot" %}<i class="fas fa-fire mr-1"></i>{% endif %}
|
||||
{% if sort=="top" %}<i class="fas fa-arrow-alt-circle-up mr-1"></i>{% endif %}
|
||||
{% if sort=="bottom" %}<i class="fas fa-arrow-alt-circle-down mr-1"></i>{% endif %}
|
||||
{% if sort=="new" %}<i class="fas fa-sparkles mr-1"></i>{% endif %}
|
||||
{% if sort=="old" %}<i class="fas fa-book mr-1"></i>{% endif %}
|
||||
{% if sort=="controversial" %}<i class="fas fa-bullhorn mr-1"></i>{% endif %}
|
||||
{% if sort=="comments" %}<i class="fas fa-comments mr-1"></i>{% endif %}
|
||||
{{sort | capitalize}}
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 31px, 0px);">
|
||||
{% if sort != "hot" %}<a class="dropdown-item" href="?sort=hot&t={{t}}"><i class="fas fa-fire mr-2"></i>Hot</a>{% endif %}
|
||||
{% if sort != "top" %}<a class="dropdown-item" href="?sort=top&t={{t}}"><i class="fas fa-arrow-alt-circle-up mr-2"></i>Top</a>{% endif %}
|
||||
{% if sort != "bottom" %}<a class="dropdown-item" href="?sort=bottom&t={{t}}"><i class="fas fa-arrow-alt-circle-down mr-2"></i>Bottom</a>{% endif %}
|
||||
{% if sort != "new" %}<a class="dropdown-item" href="?sort=new&t={{t}}"><i class="fas fa-sparkles mr-2"></i>New</a>{% endif %}
|
||||
{% if sort != "old" %}<a class="dropdown-item" href="?sort=old&t={{t}}"><i class="fas fa-book mr-2"></i>Old</a>{% endif %}
|
||||
{% if sort != "controversial" %}<a class="dropdown-item" href="?sort=controversial&t={{t}}"><i class="fas fa-bullhorn mr-2"></i>Controversial</a>{% endif %}
|
||||
{% if sort != "comments" %}<a class="dropdown-item" href="?sort=comments&t={{t}}"><i class="fas fa-comments mr-2"></i>Comments</a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if v %}
|
||||
<a id="subscribe" class="{% if v.changelogsub %}d-none{% endif %} btn btn-primary followbutton " href="javascript:void(0)" onclick="post_toast2('/changelogsub','subscribe','unsubscribe')">Subscribe</a>
|
||||
<a id="unsubscribe" class="{% if not v.changelogsub %}d-none{% endif %} btn btn-primary followbutton " href="javascript:void(0)" onclick="post_toast2('/changelogsub','subscribe','unsubscribe')">Unsubscribe</a>
|
||||
{% endif %}
|
||||
|
||||
<div class="row no-gutters {% if listing %}mt-md-3{% elif not listing %}my-md-3{% endif %}">
|
||||
|
||||
<div class="col-12">
|
||||
|
||||
<div class="posts" id="posts">
|
||||
|
||||
{% include "submission_listing.html" %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block pagenav %}
|
||||
{% if listing %}
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination pagination-sm mb-0">
|
||||
{% if page>1 %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?sort={{sort}}&page={{page-1}}&t={{t}}{% if only %}&only={{only}}{% endif %}" tabindex="-1">Prev</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Prev</span></li>
|
||||
{% endif %}
|
||||
{% if next_exists %}
|
||||
<li class="page-item">
|
||||
<small><a class="page-link" href="?sort={{sort}}&page={{page+1}}&t={{t}}{% if only %}&only={{only}}{% endif %}">Next</a></small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><span class="page-link">Next</span></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,43 +1,43 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>Unable to post comment</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagetype %}message{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="">
|
||||
|
||||
<p>Please remove the following link(s) from your comment, and then you will be able to post it:</p>
|
||||
|
||||
<ul>
|
||||
{% for site in badlinks %}
|
||||
<li>{{site}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div>
|
||||
<div class="comment-write collapsed child p-4">
|
||||
<form id="reply" action="{{action}}" method="post" class="input-group">
|
||||
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
|
||||
{% if parent_fullname %}<input type="hidden" name="parent_fullname" value="{{parent_fullname}}">{% endif %}
|
||||
{% if parent_submission %}<input type="hidden" name="submission" value="{{parent_submission}}">{% endif %}
|
||||
|
||||
<textarea name="body" form="reply" class="comment-box form-control rounded" id="reply-form" aria-label="With textarea" placeholder="Add your comment..." rows="10">{{body}}</textarea>
|
||||
|
||||
<div class="comment-format">
|
||||
<small class="format pl-0"><i class="fas fa-bold" aria-hidden="true" onclick="makeReplyBold()" data-bs-toggle="tooltip" data-bs-placement="bottom" title="" data-bs-original-title="Bold"></i></small>
|
||||
<small class="format"><i class="fas fa-italic" aria-hidden="true" onclick="makeReplyItalics()" data-bs-toggle="tooltip" data-bs-placement="bottom" title="" data-bs-original-title="Italicize"></i></small>
|
||||
<small class="format"><i class="fas fa-quote-right" aria-hidden="true" onclick="makeReplyQuote()" data-bs-toggle="tooltip" data-bs-placement="bottom" title="" data-bs-original-title="Quote"></i></small>
|
||||
<small class="format"><i class="fas fa-link" aria-hidden="true"></i></small>
|
||||
<button form="reply" class="btn btn-primary ml-auto">Comment</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>Unable to post comment</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagetype %}message{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="">
|
||||
|
||||
<p>Please remove the following link(s) from your comment, and then you will be able to post it:</p>
|
||||
|
||||
<ul>
|
||||
{% for site in badlinks %}
|
||||
<li>{{site}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div>
|
||||
<div class="comment-write collapsed child p-4">
|
||||
<form id="reply" action="{{action}}" method="post" class="input-group">
|
||||
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
|
||||
{% if parent_fullname %}<input type="hidden" name="parent_fullname" value="{{parent_fullname}}">{% endif %}
|
||||
{% if parent_submission %}<input type="hidden" name="submission" value="{{parent_submission}}">{% endif %}
|
||||
|
||||
<textarea name="body" form="reply" class="comment-box form-control rounded" id="reply-form" aria-label="With textarea" placeholder="Add your comment..." rows="10">{{body}}</textarea>
|
||||
|
||||
<div class="comment-format">
|
||||
<small class="format pl-0"><i class="fas fa-bold" aria-hidden="true" onclick="makeReplyBold()" data-bs-toggle="tooltip" data-bs-placement="bottom" title="" data-bs-original-title="Bold"></i></small>
|
||||
<a class="format" href="javscript:void(0)"><i class="fas fa-italic" aria-hidden="true" onclick="makeReplyItalics()" data-bs-toggle="tooltip" data-bs-placement="bottom" title="" data-bs-original-title="Italicize"></i></a>
|
||||
<a class="format" href="javscript:void(0)"><i class="fas fa-quote-right" aria-hidden="true" onclick="makeReplyQuote()" data-bs-toggle="tooltip" data-bs-placement="bottom" title="" data-bs-original-title="Quote"></i></a>
|
||||
<a class="format" href="javscript:void(0)"><i class="fas fa-link" aria-hidden="true"></i></small>
|
||||
<button form="reply" class="btn btn-primary ml-auto">Comment</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,68 +1,68 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>{{'SITE_NAME' | app_config}} - Contact</title>
|
||||
<meta name="description" content="Contact {{'SITE_NAME' | app_config}} Admins">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if request.values.get('error') or error %}
|
||||
<div class="alert alert-danger alert-dismissible fade show my-3" role="alert">
|
||||
<i class="fas fa-exclamation-circle my-auto"></i>
|
||||
<span>
|
||||
{{error if error else request.values.get('error')}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if request.values.get('msg') or msg %}
|
||||
<div class="alert alert-success alert-dismissible fade show my-3" role="alert">
|
||||
<i class="fas fa-check-circle my-auto" aria-hidden="true"></i>
|
||||
<span>
|
||||
{{msg if msg else request.values.get('msg')}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h1 class="article-title">Contact {{'SITE_NAME' | app_config}} Admins</h1>
|
||||
{% if v and v.is_activated and not v.is_suspended %}
|
||||
|
||||
<p>Use this form to contact {{'SITE_NAME' | app_config}} Admins.</p>
|
||||
|
||||
<label class="mt-3">Your Email</label>
|
||||
<input class="form-control" value="{{v.email}}" readonly="readonly" disabled>
|
||||
|
||||
<form id="contactform" action="/contact" method="post">
|
||||
|
||||
<label for="input-message" class="mt-3">Your message</label>
|
||||
<textarea id="input-message" form="contactform" name="message" class="form-control" required></textarea>
|
||||
|
||||
<input type="submit" value="Submit" class="btn btn-primary mt-3">
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
{% elif v %}
|
||||
|
||||
<p>Please <a target="_blank" href="/settings/security">verify your email address</a> in order to ensure we can respond to your message if needed. Then, refresh this page.</p>
|
||||
|
||||
{% else %}
|
||||
|
||||
<p>In order to ensure that we can respond to your message, please first <a href="/signup" target="_blank">sign up</a> or <a href="/login" target="_blank">log in</a> and make sure you have <a target="_blank" href="/settings/security">verified your email address</a>. Then, refresh this page.</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
|
||||
<p>If you can see this line, we haven't been contacted by any law enforcement or governmental organizations in 2021 yet.</p>
|
||||
|
||||
{% endblock %}
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>{{'SITE_NAME' | app_config}} - Contact</title>
|
||||
<meta name="description" content="Contact {{'SITE_NAME' | app_config}} Admins">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if request.values.get('error') or error %}
|
||||
<div class="alert alert-danger alert-dismissible fade show my-3" role="alert">
|
||||
<i class="fas fa-exclamation-circle my-auto"></i>
|
||||
<span>
|
||||
{{error if error else request.values.get('error')}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if request.values.get('msg') or msg %}
|
||||
<div class="alert alert-success alert-dismissible fade show my-3" role="alert">
|
||||
<i class="fas fa-check-circle my-auto" aria-hidden="true"></i>
|
||||
<span>
|
||||
{{msg if msg else request.values.get('msg')}}
|
||||
</span>
|
||||
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h1 class="article-title">Contact {{'SITE_NAME' | app_config}} Admins</h1>
|
||||
{% if v and v.is_activated and not v.is_suspended %}
|
||||
|
||||
<p>Use this form to contact {{'SITE_NAME' | app_config}} Admins.</p>
|
||||
|
||||
<label class="mt-3">Your Email</label>
|
||||
<input class="form-control" value="{{v.email}}" readonly="readonly" disabled>
|
||||
|
||||
<form id="contactform" action="/contact" method="post">
|
||||
|
||||
<label for="input-message" class="mt-3">Your message</label>
|
||||
<textarea id="input-message" form="contactform" name="message" class="form-control" required></textarea>
|
||||
|
||||
<input type="submit" value="Submit" class="btn btn-primary mt-3">
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
{% elif v %}
|
||||
|
||||
<p>Please <a target="_blank" href="/settings/security">verify your email address</a> in order to ensure we can respond to your message if needed. Then, refresh this page.</p>
|
||||
|
||||
{% else %}
|
||||
|
||||
<p>In order to ensure that we can respond to your message, please first <a href="/signup" target="_blank">sign up</a> or <a href="/login" target="_blank">log in</a> and make sure you have <a target="_blank" href="/settings/security">verified your email address</a>. Then, refresh this page.</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
<pre>
|
||||
|
||||
|
||||
</pre>
|
||||
|
||||
<p>If you can see this line, we haven't been contacted by any law enforcement or governmental organizations in 2021 yet.</p>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,357 +1,360 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="/assets/js/lozad.js?v=50"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
{% if v and v.agendaposter %}
|
||||
<script>
|
||||
var BugDispatch={options:{minDelay:500,maxDelay:1E4,minBugs:2,maxBugs:20,minSpeed:5,maxSpeed:10,maxLargeTurnDeg:150,maxSmallTurnDeg:10,maxWiggleDeg:5,imageSprite:"fly-sprite.webp",bugWidth:13,bugHeight:14,num_frames:5,zoom:10,canFly:!0,canDie:!0,numDeathTypes:3,monitorMouseMovement:!1,eventDistanceToBug:40,minTimeBetweenMultipy:1E3,mouseOver:"random"},initialize:function(a){this.options=mergeOptions(this.options,a);this.options.minBugs>this.options.maxBugs&&(this.options.minBugs=this.options.maxBugs);
|
||||
this.modes=["multiply","nothing"];this.options.canFly&&this.modes.push("fly","flyoff");this.options.canDie&&this.modes.push("die");-1==this.modes.indexOf(this.options.mouseOver)&&(this.options.mouseOver="random");this.transform=null;this.transforms={Moz:function(a){this.bug.style.MozTransform=a},webkit:function(a){this.bug.style.webkitTransform=a},O:function(a){this.bug.style.OTransform=a},ms:function(a){this.bug.style.msTransform=a},Khtml:function(a){this.bug.style.KhtmlTransform=a},w3c:function(a){this.bug.style.transform=
|
||||
a}};if("transform"in document.documentElement.style)this.transform=this.transforms.w3c;else{var b=["Moz","webkit","O","ms","Khtml"],c=0;for(c=0;c<b.length;c++)if(b[c]+"Transform"in document.documentElement.style){this.transform=this.transforms[b[c]];break}}if(this.transform){this.bugs=[];b="multiply"===this.options.mouseOver?this.options.minBugs:this.random(this.options.minBugs,this.options.maxBugs,!0);c=0;var d=this;for(c=0;c<b;c++){a=JSON.parse(JSON.stringify(this.options));var e=SpawnBug();a.wingsOpen=
|
||||
this.options.canFly?.5<Math.random()?!0:!1:!0;a.walkSpeed=this.random(this.options.minSpeed,this.options.maxSpeed);e.initialize(this.transform,a);this.bugs.push(e)}this.spawnDelay=[];for(c=0;c<b;c++)a=this.random(this.options.minDelay,this.options.maxDelay,!0),e=this.bugs[c],this.spawnDelay[c]=setTimeout(function(a){return function(){d.options.canFly?a.flyIn():a.walkIn()}}(e),a),d.add_events_to_bug(e);this.options.monitorMouseMovement&&(window.onmousemove=function(){d.check_if_mouse_close_to_bug()})}},
|
||||
stop:function(){for(var a=0;a<this.bugs.length;a++)this.spawnDelay[a]&&clearTimeout(this.spawnDelay[a]),this.bugs[a].stop()},end:function(){for(var a=0;a<this.bugs.length;a++)this.spawnDelay[a]&&clearTimeout(this.spawnDelay[a]),this.bugs[a].stop(),this.bugs[a].remove()},reset:function(){this.stop();for(var a=0;a<this.bugs.length;a++)this.bugs[a].reset(),this.bugs[a].walkIn()},killAll:function(){for(var a=0;a<this.bugs.length;a++)this.spawnDelay[a]&&clearTimeout(this.spawnDelay[a]),this.bugs[a].die()},
|
||||
add_events_to_bug:function(a){var b=this;a.bug&&(a.bug.addEventListener?a.bug.addEventListener("mouseover",function(c){b.on_bug(a)}):a.bug.attachEvent&&a.bug.attachEvent("onmouseover",function(c){b.on_bug(a)}))},check_if_mouse_close_to_bug:function(a){if(a=a||window.event){var b=0,c=0;a.client&&a.client.x?(b=a.client.x,c=a.client.y):a.clientX?(b=a.clientX,c=a.clientY):a.page&&a.page.x?(b=a.page.x-(document.body.scrollLeft+document.documentElement.scrollLeft),c=a.page.y-(document.body.scrollTop+document.documentElement.scrollTop)):
|
||||
a.pageX&&(b=a.pageX-(document.body.scrollLeft+document.documentElement.scrollLeft),c=a.pageY-(document.body.scrollTop+document.documentElement.scrollTop));a=this.bugs.length;var d;for(d=0;d<a;d++){var e=this.bugs[d].getPos();e&&Math.abs(e.top-c)+Math.abs(e.left-b)<this.options.eventDistanceToBug&&!this.bugs[d].flyperiodical&&this.near_bug(this.bugs[d])}}},near_bug:function(a){this.on_bug(a)},on_bug:function(a){if(a.alive){var b=this.options.mouseOver;"random"===b&&(b=this.modes[this.random(0,this.modes.length-
|
||||
1,!0)]);if("fly"===b)a.stop(),a.flyRand();else if("nothing"!==b)if("flyoff"===b)a.stop(),a.flyOff();else if("die"===b)a.die();else if("multiply"===b&&!this.multiplyDelay&&this.bugs.length<this.options.maxBugs){var c=SpawnBug();b=JSON.parse(JSON.stringify(this.options));var d=a.getPos(),e=this;b.wingsOpen=this.options.canFly?.5<Math.random()?!0:!1:!0;b.walkSpeed=this.random(this.options.minSpeed,this.options.maxSpeed);c.initialize(this.transform,b);c.drawBug(d.top,d.left);b.canFly?(c.flyRand(),a.flyRand()):
|
||||
(c.go(),a.go());this.bugs.push(c);this.multiplyDelay=!0;setTimeout(function(){e.add_events_to_bug(c);e.multiplyDelay=!1},this.options.minTimeBetweenMultipy)}}},random:function(a,b,c){if(a==b)return c?Math.round(a):a;var d=a-.5+Math.random()*(b-a+1);d>b?d=b:d<a&&(d=a);return c?Math.round(d):d}},BugController=function(){this.initialize.apply(this,arguments)};BugController.prototype=BugDispatch;
|
||||
var SpiderController=function(){this.options=mergeOptions(this.options,{imageSprite:"spider-sprite.webp",bugWidth:69,bugHeight:90,num_frames:7,canFly:!1,canDie:!0,numDeathTypes:2,zoom:6,minDelay:200,maxDelay:3E3,minSpeed:6,maxSpeed:13,minBugs:3,maxBugs:10});this.initialize.apply(this,arguments)};SpiderController.prototype=BugDispatch;
|
||||
var Bug={options:{wingsOpen:!1,walkSpeed:2,flySpeed:40,edge_resistance:50,zoom:10},initialize:function(a,b){this.options=mergeOptions(this.options,b);this.NEAR_TOP_EDGE=1;this.NEAR_BOTTOM_EDGE=2;this.NEAR_LEFT_EDGE=4;this.NEAR_RIGHT_EDGE=8;this.directions={};this.directions[this.NEAR_TOP_EDGE]=270;this.directions[this.NEAR_BOTTOM_EDGE]=90;this.directions[this.NEAR_LEFT_EDGE]=0;this.directions[this.NEAR_RIGHT_EDGE]=180;this.directions[this.NEAR_TOP_EDGE+this.NEAR_LEFT_EDGE]=315;this.directions[this.NEAR_TOP_EDGE+
|
||||
this.NEAR_RIGHT_EDGE]=225;this.directions[this.NEAR_BOTTOM_EDGE+this.NEAR_LEFT_EDGE]=45;this.directions[this.NEAR_BOTTOM_EDGE+this.NEAR_RIGHT_EDGE]=135;this.large_turn_angle_deg=this.angle_rad=this.angle_deg=0;this.near_edge=!1;this.edge_test_counter=10;this.fly_counter=this.large_turn_counter=this.small_turn_counter=0;this.toggle_stationary_counter=50*Math.random();this.zoom=this.random(this.options.zoom,10)/10;this.stationary=!1;this.bug=null;this.active=!0;this.wingsOpen=this.options.wingsOpen;
|
||||
this.transform=a;this.flyIndex=this.walkIndex=0;this.alive=!0;this.twitchTimer=null;this.rad2deg_k=180/Math.PI;this.deg2rad_k=Math.PI/180;this.makeBug();this.angle_rad=this.deg2rad(this.angle_deg);this.angle_deg=this.random(0,360,!0)},go:function(){if(this.transform){this.drawBug();var a=this;this.animating=!0;this.going=requestAnimFrame(function(b){a.animate(b)})}},stop:function(){this.animating=!1;this.going&&(clearTimeout(this.going),this.going=null);this.flyperiodical&&(clearTimeout(this.flyperiodical),
|
||||
this.flyperiodical=null);this.twitchTimer&&(clearTimeout(this.twitchTimer),this.twitchTimer=null)},remove:function(){this.active=!1;this.inserted&&this.bug.parentNode&&(this.bug.parentNode.removeChild(this.bug),this.inserted=!1)},reset:function(){this.active=this.alive=!0;this.bug.style.bottom="";this.bug.style.top=0;this.bug.style.left=0;this.bug.classList.remove("bug-dead")},animate:function(a){if(this.animating&&this.alive&&this.active){var b=this;this.going=requestAnimFrame(function(a){b.animate(a)});
|
||||
"_lastTimestamp"in this||(this._lastTimestamp=a);var c=a-this._lastTimestamp;if(!(40>c||(200<c&&(c=200),this._lastTimestamp=a,0>=--this.toggle_stationary_counter&&this.toggleStationary(),this.stationary))){if(0>=--this.edge_test_counter&&this.bug_near_window_edge()&&(this.angle_deg%=360,0>this.angle_deg&&(this.angle_deg+=360),15<Math.abs(this.directions[this.near_edge]-this.angle_deg))){a=this.directions[this.near_edge]-this.angle_deg;var d=360-this.angle_deg+this.directions[this.near_edge];this.large_turn_angle_deg=
|
||||
Math.abs(a)<Math.abs(d)?a:d;this.edge_test_counter=10;this.large_turn_counter=100;this.small_turn_counter=30}0>=--this.large_turn_counter&&(this.large_turn_angle_deg=this.random(1,this.options.maxLargeTurnDeg,!0),this.next_large_turn());if(0>=--this.small_turn_counter)this.angle_deg+=this.random(1,this.options.maxSmallTurnDeg),this.next_small_turn();else{a=this.random(1,this.options.maxWiggleDeg,!0);if(0<this.large_turn_angle_deg&&0>a||0>this.large_turn_angle_deg&&0<a)a=-a;this.large_turn_angle_deg-=
|
||||
a;this.angle_deg+=a}this.angle_rad=this.deg2rad(this.angle_deg);this.moveBug(this.bug.left+c/100*this.options.walkSpeed*Math.cos(this.angle_rad),this.bug.top+c/100*this.options.walkSpeed*-Math.sin(this.angle_rad),90-this.angle_deg);this.walkFrame()}}},makeBug:function(){if(!this.bug&&this.active){var a=this.wingsOpen?"0":"-"+this.options.bugHeight+"px",b=document.createElement("div");b.className="bug";b.style.background="transparent url("+this.options.imageSprite+") no-repeat 0 "+a;b.style.width=
|
||||
this.options.bugWidth+"px";b.style.height=this.options.bugHeight+"px";b.style.position="fixed";b.style.top=0;b.style.left=0;b.style.zIndex="9999999";this.bug=b;this.setPos()}},setPos:function(a,b){this.bug.top=a||this.random(this.options.edge_resistance,document.documentElement.clientHeight-this.options.edge_resistance);this.bug.left=b||this.random(this.options.edge_resistance,document.documentElement.clientWidth-this.options.edge_resistance);this.moveBug(this.bug.left,this.bug.top,90-this.angle_deg)},
|
||||
moveBug:function(a,b,c){this.bug.left=a;this.bug.top=b;a="translate("+parseInt(a)+"px,"+parseInt(b)+"px)";c&&(a+=" rotate("+c+"deg)");a+=" scale("+this.zoom+")";this.transform(a)},drawBug:function(a,b){this.bug||this.makeBug();this.bug&&(a&&b?this.setPos(a,b):this.setPos(this.bug.top,this.bug.left),this.inserted||(this.inserted=!0,document.body.appendChild(this.bug)))},toggleStationary:function(){this.stationary=!this.stationary;this.next_stationary();var a=this.wingsOpen?"0":"-"+this.options.bugHeight+
|
||||
"px";this.bug.style.backgroundPosition=this.stationary?"0 "+a:"-"+this.options.bugWidth+"px "+a},walkFrame:function(){this.bug.style.backgroundPosition=-1*this.walkIndex*this.options.bugWidth+"px "+(this.wingsOpen?"0":"-"+this.options.bugHeight+"px");this.walkIndex++;this.walkIndex>=this.options.num_frames&&(this.walkIndex=0)},fly:function(a){var b=this.bug.top,c=this.bug.left,d=c-a.left,e=b-a.top,f=Math.atan(e/d);50>Math.abs(d)+Math.abs(e)&&(this.bug.style.backgroundPosition=-2*this.options.bugWidth+
|
||||
"px -"+2*this.options.bugHeight+"px");30>Math.abs(d)+Math.abs(e)&&(this.bug.style.backgroundPosition=-1*this.options.bugWidth+"px -"+2*this.options.bugHeight+"px");if(10>Math.abs(d)+Math.abs(e))this.bug.style.backgroundPosition="0 0",this.stop(),this.go();else{var g=Math.cos(f)*this.options.flySpeed;f=Math.sin(f)*this.options.flySpeed;if(c>a.left&&0<g||c>a.left&&0>g)g*=-1,Math.abs(d)<Math.abs(g)&&(g/=4);if(b<a.top&&0>f||b>a.top&&0<f)f*=-1,Math.abs(e)<Math.abs(f)&&(f/=4);this.moveBug(c+g,b+f)}},flyRand:function(){this.stop();
|
||||
var a={};a.top=this.random(this.options.edge_resistance,document.documentElement.clientHeight-this.options.edge_resistance);a.left=this.random(this.options.edge_resistance,document.documentElement.clientWidth-this.options.edge_resistance);this.startFlying(a)},startFlying:function(a){var b=this.bug.top,c=this.bug.left,d=a.left-c,e=a.top-b;this.bug.left=a.left;this.bug.top=a.top;this.angle_rad=Math.atan(e/d);this.angle_deg=this.rad2deg(this.angle_rad);this.angle_deg=0<d?90+this.angle_deg:270+this.angle_deg;
|
||||
this.moveBug(c,b,this.angle_deg);var f=this;this.flyperiodical=setInterval(function(){f.fly(a)},10)},flyIn:function(){this.bug||this.makeBug();if(this.bug){this.stop();var a=Math.round(4*Math.random()-.5),b=document,c=b.documentElement,d=b.getElementsByTagName("body")[0];b=window.innerWidth||c.clientWidth||d.clientWidth;c=window.innerHeight||c.clientHeight||d.clientHeight;3<a&&(a=3);0>a&&(a=0);0===a?(a=-2*this.options.bugHeight,b*=Math.random()):1===a?(a=Math.random()*c,b+=2*this.options.bugWidth):
|
||||
2===a?(a=c+2*this.options.bugHeight,b*=Math.random()):(a=Math.random()*c,b=-3*this.options.bugWidth);this.bug.style.backgroundPosition=-3*this.options.bugWidth+"px "+(this.wingsOpen?"0":"-"+this.options.bugHeight+"px");this.bug.top=a;this.bug.left=b;this.drawBug();a={};a.top=this.random(this.options.edge_resistance,document.documentElement.clientHeight-this.options.edge_resistance);a.left=this.random(this.options.edge_resistance,document.documentElement.clientWidth-this.options.edge_resistance);this.startFlying(a)}},
|
||||
walkIn:function(){this.bug||this.makeBug();if(this.bug){this.stop();var a=Math.round(4*Math.random()-.5),b=document,c=b.documentElement,d=b.getElementsByTagName("body")[0];b=window.innerWidth||c.clientWidth||d.clientWidth;c=window.innerHeight||c.clientHeight||d.clientHeight;3<a&&(a=3);0>a&&(a=0);0===a?(a=-1.3*this.options.bugHeight,b*=Math.random()):1===a?(a=Math.random()*c,b+=.3*this.options.bugWidth):2===a?(a=c+.3*this.options.bugHeight,b*=Math.random()):(a=Math.random()*c,b=-1.3*this.options.bugWidth);
|
||||
this.bug.style.backgroundPosition=-3*this.options.bugWidth+"px "+(this.wingsOpen?"0":"-"+this.options.bugHeight+"px");this.bug.top=a;this.bug.left=b;this.drawBug();this.go()}},flyOff:function(){this.stop();var a=this.random(0,3),b={},c=document,d=c.documentElement,e=c.getElementsByTagName("body")[0];c=window.innerWidth||d.clientWidth||e.clientWidth;d=window.innerHeight||d.clientHeight||e.clientHeight;0===a?(b.top=-200,b.left=Math.random()*c):1===a?(b.top=Math.random()*d,b.left=c+200):2===a?(b.top=
|
||||
d+200,b.left=Math.random()*c):(b.top=Math.random()*d,b.left=-200);this.startFlying(b)},die:function(){this.stop();var a=this.random(0,this.options.numDeathTypes-1);this.alive=!1;this.drop(a)},drop:function(a){var b=this.bug.top,c=document,d=c.documentElement;c=c.getElementsByTagName("body")[0];var e=window.innerHeight||d.clientHeight||c.clientHeight;e-=this.options.bugHeight;var f=this.random(0,20,!0);Date.now();var g=this;this.bug.classList.add("bug-dead");this.dropTimer=requestAnimFrame(function(c){g._lastTimestamp=
|
||||
c;g.dropping(c,b,e,f,a)})},dropping:function(a,b,c,d,e){a-=this._lastTimestamp;var f=b+.002*a*a,g=this;f>=c?(f=c,clearTimeout(this.dropTimer),this.angle_deg=0,this.angle_rad=this.deg2rad(this.angle_deg),this.transform("rotate("+(90-this.angle_deg)+"deg) scale("+this.zoom+")"),this.bug.style.top=null,this.bug.style.bottom=Math.ceil((this.options.bugWidth*this.zoom-this.options.bugHeight*this.zoom)/2-this.options.bugHeight/2*(1-this.zoom))+"px",this.bug.style.left=this.bug.left+"px",this.bug.style.backgroundPosition=
|
||||
"-"+2*e*this.options.bugWidth+"px 100%",this.twitch(e)):(this.dropTimer=requestAnimFrame(function(a){g.dropping(a,b,c,d,e)}),20>a||(this.angle_deg=(this.angle_deg+d)%360,this.angle_rad=this.deg2rad(this.angle_deg),this.moveBug(this.bug.left,f,this.angle_deg)))},twitch:function(a,b){b||(b=0);var c=this;if(0===a||1===a)c.twitchTimer=setTimeout(function(){c.bug.style.backgroundPosition="-"+(2*a+b%2)*c.options.bugWidth+"px 100%";c.twitchTimer=setTimeout(function(){b++;c.bug.style.backgroundPosition="-"+
|
||||
(2*a+b%2)*c.options.bugWidth+"px 100%";c.twitch(a,++b)},c.random(300,800))},this.random(1E3,1E4))},rad2deg:function(a){return a*this.rad2deg_k},deg2rad:function(a){return a*this.deg2rad_k},random:function(a,b,c){if(a==b)return a;a=Math.round(a-.5+Math.random()*(b-a+1));return c?.5<Math.random()?a:-a:a},next_small_turn:function(){this.small_turn_counter=Math.round(10*Math.random())},next_large_turn:function(){this.large_turn_counter=Math.round(40*Math.random())},next_stationary:function(){this.toggle_stationary_counter=
|
||||
this.random(50,300)},bug_near_window_edge:function(){this.near_edge=0;this.bug.top<this.options.edge_resistance?this.near_edge|=this.NEAR_TOP_EDGE:this.bug.top>document.documentElement.clientHeight-this.options.edge_resistance&&(this.near_edge|=this.NEAR_BOTTOM_EDGE);this.bug.left<this.options.edge_resistance?this.near_edge|=this.NEAR_LEFT_EDGE:this.bug.left>document.documentElement.clientWidth-this.options.edge_resistance&&(this.near_edge|=this.NEAR_RIGHT_EDGE);return this.near_edge},getPos:function(){return this.inserted&&
|
||||
this.bug&&this.bug.style?{top:parseInt(this.bug.top,10),left:parseInt(this.bug.left,10)}:null}},SpawnBug=function(){var a={},b;for(b in Bug)Bug.hasOwnProperty(b)&&(a[b]=Bug[b]);return a},mergeOptions=function(a,b,c){"undefined"==typeof c&&(c=!0);a=c?cloneOf(a):a;for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);return a},cloneOf=function(a){if(null==a||"object"!=typeof a)return a;var b=a.constructor(),c;for(c in a)a.hasOwnProperty(c)&&(b[c]=cloneOf(a[c]));return b};
|
||||
window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a,b){window.setTimeout(a,1E3/60)}}();
|
||||
new BugController({
|
||||
imageSprite: "/assets/images/fly-sprite.webp",
|
||||
canDie: false,
|
||||
minBugs: 5,
|
||||
maxBugs: 30,
|
||||
mouseOver: "fly"
|
||||
});
|
||||
new SpiderController({
|
||||
imageSprite: "/assets/images/spider-sprite.webp",
|
||||
canDie: false,
|
||||
minBugs: 2,
|
||||
maxBugs: 20,
|
||||
mouseOver: "fly"
|
||||
});
|
||||
</script>
|
||||
<noscript>
|
||||
<style>
|
||||
body {
|
||||
-moz-transform: scale(-1, -1);
|
||||
-o-transform: scale(-1, -1);
|
||||
-webkit-transform: scale(-1, -1);
|
||||
transform: scale(-1, -1);
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
{% endif %}
|
||||
|
||||
{% if v %}
|
||||
<script>function formkey() {return '{{v.formkey}}';}</script>
|
||||
<script src="/assets/js/default.js?v=50"></script>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<meta name="thumbnail" content="/assets/images/{{'SITE_NAME' | app_config}}/preview.webp">
|
||||
|
||||
<link rel="icon" type="image/png" href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp">
|
||||
{% block title %}
|
||||
<title>{{'SITE_NAME' | app_config}}</title>
|
||||
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:title" content="{{'SITE_NAME' | app_config}}" />
|
||||
<meta property="og:site_name" content="{{request.host}}" />
|
||||
<meta property="og:image" content="{{'SITE_NAME' | app_config}}/assets/images/{{'SITE_NAME' | app_config}}/preview.webp" />
|
||||
<meta property="og:url" content="{{request.path | full_link}}">
|
||||
<meta property="og:description" name="description" content="{{'SITE_NAME' | app_config}} - {{'SLOGAN' | app_config}}">
|
||||
<meta property="og:author" name="author" content="@{{request.host_url}}" />
|
||||
<meta property="og:site_name" content="{{request.host}}" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image"/>
|
||||
<meta name="twitter:site" content="@{{request.host_url}}">
|
||||
<meta name="twitter:title" content="{{'SITE_NAME' | app_config}}" />
|
||||
<meta name="twitter:creator" content="@{{request.host_url}}">
|
||||
<meta name="twitter:description" content="{{'SITE_NAME' | app_config}} - {{'SLOGAN' | app_config}}" />
|
||||
<meta name="twitter:image" content="/assets/images/{{'SITE_NAME' | app_config}}/preview.webp" />
|
||||
<meta name="twitter:url" content="{{request.path | full_link}}" />
|
||||
{% endblock %}
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-touch-fullscreen" content="yes">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp">
|
||||
<!---<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp">--->
|
||||
<link rel="manifest" href="/assets/manifest.json">
|
||||
<link rel="mask-icon" href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp" color="#{{'DEFAULT_COLOR' | app_config}}">
|
||||
<link rel="shortcut icon" href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp">
|
||||
<meta name="apple-mobile-web-app-title" content="{{'SITE_NAME' | app_config}}">
|
||||
<meta name="application-name" content="{{'SITE_NAME' | app_config}}">
|
||||
<meta name="msapplication-TileColor" content="#{{'DEFAULT_COLOR' | app_config}}">
|
||||
<meta name="msapplication-config" content="/assets/images/browserconfig.xml">
|
||||
<meta name="theme-color" content="#{{'DEFAULT_COLOR' | app_config}}">
|
||||
|
||||
|
||||
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="320x480"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="640x960"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="640x1136"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="750x1334"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="768x1004"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="768x1024"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="828x1792"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1024x748"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1024x768"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1125x2436"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1242x2208"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1242x2688"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1334x750"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1536x2008"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1536x2048"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1668x2224"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1792x828"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2048x1496"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2048x1536"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2048x2732"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2208x1242"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2224x1668"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2436x1125"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2668x1242"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2737x2048"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.webp"
|
||||
/>
|
||||
|
||||
|
||||
{% block stylesheets %}
|
||||
|
||||
{% if v %}
|
||||
<link rel="stylesheet" href="/assets/css/{{v.theme}}_{{v.themecolor}}.css?v=61">
|
||||
{% if v.agendaposter %}<link rel="stylesheet" href="/assets/css/agendaposter.css?v=61">{% endif %}
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="/assets/css/{{'DEFAULT_THEME' | app_config}}.css?v=61">
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
<link href="/assets/css/fa.css" rel="stylesheet">
|
||||
{% block fixedMobileBarJS %}
|
||||
{% endblock %}
|
||||
|
||||
</head>
|
||||
|
||||
<body id="{% if request.path != '/comments' %}{% block pagetype %}frontpage{% endblock %}{% endif %}" style="overflow-x: hidden; {% if v and v.background %} background:url(/assets/images/backgrounds/{{v.background}}) no-repeat center center fixed !important; background-size: cover!important; background-color: #000!important;{% endif %} {% if 'rdrama' in request.host %}margin-top: 29px!important;{% endif %}">
|
||||
|
||||
{% if "marsey.tech" not in request.host and '@' not in request.path %}
|
||||
<a rel="nofollow noopener noreferrer" href="{% if 'rdrama' in request.host %}https://secure.transequality.org/site/Donation2?df_id=1480{% else %}/{% endif %}">
|
||||
<img loading="lazy" src="/assets/images/{{'SITE_NAME' | app_config}}/{% if v %}banner.webp{% else %}cached.webp{% endif %}" width="100%">
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% include "header.html" %}
|
||||
|
||||
|
||||
{% block mobileUserBanner %}
|
||||
{% endblock %}
|
||||
|
||||
{% block mobileBanner %}
|
||||
{% endblock %}
|
||||
|
||||
{% block postNav %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="container {% if request.path=='/' or '/post/' in request.path or '/comment/' in request.path %} transparent {% endif %}">
|
||||
<div class="row justify-content-around" id="main-content-row">
|
||||
|
||||
<div class="col h-100 {% block customPadding %}{% if not '/message' in request.path %}custom-gutters{% endif %}{% endblock %}" id="main-content-col">
|
||||
|
||||
{% block desktopUserBanner %}
|
||||
{% endblock %}
|
||||
|
||||
{% block desktopBanner %}
|
||||
{% endblock %}
|
||||
|
||||
{% block PseudoSubmitForm %}
|
||||
{% endblock %}
|
||||
|
||||
{% block searchText %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pagenav %}
|
||||
{% endblock %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block mobilenavbar %}
|
||||
{% include "mobile_navigation_bar.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block actionsModal %}
|
||||
{% endblock %}
|
||||
|
||||
{% block reportCommentModal %}
|
||||
{% endblock %}
|
||||
|
||||
{% block GIFtoast %}
|
||||
{% endblock %}
|
||||
|
||||
{% block GIFpicker %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
<div class="toast clipboard" id="toast-success" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
|
||||
<div class="toast-body text-center">
|
||||
<i class="fas fa-check-circle text-success mr-2"></i>Link copied to clipboard
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast-post-success" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
|
||||
<div class="toast-body bg-success text-center text-white">
|
||||
<i class="fas fa-comment-alt-smile mr-2"></i><span id="toast-post-success-text"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toast" id="toast-post-error" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
|
||||
<div class="toast-body bg-danger text-center text-white">
|
||||
<i class="fas fa-exclamation-circle mr-2"></i><span id="toast-post-error-text"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.mirrored {
|
||||
transform: scaleX(-1);-webkit-transform: scaleX(-1);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="/assets/js/lozad.js?v=53"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
{% if v and v.agendaposter %}
|
||||
<script>
|
||||
var BugDispatch={options:{minDelay:500,maxDelay:1E4,minBugs:2,maxBugs:20,minSpeed:5,maxSpeed:10,maxLargeTurnDeg:150,maxSmallTurnDeg:10,maxWiggleDeg:5,imageSprite:"fly-sprite.gif",bugWidth:13,bugHeight:14,num_frames:5,zoom:10,canFly:!0,canDie:!0,numDeathTypes:3,monitorMouseMovement:!1,eventDistanceToBug:40,minTimeBetweenMultipy:1E3,mouseOver:"random"},initialize:function(a){this.options=mergeOptions(this.options,a);this.options.minBugs>this.options.maxBugs&&(this.options.minBugs=this.options.maxBugs);
|
||||
this.modes=["multiply","nothing"];this.options.canFly&&this.modes.push("fly","flyoff");this.options.canDie&&this.modes.push("die");-1==this.modes.indexOf(this.options.mouseOver)&&(this.options.mouseOver="random");this.transform=null;this.transforms={Moz:function(a){this.bug.style.MozTransform=a},webkit:function(a){this.bug.style.webkitTransform=a},O:function(a){this.bug.style.OTransform=a},ms:function(a){this.bug.style.msTransform=a},Khtml:function(a){this.bug.style.KhtmlTransform=a},w3c:function(a){this.bug.style.transform=
|
||||
a}};if("transform"in document.documentElement.style)this.transform=this.transforms.w3c;else{var b=["Moz","webkit","O","ms","Khtml"],c=0;for(c=0;c<b.length;c++)if(b[c]+"Transform"in document.documentElement.style){this.transform=this.transforms[b[c]];break}}if(this.transform){this.bugs=[];b="multiply"===this.options.mouseOver?this.options.minBugs:this.random(this.options.minBugs,this.options.maxBugs,!0);c=0;var d=this;for(c=0;c<b;c++){a=JSON.parse(JSON.stringify(this.options));var e=SpawnBug();a.wingsOpen=
|
||||
this.options.canFly?.5<Math.random()?!0:!1:!0;a.walkSpeed=this.random(this.options.minSpeed,this.options.maxSpeed);e.initialize(this.transform,a);this.bugs.push(e)}this.spawnDelay=[];for(c=0;c<b;c++)a=this.random(this.options.minDelay,this.options.maxDelay,!0),e=this.bugs[c],this.spawnDelay[c]=setTimeout(function(a){return function(){d.options.canFly?a.flyIn():a.walkIn()}}(e),a),d.add_events_to_bug(e);this.options.monitorMouseMovement&&(window.onmousemove=function(){d.check_if_mouse_close_to_bug()})}},
|
||||
stop:function(){for(var a=0;a<this.bugs.length;a++)this.spawnDelay[a]&&clearTimeout(this.spawnDelay[a]),this.bugs[a].stop()},end:function(){for(var a=0;a<this.bugs.length;a++)this.spawnDelay[a]&&clearTimeout(this.spawnDelay[a]),this.bugs[a].stop(),this.bugs[a].remove()},reset:function(){this.stop();for(var a=0;a<this.bugs.length;a++)this.bugs[a].reset(),this.bugs[a].walkIn()},killAll:function(){for(var a=0;a<this.bugs.length;a++)this.spawnDelay[a]&&clearTimeout(this.spawnDelay[a]),this.bugs[a].die()},
|
||||
add_events_to_bug:function(a){var b=this;a.bug&&(a.bug.addEventListener?a.bug.addEventListener("mouseover",function(c){b.on_bug(a)}):a.bug.attachEvent&&a.bug.attachEvent("onmouseover",function(c){b.on_bug(a)}))},check_if_mouse_close_to_bug:function(a){if(a=a||window.event){var b=0,c=0;a.client&&a.client.x?(b=a.client.x,c=a.client.y):a.clientX?(b=a.clientX,c=a.clientY):a.page&&a.page.x?(b=a.page.x-(document.body.scrollLeft+document.documentElement.scrollLeft),c=a.page.y-(document.body.scrollTop+document.documentElement.scrollTop)):
|
||||
a.pageX&&(b=a.pageX-(document.body.scrollLeft+document.documentElement.scrollLeft),c=a.pageY-(document.body.scrollTop+document.documentElement.scrollTop));a=this.bugs.length;var d;for(d=0;d<a;d++){var e=this.bugs[d].getPos();e&&Math.abs(e.top-c)+Math.abs(e.left-b)<this.options.eventDistanceToBug&&!this.bugs[d].flyperiodical&&this.near_bug(this.bugs[d])}}},near_bug:function(a){this.on_bug(a)},on_bug:function(a){if(a.alive){var b=this.options.mouseOver;"random"===b&&(b=this.modes[this.random(0,this.modes.length-
|
||||
1,!0)]);if("fly"===b)a.stop(),a.flyRand();else if("nothing"!==b)if("flyoff"===b)a.stop(),a.flyOff();else if("die"===b)a.die();else if("multiply"===b&&!this.multiplyDelay&&this.bugs.length<this.options.maxBugs){var c=SpawnBug();b=JSON.parse(JSON.stringify(this.options));var d=a.getPos(),e=this;b.wingsOpen=this.options.canFly?.5<Math.random()?!0:!1:!0;b.walkSpeed=this.random(this.options.minSpeed,this.options.maxSpeed);c.initialize(this.transform,b);c.drawBug(d.top,d.left);b.canFly?(c.flyRand(),a.flyRand()):
|
||||
(c.go(),a.go());this.bugs.push(c);this.multiplyDelay=!0;setTimeout(function(){e.add_events_to_bug(c);e.multiplyDelay=!1},this.options.minTimeBetweenMultipy)}}},random:function(a,b,c){if(a==b)return c?Math.round(a):a;var d=a-.5+Math.random()*(b-a+1);d>b?d=b:d<a&&(d=a);return c?Math.round(d):d}},BugController=function(){this.initialize.apply(this,arguments)};BugController.prototype=BugDispatch;
|
||||
var SpiderController=function(){this.options=mergeOptions(this.options,{imageSprite:"spider-sprite.gif",bugWidth:69,bugHeight:90,num_frames:7,canFly:!1,canDie:!0,numDeathTypes:2,zoom:6,minDelay:200,maxDelay:3E3,minSpeed:6,maxSpeed:13,minBugs:3,maxBugs:10});this.initialize.apply(this,arguments)};SpiderController.prototype=BugDispatch;
|
||||
var Bug={options:{wingsOpen:!1,walkSpeed:2,flySpeed:40,edge_resistance:50,zoom:10},initialize:function(a,b){this.options=mergeOptions(this.options,b);this.NEAR_TOP_EDGE=1;this.NEAR_BOTTOM_EDGE=2;this.NEAR_LEFT_EDGE=4;this.NEAR_RIGHT_EDGE=8;this.directions={};this.directions[this.NEAR_TOP_EDGE]=270;this.directions[this.NEAR_BOTTOM_EDGE]=90;this.directions[this.NEAR_LEFT_EDGE]=0;this.directions[this.NEAR_RIGHT_EDGE]=180;this.directions[this.NEAR_TOP_EDGE+this.NEAR_LEFT_EDGE]=315;this.directions[this.NEAR_TOP_EDGE+
|
||||
this.NEAR_RIGHT_EDGE]=225;this.directions[this.NEAR_BOTTOM_EDGE+this.NEAR_LEFT_EDGE]=45;this.directions[this.NEAR_BOTTOM_EDGE+this.NEAR_RIGHT_EDGE]=135;this.large_turn_angle_deg=this.angle_rad=this.angle_deg=0;this.near_edge=!1;this.edge_test_counter=10;this.fly_counter=this.large_turn_counter=this.small_turn_counter=0;this.toggle_stationary_counter=50*Math.random();this.zoom=this.random(this.options.zoom,10)/10;this.stationary=!1;this.bug=null;this.active=!0;this.wingsOpen=this.options.wingsOpen;
|
||||
this.transform=a;this.flyIndex=this.walkIndex=0;this.alive=!0;this.twitchTimer=null;this.rad2deg_k=180/Math.PI;this.deg2rad_k=Math.PI/180;this.makeBug();this.angle_rad=this.deg2rad(this.angle_deg);this.angle_deg=this.random(0,360,!0)},go:function(){if(this.transform){this.drawBug();var a=this;this.animating=!0;this.going=requestAnimFrame(function(b){a.animate(b)})}},stop:function(){this.animating=!1;this.going&&(clearTimeout(this.going),this.going=null);this.flyperiodical&&(clearTimeout(this.flyperiodical),
|
||||
this.flyperiodical=null);this.twitchTimer&&(clearTimeout(this.twitchTimer),this.twitchTimer=null)},remove:function(){this.active=!1;this.inserted&&this.bug.parentNode&&(this.bug.parentNode.removeChild(this.bug),this.inserted=!1)},reset:function(){this.active=this.alive=!0;this.bug.style.bottom="";this.bug.style.top=0;this.bug.style.left=0;this.bug.classList.remove("bug-dead")},animate:function(a){if(this.animating&&this.alive&&this.active){var b=this;this.going=requestAnimFrame(function(a){b.animate(a)});
|
||||
"_lastTimestamp"in this||(this._lastTimestamp=a);var c=a-this._lastTimestamp;if(!(40>c||(200<c&&(c=200),this._lastTimestamp=a,0>=--this.toggle_stationary_counter&&this.toggleStationary(),this.stationary))){if(0>=--this.edge_test_counter&&this.bug_near_window_edge()&&(this.angle_deg%=360,0>this.angle_deg&&(this.angle_deg+=360),15<Math.abs(this.directions[this.near_edge]-this.angle_deg))){a=this.directions[this.near_edge]-this.angle_deg;var d=360-this.angle_deg+this.directions[this.near_edge];this.large_turn_angle_deg=
|
||||
Math.abs(a)<Math.abs(d)?a:d;this.edge_test_counter=10;this.large_turn_counter=100;this.small_turn_counter=30}0>=--this.large_turn_counter&&(this.large_turn_angle_deg=this.random(1,this.options.maxLargeTurnDeg,!0),this.next_large_turn());if(0>=--this.small_turn_counter)this.angle_deg+=this.random(1,this.options.maxSmallTurnDeg),this.next_small_turn();else{a=this.random(1,this.options.maxWiggleDeg,!0);if(0<this.large_turn_angle_deg&&0>a||0>this.large_turn_angle_deg&&0<a)a=-a;this.large_turn_angle_deg-=
|
||||
a;this.angle_deg+=a}this.angle_rad=this.deg2rad(this.angle_deg);this.moveBug(this.bug.left+c/100*this.options.walkSpeed*Math.cos(this.angle_rad),this.bug.top+c/100*this.options.walkSpeed*-Math.sin(this.angle_rad),90-this.angle_deg);this.walkFrame()}}},makeBug:function(){if(!this.bug&&this.active){var a=this.wingsOpen?"0":"-"+this.options.bugHeight+"px",b=document.createElement("div");b.className="bug";b.style.background="transparent url("+this.options.imageSprite+") no-repeat 0 "+a;b.style.width=
|
||||
this.options.bugWidth+"px";b.style.height=this.options.bugHeight+"px";b.style.position="fixed";b.style.top=0;b.style.left=0;b.style.zIndex="9999999";this.bug=b;this.setPos()}},setPos:function(a,b){this.bug.top=a||this.random(this.options.edge_resistance,document.documentElement.clientHeight-this.options.edge_resistance);this.bug.left=b||this.random(this.options.edge_resistance,document.documentElement.clientWidth-this.options.edge_resistance);this.moveBug(this.bug.left,this.bug.top,90-this.angle_deg)},
|
||||
moveBug:function(a,b,c){this.bug.left=a;this.bug.top=b;a="translate("+parseInt(a)+"px,"+parseInt(b)+"px)";c&&(a+=" rotate("+c+"deg)");a+=" scale("+this.zoom+")";this.transform(a)},drawBug:function(a,b){this.bug||this.makeBug();this.bug&&(a&&b?this.setPos(a,b):this.setPos(this.bug.top,this.bug.left),this.inserted||(this.inserted=!0,document.body.appendChild(this.bug)))},toggleStationary:function(){this.stationary=!this.stationary;this.next_stationary();var a=this.wingsOpen?"0":"-"+this.options.bugHeight+
|
||||
"px";this.bug.style.backgroundPosition=this.stationary?"0 "+a:"-"+this.options.bugWidth+"px "+a},walkFrame:function(){this.bug.style.backgroundPosition=-1*this.walkIndex*this.options.bugWidth+"px "+(this.wingsOpen?"0":"-"+this.options.bugHeight+"px");this.walkIndex++;this.walkIndex>=this.options.num_frames&&(this.walkIndex=0)},fly:function(a){var b=this.bug.top,c=this.bug.left,d=c-a.left,e=b-a.top,f=Math.atan(e/d);50>Math.abs(d)+Math.abs(e)&&(this.bug.style.backgroundPosition=-2*this.options.bugWidth+
|
||||
"px -"+2*this.options.bugHeight+"px");30>Math.abs(d)+Math.abs(e)&&(this.bug.style.backgroundPosition=-1*this.options.bugWidth+"px -"+2*this.options.bugHeight+"px");if(10>Math.abs(d)+Math.abs(e))this.bug.style.backgroundPosition="0 0",this.stop(),this.go();else{var g=Math.cos(f)*this.options.flySpeed;f=Math.sin(f)*this.options.flySpeed;if(c>a.left&&0<g||c>a.left&&0>g)g*=-1,Math.abs(d)<Math.abs(g)&&(g/=4);if(b<a.top&&0>f||b>a.top&&0<f)f*=-1,Math.abs(e)<Math.abs(f)&&(f/=4);this.moveBug(c+g,b+f)}},flyRand:function(){this.stop();
|
||||
var a={};a.top=this.random(this.options.edge_resistance,document.documentElement.clientHeight-this.options.edge_resistance);a.left=this.random(this.options.edge_resistance,document.documentElement.clientWidth-this.options.edge_resistance);this.startFlying(a)},startFlying:function(a){var b=this.bug.top,c=this.bug.left,d=a.left-c,e=a.top-b;this.bug.left=a.left;this.bug.top=a.top;this.angle_rad=Math.atan(e/d);this.angle_deg=this.rad2deg(this.angle_rad);this.angle_deg=0<d?90+this.angle_deg:270+this.angle_deg;
|
||||
this.moveBug(c,b,this.angle_deg);var f=this;this.flyperiodical=setInterval(function(){f.fly(a)},10)},flyIn:function(){this.bug||this.makeBug();if(this.bug){this.stop();var a=Math.round(4*Math.random()-.5),b=document,c=b.documentElement,d=b.getElementsByTagName("body")[0];b=window.innerWidth||c.clientWidth||d.clientWidth;c=window.innerHeight||c.clientHeight||d.clientHeight;3<a&&(a=3);0>a&&(a=0);0===a?(a=-2*this.options.bugHeight,b*=Math.random()):1===a?(a=Math.random()*c,b+=2*this.options.bugWidth):
|
||||
2===a?(a=c+2*this.options.bugHeight,b*=Math.random()):(a=Math.random()*c,b=-3*this.options.bugWidth);this.bug.style.backgroundPosition=-3*this.options.bugWidth+"px "+(this.wingsOpen?"0":"-"+this.options.bugHeight+"px");this.bug.top=a;this.bug.left=b;this.drawBug();a={};a.top=this.random(this.options.edge_resistance,document.documentElement.clientHeight-this.options.edge_resistance);a.left=this.random(this.options.edge_resistance,document.documentElement.clientWidth-this.options.edge_resistance);this.startFlying(a)}},
|
||||
walkIn:function(){this.bug||this.makeBug();if(this.bug){this.stop();var a=Math.round(4*Math.random()-.5),b=document,c=b.documentElement,d=b.getElementsByTagName("body")[0];b=window.innerWidth||c.clientWidth||d.clientWidth;c=window.innerHeight||c.clientHeight||d.clientHeight;3<a&&(a=3);0>a&&(a=0);0===a?(a=-1.3*this.options.bugHeight,b*=Math.random()):1===a?(a=Math.random()*c,b+=.3*this.options.bugWidth):2===a?(a=c+.3*this.options.bugHeight,b*=Math.random()):(a=Math.random()*c,b=-1.3*this.options.bugWidth);
|
||||
this.bug.style.backgroundPosition=-3*this.options.bugWidth+"px "+(this.wingsOpen?"0":"-"+this.options.bugHeight+"px");this.bug.top=a;this.bug.left=b;this.drawBug();this.go()}},flyOff:function(){this.stop();var a=this.random(0,3),b={},c=document,d=c.documentElement,e=c.getElementsByTagName("body")[0];c=window.innerWidth||d.clientWidth||e.clientWidth;d=window.innerHeight||d.clientHeight||e.clientHeight;0===a?(b.top=-200,b.left=Math.random()*c):1===a?(b.top=Math.random()*d,b.left=c+200):2===a?(b.top=
|
||||
d+200,b.left=Math.random()*c):(b.top=Math.random()*d,b.left=-200);this.startFlying(b)},die:function(){this.stop();var a=this.random(0,this.options.numDeathTypes-1);this.alive=!1;this.drop(a)},drop:function(a){var b=this.bug.top,c=document,d=c.documentElement;c=c.getElementsByTagName("body")[0];var e=window.innerHeight||d.clientHeight||c.clientHeight;e-=this.options.bugHeight;var f=this.random(0,20,!0);Date.now();var g=this;this.bug.classList.add("bug-dead");this.dropTimer=requestAnimFrame(function(c){g._lastTimestamp=
|
||||
c;g.dropping(c,b,e,f,a)})},dropping:function(a,b,c,d,e){a-=this._lastTimestamp;var f=b+.002*a*a,g=this;f>=c?(f=c,clearTimeout(this.dropTimer),this.angle_deg=0,this.angle_rad=this.deg2rad(this.angle_deg),this.transform("rotate("+(90-this.angle_deg)+"deg) scale("+this.zoom+")"),this.bug.style.top=null,this.bug.style.bottom=Math.ceil((this.options.bugWidth*this.zoom-this.options.bugHeight*this.zoom)/2-this.options.bugHeight/2*(1-this.zoom))+"px",this.bug.style.left=this.bug.left+"px",this.bug.style.backgroundPosition=
|
||||
"-"+2*e*this.options.bugWidth+"px 100%",this.twitch(e)):(this.dropTimer=requestAnimFrame(function(a){g.dropping(a,b,c,d,e)}),20>a||(this.angle_deg=(this.angle_deg+d)%360,this.angle_rad=this.deg2rad(this.angle_deg),this.moveBug(this.bug.left,f,this.angle_deg)))},twitch:function(a,b){b||(b=0);var c=this;if(0===a||1===a)c.twitchTimer=setTimeout(function(){c.bug.style.backgroundPosition="-"+(2*a+b%2)*c.options.bugWidth+"px 100%";c.twitchTimer=setTimeout(function(){b++;c.bug.style.backgroundPosition="-"+
|
||||
(2*a+b%2)*c.options.bugWidth+"px 100%";c.twitch(a,++b)},c.random(300,800))},this.random(1E3,1E4))},rad2deg:function(a){return a*this.rad2deg_k},deg2rad:function(a){return a*this.deg2rad_k},random:function(a,b,c){if(a==b)return a;a=Math.round(a-.5+Math.random()*(b-a+1));return c?.5<Math.random()?a:-a:a},next_small_turn:function(){this.small_turn_counter=Math.round(10*Math.random())},next_large_turn:function(){this.large_turn_counter=Math.round(40*Math.random())},next_stationary:function(){this.toggle_stationary_counter=
|
||||
this.random(50,300)},bug_near_window_edge:function(){this.near_edge=0;this.bug.top<this.options.edge_resistance?this.near_edge|=this.NEAR_TOP_EDGE:this.bug.top>document.documentElement.clientHeight-this.options.edge_resistance&&(this.near_edge|=this.NEAR_BOTTOM_EDGE);this.bug.left<this.options.edge_resistance?this.near_edge|=this.NEAR_LEFT_EDGE:this.bug.left>document.documentElement.clientWidth-this.options.edge_resistance&&(this.near_edge|=this.NEAR_RIGHT_EDGE);return this.near_edge},getPos:function(){return this.inserted&&
|
||||
this.bug&&this.bug.style?{top:parseInt(this.bug.top,10),left:parseInt(this.bug.left,10)}:null}},SpawnBug=function(){var a={},b;for(b in Bug)Bug.hasOwnProperty(b)&&(a[b]=Bug[b]);return a},mergeOptions=function(a,b,c){"undefined"==typeof c&&(c=!0);a=c?cloneOf(a):a;for(var d in b)b.hasOwnProperty(d)&&(a[d]=b[d]);return a},cloneOf=function(a){if(null==a||"object"!=typeof a)return a;var b=a.constructor(),c;for(c in a)a.hasOwnProperty(c)&&(b[c]=cloneOf(a[c]));return b};
|
||||
window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a,b){window.setTimeout(a,1E3/60)}}();
|
||||
new BugController({
|
||||
imageSprite: "/assets/images/fly-sprite.gif",
|
||||
canDie: false,
|
||||
minBugs: 5,
|
||||
maxBugs: 30,
|
||||
mouseOver: "fly"
|
||||
});
|
||||
new SpiderController({
|
||||
imageSprite: "/assets/images/spider-sprite.gif",
|
||||
canDie: false,
|
||||
minBugs: 2,
|
||||
maxBugs: 20,
|
||||
mouseOver: "fly"
|
||||
});
|
||||
</script>
|
||||
<noscript>
|
||||
<style>
|
||||
body {
|
||||
-moz-transform: scale(-1, -1);
|
||||
-o-transform: scale(-1, -1);
|
||||
-webkit-transform: scale(-1, -1);
|
||||
transform: scale(-1, -1);
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
{% endif %}
|
||||
|
||||
{% if v %}
|
||||
<script>function formkey() {return '{{v.formkey}}';}</script>
|
||||
<script src="/assets/js/default.js?v=54"></script>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<meta name="thumbnail" content="/assets/images/{{'SITE_NAME' | app_config}}/preview.gif">
|
||||
|
||||
<link rel="icon" type="image/png" href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif">
|
||||
{% block title %}
|
||||
<title>{{'SITE_NAME' | app_config}}</title>
|
||||
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:title" content="{{'SITE_NAME' | app_config}}" />
|
||||
<meta property="og:site_name" content="{{request.host}}" />
|
||||
<meta property="og:image" content="{{'SITE_NAME' | app_config}}/assets/images/{{'SITE_NAME' | app_config}}/preview.gif" />
|
||||
<meta property="og:url" content="{{request.path | full_link}}">
|
||||
<meta property="og:description" name="description" content="{{'SITE_NAME' | app_config}} - {{'SLOGAN' | app_config}}">
|
||||
<meta property="og:author" name="author" content="@{{request.host_url}}" />
|
||||
<meta property="og:site_name" content="{{request.host}}" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image"/>
|
||||
<meta name="twitter:site" content="@{{request.host_url}}">
|
||||
<meta name="twitter:title" content="{{'SITE_NAME' | app_config}}" />
|
||||
<meta name="twitter:creator" content="@{{request.host_url}}">
|
||||
<meta name="twitter:description" content="{{'SITE_NAME' | app_config}} - {{'SLOGAN' | app_config}}" />
|
||||
<meta name="twitter:image" content="/assets/images/{{'SITE_NAME' | app_config}}/preview.gif" />
|
||||
<meta name="twitter:url" content="{{request.path | full_link}}" />
|
||||
{% endblock %}
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-touch-fullscreen" content="yes">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif">
|
||||
<!---<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif">--->
|
||||
<link rel="manifest" href="/assets/manifest.json">
|
||||
<link rel="mask-icon" href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif" color="#{{'DEFAULT_COLOR' | app_config}}">
|
||||
<link rel="shortcut icon" href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif">
|
||||
<meta name="apple-mobile-web-app-title" content="{{'SITE_NAME' | app_config}}">
|
||||
<meta name="application-name" content="{{'SITE_NAME' | app_config}}">
|
||||
<meta name="msapplication-TileColor" content="#{{'DEFAULT_COLOR' | app_config}}">
|
||||
<meta name="msapplication-config" content="/assets/images/browserconfig.xml">
|
||||
<meta name="theme-color" content="#{{'DEFAULT_COLOR' | app_config}}">
|
||||
|
||||
|
||||
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="320x480"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="640x960"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="640x1136"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="750x1334"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="768x1004"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="768x1024"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="828x1792"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1024x748"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1024x768"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1125x2436"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1242x2208"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1242x2688"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1334x750"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1536x2008"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1536x2048"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1668x2224"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="1792x828"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2048x1496"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2048x1536"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2048x2732"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2208x1242"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2224x1668"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2436x1125"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2668x1242"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-startup-image"
|
||||
sizes="2737x2048"
|
||||
href="/assets/images/{{'SITE_NAME' | app_config}}/icon.gif"
|
||||
/>
|
||||
|
||||
|
||||
{% block stylesheets %}
|
||||
|
||||
{% if v %}
|
||||
<style>:root{--primary:#{{v.themecolor}}}</style>
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=80">
|
||||
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=80">
|
||||
{% if v.agendaposter %}<link rel="stylesheet" href="/assets/css/agendaposter.css?v=80">{% elif v.css %}<link rel="stylesheet" href="/@{{v.username}}/css">{% endif %}
|
||||
{% else %}
|
||||
<style>:root{--primary:#{{'DEFAULT_COLOR' | app_config}}</style>
|
||||
<link rel="stylesheet" href="/assets/css/main.css?v=80"><link rel="stylesheet" href="/assets/css/{{'DEFAULT_THEME' | app_config}}.css?v=80">
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
<link href="/assets/css/fa.css?v=52" rel="stylesheet">
|
||||
{% block fixedMobileBarJS %}
|
||||
{% endblock %}
|
||||
|
||||
</head>
|
||||
|
||||
<body id="{% if request.path != '/comments' %}{% block pagetype %}frontpage{% endblock %}{% endif %}" style="overflow-x: hidden; {% if v and v.background %} background:url(/assets/images/backgrounds/{{v.background}}) no-repeat center center fixed !important; background-size: cover!important; background-color: #000!important;{% endif %} {% if 'rama' in request.host %}margin-top: 29px!important;{% endif %}">
|
||||
|
||||
{% if '@' not in request.path %}
|
||||
<a rel="nofollow noopener noreferrer" href="{% if 'rama' in request.host %}https://secure.transequality.org/site/Donation2?df_id=1480{% else %}/{% endif %}">
|
||||
<img loading="lazy" style="margin-top: -3px;" src="/assets/images/{{'SITE_NAME' | app_config}}/{% if v %}banner.gif{% else %}cached.gif{% endif %}" width="100%">
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% include "header.html" %}
|
||||
|
||||
|
||||
{% block mobileUserBanner %}
|
||||
{% endblock %}
|
||||
|
||||
{% block mobileBanner %}
|
||||
{% endblock %}
|
||||
|
||||
{% block postNav %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="container {% if request.path=='/' or '/post/' in request.path or '/comment/' in request.path %} transparent {% endif %}">
|
||||
<div class="row justify-content-around" id="main-content-row">
|
||||
|
||||
<div class="col h-100 {% block customPadding %}{% if not '/message' in request.path %}custom-gutters{% endif %}{% endblock %}" id="main-content-col">
|
||||
|
||||
{% block desktopUserBanner %}
|
||||
{% endblock %}
|
||||
|
||||
{% block desktopBanner %}
|
||||
{% endblock %}
|
||||
|
||||
{% block PseudoSubmitForm %}
|
||||
{% endblock %}
|
||||
|
||||
{% block searchText %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
|
||||
{% block pagenav %}
|
||||
{% endblock %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block mobilenavbar %}
|
||||
{% include "mobile_navigation_bar.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block actionsModal %}
|
||||
{% endblock %}
|
||||
|
||||
{% block reportCommentModal %}
|
||||
{% endblock %}
|
||||
|
||||
{% block GIFtoast %}
|
||||
{% endblock %}
|
||||
|
||||
{% block GIFpicker %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
<div class="toast clipboard" id="toast-success" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
|
||||
<div class="toast-body text-center">
|
||||
<i class="fas fa-check-circle text-success mr-2"></i>Link copied to clipboard
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast-post-success" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
|
||||
<div class="toast-body bg-success text-center text-white">
|
||||
<i class="fas fa-comment-alt-smile mr-2"></i><span id="toast-post-success-text"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toast" id="toast-post-error" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
|
||||
<div class="toast-body bg-danger text-center text-white">
|
||||
<i class="fas fa-exclamation-circle mr-2"></i><span id="toast-post-error-text">Error, please try again later.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.mirrored {
|
||||
transform: scaleX(-1);-webkit-transform: scaleX(-1);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -1,66 +1,62 @@
|
|||
<script>
|
||||
function delete_postModal(id) {
|
||||
|
||||
function delete_post(){
|
||||
|
||||
this.innerHTML='<span class="spinner-border spinner-border-sm mr-2" role="status" aria-hidden="true"></span>Deleting post';
|
||||
this.disabled = true;
|
||||
|
||||
var url = '/delete_post/' + id
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", url, true);
|
||||
var form = new FormData()
|
||||
form.append("formkey", formkey());
|
||||
xhr.withCredentials=true;
|
||||
xhr.send(form);
|
||||
location.reload();
|
||||
}
|
||||
|
||||
document.getElementById("deletePostButton-mobile").onclick = delete_post;
|
||||
|
||||
document.getElementById("deletePostButton").onclick = delete_post;
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="modal fade modal-sm-bottom" id="deletePostModal" tabindex="-1" role="dialog" aria-labelledby="deletePostModalTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header d-none d-md-flex">
|
||||
<h5 class="modal-title">Delete post?</h5>
|
||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
|
||||
<div class="py-4">
|
||||
<i class="fad fa-trash-alt text-muted d-none d-md-block" style="font-size: 3.5rem;"></i>
|
||||
<span class="fa-stack fa-2x text-muted d-md-none">
|
||||
<i class="fas fa-circle text-danger opacity-25 fa-stack-2x"></i>
|
||||
<i class="fas text-danger fa-trash-alt fa-stack-1x"></i>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h4 d-md-none">Delete post?</div>
|
||||
|
||||
<p class="d-none d-md-block">Your post will be removed everywhere on {{'SITE_NAME' | app_config}}. This action can be undone.</p>
|
||||
|
||||
<p class="text-muted d-md-none">Your post will be removed everywhere on {{'SITE_NAME' | app_config}}. This action can be undone.</p>
|
||||
|
||||
<div class="d-md-none">
|
||||
|
||||
<button type="button" id="deletePostButton-mobile" class="btn btn-danger btn-block btn-lg">Delete post</button>
|
||||
|
||||
<button type="button" class="btn btn-secondary btn-block btn-lg" data-bs-dismiss="modal">Cancel</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer d-none d-md-flex">
|
||||
<button type="button" class="btn btn-link text-muted" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" id="deletePostButton" class="btn btn-danger">Delete post</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function delete_postModal(id) {
|
||||
|
||||
function delete_post(){
|
||||
|
||||
this.innerHTML='<span class="spinner-border spinner-border-sm mr-2" role="status" aria-hidden="true"></span>Deleting post';
|
||||
this.disabled = true;
|
||||
|
||||
var url = '/delete_post/' + id
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", url, true);
|
||||
var form = new FormData()
|
||||
form.append("formkey", formkey());
|
||||
xhr.withCredentials=true;
|
||||
xhr.onload = function() {location.reload(true);};
|
||||
xhr.send(form);
|
||||
}
|
||||
|
||||
document.getElementById("deletePostButton-mobile").onclick = delete_post;
|
||||
|
||||
document.getElementById("deletePostButton").onclick = delete_post;
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="modal fade modal-sm-bottom" id="deletePostModal" tabindex="-1" role="dialog" aria-labelledby="deletePostModalTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header d-none d-md-flex">
|
||||
<h5 class="modal-title">Delete post?</h5>
|
||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true"><i class="far fa-times"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
|
||||
<div class="py-4">
|
||||
<i class="fad fa-trash-alt text-muted d-none d-md-block" style="font-size: 3.5rem;"></i>
|
||||
</div>
|
||||
|
||||
<div class="h4 d-md-none">Delete post?</div>
|
||||
|
||||
<p class="d-none d-md-block">Your post will be removed everywhere on {{'SITE_NAME' | app_config}}. This action can be undone.</p>
|
||||
|
||||
<p class="text-muted d-md-none">Your post will be removed everywhere on {{'SITE_NAME' | app_config}}. This action can be undone.</p>
|
||||
|
||||
<div class="d-md-none">
|
||||
|
||||
<button type="button" id="deletePostButton-mobile" class="btn btn-danger btn-block btn-lg">Delete post</button>
|
||||
|
||||
<button type="button" class="btn btn-secondary btn-block btn-lg" data-bs-dismiss="modal">Cancel</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer d-none d-md-flex">
|
||||
<button type="button" class="btn btn-link text-muted" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" id="deletePostButton" class="btn btn-danger">Delete post</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,36 +1,34 @@
|
|||
{% extends "email/default.html" %}
|
||||
|
||||
{% block image %}verify.webp{% endblock %}
|
||||
|
||||
{% block title %}Remove Two-Factor Authentication{% endblock %}</h1>
|
||||
|
||||
{% block preheader %}Remove Two-Factor Authentication.{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p>We received a request to remove two-factor authentication from your account. In 72 hours, click the link below.</p>
|
||||
<p>If you didn't make this request, change your password and use the Log Out Everywhere feature in your <a href="/settings/security">Security Settings</a> to permanently invalidate the link.</p>
|
||||
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<!-- Border based button
|
||||
https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{action_url}}" class="f-fallback button" target="_blank">Remove 2FA</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>Please note that {{'SITE_NAME' | app_config}} will never ask you for your email, password, or two-factor token via email, text, or phone.</p>
|
||||
<table class="body-sub" role="presentation">
|
||||
<tr>
|
||||
<td>
|
||||
<p class="f-fallback sub">If you’re having trouble with the button above, copy and paste the URL below into your web browser.</p>
|
||||
<p class="f-fallback sub">{{action_url}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% extends "email/default.html" %}
|
||||
|
||||
{% block title %}Remove Two-Factor Authentication{% endblock %}</h1>
|
||||
|
||||
{% block preheader %}Remove Two-Factor Authentication.{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p>We received a request to remove two-factor authentication from your account. In 72 hours, click the link below.</p>
|
||||
<p>If you didn't make this request, change your password and use the Log Out Everywhere feature in your <a href="/settings/security">Security Settings</a> to permanently invalidate the link.</p>
|
||||
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<!-- Border based button
|
||||
https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{action_url}}" class="f-fallback button" target="_blank">Remove 2FA</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>Please note that {{'SITE_NAME' | app_config}} will never ask you for your email, password, or two-factor token via email, text, or phone.</p>
|
||||
<table class="body-sub" role="presentation">
|
||||
<tr>
|
||||
<td>
|
||||
<p class="f-fallback sub">If you’re having trouble with the button above, copy and paste the URL below into your web browser.</p>
|
||||
<p class="f-fallback sub">{{action_url}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,416 +1,49 @@
|
|||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title></title>
|
||||
<style type="text/css" rel="stylesheet" media="all">
|
||||
|
||||
@import url('https://fonts.googleapis.com/css?family=Roboto:400,500&display=swap');
|
||||
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 100% !important;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
a {
|
||||
color: #FF66AC!important;
|
||||
}
|
||||
a img {
|
||||
border: none;
|
||||
}
|
||||
td {
|
||||
word-break: break-word;
|
||||
}
|
||||
.preheader {
|
||||
display: none !important;
|
||||
visibility: hidden;
|
||||
mso-hide: all;
|
||||
font-size: 1px;
|
||||
line-height: 1px;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body,
|
||||
td,
|
||||
th {
|
||||
font-family: "Roboto", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
color: #121213;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
color: #121213;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
color: #121213;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
td,
|
||||
th {
|
||||
font-size: 1rem;
|
||||
}
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
blockquote {
|
||||
margin: .4em 0 1.1875em;
|
||||
font-size: 1rem;
|
||||
line-height: 1.625;
|
||||
}
|
||||
p.sub {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: #FF66AC;
|
||||
border-top: 10px solid #FF66AC;
|
||||
border-right: 18px solid #FF66AC;
|
||||
border-bottom: 10px solid #FF66AC;
|
||||
border-left: 18px solid #FF66AC;
|
||||
display: inline-block;
|
||||
color: #FFF!important;
|
||||
text-decoration: none;
|
||||
border-radius: .25rem;
|
||||
-webkit-text-size-adjust: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.button--green {
|
||||
background-color: #23CE6B;
|
||||
border-top: 10px solid #23CE6B;
|
||||
border-right: 18px solid #23CE6B;
|
||||
border-bottom: 10px solid #23CE6B;
|
||||
border-left: 18px solid #23CE6B;
|
||||
}
|
||||
.button--red {
|
||||
background-color: #F05D5E;
|
||||
border-top: 10px solid #F05D5E;
|
||||
border-right: 18px solid #F05D5E;
|
||||
border-bottom: 10px solid #F05D5E;
|
||||
border-left: 18px solid #F05D5E;
|
||||
}
|
||||
@media only screen and (max-width: 500px) {
|
||||
.button {
|
||||
width: 100% !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
}
|
||||
|
||||
.attributes {
|
||||
margin: 0 0 21px;
|
||||
}
|
||||
.attributes_content {
|
||||
background-color: #EDF2F7;
|
||||
padding: 1rem;
|
||||
border-radius: 0.35rem;
|
||||
}
|
||||
.attributes_item {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.related {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 25px 0 0 0;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
}
|
||||
.related_item {
|
||||
padding: 10px 0;
|
||||
color: #CBCCCF;
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.related_item-title {
|
||||
display: block;
|
||||
margin: .5em 0 0;
|
||||
}
|
||||
.related_item-thumb {
|
||||
display: block;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.related_heading {
|
||||
border-top: 1px solid #CBCCCF;
|
||||
text-align: center;
|
||||
padding: 25px 0 10px;
|
||||
}
|
||||
|
||||
.discount {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
background-color: #EDF2F7;
|
||||
border: 2px dashed #CBCCCF;
|
||||
}
|
||||
.discount_heading {
|
||||
text-align: center;
|
||||
}
|
||||
.discount_body {
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.social {
|
||||
width: auto;
|
||||
}
|
||||
.social td {
|
||||
padding: 0;
|
||||
width: auto;
|
||||
}
|
||||
.social_icon {
|
||||
height: 20px;
|
||||
margin: 0 8px 10px 8px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.purchase {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 35px 0;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
}
|
||||
.purchase_content {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 25px 0 0 0;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
}
|
||||
.purchase_item {
|
||||
padding: 10px 0;
|
||||
color: #121213;
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.purchase_heading {
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #E6E6E6;
|
||||
}
|
||||
.purchase_heading p {
|
||||
margin: 0;
|
||||
color: #85878E;
|
||||
font-size: 12px;
|
||||
}
|
||||
.purchase_footer {
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #E6E6E6;
|
||||
}
|
||||
.purchase_total {
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
color: #121213;
|
||||
}
|
||||
.purchase_total--label {
|
||||
padding: 0 15px 0 0;
|
||||
}
|
||||
body {
|
||||
background-color: #EDF2F7;
|
||||
color: #121213;
|
||||
}
|
||||
p {
|
||||
color: #121213;
|
||||
}
|
||||
p.sub {
|
||||
color: #6B6E76;
|
||||
}
|
||||
.email-wrapper {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
background-color: #EDF2F7;
|
||||
}
|
||||
.email-content {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
}
|
||||
|
||||
.email-masthead {
|
||||
display: none;
|
||||
}
|
||||
.email-masthead_logo {
|
||||
width: 94px;
|
||||
}
|
||||
.email-masthead_name {
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
color: #121213;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
background-color: #cfcfcf;
|
||||
}
|
||||
.email-body_inner {
|
||||
width: 570px;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
-premailer-width: 570px;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
background-color: #cfcfcf;
|
||||
}
|
||||
.email-footer {
|
||||
width: 570px;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
-premailer-width: 570px;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.email-footer p {
|
||||
color: #6B6E76;
|
||||
}
|
||||
.body-action {
|
||||
width: 100%;
|
||||
margin: 30px auto;
|
||||
padding: 0;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.body-sub {
|
||||
margin-top: 25px;
|
||||
padding-top: 25px;
|
||||
border-top: 1px solid #E6E6E6;
|
||||
}
|
||||
.content-cell {
|
||||
padding: 35px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.email-body_inner,
|
||||
.email-footer {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body,
|
||||
.email-body,
|
||||
.email-body_inner,
|
||||
.email-content,
|
||||
.email-wrapper,
|
||||
.email-masthead,
|
||||
.email-footer {
|
||||
background-color: #121213 !important;
|
||||
color: #FFF !important;
|
||||
}
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
blockquote,
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
color: #FFF !important;
|
||||
}
|
||||
.attributes_content,
|
||||
.discount {
|
||||
background-color: #222 !important;
|
||||
}
|
||||
.email-masthead_name {
|
||||
text-shadow: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<!--[if mso]>
|
||||
<style type="text/css">
|
||||
.f-fallback {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
</style>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<span class="preheader">{% block preheader %}Thanks for joining {{'SITE_NAME' | app_config}}! Please take a sec to verify the email you used to sign up.{% endblock %}</span>
|
||||
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="email-masthead">
|
||||
<a href="/" class="f-fallback email-masthead_name">
|
||||
{{'SITE_NAME' | app_config}}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
|
||||
<table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="content-cell">
|
||||
<div class="f-fallback">
|
||||
<h1>{% block title %}Title Goes Here{% endblock %}</h1>
|
||||
|
||||
{% block content %}
|
||||
{% for entry in data %}
|
||||
<h3>{{entry[0]}}</h3>
|
||||
<p>{{entry[1]}}</p>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<span class="preheader">{% block preheader %}Thanks for joining {{'SITE_NAME' | app_config}}! Please take a sec to verify the email you used to sign up.{% endblock %}</span>
|
||||
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="email-masthead">
|
||||
<a href="/" class="f-fallback email-masthead_name">
|
||||
{{'SITE_NAME' | app_config}}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
|
||||
<table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="content-cell">
|
||||
<div class="f-fallback">
|
||||
<h1>{% block title %}Title Goes Here{% endblock %}</h1>
|
||||
|
||||
{% block content %}
|
||||
{% for entry in data %}
|
||||
<h3>{{entry[0]}}</h3>
|
||||
<p>{{entry[1]}}</p>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,56 +1,56 @@
|
|||
{% extends "email/default.html" %}
|
||||
|
||||
{% block title %}Verify Your Email{% endblock %}</h1>
|
||||
|
||||
{% block preheader %}Verify your new {{'SITE_NAME' | app_config}} email.{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p>You told us you wanted to change your {{'SITE_NAME' | app_config}} account email. To finish this process, please verify your new email address:</p>
|
||||
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<!-- Border based button
|
||||
https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{action_url}}" class="f-fallback button" target="_blank">Verify email</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>For reference, here's your current information:</p>
|
||||
<table class="attributes" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="attributes_content">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="attributes_item">
|
||||
<span class="f-fallback">
|
||||
<strong>Email:</strong> {{v.email}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="attributes_item">
|
||||
<span class="f-fallback">
|
||||
<strong>Username:</strong> {{v.username}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>Please note that {{'SITE_NAME' | app_config}} will never ask you for your email, password, or two-factor token via email, text, or phone.</p>
|
||||
<table class="body-sub" role="presentation">
|
||||
<tr>
|
||||
<td>
|
||||
<p class="f-fallback sub">If you’re having trouble with the button above, copy and paste the URL below into your web browser.</p>
|
||||
<p class="f-fallback sub">{{action_url}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% extends "email/default.html" %}
|
||||
|
||||
{% block title %}Verify Your Email{% endblock %}</h1>
|
||||
|
||||
{% block preheader %}Verify your new {{'SITE_NAME' | app_config}} email.{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p>You told us you wanted to change your {{'SITE_NAME' | app_config}} account email. To finish this process, please verify your new email address:</p>
|
||||
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<!-- Border based button
|
||||
https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{action_url}}" class="f-fallback button" target="_blank">Verify email</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>For reference, here's your current information:</p>
|
||||
<table class="attributes" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="attributes_content">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="attributes_item">
|
||||
<span class="f-fallback">
|
||||
<strong>Email:</strong> {{v.email}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="attributes_item">
|
||||
<span class="f-fallback">
|
||||
<strong>Username:</strong> {{v.username}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>Please note that {{'SITE_NAME' | app_config}} will never ask you for your email, password, or two-factor token via email, text, or phone.</p>
|
||||
<table class="body-sub" role="presentation">
|
||||
<tr>
|
||||
<td>
|
||||
<p class="f-fallback sub">If you’re having trouble with the button above, copy and paste the URL below into your web browser.</p>
|
||||
<p class="f-fallback sub">{{action_url}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,54 +1,54 @@
|
|||
{% extends "email/default.html" %}
|
||||
|
||||
{% block title %}Welcome to {{'SITE_NAME' | app_config}}!{% endblock %}</h1>
|
||||
|
||||
{% block content %}
|
||||
<p>Thanks for joining {{'SITE_NAME' | app_config}}. We’re happy to have you on board. To get the most out of {{'SITE_NAME' | app_config}}, please verify your account email:</p>
|
||||
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<!-- Border based button
|
||||
https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{action_url}}" class="f-fallback button" target="_blank">Verify email</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>For reference, here's your username.</p>
|
||||
<table class="attributes" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="attributes_content">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="attributes_item">
|
||||
<span class="f-fallback">
|
||||
<strong>Email:</strong> {{v.email}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="attributes_item">
|
||||
<span class="f-fallback">
|
||||
<strong>Username:</strong> {{v.username}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>Please note that {{'SITE_NAME' | app_config}} will never ask you for your email, password, or two-factor token via email, text, or phone.</p>
|
||||
<table class="body-sub" role="presentation">
|
||||
<tr>
|
||||
<td>
|
||||
<p class="f-fallback sub">If you’re having trouble with the button above, copy and paste the URL below into your web browser.</p>
|
||||
<p class="f-fallback sub">{{action_url}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% extends "email/default.html" %}
|
||||
|
||||
{% block title %}Welcome to {{'SITE_NAME' | app_config}}!{% endblock %}</h1>
|
||||
|
||||
{% block content %}
|
||||
<p>Thanks for joining {{'SITE_NAME' | app_config}}. We’re happy to have you on board. To get the most out of {{'SITE_NAME' | app_config}}, please verify your account email:</p>
|
||||
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<!-- Border based button
|
||||
https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{action_url}}" class="f-fallback button" target="_blank">Verify email</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>For reference, here's your username.</p>
|
||||
<table class="attributes" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="attributes_content">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="attributes_item">
|
||||
<span class="f-fallback">
|
||||
<strong>Email:</strong> {{v.email}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="attributes_item">
|
||||
<span class="f-fallback">
|
||||
<strong>Username:</strong> {{v.username}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>Please note that {{'SITE_NAME' | app_config}} will never ask you for your email, password, or two-factor token via email, text, or phone.</p>
|
||||
<table class="body-sub" role="presentation">
|
||||
<tr>
|
||||
<td>
|
||||
<p class="f-fallback sub">If you’re having trouble with the button above, copy and paste the URL below into your web browser.</p>
|
||||
<p class="f-fallback sub">{{action_url}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,54 +1,54 @@
|
|||
{% extends "email/default.html" %}
|
||||
|
||||
{% block title %}Reset Your Password{% endblock %}
|
||||
{% block preheader %}Reset your {{'SITE_NAME' | app_config}} password.{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p>To reset your password, click the button below:</p>
|
||||
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<!-- Border based button
|
||||
https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{action_url}}" class="f-fallback button" target="_blank">Reset password</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>For reference, here's your login information:</p>
|
||||
<table class="attributes" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="attributes_content">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="attributes_item">
|
||||
<span class="f-fallback">
|
||||
<strong>Email:</strong> {{v.email}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="attributes_item">
|
||||
<span class="f-fallback">
|
||||
<strong>Username:</strong> {{v.username}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="body-sub" role="presentation">
|
||||
<tr>
|
||||
<td>
|
||||
<p class="f-fallback sub">If you’re having trouble with the button above, copy and paste the URL below into your web browser.</p>
|
||||
<p class="f-fallback sub">{{action_url}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% extends "email/default.html" %}
|
||||
|
||||
{% block title %}Reset Your Password{% endblock %}
|
||||
{% block preheader %}Reset your {{'SITE_NAME' | app_config}} password.{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p>To reset your password, click the button below:</p>
|
||||
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<!-- Border based button
|
||||
https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{action_url}}" class="f-fallback button" target="_blank">Reset password</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>For reference, here's your login information:</p>
|
||||
<table class="attributes" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="attributes_content">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="attributes_item">
|
||||
<span class="f-fallback">
|
||||
<strong>Email:</strong> {{v.email}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="attributes_item">
|
||||
<span class="f-fallback">
|
||||
<strong>Username:</strong> {{v.username}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="body-sub" role="presentation">
|
||||
<tr>
|
||||
<td>
|
||||
<p class="f-fallback sub">If you’re having trouble with the button above, copy and paste the URL below into your web browser.</p>
|
||||
<p class="f-fallback sub">{{action_url}}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{{p.embed_url | safe}}
|
||||
<script src="/assets/js/twitter.js" charset="utf-8">
|
||||
</script>
|
||||
<script>
|
||||
document.getElementById('twitter-widget-0').setAttribute('sandbox','')
|
||||
{{p.embed_url | safe}}
|
||||
<script src="/assets/js/twitter.js" charset="utf-8">
|
||||
</script>
|
||||
<script>
|
||||
document.getElementById('twitter-widget-0').setAttribute('sandbox','')
|
||||
</script>
|
|
@ -1,6 +1,6 @@
|
|||
{{p.embed_url | safe}}
|
||||
<script src="https://platform.twitter.com/widgets.js" charset="utf-8">
|
||||
</script>
|
||||
<script>
|
||||
document.getElementById('twitter-widget-0').setAttribute('sandbox','')
|
||||
{{p.embed_url | safe}}
|
||||
<script src="https://platform.twitter.com/widgets.js" charset="utf-8">
|
||||
</script>
|
||||
<script>
|
||||
document.getElementById('twitter-widget-0').setAttribute('sandbox','')
|
||||
</script>
|
|
@ -1,3 +1,3 @@
|
|||
<div class="embed-responsive embed-responsive-16by9 mb-3">
|
||||
<iframe loading="lazy" src="{{p.embed_url}}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
<div class="embed-responsive embed-responsive-16by9 mb-3">
|
||||
<iframe loading="lazy" src="{{p.embed_url}}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
|
@ -1,99 +1,99 @@
|
|||
<script src="/assets/js/emoji_modal.js?v=50"></script>
|
||||
|
||||
<style>
|
||||
a.emojitab {
|
||||
padding: 0.5rem 0.7rem !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
@media (min-width: 576px)
|
||||
{
|
||||
.modal-dialog {
|
||||
max-width: 65% !important;
|
||||
margin: 1.75rem auto !important;
|
||||
}
|
||||
}
|
||||
.emoji2:focus {
|
||||
border: 1px solid var(--primary) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="form" class="d-none"></div>
|
||||
<div class="modal fade" id="emojiModal" tabindex="-1" role="dialog" aria-labelledby="emojiModalTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered p-2 py-5" role="document">
|
||||
<div class="modal-content" id="emojiTabs">
|
||||
<div class="modal-header">
|
||||
<div>
|
||||
<ul class="nav nav-pills py-2">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active emojitab" data-bs-toggle="tab" href="#emoji-tab-favorite">Favorite</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-marsey">Marsey</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-platy">Platy</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-tay">Tay</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-classic">Classic</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-rage">Rage</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-wojak">Wojak</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-flags">Flags</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<i class="fal fa-times text-muted"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="px-3"><input class="form-control px-2" type="text" id="emoji_search" placeholder="Search.."></div>
|
||||
|
||||
<div style="overflow-y: scroll;">
|
||||
<div class="modal-body p-0" id="emoji-modal-body">
|
||||
<div id="emoji-tab-search"></div>
|
||||
<div id="no-emojis-found"></div>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="emoji-tab-favorite">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_favorite">
|
||||
{% if session.get("favorite_emojis") %}
|
||||
{{session.get("favorite_emojis") | favorite_emojis | safe}}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="emoji-tab-marsey">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_marsey"></div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="emoji-tab-platy">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_platy"></div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="emoji-tab-tay">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_tay"></div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="emoji-tab-classic">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_classic"></div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="emoji-tab-rage">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_rage"></div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="emoji-tab-wojak">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_wojak"></div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="emoji-tab-flags">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_flags"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/assets/js/emoji_modal.js?v=56"></script>
|
||||
|
||||
<style>
|
||||
a.emojitab {
|
||||
padding: 0.5rem 0.7rem !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
@media (min-width: 576px)
|
||||
{
|
||||
.modal-dialog {
|
||||
max-width: 65% !important;
|
||||
margin: 1.75rem auto !important;
|
||||
}
|
||||
}
|
||||
.emoji2:focus {
|
||||
border: 1px solid var(--primary) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="form" class="d-none"></div>
|
||||
<div class="modal fade" id="emojiModal" tabindex="-1" role="dialog" aria-labelledby="emojiModalTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered p-2 py-5" role="document">
|
||||
<div class="modal-content" id="emojiTabs">
|
||||
<div class="modal-header">
|
||||
<div>
|
||||
<ul class="nav nav-pills py-2">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active emojitab" data-bs-toggle="tab" href="#emoji-tab-favorite">Favorite</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-marsey">Marsey</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-platy">Platy</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-tay">Tay</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-classic">Classic</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-rage">Rage</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-wojak">Wojak</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-flags">Flags</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<i class="fal fa-times text-muted"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="px-3"><input class="form-control px-2" type="text" id="emoji_search" placeholder="Search.."></div>
|
||||
|
||||
<div style="overflow-y: scroll;">
|
||||
<div class="modal-body p-0" id="emoji-modal-body">
|
||||
<div id="emoji-tab-search"></div>
|
||||
<div id="no-emojis-found"></div>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="emoji-tab-favorite">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_favorite">
|
||||
{% if session.get("favorite_emojis") %}
|
||||
{{session.get("favorite_emojis") | favorite_emojis | safe}}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="emoji-tab-marsey">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_marsey"></div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="emoji-tab-platy">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_platy"></div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="emoji-tab-tay">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_tay"></div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="emoji-tab-classic">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_classic"></div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="emoji-tab-rage">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_rage"></div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="emoji-tab-wojak">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_wojak"></div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="emoji-tab-flags">
|
||||
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_flags"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,20 +1,20 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>400 Bad Request</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagetype %}error-400{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-10 col-md-5">
|
||||
<div class="text-center px-3 my-8">
|
||||
<img loading="lazy" src="/assets/images/emojis/marseybrainlet.webp">
|
||||
<pre></pre>
|
||||
<h1 class="h5">400 Bad Request</h1>
|
||||
<p class="text-muted mb-5">That request was bad and you should feel bad.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>400 Bad Request</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagetype %}error-400{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-10 col-md-5">
|
||||
<div class="text-center px-3 my-8">
|
||||
<img loading="lazy" src="/assets/images/emojis/marseybrainlet.webp">
|
||||
<pre></pre>
|
||||
<h1 class="h5">400 Bad Request</h1>
|
||||
<p class="text-muted mb-5">That request was bad and you should feel bad.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>401 Not Authorized</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagetype %}error-401{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-10 col-md-5">
|
||||
<div class="text-center px-3 my-8">
|
||||
|
||||
<img loading="lazy" src="/assets/images/emojis/marseydead.webp">
|
||||
<pre></pre>
|
||||
|
||||
<h1 class="h5">401 Not Authorized</h1>
|
||||
<p class="text-muted mb-5">What you're trying to do requires an account. I think. The original error message said something about a castle and I hated that.</p>
|
||||
<div><a href="/signup" class="btn btn-primary mb-2">Create an account</a></div>
|
||||
<div><a href="/login" class="text-muted text-small">Or sign in</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>401 Not Authorized</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagetype %}error-401{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-10 col-md-5">
|
||||
<div class="text-center px-3 my-8">
|
||||
|
||||
<img loading="lazy" src="/assets/images/emojis/marseydead.webp">
|
||||
<pre></pre>
|
||||
|
||||
<h1 class="h5">401 Not Authorized</h1>
|
||||
<p class="text-muted mb-5">What you're trying to do requires an account. I think. The original error message said something about a castle and I hated that.</p>
|
||||
<div><a href="/signup" class="btn btn-primary mb-2">Create an account</a></div>
|
||||
<div><a href="/login" class="text-muted text-small">Or sign in</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,21 +1,21 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>403 Forbidden</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagetype %}error-403{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-10 col-md-5">
|
||||
<div class="text-center px-3 my-8">
|
||||
<img loading="lazy" src="/assets/images/emojis/marseytroll.webp">
|
||||
<pre></pre>
|
||||
<h1 class="h5">403 Forbidden</h1>
|
||||
<p class="text-muted mb-5">YOU AREN'T WELCOME HERE GO AWAY</p>
|
||||
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>403 Forbidden</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagetype %}error-403{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-10 col-md-5">
|
||||
<div class="text-center px-3 my-8">
|
||||
<img loading="lazy" src="/assets/images/emojis/marseytroll.webp">
|
||||
<pre></pre>
|
||||
<h1 class="h5">403 Forbidden</h1>
|
||||
<p class="text-muted mb-5">YOU AREN'T WELCOME HERE GO AWAY</p>
|
||||
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>404 Page Not Found</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagetype %}error-404{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-10 col-md-5">
|
||||
<div class="text-center px-3 my-8">
|
||||
<img loading="lazy" src="/assets/images/emojis/marseyconfused.webp">
|
||||
<pre></pre>
|
||||
<h1 class="h5">404 Page Not Found</h1>
|
||||
<p class="text-muted mb-5">Someone typed something wrong and it was probably you, please do better.</p>
|
||||
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>404 Page Not Found</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagetype %}error-404{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-10 col-md-5">
|
||||
<div class="text-center px-3 my-8">
|
||||
<img loading="lazy" src="/assets/images/emojis/marseyconfused.webp">
|
||||
<pre></pre>
|
||||
<h1 class="h5">404 Page Not Found</h1>
|
||||
<p class="text-muted mb-5">Someone typed something wrong and it was probably you, please do better.</p>
|
||||
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue