Compare commits

...

72 Commits

Author SHA1 Message Date
Dorian Vertumna 28aae9942a Merge branch 'accessibility' into 'main'
Modified header tags

See merge request brutaldon/brutaldon!12
2024-03-15 23:12:26 +00:00
Dorian Wood e03144c2e9 Modified header tags
There is now a single h1 tag. User posting forms and the timeline
are in their own h2, and individual posts are in an h3 made around
the toot title.
2023-08-01 11:16:44 -04:00
DJ Sundog 9768c08466 Merge branch 'fix/support-gts' into 'main'
pin bleach dep to 4.1, disable mastodon version checks

See merge request brutaldon/brutaldon!11
2023-02-03 17:28:45 +00:00
David Quick fdfb7bb93e pin bleach dep to 4.1, disable mastodon version checks 2023-02-03 12:15:54 -05:00
DJ Sundog b9e064967c Merge branch 'main' into 'main'
require Django 3.x for compatibility

See merge request brutaldon/brutaldon!7
2022-11-14 14:53:02 +00:00
franklint 397306a8d9 require Django 3.x for compatibility 2022-11-12 19:04:30 +00:00
DJ Sundog 8b147bbc16 Merge branch 'fix-default-field-warning' into 'main'
Deploy CI Pipeline for tests

Closes #19

See merge request brutaldon/brutaldon!6
2021-08-03 15:20:35 +00:00
DJ Sundog 19b4e56007 Merge branch 'show-local-only' into 'main'
Added local-only toot display support

Closes #21

See merge request brutaldon/brutaldon!5
2021-08-03 15:19:14 +00:00
Zachery Bohon 2131b9da3f Add .gitlab-ci.yml 2021-08-03 04:45:35 +00:00
Zac Bohon badd8922fb Added DEFAULT_AUTO_FIELD to settings.py
Resolves warnings thrown by PyTest.

see https://dev.to/weplayinternet/upgrading-to-django-3-2-and-fixing-defaultautofield-warnings-518n
2021-08-02 23:41:36 -05:00
Zac Bohon cdc199532d Added local-only toot display support
If the API returns a toot with the local_only property set to true:
Adds a "(local only)" text to the visibility.
Tested to work on Glitch-Soc and Hometown, and not break on Vanilla
2021-08-02 16:50:38 -05:00
DJ Sundog 6d7d8d936a Merge branch 'add-rel-attribute' into 'main'
Added rel attributes to the next and prev links

Closes #4

See merge request brutaldon/brutaldon!4
2021-08-01 20:39:25 +00:00
Zac Bohon 06d306ce2b Added rel attributes to the next and prev links 2021-08-01 15:37:06 -05:00
DJ Sundog 13761e6125 Merge branch 'fix-source-link' into 'main'
fix the html templates to point to gitlab repo

See merge request brutaldon/brutaldon!3
2021-08-01 20:19:49 +00:00
Zac Bohon 287e2fc19c Change the html templates to point to gitlab repo 2021-08-01 15:14:29 -05:00
Zac Bohon 9981d0ff38 Added email address to CoC 2021-08-01 11:00:27 -05:00
DJ Sundog 5f97d9fccd Merge branch 'contributing-doc' into 'main'
Add CONTRIBUTING.md

Closes #2

See merge request brutaldon/brutaldon!2
2021-08-01 14:41:37 +00:00
DJ Sundog 38904d4628 Merge branch 'code-of-conduct' into 'main'
Added CODE_OF_CONDUCT.md

Closes #1

See merge request brutaldon/brutaldon!1
2021-08-01 14:39:12 +00:00
Zachery Bohon 11df272d5d Add CONTRIBUTING.md 2021-08-01 06:50:28 +00:00
Zachery Bohon 0b51f6b390 Added CODE_OF_CONDUCT.md 2021-08-01 06:35:08 +00:00
DJ Sundog ba0d0f2df0 Update README.md to refer to new primary repo and remove former maintainer's tip jar 2021-08-01 00:56:41 +00:00
Jason McBrayer e6c5273a2f Bump version number for pleroma features 2020-07-08 13:26:11 -04:00
Jason McBrayer 012c0b74c1 Update intercooler.js 2020-07-08 13:24:00 -04:00
Jason McBrayer adc65f8d5a Upgrade jquery and remove zepto 2020-07-08 13:23:04 -04:00
Jason McBrayer d43d9cf6a6 Ignore yarn.lock 2020-07-08 13:21:34 -04:00
Jason McBrayer fd5e4874c8 Fix errors introduced in same_user, apply standard formatting 2020-07-08 12:58:38 -04:00
GCU Prosthetic Conscience b1a2c7f57d
Merge pull request #76 from cyisfor/handle_feature_set_errors
Handle feature set errors from Pleroma
2020-07-08 12:37:02 -04:00
GCU Prosthetic Conscience 8c5c48fd19
Merge pull request #79 from cyisfor/pleroma_account_search_workaround
Pleroma account search workaround
2020-07-08 12:16:27 -04:00
GCU Prosthetic Conscience ab5b734dbb
Merge pull request #77 from cyisfor/catch_session_expiration_error
Mastodon.py raises an error when session is expired
2020-07-01 08:37:40 -04:00
GCU Prosthetic Conscience a05d09289a
Merge pull request #74 from cyisfor/sophistifimacation
hiding, I mean, sophistimacatifying the instance url checking
2020-06-01 10:40:42 -04:00
GCU Prosthetic Conscience 9a99f34945
Merge pull request #73 from cyisfor/last_seen_index_error
random exception when viewing notifications
2020-06-01 10:36:23 -04:00
GCU Prosthetic Conscience 12f0fab3b2
Merge pull request #72 from cyisfor/dark_textareas
textareas and inputs in bulmaswatch-darkly were hurting my eyes.
2020-06-01 10:34:14 -04:00
Jason McBrayer b2d13e4520 Add help to preview_sensitive preference 2020-06-01 10:32:43 -04:00
Jason McBrayer b806d6c89d Reformat settings.html 2020-06-01 10:26:25 -04:00
Jason McBrayer 0971997ad3 Rearrange/reorganize settings 2020-06-01 10:25:43 -04:00
Jason McBrayer 391f31c76c Add migration for last config change 2020-06-01 10:22:28 -04:00
GCU Prosthetic Conscience a536c35dfd
Merge pull request #71 from cyisfor/preview_sensitive
Preview sensitive media
2020-06-01 10:15:25 -04:00
GCU Prosthetic Conscience 62362d8ab6
Merge pull request #70 from cyisfor/cohesive_preferences_code
Cohesive preferences code
2020-06-01 10:09:55 -04:00
Jason McBrayer 496d045356 Apparently we were missing a migration?
Hope this doesn't break anything for anyone else.
2020-06-01 09:53:58 -04:00
cyisfor 22036aa22c
Flake ids (#68)
This is a series of squashed commits from cysfor @ github.

* Pleroma base62 "flake_id" isn't a decimal integer

* Is it the path order?

Still learning Django, making sure it's not erroring out because the path order is backwards. /user/:id dies saying /user/:id/next/:thingy won't parse, so I thought they might be out of order. But actually the template main/user.html silently recurses when you do /user/:id sending another query for /user/:id/next/:etc.

* I figure out flake_ids

Go figure Pleroma can't even use hexadecimal numbers. Nope, base62 is clearly the way to go. They're working for me, at any rate.

Co-authored-by: Cy <autocommit>
Co-authored-by: Cy <email>
2020-06-01 09:43:54 -04:00
Jason McBrayer ae7be271c9 Update notification about github mirror 2020-06-01 09:41:33 -04:00
Jason McBrayer 9a89664baf Merge branch 'master' of github.com:jfmcbrayer/brutaldon 2020-06-01 09:40:02 -04:00
Cy 9b062437af
Oops, left some debugging in there 2020-06-01 08:39:16 +00:00
Cy f471cf656f
Missed a same_username
Making sure to check the username in the fallback too. Otherwise, seems to be working!
2020-06-01 08:34:39 +00:00
Cy 6d22b6fc66 Pleroma bug requires /search before /account_search works
Bluh... falling back to /search if /account_search fails.
2020-06-01 08:31:31 +00:00
Cy 9dd5e44e6f More debugging
Trying to understand what's coming over the wire again
2020-06-01 08:30:28 +00:00
Cy 0b418f985d Trying to figure out why it's not finding accounts
Refactoring the code to be a little more readable...
2020-06-01 08:25:01 +00:00
Cy dd19608b66
Mastodon.py raises an error when session is expired
Sometimes Brutaldon thinks it has a valid session key, but the upstream Fediverse server does not, and Brutaldon needs to handle that. Brutal probably should un-set its own internal logged-in state when that happens, but for now it just returns the same thing as if you weren't logged into Brutaldon, making you log in again, making Brutaldon get a new session token from upstream.
2020-06-01 00:21:38 +00:00
Cy 9415defede
whoops, left an old mistake in the code 2020-05-31 02:22:47 +00:00
Cy c0caab4919
Missed a few status_post calls 2020-05-31 01:04:43 +00:00
Cy 39f13d64fd
Allow previewing sensitive images
A setting to not censor sensitive images if you think you're not in danger for looking at them.
2020-05-30 23:23:36 +00:00
Cy 0439440f40
Foreign keys add magic members
Because when you add one field to a class, you clearly want to add two fields to a class, and there's no need to consider the field you never asked to add to be something other than a field.
2020-05-30 23:20:16 +00:00
Cy 0b93eb78f4
Refactoring preferences to be more cohesive
No need to list the preference fields three times in three places in the code. A class decorator ought to be able to sleuth them out from the model itself. Should make it easier to add new preferences.
2020-05-30 23:19:49 +00:00
Cy 12d7b4cb7d
Handle feature_set errors automatically
Recreate the mastodon object if there's an error complaining about a missing feature set. Only happens for status_post I think. Could be further generalized...
2020-05-30 23:10:50 +00:00
Cy 2dfdb0b859
Support feature_set= other than mainline for Mastodon
Pleroma will send records that cause brutaldon to make pleroma-specific responses, which the mastodon python module wigs out on claiming it doesn't support that "feature set" so allow for a feature set to be specified...
2020-05-30 23:07:38 +00:00
Cy c3f6503bec
random exception when viewing notifications
I think it's a rare occasion that there are no last seen notifications, in which case just ignoring it and setting account.note_seen later works fine.
2020-05-30 21:26:02 +00:00
Cy bf0394ca36
textareas and inputs in bulmaswatch-darkly were hurting my eyes.
(#fff) is not a good background color to compose posts in, that are then displayed with the dark background color.
2020-05-30 21:23:46 +00:00
Cy 17aae685ea
hiding, I mean, sophistimacatifying the instance url checking 2020-05-30 20:58:21 +00:00
Jason McBrayer 2354dadddb Catch MastodonNotFoundErrors that were not being caught
This will cause a much more sensible error message in the case of
trying to reply to or view the thread of deleted messages
2020-03-10 18:38:03 -04:00
GCU Prosthetic Conscience 00e35409ef
Merge pull request #66 from garbados/master
Bring Brutaldon mirror up to date

Note the primary repository is https://git.carcosa.net/jmcbray/brutaldon
2020-01-31 13:58:33 -05:00
Diana Thayer aa8585bb24
Merge branch 'master' of github.com:garbados/brutaldon 2020-01-31 03:15:21 +01:00
Jason McBrayer 72c7bbcc3d Bump patch level for bug fixes 2019-11-26 08:51:59 -05:00
Jason McBrayer 31c6d0b5f0 Fix rare crash in notifications 2019-11-26 08:48:50 -05:00
Jason McBrayer ecdaabff33 Fix accessibility issue with expand CW button 2019-11-08 10:38:46 -05:00
Jason McBrayer f6d0cfee61 Fix sorting of bundled notifications 2019-11-08 10:24:10 -05:00
Jason McBrayer 428c1e1508 Make bookmarklet available in footer of every page 2019-11-08 10:06:45 -05:00
Jason McBrayer fb2970af3b Fix a bug in bundling notes 2019-11-08 07:46:22 -05:00
Jason McBrayer 2da4fd0de2 Sharing bookmarklet works 2019-11-07 19:44:09 -05:00
Jason McBrayer da1de5ea32 Add share view 2019-11-07 13:32:51 -05:00
Jason McBrayer c836861027 Fix formatting of migrations 2019-11-07 13:32:41 -05:00
Jason McBrayer eaf0cbd46b Fix emojos in display names in notifications 2019-11-06 12:49:51 -05:00
GCU Prosthetic Conscience 9744e6f647
Update README for github - moving to gitea. 2019-07-30 07:51:21 -04:00
30 changed files with 1361 additions and 2069 deletions

1
.gitignore vendored
View File

@ -113,3 +113,4 @@ node_modules
/TAGS
.vscode
package-lock.json
yarn.lock

56
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,56 @@
# This file is a template, and might need editing before it works on your project.
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Django.gitlab-ci.yml
# Official framework image. Look for the different tagged releases at:
# https://hub.docker.com/r/library/python
image: python:latest
# Pick zero or more services to be used on all builds.
# Only needed when using a docker container to run your tests in.
# Check out: http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service
services:
# - mysql:latest
# - postgres:latest
variables:
# POSTGRES_DB: database_name
# This folder is cached between builds
# https://docs.gitlab.com/ee/ci/yaml/index.html#cache
cache:
paths:
- ~/.cache/pip/
# This is a basic example for a gem or script which doesn't use
# services such as redis or postgres
before_script:
- python -V # Print out python version for debugging
# Uncomment next line if your Django app needs a JS runtime:
# - apt-get update -q && apt-get install nodejs -yqq
- pip install pipenv
- pipenv install
# To get Django tests to work you may need to create a settings file using
# the following DATABASES:
#
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.postgresql_psycopg2',
# 'NAME': 'ci',
# 'USER': 'postgres',
# 'PASSWORD': 'postgres',
# 'HOST': 'postgres',
# 'PORT': '5432',
# },
# }
#
# and then adding `--settings app.settings.ci` (or similar) to the test command
test:
variables:
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
script:
- pipenv run python manage.py test

46
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at sundog@reclaim.technology. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

29
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,29 @@
Contributing
============
Thank you for considering contributing to Brutaldon
You can contribute in the following ways:
- Finding and reporting bugs
- Translating the Brutaldon interface into various languages
- Contributing code to Brutaldon by fixing bugs or implementing features
- Creating/improving the documentation
## Bug reports
Bug reports and feature suggestions must use descriptive and concise titles and be submitted to [GitLab Issues](https://gitlab.com/brutaldon/brutaldon/-/issues). Please use the search function to make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected.
## Translations
We currently lack a formal process for this. Translations can be submitted via merge request to the gitlab repo.
## Merge requests
**Please use clean, concise titles for your merge requests.** Unless the merge request is about refactoring code, updating dependencies or other internal tasks, assume that the person reading the merge request title is not a programmer or Brutaldon developer, but instead a Brutaldon user or server administrator, and **try to describe your change or fix from their perspective**.
**The smaller the set of changes in the merge request is, the quicker it can be reviewed and merged.** Splitting tasks into multiple smaller merge requests is often preferable.
## Documentation
We would love assistance in creating a formal documentation process for this project.

View File

@ -5,7 +5,7 @@ name = "pypi"
[packages]
"beautifulsoup4" = "*"
bleach = "*"
bleach = "~=4.1"
certifi = "*"
chardet = "*"
decorator = "*"
@ -21,7 +21,7 @@ requests = "*"
six = "*"
"urllib3" = "*"
webencodings = "*"
Django = "*"
Django = "~=3.2"
django-html_sanitizer = "*"
inscriptis = "*"
lxml = "*"

View File

@ -1,5 +1,7 @@
# Brutaldon
Note: If you are seeing this on Github, this repo is a mirror that may not be up-to-date. Please go to https://gitlab.com/brutaldon/brutaldon for the latest code.
Brutaldon is a [brutalist][0], [Web 1.0][0.5] web interface for [Mastodon][1] and [Pleroma][p]. It is not a Mastodon-compatible social networking server; rather, it is just a client, like the Android or iOS client for Mastodon you may already be using, but it runs in a web server, and is accessed through a web browser. It works great in text-mode browsers such as [Lynx][2], [w3m][3], or [elinks][4], and also in more heavy-weight graphical browsers, such as Firefox. It works completely without JavaScript, but if JavaScript is available and enabled, it will be used to unobtrusively enhance the user experience.
[0]:http://brutalistwebsites.com/
@ -10,11 +12,11 @@ Brutaldon is a [brutalist][0], [Web 1.0][0.5] web interface for [Mastodon][1] an
[4]: http://elinks.or.cz/
[p]: https://pleroma.social/
There is a hosted instance at [brutaldon.online][hosted] which you can use to log in to any instance. However, you are also encouraged to run your own, either locally or on a public server.
There is a hosted instance at [brutaldon.org][hosted] which you can use to log in to any instance. However, you are also encouraged to run your own, either locally or on a public server.
[hosted]: https://brutaldon.online/
[hosted]: https://brutaldon.org/
Brutaldon is ready for day to day use, and is my main way of interacting with the fediverse. It is still missing some features you might want, like lists, filters, and editing your own profile.
Brutaldon is ready for day to day use. It is still missing some features you might want, like lists, filters, and editing your own profile.
Please see the issues tracker.
## Screenshots
@ -64,8 +66,3 @@ People love screenshots, whatever the project, so here we are. These screenshots
## Aesthetic
No automatic page updates: refresh the page to see new toots. No endless scroll: there's a "next page" link. No autocompletion of anything: use another lynx process in another screen window to look things up. UTF8 clean.
## Tip Jar
You can buy me a coffee to give me energy to work on this, but only if you have it to spare.
[![ko-fi](https://www.ko-fi.com/img/donate_sm.png)](https://ko-fi.com/D1D7QBZC)

View File

@ -0,0 +1,8 @@
from django.urls import reverse
def bookmarklet_url(request):
share_url = request.build_absolute_uri(reverse("share"))
return {
"bookmarklet_url": f"javascript:location.href='{share_url}?url='+encodeURIComponent(location.href)+';title='+encodeURIComponent(document.title)"
}

View File

@ -28,19 +28,7 @@ class OAuthLoginForm(forms.Form):
class PreferencesForm(forms.ModelForm):
class Meta:
model = Preference
fields = [
"theme",
"filter_replies",
"filter_boosts",
"timezone",
"no_javascript",
"notifications",
"click_to_load",
"lightbox",
"filter_notifications",
"bundle_notifications",
"poll_frequency",
]
fields = Preference._fields
class PostForm(forms.Form):

View File

@ -5,14 +5,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('brutaldon', '0022_auto_20190506_0938'),
]
dependencies = [("brutaldon", "0022_auto_20190506_0938")]
operations = [
migrations.AddField(
model_name='preference',
name='bundle_notifications',
field=models.BooleanField(default=False, help_text='Collapse together boosts or likes of the same toot in the notifications page.'),
),
model_name="preference",
name="bundle_notifications",
field=models.BooleanField(
default=False,
help_text="Collapse together boosts or likes of the same toot in the notifications page.",
),
)
]

View File

@ -0,0 +1,474 @@
# Generated by Django 3.0.6 on 2020-06-01 13:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("brutaldon", "0023_preference_bundle_notifications"),
]
operations = [
migrations.AlterField(
model_name="preference",
name="timezone",
field=models.CharField(
blank=True,
choices=[
("Africa/Abidjan", "Africa/Abidjan"),
("Africa/Accra", "Africa/Accra"),
("Africa/Addis_Ababa", "Africa/Addis_Ababa"),
("Africa/Algiers", "Africa/Algiers"),
("Africa/Asmara", "Africa/Asmara"),
("Africa/Bamako", "Africa/Bamako"),
("Africa/Bangui", "Africa/Bangui"),
("Africa/Banjul", "Africa/Banjul"),
("Africa/Bissau", "Africa/Bissau"),
("Africa/Blantyre", "Africa/Blantyre"),
("Africa/Brazzaville", "Africa/Brazzaville"),
("Africa/Bujumbura", "Africa/Bujumbura"),
("Africa/Cairo", "Africa/Cairo"),
("Africa/Casablanca", "Africa/Casablanca"),
("Africa/Ceuta", "Africa/Ceuta"),
("Africa/Conakry", "Africa/Conakry"),
("Africa/Dakar", "Africa/Dakar"),
("Africa/Dar_es_Salaam", "Africa/Dar_es_Salaam"),
("Africa/Djibouti", "Africa/Djibouti"),
("Africa/Douala", "Africa/Douala"),
("Africa/El_Aaiun", "Africa/El_Aaiun"),
("Africa/Freetown", "Africa/Freetown"),
("Africa/Gaborone", "Africa/Gaborone"),
("Africa/Harare", "Africa/Harare"),
("Africa/Johannesburg", "Africa/Johannesburg"),
("Africa/Juba", "Africa/Juba"),
("Africa/Kampala", "Africa/Kampala"),
("Africa/Khartoum", "Africa/Khartoum"),
("Africa/Kigali", "Africa/Kigali"),
("Africa/Kinshasa", "Africa/Kinshasa"),
("Africa/Lagos", "Africa/Lagos"),
("Africa/Libreville", "Africa/Libreville"),
("Africa/Lome", "Africa/Lome"),
("Africa/Luanda", "Africa/Luanda"),
("Africa/Lubumbashi", "Africa/Lubumbashi"),
("Africa/Lusaka", "Africa/Lusaka"),
("Africa/Malabo", "Africa/Malabo"),
("Africa/Maputo", "Africa/Maputo"),
("Africa/Maseru", "Africa/Maseru"),
("Africa/Mbabane", "Africa/Mbabane"),
("Africa/Mogadishu", "Africa/Mogadishu"),
("Africa/Monrovia", "Africa/Monrovia"),
("Africa/Nairobi", "Africa/Nairobi"),
("Africa/Ndjamena", "Africa/Ndjamena"),
("Africa/Niamey", "Africa/Niamey"),
("Africa/Nouakchott", "Africa/Nouakchott"),
("Africa/Ouagadougou", "Africa/Ouagadougou"),
("Africa/Porto-Novo", "Africa/Porto-Novo"),
("Africa/Sao_Tome", "Africa/Sao_Tome"),
("Africa/Tripoli", "Africa/Tripoli"),
("Africa/Tunis", "Africa/Tunis"),
("Africa/Windhoek", "Africa/Windhoek"),
("America/Adak", "America/Adak"),
("America/Anchorage", "America/Anchorage"),
("America/Anguilla", "America/Anguilla"),
("America/Antigua", "America/Antigua"),
("America/Araguaina", "America/Araguaina"),
(
"America/Argentina/Buenos_Aires",
"America/Argentina/Buenos_Aires",
),
("America/Argentina/Catamarca", "America/Argentina/Catamarca"),
("America/Argentina/Cordoba", "America/Argentina/Cordoba"),
("America/Argentina/Jujuy", "America/Argentina/Jujuy"),
("America/Argentina/La_Rioja", "America/Argentina/La_Rioja"),
("America/Argentina/Mendoza", "America/Argentina/Mendoza"),
(
"America/Argentina/Rio_Gallegos",
"America/Argentina/Rio_Gallegos",
),
("America/Argentina/Salta", "America/Argentina/Salta"),
("America/Argentina/San_Juan", "America/Argentina/San_Juan"),
("America/Argentina/San_Luis", "America/Argentina/San_Luis"),
("America/Argentina/Tucuman", "America/Argentina/Tucuman"),
("America/Argentina/Ushuaia", "America/Argentina/Ushuaia"),
("America/Aruba", "America/Aruba"),
("America/Asuncion", "America/Asuncion"),
("America/Atikokan", "America/Atikokan"),
("America/Bahia", "America/Bahia"),
("America/Bahia_Banderas", "America/Bahia_Banderas"),
("America/Barbados", "America/Barbados"),
("America/Belem", "America/Belem"),
("America/Belize", "America/Belize"),
("America/Blanc-Sablon", "America/Blanc-Sablon"),
("America/Boa_Vista", "America/Boa_Vista"),
("America/Bogota", "America/Bogota"),
("America/Boise", "America/Boise"),
("America/Cambridge_Bay", "America/Cambridge_Bay"),
("America/Campo_Grande", "America/Campo_Grande"),
("America/Cancun", "America/Cancun"),
("America/Caracas", "America/Caracas"),
("America/Cayenne", "America/Cayenne"),
("America/Cayman", "America/Cayman"),
("America/Chicago", "America/Chicago"),
("America/Chihuahua", "America/Chihuahua"),
("America/Costa_Rica", "America/Costa_Rica"),
("America/Creston", "America/Creston"),
("America/Cuiaba", "America/Cuiaba"),
("America/Curacao", "America/Curacao"),
("America/Danmarkshavn", "America/Danmarkshavn"),
("America/Dawson", "America/Dawson"),
("America/Dawson_Creek", "America/Dawson_Creek"),
("America/Denver", "America/Denver"),
("America/Detroit", "America/Detroit"),
("America/Dominica", "America/Dominica"),
("America/Edmonton", "America/Edmonton"),
("America/Eirunepe", "America/Eirunepe"),
("America/El_Salvador", "America/El_Salvador"),
("America/Fort_Nelson", "America/Fort_Nelson"),
("America/Fortaleza", "America/Fortaleza"),
("America/Glace_Bay", "America/Glace_Bay"),
("America/Goose_Bay", "America/Goose_Bay"),
("America/Grand_Turk", "America/Grand_Turk"),
("America/Grenada", "America/Grenada"),
("America/Guadeloupe", "America/Guadeloupe"),
("America/Guatemala", "America/Guatemala"),
("America/Guayaquil", "America/Guayaquil"),
("America/Guyana", "America/Guyana"),
("America/Halifax", "America/Halifax"),
("America/Havana", "America/Havana"),
("America/Hermosillo", "America/Hermosillo"),
("America/Indiana/Indianapolis", "America/Indiana/Indianapolis"),
("America/Indiana/Knox", "America/Indiana/Knox"),
("America/Indiana/Marengo", "America/Indiana/Marengo"),
("America/Indiana/Petersburg", "America/Indiana/Petersburg"),
("America/Indiana/Tell_City", "America/Indiana/Tell_City"),
("America/Indiana/Vevay", "America/Indiana/Vevay"),
("America/Indiana/Vincennes", "America/Indiana/Vincennes"),
("America/Indiana/Winamac", "America/Indiana/Winamac"),
("America/Inuvik", "America/Inuvik"),
("America/Iqaluit", "America/Iqaluit"),
("America/Jamaica", "America/Jamaica"),
("America/Juneau", "America/Juneau"),
("America/Kentucky/Louisville", "America/Kentucky/Louisville"),
("America/Kentucky/Monticello", "America/Kentucky/Monticello"),
("America/Kralendijk", "America/Kralendijk"),
("America/La_Paz", "America/La_Paz"),
("America/Lima", "America/Lima"),
("America/Los_Angeles", "America/Los_Angeles"),
("America/Lower_Princes", "America/Lower_Princes"),
("America/Maceio", "America/Maceio"),
("America/Managua", "America/Managua"),
("America/Manaus", "America/Manaus"),
("America/Marigot", "America/Marigot"),
("America/Martinique", "America/Martinique"),
("America/Matamoros", "America/Matamoros"),
("America/Mazatlan", "America/Mazatlan"),
("America/Menominee", "America/Menominee"),
("America/Merida", "America/Merida"),
("America/Metlakatla", "America/Metlakatla"),
("America/Mexico_City", "America/Mexico_City"),
("America/Miquelon", "America/Miquelon"),
("America/Moncton", "America/Moncton"),
("America/Monterrey", "America/Monterrey"),
("America/Montevideo", "America/Montevideo"),
("America/Montserrat", "America/Montserrat"),
("America/Nassau", "America/Nassau"),
("America/New_York", "America/New_York"),
("America/Nipigon", "America/Nipigon"),
("America/Nome", "America/Nome"),
("America/Noronha", "America/Noronha"),
("America/North_Dakota/Beulah", "America/North_Dakota/Beulah"),
("America/North_Dakota/Center", "America/North_Dakota/Center"),
(
"America/North_Dakota/New_Salem",
"America/North_Dakota/New_Salem",
),
("America/Nuuk", "America/Nuuk"),
("America/Ojinaga", "America/Ojinaga"),
("America/Panama", "America/Panama"),
("America/Pangnirtung", "America/Pangnirtung"),
("America/Paramaribo", "America/Paramaribo"),
("America/Phoenix", "America/Phoenix"),
("America/Port-au-Prince", "America/Port-au-Prince"),
("America/Port_of_Spain", "America/Port_of_Spain"),
("America/Porto_Velho", "America/Porto_Velho"),
("America/Puerto_Rico", "America/Puerto_Rico"),
("America/Punta_Arenas", "America/Punta_Arenas"),
("America/Rainy_River", "America/Rainy_River"),
("America/Rankin_Inlet", "America/Rankin_Inlet"),
("America/Recife", "America/Recife"),
("America/Regina", "America/Regina"),
("America/Resolute", "America/Resolute"),
("America/Rio_Branco", "America/Rio_Branco"),
("America/Santarem", "America/Santarem"),
("America/Santiago", "America/Santiago"),
("America/Santo_Domingo", "America/Santo_Domingo"),
("America/Sao_Paulo", "America/Sao_Paulo"),
("America/Scoresbysund", "America/Scoresbysund"),
("America/Sitka", "America/Sitka"),
("America/St_Barthelemy", "America/St_Barthelemy"),
("America/St_Johns", "America/St_Johns"),
("America/St_Kitts", "America/St_Kitts"),
("America/St_Lucia", "America/St_Lucia"),
("America/St_Thomas", "America/St_Thomas"),
("America/St_Vincent", "America/St_Vincent"),
("America/Swift_Current", "America/Swift_Current"),
("America/Tegucigalpa", "America/Tegucigalpa"),
("America/Thule", "America/Thule"),
("America/Thunder_Bay", "America/Thunder_Bay"),
("America/Tijuana", "America/Tijuana"),
("America/Toronto", "America/Toronto"),
("America/Tortola", "America/Tortola"),
("America/Vancouver", "America/Vancouver"),
("America/Whitehorse", "America/Whitehorse"),
("America/Winnipeg", "America/Winnipeg"),
("America/Yakutat", "America/Yakutat"),
("America/Yellowknife", "America/Yellowknife"),
("Antarctica/Casey", "Antarctica/Casey"),
("Antarctica/Davis", "Antarctica/Davis"),
("Antarctica/DumontDUrville", "Antarctica/DumontDUrville"),
("Antarctica/Macquarie", "Antarctica/Macquarie"),
("Antarctica/Mawson", "Antarctica/Mawson"),
("Antarctica/McMurdo", "Antarctica/McMurdo"),
("Antarctica/Palmer", "Antarctica/Palmer"),
("Antarctica/Rothera", "Antarctica/Rothera"),
("Antarctica/Syowa", "Antarctica/Syowa"),
("Antarctica/Troll", "Antarctica/Troll"),
("Antarctica/Vostok", "Antarctica/Vostok"),
("Arctic/Longyearbyen", "Arctic/Longyearbyen"),
("Asia/Aden", "Asia/Aden"),
("Asia/Almaty", "Asia/Almaty"),
("Asia/Amman", "Asia/Amman"),
("Asia/Anadyr", "Asia/Anadyr"),
("Asia/Aqtau", "Asia/Aqtau"),
("Asia/Aqtobe", "Asia/Aqtobe"),
("Asia/Ashgabat", "Asia/Ashgabat"),
("Asia/Atyrau", "Asia/Atyrau"),
("Asia/Baghdad", "Asia/Baghdad"),
("Asia/Bahrain", "Asia/Bahrain"),
("Asia/Baku", "Asia/Baku"),
("Asia/Bangkok", "Asia/Bangkok"),
("Asia/Barnaul", "Asia/Barnaul"),
("Asia/Beirut", "Asia/Beirut"),
("Asia/Bishkek", "Asia/Bishkek"),
("Asia/Brunei", "Asia/Brunei"),
("Asia/Chita", "Asia/Chita"),
("Asia/Choibalsan", "Asia/Choibalsan"),
("Asia/Colombo", "Asia/Colombo"),
("Asia/Damascus", "Asia/Damascus"),
("Asia/Dhaka", "Asia/Dhaka"),
("Asia/Dili", "Asia/Dili"),
("Asia/Dubai", "Asia/Dubai"),
("Asia/Dushanbe", "Asia/Dushanbe"),
("Asia/Famagusta", "Asia/Famagusta"),
("Asia/Gaza", "Asia/Gaza"),
("Asia/Hebron", "Asia/Hebron"),
("Asia/Ho_Chi_Minh", "Asia/Ho_Chi_Minh"),
("Asia/Hong_Kong", "Asia/Hong_Kong"),
("Asia/Hovd", "Asia/Hovd"),
("Asia/Irkutsk", "Asia/Irkutsk"),
("Asia/Jakarta", "Asia/Jakarta"),
("Asia/Jayapura", "Asia/Jayapura"),
("Asia/Jerusalem", "Asia/Jerusalem"),
("Asia/Kabul", "Asia/Kabul"),
("Asia/Kamchatka", "Asia/Kamchatka"),
("Asia/Karachi", "Asia/Karachi"),
("Asia/Kathmandu", "Asia/Kathmandu"),
("Asia/Khandyga", "Asia/Khandyga"),
("Asia/Kolkata", "Asia/Kolkata"),
("Asia/Krasnoyarsk", "Asia/Krasnoyarsk"),
("Asia/Kuala_Lumpur", "Asia/Kuala_Lumpur"),
("Asia/Kuching", "Asia/Kuching"),
("Asia/Kuwait", "Asia/Kuwait"),
("Asia/Macau", "Asia/Macau"),
("Asia/Magadan", "Asia/Magadan"),
("Asia/Makassar", "Asia/Makassar"),
("Asia/Manila", "Asia/Manila"),
("Asia/Muscat", "Asia/Muscat"),
("Asia/Nicosia", "Asia/Nicosia"),
("Asia/Novokuznetsk", "Asia/Novokuznetsk"),
("Asia/Novosibirsk", "Asia/Novosibirsk"),
("Asia/Omsk", "Asia/Omsk"),
("Asia/Oral", "Asia/Oral"),
("Asia/Phnom_Penh", "Asia/Phnom_Penh"),
("Asia/Pontianak", "Asia/Pontianak"),
("Asia/Pyongyang", "Asia/Pyongyang"),
("Asia/Qatar", "Asia/Qatar"),
("Asia/Qostanay", "Asia/Qostanay"),
("Asia/Qyzylorda", "Asia/Qyzylorda"),
("Asia/Riyadh", "Asia/Riyadh"),
("Asia/Sakhalin", "Asia/Sakhalin"),
("Asia/Samarkand", "Asia/Samarkand"),
("Asia/Seoul", "Asia/Seoul"),
("Asia/Shanghai", "Asia/Shanghai"),
("Asia/Singapore", "Asia/Singapore"),
("Asia/Srednekolymsk", "Asia/Srednekolymsk"),
("Asia/Taipei", "Asia/Taipei"),
("Asia/Tashkent", "Asia/Tashkent"),
("Asia/Tbilisi", "Asia/Tbilisi"),
("Asia/Tehran", "Asia/Tehran"),
("Asia/Thimphu", "Asia/Thimphu"),
("Asia/Tokyo", "Asia/Tokyo"),
("Asia/Tomsk", "Asia/Tomsk"),
("Asia/Ulaanbaatar", "Asia/Ulaanbaatar"),
("Asia/Urumqi", "Asia/Urumqi"),
("Asia/Ust-Nera", "Asia/Ust-Nera"),
("Asia/Vientiane", "Asia/Vientiane"),
("Asia/Vladivostok", "Asia/Vladivostok"),
("Asia/Yakutsk", "Asia/Yakutsk"),
("Asia/Yangon", "Asia/Yangon"),
("Asia/Yekaterinburg", "Asia/Yekaterinburg"),
("Asia/Yerevan", "Asia/Yerevan"),
("Atlantic/Azores", "Atlantic/Azores"),
("Atlantic/Bermuda", "Atlantic/Bermuda"),
("Atlantic/Canary", "Atlantic/Canary"),
("Atlantic/Cape_Verde", "Atlantic/Cape_Verde"),
("Atlantic/Faroe", "Atlantic/Faroe"),
("Atlantic/Madeira", "Atlantic/Madeira"),
("Atlantic/Reykjavik", "Atlantic/Reykjavik"),
("Atlantic/South_Georgia", "Atlantic/South_Georgia"),
("Atlantic/St_Helena", "Atlantic/St_Helena"),
("Atlantic/Stanley", "Atlantic/Stanley"),
("Australia/Adelaide", "Australia/Adelaide"),
("Australia/Brisbane", "Australia/Brisbane"),
("Australia/Broken_Hill", "Australia/Broken_Hill"),
("Australia/Currie", "Australia/Currie"),
("Australia/Darwin", "Australia/Darwin"),
("Australia/Eucla", "Australia/Eucla"),
("Australia/Hobart", "Australia/Hobart"),
("Australia/Lindeman", "Australia/Lindeman"),
("Australia/Lord_Howe", "Australia/Lord_Howe"),
("Australia/Melbourne", "Australia/Melbourne"),
("Australia/Perth", "Australia/Perth"),
("Australia/Sydney", "Australia/Sydney"),
("Canada/Atlantic", "Canada/Atlantic"),
("Canada/Central", "Canada/Central"),
("Canada/Eastern", "Canada/Eastern"),
("Canada/Mountain", "Canada/Mountain"),
("Canada/Newfoundland", "Canada/Newfoundland"),
("Canada/Pacific", "Canada/Pacific"),
("Europe/Amsterdam", "Europe/Amsterdam"),
("Europe/Andorra", "Europe/Andorra"),
("Europe/Astrakhan", "Europe/Astrakhan"),
("Europe/Athens", "Europe/Athens"),
("Europe/Belgrade", "Europe/Belgrade"),
("Europe/Berlin", "Europe/Berlin"),
("Europe/Bratislava", "Europe/Bratislava"),
("Europe/Brussels", "Europe/Brussels"),
("Europe/Bucharest", "Europe/Bucharest"),
("Europe/Budapest", "Europe/Budapest"),
("Europe/Busingen", "Europe/Busingen"),
("Europe/Chisinau", "Europe/Chisinau"),
("Europe/Copenhagen", "Europe/Copenhagen"),
("Europe/Dublin", "Europe/Dublin"),
("Europe/Gibraltar", "Europe/Gibraltar"),
("Europe/Guernsey", "Europe/Guernsey"),
("Europe/Helsinki", "Europe/Helsinki"),
("Europe/Isle_of_Man", "Europe/Isle_of_Man"),
("Europe/Istanbul", "Europe/Istanbul"),
("Europe/Jersey", "Europe/Jersey"),
("Europe/Kaliningrad", "Europe/Kaliningrad"),
("Europe/Kiev", "Europe/Kiev"),
("Europe/Kirov", "Europe/Kirov"),
("Europe/Lisbon", "Europe/Lisbon"),
("Europe/Ljubljana", "Europe/Ljubljana"),
("Europe/London", "Europe/London"),
("Europe/Luxembourg", "Europe/Luxembourg"),
("Europe/Madrid", "Europe/Madrid"),
("Europe/Malta", "Europe/Malta"),
("Europe/Mariehamn", "Europe/Mariehamn"),
("Europe/Minsk", "Europe/Minsk"),
("Europe/Monaco", "Europe/Monaco"),
("Europe/Moscow", "Europe/Moscow"),
("Europe/Oslo", "Europe/Oslo"),
("Europe/Paris", "Europe/Paris"),
("Europe/Podgorica", "Europe/Podgorica"),
("Europe/Prague", "Europe/Prague"),
("Europe/Riga", "Europe/Riga"),
("Europe/Rome", "Europe/Rome"),
("Europe/Samara", "Europe/Samara"),
("Europe/San_Marino", "Europe/San_Marino"),
("Europe/Sarajevo", "Europe/Sarajevo"),
("Europe/Saratov", "Europe/Saratov"),
("Europe/Simferopol", "Europe/Simferopol"),
("Europe/Skopje", "Europe/Skopje"),
("Europe/Sofia", "Europe/Sofia"),
("Europe/Stockholm", "Europe/Stockholm"),
("Europe/Tallinn", "Europe/Tallinn"),
("Europe/Tirane", "Europe/Tirane"),
("Europe/Ulyanovsk", "Europe/Ulyanovsk"),
("Europe/Uzhgorod", "Europe/Uzhgorod"),
("Europe/Vaduz", "Europe/Vaduz"),
("Europe/Vatican", "Europe/Vatican"),
("Europe/Vienna", "Europe/Vienna"),
("Europe/Vilnius", "Europe/Vilnius"),
("Europe/Volgograd", "Europe/Volgograd"),
("Europe/Warsaw", "Europe/Warsaw"),
("Europe/Zagreb", "Europe/Zagreb"),
("Europe/Zaporozhye", "Europe/Zaporozhye"),
("Europe/Zurich", "Europe/Zurich"),
("GMT", "GMT"),
("Indian/Antananarivo", "Indian/Antananarivo"),
("Indian/Chagos", "Indian/Chagos"),
("Indian/Christmas", "Indian/Christmas"),
("Indian/Cocos", "Indian/Cocos"),
("Indian/Comoro", "Indian/Comoro"),
("Indian/Kerguelen", "Indian/Kerguelen"),
("Indian/Mahe", "Indian/Mahe"),
("Indian/Maldives", "Indian/Maldives"),
("Indian/Mauritius", "Indian/Mauritius"),
("Indian/Mayotte", "Indian/Mayotte"),
("Indian/Reunion", "Indian/Reunion"),
("Pacific/Apia", "Pacific/Apia"),
("Pacific/Auckland", "Pacific/Auckland"),
("Pacific/Bougainville", "Pacific/Bougainville"),
("Pacific/Chatham", "Pacific/Chatham"),
("Pacific/Chuuk", "Pacific/Chuuk"),
("Pacific/Easter", "Pacific/Easter"),
("Pacific/Efate", "Pacific/Efate"),
("Pacific/Enderbury", "Pacific/Enderbury"),
("Pacific/Fakaofo", "Pacific/Fakaofo"),
("Pacific/Fiji", "Pacific/Fiji"),
("Pacific/Funafuti", "Pacific/Funafuti"),
("Pacific/Galapagos", "Pacific/Galapagos"),
("Pacific/Gambier", "Pacific/Gambier"),
("Pacific/Guadalcanal", "Pacific/Guadalcanal"),
("Pacific/Guam", "Pacific/Guam"),
("Pacific/Honolulu", "Pacific/Honolulu"),
("Pacific/Kiritimati", "Pacific/Kiritimati"),
("Pacific/Kosrae", "Pacific/Kosrae"),
("Pacific/Kwajalein", "Pacific/Kwajalein"),
("Pacific/Majuro", "Pacific/Majuro"),
("Pacific/Marquesas", "Pacific/Marquesas"),
("Pacific/Midway", "Pacific/Midway"),
("Pacific/Nauru", "Pacific/Nauru"),
("Pacific/Niue", "Pacific/Niue"),
("Pacific/Norfolk", "Pacific/Norfolk"),
("Pacific/Noumea", "Pacific/Noumea"),
("Pacific/Pago_Pago", "Pacific/Pago_Pago"),
("Pacific/Palau", "Pacific/Palau"),
("Pacific/Pitcairn", "Pacific/Pitcairn"),
("Pacific/Pohnpei", "Pacific/Pohnpei"),
("Pacific/Port_Moresby", "Pacific/Port_Moresby"),
("Pacific/Rarotonga", "Pacific/Rarotonga"),
("Pacific/Saipan", "Pacific/Saipan"),
("Pacific/Tahiti", "Pacific/Tahiti"),
("Pacific/Tarawa", "Pacific/Tarawa"),
("Pacific/Tongatapu", "Pacific/Tongatapu"),
("Pacific/Wake", "Pacific/Wake"),
("Pacific/Wallis", "Pacific/Wallis"),
("US/Alaska", "US/Alaska"),
("US/Arizona", "US/Arizona"),
("US/Central", "US/Central"),
("US/Eastern", "US/Eastern"),
("US/Hawaii", "US/Hawaii"),
("US/Mountain", "US/Mountain"),
("US/Pacific", "US/Pacific"),
("UTC", "UTC"),
],
default="UTC",
max_length=80,
null=True,
),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.0.6 on 2020-06-01 14:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("brutaldon", "0024_auto_20200601_0945"),
]
operations = [
migrations.AddField(
model_name="preference",
name="preview_sensitive",
field=models.BooleanField(
default=False, help_text='Show preview for media marked as "sensitive"'
),
),
]

View File

@ -30,6 +30,26 @@ class Theme(models.Model):
return self.name
from django.db.models.fields.related_descriptors import ForeignKeyDeferredAttribute
def set_fields(klass):
fields = []
for n in dir(klass):
assert n != "_fields"
v = getattr(klass, n)
if not hasattr(v, "field"):
continue
if not isinstance(v.field, models.Field):
continue
if isinstance(v, ForeignKeyDeferredAttribute):
continue
fields.append(n)
setattr(klass, "_fields", fields)
return klass
@set_fields
class Preference(models.Model):
theme = models.ForeignKey(Theme, models.CASCADE, null=False, default=1)
filter_replies = models.BooleanField(default=False)
@ -37,6 +57,10 @@ class Preference(models.Model):
timezone = models.CharField(
max_length=80, blank=True, null=True, choices=timezones, default="UTC"
)
preview_sensitive = models.BooleanField(
default=False, help_text=_('Show preview for media marked as "sensitive"')
)
no_javascript = models.BooleanField(
default=False,
help_text=_(

View File

@ -67,6 +67,7 @@ TEMPLATES = [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"brutaldon.context_processors.bookmarklet_url",
]
},
}
@ -199,8 +200,19 @@ SESSION_SERIALIZER = "django.contrib.sessions.serializers.PickleSerializer"
# URL to redirect users to when not logged in
ANONYMOUS_HOME_URL = "about"
# URL to redirect gab users to
GAB_RICKROLL_URL = "https://invidio.us/watch?v=dQw4w9WgXcQ"
# URL to redirect galaxy brain users to
RICKROLL_URL = "https://invidio.us/watch?v=dQw4w9WgXcQ"
# Function to check if trying to add an account should trigger a special response
def CHECK_INSTANCE_URL(url, redirect):
if "gab.com" in url:
return redirect(RICKROLL_URL)
elif "shitposter.club" in url:
return redirect(RICKROLL_URL)
# See https://dev.to/weplayinternet/upgrading-to-django-3-2-and-fixing-defaultautofield-warnings-518n
DEFAULT_AUTO_FIELD='django.db.models.AutoField'
# Version number displayed on about page
BRUTALDON_VERSION = "2.14.0"
BRUTALDON_VERSION = "2.15.0"

View File

@ -255,3 +255,14 @@ div.poll {
{
margin-top: 0;
}
.input,
.textarea {
color: inherit;
background-color: inherit;
}
input[type="text"] {
color: inherit;
background-color: inherit;
}

View File

@ -72,7 +72,7 @@ function expandCWButtonPrepare()
{
var theButton = document.querySelector('#expandCWs');
if (!theButton) {
theButton = document.createElement('p');
theButton = document.createElement('button');
theButton.id = "expandCWs";
theButton.textContent = "Expand CWs";
theButton.classList.toggle('button');

View File

@ -4,7 +4,7 @@
define(["jquery"], function (a0) {
return (root['Intercooler'] = factory(a0));
});
} else if (typeof exports === 'object') {
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
@ -24,7 +24,7 @@ var Intercooler = Intercooler || (function() {
// work around zepto build issue TODO - fix me
if((typeof Zepto !== "undefined") && ($ == null)) {
$ = Zepto
window["$"] = Zepto
}
//--------------------------------------------------
@ -68,7 +68,7 @@ var Intercooler = Intercooler || (function() {
return false;
}
return adest.slice(0, asrc.length).join("/") == asrc.join("/") ||
asrc.slice(0, adest.length).join("/") == adest.join("/");
asrc.slice(0, adest.length).join("/") == adest.join("/");
};
//============================================================
@ -88,9 +88,10 @@ var Intercooler = Intercooler || (function() {
}
function hideIndicator(elt) {
if (elt.data('ic-use-transition')) {
if (elt.data('ic-use-transition') || elt.data('ic-indicator-cleared')) {
elt.data('ic-use-transition', null);
elt.addClass('ic-use-transition');
elt.data('ic-indicator-cleared', true);
} else {
elt.hide();
}
@ -259,6 +260,11 @@ var Intercooler = Intercooler || (function() {
document.title = xhr.getResponseHeader("X-IC-Title");
}
if (xhr.getResponseHeader("X-IC-Title-Encoded")) {
var decodedTitle = decodeURIComponent((xhr.getResponseHeader("X-IC-Title-Encoded")).replace(/\+/g, '%20'));
document.title = decodedTitle;
}
if (xhr.getResponseHeader("X-IC-Refresh")) {
var pathsToRefresh = xhr.getResponseHeader("X-IC-Refresh").split(",");
log(elt, "X-IC-Refresh: refreshing " + pathsToRefresh, "DEBUG");
@ -351,14 +357,19 @@ var Intercooler = Intercooler || (function() {
function beforeRequest(elt) {
elt.addClass('disabled');
elt.addClass('ic-request-in-flight');
elt.data('ic-request-in-flight', true);
}
function requestCleanup(indicator, elt) {
function requestCleanup(indicator, globalIndicator, elt) {
if (indicator.length > 0) {
hideIndicator(indicator);
}
if (globalIndicator.length > 0) {
hideIndicator(globalIndicator);
}
elt.removeClass('disabled');
elt.removeClass('ic-request-in-flight');
elt.data('ic-request-in-flight', false);
if (elt.data('ic-next-request')) {
elt.data('ic-next-request')["req"]();
@ -404,19 +415,19 @@ var Intercooler = Intercooler || (function() {
var names = [];
var values = [];
if (args) {
for (var i = 0; i < args.length; i++) {
names.push(args[i][0]);
values.push(args[i][1]);
}
for (var i = 0; i < args.length; i++) {
names.push(args[i][0]);
values.push(args[i][1]);
}
}
if (isIdentifier(script)) {
return window[script].apply(this, values);
return window[script].apply(this, values);
} else {
var outerfunc = window["eval"].call(
window,
'(function (' + names.join(", ") + ') {' + script + '})'
);
return outerfunc.apply(this, values);
var outerfunc = window["eval"].call(
window,
'(function (' + names.join(", ") + ') {' + script + '})'
);
return outerfunc.apply(this, values);
}
}
@ -439,12 +450,35 @@ var Intercooler = Intercooler || (function() {
return msg;
}
function getLocalURL(baseURL, paramsToPush, data) {
if (paramsToPush) {
baseURL = baseURL + "?";
var vars = {};
data.replace(/([^=&]+)=([^&]*)/gi, function(m,key,value) {
vars[key] = value;
});
$(paramsToPush.split(",")).each(function(index) {
var param = $.trim(this);
var value = vars[param] || "";
baseURL += (index == 0) ? "" : "&";
baseURL += param + "=" + value;
});
}
return baseURL;
}
function handleRemoteRequest(elt, type, url, data, success) {
beforeRequest(elt);
data = replaceOrAddMethod(data, type);
// Global spinner support
var globalIndicator = findGlobalIndicator(elt);
if (globalIndicator && globalIndicator.length > 0) {
showIndicator(globalIndicator);
}
// Spinner support
var indicator = findIndicator(elt);
if (indicator.length > 0) {
@ -479,17 +513,18 @@ var Intercooler = Intercooler || (function() {
}
maybeInvokeLocalAction(elt, "-beforeSend");
},
success: function(data, textStatus, xhr) {
triggerEvent(elt, "success.ic", [elt, data, textStatus, xhr, requestId]);
success: function(responseData, textStatus, xhr) {
triggerEvent(elt, "success.ic", [elt, responseData, textStatus, xhr, requestId]);
log(elt, "AJAX request " + requestId + " was successful.", "DEBUG");
var onSuccess = closestAttrValue(elt, 'ic-on-success');
if (onSuccess) {
if (globalEval(onSuccess, [["elt", elt], ["data", data], ["textStatus", textStatus], ["xhr", xhr]]) == false) {
if (globalEval(onSuccess, [["elt", elt], ["data", responseData], ["textStatus", textStatus], ["xhr", xhr]]) == false) {
return;
}
}
var beforeHeaders = new Date();
var oldTitle = document.title;
try {
if (processHeaders(elt, xhr)) {
log(elt, "Processed headers for request " + requestId + " in " + (new Date() - beforeHeaders) + "ms", "DEBUG");
@ -497,10 +532,12 @@ var Intercooler = Intercooler || (function() {
if (xhr.getResponseHeader("X-IC-PushURL") || closestAttrValue(elt, 'ic-push-url') == "true") {
try {
requestCleanup(indicator, elt); // clean up before snap-shotting HTML
var newUrl = xhr.getResponseHeader("X-IC-PushURL") || closestAttrValue(elt, 'ic-src');
requestCleanup(indicator, globalIndicator, elt); // clean up before snap-shotting HTML
var baseURL = closestAttrValue(elt, 'ic-src');
var paramsToPush = closestAttrValue(elt, 'ic-push-params');
var newUrl = xhr.getResponseHeader("X-IC-PushURL") || getLocalURL(baseURL, paramsToPush, data);
if(_history) {
_history.snapshotForHistory(newUrl);
_history.snapshotForHistory(newUrl, oldTitle);
} else {
throw "History support not enabled";
}
@ -509,11 +546,11 @@ var Intercooler = Intercooler || (function() {
}
}
success(data, textStatus, elt, xhr);
success(responseData, textStatus, elt, xhr);
log(elt, "Process content for request " + requestId + " in " + (new Date() - beforeSuccess) + "ms", "DEBUG");
}
triggerEvent(elt, "after.success.ic", [elt, data, textStatus, xhr, requestId]);
triggerEvent(elt, "after.success.ic", [elt, responseData, textStatus, xhr, requestId]);
maybeInvokeLocalAction(elt, "-success");
} catch (e) {
log(elt, "Error processing successful request " + requestId + " : " + formatError(e), "ERROR");
@ -531,7 +568,7 @@ var Intercooler = Intercooler || (function() {
},
complete: function(xhr, status) {
log(elt, "AJAX request " + requestId + " completed in " + (new Date() - requestStart) + "ms", "DEBUG");
requestCleanup(indicator, elt);
requestCleanup(indicator, globalIndicator, elt);
try {
if ($.contains(document, elt[0])) {
triggerEvent(elt, "complete.ic", [elt, data, status, xhr, requestId]);
@ -557,14 +594,24 @@ var Intercooler = Intercooler || (function() {
triggerEvent($(document), "beforeAjaxSend.ic", [ajaxSetup, elt]);
if(ajaxSetup.cancel) {
requestCleanup(indicator, elt);
requestCleanup(indicator, globalIndicator, elt);
} else {
$.ajax(ajaxSetup)
}
}
function findGlobalIndicator(elt) {
var indicator = $([]);
elt = $(elt);
var attr = closestAttrValue(elt, 'ic-global-indicator');
if (attr && attr !== "false") {
indicator = $(attr).first();
}
return indicator;
}
function findIndicator(elt) {
var indicator = null;
var indicator = $([]);
elt = $(elt);
if (getICAttribute(elt, 'ic-indicator')) {
indicator = $(getICAttribute(elt, 'ic-indicator')).first();
@ -612,14 +659,14 @@ var Intercooler = Intercooler || (function() {
return data;
}
function appendData(data, string, value) {
function appendData(data, key, value) {
if ($.type(data) === "string") {
if($.type(value) !== "string") {
value = JSON.stringify(value);
}
return data + "&" + string + "=" + encodeURIComponent(value);
return data + "&" + key + "=" + encodeURIComponent(value);
} else {
data.append(string, value);
data.append(key, value);
return data;
}
}
@ -727,6 +774,7 @@ var Intercooler = Intercooler || (function() {
});
} else {
processMacros(elt);
processEnhancement(elt);
processSources(elt);
processPolling(elt);
processEventSources(elt);
@ -846,6 +894,18 @@ var Intercooler = Intercooler || (function() {
}
}
function processEnhancement(elt) {
if (elt.closest('.ic-ignore').length == 0) {
if(closestAttrValue(elt, 'ic-enhance') === 'true') {
enhanceDomTree(elt);
} else {
elt.find(getICAttributeSelector('ic-enhance')).each(function(){
enhanceDomTree($(this));
});
}
}
}
function processEventSources(elt) {
if (elt.closest('.ic-ignore').length == 0) {
handleEventSource(elt);
@ -1024,14 +1084,15 @@ var Intercooler = Intercooler || (function() {
elt = $(elt);
if (getICAttribute(elt, 'ic-sse-src')) {
var evtSrcUrl = getICAttribute(elt, 'ic-sse-src');
var eventSource = initEventSource(elt, evtSrcUrl);
var evtSrcWithCredentials = getICAttribute(elt, 'ic-sse-with-credentials') === 'true';
var eventSource = initEventSource(elt, evtSrcUrl, evtSrcWithCredentials);
elt.data('ic-event-sse-source', eventSource);
elt.data('ic-event-sse-map', {});
}
}
function initEventSource(elt, evtSrcUrl) {
var eventSource = Intercooler._internal.initEventSource(evtSrcUrl);
function initEventSource(elt, evtSrcUrl, evtSrcWithCredentials) {
var eventSource = Intercooler._internal.initEventSource(evtSrcUrl, evtSrcWithCredentials);
eventSource.onmessage = function(e) {
processICResponse(e.data, elt, false);
};
@ -1069,7 +1130,9 @@ var Intercooler = Intercooler || (function() {
}
function handleTriggerOn(elt) {
if (getICAttribute(elt, 'ic-trigger-on')) {
var triggerOnValue = getICAttribute(elt, 'ic-trigger-on');
if (triggerOnValue) {
// record button or submit input click info
if(elt.is('form')) {
elt.on('click focus', 'input, button, select, textarea', function(e){
@ -1080,59 +1143,65 @@ var Intercooler = Intercooler || (function() {
}
});
}
if (getICAttribute(elt, 'ic-trigger-on') == 'load') {
fireICRequest(elt);
} else if (getICAttribute(elt, 'ic-trigger-on') == 'scrolled-into-view') {
initScrollHandler();
setTimeout(function() {
triggerEvent($(window), 'scroll');
}, 100); // Trigger a scroll in case element is already viewable
} else {
var triggerOn = getICAttribute(elt, 'ic-trigger-on').split(" ");
if(triggerOn[0].indexOf("sse:") == 0) {
//Server-sent event, find closest event source and register for it
var sourceElt = elt.closest(getICAttributeSelector('ic-sse-src'));
if(sourceElt) {
registerSSE(sourceElt, triggerOn[0].substr(4))
}
var triggerOnArray = triggerOnValue.split(",");
for (var i = 0; i < triggerOnArray.length; i++) {
var triggerOn = $.trim(triggerOnArray[i]);
var splitTriggerOn = triggerOn.split(" ");
var eventString = eventFor(splitTriggerOn[0], $(elt));
var eventModifier = splitTriggerOn[1];
if (triggerOn == 'load') {
fireICRequest(elt);
} else if (triggerOn == 'scrolled-into-view') {
initScrollHandler();
setTimeout(function() {
triggerEvent($(window), 'scroll');
}, 100); // Trigger a scroll in case element is already viewable
} else {
var triggerOn = getICAttribute($(elt), 'ic-trigger-on').split(" ");
var event = eventFor(triggerOn[0], $(elt));
$(getTriggeredElement(elt)).on(event, function(e) {
var onBeforeTrigger = closestAttrValue(elt, 'ic-on-beforeTrigger');
if (onBeforeTrigger) {
if (globalEval(onBeforeTrigger, [["elt", elt], ["evt", e], ["elt", elt]]) == false) {
log(elt, "ic-trigger cancelled by ic-on-beforeTrigger", "DEBUG");
if(eventString.indexOf("sse:") == 0) {
//Server-sent event, find closest event source and register for it
var sourceElt = elt.closest(getICAttributeSelector('ic-sse-src'));
if(sourceElt.length > 0) {
registerSSE(sourceElt, splitTriggerOn[0].substr(4))
}
} else {
$(getTriggeredElement(elt)).on(eventString, function(e) {
var onBeforeTrigger = closestAttrValue(elt, 'ic-on-beforeTrigger');
if (onBeforeTrigger) {
if (globalEval(onBeforeTrigger, [["elt", elt], ["evt", e], ["elt", elt]]) == false) {
log(elt, "ic-trigger cancelled by ic-on-beforeTrigger", "DEBUG");
return false;
}
}
if (eventModifier == 'changed') {
var currentVal = elt.val();
var previousVal = elt.data('ic-previous-val');
elt.data('ic-previous-val', currentVal);
if (currentVal != previousVal) {
fireICRequest(elt);
}
} else if (eventModifier == 'once') {
var alreadyTriggered = elt.data('ic-already-triggered');
elt.data('ic-already-triggered', true);
if (alreadyTriggered !== true) {
fireICRequest(elt);
}
} else {
fireICRequest(elt);
}
if (preventDefault(elt, e)) {
e.preventDefault();
return false;
}
return true;
});
if(eventString && (eventString.indexOf("timeout:") == 0)) {
var timeout = parseInterval(eventString.split(":")[1]);
setTimeout(function () {
$(getTriggeredElement(elt)).trigger(eventString);
}, timeout);
}
if (triggerOn[1] == 'changed') {
var currentVal = elt.val();
var previousVal = elt.data('ic-previous-val');
elt.data('ic-previous-val', currentVal);
if (currentVal != previousVal) {
fireICRequest(elt);
}
} else if (triggerOn[1] == 'once') {
var alreadyTriggered = elt.data('ic-already-triggered');
elt.data('ic-already-triggered', true);
if (alreadyTriggered !== true) {
fireICRequest(elt);
}
} else {
fireICRequest(elt);
}
if (preventDefault(elt, e)) {
e.preventDefault();
return false;
}
return true;
});
if(event && (event.indexOf("timeout:") == 0)) {
setTimeout(function () {
$(getTriggeredElement(elt)).trigger(event);
}, parseInterval(event.split(":")[1]));
}
}
}
@ -1178,6 +1247,7 @@ var Intercooler = Intercooler || (function() {
setIfAbsent(elt, 'ic-trigger-on', 'default');
setIfAbsent(elt, 'ic-deps', 'ignore');
}
if (macroIs(macro, 'ic-action')) {
setIfAbsent(elt, 'ic-trigger-on', 'default');
}
@ -1209,6 +1279,51 @@ var Intercooler = Intercooler || (function() {
}
}
function isLocalLink(anchor) {
return location.hostname === anchor[0].hostname &&
anchor.attr('href') &&
!anchor.attr('href').startsWith("#")
}
function enhanceAnchor(anchor) {
if (closestAttrValue(anchor, 'ic-enhance') === "true") {
if (isLocalLink(anchor)) {
setIfAbsent(anchor, 'ic-src', anchor.attr('href'));
setIfAbsent(anchor, 'ic-trigger-on', 'default');
setIfAbsent(anchor, 'ic-deps', 'ignore');
setIfAbsent(anchor, 'ic-push-url', 'true');
}
}
}
function determineFormVerb(form) {
return form.find('input[name="_method"]').val() || form.attr('method') || form[0].method;
}
function enhanceForm(form) {
if (closestAttrValue(form, 'ic-enhance') === "true") {
setIfAbsent(form, 'ic-src', form.attr('action'));
setIfAbsent(form, 'ic-trigger-on', 'default');
setIfAbsent(form, 'ic-deps', 'ignore');
setIfAbsent(form, 'ic-verb', determineFormVerb(form));
}
}
function enhanceDomTree(elt) {
if(elt.is('a')) {
enhanceAnchor(elt);
}
elt.find('a').each(function(){
enhanceAnchor($(this));
});
if(elt.is('form')){
enhanceForm(elt);
}
elt.find('form').each(function(){
enhanceForm($(this));
});
}
function setIfAbsent(elt, attr, value) {
if (getICAttribute(elt, attr) == null) {
setICAttribute(elt, attr, value);
@ -1222,7 +1337,7 @@ var Intercooler = Intercooler || (function() {
function isScrolledIntoView(elem) {
elem = $(elem);
if (elem.height() == 0 && elem.width() == 0) {
return false;
return false;
}
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
@ -1231,13 +1346,13 @@ var Intercooler = Intercooler || (function() {
var elemBottom = elemTop + elem.height();
return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom)
&& (elemBottom <= docViewBottom) && (elemTop >= docViewTop));
&& (elemBottom <= docViewBottom) && (elemTop >= docViewTop));
}
function maybeScrollToTarget(elt, target) {
if (closestAttrValue(elt, 'ic-scroll-to-target') != "false" &&
(closestAttrValue(elt, 'ic-scroll-to-target') == 'true' ||
closestAttrValue(target, 'ic-scroll-to-target') == 'true')) {
(closestAttrValue(elt, 'ic-scroll-to-target') == 'true' ||
closestAttrValue(target, 'ic-scroll-to-target') == 'true')) {
var offset = -50; // -50 px default offset padding
if (closestAttrValue(elt, 'ic-scroll-offset')) {
offset = parseInt(closestAttrValue(elt, 'ic-scroll-offset'));
@ -1352,6 +1467,19 @@ var Intercooler = Intercooler || (function() {
if (forHistory != true) {
maybeScrollToTarget(elt, target);
}
var switchClass = elt.closest(getICAttributeSelector('ic-switch-class'));
var classToSwitch = switchClass.attr(fixICAttributeName('ic-switch-class'));
if(classToSwitch) {
switchClass.children().removeClass(classToSwitch);
switchClass.children().each(function(){
if($.contains($(this)[0], $(elt)[0]) || $(this)[0] == $(elt)[0]) {
$(this).addClass(classToSwitch);
$(this).addClass(classToSwitch);
}
})
}
}
};
@ -1449,7 +1577,7 @@ var Intercooler = Intercooler || (function() {
}
}
function fireICRequest(elt, alternateHandler) {
function fireICRequest(elt, alternateHandler) {
elt = $(elt);
var triggerOrigin = elt;
@ -1628,9 +1756,9 @@ var Intercooler = Intercooler || (function() {
/* Instance Methods */
function historyConfigHasChanged(historySupportData) {
return historySupportData == null ||
historySupportData.slotLimit != slotLimit ||
historySupportData.historyVersion != historyVersion ||
historySupportData.lruList == null
historySupportData.slotLimit != slotLimit ||
historySupportData.historyVersion != historyVersion ||
historySupportData.lruList == null
}
function clearHistory() {
@ -1687,18 +1815,19 @@ var Intercooler = Intercooler || (function() {
storage.setItem(restorationData.id, content);
} catch (e) {
log(getTargetForHistory($('body')), "Unable to save intercooler history with entire history cleared, is something else eating " +
"local storage? History Limit:" + slotLimit, "ERROR");
"local storage? History Limit:" + slotLimit, "ERROR");
}
}
}
function makeHistoryEntry(html, yOffset, url) {
function makeHistoryEntry(html, yOffset, url, title) {
var restorationData = {
"url": url,
"id": HISTORY_SLOT_PREFIX + url,
"content": html,
"yOffset": yOffset,
"timestamp": new Date().getTime()
"timestamp": new Date().getTime(),
"title": title
};
updateLRUList(url);
// save to the history slot
@ -1724,18 +1853,18 @@ var Intercooler = Intercooler || (function() {
function updateHistory() {
if (_snapshot) {
pushUrl(_snapshot.newUrl, currentUrl(), _snapshot.oldHtml, _snapshot.yOffset);
pushUrl(_snapshot.newUrl, currentUrl(), _snapshot.oldHtml, _snapshot.yOffset, _snapshot.oldTitle);
_snapshot = null;
}
}
function pushUrl(newUrl, originalUrl, originalHtml, yOffset) {
function pushUrl(newUrl, originalUrl, originalHtml, yOffset, originalTitle) {
var historyEntry = makeHistoryEntry(originalHtml, yOffset, originalUrl);
var historyEntry = makeHistoryEntry(originalHtml, yOffset, originalUrl, originalTitle);
history.replaceState({"ic-id": historyEntry.id}, "", "");
var t = getTargetForHistory($('body'));
var restorationData = makeHistoryEntry(t.html(), window.pageYOffset, newUrl);
var restorationData = makeHistoryEntry(t.html(), window.pageYOffset, newUrl, document.title);
history.pushState({'ic-id': restorationData.id}, "", newUrl);
triggerEvent(t, "pushUrl.ic", [t, restorationData]);
@ -1748,7 +1877,12 @@ var Intercooler = Intercooler || (function() {
if (historyData) {
processICResponse(historyData["content"], getTargetForHistory($('body')), true);
if (historyData["yOffset"]) {
window.scrollTo(0, historyData["yOffset"])
setTimeout(function () {
window.scrollTo(0, historyData["yOffset"]);
}, 100);
}
if (historyData["title"]) {
document.title = historyData["title"];
}
return true;
} else {
@ -1771,13 +1905,14 @@ var Intercooler = Intercooler || (function() {
}
}
function snapshotForHistory(newUrl) {
function snapshotForHistory(newUrl, oldTitle) {
var t = getTargetForHistory($('body'));
triggerEvent(t, "beforeHistorySnapshot.ic", [t]);
_snapshot = {
newUrl: newUrl,
oldHtml: t.html(),
yOffset: window.pageYOffset
yOffset: window.pageYOffset,
oldTitle: oldTitle
};
}
@ -1897,12 +2032,11 @@ var Intercooler = Intercooler || (function() {
if($.zepto) {
$('body').data('zeptoDataTest', {});
if(typeof($('body').data('zeptoDataTest')) == "string") {
console.log("!!!! Please include the data module with Zepto! Intercooler requires full data support to function !!!!")
log(null,
"!!!! Please include the data module with Zepto! Intercooler requires full data support to function !!!!",
"ERROR")
}
}
if (location.search && location.search.indexOf("ic-launch-debugger=true") >= 0) {
Intercooler.debug();
}
}
$(function() {
@ -1922,27 +2056,22 @@ var Intercooler = Intercooler || (function() {
isDependent: isDependent,
getTarget: getTarget,
processHeaders: processHeaders,
startPolling: startPolling,
cancelPolling: cancelPolling,
setIsDependentFunction: function(func) {
_isDependentFunction = func;
},
ready: function(readyHandler) {
_readyHandlers.push(readyHandler);
},
debug: function() {
var debuggerUrl = closestAttrValue('body', 'ic-debugger-url') ||
"https://intercoolerreleases-leaddynocom.netdna-ssl.com/intercooler-debugger.js";
$.getScript(debuggerUrl)
.fail(function(jqxhr, settings, exception) {
log($('body'), formatError(exception), "ERROR");
});
},
_internal: {
init: init,
replaceOrAddMethod: replaceOrAddMethod,
initEventSource: function(url) {
return new EventSource(url);
initEventSource: function(url, withCredentials) {
return new EventSource(url, {withCredentials: withCredentials});
},
globalEval: globalEval
globalEval: globalEval,
getLocalURL: getLocalURL
}
};
})();

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -60,7 +60,7 @@
<a class="level-item is-size-7" href="/about">
About
</a>
<a class="level-item is-size-7" href="https://github.com/jfmcbrayer/brutaldon">
<a class="level-item is-size-7" href="https://gitlab.com/brutaldon/brutaldon">
Source
</a>
</div>

View File

@ -165,7 +165,7 @@
<div class="container">
{% block content %}
<h1 class="title">
Title
Brutaldon
</h1>
<p class="subtitle">
Brutaldon is totally a thing.
@ -180,9 +180,10 @@
<a class="level-item is-size-7" href="{% url "about" %}">
About
</a>
<a class="level-item is-size-7" href="https://github.com/jfmcbrayer/brutaldon">
<a class="level-item is-size-7" href="https://gitlab.com/brutaldon/brutaldon">
Source
</a>
<span class="level-item is-size-7" >Bookmarklet: <a href="{{ bookmarklet_url }}">Share via brutaldon</a></span>
</div>
<div class="level-right">
{% if preferences.theme.is_brutalist %}

View File

@ -3,10 +3,10 @@
{% block pagination %}
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
{% if prev %}
<a class="pagination-next" href="{% url 'local_prev' prev.min_id %}">Newer</a>
<a class="pagination-next" rel="next" href="{% url 'local_prev' prev.min_id %}">Newer</a>
{% endif %}
{% if next %}
<a class="pagination-previous" href="{% url 'local_next' next.max_id %}">Older</a>
<a class="pagination-previous" rel="prev" href="{% url 'local_next' next.max_id %}">Older</a>
{% endif %}
</nav>
{% endblock %}

View File

@ -1,6 +1,7 @@
{% extends "base.html" %}
{% load humanetime %}
{% load taglinks %}
{% load sanitizer %}
{% block title %}
Brutaldon ({{ own_acct.username }}) - Notifications timelime
@ -27,7 +28,7 @@ mastodon.notifications()[0]
{% if group.0.type == 'favourite' %}
<p>
{% for account in group.accounts %}
{% include "comma.html" %}{{ account.display_name }}
{% include "comma.html" %}{{ account.display_name | fix_emojos:account.emojis |strip_html |safe }}
(<a href="{{ account.url | localuser}}">{{ account.acct }}</a>)
{% endfor %}
favorited your toot.
@ -37,7 +38,7 @@ mastodon.notifications()[0]
{% elif group.0.type == 'reblog' %}
<p>
{% for account in group.accounts %}
{% include "comma.html" %}{{ account.display_name }}
{% include "comma.html" %}{{ account.display_name | fix_emojos:account.emojis |strip_html |safe }}
(<a href="{{ account.url | localuser }}">{{ account.acct }}</a>)
{% endfor %}
boosted your toot.
@ -49,7 +50,7 @@ mastodon.notifications()[0]
{% for note in group %}
{% if note.type == 'mention' %}
<p>
<strong>{{ note.account.display_name }}</strong>
<strong>{{ note.account.display_name | fix_emojos:note.account.emojis |strip_html |safe }}</strong>
(<a href="{{ note.account.url | localuser }}">{{ note.account.acct }}</a>)
mentioned you.
</p>
@ -58,7 +59,7 @@ mastodon.notifications()[0]
<hr class="is-hidden">
{% elif note.type == 'reblog' %}
<p>
{{ note.account.display_name }}
{{ note.account.display_name | fix_emojos:note.account.emojis |strip_html |safe }}
(<a href="{{ note.account.url | localuser }}">{{ note.account.acct }}</a>)
boosted your toot.
(<span>
@ -69,7 +70,7 @@ mastodon.notifications()[0]
<hr class="is-hidden">
{% elif note.type == 'favourite' %}
<p>
{{ note.account.display_name }}
{{ note.account.display_name | fix_emojos:note.account.emojis |strip_html |safe }}
(<a href="{{ note.account.url | localuser}}">{{ note.account.acct }}</a>)
favorited your toot.
(<span>
@ -87,7 +88,7 @@ mastodon.notifications()[0]
</figure>
<div class="media-content" >
<div class="content">
<strong>{{ note.account.display_name }}</strong>
<strong>{{ note.account.display_name | fix_emojos:note.account.emojis |strip_html |safe }}</strong>
(<a href="{{ note.account.url |localuser }}">{{ note.account.acct }}</a>)
followed you.
(<a href="{{ note.url }}">
@ -108,10 +109,10 @@ mastodon.notifications()[0]
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
{% if prev %}
<a class="pagination-next" href="{% url 'note_prev' prev.min_id %}">Newer</a>
<a class="pagination-next" rel="next" href="{% url 'note_prev' prev.min_id %}">Newer</a>
{% endif %}
{% if next %}
<a class="pagination-previous" href="{% url 'note_next' next.max_id %}">Older</a>
<a class="pagination-previous" rel="prev" href="{% url 'note_next' next.max_id %}">Older</a>
{% endif %}
</nav>

View File

@ -3,10 +3,10 @@
{% block pagination %}
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
{% if prev %}
<a class="pagination-next" href="{% url 'fed_prev' prev.min_id %}">Newer</a>
<a class="pagination-next" rel="next" href="{% url 'fed_prev' prev.min_id %}">Newer</a>
{% endif %}
{% if next %}
<a class="pagination-previous" href="{% url 'fed_next' next.max_id %}">Older</a>
<a class="pagination-previous" rel="prev" href="{% url 'fed_next' next.max_id %}">Older</a>
{% endif %}
</nav>
{% endblock %}

View File

@ -14,14 +14,15 @@
{% endblock %}
{% block content %}
<h1>Brutaldon ({{ own_acct.username }}) - {{ timeline_name }} timelime</h1>
{% if form %}
<h1 class="title">Post</h1>
<h2 class="title">Post</h2>
<div class="box">
{% include "main/post_minimal_partial.html" %}
</div>
<hr class="is-hidden">
{% endif %}
<h1 class="title">Your {{ timeline_name }} timeline</h1>
<h2 class="title">Your {{ timeline_name }} timeline</h2>
<div id="timeline">
{% for toot in toots %}
{% cache 600 toot_partial toot.id %}
@ -40,6 +41,7 @@
{% if next %}
<p class="column is-one-quarter">
<a class="pagination-previous is-fullwidth button"
rel="prev"
href="{% url 'home_next' next.max_id %}"
{% if preferences.click_to_load %}
ic-get-from="{% url 'home_next' next.max_id %}"
@ -57,6 +59,7 @@
{% if prev %}
<p class="column is-one-quarter">
<a class="pagination-next is-fullwidth button"
rel="next"
href="{% url 'home_prev' prev.min_id %}">
Newer
</a>

View File

@ -4,7 +4,7 @@
{% load taglinks %}
{% load static %}
{% if toot %}
{% if active %}
<article id="toot-{{toot.id}}" class="media box active-context">
{% else %}
@ -29,7 +29,7 @@
<div class="media-content">
<div class="content">
<p>
<strong>{{ toot.account.display_name | fix_emojos:toot.account.emojis | strip_html |safe}}</strong>
<h3><strong>{{ toot.account.display_name | fix_emojos:toot.account.emojis | strip_html |safe}}</strong></h3>
<small><a href="{% url "user" toot.account.acct %}">
@{{ toot.account.acct }}</a></small>
<a href="{{ toot.url }}">
@ -101,7 +101,7 @@
<figure class="column attachment-image">
<a href="{{ media.url }}">
<noscript class="loading-lazy">
{% if toot.sensitive %}
{% if toot.sensitive and not preferences.preview_sensitive %}
<img loading="lazy" src="{% static "images/sensitive.png" %}"
{% else %}
<img loading="lazy" src="{{ media.preview_url }}"
@ -127,7 +127,7 @@
<source src="{{ media.url }}" type="video/mp4">
<a href="{{ media.url }}">
<noscript class="loading-lazy">
{% if toot.sensitive %}
{% if toot.sensitive and not preferences.preview_sensitive %}
<img loading="lazy" src="{% static "images/sensitive.png" %}"
{% else %}
<img loading="lazy" src="{{ media.preview_url }}"
@ -214,7 +214,11 @@
delete
</a>
{% endif %}
{{ toot.visibility }}
{% if toot.local_only %}
{{ toot.visibility }} (local-only)
{% else %}
{{ toot.visibility }}
{% endif %}
&nbsp;&nbsp;
{% if toot.in_reply_to_id or toot.replies_count > 0 %}
<a class="level-item" href="{% url "thread" toot.id %}#toot-{{ toot.id }}">
@ -233,3 +237,4 @@
</div>
<div class="media-right"></div>
</article>
{% endif %}

View File

@ -120,10 +120,10 @@ Brutaldon ({{ own_acct.username }}) - {{ user.acct }} timelime
{% endfor %}
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
{% if prev %}
<a class="pagination-next" href="{% url 'user_prev' user.acct prev.min_id %}">Newer</a>
<a class="pagination-next" rel="next" href="{% url 'user_prev' user.acct prev.min_id %}">Newer</a>
{% endif %}
{% if next %}
<a class="pagination-previous" href="{% url 'user_next' user.acct next.max_id %}">Older</a>
<a class="pagination-previous" rel="prev" href="{% url 'user_next' user.acct next.max_id %}">Older</a>
{% endif %}
</nav>

View File

@ -2,173 +2,197 @@
{% load widget_tweaks %}
{% block content %}
<div class="container">
<h1 class="title">Settings</h1>
<form method="post" action="{% url "settings" %}" >
{% csrf_token %}
<div class="container">
<h1 class="title">Settings</h1>
<form method="post" action="{% url "settings" %}" >
{% csrf_token %}
<h2 class="subtitle">General Options</h2>
<div class="field">
<label class="label" for="id_theme">{{ form.theme.label }}</label>
<div class="control has-icons-left">
<div class="select">
{% render_field form.theme class+="select" %}
<span class="icon is-small is-left">
<span class="fa fa-paint-brush"></span>
</div>
<h2 class="subtitle">General Options</h2>
<div class="field">
<label class="label" for="id_theme">{{ form.theme.label }}</label>
<div class="control has-icons-left">
<div class="select">
{% render_field form.theme class+="select" %}
<span class="icon is-small is-left">
<span class="fa fa-paint-brush"></span>
</div>
</div>
</div>
<div class="field">
<label class="label" for="id_timezone">{{ form.timezone.label }}</label>
<div class="control has-icons-left">
<div class="select">
{% render_field form.timezone class+="select" %}
<span class="icon is-small is-left">
<span class="fa fa-clock-o"></span>
</div>
<div class="field">
<label class="label" for="id_timezone">{{ form.timezone.label }}</label>
<div class="control has-icons-left">
<div class="select">
{% render_field form.timezone class+="select" %}
<span class="icon is-small is-left">
<span class="fa fa-clock-o"></span>
</div>
</div>
</div>
<h2 class="subtitle">Timeline Options</h2>
<div class="field">
<label class="label checkbox">
{% render_field form.filter_replies %}
{{ form.filter_replies.label }}
<h2 class="subtitle">Content Options</h2>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_preview_sensitive">
{% render_field form.preview_sensitive class+="checkbox" %}
{{ form.preview_sensitive.label }}
</label>
</div>
<div class="field">
<label class="label checkbox"">
{% render_field form.filter_boosts %}
{{ form.filter_boosts.label }}
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.preview_sensitive.help_text }}
</p>
</div>
<div class="column is-half">
</div>
</div>
<h2 class="subtitle">Timeline Options</h2>
<div class="field">
<label class="label checkbox">
{% render_field form.filter_replies %}
{{ form.filter_replies.label }}
</label>
</div>
<div class="field">
<label class="label checkbox"">
{% render_field form.filter_boosts %}
{{ form.filter_boosts.label }}
</label>
</div>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_filter_notifications">
{% render_field form.filter_notifications class+="checkbox" %}
{{ form.filter_notifications.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.filter_notifications.help_text }}
</p>
</div>
<div class="column is-half">
<h2 class="subtitle">JavaScript Options</h2>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_no_javascript">
{% render_field form.no_javascript class+="checkbox" %}
{{ form.no_javascript.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.no_javascript.help_text }}
</p>
</div>
<div class="column is-half">
</div>
</div>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_bundle_notifications">
{% render_field form.bundle_notifications class+="checkbox" %}
{{ form.bundle_notifications.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.bundle_notifications.help_text }}
</p>
</div>
<div class="column is-half">
</div>
</div>
<h2 class="subtitle">JavaScript Options</h2>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_no_javascript">
{% render_field form.no_javascript class+="checkbox" %}
{{ form.no_javascript.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.no_javascript.help_text }}
</p>
</div>
<div class="column is-half">
</div>
</div>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_notifications">
{% render_field form.notifications class+="checkbox" %}
{{ form.notifications.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.notifications.help_text }}
</p>
</div>
<div class="column is-half">
</div>
</div>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_click_to_load">
{% render_field form.click_to_load class+="checkbox" %}
{{ form.click_to_load.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.click_to_load.help_text }}
</p>
</div>
<div class="column is-half">
</div>
</div>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_lightbox">
{% render_field form.lightbox class+="checkbox" %}
{{ form.lightbox.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.lightbox.help_text }}
</p>
</div>
<div class="column is-half">
</div>
</div>
<div class="columns">
<div class="column is-quarter">
<label class="label" for="id_poll_frequency">
{{ form.poll_frequency.label }}
</label>
<div class="control">
{% render_field form.poll_frequency class+="input" %}
</div>
</div>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_notifications">
{% render_field form.notifications class+="checkbox" %}
{{ form.notifications.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.notifications.help_text }}
</p>
</div>
<div class="column is-half">
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.poll_frequency.help_text }}
</p>
</div>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_click_to_load">
{% render_field form.click_to_load class+="checkbox" %}
{{ form.click_to_load.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.click_to_load.help_text }}
</p>
</div>
<div class="column is-half">
<div class="column is-half">
</div>
</div>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_lightbox">
{% render_field form.lightbox class+="checkbox" %}
{{ form.lightbox.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.lightbox.help_text }}
</p>
</div>
<div class="column is-half">
</div>
</div>
</div>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_filter_notifications">
{% render_field form.filter_notifications class+="checkbox" %}
{{ form.filter_notifications.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.filter_notifications.help_text }}
</p>
</div>
<div class="column is-half">
<div class="field">
<input type="submit" name="submit"
value="Save" class="button is-primary" >
</div>
</form>
</div>
</div>
<div class="columns">
<div class="column is-quarter">
<label class="label checkbox" for="id_bundle_notifications">
{% render_field form.bundle_notifications class+="checkbox" %}
{{ form.bundle_notifications.label }}
</label>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.bundle_notifications.help_text }}
</p>
</div>
<div class="column is-half">
<h2 class="subtitle">Bookmarklet</h2>
<p>
<a href="{{ bookmarklet_url }}">Share via brutaldon</a>
</p>
</div>
</div>
<h2 class="subtitle">Filters and More</h2>
<p><a href="{% url "list_filters" %}">List filters</a></p>
<p><a href="{% url "follow_requests" %}">Follow requests</a></p>
<div class="columns">
<div class="column is-quarter">
<label class="label" for="id_poll_frequency">
{{ form.poll_frequency.label }}
</label>
<div class="control">
{% render_field form.poll_frequency class+="input" %}
</div>
</div>
<div class="column is-quarter">
<p class="notification is-info preferences-help">
{{ form.poll_frequency.help_text }}
</p>
</div>
<div class="column is-half">
</div>
</div>
<div class="field">
<input type="submit" name="submit"
value="Save" class="button is-primary" >
</div>
</form>
<h2 class="subtitle">Filters and More</h2>
<p><a href="{% url "list_filters" %}">List filters</a></p>
<p><a href="{% url "follow_requests" %}">Follow requests</a></p>
</div>
</div>
{% endblock %}

View File

@ -45,8 +45,13 @@ urlpatterns = [
path("tags/<tag>", views.tag, name="tag"),
path("user/", views.home, name="user_bad"),
path("user/<username>", views.user, name="user"),
path("user/<username>/next/<int:next>", views.user, name="user_next"),
path("user/<username>/prev/<int:prev>", views.user, name="user_prev"),
# next/prev are integers, but pleroma uses 128 bit integers
# ...encoded in Base62.
# aka a "flake_id"
# from baseconv import base62, but we don't need to decode it
# just pass it along back to pleroma but it is NOT an <int:>
path("user/<username>/next/<next>", views.user, name="user_next"),
path("user/<username>/prev/<prev>", views.user, name="user_prev"),
path("toot/<mention>", views.toot, name="toot"),
path("toot", views.toot, name="toot"),
path("reply/<id>", views.reply, name="reply"),
@ -69,5 +74,6 @@ urlpatterns = [
path("accounts/", views.accounts, name="accounts"),
path("accounts/<id>", views.accounts, name="accounts"),
path("vote/<id>", views.vote, name="vote"),
path("share/", views.share, name="share"),
path("", views.home, name=""),
]

View File

@ -4,7 +4,6 @@ from django.conf import settings as django_settings
from django.shortcuts import render, redirect
from django.urls import reverse
from django.views.decorators.cache import never_cache, cache_page
from django.urls import reverse
from django.core.files.uploadhandler import TemporaryFileUploadHandler
from django.utils.translation import gettext as _
from brutaldon.forms import (
@ -17,6 +16,7 @@ from brutaldon.forms import (
from brutaldon.models import Client, Account, Preference, Theme
from mastodon import (
Mastodon,
MastodonIllegalArgumentError,
AttribAccessDict,
MastodonError,
MastodonAPIError,
@ -33,20 +33,26 @@ import re
class NotLoggedInException(Exception):
pass
class LabeledList(list):
"""A subclass of list that can accept additional attributes"""
def __new__(self, *args, **kwargs):
return super(LabeledList, self).__new__(self, args, kwargs)
def __init(self, *args, **kwargs):
if len(args) == 1 and hasattr(args[0], '__iter__'):
if len(args) == 1 and hasattr(args[0], "__iter__"):
list.__init__(self, args[0])
else:
list.__init__(self, args)
self.__dict__.update(kwargs)
def __call(self, **kwargs):
self.__dict__.update(kwargs)
return self
global sessons_cache
sessions_cache = {}
@ -66,7 +72,7 @@ def get_session(domain):
return s
def get_usercontext(request):
def get_usercontext(request, feature_set="mainline"):
if is_logged_in(request):
try:
client = Client.objects.get(api_base_id=request.session["active_instance"])
@ -85,6 +91,8 @@ def get_usercontext(request):
api_base_url=client.api_base_id,
session=get_session(client.api_base_id),
ratelimit_method="throw",
feature_set=feature_set,
version_check_mode='none',
)
return user, mastodon
else:
@ -98,6 +106,7 @@ def is_logged_in(request):
def _notes_count(account, mastodon):
if not mastodon:
return ""
mastodon.version_check_mode = 'none'
notes = mastodon.notifications(limit=40)
if account.preferences.filter_notifications:
notes = [
@ -128,7 +137,7 @@ def br_login_required(function=None, home_url=None, redirect_field_name=None):
def _dec(view_func):
def _view(request, *args, **kwargs):
if not is_logged_in(request):
def not_logged_in():
url = None
if redirect_field_name and redirect_field_name in request.REQUEST:
url = request.REQUEST[redirect_field_name]
@ -137,8 +146,15 @@ def br_login_required(function=None, home_url=None, redirect_field_name=None):
if not url:
url = "/"
return HttpResponseRedirect(url)
if not is_logged_in(request):
return not_logged_in()
else:
return view_func(request, *args, **kwargs)
try:
return view_func(request, *args, **kwargs)
except MastodonAPIError:
# mastodon must have expired our session
return not_logged_in()
_view.__name__ = view_func.__name__
_view.__dict__ = view_func.__dict__
@ -168,6 +184,7 @@ def user_search(request):
def user_search_inner(request, query):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
results = mastodon.search(query)
return render(
request,
@ -194,6 +211,7 @@ def timeline(
filter_context="home",
):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
data = mastodon.timeline(timeline, limit=40, max_id=max_id, min_id=min_id)
form = PostForm(
initial={"visibility": request.session["active_user"].source.privacy}
@ -243,6 +261,7 @@ def timeline(
def get_filters(mastodon, context=None):
try:
mastodon.version_check_mode = 'none'
if context:
return [ff for ff in mastodon.filters() if context in ff.context]
else:
@ -327,6 +346,7 @@ def forget_account(request, account_name):
def notes_count(request):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
count = _notes_count(account, mastodon)
return render(
request,
@ -358,6 +378,7 @@ def tag(request, tag):
account, mastodon = get_usercontext(request)
except NotLoggedInException:
return redirect(login)
mastodon.version_check_mode = 'none'
data = mastodon.timeline_hashtag(tag)
notifications = _notes_count(account, mastodon)
return render(
@ -387,8 +408,9 @@ def login(request):
redirect_uris = request.build_absolute_uri(reverse("oauth_callback"))
if form.is_valid():
api_base_url = form.cleaned_data["instance"]
if "gab.com" in api_base_url:
return redirect(django_settings.GAB_RICKROLL_URL)
resp = django_settings.CHECK_INSTANCE_URL(api_base_url, redirect)
if resp is not None:
return resp
tmp_base = parse.urlparse(api_base_url.lower())
if tmp_base.netloc == "":
api_base_url = parse.urlunparse(
@ -423,6 +445,7 @@ def login(request):
client_id=client.client_id,
client_secret=client.client_secret,
api_base_url=api_base_url,
version_check_mode='none',
)
client.version = mastodon.instance().get("version")
client.save()
@ -445,6 +468,7 @@ def oauth_callback(request):
client_id=request.session["active_client_id"],
client_secret=request.session["active_client_secret"],
api_base_url=request.session["active_instance"],
version_check_mode="none",
)
redirect_uri = request.build_absolute_uri(reverse("oauth_callback"))
access_token = mastodon.log_in(
@ -452,6 +476,7 @@ def oauth_callback(request):
)
request.session["access_token"] = access_token
user = mastodon.account_verify_credentials()
try:
account = Account.objects.get(
username=user.username + "@" + request.session["active_instance_hostname"]
@ -600,9 +625,14 @@ def note(request, next=None, prev=None):
account, mastodon = get_usercontext(request)
except NotLoggedInException:
return redirect(about)
last_seen = mastodon.notifications(limit=1)[0]
account.note_seen = last_seen.id
account.save()
mastodon.version_check_mode = 'none'
try:
last_seen = mastodon.notifications(limit=1)[0]
except IndexError:
pass
else:
account.note_seen = last_seen.id
account.save()
notes = mastodon.notifications(limit=40, max_id=next, min_id=prev)
filters = get_filters(mastodon, context="notifications")
@ -629,13 +659,22 @@ def note(request, next=None, prev=None):
# Now group notes into lists based on type and status
groups = []
if account.preferences.bundle_notifications:
def bundle_key(note):
return str(note.status.id) + note.type
try:
return str(note.status.id) + note.type
except:
return str(note.id) + note.type
def group_sort_key(group):
return max([k.id for k in group])
sorted_notes = sorted(notes, key=bundle_key, reverse=True)
for _, group in groupby(sorted_notes, bundle_key):
group = LabeledList(group)
group.accounts = [x.account for x in group]
groups.append(group)
groups.sort(key=group_sort_key, reverse=True)
else:
groups.append(notes)
@ -660,9 +699,9 @@ def note(request, next=None, prev=None):
@br_login_required
def thread(request, id):
account, mastodon = get_usercontext(request)
toot = mastodon.status(id)
root = toot
try:
toot = mastodon.status(id)
root = toot
context = mastodon.status_context(id)
if context.ancestors and len(context.ancestors) > 0:
root = context.ancestors[0]
@ -692,26 +731,44 @@ def thread(request, id):
)
def same_username(account, acct, username):
if acct == username:
return True
try:
user, host = username.split("@", 1)
except ValueError:
user, host = username, ""
myhost = account.username.split("@", 1)[1]
if acct == user and host == myhost:
return True
return False
@br_login_required
def user(request, username, prev=None, next=None):
try:
account, mastodon = get_usercontext(request)
except NotLoggedInException:
return redirect(about)
try:
user_dict = [
dict
for dict in mastodon.account_search(username)
if (
(dict.acct == username)
or (
dict.acct == username.split("@")[0]
and username.split("@")[1] == account.username.split("@")[1]
)
)
][0]
except (IndexError, AttributeError):
raise Http404(_("The user %s could not be found.") % username)
user_dict = None
mastodon.version_check_mode = 'none'
# pleroma currently flops if the user's not already locally known
# this is a BUG that they MUST FIX
# but until then, we might have to fallback to a regular search,
# if the account search fails to return results.
for dict in mastodon.account_search(username):
if not same_username(account, dict.acct, username):
continue
user_dict = dict
break
else:
for dict in mastodon.search(username, result_type="accounts").accounts:
if not same_username(account, dict.acct, username):
continue
user_dict = dict
break
else:
raise Http404(_("The user %s could not be found.") % username)
data = mastodon.account_statuses(user_dict.id, max_id=next, min_id=prev)
relationship = mastodon.account_relationships(user_dict.id)[0]
notifications = _notes_count(account, mastodon)
@ -754,21 +811,9 @@ def settings(request):
if request.method == "POST":
form = PreferencesForm(request.POST)
if form.is_valid():
account.preferences.theme = form.cleaned_data["theme"]
account.preferences.filter_replies = form.cleaned_data["filter_replies"]
account.preferences.filter_boosts = form.cleaned_data["filter_boosts"]
account.preferences.timezone = form.cleaned_data["timezone"]
account.preferences.no_javascript = form.cleaned_data["no_javascript"]
account.preferences.notifications = form.cleaned_data["notifications"]
account.preferences.click_to_load = form.cleaned_data["click_to_load"]
account.preferences.lightbox = form.cleaned_data["lightbox"]
account.preferences.filter_notifications = form.cleaned_data[
"filter_notifications"
]
account.preferences.bundle_notifications = form.cleaned_data[
"bundle_notifications"
]
account.preferences.poll_frequency = form.cleaned_data["poll_frequency"]
for field in account.preferences._fields:
if field in form.cleaned_data:
setattr(account.preferences, field, form.cleaned_data[field])
request.session["timezone"] = account.preferences.timezone
account.preferences.save()
account.save()
@ -796,10 +841,33 @@ def settings(request):
)
def status_post(account, request, mastodon, **kw):
while True:
try:
mastodon.status_post(**kw)
except MastodonIllegalArgumentError as e:
if not "is only available with feature set" in e.args[0]:
raise
feature_set = e.args[0].rsplit(" ", 1)[-1]
account, mastodon = get_usercontext(request, feature_set=feature_set)
continue
except TypeError:
# not sure why, but the old code retried status_post without a
# content_type keyword, if there was a TypeError
kw.pop("content_type")
continue
else:
break
return account, mastodon
@never_cache
@br_login_required
def toot(request, mention=None):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
if request.method == "GET":
if mention:
if not mention.startswith("@"):
@ -851,26 +919,22 @@ def toot(request, mention=None):
),
)
)
if form.cleaned_data["visibility"] == "":
form.cleaned_data["visibility"] = request.session[
"active_user"
].source.privacy
try:
try:
mastodon.status_post(
status=form.cleaned_data["status"],
visibility=form.cleaned_data["visibility"],
spoiler_text=form.cleaned_data["spoiler_text"],
media_ids=media_objects,
content_type="text/markdown",
)
except TypeError:
mastodon.status_post(
status=form.cleaned_data["status"],
visibility=form.cleaned_data["visibility"],
spoiler_text=form.cleaned_data["spoiler_text"],
media_ids=media_objects,
)
status_post(
account,
request,
mastodon,
status=form.cleaned_data["status"],
visibility=form.cleaned_data["visibility"],
spoiler_text=form.cleaned_data["spoiler_text"],
media_ids=media_objects,
content_type="text/markdown",
)
except MastodonAPIError as error:
form.add_error(
"",
@ -909,6 +973,7 @@ def toot(request, mention=None):
def redraft(request, id):
if request.method == "GET":
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
toot = mastodon.status(id)
toot_content = get_text(toot.content) # convert to plain text
# fix up white space
@ -944,6 +1009,7 @@ def redraft(request, id):
elif request.method == "POST":
form = PostForm(request.POST, request.FILES)
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
toot = mastodon.status(id)
if form.is_valid():
media_objects = []
@ -964,23 +1030,17 @@ def redraft(request, id):
"active_user"
].source.privacy
try:
try:
mastodon.status_post(
status=form.cleaned_data["status"],
visibility=form.cleaned_data["visibility"],
spoiler_text=form.cleaned_data["spoiler_text"],
media_ids=media_objects,
in_reply_to_id=toot.in_reply_to_id,
content_type="text/markdown",
)
except TypeError:
mastodon.status_post(
status=form.cleaned_data["status"],
visibility=form.cleaned_data["visibility"],
spoiler_text=form.cleaned_data["spoiler_text"],
media_ids=media_objects,
in_reply_to_id=toot.in_reply_to_id,
)
status_post(
account,
request,
mastodon,
status=form.cleaned_data["status"],
visibility=form.cleaned_data["visibility"],
spoiler_text=form.cleaned_data["spoiler_text"],
media_ids=media_objects,
in_reply_to_id=toot.in_reply_to_id,
content_type="text/markdown",
)
mastodon.status_delete(id)
except MastodonAPIError as error:
form.add_error(
@ -1036,8 +1096,12 @@ def safe_get_attachment(toot, index):
def reply(request, id):
if request.method == "GET":
account, mastodon = get_usercontext(request)
toot = mastodon.status(id)
context = mastodon.status_context(id)
mastodon.version_check_mode = 'none'
try:
toot = mastodon.status(id)
context = mastodon.status_context(id)
except MastodonNotFoundError:
raise Http404(_("Thread not found; the message may have been deleted."))
notifications = _notes_count(account, mastodon)
if toot.account.acct != request.session["active_user"].acct:
initial_text = "@" + toot.account.acct + " "
@ -1094,23 +1158,17 @@ def reply(request, id):
)
)
try:
try:
mastodon.status_post(
status=form.cleaned_data["status"],
visibility=form.cleaned_data["visibility"],
spoiler_text=form.cleaned_data["spoiler_text"],
media_ids=media_objects,
in_reply_to_id=id,
content_type="text/markdown",
)
except TypeError:
mastodon.status_post(
status=form.cleaned_data["status"],
visibility=form.cleaned_data["visibility"],
spoiler_text=form.cleaned_data["spoiler_text"],
media_ids=media_objects,
in_reply_to_id=id,
)
status_post(
account,
request,
mastodon,
status=form.cleaned_data["status"],
visibility=form.cleaned_data["visibility"],
spoiler_text=form.cleaned_data["spoiler_text"],
media_ids=media_objects,
in_reply_to_id=id,
content_type="text/markdown",
)
except MastodonAPIError as error:
form.add_error(
"",
@ -1154,10 +1212,43 @@ def reply(request, id):
return HttpResponseRedirect(reverse("reply", args=[id]) + "#toot-" + str(id))
@br_login_required
def share(request):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
if request.method == "GET":
params = request.GET
if request.method == "POST":
params = request.POST
title = params.get("title")
url = params.get("url")
if title:
initial_text = f"{title}\n\n{url}"
else:
initial_text = f"{url}"
form = PostForm(
initial={
"status": initial_text,
"visibility": request.session["active_user"].source.privacy,
}
)
return render(
request,
"main/post.html",
{
"form": form,
"own_acct": request.session["active_user"],
"preferences": account.preferences,
},
)
@never_cache
@br_login_required
def fav(request, id):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
toot = mastodon.status(id)
if request.method == "POST":
if not request.POST.get("cancel", None):
@ -1197,6 +1288,7 @@ def fav(request, id):
@br_login_required
def boost(request, id):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
toot = mastodon.status(id)
if request.method == "POST":
if not request.POST.get("cancel", None):
@ -1236,6 +1328,7 @@ def boost(request, id):
@br_login_required
def delete(request, id):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
toot = mastodon.status(id)
if request.method == "POST" or request.method == "DELETE":
if toot.account.acct != request.session["active_user"].acct:
@ -1262,6 +1355,7 @@ def delete(request, id):
@br_login_required
def follow(request, id):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
try:
user_dict = mastodon.account(id)
relationship = mastodon.account_relationships(user_dict.id)[0]
@ -1308,6 +1402,7 @@ def follow(request, id):
@br_login_required
def block(request, id):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
try:
user_dict = mastodon.account(id)
relationship = mastodon.account_relationships(user_dict.id)[0]
@ -1346,6 +1441,7 @@ def block(request, id):
@br_login_required
def mute(request, id):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
try:
user_dict = mastodon.account(id)
relationship = mastodon.account_relationships(user_dict.id)[0]
@ -1383,6 +1479,7 @@ def mute(request, id):
@br_login_required
def search(request):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
if request.GET.get("ic-request"):
return render(
request,
@ -1413,6 +1510,8 @@ def search_results(request):
else:
query = ""
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
results = mastodon.search(query)
notifications = _notes_count(account, mastodon)
return render(
@ -1449,6 +1548,7 @@ def about(request):
@cache_page(60 * 30)
def privacy(request):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
if account:
preferences = account.preferences
else:
@ -1467,6 +1567,7 @@ def privacy(request):
@br_login_required
def emoji_reference(request):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
emojos = mastodon.custom_emojis()
notifications = _notes_count(account, mastodon)
return render(
@ -1484,6 +1585,7 @@ def emoji_reference(request):
@br_login_required
def list_filters(request):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
filters = mastodon.filters()
return render(
request,
@ -1495,6 +1597,7 @@ def list_filters(request):
@br_login_required
def create_filter(request):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
if request.method == "POST":
form = FilterForm(request.POST)
if form.is_valid():
@ -1535,6 +1638,7 @@ def create_filter(request):
@br_login_required
def delete_filter(request, id):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
filter = mastodon.filter(id)
if request.method == "POST" or request.method == "DELETE":
@ -1559,6 +1663,7 @@ def delete_filter(request, id):
@br_login_required
def edit_filter(request, id):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
filter = mastodon.filter(id)
contexts = []
@ -1622,6 +1727,7 @@ def edit_filter(request, id):
@br_login_required
def follow_requests(request, id=None):
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
if request.method == "GET":
reqs = mastodon.follow_requests()
return render(
@ -1642,6 +1748,7 @@ def follow_requests(request, id=None):
@br_login_required
def accounts(request, id=None):
active_account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
if request.method == "GET":
accounts = [x for x in request.session.get("accounts_dict").values()]
return render(
@ -1684,6 +1791,7 @@ def vote(request, id):
return redirect("thread", id)
if request.method == "POST":
account, mastodon = get_usercontext(request)
mastodon.version_check_mode = 'none'
toot = mastodon.status(id)
poll = toot.poll
if not poll: