Compare commits

..

280 Commits

Author SHA1 Message Date
Corentin Bacqué-Cazenave
d5ad0fede5 Translated using Weblate (French)
Currently translated at 97.0% (650 of 670 strings)

Translation: TWBlue/TWBlue
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/twblue/fr/
2024-05-23 13:15:20 -06:00
52ee056759 Translated using Weblate (Spanish)
Currently translated at 99.4% (666 of 670 strings)

Translation: TWBlue/TWBlue
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/twblue/es/
2024-05-23 09:59:46 -06:00
fe43ce562c Feat: Read long posts from GUI 2024-05-22 17:55:52 -06:00
503bf72b11 change: Removed some settings that are no longer needed from our global settings dialog. 2024-05-22 17:42:23 -06:00
14a956d207 Change: Change the way sessions are named to put the account first, then the social network. 2024-05-22 16:28:06 -06:00
68651ff736 Feat: Open conversations from community timelines. 2024-05-21 12:50:43 -06:00
49c32ad4b8 doc: Updated changelog 2024-05-21 12:43:00 -06:00
bd69745cef Merge pull request #643 from Mohamed00/profileShortcuts
Added keyboard shortcuts to update profile dialog
2024-05-21 12:28:34 -06:00
Mohamed
404e545a6d Added keyboard shortcuts to update profile dialog 2024-05-21 13:36:10 -04:00
23468c7c63 Merge pull request #640 from MCV-Software/dependabot/pip/requests-2.32.1
Bump requests from 2.31.0 to 2.32.1
2024-05-20 17:38:29 -06:00
fb08352a91 Merge pull request #641 from MCV-Software/dependabot/pip/pytest-8.2.1
Bump pytest from 8.2.0 to 8.2.1
2024-05-20 17:38:04 -06:00
00b33550f4 Merge pull request #642 from MCV-Software/dependabot/pip/zipp-3.18.2
Bump zipp from 3.18.1 to 3.18.2
2024-05-20 17:37:35 -06:00
dependabot[bot]
1b61545375 ---
updated-dependencies:
- dependency-name: zipp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 23:10:10 +00:00
dependabot[bot]
31beabe86e ---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 23:10:04 +00:00
dependabot[bot]
bb531a41d0 ---
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 23:09:51 +00:00
6db1e0b79c fix: Display unicode characters on some dialogs 2024-05-19 19:05:03 -06:00
94ad7ce180 Fix: Added proper name to local and federated timelines for instances on startup. 2024-05-19 19:04:34 -06:00
manuelcortez
7112d0e159 Updated translation catalogs 2024-05-19 00:55:03 +00:00
5620635a36 Updated version files 2024-05-18 18:51:14 -06:00
24767437f5 Fixes 2024-05-18 18:41:33 -06:00
0858b17f17 Updated CI script 2024-05-18 18:34:14 -06:00
e91391a3fa More edits to update file 2024-05-18 18:25:43 -06:00
3320aa5509 Fixed a link 2024-05-18 18:19:52 -06:00
4bb21e15f8 Updated website of TWBlue on installer script 2024-05-18 18:07:43 -06:00
41d585226a Updated Release notes 2024-05-18 18:04:45 -06:00
9c2511561a Code cleanup 2024-05-18 17:24:28 -06:00
cd7279e83b Change: Added check for valid communities when creating a community timeline 2024-05-18 16:53:57 -06:00
533f15de55 Feat: Added support to display local and public timelines for remote instances 2024-05-18 14:17:06 -06:00
10d2c47f9a Updated code for handler 2024-05-17 17:46:49 -06:00
b39ccb9f2c Started implementation of community timelines 2024-05-17 17:45:47 -06:00
2a1d86f917 Feat: Added muting posts. 2024-05-17 16:38:26 -06:00
aee2a3b8b2 Feat: display users who boosted or favorited a post. 2024-05-17 13:58:54 -06:00
fd1a64c7b8 fix: Switch to Windows 11 keymap when running Windows 11. Closes #494 2024-05-16 10:52:11 -06:00
a5cd118b99 change: Improved translation services' settings. Closes #632 2024-05-16 08:58:41 -06:00
ee4f254825 Feat: Replaced old translator module. The new translator can translate by using LibreTranslate or DeepL with an user provided API key 2024-05-15 13:56:30 -06:00
manuelcortez
a1eb546f23 Updated translation catalogs 2024-05-12 00:55:02 +00:00
74360ac50f Change: on SessionManager, identify via nodeinfo which kind of platform we are talking with and create gts or mastodon sessions accordingly. 2024-05-11 18:06:44 -06:00
c05dc4b211 Feat: Added initial session to provide support to GoToSocial instances. 2024-05-11 18:05:29 -06:00
5ad29130b9 Change: Add type to mastodon default config so we can track which kind of session we currently detect on authorisation. 2024-05-11 18:02:04 -06:00
a322507f8b Change: Dropped googletrans as is no longer maintaned. 2024-05-11 01:23:46 -06:00
José Manuel Delicado
ea60f308c6 Merge pull request #634 from MCV-Software/dependabot/pip/hstspreload-2024.5.1
Bump hstspreload from 2024.3.1 to 2024.5.1
2024-05-07 07:38:11 +02:00
José Manuel Delicado
c784ca0cd5 Merge pull request #635 from MCV-Software/dependabot/pip/coverage-7.5.1
Bump coverage from 7.5.0 to 7.5.1
2024-05-07 07:37:18 +02:00
dependabot[bot]
04aa4b72f8 Bump coverage from 7.5.0 to 7.5.1
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.5.0 to 7.5.1.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.5.0...7.5.1)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-07 00:00:20 +00:00
dependabot[bot]
d4a6b95ea4 Bump hstspreload from 2024.3.1 to 2024.5.1
Bumps [hstspreload](https://github.com/sethmlarson/hstspreload) from 2024.3.1 to 2024.5.1.
- [Commits](https://github.com/sethmlarson/hstspreload/compare/2024.3.1...2024.5.1)

---
updated-dependencies:
- dependency-name: hstspreload
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-07 00:00:10 +00:00
manuelcortez
ce9c2b4456 Updated translation catalogs 2024-05-05 00:55:01 +00:00
José Manuel Delicado
7752fa6f58 Merge pull request #630 from MCV-Software/dependabot/pip/pytest-8.2.0
Bump pytest from 8.1.1 to 8.2.0
2024-04-30 19:12:44 +02:00
José Manuel Delicado
cfbdab9b9b Merge pull request #631 from MCV-Software/dependabot/pip/coverage-7.5.0
Bump coverage from 7.4.4 to 7.5.0
2024-04-30 18:59:10 +02:00
dependabot[bot]
8c43525d6a Bump coverage from 7.4.4 to 7.5.0
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.4.4 to 7.5.0.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.4.4...7.5.0)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 23:52:27 +00:00
dependabot[bot]
6321ae68b5 Bump pytest from 8.1.1 to 8.2.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.1.1 to 8.2.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.1.1...8.2.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 23:52:17 +00:00
manuelcortez
1552c412bb Updated translation catalogs 2024-04-28 00:55:00 +00:00
José Manuel Delicado
1b46c21aa0 Merge pull request #628 from MCV-Software/dependabot/pip/pluggy-1.5.0
Bump pluggy from 1.4.0 to 1.5.0
2024-04-23 07:36:13 +02:00
José Manuel Delicado
47f02ce0df Merge pull request #629 from MCV-Software/dependabot/pip/cx-freeze-7.0.0
Bump cx-freeze from 6.15.16 to 7.0.0
2024-04-23 07:35:54 +02:00
dependabot[bot]
72f10f3654 Bump cx-freeze from 6.15.16 to 7.0.0
Bumps [cx-freeze](https://github.com/marcelotduarte/cx_Freeze) from 6.15.16 to 7.0.0.
- [Release notes](https://github.com/marcelotduarte/cx_Freeze/releases)
- [Commits](https://github.com/marcelotduarte/cx_Freeze/compare/6.15.16...7.0.0)

---
updated-dependencies:
- dependency-name: cx-freeze
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 23:14:18 +00:00
dependabot[bot]
d2dbdb9a71 Bump pluggy from 1.4.0 to 1.5.0
Bumps [pluggy](https://github.com/pytest-dev/pluggy) from 1.4.0 to 1.5.0.
- [Changelog](https://github.com/pytest-dev/pluggy/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pluggy/compare/1.4.0...1.5.0)

---
updated-dependencies:
- dependency-name: pluggy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 23:14:06 +00:00
manuelcortez
ad2a800dad Updated translation catalogs 2024-04-21 00:54:58 +00:00
manuelcortez
174a5fad61 Updated translation catalogs 2024-04-14 00:54:58 +00:00
José Manuel Delicado
fa17af13af Merge pull request #627 from MCV-Software/dependabot/pip/idna-3.7
Bump idna from 3.6 to 3.7
2024-04-12 07:31:18 +02:00
dependabot[bot]
d2322afeba Bump idna from 3.6 to 3.7
Bumps [idna](https://github.com/kjd/idna) from 3.6 to 3.7.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst)
- [Commits](https://github.com/kjd/idna/compare/v3.6...v3.7)

---
updated-dependencies:
- dependency-name: idna
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-12 04:25:39 +00:00
manuelcortez
1c7ffb1f97 Updated translation catalogs 2024-04-07 00:54:55 +00:00
José Manuel Delicado
f1f83e875b Merge pull request #624 from MCV-Software/dependabot/pip/httpcore-1.0.5
Bump httpcore from 1.0.4 to 1.0.5
2024-04-02 07:33:46 +02:00
José Manuel Delicado
ed3b25ec85 Merge pull request #625 from MCV-Software/dependabot/pip/pillow-10.3.0
Bump pillow from 10.2.0 to 10.3.0
2024-04-02 07:33:28 +02:00
dependabot[bot]
3ccd9018ed Bump pillow from 10.2.0 to 10.3.0
Bumps [pillow](https://github.com/python-pillow/Pillow) from 10.2.0 to 10.3.0.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/10.2.0...10.3.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 23:19:55 +00:00
dependabot[bot]
5da35a3039 Bump httpcore from 1.0.4 to 1.0.5
Bumps [httpcore](https://github.com/encode/httpcore) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/encode/httpcore/releases)
- [Changelog](https://github.com/encode/httpcore/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/httpcore/compare/1.0.4...1.0.5)

---
updated-dependencies:
- dependency-name: httpcore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 23:19:47 +00:00
manuelcortez
11d9414670 Updated translation catalogs 2024-03-31 00:54:59 +00:00
José Manuel Delicado
6169bcfbc7 Merge pull request #622 from MCV-Software/dependabot/pip/importlib-metadata-7.1.0
Bump importlib-metadata from 7.0.2 to 7.1.0
2024-03-26 07:33:18 +01:00
José Manuel Delicado
82791fa648 Merge pull request #621 from MCV-Software/dependabot/pip/cx-logging-3.2.0
Bump cx-logging from 3.1.0 to 3.2.0
2024-03-26 07:33:02 +01:00
José Manuel Delicado
ada246863c Merge pull request #620 from MCV-Software/dependabot/pip/requests-oauthlib-2.0.0
Bump requests-oauthlib from 1.4.0 to 2.0.0
2024-03-26 07:32:46 +01:00
dependabot[bot]
1ac04422f9 Bump importlib-metadata from 7.0.2 to 7.1.0
Bumps [importlib-metadata](https://github.com/python/importlib_metadata) from 7.0.2 to 7.1.0.
- [Release notes](https://github.com/python/importlib_metadata/releases)
- [Changelog](https://github.com/python/importlib_metadata/blob/main/NEWS.rst)
- [Commits](https://github.com/python/importlib_metadata/compare/v7.0.2...v7.1.0)

---
updated-dependencies:
- dependency-name: importlib-metadata
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 23:09:54 +00:00
dependabot[bot]
184b000f78 Bump cx-logging from 3.1.0 to 3.2.0
Bumps [cx-logging](https://github.com/anthony-tuininga/cx_Logging) from 3.1.0 to 3.2.0.
- [Release notes](https://github.com/anthony-tuininga/cx_Logging/releases)
- [Commits](https://github.com/anthony-tuininga/cx_Logging/compare/v3.1.0...v3.2.0)

---
updated-dependencies:
- dependency-name: cx-logging
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 23:09:44 +00:00
dependabot[bot]
1aa2ded0bb Bump requests-oauthlib from 1.4.0 to 2.0.0
Bumps [requests-oauthlib](https://github.com/requests/requests-oauthlib) from 1.4.0 to 2.0.0.
- [Release notes](https://github.com/requests/requests-oauthlib/releases)
- [Changelog](https://github.com/requests/requests-oauthlib/blob/master/HISTORY.rst)
- [Commits](https://github.com/requests/requests-oauthlib/compare/v1.4.0...v2.0.0)

---
updated-dependencies:
- dependency-name: requests-oauthlib
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 23:09:37 +00:00
manuelcortez
eba2d34223 Updated translation catalogs 2024-03-24 00:55:05 +00:00
José Manuel Delicado
75e6027381 Merge pull request #615 from MCV-Software/dependabot/pip/coverage-7.4.4
Bump coverage from 7.4.3 to 7.4.4
2024-03-19 07:39:23 +01:00
dependabot[bot]
698b1d8ae8 Bump coverage from 7.4.3 to 7.4.4
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.4.3 to 7.4.4.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.4.3...7.4.4)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-19 06:38:25 +00:00
José Manuel Delicado
86dc45bc2b Merge pull request #616 from MCV-Software/dependabot/pip/markdown-3.6
Bump markdown from 3.5.2 to 3.6
2024-03-19 07:37:45 +01:00
José Manuel Delicado
74f403b750 Merge pull request #617 from MCV-Software/dependabot/pip/cx-freeze-6.15.16
Bump cx-freeze from 6.15.15 to 6.15.16
2024-03-19 07:37:26 +01:00
José Manuel Delicado
9422fd6fad Merge pull request #618 from MCV-Software/dependabot/pip/types-python-dateutil-2.9.0.20240316
Bump types-python-dateutil from 2.8.19.20240311 to 2.9.0.20240316
2024-03-19 07:37:04 +01:00
José Manuel Delicado
0546679452 Merge pull request #619 from MCV-Software/dependabot/pip/zipp-3.18.1
Bump zipp from 3.17.0 to 3.18.1
2024-03-19 07:36:50 +01:00
dependabot[bot]
4adf8e4a47 Bump zipp from 3.17.0 to 3.18.1
Bumps [zipp](https://github.com/jaraco/zipp) from 3.17.0 to 3.18.1.
- [Release notes](https://github.com/jaraco/zipp/releases)
- [Changelog](https://github.com/jaraco/zipp/blob/main/NEWS.rst)
- [Commits](https://github.com/jaraco/zipp/compare/v3.17.0...v3.18.1)

---
updated-dependencies:
- dependency-name: zipp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-18 23:29:32 +00:00
dependabot[bot]
69c6fb1ee6 Bump types-python-dateutil from 2.8.19.20240311 to 2.9.0.20240316
Bumps [types-python-dateutil](https://github.com/python/typeshed) from 2.8.19.20240311 to 2.9.0.20240316.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-python-dateutil
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-18 23:29:28 +00:00
dependabot[bot]
e59d3da0fb Bump cx-freeze from 6.15.15 to 6.15.16
Bumps [cx-freeze](https://github.com/marcelotduarte/cx_Freeze) from 6.15.15 to 6.15.16.
- [Release notes](https://github.com/marcelotduarte/cx_Freeze/releases)
- [Commits](https://github.com/marcelotduarte/cx_Freeze/compare/6.15.15...6.15.16)

---
updated-dependencies:
- dependency-name: cx-freeze
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-18 23:29:22 +00:00
dependabot[bot]
a3163f4a08 Bump markdown from 3.5.2 to 3.6
Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.5.2 to 3.6.
- [Release notes](https://github.com/Python-Markdown/markdown/releases)
- [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md)
- [Commits](https://github.com/Python-Markdown/markdown/compare/3.5.2...3.6)

---
updated-dependencies:
- dependency-name: markdown
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-18 23:29:17 +00:00
manuelcortez
b8bcff3157 Updated translation catalogs 2024-03-17 00:54:46 +00:00
José Manuel Delicado
72df3fa115 Merge pull request #610 from MCV-Software/dependabot/pip/importlib-metadata-7.0.2
Bump importlib-metadata from 7.0.1 to 7.0.2
2024-03-12 07:40:59 +01:00
José Manuel Delicado
afb9d0414e Merge pull request #611 from MCV-Software/dependabot/pip/types-python-dateutil-2.8.19.20240311
Bump types-python-dateutil from 2.8.19.20240106 to 2.8.19.20240311
2024-03-12 07:40:45 +01:00
José Manuel Delicado
327bc99924 Merge pull request #612 from MCV-Software/dependabot/pip/packaging-24.0
Bump packaging from 23.2 to 24.0
2024-03-12 07:40:18 +01:00
José Manuel Delicado
b8b93a900a Merge pull request #613 from MCV-Software/dependabot/pip/pytest-8.1.1
Bump pytest from 8.0.2 to 8.1.1
2024-03-12 07:40:04 +01:00
José Manuel Delicado
185d4c295b Merge pull request #614 from MCV-Software/dependabot/pip/requests-oauthlib-1.4.0
Bump requests-oauthlib from 1.3.1 to 1.4.0
2024-03-12 07:39:51 +01:00
dependabot[bot]
4c3ef2dda5 Bump requests-oauthlib from 1.3.1 to 1.4.0
Bumps [requests-oauthlib](https://github.com/requests/requests-oauthlib) from 1.3.1 to 1.4.0.
- [Release notes](https://github.com/requests/requests-oauthlib/releases)
- [Changelog](https://github.com/requests/requests-oauthlib/blob/master/HISTORY.rst)
- [Commits](https://github.com/requests/requests-oauthlib/compare/v1.3.1...v1.4.0)

---
updated-dependencies:
- dependency-name: requests-oauthlib
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 23:23:19 +00:00
dependabot[bot]
34ca6ca7d1 Bump pytest from 8.0.2 to 8.1.1
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.0.2 to 8.1.1.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.0.2...8.1.1)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 23:23:15 +00:00
dependabot[bot]
37728ce9ba Bump packaging from 23.2 to 24.0
Bumps [packaging](https://github.com/pypa/packaging) from 23.2 to 24.0.
- [Release notes](https://github.com/pypa/packaging/releases)
- [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/packaging/compare/23.2...24.0)

---
updated-dependencies:
- dependency-name: packaging
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 23:22:59 +00:00
dependabot[bot]
479b4f8a76 Bump types-python-dateutil from 2.8.19.20240106 to 2.8.19.20240311
Bumps [types-python-dateutil](https://github.com/python/typeshed) from 2.8.19.20240106 to 2.8.19.20240311.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-python-dateutil
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 23:22:55 +00:00
dependabot[bot]
0a853d9dfd Bump importlib-metadata from 7.0.1 to 7.0.2
Bumps [importlib-metadata](https://github.com/python/importlib_metadata) from 7.0.1 to 7.0.2.
- [Release notes](https://github.com/python/importlib_metadata/releases)
- [Changelog](https://github.com/python/importlib_metadata/blob/main/NEWS.rst)
- [Commits](https://github.com/python/importlib_metadata/compare/v7.0.1...v7.0.2)

---
updated-dependencies:
- dependency-name: importlib-metadata
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 23:22:49 +00:00
manuelcortez
9f1d3b1fb2 Updated translation catalogs 2024-03-10 00:54:45 +00:00
José Manuel Delicado
ba0fe23080 Merge pull request #603 from MCV-Software/dependabot/pip/httpx-0.27.0
Bump httpx from 0.26.0 to 0.27.0
2024-03-05 07:38:24 +01:00
dependabot[bot]
f06208106d Bump httpx from 0.26.0 to 0.27.0
Bumps [httpx](https://github.com/encode/httpx) from 0.26.0 to 0.27.0.
- [Release notes](https://github.com/encode/httpx/releases)
- [Changelog](https://github.com/encode/httpx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/httpx/compare/0.26.0...0.27.0)

---
updated-dependencies:
- dependency-name: httpx
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-05 06:34:47 +00:00
José Manuel Delicado
5e074d40cf Merge pull request #606 from MCV-Software/dependabot/pip/future-1.0.0
Bump future from 0.18.3 to 1.0.0
2024-03-05 07:33:59 +01:00
José Manuel Delicado
1eddebb775 Merge pull request #607 from MCV-Software/dependabot/pip/hstspreload-2024.3.1
Bump hstspreload from 2024.2.1 to 2024.3.1
2024-03-05 07:33:40 +01:00
José Manuel Delicado
9906047f16 Merge pull request #608 from MCV-Software/dependabot/pip/python-dateutil-2.9.0.post0
Bump python-dateutil from 2.8.2 to 2.9.0.post0
2024-03-05 07:33:19 +01:00
dependabot[bot]
6bccfe08b7 Bump python-dateutil from 2.8.2 to 2.9.0.post0
Bumps [python-dateutil](https://github.com/dateutil/dateutil) from 2.8.2 to 2.9.0.post0.
- [Release notes](https://github.com/dateutil/dateutil/releases)
- [Changelog](https://github.com/dateutil/dateutil/blob/master/NEWS)
- [Commits](https://github.com/dateutil/dateutil/compare/2.8.2...2.9.0.post0)

---
updated-dependencies:
- dependency-name: python-dateutil
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 23:40:22 +00:00
dependabot[bot]
ff25f7cbd2 Bump hstspreload from 2024.2.1 to 2024.3.1
Bumps [hstspreload](https://github.com/sethmlarson/hstspreload) from 2024.2.1 to 2024.3.1.
- [Commits](https://github.com/sethmlarson/hstspreload/compare/2024.2.1...2024.3.1)

---
updated-dependencies:
- dependency-name: hstspreload
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 23:40:13 +00:00
dependabot[bot]
98aeafa0cc Bump future from 0.18.3 to 1.0.0
Bumps [future](https://github.com/PythonCharmers/python-future) from 0.18.3 to 1.0.0.
- [Release notes](https://github.com/PythonCharmers/python-future/releases)
- [Changelog](https://github.com/PythonCharmers/python-future/blob/master/docs/changelog.rst)
- [Commits](https://github.com/PythonCharmers/python-future/compare/v0.18.3...v1.0.0)

---
updated-dependencies:
- dependency-name: future
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 23:40:03 +00:00
manuelcortez
32fbf0417c Updated translation catalogs 2024-03-03 00:54:47 +00:00
José Manuel Delicado
3ed52ae1a8 Merge pull request #601 from MCV-Software/dependabot/pip/sniffio-1.3.1
Bump sniffio from 1.3.0 to 1.3.1
2024-02-27 07:41:51 +01:00
José Manuel Delicado
9b8f989c35 Merge pull request #602 from MCV-Software/dependabot/pip/pytest-8.0.2
Bump pytest from 8.0.1 to 8.0.2
2024-02-27 07:41:36 +01:00
José Manuel Delicado
0b6d93b376 Merge pull request #604 from MCV-Software/dependabot/pip/httpcore-1.0.4
Bump httpcore from 1.0.3 to 1.0.4
2024-02-27 07:40:58 +01:00
José Manuel Delicado
b2282e47eb Merge pull request #605 from MCV-Software/dependabot/pip/coverage-7.4.3
Bump coverage from 7.4.0 to 7.4.3
2024-02-27 07:40:43 +01:00
dependabot[bot]
0e5fd4d2a1 Bump coverage from 7.4.0 to 7.4.3
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.4.0 to 7.4.3.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.4.0...7.4.3)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 23:21:00 +00:00
dependabot[bot]
d10fa02d75 Bump httpcore from 1.0.3 to 1.0.4
Bumps [httpcore](https://github.com/encode/httpcore) from 1.0.3 to 1.0.4.
- [Release notes](https://github.com/encode/httpcore/releases)
- [Changelog](https://github.com/encode/httpcore/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/httpcore/compare/1.0.3...1.0.4)

---
updated-dependencies:
- dependency-name: httpcore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 23:20:48 +00:00
dependabot[bot]
ce9f3c0be4 Bump pytest from 8.0.1 to 8.0.2
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.0.1 to 8.0.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.0.1...8.0.2)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 23:20:37 +00:00
dependabot[bot]
c404beda68 Bump sniffio from 1.3.0 to 1.3.1
Bumps [sniffio](https://github.com/python-trio/sniffio) from 1.3.0 to 1.3.1.
- [Commits](https://github.com/python-trio/sniffio/compare/v1.3.0...v1.3.1)

---
updated-dependencies:
- dependency-name: sniffio
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 23:20:28 +00:00
manuelcortez
e503766eee Updated translation catalogs 2024-02-25 00:54:56 +00:00
José Manuel Delicado
29188ead9c Merge pull request #598 from MCV-Software/dependabot/pip/httpcore-1.0.3
Bump httpcore from 1.0.2 to 1.0.3
2024-02-20 07:37:04 +01:00
José Manuel Delicado
39525f8cb3 Merge pull request #599 from MCV-Software/dependabot/pip/urllib3-2.2.1
Bump urllib3 from 2.2.0 to 2.2.1
2024-02-20 07:36:47 +01:00
José Manuel Delicado
d7ca5c3820 Merge pull request #600 from MCV-Software/dependabot/pip/pytest-8.0.1
Bump pytest from 8.0.0 to 8.0.1
2024-02-20 07:36:32 +01:00
dependabot[bot]
cb1d06f05d Bump pytest from 8.0.0 to 8.0.1
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.0.0 to 8.0.1.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.0.0...8.0.1)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 23:21:14 +00:00
dependabot[bot]
177cf1a896 Bump urllib3 from 2.2.0 to 2.2.1
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.2.0...2.2.1)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 23:21:03 +00:00
dependabot[bot]
d93620a74f Bump httpcore from 1.0.2 to 1.0.3
Bumps [httpcore](https://github.com/encode/httpcore) from 1.0.2 to 1.0.3.
- [Release notes](https://github.com/encode/httpcore/releases)
- [Changelog](https://github.com/encode/httpcore/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/httpcore/compare/1.0.2...1.0.3)

---
updated-dependencies:
- dependency-name: httpcore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 23:21:00 +00:00
manuelcortez
a571cef885 Updated translation catalogs 2024-02-18 00:54:56 +00:00
José Manuel Delicado
37f2870950 Merge pull request #595 from MCV-Software/dependabot/pip/lief-0.14.1
Bump lief from 0.14.0 to 0.14.1
2024-02-13 07:47:33 +01:00
José Manuel Delicado
7a31f8e474 Merge pull request #596 from MCV-Software/dependabot/pip/cx-freeze-6.15.15
Bump cx-freeze from 6.15.14 to 6.15.15
2024-02-13 07:47:16 +01:00
José Manuel Delicado
f5a9578331 Merge pull request #597 from MCV-Software/dependabot/pip/numpy-1.26.4
Bump numpy from 1.26.3 to 1.26.4
2024-02-13 07:46:59 +01:00
dependabot[bot]
c9e41a534e Bump numpy from 1.26.3 to 1.26.4
Bumps [numpy](https://github.com/numpy/numpy) from 1.26.3 to 1.26.4.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst)
- [Commits](https://github.com/numpy/numpy/compare/v1.26.3...v1.26.4)

---
updated-dependencies:
- dependency-name: numpy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 23:51:03 +00:00
dependabot[bot]
90df276479 Bump cx-freeze from 6.15.14 to 6.15.15
Bumps [cx-freeze](https://github.com/marcelotduarte/cx_Freeze) from 6.15.14 to 6.15.15.
- [Release notes](https://github.com/marcelotduarte/cx_Freeze/releases)
- [Commits](https://github.com/marcelotduarte/cx_Freeze/compare/6.15.14...6.15.15)

---
updated-dependencies:
- dependency-name: cx-freeze
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 23:50:53 +00:00
dependabot[bot]
b6a3324dc9 Bump lief from 0.14.0 to 0.14.1
Bumps [lief](https://github.com/sponsors/lief-project) from 0.14.0 to 0.14.1.
- [Commits](https://github.com/sponsors/lief-project/commits)

---
updated-dependencies:
- dependency-name: lief
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 23:50:47 +00:00
manuelcortez
ed83d6b839 Updated translation catalogs 2024-02-11 00:55:06 +00:00
d6d3f0810c Fix: Add search buffers inside searches in GUI 2024-02-07 12:14:06 -06:00
9f80891e1d Fix: Open actions dialogue from followers and following buffers. Closes #575 2024-02-07 12:04:24 -06:00
José Manuel Delicado
04853165b3 Merge pull request #591 from MCV-Software/dependabot/pip/certifi-2024.2.2
Bump certifi from 2023.11.17 to 2024.2.2
2024-02-06 07:42:59 +01:00
José Manuel Delicado
8777fde660 Merge pull request #592 from MCV-Software/dependabot/pip/hstspreload-2024.2.1
Bump hstspreload from 2024.1.5 to 2024.2.1
2024-02-06 07:42:38 +01:00
José Manuel Delicado
8a6d505205 Merge pull request #593 from MCV-Software/dependabot/pip/urllib3-2.2.0
Bump urllib3 from 2.1.0 to 2.2.0
2024-02-06 07:42:17 +01:00
José Manuel Delicado
e53848fd33 Merge pull request #594 from MCV-Software/dependabot/pip/cx-freeze-6.15.14
Bump cx-freeze from 6.15.13 to 6.15.14
2024-02-06 07:42:00 +01:00
dependabot[bot]
a533dccb38 Bump cx-freeze from 6.15.13 to 6.15.14
Bumps [cx-freeze](https://github.com/marcelotduarte/cx_Freeze) from 6.15.13 to 6.15.14.
- [Release notes](https://github.com/marcelotduarte/cx_Freeze/releases)
- [Commits](https://github.com/marcelotduarte/cx_Freeze/compare/6.15.13...6.15.14)

---
updated-dependencies:
- dependency-name: cx-freeze
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-05 23:43:28 +00:00
dependabot[bot]
7fb5ff3927 Bump urllib3 from 2.1.0 to 2.2.0
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.1.0 to 2.2.0.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.1.0...2.2.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-05 23:43:22 +00:00
dependabot[bot]
a8301c0df3 Bump hstspreload from 2024.1.5 to 2024.2.1
Bumps [hstspreload](https://github.com/sethmlarson/hstspreload) from 2024.1.5 to 2024.2.1.
- [Commits](https://github.com/sethmlarson/hstspreload/compare/2024.1.5...2024.2.1)

---
updated-dependencies:
- dependency-name: hstspreload
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-05 23:43:17 +00:00
dependabot[bot]
227b1934a8 Bump certifi from 2023.11.17 to 2024.2.2
Bumps [certifi](https://github.com/certifi/python-certifi) from 2023.11.17 to 2024.2.2.
- [Commits](https://github.com/certifi/python-certifi/compare/2023.11.17...2024.02.02)

---
updated-dependencies:
- dependency-name: certifi
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-05 23:43:13 +00:00
manuelcortez
4161392e12 Updated translation catalogs 2024-02-04 00:54:59 +00:00
José Manuel Delicado
9294310d65 Merge pull request #588 from MCV-Software/dependabot/pip/cx-freeze-6.15.13
Bump cx-freeze from 6.15.12 to 6.15.13
2024-01-30 07:37:00 +01:00
José Manuel Delicado
1a1248e445 Merge pull request #589 from MCV-Software/dependabot/pip/pluggy-1.4.0
Bump pluggy from 1.3.0 to 1.4.0
2024-01-30 07:36:31 +01:00
José Manuel Delicado
ad0a100838 Merge pull request #590 from MCV-Software/dependabot/pip/pytest-8.0.0
Bump pytest from 7.4.4 to 8.0.0
2024-01-30 07:36:10 +01:00
dependabot[bot]
30b87d447c Bump pytest from 7.4.4 to 8.0.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.4 to 8.0.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.4.4...8.0.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-29 23:50:08 +00:00
dependabot[bot]
085f63bcb5 Bump pluggy from 1.3.0 to 1.4.0
Bumps [pluggy](https://github.com/pytest-dev/pluggy) from 1.3.0 to 1.4.0.
- [Changelog](https://github.com/pytest-dev/pluggy/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pluggy/compare/1.3.0...1.4.0)

---
updated-dependencies:
- dependency-name: pluggy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-29 23:49:54 +00:00
dependabot[bot]
5ac71413ce Bump cx-freeze from 6.15.12 to 6.15.13
Bumps [cx-freeze](https://github.com/marcelotduarte/cx_Freeze) from 6.15.12 to 6.15.13.
- [Release notes](https://github.com/marcelotduarte/cx_Freeze/releases)
- [Commits](https://github.com/marcelotduarte/cx_Freeze/compare/6.15.12...6.15.13)

---
updated-dependencies:
- dependency-name: cx-freeze
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-29 23:49:50 +00:00
manuelcortez
9765a1d9f4 Updated translation catalogs 2024-01-28 00:54:48 +00:00
José Manuel Delicado
bcf05d344a Merge pull request #585 from MCV-Software/dependabot/pip/psutil-5.9.8
Bump psutil from 5.9.7 to 5.9.8
2024-01-23 07:44:38 +01:00
José Manuel Delicado
cfdc3abf08 Merge pull request #586 from MCV-Software/dependabot/pip/lief-0.14.0
Bump lief from 0.13.2 to 0.14.0
2024-01-23 07:43:54 +01:00
dependabot[bot]
7c93366b37 Bump lief from 0.13.2 to 0.14.0
Bumps [lief](https://github.com/sponsors/lief-project) from 0.13.2 to 0.14.0.
- [Commits](https://github.com/sponsors/lief-project/commits)

---
updated-dependencies:
- dependency-name: lief
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-22 23:31:00 +00:00
dependabot[bot]
614f2b3aaa Bump psutil from 5.9.7 to 5.9.8
Bumps [psutil](https://github.com/giampaolo/psutil) from 5.9.7 to 5.9.8.
- [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/release-5.9.7...release-5.9.8)

---
updated-dependencies:
- dependency-name: psutil
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-22 23:30:57 +00:00
manuelcortez
65d7ff8f8b Updated translation catalogs 2024-01-21 00:55:14 +00:00
José Manuel Delicado
70a2635dc2 Merge pull request #584 from MCV-Software/dependabot/pip/markdown-3.5.2
Bump markdown from 3.5.1 to 3.5.2
2024-01-16 07:35:00 +01:00
dependabot[bot]
eef6637f85 Bump markdown from 3.5.1 to 3.5.2
Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.5.1 to 3.5.2.
- [Release notes](https://github.com/Python-Markdown/markdown/releases)
- [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md)
- [Commits](https://github.com/Python-Markdown/markdown/compare/3.5.1...3.5.2)

---
updated-dependencies:
- dependency-name: markdown
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-15 23:17:05 +00:00
manuelcortez
cd5fb8747b Updated translation catalogs 2024-01-14 00:55:19 +00:00
José Manuel Delicado
cedbb77eb5 Merge pull request #580 from MCV-Software/dependabot/pip/hyperframe-6.0.1
Bump hyperframe from 5.2.0 to 6.0.1
2024-01-09 07:50:27 +01:00
dependabot[bot]
4f6f98f56f Bump hyperframe from 5.2.0 to 6.0.1
Bumps [hyperframe](https://github.com/python-hyper/hyperframe) from 5.2.0 to 6.0.1.
- [Changelog](https://github.com/python-hyper/hyperframe/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/python-hyper/hyperframe/compare/v5.2.0...v6.0.1)

---
updated-dependencies:
- dependency-name: hyperframe
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-09 06:49:34 +00:00
José Manuel Delicado
18a89df400 Merge pull request #579 from MCV-Software/dependabot/pip/httpcore-1.0.2
Bump httpcore from 0.9.1 to 1.0.2
2024-01-09 07:43:23 +01:00
José Manuel Delicado
935c7d7fa2 Merge pull request #578 from MCV-Software/dependabot/pip/h11-0.14.0
Bump h11 from 0.9.0 to 0.14.0
2024-01-09 07:42:08 +01:00
dependabot[bot]
dab7b42bee Bump httpcore from 0.9.1 to 1.0.2
Bumps [httpcore](https://github.com/encode/httpcore) from 0.9.1 to 1.0.2.
- [Release notes](https://github.com/encode/httpcore/releases)
- [Changelog](https://github.com/encode/httpcore/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/httpcore/compare/0.9.1...1.0.2)

---
updated-dependencies:
- dependency-name: httpcore
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-09 06:42:06 +00:00
dependabot[bot]
58749b83af Bump h11 from 0.9.0 to 0.14.0
Bumps [h11](https://github.com/python-hyper/h11) from 0.9.0 to 0.14.0.
- [Commits](https://github.com/python-hyper/h11/compare/v0.9.0...v0.14.0)

---
updated-dependencies:
- dependency-name: h11
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-09 06:41:50 +00:00
José Manuel Delicado
7249fbfc01 Merge pull request #577 from MCV-Software/dependabot/pip/idna-3.6
Bump idna from 2.10 to 3.6
2024-01-09 07:41:29 +01:00
José Manuel Delicado
e926d4f817 Merge pull request #576 from MCV-Software/dependabot/pip/h2-4.1.0
Bump h2 from 3.2.0 to 4.1.0
2024-01-09 07:40:22 +01:00
dependabot[bot]
870d6ed147 Bump idna from 2.10 to 3.6
Bumps [idna](https://github.com/kjd/idna) from 2.10 to 3.6.
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst)
- [Commits](https://github.com/kjd/idna/compare/v2.10...v3.6)

---
updated-dependencies:
- dependency-name: idna
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-08 23:50:57 +00:00
dependabot[bot]
6e10f8e31c Bump h2 from 3.2.0 to 4.1.0
Bumps [h2](https://github.com/python-hyper/h2) from 3.2.0 to 4.1.0.
- [Changelog](https://github.com/python-hyper/h2/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/python-hyper/h2/compare/v3.2.0...v4.1.0)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-08 23:50:53 +00:00
434328056e Merge branch 'next-gen' of github.com:mcv-software/twblue into next-gen 2024-01-07 18:30:52 -06:00
36420d6a5b Fix: Added missing parameter coming from WX Widgets to some calls. Fixes GUI buttons not responding when pressed in most buffers 2024-01-07 18:28:29 -06:00
José Manuel Delicado
ccd3edd2da Merge pull request #571 from MCV-Software/dependabot/pip/httpx-0.26.0
Bump httpx from 0.13.3 to 0.26.0
2024-01-07 10:10:02 +01:00
José Manuel Delicado
c3744fb680 Merge pull request #572 from MCV-Software/dependabot/pip/chardet-5.2.0
Bump chardet from 3.0.4 to 5.2.0
2024-01-07 10:05:49 +01:00
dependabot[bot]
4d89e8c437 Bump httpx from 0.13.3 to 0.26.0
Bumps [httpx](https://github.com/encode/httpx) from 0.13.3 to 0.26.0.
- [Release notes](https://github.com/encode/httpx/releases)
- [Changelog](https://github.com/encode/httpx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/httpx/compare/0.13.3...0.26.0)

---
updated-dependencies:
- dependency-name: httpx
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-07 09:03:43 +00:00
José Manuel Delicado
d3cc46b723 Merge pull request #573 from MCV-Software/dependabot/pip/rfc3986-2.0.0
Bump rfc3986 from 1.5.0 to 2.0.0
2024-01-07 10:03:26 +01:00
José Manuel Delicado
dc7d9b6923 Merge pull request #574 from MCV-Software/dependabot/pip/hpack-4.0.0
Bump hpack from 3.0.0 to 4.0.0
2024-01-07 10:02:46 +01:00
dependabot[bot]
22c5147c92 Bump hpack from 3.0.0 to 4.0.0
Bumps [hpack](https://github.com/python-hyper/hpack) from 3.0.0 to 4.0.0.
- [Changelog](https://github.com/python-hyper/hpack/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/python-hyper/hpack/compare/v3.0.0...v4.0.0)

---
updated-dependencies:
- dependency-name: hpack
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-07 01:49:41 +00:00
dependabot[bot]
dda3d21008 Bump rfc3986 from 1.5.0 to 2.0.0
Bumps [rfc3986](https://github.com/python-hyper/rfc3986) from 1.5.0 to 2.0.0.
- [Commits](https://github.com/python-hyper/rfc3986/compare/1.5.0...2.0.0)

---
updated-dependencies:
- dependency-name: rfc3986
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-07 01:49:38 +00:00
dependabot[bot]
871562fa4c Bump chardet from 3.0.4 to 5.2.0
Bumps [chardet](https://github.com/chardet/chardet) from 3.0.4 to 5.2.0.
- [Release notes](https://github.com/chardet/chardet/releases)
- [Commits](https://github.com/chardet/chardet/compare/3.0.4...5.2.0)

---
updated-dependencies:
- dependency-name: chardet
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-07 01:49:27 +00:00
88ddace811 Added dependabot config 2024-01-06 19:48:53 -06:00
8d1b0c73df Update Python requirements to include versions we currently use 2024-01-06 19:45:22 -06:00
3e3396687d Merge branch 'next-gen' of github.com:mcv-software/twblue into next-gen 2024-01-06 19:08:46 -06:00
c9c78105a5 Added info about new version on updater 2024-01-06 19:08:28 -06:00
manuelcortez
49898c4407 Updated translation catalogs 2024-01-07 00:55:13 +00:00
José Manuel Delicado
46c897221f Merge pull request #569 from Arfs6/fixed-generating-documentation-readme
Fixed documentation generation step in readme
2024-01-06 10:25:14 +01:00
Abdulqadir Ahmad
7a83e399fc documentation: Changed document_importer.py to documentation_importer.py in documentation generation of README 2024-01-06 08:34:12 +01:00
147e13bb26 More fixes 2024-01-05 17:28:17 -06:00
faa54108fa Second attempt 2024-01-05 17:13:02 -06:00
44c1dbe6e0 Fix portable creation 2024-01-05 17:04:51 -06:00
01563af114 Updated Changelog and release notes 2024-01-05 16:39:19 -06:00
cdcbcf754a Mastodon: Added actions for notifications. closes #517 2024-01-05 15:49:18 -06:00
3907777c91 fix: Handle empty notifications 2024-01-05 11:13:37 -06:00
fa0b6a63b9 Change: Updated text for some menu items on posts' contextual menu 2024-01-05 09:40:04 -06:00
9cfeacbd5a Core: Removed files no longer needed for TWBlue distributions 2023-12-31 20:08:58 -06:00
08f6ee7a1b Actions: Removed --draft flag for new releases 2023-12-31 20:03:14 -06:00
3e571c82d1 Update Readme 2023-12-31 19:51:00 -06:00
e835274ce9 Mastodon: TWBlue should be able to ignore deleted messages, so it will load everything else properly 2023-12-31 14:45:05 -06:00
8b5c47da28 Core: Updated website references for TWBlue 2023-12-31 12:20:06 -06:00
47271cd34d Core: Updated changelog and release info 2023-12-31 11:57:40 -06:00
2919c3b851 Actions: Typo fix 2023-12-31 00:51:36 -06:00
8388899481 Actions: Write new version info without the leading 'v' 2023-12-31 00:42:47 -06:00
acc17170f9 Actions: prepare a draft release 2023-12-31 00:36:06 -06:00
6f2642914c Actions: Get release version from pushed tag and use Release notes file to generate releases 2023-12-31 00:34:05 -06:00
ae56f879a8 Actions: Added Release Notes file 2023-12-31 00:33:30 -06:00
f8d3dfc37f Actions: Attempt to set TWBlue version based in pushed tag 2023-12-31 00:30:31 -06:00
3a867f0b82 Run new release workflow on pushed tags 2023-12-31 00:05:07 -06:00
2aff1076cb Merge branch 'next-gen' of github.com:mcv-software/twblue into next-gen 2023-12-31 00:03:56 -06:00
e0a4bd6a5d Core: Update information will be retrieved from GitHub repo only 2023-12-31 00:03:30 -06:00
manuelcortez
6e230b1216 Updated translation catalogs 2023-12-31 00:55:06 +00:00
19e18bcfe1 Merge pull request #567 from Arfs6/create-github-releases-action
Created release.yml - action file for automated release on push.
2023-12-30 11:11:21 -06:00
Abdulqadir Ahmad
6e91c6419c changed the command that creates a new release in the release workflow to create a general note and dropped the -p (pre release) flag. 2023-12-30 16:52:09 +01:00
Abdulqadir Ahmad
275f5e763b changed release workflow from releasing snapshot versions to "stable" versions. More info at https://github.com/MCV-Software/TWBlue/pull/567 2023-12-30 16:40:19 +01:00
Abdulqadir Ahmad
8d93c170e2 changed release.yml to run on pushes on next-gen branch 2023-12-29 19:01:14 +01:00
Abdulqadir Ahmad
7d66fa0695 Created release.yml - action file for automated release on push. 2023-12-29 18:10:26 +01:00
701c509cf4 Merge pull request #565 from Arfs6/profile-dialog-fixes
Show Profile dialog fixes
2023-12-27 12:32:50 -06:00
manuelcortez
4b988755e4 Updated translation catalogs 2023-12-24 00:55:03 +00:00
manuelcortez
1e8c893313 Updated translation catalogs 2023-12-17 00:55:13 +00:00
manuelcortez
6692d21269 Updated translation catalogs 2023-12-10 00:55:11 +00:00
José Manuel Delicado Alcolea
1547e14ed5 Updated version on updates.json to match current stable version. This should prevent unwanted behaviour on the updater when the website is offline 2023-12-07 23:03:59 +01:00
manuelcortez
712fd6574c Updated translation catalogs 2023-12-03 00:55:05 +00:00
Abdulqadir Ahmad
32718f5865 Merge branch 'next-gen' into profile-dialog-fixes
Merged updates from next-gen
2023-12-02 09:41:46 +01:00
Abdulqadir Ahmad
8c3fc2bbb8 updated show user profile dialog to download images in a separate thread 2023-12-02 08:50:57 +01:00
Abdulqadir Ahmad
80df3f254b Removed number of rows in grid sizer of show user profile dialog 2023-12-02 06:56:58 +01:00
manuelcortez
ce12eaad7b Updated translation catalogs 2023-11-26 00:55:04 +00:00
manuelcortez
6408d533e0 Updated translation catalogs 2023-11-19 00:55:06 +00:00
manuelcortez
a49003b340 Updated translation catalogs 2023-11-12 00:54:57 +00:00
manuelcortez
04e848b18c Updated translation catalogs 2023-11-05 00:55:09 +00:00
manuelcortez
3499162ffb Updated translation catalogs 2023-10-29 00:54:58 +00:00
manuelcortez
614de0c8e2 Updated translation catalogs 2023-10-22 00:54:57 +00:00
Riku
2993331f63 Translated using Weblate (Japanese)
Currently translated at 100.0% (727 of 727 strings)

Translation: TWBlue/TWBlue
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/twblue/ja/
2023-10-15 22:58:39 -05:00
manuelcortez
691f49fc04 Updated translation catalogs 2023-10-15 00:55:03 +00:00
6c43c419fe Mastodon: Added autocomplete users management in account settings; user autocomplete works already in posts 2023-10-11 11:44:43 -06:00
14f48e4bbe Mastodon: Added user autocompletion module (not yet implemented on GUI) 2023-10-11 11:29:39 -06:00
a5dba08b06 Updated changelog 2023-10-10 17:56:40 -06:00
d56c8b4372 Renamed showUserProfile to user_details to keep compatibility with invisible interface and naming pattern of functions 2023-10-10 17:43:31 -06:00
b3e0b21ee7 Merge pull request #540 from Arfs6/stop_update_running_source
stop updating while running from source
2023-10-10 16:50:11 -06:00
e6ad42de48 Merge pull request #555 from Arfs6/create-show-user-profile
created show user profile dialogh
2023-10-10 16:48:01 -06:00
manuelcortez
abb97448b0 Updated translation catalogs 2023-10-08 00:54:55 +00:00
manuelcortez
371a8969e6 Updated translation catalogs 2023-10-01 00:55:14 +00:00
manuelcortez
91e91d0e8d Updated translation catalogs 2023-09-24 00:55:00 +00:00
manuelcortez
ea42f5a1b2 Updated translation catalogs 2023-09-17 00:54:54 +00:00
manuelcortez
c1987c60e1 Updated translation catalogs 2023-09-10 00:54:48 +00:00
manuelcortez
312ec050ac Updated translation catalogs 2023-09-03 00:54:47 +00:00
Riku
c82f94c636 Translated using Weblate (Japanese)
Currently translated at 100.0% (684 of 684 strings)

Translation: TWBlue/TWBlue
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/twblue/ja/
2023-08-27 20:51:33 -05:00
Abdulqadir Ahmad
f4ec03099a added actions, following, followers and posts button to show user profile dialog 2023-08-27 17:56:39 +01:00
manuelcortez
1aea675fa5 Updated translation catalogs 2023-08-27 00:54:53 +00:00
Corentin Bacqué-Cazenave
f005352da0 Translated using Weblate (French)
Currently translated at 100.0% (684 of 684 strings)

Translation: TWBlue/TWBlue
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/twblue/fr/
2023-08-24 06:50:22 -05:00
manuelcortez
06d6e9a15b Updated translation catalogs 2023-08-20 00:54:47 +00:00
manuelcortez
9c37c32633 Updated translation catalogs 2023-08-13 00:54:56 +00:00
Abdulqadir Ahmad
15e2032afb added fields for created_at, locked, bot and discoverable 2023-08-12 10:32:29 +01:00
Abdulqadir Ahmad
35ba915be6 fix show user profile not working when a post have mentions 2023-08-10 19:15:34 +01:00
Abdulqadir Ahmad
1d8fefe7d3 created show user profile dialog 2023-08-10 17:19:58 +01:00
manuelcortez
d78335407a Updated translation catalogs 2023-08-06 00:49:23 +00:00
manuelcortez
f42ea96ce8 Updated translation catalogs 2023-07-30 00:55:18 +00:00
manuelcortez
a71bb716b2 Updated translation catalogs 2023-07-23 00:56:10 +00:00
manuelcortez
98c728cf48 Updated translation catalogs 2023-07-16 00:55:52 +00:00
53af099300 Mastodon: Create searches for posts during startup 2023-07-10 12:53:24 -06:00
7c5a41791c Mastodon: Added missing method to search buffer and removed some unneeded code 2023-07-10 12:52:42 -06:00
2cd90e8df1 Merge branch 'Arfs6-create_search_buffer' into next-gen 2023-07-10 09:59:27 -06:00
ceee7775c8 Fix conflicts 2023-07-10 09:58:08 -06:00
manuelcortez
4a256414d6 Updated translation catalogs 2023-07-09 00:56:01 +00:00
manuelcortez
94f48aa7fc Updated translation catalogs 2023-07-02 00:55:47 +00:00
1963c13b48 Merge pull request #547 from Arfs6/create_update_profile
Create update profile
2023-06-26 17:47:32 -06:00
manuelcortez
bb0edfdc65 Updated translation catalogs 2023-06-25 00:56:20 +00:00
Nikola Jović
23453ae6ab Translated using Weblate (Serbian)
Currently translated at 100.0% (667 of 667 strings)

Translation: TWBlue/TWBlue
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/twblue/sr/
2023-06-21 17:45:45 -05:00
Corentin Bacqué-Cazenave
bade40e8d7 Translated using Weblate (French)
Currently translated at 100.0% (667 of 667 strings)

Translation: TWBlue/TWBlue
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/twblue/fr/
2023-06-19 14:45:45 -05:00
manuelcortez
3080361275 Updated translation catalogs 2023-06-18 00:56:39 +00:00
manuelcortez
013bceeb64 Updated translation catalogs 2023-06-11 00:55:56 +00:00
Abdulqadir Ahmad
c31bf3f1b2 increased size of text boxes for better visual.push 2023-06-09 15:28:19 +01:00
Abdulqadir Ahmad
f5815d7911 Added check boxes for bot, private and discoverable 2023-06-09 14:38:22 +01:00
Abdulqadir Ahmad
6d7f808196 added support for updating header, avatar and the 4 fields 2023-06-09 10:53:11 +01:00
Abdulqadir Ahmad
453630e655 created basic update profile with support for name and bio 2023-06-08 00:40:38 +01:00
manuelcortez
61525023ce Updated translation catalogs 2023-06-04 00:56:16 +00:00
Abdulqadir Ahmad
f9b54ede81 fix updating while running from source if user checks for update from menu 2023-06-03 00:39:42 +01:00
Abdulqadir Ahmad
f0d6e8dcf9 created search buffer 2023-06-01 15:48:53 +01:00
Abdulqadir Ahmad
288286f21e now tw blue doesn't ask for updates when running from source 2023-05-30 09:26:29 +01:00
manuelcortez
6bd0161818 Updated translation catalogs 2023-05-28 00:55:31 +00:00
manuelcortez
5a7a5363ea Updated translation catalogs 2023-05-21 00:55:16 +00:00
manuelcortez
08efeeb5ed Updated translation catalogs 2023-05-14 00:55:21 +00:00
manuelcortez
2499b75bc2 Updated translation catalogs 2023-05-07 00:55:16 +00:00
Riku
7cfabca63d Translated using Weblate (Japanese)
Currently translated at 100.0% (667 of 667 strings)

Translation: TWBlue/TWBlue
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/twblue/ja/
2023-05-04 21:45:28 -05:00
manuelcortez
a00047b8a3 Updated translation catalogs 2023-04-30 00:55:38 +00:00
manuelcortez
805e33c44d Updated translation catalogs 2023-04-23 00:56:00 +00:00
zvonimir stanecic
283d4d9317 Translated using Weblate (Polish)
Currently translated at 100.0% (667 of 667 strings)

Translation: TWBlue/TWBlue
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/twblue/pl/
2023-04-21 02:45:24 -05:00
manuelcortez
b437e46101 Updated translation catalogs 2023-04-16 00:55:11 +00:00
Nikola Jović
47681d7c9d Translated using Weblate (Serbian)
Currently translated at 100.0% (667 of 667 strings)

Translation: TWBlue/TWBlue
Translate-URL: https://weblate.mcvsoftware.com/projects/twblue/twblue/sr/
2023-04-14 14:45:23 -05:00
bea4096d16 Remove keys directory from setup file 2023-04-13 12:55:43 -06:00
6135412d4b Removed twitter config include from setup script 2023-04-13 12:38:46 -06:00
123 changed files with 22957 additions and 15815 deletions

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

49
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
# Release a new TW Blue installer on github.
# This workflow runs on push.
name: Release
on:
push:
tags:
- v20*
workflow_dispatch:
jobs:
build:
# Builds an x64 binary and an installer of TW Blue.
runs-on: windows-latest
steps:
- name: clone repo
uses: actions/checkout@v4
with:
submodules: true
- name: Get python interpreter
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install python packages
run: python -m pip install -r requirements.txt
- name: Build binary
run: |
.\scripts\build.ps1
mv src/dist scripts\TWBlue64
- name: make installer
run: |
cd scripts
makensis twblue.nsi
- name: Create portable
working-directory: scripts\TWBlue64
run: |
7z a -tzip TWBlue_portable.zip .
- name: Create new release
env:
gh_token: ${{ github.token }}
run: |
mkdir .release-assets
mv scripts\TWBlue_setup.exe .release-assets\TWBlue_setup_${{github.ref_name}}.exe
mv scripts\TWBlue64\TWBlue_portable.zip .release-assets\TWBlue_portable_${{github.ref_name}}.zip
gh release create "${{github.ref_name}}" -F "release-notes.md" -t "${{github.ref_name}}" .release-assets\TWBlue_setup_${{github.ref_name}}.exe .release-assets\TWBlue_portable_${{github.ref_name}}.zip

3
.gitignore vendored
View File

@@ -20,4 +20,5 @@ release-snapshot/
src/com_cache/ src/com_cache/
doc/strings.py doc/strings.py
doc/changelog.py doc/changelog.py
env/ env/
version.txt

View File

@@ -1,164 +0,0 @@
variables:
GIT_SUBMODULE_STRATEGY: recursive
PYTHON: "C:\\python310\\python.exe"
PYTHON37: "C:\\python37\\python.exe" # for Windows 7 support.
NSIS: "C:\\program files (x86)\\nsis\\makensis.exe"
stages:
- build
- make_installer
- upload
twblue32:
tags:
- shared-windows
- windows
- windows-1809
before_script:
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
- choco install python --version 3.10.8 -y -ForceX86
- '&$env:PYTHON -V'
- '&$env:PYTHON -m pip install --upgrade pip'
- '&$env:PYTHON -m pip install --upgrade https://github.com/josephsl/wxpy32whl/blob/main/wxPython-4.2.0-cp310-cp310-win32.whl?raw=true'
- '&$env:PYTHON -m pip install --upgrade -r requirements.txt'
stage: build
interruptible: true
script:
# Create html documentation firstly.
- cd doc
- '&$env:PYTHON documentation_importer.py'
- cd ..\src
- '&$env:PYTHON ..\doc\generator.py'
- '&$env:PYTHON write_version_data.py'
- '&$env:PYTHON setup.py build'
- cd ..
- mkdir artifacts
- cd scripts
- '&$env:PYTHON make_archive.py'
- cd ..
- mv src/dist artifacts/TWBlue
- move src/twblue.zip artifacts/twblue_x86.zip
# Move the generated script nsis file to artifacts, so we won't need python when generating the installer.
- move scripts/twblue.nsi artifacts/twblue.nsi
only:
- tags
artifacts:
paths:
- artifacts
expire_in: 1 day
twblue64:
tags:
- shared-windows
- windows
- windows-1809
before_script:
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
- choco install python --version 3.10.8 -y
- '&$env:PYTHON -V'
- '&$env:PYTHON -m pip install --upgrade pip'
- '&$env:PYTHON -m pip install --upgrade -r requirements.txt'
stage: build
interruptible: true
script:
# Create html documentation firstly.
- cd doc
- '&$env:PYTHON documentation_importer.py'
- cd ..\src
- '&$env:PYTHON ..\doc\generator.py'
- '&$env:PYTHON write_version_data.py'
- New-Item "appkeys.py" -ItemType File -Value "twitter_api_key='$TWITTER_API_KEY'`ntwitter_api_secret='$TWITTER_API_SECRET'"
- '&$env:PYTHON setup.py build'
- cd ..
- mkdir artifacts
- cd scripts
- '&$env:PYTHON make_archive.py'
- cd ..
- mv src/dist artifacts/TWBlue64
- move src/twblue.zip artifacts/twblue_x64.zip
only:
- tags
artifacts:
paths:
- artifacts
expire_in: 1 day
twblueWin7:
tags:
- shared-windows
- windows
- windows-1809
before_script:
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
- choco install python --version 3.7.9 -y -ForceX86
- '&$env:PYTHON37 -V'
- '&$env:PYTHON37 -m pip install --upgrade pip'
- '&$env:PYTHON37 -m pip install --upgrade https://github.com/josephsl/wxpy32whl/blob/main/wxPython-4.2.0-cp37-cp37m-win32.whl?raw=true'
- '&$env:PYTHON37 -m pip install --upgrade -r requirements.txt'
stage: build
interruptible: true
script:
# Create html documentation firstly.
- cd doc
- '&$env:PYTHON37 documentation_importer.py'
- cd ..\src
- '&$env:PYTHON37 ..\doc\generator.py'
- '&$env:PYTHON37 write_version_data.py'
- New-Item "appkeys.py" -ItemType File -Value "twitter_api_key='$TWITTER_API_KEY'`ntwitter_api_secret='$TWITTER_API_SECRET'"
- '&$env:PYTHON37 setup.py build'
- cd ..
- mkdir artifacts
- cd scripts
- '&$env:PYTHON37 make_archive.py'
- cd ..
- move src/twblue.zip artifacts/twblue_windows7_x86.zip
only:
- tags
artifacts:
paths:
- artifacts
expire_in: 1 day
generate_versions:
stage: make_installer
tags:
- shared-windows
- windows
- windows-1809
before_script:
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
- choco install nsis -y -ForceX86
script:
- move artifacts/TWBlue scripts/
- move artifacts/TWBlue64 scripts/
- move artifacts/twblue.nsi scripts/installer.nsi
- cd scripts
- '&$env:NSIS installer.nsi'
- move twblue_setup.exe ../artifacts
only:
- tags
artifacts:
paths:
- artifacts
expire_in: 1 day
upload:
stage: upload
tags:
- linux
image: python
interruptible: true
script:
- cd artifacts
- python ../scripts/upload.py
only:
- tags
- schedules

View File

@@ -1,11 +1,11 @@
TWBlue TWBlue
====== ======
[![Build status](https://ci.appveyor.com/api/projects/status/fml5fu7h1fj8vf6l?svg=true)](https://ci.appveyor.com/project/manuelcortez/twblue) ![Release status badge](https://github.com/mcv-software/twblue/actions/workflows/release.yml/badge.svg)
TWBlue is a free and open source application that allows you to interact with the main features of mastodon from the comfort of a windows software, with 2 different interfaces specially designed for screen reader users. TWBlue is a free and open source application that allows you to interact with the main features of mastodon from the comfort of a windows software, with 2 different interfaces specially designed for screen reader users.
See [TWBlue's webpage](https://twblue.es) for more details. See [TWBlue's webpage](https://twblue.mcvsoftware.com) for more details.
## Running TWBlue from source ## Running TWBlue from source
@@ -22,7 +22,6 @@ Although most dependencies can be found in the windows-dependencies directory, w
#### Dependencies packaged in windows installers #### Dependencies packaged in windows installers
* [Python,](https://python.org) version 3.10.8 * [Python,](https://python.org) version 3.10.8
If you want to build both x86 and x64 binaries, you can install python x64 to C:\python310 and python x86 to C:\python310-32, for example.
#### Dependencies that must be installed using pip #### Dependencies that must be installed using pip
@@ -33,10 +32,6 @@ Python installs a tool called Pip that allows to install packages in a simple wa
You can also add the scripts folder to your path environment variable or choose the corresponding option when installing Python. You can also add the scripts folder to your path environment variable or choose the corresponding option when installing Python.
Note: pip and setuptools are included in the Python installer since version 2.7.9. Note: pip and setuptools are included in the Python installer since version 2.7.9.
Note: If you are using Python for 32-bit systems, you will need to install WXPython for 32-bits before running the command for installing everything else. You can do so by running the following command:
`pip install --upgrade https://github.com/josephsl/wxpy32whl/blob/main/wxPython-4.2.0-cp310-cp310-win32.whl?raw=true`
Pip is able to install packages listed in a special text file, called the requirements file. To install all remaining dependencies, perform the following command: Pip is able to install packages listed in a special text file, called the requirements file. To install all remaining dependencies, perform the following command:
`pip install -r requirements.txt` `pip install -r requirements.txt`
@@ -74,13 +69,13 @@ Now that you have installed all these packages, you can run TW Blue from source
`python main.py` `python main.py`
If necessary, change the first part of the command to reflect the location of your python executable. You can run TW Blue using python x86 and x64. If necessary, change the first part of the command to reflect the location of your python executable.
### Generating the documentation ### Generating the documentation
To generate the documentation in html format, navigate to the doc folder inside this repo. After that, run these commands: To generate the documentation in html format, navigate to the doc folder inside this repo. After that, run these commands:
`python document_importer.py` `python documentation_importer.py`
`python generator.py` `python generator.py`
The documentation will be generated, placing each language in a separate folder in the doc directory. Move these folders (for example `de`, `en`, `es`, `fr`, `it`, ...) to `src/documentation`, creating the directory if necessary. The documentation will be generated, placing each language in a separate folder in the doc directory. Move these folders (for example `de`, `en`, `es`, `fr`, `it`, ...) to `src/documentation`, creating the directory if necessary.
@@ -100,10 +95,8 @@ To build it, run the following command from the src folder:
If you want to install TWBlue on your computer, you must create the installer first. Follow these steps: If you want to install TWBlue on your computer, you must create the installer first. Follow these steps:
* Navigate to the src directory, and create a binary version for x86: C:\python37\python setup.py build * Navigate to the src directory, and create a binary version: C:\python310\python setup.py build
* Move the dist directory to the scripts folder in this repo, and rename it to twblue * Move the dist directory to the scripts folder in this repo, and rename it to twblue64
* Repeat these steps with Python for x64: C:\python37x64\python setup.py build
* Move the new dist directory to the scripts folder, and rename it to twblue64
* Go to the scripts folder, right click on the twblue.nsi file, and choose compyle unicode NSIS script * Go to the scripts folder, right click on the twblue.nsi file, and choose compyle unicode NSIS script
* This may take a while. After the process, you will find the installer in the scripts folder * This may take a while. After the process, you will find the installer in the scripts folder
@@ -111,6 +104,8 @@ If you want to install TWBlue on your computer, you must create the installer fi
To manage translations in TWBlue, you can install the [Babel package.](https://pypi.org/project/Babel/) You can extract message catalogs and generate the main template file with the following command: To manage translations in TWBlue, you can install the [Babel package.](https://pypi.org/project/Babel/) You can extract message catalogs and generate the main template file with the following command:
```bash
pybabel extract -o twblue.pot --msgid-bugs-address "manuel@manuelcortez.net" --copyright-holder "MCV software" --input-dirs ..\src pybabel extract -o twblue.pot --msgid-bugs-address "manuel@manuelcortez.net" --copyright-holder "MCV software" --input-dirs ..\src
```
Take into account, though, that we use [weblate](https://weblate.mcvsoftware.com) to track translation work for TWBlue. If you wish to be part of our translation team, please open an issue so we can create an account for you in Weblate. Take into account, though, that we use [weblate](https://weblate.mcvsoftware.com) to track translation work for TWBlue. If you wish to be part of our translation team, please open an issue so we can create an account for you in Weblate.

View File

@@ -2,6 +2,50 @@ TWBlue Changelog
## changes in this version ## changes in this version
* Core:
* The way sessions are named has been changed. Now the account is indicated first, followed by the social network it belongs to.
* An option has been added to the global options dialog that allows for the reading of long posts in the graphical interface. This is especially useful since, by default, the graphical interface can only display a limited number of characters in the post.
* Some options that are no longer necessary in the application have been removed from the global settings dialog.
* Mastodon:
* fixed an error that caused TWBlue to not display some posts correctly.
* Fixed name for community timelines when created during startup. Now it should be clear if it's a federated or local timeline.
* Defined shortcuts to fields on the update profile dialog so it will be easier to navigate.
* Now it is possible to load conversations directly from community timelines.
## Changes in version 2024.5.19
In this version of TWBlue, which is being released several months after the previous one, we focused on adding initial support for GoToSocial-type networks. GoToSocial is a server for creating decentralized networks similar to Mastodon. Its API is very similar but retains some differences. In this version, TWBlue can be used to log into GoToSocial accounts, although there will be some features, such as the Streaming API and Markdown support, that are not yet functional. Another significant addition is support for creating community timelines, which will allow you to load the local and public timeline of remote instances. This is useful if your instance does not federate directly with them, as it will allow you to see posts from other communities and interact directly with them. Finally, the translation module has been rewritten; it now supports using LibreTranslate by default and DeepL, for which an API key is required. Below is the detailed list of changes:
* Core:
* Added Initial Support to GoToSocial. Some features are not fully implemented yet, although GoToXocial instances should be able to be used as normal sessions in TWBlue. Streaming, poll options and markdown are not supported but planned for the near future.
* The translation module has been rewritten. Now, instead of offering translations with Google Translator, the user can choose between [LibreTranslate,](https://github.com/LibreTranslate/LibreTranslate) which requires no configuration thanks to the [instance of the NVDA Spanish community;](https://translate.nvda.es) or translate using [DeepL,](https://deepl.com) for which it is necessary to create an account on DeepL and [subscribe to a DeepL API Free plan](https://support.deepl.com/hc/en-us/articles/360021200939-DeepL-API-Free) to obtain the API key which can be used to translate up to 500000 characters every month. The API key can be entered in the global options dialog, under a new tab called translation services. When translating a text, the translation engine can be changed. When changing the translation engine, the target language must be selected again before translation takes place.
* TWBlue should be able to switch to Windows 11 Keymap when running under Windows 11. ([#494](https://github.com/mcv-software/twblue/issues/494))
* Mastodon:
* Added support for viewing communities: A community timeline is the local or public timeline of another instance. This is especially useful when the instance one is part of does not federate with other remote instances. The posts displayed are only those that are shared publicly. It is possible to interact with the posts from community timelines, but it should be noted that TWBlue will take some time to retrieve the post one wishes to interact with.
* When viewing a post, a button displays the number of boosts and times it has been added to favorites. Clicking on that button will open a list of users who have interacted with the post. From that list, it is possible to view profiles and perform common user actions.
* Now it is possible to mute conversations in Mastodon sessions. To do this, there is a button that can be called "Mute" or "Unmute Conversation" in the dialog to display the post. Conversations that have been muted will not generate notifications or mentions when they receive new replies. Only conversations that you are a part of can be muted.
* Fixed an error that caused TWBlue to be unable to properly display the user action dialog from the followers or following buffer. ([#575](https://github.com/mcv-software/twblue/issues/575))
## changes in version 2024.01.05
* Core:
* The TWBlue website will no longer be available on the twblue.es domain. Beginning in January 2024, TWBlue will live at https://twblue.mcvsoftware.com. Also, we will start releasing versions on [gitHub releases](https://github.com/mcv-software/twblue/releases) so it will be easier to track specific versions.
* As of the first release of TWBlue in 2024, we will officially stop generating 32-bit (X86) compatible binaries due to the increasing difficulty of generating versions compatible with this architecture in modern Python.
* TWBlue should be more reliable when checking for updates.
* If running from source, automatic updates will not be checked as this works only for distribution. ([#540](https://github.com/MCV-Software/TWBlue/pull/540))
* Fixed the 'report an error' item in the help menu. Now this item redirects to our gitHub issue tracker. ([#524](https://github.com/MCV-Software/TWBlue/pull/524))
* Mastodon:
* Implemented actions for the notifications buffer on a mastodon instance. Actions can be performed from the contextual menu on every notification, or by using invisible keystrokes. ([#517](https://github.com/mcv-software/twblue/issues/517))
* Implemented update profile dialog. ([#547](https://github.com/MCV-Software/TWBlue/pull/547))
* It is possible to display an user profile from the user menu within the menu bar, or by using the invisible keystroke for user details. ([#555](https://github.com/MCV-Software/TWBlue/pull/555))
* Added possibility to vote in polls. This is mapped to Alt+Win+Shift+V in the invisible keymaps for windows 10/11.
* Added posts search. Take into account that Mastodon instances should be configured with full text search enabled. Search for posts only include posts the logged-in user has interacted with. ([#541](https://github.com/MCV-Software/TWBlue/pull/541))
* Added user autocompletion settings in account settings dialog, so it is possible to ask TWBlue to scan mastodon accounts and add people from followers and following buffers. For now, user autocompletion can be used only when composing new posts or replies.
* TWBlue should be able to ignore deleted direct messages or messages from deleted accounts. Previously, a direct message that no longer existed in the instance caused errors when loading the direct messages buffer and could potentially affect notifications as well.
* TWBlue should be able to ignore notifications from deleted accounts or posts.
## changes in version 2023.4.13
During the development of the current TWBlue version, Twitter has cut out access from their API, meaning TWBlue will no longer be able to communicate with Twitter. This is the end of the support of TWBlue for Twitter sessions. No new sessions will be available for this social network, and we will focus in adding more features to our Mastodon support and writing support for more websites and networks. Thank you everyone who have been using TWBlue to manage your Twitter accounts since 2013. During the development of the current TWBlue version, Twitter has cut out access from their API, meaning TWBlue will no longer be able to communicate with Twitter. This is the end of the support of TWBlue for Twitter sessions. No new sessions will be available for this social network, and we will focus in adding more features to our Mastodon support and writing support for more websites and networks. Thank you everyone who have been using TWBlue to manage your Twitter accounts since 2013.
* TWBlue should be able to display variables within templates (for example, now it is possible to send a template inside a post's text). Before, it was removing $variables so it was difficult to show how to edit templates from the client. ([#515](https://github.com/MCV-Software/TWBlue/issues/515)) * TWBlue should be able to display variables within templates (for example, now it is possible to send a template inside a post's text). Before, it was removing $variables so it was difficult to show how to edit templates from the client. ([#515](https://github.com/MCV-Software/TWBlue/issues/515))
@@ -15,7 +59,7 @@ During the development of the current TWBlue version, Twitter has cut out access
* Fixed an error on mentions buffer that was making TWBlue unable to load posts if there were mentions from a blocked or deleted account. * Fixed an error on mentions buffer that was making TWBlue unable to load posts if there were mentions from a blocked or deleted account.
* Fixed an error when loading timelines during startup where TWBlue was unable to change the buffer title properly. * Fixed an error when loading timelines during startup where TWBlue was unable to change the buffer title properly.
## Changes on version 2023.2.6 ## Changes on version 2023.2.8
This release focuses on fixing some important bugs that have been reported in the previous version. Particularly, TWBlue should be able to authorize on some instances that have blocked the Mastodon.py library, and should be able to avoid repeatedly calling some endpoints that cause excessive connections for some instances. Additionally, it is possible to disable Streaming from the account options in Mastodon. This can be especially useful if TWBlue keeps making a lot of API calls for some instances. This release focuses on fixing some important bugs that have been reported in the previous version. Particularly, TWBlue should be able to authorize on some instances that have blocked the Mastodon.py library, and should be able to avoid repeatedly calling some endpoints that cause excessive connections for some instances. Additionally, it is possible to disable Streaming from the account options in Mastodon. This can be especially useful if TWBlue keeps making a lot of API calls for some instances.

View File

@@ -178,7 +178,7 @@ Visually, Towards the top of the main application window, can be found a menu ba
* Sounds tutorial: Opens a dialog box where you can familiarize yourself with the different sounds of the program. * Sounds tutorial: Opens a dialog box where you can familiarize yourself with the different sounds of the program.
* What's new in this version?: opens up a document with the list of changes from the current version to the earliest. * What's new in this version?: opens up a document with the list of changes from the current version to the earliest.
* Check for updates: every time you open the program it automatically checks for new versions. If an update is available, it will ask you if you want to download the update. If you accept, the updating process will commence. When complete, TWBlue will be restarted. This item checks for new updates without having to restart the application. * Check for updates: every time you open the program it automatically checks for new versions. If an update is available, it will ask you if you want to download the update. If you accept, the updating process will commence. When complete, TWBlue will be restarted. This item checks for new updates without having to restart the application.
* TWBlue's website: visit our [home page](http://twblue.es) where you can find all relevant information and downloads for TWBlue and become a part of the community. * TWBlue's website: visit our [home page](https://twblue.mcvsoftware.com) where you can find all relevant information and downloads for TWBlue and become a part of the community.
* Get soundpacks for TWBlue: * Get soundpacks for TWBlue:
* Make a Donation: Opens a website from which you can donate to the TWBlue project. Donations are made through paypal and you don't need an account to donate. * Make a Donation: Opens a website from which you can donate to the TWBlue project. Donations are made through paypal and you don't need an account to donate.
* About TWBlue: shows the credits of the program. * About TWBlue: shows the credits of the program.
@@ -322,11 +322,11 @@ Tw Blue is free software, licensed under the GNU GPL license, either version 2 o
The source code of the program is available on GitHub at <https://www.github.com/manuelcortez/twblue>. The source code of the program is available on GitHub at <https://www.github.com/manuelcortez/twblue>.
If you want to donate to the project, you can do so at <https://twblue.es/donate>. Thank you for your support! If you want to donate to the project, you can do so at <https://twblue.mcvsoftware.com/donate>. Thank you for your support!
## Contact ## Contact
If you still have questions after reading this document, if you wish to collaborate to the project in some other way, or if you simply want to get in touch with the application developer, follow the Twitter account [@tw\_blue2](https://twitter.com/tw_blue2) or [@manuelcortez00.](https://twitter.com/manuelcortez00) You can also visit [our website](https://twblue.es) If you still have questions after reading this document, if you wish to collaborate to the project in some other way, or if you simply want to get in touch with the application developer, follow the Twitter account [@tw\_blue2](https://twitter.com/tw_blue2) or [@manuelcortez00.](https://twitter.com/manuelcortez00) You can also visit [our website](https://twblue.mcvsoftware.com)
## Credits ## Credits

View File

@@ -1,10 +0,0 @@
#include "api_keys.h"
char *get_api_key(){
return "key\0";
}
char *get_api_secret(){
return "secret_key\0";
}
char *get_twishort_api_key(){
return "key\0";
}

View File

@@ -1,8 +0,0 @@
#ifndef _API_KEYS_H
#define API_KEYS_H
char *get_api_key();
char *get_api_secret();
char *get_twishort_api_key();
#endif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -1,8 +0,0 @@
[Launch]
ProgramExecutable=TWBlue\TWBlue.exe
ProgramExecutable64=TWBlue64\TWBlue.exe
CommandLineArguments=-d "%PAL:DataDir%"
SinglePortableAppInstance=true
MinOS=XP
SingleAppInstance=false
DirectoryMoveOK=yes

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 B

View File

@@ -1,28 +0,0 @@
[Format]
Type=PortableApps.comFormat
Version=3.5
[Details]
Name=tw blue portable
AppID=TWBluePortable
Publisher=jmdaweb & TW blue & PortableApps.com
Homepage=PortableApps.com/TWBluePortable
Category=Internet
Description=A portable, fast and accessible Twitter client with many options.
Language=Multilingual
InstallType=Multilingual
[License]
Shareable=true
OpenSource=true
Freeware=true
CommercialUse=true
EULAVersion=2
[Version]
PackageVersion=0.95.0.0
DisplayVersion=0.95
[Control]
Icons=1
Start=TWBluePortable.exe

View File

@@ -1,339 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@@ -1,21 +0,0 @@
[Languages]
ENGLISH=true
ENGLISHGB=true
ARABIC=true
BASQUE=true
CATALAN=true
FINNISH=true
FRENCH=true
GALICIAN=true
GERMAN=true
CROATIAN=true
HUNGARIAN=true
ITALIAN=true
JAPANESE=true
POLISH=true
PORTUGUESEBR=true
ROMANIAN=true
RUSSIAN=true
SERBIAN=true
SPANISHINTERNATIONAL=true
TURKISH=true

View File

@@ -1,3 +0,0 @@
The files in this directory are necessary for the portable application to
function. There is normally no need to directly access or alter any of the
files within these directories.

View File

@@ -1,339 +0,0 @@
 GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,339 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@@ -1,41 +0,0 @@
You can get tw blue's source code at https://github.com/manuelcortez/TWBlue
LICENSE
=======
This package's installer and launcher are released under the GPL. The launcher
is the PortableApps.com Launcher, available with full source and documentation
from http://portableapps.com/development. We request that developers using the
PortableApps.com Launcher please leave this directory intact and unchanged.
USER CONFIGURATION
==================
Some configuration in the PortableApps.com Launcher can be overridden by the
user in an INI file next to TWBluePortable.exe called TWBluePortable.ini.
If you are happy with the default options, it is not necessary, though. There
is an example INI included with this package to get you started. To use it,
copy AppNamePortable.ini from this directory to TWBluePortable.ini next to
TWBluePortable.exe. The options in the INI file are as follows:
AdditionalParameters=
DisableSplashScreen=false
RunLocally=false
(There is no need for an INI header in this file; if you have one, though, it
won't damage anything.)
The AdditionalParameters entry allows you to pass additional command-line
parameters to the application.
The DisableSplashScreen entry allows you to run the launcher without the splash
screen showing up. The default is false.
The RunLocally entry allows you to run the portable application from a read-
only medium. This is known as Live mode. It copies what it needs to to a
temporary directory on the host computer, runs the application, and then
deletes it afterwards, leaving nothing behind. This can be useful for running
the application from a CD or if you work on a computer that may have spyware or
viruses and you'd like to keep your device set to read-only. As a consequence
of this technique, any changes you make during the Live mode session aren't
saved back to your device. The default is false.

View File

@@ -1,6 +0,0 @@
AdditionalParameters=
DisableSplashScreen=false
RunLocally=false
# The above options are explained in the included readme.txt
# This INI file is an example only and is not used unless it is placed as described in the included readme.txt

View File

@@ -1,150 +0,0 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<title>tw blue Portable Help</title>
<link rel="alternate" href="http://portableapps.com/feeds/general" type="application/rss+xml" title="PortableApps.com">
<link rel="shortcut icon" href="Other/Help/Images/Favicon.ico">
<style type="text/css">
body {
font-family : Verdana,Arial,Helvetica,sans-serif;
font-size : 76%;
color : black;
margin : 20px;
background : #e6e8ea;
text-align : center;
}
a {
color : #b31616;
font-weight : bold;
}
a:link, a:visited, a:active {}
a:hover {
color : red;
}
h1, h2, h3, h4, h5, h6 {
font-family : Arial, sans-serif;
font-weight : normal;
}
h1 {
color : #b31616;
font-weight : bold;
letter-spacing : -2px;
font-size : 2.2em;
border-bottom : 1px solid silver;
padding-bottom : 5px;
}
h2 {
font-size : 1.5em;
border-bottom : 1px solid silver;
padding-bottom : 3px;
clear : both;
}
h3 { font-size : 1.2em; }
h4 { font-size : 1.1em; }
h5 { font-size : 1.0em; }
h6 { font-size : 0.8em; }
img { border : 0; }
ol, ul, li, p, pre, table, tr, td, th { font-size : 1.0em; }
pre { font-family : monospace; }
strong, b { font-weight : bold; }
td, th {
border : 1px solid #aaaaaa;
border-collapse : collapse;
padding : 3px;
}
th {
background : #3667a8;
color : white;
}
ol ol { list-style-type : lower-alpha; }
.content {
text-align : left;
margin-left : auto;
margin-right : auto;
width : 780px;
background-color : white;
border-left : 1px solid black;
border-right : 1px solid black;
padding : 12px 30px;
line-height : 150%;
}
.logo {
background : white url("Other/Help/Images/Help_Background_Header.png") repeat-x;
width : 840px;
margin-top : 20px;
margin-left : auto;
margin-right : auto;
text-align : left;
border-right : 1px solid black;
border-left : 1px solid black;
}
.footer {
background : white url("Other/Help/Images/Help_Background_Footer.png") repeat-x;
width : 840px;
height : 16px;
margin-left : auto;
margin-right : auto;
text-align : left;
border-right : 1px solid black;
border-left : 1px solid black;
}
.logo img {
padding-left : 0px;
border : none;
position : relative;
top : -4px;
}
* html .content { width : 760px; }
* html .logo, * html .footer { width : 820px; }
.content h1 { margin : 0px; }
h1.hastagline { border : 0; }
h2.tagline {
color : #747673;
clear : none;
margin-top : 0em;
}
/* printer styles */
@media print {
body, .content {
margin : 0;
padding : 0;
}
.navigation, .locator, .footer a, .message, .footer-links { display : none; }
.footer, .content, .header { border : none; }
a {
text-decoration : none;
font-weight : normal;
color : black;
}
}
</style>
</head>
<body>
<div class="logo"><a href="http://portableapps.com/"><img src="Other/Help/Images/Help_Logo_Top.png" alt="PortableApps.com - Your Digital Life, Anywhere"></a></div>
<div class="content">
<h1 class="hastagline">tw blue Portable Help</h1>
<h2 class="tagline">A powerful and accessible Twitter client</h2>
<p>tw blue Portable is the tw blue whatever it is packaged with a PortableApps.com launcher as a <a href="http://portableapps.com/about/what_is_a_portable_app">portable app</a>, so you can view and send tweets on your iPod, USB flash drive, portable hard drive, etc. It has all the same features as tw blue, plus, it leaves no personal information behind on the machine you run it on, so you can take it with you wherever you go. <a href="http://twblue.es">Learn more about tw blue...</a></p>
<p><a href="http://portableapps.com/donate"><img src="Other/Help/Images/Donation_Button.png" style="vertical-align:middle" alt="Make a Donation"></a> - Support PortableApps.com's Hosting and Development</p>
<p><a href="http://portableapps.com/node/*Node ID*">Go to the tw blue Portable Homepage &gt;&gt;</a></p>
<p><a href="http://portableapps.com/">Get more portable apps at PortableApps.com</a></p>
<p>This software is OSI Certified Open Source Software. OSI Certified is a certification mark of the Open Source Initiative.</p>
<h2>Portable App Issues</h2>
<ul>
<li><a href="http://portableapps.com/support/portable_app#downloading">Downloading a Portable App</a></li>
<li><a href="http://portableapps.com/support/portable_app#installing">Installing a Portable App</a></li>
<li><a href="http://portableapps.com/support/portable_app#using">Using a Portable App</a></li>
<li><a href="http://portableapps.com/support/portable_app#upgrading">Upgrading a Portable App</a></li>
</ul>
<p>You can read about advanced configuration options for the PortableApps.com Launcher in its <a href="Other/Source/Readme.txt">readme file</a>.</p>
</div>
<div class="footer"></div>
</body>
</html>

13
release-notes.md Normal file
View File

@@ -0,0 +1,13 @@
## Changelog
In this version of TWBlue, which is being released several months after the previous one, we focused on adding initial support for GoToSocial-type networks. GoToSocial is a server for creating decentralized networks similar to Mastodon. Its API is very similar but retains some differences. In this version, TWBlue can be used to log into GoToSocial accounts, although there will be some features, such as the Streaming API and Markdown support, that are not yet functional. Another significant addition is support for creating community timelines, which will allow you to load the local and public timeline of remote instances. This is useful if your instance does not federate directly with them, as it will allow you to see posts from other communities and interact directly with them. Finally, the translation module has been rewritten; it now supports using LibreTranslate by default and DeepL, for which an API key is required. Below is the detailed list of changes:
* Core:
* Added Initial Support to GoToSocial. Some features are not fully implemented yet, although GoToXocial instances should be able to be used as normal sessions in TWBlue. Streaming, poll options and markdown are not supported but planned for the near future.
* The translation module has been rewritten. Now, instead of offering translations with Google Translator, the user can choose between [LibreTranslate,](https://github.com/LibreTranslate/LibreTranslate) which requires no configuration thanks to the [instance of the NVDA Spanish community;](https://translate.nvda.es) or translate using [DeepL,](https://deepl.com) for which it is necessary to create an account on DeepL and [subscribe to a DeepL API Free plan](https://support.deepl.com/hc/en-us/articles/360021200939-DeepL-API-Free) to obtain the API key which can be used to translate up to 500000 characters every month. The API key can be entered in the global options dialog, under a new tab called translation services. When translating a text, the translation engine can be changed. When changing the translation engine, the target language must be selected again before translation takes place.
* TWBlue should be able to switch to Windows 11 Keymap when running under Windows 11. ([#494](https://github.com/mcv-software/twblue/issues/494))
* Mastodon:
* Added support for viewing communities: A community timeline is the local or public timeline of another instance. This is especially useful when the instance one is part of does not federate with other remote instances. The posts displayed are only those that are shared publicly. It is possible to interact with the posts from community timelines, but it should be noted that TWBlue will take some time to retrieve the post one wishes to interact with.
* When viewing a post, a button displays the number of boosts and times it has been added to favorites. Clicking on that button will open a list of users who have interacted with the post. From that list, it is possible to view profiles and perform common user actions.
* Now it is possible to mute conversations in Mastodon sessions. To do this, there is a button that can be called "Mute" or "Unmute Conversation" in the dialog to display the post. Conversations that have been muted will not generate notifications or mentions when they receive new replies. Only conversations that you are a part of can be muted.
* Fixed an error that caused TWBlue to be unable to properly display the user action dialog from the followers or following buffer. ([#575](https://github.com/mcv-software/twblue/issues/575))

View File

@@ -1,55 +1,57 @@
wxpython accessible_output2 @ git+https://github.com/accessibleapps/accessible_output2@57bda997d98e87dd78aa049e7021cf777871619b
pytest arrow==1.3.0
coverage attrs==23.2.0
wheel backports.functools-lru-cache==2.0.0
future blurhash==1.1.4
configobj certifi==2024.2.2
markdown chardet==5.2.0
requests charset-normalizer==3.3.2
oauthlib colorama==0.4.6
requests-oauthlib configobj==5.0.8
requests-toolbelt coverage==7.5.1
pypubsub cx-Freeze==7.0.0
arrow cx-Logging==3.2.0
python-dateutil decorator==5.1.1
winpaths demoji==1.1.0
PySocks deepl==1.18.0
win_inet_pton future==1.0.0
# Install the latest RC of this lib idna==3.7
# see https://github.com/ssut/py-googletrans/issues/234 importlib-metadata==7.1.0
googletrans==4.0.0-rc1 iniconfig==2.0.0
idna<3,>=2.5 libloader @ git+https://github.com/accessibleapps/libloader@bc94811c095b2e57a036acd88660be9a33260267
chardet libretranslatepy==2.1.4
urllib3 lief==0.14.1
youtube-dl Markdown==3.6
python-vlc Mastodon.py==1.8.1
pypiwin32 numpy==1.26.4
pywin32 oauthlib==3.2.2
certifi packaging==24.0
backports.functools_lru_cache pillow==10.3.0
cx_freeze platform_utils @ git+https://github.com/accessibleapps/platform_utils@e0d79f7b399c4ea677a633d2dde9202350d62c38
twitter-text-parser pluggy==1.5.0
mastodon.py psutil==5.9.8
pyenchant pyenchant==3.2.2
sqlitedict pypiwin32==223
cx-Logging Pypubsub==4.0.3
h11 PySocks==1.7.1
h2 pytest==8.2.1
hpack python-dateutil==2.9.0.post0
hstspreload python-magic-bin==0.4.14
httpcore python-vlc==3.0.20123
httpx pywin32==306
hyperframe requests==2.32.1
rfc3986 requests-oauthlib==2.0.0
sniffio requests-toolbelt==1.0.0
attrs rfc3986==2.0.0
importlib-metadata six==1.16.0
numpy sniffio==1.3.1
pillow sound_lib @ git+https://github.com/accessibleapps/sound_lib@a439f0943fb95ee7b6ba24f51a686f47c4ad66b2
charset-normalizer sqlitedict==2.1.0
demoji twitter-text-parser==3.0.0
psutil types-python-dateutil==2.9.0.20240316
git+https://github.com/accessibleapps/libloader urllib3==2.2.1
git+https://github.com/accessibleapps/platform_utils win-inet-pton==1.1.0
git+https://github.com/accessibleapps/accessible_output2 winpaths==0.2
git+https://github.com/accessibleapps/sound_lib wxPython==4.2.1
youtube-dl==2021.12.17
zipp==3.18.2

16
scripts/build.ps1 Normal file
View File

@@ -0,0 +1,16 @@
# Build a TW Blue installer.
# Must be called from root of repo
echo "Generating documentation..."
cd doc
python documentation_importer.py
python generator.py
mv documentation ..\src
cd ..
echo "done."
echo "Building binary..."
cd src
python write_version_data.py
python setup.py build
cd ..
echo "done."

View File

@@ -1,12 +0,0 @@
import shutil
import os
import sys
def create_archive():
os.chdir("..\\src")
print("Creating zip archive...")
folder = "dist"
shutil.make_archive("twblue", "zip", folder)
os.chdir("..\\scripts")
create_archive()

View File

@@ -27,7 +27,7 @@ var StartMenuFolder
!insertmacro MUI_PAGE_STARTMENU startmenu $StartMenuFolder !insertmacro MUI_PAGE_STARTMENU startmenu $StartMenuFolder
!insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_LINK "Visit TWBlue website" !define MUI_FINISHPAGE_LINK "Visit TWBlue website"
!define MUI_FINISHPAGE_LINK_LOCATION "https://twblue.es" !define MUI_FINISHPAGE_LINK_LOCATION "https://twblue.mcvsoftware.com"
!define MUI_FINISHPAGE_RUN "$INSTDIR\TWBlue.exe" !define MUI_FINISHPAGE_RUN "$INSTDIR\TWBlue.exe"
!insertmacro MUI_PAGE_FINISH !insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_CONFIRM
@@ -58,13 +58,14 @@ SetOutPath "$INSTDIR"
${If} ${RunningX64} ${If} ${RunningX64}
File /r TWBlue64\* File /r TWBlue64\*
${Else} ${Else}
File /r TWBlue\* messagebox MB_ICONSTOP "Error: This TWBlue installer is only compatible with 64-bit systems. TWBlue does not support 32 bit systems any more."
Quit
${EndIf} ${EndIf}
CreateShortCut "$DESKTOP\TWBlue.lnk" "$INSTDIR\TWBlue.exe" CreateShortCut "$DESKTOP\TWBlue.lnk" "$INSTDIR\TWBlue.exe"
!insertmacro MUI_STARTMENU_WRITE_BEGIN startmenu !insertmacro MUI_STARTMENU_WRITE_BEGIN startmenu
CreateDirectory "$SMPROGRAMS\$StartMenuFolder" CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
CreateShortCut "$SMPROGRAMS\$StartMenuFolder\TWBlue.lnk" "$INSTDIR\TWBlue.exe" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\TWBlue.lnk" "$INSTDIR\TWBlue.exe"
CreateShortCut "$SMPROGRAMS\$StartMenuFolder\TWBlue on the web.lnk" "http://twblue.es" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\TWBlue on the web.lnk" "https://twblue.mcvsoftware.com"
CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\Uninstall.exe" CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
!insertmacro MUI_STARTMENU_WRITE_END !insertmacro MUI_STARTMENU_WRITE_END
WriteUninstaller "$INSTDIR\Uninstall.exe" WriteUninstaller "$INSTDIR\Uninstall.exe"
@@ -73,7 +74,7 @@ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "U
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayVersion" "0.95" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayVersion" "0.95"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "URLInfoAbout" "https://twblue.es" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "URLInfoAbout" "https://twblue.mcvsoftware.com"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMajor" 0 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMajor" 0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMinor" 0 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMinor" 0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoModify" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoModify" 1

View File

@@ -1,48 +0,0 @@
#! /usr/bin/env python
import sys
import os
import glob
import ftplib
transferred=0
def convert_bytes(n):
K, M, G, T, P = 1 << 10, 1 << 20, 1 << 30, 1 << 40, 1 << 50
if n >= P:
return '%.2fPb' % (float(n) / T)
elif n >= T:
return '%.2fTb' % (float(n) / T)
elif n >= G:
return '%.2fGb' % (float(n) / G)
elif n >= M:
return '%.2fMb' % (float(n) / M)
elif n >= K:
return '%.2fKb' % (float(n) / K)
else:
return '%d' % n
def callback(progress):
global transferred
transferred = transferred+len(progress)
print("Uploaded {}".format(convert_bytes(transferred),))
ftp_server = os.environ.get("FTP_SERVER") or sys.argv[1]
ftp_username = os.environ.get("FTP_USERNAME") or sys.argv[2]
ftp_password = os.environ.get("FTP_PASSWORD") or sys.argv[3]
print("Uploading files to the TWBlue server...")
print("Connecting to %s" % (ftp_server,))
connection = ftplib.FTP(ftp_server)
print("Connected to FTP server {}".format(ftp_server,))
connection.login(user=ftp_username, passwd=ftp_password)
print("Logged in successfully")
connection.cwd("web/pubs")
files = glob.glob("*.zip")+glob.glob("*.exe")
print("These files will be uploaded into the version folder: {}".format(files,))
for file in files:
transferred = 0
print("Uploading {}".format(file,))
with open(file, "rb") as f:
connection.storbinary('STOR %s' % file, f, callback=callback, blocksize=1024*1024)
print("Upload completed. exiting...")
connection.quit()

View File

@@ -9,8 +9,7 @@ update_period = integer(default=2)
hide_gui = boolean(default=False) hide_gui = boolean(default=False)
voice_enabled = boolean(default=False) voice_enabled = boolean(default=False)
ask_at_exit = boolean(default=True) ask_at_exit = boolean(default=True)
autostart = boolean(default=False) read_long_posts_in_gui = boolean(default=True)
handle_longtweets = boolean(default=True)
use_invisible_keyboard_shorcuts = boolean(default=True) use_invisible_keyboard_shorcuts = boolean(default=True)
play_ready_sound = boolean(default=True) play_ready_sound = boolean(default=True)
speak_ready_msg = boolean(default=True) speak_ready_msg = boolean(default=True)
@@ -18,9 +17,6 @@ log_level = string(default="error")
load_keymap = string(default="default.keymap") load_keymap = string(default="default.keymap")
donation_dialog_displayed = boolean(default=False) donation_dialog_displayed = boolean(default=False)
check_for_updates = boolean(default=True) check_for_updates = boolean(default=True)
remember_mention_and_longtweet = boolean(default=False)
longtweet = boolean(default=false)
mention_all = boolean(default=False)
no_streaming = boolean(default=False) no_streaming = boolean(default=False)
[proxy] [proxy]
@@ -28,4 +24,10 @@ type = integer(default=0)
server = string(default="") server = string(default="")
port = integer(default=8080) port = integer(default=8080)
user = string(default="") user = string(default="")
password = string(default="") password = string(default="")
[translator]
engine=string(default="LibreTranslate")
lt_api_url=string(default="https://translate.nvda.es")
lt_api_key=string(default="")
deepl_api_key = string(default="")

View File

@@ -1,14 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
name = 'TWBlue' name = 'TWBlue'
short_name='twblue' short_name='twblue'
update_url = 'https://twblue.es/updates/updates.php' update_url = 'https://raw.githubusercontent.com/mcv-software/TWBlue/next-gen/updates/updates.json'
mirror_update_url = 'https://raw.githubusercontent.com/mcv-software/TWBlue/next-gen/updates/updates.json'
authors = ["Manuel Cortéz", "José Manuel Delicado"] authors = ["Manuel Cortéz", "José Manuel Delicado"]
authorEmail = "manuel@manuelcortez.net" authorEmail = "manuel@manuelcortez.net"
copyright = "Copyright (C) 2013-2023, MCV Software." copyright = "Copyright (C) 2013-2024, MCV Software."
description = name+" is an app designed to use Twitter simply and efficiently while using minimal system resources. This app provides access to most Twitter features." description = name+" is an app designed to use Twitter simply and efficiently while using minimal system resources. This app provides access to most Twitter features."
translators = ["Manuel Cortéz (English)", "Mohammed Al Shara, Hatoun Felemban (Arabic)", "Francisco Torres (Catalan)", "Manuel cortéz (Spanish)", "Sukil Etxenike Arizaleta (Basque)", "Jani Kinnunen (finnish)", "Corentin Bacqué-Cazenave (Français)", "Juan Buño (Galician)", "Steffen Schultz (German)", "Zvonimir Stanečić (Croatian)", "Robert Osztolykan (Hungarian)", "Christian Leo Mameli (Italian)", "Riku (Japanese)", "Paweł Masarczyk (Polish)", "Odenilton Júnior Santos (Portuguese)", "Florian Ionașcu, Nicușor Untilă (Romanian)", "Natalia Hedlund, Valeria Kuznetsova (Russian)", "Aleksandar Đurić (Serbian)", "Burak Yüksek (Turkish)"] translators = ["Manuel Cortéz (English)", "Mohammed Al Shara, Hatoun Felemban (Arabic)", "Francisco Torres (Catalan)", "Manuel cortéz (Spanish)", "Sukil Etxenike Arizaleta (Basque)", "Jani Kinnunen (finnish)", "Corentin Bacqué-Cazenave (Français)", "Juan Buño (Galician)", "Steffen Schultz (German)", "Zvonimir Stanečić (Croatian)", "Robert Osztolykan (Hungarian)", "Christian Leo Mameli (Italian)", "Riku (Japanese)", "Paweł Masarczyk (Polish)", "Odenilton Júnior Santos (Portuguese)", "Florian Ionașcu, Nicușor Untilă (Romanian)", "Natalia Hedlund, Valeria Kuznetsova (Russian)", "Aleksandar Đurić (Serbian)", "Burak Yüksek (Turkish)"]
url = "https://twblue.es" url = "https://twblue.mcvsoftware.com"
report_bugs_url = "https://github.com/MCV-Software/TWBlue/issues" report_bugs_url = "https://github.com/MCV-Software/TWBlue/issues"
supported_languages = [] supported_languages = []
version = "11" version = "11"

View File

@@ -1,5 +1,6 @@
# -*- coding: cp1252 -*- # -*- coding: cp1252 -*-
import os import os
import sys
import config_utils import config_utils
import paths import paths
import logging import logging
@@ -21,7 +22,10 @@ def setup ():
log.debug("Loading keymap...") log.debug("Loading keymap...")
global keymap global keymap
if float(platform.version()[:2]) >= 10 and app["app-settings"]["load_keymap"] == "default.keymap": if float(platform.version()[:2]) >= 10 and app["app-settings"]["load_keymap"] == "default.keymap":
app["app-settings"]["load_keymap"] = "Windows 10.keymap" if sys.getwindowsversion().build > 22000:
app["app-settings"]["load_keymap"] = "Windows11.keymap"
else:
app["app-settings"]["load_keymap"] = "Windows 10.keymap"
app.write() app.write()
global changed_keymap global changed_keymap
changed_keymap = True changed_keymap = True

View File

@@ -3,4 +3,6 @@ from .base import BaseBuffer
from .mentions import MentionsBuffer from .mentions import MentionsBuffer
from .conversations import ConversationBuffer, ConversationListBuffer from .conversations import ConversationBuffer, ConversationListBuffer
from .users import UserBuffer from .users import UserBuffer
from .notifications import NotificationsBuffer from .notifications import NotificationsBuffer
from .search import SearchBuffer
from .community import CommunityBuffer

View File

@@ -40,7 +40,7 @@ class BaseBuffer(base.Buffer):
self.buffer.account = account self.buffer.account = account
self.bind_events() self.bind_events()
self.sound = sound self.sound = sound
if "-timeline" in self.name or "-followers" in self.name or "-following" in self.name: if "-timeline" in self.name or "-followers" in self.name or "-following" in self.name or "searchterm" in self.name:
self.finished_timeline = False self.finished_timeline = False
def create_buffer(self, parent, name): def create_buffer(self, parent, name):
@@ -263,7 +263,10 @@ class BaseBuffer(base.Buffer):
menu = menus.base() menu = menus.base()
widgetUtils.connect_event(menu, widgetUtils.MENU, self.reply, menuitem=menu.reply) widgetUtils.connect_event(menu, widgetUtils.MENU, self.reply, menuitem=menu.reply)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.user_actions, menuitem=menu.userActions) widgetUtils.connect_event(menu, widgetUtils.MENU, self.user_actions, menuitem=menu.userActions)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.share_item, menuitem=menu.boost) if self.can_share() == True:
widgetUtils.connect_event(menu, widgetUtils.MENU, self.share_item, menuitem=menu.boost)
else:
menu.boost.Enable(False)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.fav, menuitem=menu.fav) widgetUtils.connect_event(menu, widgetUtils.MENU, self.fav, menuitem=menu.fav)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.unfav, menuitem=menu.unfav) widgetUtils.connect_event(menu, widgetUtils.MENU, self.unfav, menuitem=menu.unfav)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.url_, menuitem=menu.openUrl) widgetUtils.connect_event(menu, widgetUtils.MENU, self.url_, menuitem=menu.openUrl)
@@ -310,14 +313,16 @@ class BaseBuffer(base.Buffer):
if index > -1 and self.session.db.get(self.name) != None: if index > -1 and self.session.db.get(self.name) != None:
return self.session.db[self.name][index] return self.session.db[self.name][index]
def can_share(self): def can_share(self, item=None):
post = self.get_item() if item == None:
if post.visibility == "direct": item = self.get_item()
if item.visibility == "direct":
return False return False
return True return True
def reply(self, *args): def reply(self, event=None, item=None, *args, **kwargs):
item = self.get_item() if item == None:
item = self.get_item()
visibility = item.visibility visibility = item.visibility
if visibility == "direct": if visibility == "direct":
title = _("Conversation with {}").format(item.account.username) title = _("Conversation with {}").format(item.account.username)
@@ -352,8 +357,9 @@ class BaseBuffer(base.Buffer):
if hasattr(post.message, "destroy"): if hasattr(post.message, "destroy"):
post.message.destroy() post.message.destroy()
def send_message(self, *args, **kwargs): def send_message(self, event=None, item=None, *args, **kwargs):
item = self.get_item() if item == None:
item = self.get_item()
title = _("Conversation with {}").format(item.account.username) title = _("Conversation with {}").format(item.account.username)
caption = _("Write your message here") caption = _("Write your message here")
if item.reblog != None: if item.reblog != None:
@@ -378,11 +384,12 @@ class BaseBuffer(base.Buffer):
if hasattr(post.message, "destroy"): if hasattr(post.message, "destroy"):
post.message.destroy() post.message.destroy()
def share_item(self, *args, **kwargs): def share_item(self, event=None, item=None, *args, **kwargs):
if self.can_share() == False: if item == None:
item = self.get_item()
if self.can_share(item=item) == False:
return output.speak(_("This action is not supported on conversations.")) return output.speak(_("This action is not supported on conversations."))
post = self.get_item() id = item.id
id = post.id
if self.session.settings["general"]["boost_mode"] == "ask": if self.session.settings["general"]["boost_mode"] == "ask":
answer = mastodon_dialogs.boost_question() answer = mastodon_dialogs.boost_question()
if answer == True: if answer == True:
@@ -399,6 +406,8 @@ class BaseBuffer(base.Buffer):
original_date = arrow.get(self.session.db[self.name][self.buffer.list.get_selected()].created_at) original_date = arrow.get(self.session.db[self.name][self.buffer.list.get_selected()].created_at)
ts = original_date.humanize(locale=languageHandler.getLanguage()) ts = original_date.humanize(locale=languageHandler.getLanguage())
self.buffer.list.list.SetItem(self.buffer.list.get_selected(), 2, ts) self.buffer.list.list.SetItem(self.buffer.list.get_selected(), 2, ts)
if config.app["app-settings"]["read_long_posts_in_gui"] == True and self.buffer.list.list.HasFocus():
output.speak(self.get_message(), interrupt=True)
if self.session.settings['sound']['indicate_audio'] and utils.is_audio_or_video(post): if self.session.settings['sound']['indicate_audio'] and utils.is_audio_or_video(post):
self.session.sound.play("audio.ogg") self.session.sound.play("audio.ogg")
if self.session.settings['sound']['indicate_img'] and utils.is_image(post): if self.session.settings['sound']['indicate_img'] and utils.is_image(post):
@@ -407,12 +416,11 @@ class BaseBuffer(base.Buffer):
pub.sendMessage("toggleShare", shareable=can_share) pub.sendMessage("toggleShare", shareable=can_share)
self.buffer.boost.Enable(can_share) self.buffer.boost.Enable(can_share)
def audio(self, url='', *args, **kwargs): def audio(self, event=None, item=None, *args, **kwargs):
if sound.URLPlayer.player.is_playing(): if sound.URLPlayer.player.is_playing():
return sound.URLPlayer.stop_audio() return sound.URLPlayer.stop_audio()
item = self.get_item()
if item == None: if item == None:
return item = self.get_item()
urls = utils.get_media_urls(item) urls = utils.get_media_urls(item)
if len(urls) == 1: if len(urls) == 1:
url=urls[0] url=urls[0]
@@ -428,25 +436,25 @@ class BaseBuffer(base.Buffer):
# except: # except:
# log.error("Exception while executing audio method.") # log.error("Exception while executing audio method.")
def url(self, url='', announce=True, *args, **kwargs): def url(self, announce=True, item=None, *args, **kwargs):
if url == '': if item == None:
post = self.get_item() item = self.get_item()
if post.reblog != None: if item.reblog != None:
urls = utils.find_urls(post.reblog) urls = utils.find_urls(item.reblog)
else: else:
urls = utils.find_urls(post) urls = utils.find_urls(item)
if len(urls) == 1: if len(urls) == 1:
url=urls[0] url=urls[0]
elif len(urls) > 1: elif len(urls) > 1:
urls_list = urlList.urlList() urls_list = urlList.urlList()
urls_list.populate_list(urls) urls_list.populate_list(urls)
if urls_list.get_response() == widgetUtils.OK: if urls_list.get_response() == widgetUtils.OK:
url=urls_list.get_string() url=urls_list.get_string()
if hasattr(urls_list, "destroy"): urls_list.destroy() if hasattr(urls_list, "destroy"): urls_list.destroy()
if url != '': if url != '':
if announce: if announce:
output.speak(_(u"Opening URL..."), True) output.speak(_(u"Opening URL..."), True)
webbrowser.open_new_tab(url) webbrowser.open_new_tab(url)
def clear_list(self): def clear_list(self):
dlg = commonMessageDialogs.clear_list() dlg = commonMessageDialogs.clear_list()
@@ -476,31 +484,37 @@ class BaseBuffer(base.Buffer):
item = self.get_item() item = self.get_item()
pass pass
def get_item_url(self): def get_item_url(self, item=None):
post = self.get_item() if item == None:
if post.reblog != None: item = self.get_item()
return post.reblog.url if item.reblog != None:
return post.url return item.reblog.url
return item.url
def open_in_browser(self, *args, **kwargs): def open_in_browser(self, event=None, item=None, *args, **kwargs):
url = self.get_item_url() if item == None:
item = self.get_item()
url = self.get_item_url(item=item)
output.speak(_("Opening item in web browser...")) output.speak(_("Opening item in web browser..."))
webbrowser.open(url) webbrowser.open(url)
def add_to_favorites(self): def add_to_favorites(self, item=None):
item = self.get_item() if item == None:
item = self.get_item()
if item.reblog != None: if item.reblog != None:
item = item.reblog item = item.reblog
call_threaded(self.session.api_call, call_name="status_favourite", preexec_message=_("Adding to favorites..."), _sound="favourite.ogg", id=item.id) call_threaded(self.session.api_call, call_name="status_favourite", preexec_message=_("Adding to favorites..."), _sound="favourite.ogg", id=item.id)
def remove_from_favorites(self): def remove_from_favorites(self, item=None):
item = self.get_item() if item == None:
item = self.get_item()
if item.reblog != None: if item.reblog != None:
item = item.reblog item = item.reblog
call_threaded(self.session.api_call, call_name="status_unfavourite", preexec_message=_("Removing from favorites..."), _sound="favourite.ogg", id=item.id) call_threaded(self.session.api_call, call_name="status_unfavourite", preexec_message=_("Removing from favorites..."), _sound="favourite.ogg", id=item.id)
def toggle_favorite(self, *args, **kwargs): def toggle_favorite(self, event=None, item=None, *args, **kwargs):
item = self.get_item() if item == None:
item = self.get_item()
if item.reblog != None: if item.reblog != None:
item = item.reblog item = item.reblog
try: try:
@@ -513,8 +527,9 @@ class BaseBuffer(base.Buffer):
else: else:
call_threaded(self.session.api_call, call_name="status_unfavourite", preexec_message=_("Removing from favorites..."), _sound="favourite.ogg", id=item.id) call_threaded(self.session.api_call, call_name="status_unfavourite", preexec_message=_("Removing from favorites..."), _sound="favourite.ogg", id=item.id)
def toggle_bookmark(self, *args, **kwargs): def toggle_bookmark(self, event=None, item=None, *args, **kwargs):
item = self.get_item() if item == None:
item = self.get_item()
if item.reblog != None: if item.reblog != None:
item = item.reblog item = item.reblog
try: try:
@@ -527,16 +542,17 @@ class BaseBuffer(base.Buffer):
else: else:
call_threaded(self.session.api_call, call_name="status_unbookmark", preexec_message=_("Removing from bookmarks..."), _sound="favourite.ogg", id=item.id) call_threaded(self.session.api_call, call_name="status_unbookmark", preexec_message=_("Removing from bookmarks..."), _sound="favourite.ogg", id=item.id)
def view_item(self): def view_item(self, item=None):
post = self.get_item() if item == None:
item = self.get_item()
# Update object so we can retrieve newer stats # Update object so we can retrieve newer stats
try: try:
post = self.session.api.status(id=post.id) item = self.session.api.status(id=item.id)
except MastodonNotFoundError: except MastodonNotFoundError:
output.speak(_("No status found with that ID")) output.speak(_("No status found with that ID"))
return return
# print(post) # print(post)
msg = messages.viewPost(post, offset_hours=self.session.db["utc_offset"], item_url=self.get_item_url()) msg = messages.viewPost(self.session, item, offset_hours=self.session.db["utc_offset"], item_url=self.get_item_url(item=item))
def ocr_image(self): def ocr_image(self):
post = self.get_item() post = self.get_item()
@@ -577,8 +593,11 @@ class BaseBuffer(base.Buffer):
response = viewer.message.ShowModal() response = viewer.message.ShowModal()
viewer.message.Destroy() viewer.message.Destroy()
def vote(self): def vote(self, item=None):
post = self.get_item() if item == None:
post = self.get_item()
else:
post = item
if not hasattr(post, "poll") or post.poll == None: if not hasattr(post, "poll") or post.poll == None:
return return
poll = post.poll poll = post.poll

View File

@@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
import time
import logging
import mastodon
import widgetUtils
import output
from wxUI import commonMessageDialogs
from sessions.mastodon import utils
from . import base
log = logging.getLogger("controller.buffers.mastodon.community")
class CommunityBuffer(base.BaseBuffer):
def __init__(self, community_url, *args, **kwargs):
super(CommunityBuffer, self).__init__(*args, **kwargs)
self.community_url = community_url
self.community_api = mastodon.Mastodon(api_base_url=self.community_url)
self.timeline = kwargs.get("timeline", "local")
self.kwargs.pop("timeline")
def get_buffer_name(self):
type = _("Local") if self.timeline == "local" else _("Federated")
instance = self.community_url.replace("https://", "")
return _(f"{type} timeline for {instance}")
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
current_time = time.time()
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
self.execution_time = current_time
log.debug("Starting stream for buffer %s, account %s and type %s" % (self.name, self.account, self.type))
log.debug("args: %s, kwargs: %s" % (self.args, self.kwargs))
count = self.session.settings["general"]["max_posts_per_call"]
min_id = None
# toDo: Implement reverse timelines properly here.
if self.name in self.session.db and len(self.session.db[self.name]) > 0:
if self.session.settings["general"]["reverse_timelines"]:
min_id = self.session.db[self.name][0].id
else:
min_id = self.session.db[self.name][-1].id
try:
results = self.community_api.timeline(timeline=self.timeline, min_id=min_id, limit=count, *self.args, **self.kwargs)
results.reverse()
except Exception as e:
log.exception("Error %s" % (str(e)))
return
number_of_items = self.session.order_buffer(self.name, results)
log.debug("Number of items retrieved: %d" % (number_of_items,))
self.put_items_on_list(number_of_items)
if number_of_items > 0 and self.sound != None and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and play_sound == True:
self.session.sound.play(self.sound)
# Autoread settings
if avoid_autoreading == False and mandatory == True and number_of_items > 0 and self.name in self.session.settings["other_buffers"]["autoread_buffers"]:
self.auto_read(number_of_items)
return number_of_items
def get_more_items(self):
elements = []
if self.session.settings["general"]["reverse_timelines"] == False:
max_id = self.session.db[self.name][0].id
else:
max_id = self.session.db[self.name][-1].id
try:
items = self.community_api.timeline(timeline=self.timeline, max_id=max_id, limit=self.session.settings["general"]["max_posts_per_call"], *self.args, **self.kwargs)
except Exception as e:
log.exception("Error %s" % (str(e)))
return
items_db = self.session.db[self.name]
for i in items:
if utils.find_item(i, self.session.db[self.name]) == None:
elements.append(i)
if self.session.settings["general"]["reverse_timelines"] == False:
items_db.insert(0, i)
else:
items_db.append(i)
self.session.db[self.name] = items_db
selection = self.buffer.list.get_selected()
log.debug("Retrieved %d items from cursored search in function %s." % (len(elements), self.function))
safe = True
if self.session.settings["general"]["read_preferences_from_instance"]:
safe = self.session.expand_spoilers == False
if self.session.settings["general"]["reverse_timelines"] == False:
for i in elements:
post = self.compose_function(i, self.session.db, self.session.settings, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
self.buffer.list.insert_item(True, *post)
else:
for i in elements:
post = self.compose_function(i, self.session.db, self.session.settings, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], safe=safe)
self.buffer.list.insert_item(False, *post)
self.buffer.list.select_item(selection)
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)
def remove_buffer(self, force=False):
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
tl_info = f"{self.timeline}@{self.community_url}"
self.session.settings["other_buffers"]["communities"].remove(tl_info)
self.session.settings.write()
if self.name in self.session.db:
self.session.db.pop(self.name)
return True
elif dlg == widgetUtils.NO:
return False
def get_item_from_instance(self, *args, **kwargs):
item = self.get_item()
try:
results = self.session.api.search(q=item.url, resolve=True, result_type="statuses")
except Exception as e:
log.exception("Error when searching for remote post.")
return None
item = results["statuses"][0]
return item
def reply(self, *args, **kwargs):
item = self.get_item_from_instance()
if item != None:
super(CommunityBuffer, self).reply(item=item)
def send_message(self, *args, **kwargs):
item = self.get_item_from_instance()
if item != None:
super(CommunityBuffer, self).send_message(item=item)
def share_item(self, *args, **kwargs):
item = self.get_item_from_instance()
if item != None:
super(CommunityBuffer, self).share_item(item=item)
def add_to_favorites(self, *args, **kwargs):
item = self.get_item_from_instance()
if item != None:
super(CommunityBuffer, self).add_to_favorite(item=item)
def remove_from_favorites(self, *args, **kwargs):
item = self.get_item_from_instance()
if item != None:
super(CommunityBuffer, self).remove_from_favorites(item=item)
def toggle_favorite(self, *args, **kwargs):
item = self.get_item_from_instance()
if item != None:
super(CommunityBuffer, self).toggle_favorite(item=item)
def toggle_bookmark(self, *args, **kwargs):
item = self.get_item_from_instance()
if item != None:
super(CommunityBuffer, self).toggle_bookmark(item=item)
def vote(self, *args, **kwargs):
item = self.get_item_from_instance()
if item != None:
super(CommunityBuffer, self).vote(item=item)
def view_item(self, *args, **kwargs):
item = self.get_item_from_instance()
if item != None:
super(CommunityBuffer, self).view_item(item=item)

View File

@@ -4,6 +4,7 @@ import logging
import wx import wx
import widgetUtils import widgetUtils
import output import output
import config
from mastodon import MastodonNotFoundError from mastodon import MastodonNotFoundError
from controller.mastodon import messages from controller.mastodon import messages
from controller.buffers.mastodon.base import BaseBuffer from controller.buffers.mastodon.base import BaseBuffer
@@ -51,7 +52,7 @@ class ConversationListBuffer(BaseBuffer):
results = getattr(self.session.api, self.function)(min_id=min_id, limit=count, *self.args, **self.kwargs) results = getattr(self.session.api, self.function)(min_id=min_id, limit=count, *self.args, **self.kwargs)
results.reverse() results.reverse()
except Exception as e: except Exception as e:
log.exception("Error %s" % (str(e))) log.exception("Error %s loading %s with args of %r and kwargs of %r" % (str(e), self.function, self.args, self.kwargs))
return return
new_position, number_of_items = self.order_buffer(results) new_position, number_of_items = self.order_buffer(results)
log.debug("Number of items retrieved: %d" % (number_of_items,)) log.debug("Number of items retrieved: %d" % (number_of_items,))
@@ -110,6 +111,9 @@ class ConversationListBuffer(BaseBuffer):
self.session.db[self.name] = [] self.session.db[self.name] = []
objects = self.session.db[self.name] objects = self.session.db[self.name]
for i in data: for i in data:
# Deleted conversations handling.
if i.last_status == None:
continue
position = self.get_item_position(i) position = self.get_item_position(i)
if position != None: if position != None:
conversation = self.session.db[self.name][position] conversation = self.session.db[self.name][position]
@@ -159,6 +163,8 @@ class ConversationListBuffer(BaseBuffer):
def onFocus(self, *args, **kwargs): def onFocus(self, *args, **kwargs):
post = self.get_item() post = self.get_item()
if config.app["app-settings"]["read_long_posts_in_gui"] == True and self.buffer.list.list.HasFocus():
output.speak(self.get_message(), interrupt=True)
if self.session.settings['sound']['indicate_audio'] and utils.is_audio_or_video(post): if self.session.settings['sound']['indicate_audio'] and utils.is_audio_or_video(post):
self.session.sound.play("audio.ogg") self.session.sound.play("audio.ogg")
if self.session.settings['sound']['indicate_img'] and utils.is_image(post): if self.session.settings['sound']['indicate_img'] and utils.is_image(post):

View File

@@ -1,17 +1,28 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import time import time
import logging import logging
import arrow
import widgetUtils import widgetUtils
import output import output
import languageHandler
import config
from pubsub import pub
from controller.buffers.mastodon.base import BaseBuffer from controller.buffers.mastodon.base import BaseBuffer
from controller.mastodon import messages
from sessions.mastodon import compose, templates from sessions.mastodon import compose, templates
from wxUI import buffers from wxUI import buffers
from wxUI.dialogs.mastodon import dialogs as mastodon_dialogs from wxUI.dialogs.mastodon import dialogs as mastodon_dialogs
from wxUI.dialogs.mastodon import menus
from mysc.thread_utils import call_threaded
log = logging.getLogger("controller.buffers.mastodon.notifications") log = logging.getLogger("controller.buffers.mastodon.notifications")
class NotificationsBuffer(BaseBuffer): class NotificationsBuffer(BaseBuffer):
def __init__(self, *args, **kwargs):
super(NotificationsBuffer, self).__init__(*args, **kwargs)
self.type = "notificationsBuffer"
def get_message(self): def get_message(self):
notification = self.get_item() notification = self.get_item()
if notification == None: if notification == None:
@@ -30,22 +41,99 @@ class NotificationsBuffer(BaseBuffer):
original_date = arrow.get(self.session.db[self.name][self.buffer.list.get_selected()].created_at) original_date = arrow.get(self.session.db[self.name][self.buffer.list.get_selected()].created_at)
ts = original_date.humanize(locale=languageHandler.getLanguage()) ts = original_date.humanize(locale=languageHandler.getLanguage())
self.buffer.list.list.SetItem(self.buffer.list.get_selected(), 1, ts) self.buffer.list.list.SetItem(self.buffer.list.get_selected(), 1, ts)
if config.app["app-settings"]["read_long_posts_in_gui"] == True and self.buffer.list.list.HasFocus():
output.speak(self.get_message(), interrupt=True)
def bind_events(self): def bind_events(self):
self.buffer.set_focus_function(self.onFocus)
widgetUtils.connect_event(self.buffer.list.list, widgetUtils.KEYPRESS, self.get_event) widgetUtils.connect_event(self.buffer.list.list, widgetUtils.KEYPRESS, self.get_event)
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.post_status, self.buffer.post) widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.post_status, self.buffer.post)
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.destroy_status, self.buffer.dismiss) widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.destroy_status, self.buffer.dismiss)
def fav(self):
pass
def unfav(self):
pass
def vote(self): def vote(self):
pass pass
def can_share(self): def can_share(self, *args, **kwargs):
if self.is_post():
item = self.get_item()
return super(NotificationsBuffer, self).can_share(item=item.status)
return False
def add_to_favorites(self):
if self.is_post():
item = self.get_item()
super(NotificationsBuffer, self).add_to_favorites(item=item.status)
def remove_from_favorites(self):
if self.is_post():
item = self.get_item()
super(NotificationsBuffer, self).remove_from_favorites(item=item.status)
def toggle_favorite(self, *args, **kwargs):
if self.is_post():
item = self.get_item()
super(NotificationsBuffer, self).toggle_favorite(item=item.status)
def toggle_bookmark(self, *args, **kwargs):
if self.is_post():
item = self.get_item()
super(NotificationsBuffer, self).toggle_bookmark(item=item.status)
def reply(self, *args, **kwargs):
if self.is_post():
item = self.get_item()
super(NotificationsBuffer, self).reply(item=item.status)
def share_item(self, *args, **kwargs):
if self.is_post():
item = self.get_item()
super(NotificationsBuffer, self).share_item(item=item.status)
def url(self, *args, **kwargs):
if self.is_post():
item = self.get_item()
super(NotificationsBuffer, self).url(item=item.status, *args, **kwargs)
def audio(self, *args, **kwargs):
if self.is_post():
item = self.get_item()
super(NotificationsBuffer, self).audio(item=item.status)
def view_item(self, *args, **kwargs):
if self.is_post():
item = self.get_item()
super(NotificationsBuffer, self).view_item(item=item.status)
else:
pub.sendMessage("execute-action", action="user_details")
def open_in_browser(self, *args, **kwargs):
if self.is_post():
item = self.get_item()
super(NotificationsBuffer, self).open_in_browser(item=item.status)
def send_message(self, *args, **kwargs):
if self.is_post():
item = self.get_item()
super(NotificationsBuffer, self).send_message(item=item.status)
else:
item = self.get_item()
title = _("New conversation with {}").format(item.account.username)
caption = _("Write your message here")
users_str = "@{} ".format(item.account.acct)
post = messages.post(session=self.session, title=title, caption=caption, text=users_str)
post.message.visibility.SetSelection(3)
response = post.message.ShowModal()
if response == wx.ID_OK:
post_data = post.get_data()
call_threaded(self.session.send_post, posts=post_data, visibility="direct")
if hasattr(post.message, "destroy"):
post.message.destroy()
def is_post(self):
post_types = ["status", "mention", "reblog", "favourite", "update", "poll"]
item = self.get_item()
if item.type in post_types:
return True
return False return False
def destroy_status(self, *args, **kwargs): def destroy_status(self, *args, **kwargs):
@@ -64,3 +152,29 @@ class NotificationsBuffer(BaseBuffer):
self.session.sound.play("error.ogg") self.session.sound.play("error.ogg")
log.exception("") log.exception("")
self.session.db[self.name] = items self.session.db[self.name] = items
def show_menu(self, ev, pos=0, *args, **kwargs):
if self.buffer.list.get_count() == 0:
return
notification = self.get_item()
menu = menus.notification(notification.type)
if self.is_post():
widgetUtils.connect_event(menu, widgetUtils.MENU, self.reply, menuitem=menu.reply)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.user_actions, menuitem=menu.userActions)
if self.can_share() == True:
widgetUtils.connect_event(menu, widgetUtils.MENU, self.share_item, menuitem=menu.boost)
else:
menu.boost.Enable(False)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.fav, menuitem=menu.fav)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.unfav, menuitem=menu.unfav)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.url_, menuitem=menu.openUrl)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.audio, menuitem=menu.play)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.view, menuitem=menu.view)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.copy, menuitem=menu.copy)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.destroy_status, menuitem=menu.remove)
if hasattr(menu, "openInBrowser"):
widgetUtils.connect_event(menu, widgetUtils.MENU, self.open_in_browser, menuitem=menu.openInBrowser)
if pos != 0:
self.buffer.PopupMenu(menu, pos)
else:
self.buffer.PopupMenu(menu, self.buffer.list.list.GetPosition())

View File

@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
"""
Implements searching functionality for mastodon
Used for searching for statuses (posts) or possibly hashtags
"""
import logging
import time
from pubsub import pub
from .base import BaseBuffer
import output
import widgetUtils
from wxUI import commonMessageDialogs
log = logging.getLogger("controller.buffers.mastodon.search")
class SearchBuffer(BaseBuffer):
"""Search buffer
There are some methods of the Base Buffer that can't be used here
"""
def start_stream(self, mandatory: bool=False, play_sound: bool=True, avoid_autoreading: bool=False) -> None:
"""Start streaming
Parameters:
- mandatory [bool]: Force start stream if True
- play_sound [bool]: Specifies whether to play sound after receiving posts
avoid_autoreading [bool]: Reads the posts if set to True
returns [None | int]: Number of posts received
"""
log.debug(f"Starting streamd for buffer {self.name} account {self.account} and type {self.type}")
log.debug(f"Args: {self.args}, Kwargs: {self.kwargs}")
current_time = time.time()
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
self.execution_time = current_time
min_id = None
if self.name in self.session.db and len(self.session.db[self.name]) > 0:
if self.session.settings["general"]["reverse_timelines"]:
min_id = self.session.db[self.name][0].id
else:
min_id = self.session.db[self.name][-1].id
try:
results = getattr(self.session.api, self.function)(min_id=min_id, **self.kwargs)
except Exception as mess:
log.exception(f"Error while receiving search posts {mess}")
return
results = results.statuses
results.reverse()
num_of_items = self.session.order_buffer(self.name, results)
log.debug(f"Number of items retrieved: {num_of_items}")
self.put_items_on_list(num_of_items)
# playsound and autoread
if num_of_items > 0:
if self.sound != None and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and play_sound == True:
self.session.sound.play(self.sound)
if avoid_autoreading == False and mandatory == True and self.name in self.session.settings["other_buffers"]["autoread_buffers"]:
self.auto_read(num_of_items)
return num_of_items
def remove_buffer(self, force: bool=False) -> bool:
"""Performs clean-up tasks before removing buffer
Parameters:
- force [bool]: Force removes buffer if true
Returns [bool]: True proceed with removing buffer or False abort
removing buffer
"""
# Ask user
if not force:
response = commonMessageDialogs.remove_buffer()
else:
response = widgetUtils.YES
if response == widgetUtils.NO:
return False
# remove references of this buffer in db and settings
if self.name in self.session.db:
self.session.db.pop(self.name)
if self.kwargs.get('q') in self.session.settings['other_buffers']['post_searches']:
self.session.settings['other_buffers']['post_searches'].remove(self.kwargs['q'])
return True
def get_more_items(self):
output.speak(_(u"This action is not supported for this buffer"), True)

View File

@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os import os
import sys
import logging import logging
import webbrowser import webbrowser
import wx import wx
@@ -116,6 +117,7 @@ class Controller(object):
# connect application events to GUI # connect application events to GUI
widgetUtils.connect_event(self.view, widgetUtils.CLOSE_EVENT, self.exit_) widgetUtils.connect_event(self.view, widgetUtils.CLOSE_EVENT, self.exit_)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.show_hide, menuitem=self.view.show_hide) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.show_hide, menuitem=self.view.show_hide)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.update_profile, menuitem=self.view.updateProfile)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.search, menuitem=self.view.menuitem_search) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.search, menuitem=self.view.menuitem_search)
# widgetUtils.connect_event(self.view, widgetUtils.MENU, self.list_manager, menuitem=self.view.lists) # widgetUtils.connect_event(self.view, widgetUtils.MENU, self.list_manager, menuitem=self.view.lists)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.find, menuitem=self.view.find) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.find, menuitem=self.view.find)
@@ -138,6 +140,7 @@ class Controller(object):
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.delete, self.view.delete) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.delete, self.view.delete)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.follow, menuitem=self.view.follow) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.follow, menuitem=self.view.follow)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.send_dm, self.view.dm) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.send_dm, self.view.dm)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.user_details, self.view.details)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.get_more_items, menuitem=self.view.load_previous_items) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.get_more_items, menuitem=self.view.load_previous_items)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.clear_buffer, menuitem=self.view.clear) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.clear_buffer, menuitem=self.view.clear)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.remove_buffer, self.view.deleteTl) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.remove_buffer, self.view.deleteTl)
@@ -150,6 +153,7 @@ class Controller(object):
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.toggle_buffer_mute, self.view.mute_buffer) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.toggle_buffer_mute, self.view.mute_buffer)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.open_timeline, self.view.timeline) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.open_timeline, self.view.timeline)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.open_favs_timeline, self.view.favs) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.open_favs_timeline, self.view.favs)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.community_timeline, self.view.community_timeline)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.open_conversation, menuitem=self.view.view_conversation) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.open_conversation, menuitem=self.view.view_conversation)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.seekLeft, menuitem=self.view.seekLeft) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.seekLeft, menuitem=self.view.seekLeft)
widgetUtils.connect_event(self.view, widgetUtils.MENU, self.seekRight, menuitem=self.view.seekRight) widgetUtils.connect_event(self.view, widgetUtils.MENU, self.seekRight, menuitem=self.view.seekRight)
@@ -427,6 +431,10 @@ class Controller(object):
return handler.account_settings(buffer=buffer, controller=self) return handler.account_settings(buffer=buffer, controller=self)
def check_for_updates(self, *args, **kwargs): def check_for_updates(self, *args, **kwargs):
if not getattr(sys, 'frozen', False):
log.debug("Running from source, can't update")
commonMessageDialogs.cant_update_source()
return
update = updater.do_update() update = updater.do_update()
if update == False: if update == False:
view.no_update_available() view.no_update_available()
@@ -839,7 +847,14 @@ class Controller(object):
def register_invisible_keyboard_shorcuts(self, keymap): def register_invisible_keyboard_shorcuts(self, keymap):
if config.changed_keymap: if config.changed_keymap:
commonMessageDialogs.changed_keymap() build_number = sys.getwindowsversion().build
if build_number > 22000:
system = "Windows 11"
keystroke_editor_shortcut = "Control+Win+Alt+K"
else:
system = "Windows 10"
keystroke_editor_shortcut = "Win+Alt+K"
commonMessageDialogs.changed_keymap(system, keystroke_editor_shortcut)
# Make sure we pass a keymap without undefined keystrokes. # Make sure we pass a keymap without undefined keystrokes.
new_keymap = {key: keymap[key] for key in keymap.keys() if keymap[key] != ""} new_keymap = {key: keymap[key] for key in keymap.keys() if keymap[key] != ""}
self.keyboard_handler = WXKeyboardHandler(self.view) self.keyboard_handler = WXKeyboardHandler(self.view)
@@ -892,7 +907,7 @@ class Controller(object):
except: except:
output.speak(_(u"An error happened while trying to connect to the server. Please try later.")) output.speak(_(u"An error happened while trying to connect to the server. Please try later."))
return return
# There is no twblue.es/en, so if English is the language used this should be False anyway. # There is no twblue.mcvsoftware.com/en, so if English is the language used this should be False anyway.
if response.status_code == 200 and lang != "en": if response.status_code == 200 and lang != "en":
webbrowser.open_new_tab(final_url) webbrowser.open_new_tab(final_url)
else: else:
@@ -908,7 +923,7 @@ class Controller(object):
except: except:
output.speak(_(u"An error happened while trying to connect to the server. Please try later.")) output.speak(_(u"An error happened while trying to connect to the server. Please try later."))
return return
# There is no twblue.es/en, so if English is the language used this should be False anyway. # There is no twblue.mcvsoftware.com/en, so if English is the language used this should be False anyway.
if response.status_code == 200 and lang != "en": if response.status_code == 200 and lang != "en":
webbrowser.open_new_tab(final_url) webbrowser.open_new_tab(final_url)
else: else:
@@ -984,9 +999,9 @@ class Controller(object):
def repeat_item(self, *args, **kwargs): def repeat_item(self, *args, **kwargs):
output.speak(self.get_current_buffer().get_message()) output.speak(self.get_current_buffer().get_message())
def execute_action(self, action): def execute_action(self, action, kwargs={}):
if hasattr(self, action): if hasattr(self, action):
getattr(self, action)() getattr(self, action)(**kwargs)
def update_buffers(self): def update_buffers(self):
for i in self.buffers[:]: for i in self.buffers[:]:
@@ -1085,3 +1100,58 @@ class Controller(object):
"""Redirects the user to the issue page on github""" """Redirects the user to the issue page on github"""
log.debug("Redirecting the user to report an error...") log.debug("Redirecting the user to report an error...")
webbrowser.open_new_tab(application.report_bugs_url) webbrowser.open_new_tab(application.report_bugs_url)
def update_profile(self, *args):
"""Updates the users profile"""
log.debug("Update profile")
buffer = self.get_best_buffer()
handler = self.get_handler(buffer.session.type)
if handler:
handler.update_profile(buffer.session)
def user_details(self, *args):
"""Displays a user's profile."""
log.debug("Showing user profile...")
buffer = self.get_best_buffer()
handler = self.get_handler(type=buffer.session.type)
if handler and hasattr(handler, 'user_details'):
handler.user_details(buffer)
def openPostTimeline(self, *args, user=None):
"""Opens selected user's posts timeline
Parameters:
args: Other argument. Useful when binding to widgets.
user: if specified, open this user timeline. It is currently mandatory, but could be optional when user selection is implemented in handler
"""
buffer = self.get_best_buffer()
handler = self.get_handler(type=buffer.session.type)
if handler and hasattr(handler, 'openPostTimeline'):
handler.openPostTimeline(self, buffer, user)
def openFollowersTimeline(self, *args, user=None):
"""Opens selected user's followers timeline
Parameters:
args: Other argument. Useful when binding to widgets.
user: if specified, open this user timeline. It is currently mandatory, but could be optional when user selection is implemented in handler
"""
buffer = self.get_best_buffer()
handler = self.get_handler(type=buffer.session.type)
if handler and hasattr(handler, 'openFollowersTimeline'):
handler.openFollowersTimeline(self, buffer, user)
def openFollowingTimeline(self, *args, user=None):
"""Opens selected user's following timeline
Parameters:
args: Other argument. Useful when binding to widgets.
user: if specified, open this user timeline. It is currently mandatory, but could be optional when user selection is implemented in handler
"""
buffer = self.get_best_buffer()
handler = self.get_handler(type=buffer.session.type)
if handler and hasattr(handler, 'openFollowingTimeline'):
handler.openFollowingTimeline(self, buffer, user)
def community_timeline(self, *args, user=None):
buffer = self.get_best_buffer()
handler = self.get_handler(type=buffer.session.type)
if handler and hasattr(handler, 'community_timeline'):
handler.community_timeline(self, buffer)

View File

@@ -1,13 +1,19 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import wx import wx
import logging import logging
import mastodon
import output import output
from mastodon import MastodonError
from pubsub import pub from pubsub import pub
from mysc import restart from mysc import restart
from mysc.thread_utils import call_threaded
from wxUI.dialogs.mastodon import search as search_dialogs from wxUI.dialogs.mastodon import search as search_dialogs
from wxUI.dialogs.mastodon import dialogs from wxUI.dialogs.mastodon import dialogs
from wxUI.dialogs import userAliasDialogs from wxUI.dialogs import userAliasDialogs
from wxUI import commonMessageDialogs from wxUI import commonMessageDialogs
from wxUI.dialogs.mastodon import updateProfile as update_profile_dialogs
from wxUI.dialogs.mastodon import showUserProfile, communityTimeline
from sessions.mastodon.utils import html_filter
from . import userActions, settings from . import userActions, settings
log = logging.getLogger("controller.mastodon.handler") log = logging.getLogger("controller.mastodon.handler")
@@ -20,7 +26,7 @@ class Handler(object):
# empty names mean the item will be Disabled. # empty names mean the item will be Disabled.
self.menus = dict( self.menus = dict(
# In application menu. # In application menu.
updateProfile=None, updateProfile=_("Update Profile"),
menuitem_search=_("&Search"), menuitem_search=_("&Search"),
lists=None, lists=None,
manageAliases=_("Manage user aliases"), manageAliases=_("Manage user aliases"),
@@ -41,10 +47,10 @@ class Handler(object):
addAlias=_("Add a&lias"), addAlias=_("Add a&lias"),
addToList=None, addToList=None,
removeFromList=None, removeFromList=None,
details=None, details=_("Show user profile"),
favs=None, favs=None,
# In buffer Menu. # In buffer Menu.
trends=None, community_timeline =_("Create community timeline"),
filter=None, filter=None,
manage_filters=None manage_filters=None
) )
@@ -98,9 +104,16 @@ class Handler(object):
# for i in session.settings["other_buffers"]["lists"]: # for i in session.settings["other_buffers"]["lists"]:
# pub.sendMessage("createBuffer", buffer_type="ListBuffer", session_type=session.type, buffer_title=_(u"List for {}").format(i), parent_tab=lists_position, start=False, kwargs=dict(parent=controller.view.nb, function="list_timeline", name="%s-list" % (i,), sessionObject=session, name, bufferType=None, sound="list_tweet.ogg", list_id=utils.find_list(i, session.db["lists"]), include_ext_alt_text=True, tweet_mode="extended")) # pub.sendMessage("createBuffer", buffer_type="ListBuffer", session_type=session.type, buffer_title=_(u"List for {}").format(i), parent_tab=lists_position, start=False, kwargs=dict(parent=controller.view.nb, function="list_timeline", name="%s-list" % (i,), sessionObject=session, name, bufferType=None, sound="list_tweet.ogg", list_id=utils.find_list(i, session.db["lists"]), include_ext_alt_text=True, tweet_mode="extended"))
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Searches"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="searches", account=name)) pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Searches"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="searches", account=name))
# searches_position =controller.view.search("searches", session.db["user_name"]) searches_position =controller.view.search("searches", name)
# for i in session.settings["other_buffers"]["tweet_searches"]: for term in session.settings["other_buffers"]["post_searches"]:
# pub.sendMessage("createBuffer", buffer_type="SearchBuffer", session_type=session.type, buffer_title=_(u"Search for {}").format(i), parent_tab=searches_position, start=False, kwargs=dict(parent=controller.view.nb, function="search_tweets", name="%s-searchterm" % (i,), sessionObject=session, name, bufferType="searchPanel", sound="search_updated.ogg", q=i, include_ext_alt_text=True, tweet_mode="extended")) pub.sendMessage("createBuffer", buffer_type="SearchBuffer", session_type=session.type, buffer_title=_("Search for {}").format(term), parent_tab=searches_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_post", function="search", name="%s-searchterm" % (term,), sessionObject=session, account=session.get_name(), sound="search_updated.ogg", q=term, result_type="statuses"))
pub.sendMessage("createBuffer", buffer_type="EmptyBuffer", session_type="base", buffer_title=_("Communities"), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="communities", account=name))
communities_position =controller.view.search("communities", name)
for community in session.settings["other_buffers"]["communities"]:
bufftype = _("Local") if community.split("@")[0] == "local" else _("federated")
community_name = community.split("@")[1].replace("https://", "")
title = _(f"{bufftype} timeline for {community_name}")
pub.sendMessage("createBuffer", buffer_type="CommunityBuffer", session_type=session.type, buffer_title=title, parent_tab=communities_position, start=True, kwargs=dict(parent=controller.view.nb, function="timeline", compose_func="compose_post", name=community, sessionObject=session, community_url=community.split("@")[1], account=session.get_name(), sound="search_updated.ogg", timeline=community.split("@")[0]))
# for i in session.settings["other_buffers"]["trending_topic_buffers"]: # for i in session.settings["other_buffers"]["trending_topic_buffers"]:
# pub.sendMessage("createBuffer", buffer_type="TrendsBuffer", session_type=session.type, buffer_title=_("Trending topics for %s") % (i), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="%s_tt" % (i,), sessionObject=session, name, trendsFor=i, sound="trends_updated.ogg")) # pub.sendMessage("createBuffer", buffer_type="TrendsBuffer", session_type=session.type, buffer_title=_("Trending topics for %s") % (i), parent_tab=root_position, start=False, kwargs=dict(parent=controller.view.nb, name="%s_tt" % (i,), sessionObject=session, name, trendsFor=i, sound="trends_updated.ogg"))
@@ -117,7 +130,12 @@ class Handler(object):
pub.sendMessage("buffer-title-changed", buffer=buffer) pub.sendMessage("buffer-title-changed", buffer=buffer)
def open_conversation(self, controller, buffer): def open_conversation(self, controller, buffer):
post = buffer.get_item() # detect if we are in a community buffer.
# Community buffers are special because we'll need to retrieve the object locally at first.
if hasattr(buffer, "community_url"):
post = buffer.get_item_from_instance()
else:
post = buffer.get_item()
if post.reblog != None: if post.reblog != None:
post = post.reblog post = post.reblog
conversations_position =controller.view.search("direct_messages", buffer.session.get_name()) conversations_position =controller.view.search("direct_messages", buffer.session.get_name())
@@ -138,6 +156,21 @@ class Handler(object):
users = [user.acct for user in item.mentions if user.id != buffer.session.db["user_id"]] users = [user.acct for user in item.mentions if user.id != buffer.session.db["user_id"]]
if item.account.acct not in users: if item.account.acct not in users:
users.insert(0, item.account.acct) users.insert(0, item.account.acct)
elif buffer.type == "notificationsBuffer":
if buffer.is_post():
status = item.status
if status.reblog != None:
users = [user.acct for user in status.reblog.mentions if user.id != buffer.session.db["user_id"]]
if status.reblog.account.acct not in users and status.account.id != buffer.session.db["user_id"]:
users.insert(0, status.reblog.account.acct)
else:
users = [user.acct for user in status.mentions if user.id != buffer.session.db["user_id"]]
if hasattr(item, "account"):
acct = item.account.acct
else:
acct = item.acct
if acct not in users:
users.insert(0, item.account.acct)
u = userActions.userActions(buffer.session, users) u = userActions.userActions(buffer.session, users)
def search(self, controller, session, value): def search(self, controller, session, value):
@@ -150,7 +183,7 @@ class Handler(object):
if term not in session.settings["other_buffers"]["post_searches"]: if term not in session.settings["other_buffers"]["post_searches"]:
session.settings["other_buffers"]["post_searches"].append(term) session.settings["other_buffers"]["post_searches"].append(term)
session.settings.write() session.settings.write()
# pub.sendMessage("createBuffer", buffer_type="SearchBuffer", session_type=session.type, buffer_title=_("Search for {}").format(term), parent_tab=searches_position, start=True, kwargs=dict(parent=controller.view.nb, function="search_tweets", name="%s-searchterm" % (term,), sessionObject=session, account=session.get_name(), bufferType="searchPanel", sound="search_updated.ogg", q=term, include_ext_alt_text=True, tweet_mode="extended")) pub.sendMessage("createBuffer", buffer_type="SearchBuffer", session_type=session.type, buffer_title=_("Search for {}").format(term), parent_tab=searches_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_post", function="search", name="%s-searchterm" % (term,), sessionObject=session, account=session.get_name(), sound="search_updated.ogg", q=term, result_type="statuses"))
else: else:
log.error("A buffer for the %s search term is already created. You can't create a duplicate buffer." % (term,)) log.error("A buffer for the %s search term is already created. You can't create a duplicate buffer." % (term,))
return return
@@ -182,38 +215,52 @@ class Handler(object):
return return
user = u.user user = u.user
if action == "posts": if action == "posts":
if user.statuses_count == 0: self.openPostTimeline(controller, buffer, user)
dialogs.no_posts()
return
if user.id in buffer.session.settings["other_buffers"]["timelines"]:
commonMessageDialogs.timeline_exist()
return
timelines_position =controller.view.search("timelines", buffer.session.get_name())
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=buffer.session.type, buffer_title=_("Timeline for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="account_statuses", name="%s-timeline" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="tweet_timeline.ogg", id=user.id))
buffer.session.settings["other_buffers"]["timelines"].append(user.id)
buffer.session.sound.play("create_timeline.ogg")
elif action == "followers": elif action == "followers":
if user.followers_count == 0: self.openFollowersTimeline(controller, buffer, user)
dialogs.no_followers()
return
if user.id in buffer.session.settings["other_buffers"]["followers_timelines"]:
commonMessageDialogs.timeline_exist()
return
timelines_position =controller.view.search("timelines", buffer.session.get_name())
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Followers for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_followers", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id))
buffer.session.settings["other_buffers"]["followers_timelines"].append(user.id)
buffer.session.sound.play("create_timeline.ogg")
elif action == "following": elif action == "following":
if user.following_count == 0: self.openFollowingTimeline(controller, buffer, user)
dialogs.no_following()
return def openPostTimeline(self, controller, buffer, user):
if user.id in buffer.session.settings["other_buffers"]["following_timelines"]: """Opens post timeline for user"""
commonMessageDialogs.timeline_exist() if user.statuses_count == 0:
return dialogs.no_posts()
timelines_position =controller.view.search("timelines", buffer.session.get_name()) return
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Following for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_following", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id)) if user.id in buffer.session.settings["other_buffers"]["timelines"]:
buffer.session.settings["other_buffers"]["following_timelines"].append(user.id) commonMessageDialogs.timeline_exist()
buffer.session.sound.play("create_timeline.ogg") return
timelines_position =controller.view.search("timelines", buffer.session.get_name())
pub.sendMessage("createBuffer", buffer_type="BaseBuffer", session_type=buffer.session.type, buffer_title=_("Timeline for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, function="account_statuses", name="%s-timeline" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="tweet_timeline.ogg", id=user.id))
buffer.session.settings["other_buffers"]["timelines"].append(user.id)
buffer.session.sound.play("create_timeline.ogg")
buffer.session.settings.write()
def openFollowersTimeline(self, controller, buffer, user):
"""Open followers timeline for user"""
if user.followers_count == 0:
dialogs.no_followers()
return
if user.id in buffer.session.settings["other_buffers"]["followers_timelines"]:
commonMessageDialogs.timeline_exist()
return
timelines_position =controller.view.search("timelines", buffer.session.get_name())
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Followers for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_followers", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id))
buffer.session.settings["other_buffers"]["followers_timelines"].append(user.id)
buffer.session.sound.play("create_timeline.ogg")
buffer.session.settings.write()
def openFollowingTimeline(self, controller, buffer, user):
"""Open following timeline for user"""
if user.following_count == 0:
dialogs.no_following()
return
if user.id in buffer.session.settings["other_buffers"]["following_timelines"]:
commonMessageDialogs.timeline_exist()
return
timelines_position =controller.view.search("timelines", buffer.session.get_name())
pub.sendMessage("createBuffer", buffer_type="UserBuffer", session_type=buffer.session.type, buffer_title=_("Following for {}").format(user.username,), parent_tab=timelines_position, start=True, kwargs=dict(parent=controller.view.nb, compose_func="compose_user", function="account_following", name="%s-followers" % (user.id,), sessionObject=buffer.session, account=buffer.session.get_name(), sound="new_event.ogg", id=user.id))
buffer.session.settings["other_buffers"]["following_timelines"].append(user.id)
buffer.session.sound.play("create_timeline.ogg")
buffer.session.settings.write() buffer.session.settings.write()
def account_settings(self, buffer, controller): def account_settings(self, buffer, controller):
@@ -254,4 +301,101 @@ class Handler(object):
buffer.session.settings["user-aliases"][str(full_user.id)] = alias buffer.session.settings["user-aliases"][str(full_user.id)] = alias
buffer.session.settings.write() buffer.session.settings.write()
output.speak(_("Alias has been set correctly for {}.").format(user)) output.speak(_("Alias has been set correctly for {}.").format(user))
pub.sendMessage("alias-added") pub.sendMessage("alias-added")
def update_profile(self, session):
"""Updates the users dialog"""
profile = session.api.me()
data = {
'display_name': profile.display_name,
'note': html_filter(profile.note),
'header': profile.header,
'avatar': profile.avatar,
'fields': [(field.name, html_filter(field.value)) for field in profile.fields],
'locked': profile.locked,
'bot': profile.bot,
# discoverable could be None, set it to False
'discoverable': profile.discoverable if profile.discoverable else False,
}
log.debug(f"Received data_ {data['fields']}")
dialog = update_profile_dialogs.UpdateProfileDialog(**data)
if dialog.ShowModal() != wx.ID_OK:
log.debug("User canceled dialog")
return
updated_data = dialog.data
if updated_data == data:
log.debug("No profile info was changed.")
return
# remove data that hasn't been updated
for key in data:
if data[key] == updated_data[key]:
del updated_data[key]
log.debug(f"Updating users profile with: {updated_data}")
call_threaded(session.api_call, "account_update_credentials", _("Update profile"), report_success=True, **updated_data)
def user_details(self, buffer):
"""Displays user profile in a dialog.
This works as long as the focused item hass a 'account' key."""
if not hasattr(buffer, 'get_item'):
return # Tell user?
item = buffer.get_item()
if not item:
return # empty buffer
log.debug(f"Opening user profile. dictionary: {item}")
mentionedUsers = list()
holdUser = item.account if item.get('account') else None
if hasattr(item, "type") and item.type in ["status", "mention", "reblog", "favourite", "update", "poll"]: # statuses in Notification buffers
item = item.status
if item.get('username'): # account dict
holdUser = item
elif isinstance(item.get('mentions'), list):
# mentions in statuses
if item.reblog:
item = item.reblog
mentionedUsers = [(user.acct, user.id) for user in item.mentions]
holdUser = item.account
if not holdUser:
dialogs.no_user()
return
if len(mentionedUsers) == 0:
user = holdUser
else:
mentionedUsers.insert(0, (holdUser.display_name, holdUser.username, holdUser.id))
mentionedUsers = list(set(mentionedUsers))
selectedUser = showUserProfile.selectUserDialog(mentionedUsers)
if not selectedUser:
return # Canceled selection
elif selectedUser[-1] == holdUser.id:
user = holdUser
else: # We don't have this user's dictionary, get it!
user = buffer.session.api.account(selectedUser[-1])
dlg = showUserProfile.ShowUserProfile(user)
dlg.ShowModal()
def community_timeline(self, controller, buffer):
dlg = communityTimeline.CommunityTimeline()
if dlg.ShowModal() != wx.ID_OK:
return
url = dlg.url.GetValue()
bufftype = dlg.get_action()
local_api = mastodon.Mastodon(api_base_url=url)
try:
instance = local_api.instance()
except MastodonError:
commonMessageDialogs.invalid_instance()
return
if bufftype == "local":
title = _(f"Local timeline for {url.replace('https://', '')}")
else:
title = _(f"Federated timeline for {url}")
bufftype = "public"
dlg.Destroy()
tl_info = f"{bufftype}@{url}"
if tl_info in buffer.session.settings["other_buffers"]["communities"]:
return # buffer already exists.
buffer.session.settings["other_buffers"]["communities"].append(tl_info)
buffer.session.settings.write()
communities_position =controller.view.search("communities", buffer.session.get_name())
pub.sendMessage("createBuffer", buffer_type="CommunityBuffer", session_type=buffer.session.type, buffer_title=title, parent_tab=communities_position, start=True, kwargs=dict(parent=controller.view.nb, function="timeline", name=tl_info, sessionObject=buffer.session, account=buffer.session.get_name(), sound="tweet_timeline.ogg", community_url=url, timeline=bufftype))

View File

@@ -6,9 +6,12 @@ import widgetUtils
import config import config
import output import output
from twitter_text import parse_tweet, config from twitter_text import parse_tweet, config
from mastodon import MastodonError
from controller import messages from controller import messages
from sessions.mastodon import templates from sessions.mastodon import templates
from wxUI.dialogs.mastodon import postDialogs from wxUI.dialogs.mastodon import postDialogs
from extra.autocompletionUsers import completion
from . import userList
def character_count(post_text, post_cw, character_limit=500): def character_count(post_text, post_cw, character_limit=500):
# We will use text for counting character limit only. # We will use text for counting character limit only.
@@ -38,14 +41,17 @@ class post(messages.basicMessage):
widgetUtils.connect_event(self.message.translate, widgetUtils.BUTTON_PRESSED, self.translate) widgetUtils.connect_event(self.message.translate, widgetUtils.BUTTON_PRESSED, self.translate)
widgetUtils.connect_event(self.message.add, widgetUtils.BUTTON_PRESSED, self.on_attach) widgetUtils.connect_event(self.message.add, widgetUtils.BUTTON_PRESSED, self.on_attach)
widgetUtils.connect_event(self.message.remove_attachment, widgetUtils.BUTTON_PRESSED, self.remove_attachment) widgetUtils.connect_event(self.message.remove_attachment, widgetUtils.BUTTON_PRESSED, self.remove_attachment)
# ToDo: Add autocomplete feature to mastodon and uncomment this. widgetUtils.connect_event(self.message.autocomplete_users, widgetUtils.BUTTON_PRESSED, self.autocomplete_users)
# widgetUtils.connect_event(self.message.autocomplete_users, widgetUtils.BUTTON_PRESSED, self.autocomplete_users)
widgetUtils.connect_event(self.message.add_post, widgetUtils.BUTTON_PRESSED, self.add_post) widgetUtils.connect_event(self.message.add_post, widgetUtils.BUTTON_PRESSED, self.add_post)
widgetUtils.connect_event(self.message.remove_post, widgetUtils.BUTTON_PRESSED, self.remove_post) widgetUtils.connect_event(self.message.remove_post, widgetUtils.BUTTON_PRESSED, self.remove_post)
self.attachments = [] self.attachments = []
self.thread = [] self.thread = []
self.text_processor() self.text_processor()
def autocomplete_users(self, *args, **kwargs):
c = completion.autocompletionUsers(self.message, self.session.session_id)
c.show_menu()
def add_post(self, event, update_gui=True, *args, **kwargs): def add_post(self, event, update_gui=True, *args, **kwargs):
text = self.message.text.GetValue() text = self.message.text.GetValue()
attachments = self.attachments[::] attachments = self.attachments[::]
@@ -231,9 +237,11 @@ class post(messages.basicMessage):
self.message.visibility.SetSelection(setting) self.message.visibility.SetSelection(setting)
class viewPost(post): class viewPost(post):
def __init__(self, post, offset_hours=0, date="", item_url=""): def __init__(self, session, post, offset_hours=0, date="", item_url=""):
self.session = session
if post.reblog != None: if post.reblog != None:
post = post.reblog post = post.reblog
self.post_id = post.id
author = post.account.display_name if post.account.display_name != "" else post.account.username author = post.account.display_name if post.account.display_name != "" else post.account.username
title = _(u"Post from {}").format(author) title = _(u"Post from {}").format(author)
image_description = templates.process_image_descriptions(post.media_attachments) image_description = templates.process_image_descriptions(post.media_attachments)
@@ -250,6 +258,13 @@ class viewPost(post):
else: else:
source = source_obj.get("name") source = source_obj.get("name")
self.message = postDialogs.viewPost(text=text, boosts_count=boost_count, favs_count=favs_count, source=source, date=date, privacy=privacy) self.message = postDialogs.viewPost(text=text, boosts_count=boost_count, favs_count=favs_count, source=source, date=date, privacy=privacy)
participants = [post.account.id] + [account.id for account in post.mentions]
print(post, participants)
if self.session.db["user_id"] in participants:
self.message.mute.Enable(True)
if post.muted:
self.message.mute.SetLabel(_("Unmute conversation"))
widgetUtils.connect_event(self.message.mute, widgetUtils.BUTTON_PRESSED, self.mute_unmute)
self.message.SetTitle(title) self.message.SetTitle(title)
if image_description != "": if image_description != "":
self.message.image_description.Enable(True) self.message.image_description.Enable(True)
@@ -260,12 +275,42 @@ class viewPost(post):
widgetUtils.connect_event(self.message.share, widgetUtils.BUTTON_PRESSED, self.share) widgetUtils.connect_event(self.message.share, widgetUtils.BUTTON_PRESSED, self.share)
self.item_url = item_url self.item_url = item_url
widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate) widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate)
widgetUtils.connect_event(self.message.boosts_button, widgetUtils.BUTTON_PRESSED, self.on_boosts)
widgetUtils.connect_event(self.message.favorites_button, widgetUtils.BUTTON_PRESSED, self.on_favorites)
self.message.ShowModal() self.message.ShowModal()
# We won't need text_processor in this dialog, so let's avoid it. # We won't need text_processor in this dialog, so let's avoid it.
def text_processor(self): def text_processor(self):
pass pass
def mute_unmute(self, *args, **kwargs):
post = self.session.api.status(self.post_id)
if post.muted == True:
action = "status_unmute"
new_label = _("Mute conversation")
msg = _("Conversation unmuted.")
else:
action = "status_mute"
new_label = _("Unmute conversation")
msg = _("Conversation muted.")
try:
getattr(self.session.api, action)(self.post_id)
self.message.mute.SetLabel(new_label)
output.speak(msg)
except MastodonError:
return
def on_boosts(self, *args, **kwargs):
users = self.session.api.status_reblogged_by(self.post_id)
title = _("people who boosted this post")
user_list = userList.MastodonUserList(session=self.session, users=users, title=title)
def on_favorites(self, *args, **kwargs):
users = self.session.api.status_favourited_by(self.post_id)
title = _("people who favorited this post")
user_list = userList.MastodonUserList(session=self.session, users=users, title=title)
def share(self, *args, **kwargs): def share(self, *args, **kwargs):
if hasattr(self, "item_url"): if hasattr(self, "item_url"):
output.copy(self.item_url) output.copy(self.item_url)

View File

@@ -9,7 +9,8 @@ import output
from collections import OrderedDict from collections import OrderedDict
from wxUI import commonMessageDialogs from wxUI import commonMessageDialogs
from wxUI.dialogs.mastodon import configuration from wxUI.dialogs.mastodon import configuration
#from extra.autocompletionUsers import scan, manage from extra.autocompletionUsers import manage
from extra.autocompletionUsers.mastodon import scan
from extra.ocr import OCRSpace from extra.ocr import OCRSpace
from controller.settings import globalSettingsController from controller.settings import globalSettingsController
from . templateEditor import EditTemplate from . templateEditor import EditTemplate
@@ -29,8 +30,8 @@ class accountSettingsController(globalSettingsController):
def create_config(self): def create_config(self):
self.dialog.create_general_account() self.dialog.create_general_account()
# widgetUtils.connect_event(self.dialog.general.userAutocompletionScan, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_scan) widgetUtils.connect_event(self.dialog.general.userAutocompletionScan, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_scan)
# widgetUtils.connect_event(self.dialog.general.userAutocompletionManage, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_manage) widgetUtils.connect_event(self.dialog.general.userAutocompletionManage, widgetUtils.BUTTON_PRESSED, self.on_autocompletion_manage)
self.dialog.set_value("general", "disable_streaming", self.config["general"]["disable_streaming"]) self.dialog.set_value("general", "disable_streaming", self.config["general"]["disable_streaming"])
self.dialog.set_value("general", "relative_time", self.config["general"]["relative_times"]) self.dialog.set_value("general", "relative_time", self.config["general"]["relative_times"])
self.dialog.set_value("general", "read_preferences_from_instance", self.config["general"]["read_preferences_from_instance"]) self.dialog.set_value("general", "read_preferences_from_instance", self.config["general"]["read_preferences_from_instance"])

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from mastodon import MastodonError
from wxUI.dialogs.mastodon import showUserProfile
from controller.userList import UserListController
from . import userActions
class MastodonUserList(UserListController):
def process_users(self, users):
return [dict(id=user.id, display_name=f"{user.display_name} (@{user.acct})", acct=user.acct) for user in users]
def on_actions(self, *args, **kwargs):
user = self.dialog.user_list.GetSelection()
user_account = self.users[user]
u = userActions.userActions(self.session, [user_account.get("acct")])
def on_details(self, *args, **kwargs):
user = self.dialog.user_list.GetSelection()
user_id = self.users[user].get("id")
try:
user_object = self.session.api.account(user_id)
except MastodonError:
return
dlg = showUserProfile.ShowUserProfile(user_object)
dlg.ShowModal()

View File

@@ -1,25 +1,21 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import widgetUtils import widgetUtils
import output import output
from extra import translator, SpellChecker import config
from extra import SpellChecker
from extra.translator import TranslatorController
class basicMessage(object): class basicMessage(object):
def translate(self, event=None): def translate(self, event=None):
dlg = translator.gui.translateDialog() t = TranslatorController(self.message.text.GetValue())
if dlg.get_response() == widgetUtils.OK: if t.response == False:
text_to_translate = self.message.text.GetValue()
language_dict = translator.translator.available_languages()
for k in language_dict:
if language_dict[k] == dlg.dest_lang.GetStringSelection():
dst = k
msg = translator.translator.translate(text=text_to_translate, target=dst)
self.message.text.ChangeValue(msg)
self.message.text.SetInsertionPoint(len(self.message.text.GetValue()))
self.text_processor()
self.message.text.SetFocus()
output.speak(_(u"Translated"))
else:
return return
msg = t.translate()
self.message.text.ChangeValue(msg)
self.message.text.SetInsertionPoint(len(self.message.text.GetValue()))
self.text_processor()
self.message.text.SetFocus()
output.speak(_(u"Translated"))
def text_processor(self, *args, **kwargs): def text_processor(self, *args, **kwargs):
pass pass

View File

@@ -6,7 +6,6 @@ import config
import languageHandler import languageHandler
import application import application
from pubsub import pub from pubsub import pub
from mysc import autostart as autostart_windows
from wxUI.dialogs import configuration from wxUI.dialogs import configuration
from wxUI import commonMessageDialogs from wxUI import commonMessageDialogs
@@ -48,21 +47,16 @@ class globalSettingsController(object):
self.dialog.create_general(langs,self.kmfriendlies) self.dialog.create_general(langs,self.kmfriendlies)
self.dialog.general.language.SetSelection(id) self.dialog.general.language.SetSelection(id)
self.dialog.general.km.SetSelection(self.kmid) self.dialog.general.km.SetSelection(self.kmid)
if paths.mode == "installed":
self.dialog.set_value("general", "autostart", config.app["app-settings"]["autostart"])
else:
self.dialog.general.autostart.Enable(False)
self.dialog.set_value("general", "ask_at_exit", config.app["app-settings"]["ask_at_exit"]) self.dialog.set_value("general", "ask_at_exit", config.app["app-settings"]["ask_at_exit"])
self.dialog.set_value("general", "no_streaming", config.app["app-settings"]["no_streaming"]) self.dialog.set_value("general", "no_streaming", config.app["app-settings"]["no_streaming"])
self.dialog.set_value("general", "play_ready_sound", config.app["app-settings"]["play_ready_sound"]) self.dialog.set_value("general", "play_ready_sound", config.app["app-settings"]["play_ready_sound"])
self.dialog.set_value("general", "speak_ready_msg", config.app["app-settings"]["speak_ready_msg"]) self.dialog.set_value("general", "speak_ready_msg", config.app["app-settings"]["speak_ready_msg"])
self.dialog.set_value("general", "handle_longtweets", config.app["app-settings"]["handle_longtweets"]) self.dialog.set_value("general", "read_long_posts_in_gui", config.app["app-settings"]["read_long_posts_in_gui"])
self.dialog.set_value("general", "use_invisible_shorcuts", config.app["app-settings"]["use_invisible_keyboard_shorcuts"]) self.dialog.set_value("general", "use_invisible_shorcuts", config.app["app-settings"]["use_invisible_keyboard_shorcuts"])
self.dialog.set_value("general", "disable_sapi5", config.app["app-settings"]["voice_enabled"]) self.dialog.set_value("general", "disable_sapi5", config.app["app-settings"]["voice_enabled"])
self.dialog.set_value("general", "hide_gui", config.app["app-settings"]["hide_gui"]) self.dialog.set_value("general", "hide_gui", config.app["app-settings"]["hide_gui"])
self.dialog.set_value("general", "update_period", config.app["app-settings"]["update_period"]) self.dialog.set_value("general", "update_period", config.app["app-settings"]["update_period"])
self.dialog.set_value("general", "check_for_updates", config.app["app-settings"]["check_for_updates"]) self.dialog.set_value("general", "check_for_updates", config.app["app-settings"]["check_for_updates"])
self.dialog.set_value("general", "remember_mention_and_longtweet", config.app["app-settings"]["remember_mention_and_longtweet"])
proxyTypes = [_("System default"), _("HTTP"), _("SOCKS v4"), _("SOCKS v4 with DNS support"), _("SOCKS v5"), _("SOCKS v5 with DNS support")] proxyTypes = [_("System default"), _("HTTP"), _("SOCKS v4"), _("SOCKS v4 with DNS support"), _("SOCKS v5"), _("SOCKS v5 with DNS support")]
self.dialog.create_proxy(proxyTypes) self.dialog.create_proxy(proxyTypes)
try: try:
@@ -73,7 +67,10 @@ class globalSettingsController(object):
self.dialog.set_value("proxy", "port", config.app["proxy"]["port"]) self.dialog.set_value("proxy", "port", config.app["proxy"]["port"])
self.dialog.set_value("proxy", "user", config.app["proxy"]["user"]) self.dialog.set_value("proxy", "user", config.app["proxy"]["user"])
self.dialog.set_value("proxy", "password", config.app["proxy"]["password"]) self.dialog.set_value("proxy", "password", config.app["proxy"]["password"])
self.dialog.create_translator_panel()
self.dialog.set_value("translator_panel", "libre_api_url", config.app["translator"]["lt_api_url"])
self.dialog.set_value("translator_panel", "libre_api_key", config.app["translator"]["lt_api_key"])
self.dialog.set_value("translator_panel", "deepL_api_key", config.app["translator"]["deepl_api_key"])
self.dialog.realize() self.dialog.realize()
self.response = self.dialog.get_response() self.response = self.dialog.get_response()
@@ -89,9 +86,6 @@ class globalSettingsController(object):
kmFile.close() kmFile.close()
log.debug("Triggered app restart due to a keymap change.") log.debug("Triggered app restart due to a keymap change.")
self.needs_restart = True self.needs_restart = True
if config.app["app-settings"]["autostart"] != self.dialog.get_value("general", "autostart") and paths.mode == "installed":
config.app["app-settings"]["autostart"] = self.dialog.get_value("general", "autostart")
autostart_windows.setAutoStart(application.name, enable=self.dialog.get_value("general", "autostart"))
if config.app["app-settings"]["use_invisible_keyboard_shorcuts"] != self.dialog.get_value("general", "use_invisible_shorcuts"): if config.app["app-settings"]["use_invisible_keyboard_shorcuts"] != self.dialog.get_value("general", "use_invisible_shorcuts"):
config.app["app-settings"]["use_invisible_keyboard_shorcuts"] = self.dialog.get_value("general", "use_invisible_shorcuts") config.app["app-settings"]["use_invisible_keyboard_shorcuts"] = self.dialog.get_value("general", "use_invisible_shorcuts")
pub.sendMessage("invisible-shorcuts-changed", registered=self.dialog.get_value("general", "use_invisible_shorcuts")) pub.sendMessage("invisible-shorcuts-changed", registered=self.dialog.get_value("general", "use_invisible_shorcuts"))
@@ -106,11 +100,10 @@ class globalSettingsController(object):
config.app["app-settings"]["voice_enabled"] = self.dialog.get_value("general", "disable_sapi5") config.app["app-settings"]["voice_enabled"] = self.dialog.get_value("general", "disable_sapi5")
config.app["app-settings"]["hide_gui"] = self.dialog.get_value("general", "hide_gui") config.app["app-settings"]["hide_gui"] = self.dialog.get_value("general", "hide_gui")
config.app["app-settings"]["ask_at_exit"] = self.dialog.get_value("general", "ask_at_exit") config.app["app-settings"]["ask_at_exit"] = self.dialog.get_value("general", "ask_at_exit")
config.app["app-settings"]["handle_longtweets"] = self.dialog.get_value("general", "handle_longtweets") config.app["app-settings"]["read_long_posts_in_gui"] = self.dialog.get_value("general", "read_long_posts_in_gui")
config.app["app-settings"]["play_ready_sound"] = self.dialog.get_value("general", "play_ready_sound") config.app["app-settings"]["play_ready_sound"] = self.dialog.get_value("general", "play_ready_sound")
config.app["app-settings"]["speak_ready_msg"] = self.dialog.get_value("general", "speak_ready_msg") config.app["app-settings"]["speak_ready_msg"] = self.dialog.get_value("general", "speak_ready_msg")
config.app["app-settings"]["check_for_updates"] = self.dialog.get_value("general", "check_for_updates") config.app["app-settings"]["check_for_updates"] = self.dialog.get_value("general", "check_for_updates")
config.app["app-settings"]["remember_mention_and_longtweet"] = self.dialog.get_value("general", "remember_mention_and_longtweet")
if config.app["proxy"]["type"]!=self.dialog.get_value("proxy", "type") or config.app["proxy"]["server"] != self.dialog.get_value("proxy", "server") or config.app["proxy"]["port"] != self.dialog.get_value("proxy", "port") or config.app["proxy"]["user"] != self.dialog.get_value("proxy", "user") or config.app["proxy"]["password"] != self.dialog.get_value("proxy", "password"): if config.app["proxy"]["type"]!=self.dialog.get_value("proxy", "type") or config.app["proxy"]["server"] != self.dialog.get_value("proxy", "server") or config.app["proxy"]["port"] != self.dialog.get_value("proxy", "port") or config.app["proxy"]["user"] != self.dialog.get_value("proxy", "user") or config.app["proxy"]["password"] != self.dialog.get_value("proxy", "password"):
if self.is_started == True: if self.is_started == True:
self.needs_restart = True self.needs_restart = True
@@ -120,4 +113,7 @@ class globalSettingsController(object):
config.app["proxy"]["port"] = self.dialog.get_value("proxy", "port") config.app["proxy"]["port"] = self.dialog.get_value("proxy", "port")
config.app["proxy"]["user"] = self.dialog.get_value("proxy", "user") config.app["proxy"]["user"] = self.dialog.get_value("proxy", "user")
config.app["proxy"]["password"] = self.dialog.get_value("proxy", "password") config.app["proxy"]["password"] = self.dialog.get_value("proxy", "password")
config.app["translator"]["lt_api_url"] = self.dialog.get_value("translator_panel", "libre_api_url")
config.app["translator"]["lt_api_key"] = self.dialog.get_value("translator_panel", "libre_api_key")
config.app["translator"]["deepl_api_key"] = self.dialog.get_value("translator_panel", "deepL_api_key")
config.app.write() config.app.write()

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
import widgetUtils
from pubsub import pub
from wxUI.dialogs import userList
class UserListController(object):
def __init__(self, users, session, title):
super(UserListController, self).__init__()
self.session = session
self.users = self.process_users(users)
self.dialog = userList.UserListDialog(title=title, users=[user.get("display_name", user.get("acct")) for user in self.users])
widgetUtils.connect_event(self.dialog.actions_button, widgetUtils.BUTTON_PRESSED, self.on_actions)
widgetUtils.connect_event(self.dialog.details_button, widgetUtils.BUTTON_PRESSED, self.on_details)
self.dialog.ShowModal()
def process_users(self, users):
return {}
def on_actions(self):
pass
def on_details(self, *args, **kwargs):
pass

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
""" Autocompletion users for TWBlue. This package contains all needed code to support this feature, including automatic addition of users, management and code to show the autocompletion menu when an user is composing a post. """

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
""" Module to display the user autocompletion menu in post dialogs. """
import output
from . import storage
from . import wx_menu
class autocompletionUsers(object):
def __init__(self, window, session_id):
""" Class constructor. Displays a menu with users matching the specified pattern for autocompletion.
:param window: A wx control where the menu should be displayed. Normally this is going to be the wx.TextCtrl indicating the tweet's text or direct message recipient.
:type window: wx.Dialog
:param session_id: Session ID which calls this class. We will load the users database from this session.
:type session_id: str.
"""
super(autocompletionUsers, self).__init__()
self.window = window
self.db = storage.storage(session_id)
def show_menu(self, mode="mastodon"):
""" displays a menu with possible users matching the specified pattern.
this menu can be displayed in dialogs where an username is expected. For Mastodon's post dialogs, the string should start with an at symbol (@), otherwise it won't match the pattern.
Of course, users must be already loaded in database before attempting this.
If no users are found, an error message will be spoken.
:param mode: this controls how the dialog will behave. Possible values are 'mastodon' and 'free'. In mastodon mode, the matching pattern will be @user (@ is required), while in 'free' mode the matching pattern will be anything written in the text control.
:type mode: str
"""
if mode == "mastodon":
position = self.window.text.GetInsertionPoint()
text = self.window.text.GetValue()
text = text[:position]
try:
pattern = text.split()[-1]
except IndexError:
output.speak(_(u"You have to start writing"))
return
if pattern.startswith("@") == True:
menu = wx_menu.menu(self.window.text, pattern[1:], mode=mode)
users = self.db.get_users(pattern[1:])
if len(users) > 0:
menu.append_options(users)
self.window.PopupMenu(menu, self.window.text.GetPosition())
menu.destroy()
else:
output.speak(_(u"There are no results in your users database"))
else:
output.speak(_(u"Autocompletion only works for users."))
elif mode == "free":
text = self.window.cb.GetValue()
try:
pattern = text.split()[-1]
except IndexError:
output.speak(_(u"You have to start writing"))
return
menu = wx_menu.menu(self.window.cb, pattern, mode=mode)
users = self.db.get_users(pattern)
if len(users) > 0:
menu.append_options(users)
self.window.PopupMenu(menu, self.window.cb.GetPosition())
menu.destroy()
else:
output.speak(_(u"There are no results in your users database"))

View File

@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
""" Management of users in the local database for autocompletion. """
import time
import widgetUtils
from wxUI import commonMessageDialogs
from . import storage, wx_manage
from .mastodon import scan as mastodon
class autocompletionManage(object):
def __init__(self, session):
""" class constructor. Manages everything related to user autocompletion.
:param session: Sessiom where the autocompletion management has been requested.
:type session: sessions.base.Session.
"""
super(autocompletionManage, self).__init__()
self.session = session
# Instantiate database so we can perform modifications on it.
self.database = storage.storage(self.session.session_id)
def show_settings(self):
""" display user management dialog and connect events associated to it. """
self.dialog = wx_manage.autocompletionManageDialog()
self.users = self.database.get_all_users()
self.dialog.put_users(self.users)
widgetUtils.connect_event(self.dialog.add, widgetUtils.BUTTON_PRESSED, self.add_user)
widgetUtils.connect_event(self.dialog.remove, widgetUtils.BUTTON_PRESSED, self.remove_user)
self.dialog.get_response()
def update_list(self):
""" update users list in management dialog. This function is normallhy used after we modify the database in any way, so we can reload all users in the autocompletion user management list. """
item = self.dialog.users.get_selected()
self.dialog.users.clear()
self.users = self.database.get_all_users()
self.dialog.put_users(self.users)
self.dialog.users.select_item(item)
def add_user(self, *args, **kwargs):
""" Add a new username to the autocompletion database. """
usr = self.dialog.get_user()
if usr == False:
return
user_added = False
if self.session.type == "mastodon":
user_added = mastodon.add_user(session=self.session, database=self.database, user=usr)
if user_added == False:
self.dialog.show_invalid_user_error()
return
self.update_list()
def remove_user(self, *args, **kwargs):
""" Remove focused user from the autocompletion database. """
if commonMessageDialogs.delete_user_from_db() == widgetUtils.YES:
item = self.dialog.users.get_selected()
user = self.users[item]
self.database.remove_user(user[0])
self.update_list()

View File

@@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
""" Scanning code for autocompletion feature on TWBlue. This module can retrieve user objects from the selected Mastodon account automatically. """
import time
import wx
import widgetUtils
import output
from pubsub import pub
from . import wx_scan
from extra.autocompletionUsers import manage, storage
class autocompletionScan(object):
def __init__(self, config, buffer, window):
""" Class constructor. This class will take care of scanning the selected Mastodon account to populate the database with users automatically upon request.
:param config: Config for the session that will be scanned in search for users.
:type config: dict
:param buffer: home buffer for the focused session.
:type buffer: controller.buffers.mastodon.base.baseBuffer
:param window: Main Window of TWBlue.
:type window:wx.Frame
"""
super(autocompletionScan, self).__init__()
self.config = config
self.buffer = buffer
self.window = window
def show_dialog(self):
""" displays a dialog to confirm which buffers should be scanned (followers or following users). """
self.dialog = wx_scan.autocompletionScanDialog()
self.dialog.set("friends", self.config["mysc"]["save_friends_in_autocompletion_db"])
self.dialog.set("followers", self.config["mysc"]["save_followers_in_autocompletion_db"])
if self.dialog.get_response() == widgetUtils.OK:
confirmation = wx_scan.confirm()
return confirmation
def prepare_progress_dialog(self):
self.progress_dialog = wx_scan.autocompletionScanProgressDialog()
# connect method to update progress dialog
pub.subscribe(self.on_update_progress, "on-update-progress")
self.progress_dialog.Show()
def on_update_progress(self):
wx.CallAfter(self.progress_dialog.progress_bar.Pulse)
def scan(self):
""" Attempts to add all users selected by current user to the autocomplete database. """
self.config["mysc"]["save_friends_in_autocompletion_db"] = self.dialog.get("friends")
self.config["mysc"]["save_followers_in_autocompletion_db"] = self.dialog.get("followers")
output.speak(_("Updating database... You can close this window now. A message will tell you when the process finishes."))
database = storage.storage(self.buffer.session.session_id)
percent = 0
users = []
if self.dialog.get("friends") == True:
first_page = self.buffer.session.api.account_following(id=self.buffer.session.db["user_id"], limit=80)
pub.sendMessage("on-update-progress")
if first_page != None:
for user in first_page:
users.append(user)
next_page = first_page
while next_page != None:
next_page = self.buffer.session.api.fetch_next(next_page)
pub.sendMessage("on-update-progress")
if next_page == None:
break
for user in next_page:
users.append(user)
# same step, but for followers.
if self.dialog.get("followers") == True:
first_page = self.buffer.session.api.account_followers(id=self.buffer.session.db["user_id"], limit=80)
pub.sendMessage("on-update-progress")
if first_page != None:
for user in first_page:
if user not in users:
users.append(user)
next_page = first_page
while next_page != None:
next_page = self.buffer.session.api.fetch_next(next_page)
pub.sendMessage("on-update-progress")
if next_page == None:
break
for user in next_page:
if user not in users:
users.append(user)
# except TweepyException:
# wx.CallAfter(wx_scan.show_error)
# return self.done()
for user in users:
name = user.display_name if user.display_name != None and user.display_name != "" else user.username
database.set_user(user.acct, name, 1)
wx.CallAfter(wx_scan .show_success, len(users))
self.done()
def done(self):
wx.CallAfter(self.progress_dialog.Destroy)
wx.CallAfter(self.dialog.Destroy)
pub.unsubscribe(self.on_update_progress, "on-update-progress")
def add_user(session, database, user):
""" Adds an user to the database. """
user = session.api.account_lookup(user)
if user != None:
name = user.display_name if user.display_name != None and user.display_name != "" else user.username
database.set_user(user.acct, name, 1)

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
import wx
import widgetUtils
import application
class autocompletionScanDialog(widgetUtils.BaseDialog):
def __init__(self):
super(autocompletionScanDialog, self).__init__(parent=None, id=-1, title=_(u"Autocomplete users' settings"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.followers = wx.CheckBox(panel, -1, _("Add followers to database"))
self.friends = wx.CheckBox(panel, -1, _("Add following to database"))
sizer.Add(self.followers, 0, wx.ALL, 5)
sizer.Add(self.friends, 0, wx.ALL, 5)
ok = wx.Button(panel, wx.ID_OK)
cancel = wx.Button(panel, wx.ID_CANCEL)
sizerBtn = wx.BoxSizer(wx.HORIZONTAL)
sizerBtn.Add(ok, 0, wx.ALL, 5)
sizer.Add(cancel, 0, wx.ALL, 5)
sizer.Add(sizerBtn, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
class autocompletionScanProgressDialog(widgetUtils.BaseDialog):
def __init__(self, *args, **kwargs):
super(autocompletionScanProgressDialog, self).__init__(parent=None, id=wx.ID_ANY, title=_("Updating autocompletion database"), *args, **kwargs)
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.progress_bar = wx.Gauge(parent=panel)
sizer.Add(self.progress_bar)
panel.SetSizerAndFit(sizer)
def confirm():
with wx.MessageDialog(None, _("This process will retrieve the users you selected from your Mastodon account, and add them to the user autocomplete database. Please note that if there are many users or you have tried to perform this action less than 15 minutes ago, TWBlue may reach a limit in API calls when trying to load the users into the database. If this happens, we will show you an error, in which case you will have to try this process again in a few minutes. If this process ends with no error, you will be redirected back to the account settings dialog. Do you want to continue?"), _("Attention"), style=wx.ICON_QUESTION|wx.YES_NO) as result:
if result.ShowModal() == wx.ID_YES:
return True
return False
def show_success(users):
with wx.MessageDialog(None, _("TWBlue has imported {} users successfully.").format(users), _("Done")) as dlg:
dlg.ShowModal()
def show_error():
with wx.MessageDialog(None, _("Error adding users from Mastodon. Please try again in about 15 minutes."), _("Error"), style=wx.ICON_ERROR) as dlg:
dlg.ShowModal()

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
import os, sqlite3, paths
class storage(object):
def __init__(self, session_id):
self.connection = sqlite3.connect(os.path.join(paths.config_path(), "%s/autocompletionUsers.dat" % (session_id)))
self.cursor = self.connection.cursor()
if self.table_exist("users") == False:
self.create_table()
def table_exist(self, table):
ask = self.cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='%s'" % (table))
answer = ask.fetchone()
if answer == None:
return False
else:
return True
def get_all_users(self):
self.cursor.execute("""select * from users""")
return self.cursor.fetchall()
def get_users(self, term):
self.cursor.execute("""SELECT * FROM users WHERE UPPER(user) LIKE :term OR UPPER(name) LIKE :term""", {"term": "%{}%".format(term.upper())})
return self.cursor.fetchall()
def set_user(self, screen_name, user_name, from_a_buffer):
self.cursor.execute("""insert or ignore into users values(?, ?, ?)""", (screen_name, user_name, from_a_buffer))
self.connection.commit()
def remove_user(self, user):
self.cursor.execute("""DELETE FROM users WHERE user = ?""", (user,))
self.connection.commit()
return self.cursor.fetchone()
def remove_by_buffer(self, bufferType):
""" Removes all users saved on a buffer. BufferType is 0 for no buffer, 1 for friends and 2 for followers"""
self.cursor.execute("""DELETE FROM users WHERE from_a_buffer = ?""", (bufferType,))
self.connection.commit()
return self.cursor.fetchone()
def create_table(self):
self.cursor.execute("""
create table users(
user TEXT UNIQUE,
name TEXT,
from_a_buffer INTEGER
)""")
def __del__(self):
self.cursor.close()
self.connection.close()

View File

@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
import wx
import widgetUtils
from multiplatform_widgets import widgets
import application
class autocompletionManageDialog(widgetUtils.BaseDialog):
def __init__(self):
super(autocompletionManageDialog, self).__init__(parent=None, id=-1, title=_(u"Manage Autocompletion database"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
label = wx.StaticText(panel, -1, _(u"Editing {0} users database").format(application.name,))
self.users = widgets.list(panel, _(u"Username"), _(u"Name"), style=wx.LC_REPORT)
sizer.Add(label, 0, wx.ALL, 5)
sizer.Add(self.users.list, 0, wx.ALL, 5)
self.add = wx.Button(panel, -1, _(u"Add user"))
self.remove = wx.Button(panel, -1, _(u"Remove user"))
optionsBox = wx.BoxSizer(wx.HORIZONTAL)
optionsBox.Add(self.add, 0, wx.ALL, 5)
optionsBox.Add(self.remove, 0, wx.ALL, 5)
sizer.Add(optionsBox, 0, wx.ALL, 5)
ok = wx.Button(panel, wx.ID_OK)
cancel = wx.Button(panel, wx.ID_CANCEL)
sizerBtn = wx.BoxSizer(wx.HORIZONTAL)
sizerBtn.Add(ok, 0, wx.ALL, 5)
sizer.Add(cancel, 0, wx.ALL, 5)
sizer.Add(sizerBtn, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def put_users(self, users):
for i in users:
j = [i[0], i[1]]
self.users.insert_item(False, *j)
def get_user(self):
usr = False
userDlg = wx.TextEntryDialog(None, _(u"Twitter username"), _(u"Add user to database"))
if userDlg.ShowModal() == wx.ID_OK:
usr = userDlg.GetValue()
return usr
def show_invalid_user_error(self):
wx.MessageDialog(None, _(u"The user does not exist"), _(u"Error!"), wx.ICON_ERROR).ShowModal()

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
import wx
class menu(wx.Menu):
def __init__(self, window, pattern, mode):
super(menu, self).__init__()
self.window = window
self.pattern = pattern
self.mode = mode
def append_options(self, options):
for i in options:
item = wx.MenuItem(self, wx.ID_ANY, "%s (@%s)" % (i[1], i[0]))
self.Append(item)
self.Bind(wx.EVT_MENU, lambda evt, temp=i[0]: self.select_text(evt, temp), item)
def select_text(self, ev, text):
if self.mode == "mastodon":
self.window.ChangeValue(self.window.GetValue().replace("@"+self.pattern, "@"+text+" "))
elif self.mode == "free":
self.window.SetValue(self.window.GetValue().replace(self.pattern, text))
self.window.SetInsertionPointEnd()
def destroy(self):
self.Destroy()

View File

@@ -1,5 +1,2 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import from .translator import TranslatorController
from __future__ import unicode_literals
from . import translator
from . import wx_ui as gui

View File

@@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
import config
from deepl import Translator
def translate(text: str, target_language: str) -> str:
key = config.app["translator"]["deepl_api_key"]
t = Translator(key)
return t.translate_text(text, target_lang=target_language).text
def languages():
key = config.app["translator"]["deepl_api_key"]
t = Translator(key)
langs = t.get_target_languages()
return langs

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
""" Modified Libretranslatepy module which adds an user agent for making requests against more instances. """
import json
from typing import Any, Dict
from urllib import request, parse
from libretranslatepy import LibreTranslateAPI
class CustomLibreTranslateAPI(LibreTranslateAPI):
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
def _create_request(self, url: str, method: str, data: Dict[str, str]) -> request.Request:
url_params = parse.urlencode(data)
req = request.Request(url, method=method, data=url_params.encode())
req.add_header("User-Agent", self.USER_AGENT)
return req
def translate(self, q: str, source: str = "en", target: str = "es", timeout: int | None = None) -> Any:
url = self.url + "translate"
params: Dict[str, str] = {"q": q, "source": source, "target": target}
if self.api_key is not None:
params["api_key"] = self.api_key
req = self._create_request(url=url, method="POST", data=params)
response = request.urlopen(req, timeout=timeout)
response_str = response.read().decode()
return json.loads(response_str)["translatedText"]
def detect(self, q: str, timeout: int | None = None) -> Any:
url = self.url + "detect"
params: Dict[str, str] = {"q": q}
if self.api_key is not None:
params["api_key"] = self.api_key
req = self._create_request(url=url, method="POST", data=params)
response = request.urlopen(req, timeout=timeout)
response_str = response.read().decode()
return json.loads(response_str)
def languages(self, timeout: int | None = None) -> Any:
url = self.url + "languages"
params: Dict[str, str] = dict()
if self.api_key is not None:
params["api_key"] = self.api_key
req = self._create_request(url=url, method="GET", data=params)
response = request.urlopen(req, timeout=timeout)
response_str = response.read().decode()
return json.loads(response_str)

View File

@@ -1,116 +1,58 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging import logging
from googletrans import Translator, LANGUAGES import threading
import wx
import config
from pubsub import pub
from . engines import libre_translate, deep_l
from .wx_ui import translateDialog
log = logging.getLogger("extras.translator") log = logging.getLogger("extras.translator")
# create a single translator instance class TranslatorController(object):
# see https://github.com/ssut/py-googletrans/issues/234 def __init__(self, text):
t = None super(TranslatorController, self).__init__()
self.text = text
self.languages = []
self.response = False
self.dialog = translateDialog()
pub.subscribe(self.on_engine_changed, "translator.engine_changed")
if config.app["translator"]["engine"] == "LibreTranslate":
self.dialog.engine_select.SetSelection(0)
elif config.app["translator"]["engine"] == "DeepL":
self.dialog.engine_select.SetSelection(1)
threading.Thread(target=self.load_languages).start()
if self.dialog.ShowModal() == wx.ID_OK:
self.response = True
for k in self.language_dict:
if self.language_dict[k] == self.dialog.dest_lang.GetStringSelection():
self.target_language= k
pub.unsubscribe(self.on_engine_changed, "translator.engine_changed")
def translate(text="", target="en"): def load_languages(self):
global t self.language_dict = self.get_languages()
log.debug("Received translation request for language %s, text=%s" % (target, text)) self.languages = [self.language_dict[k] for k in self.language_dict]
if t == None: self.dialog.set_languages(self.languages)
t = Translator()
vars = dict(text=text, dest=target)
return t.translate(**vars).text
supported_langs = None def on_engine_changed(self, engine):
config.app["translator"]["engine"] = engine
config.app.write()
threading.Thread(target=self.load_languages).start()
languages = { def translate(self):
"af": _(u"Afrikaans"), log.debug("Received translation request for language %s, text=%s" % (self.target_language, self.text))
"sq": _(u"Albanian"), if config.app["translator"].get("engine") == "LibreTranslate":
"am": _(u"Amharic"), translator = libre_translate.CustomLibreTranslateAPI(config.app["translator"]["lt_api_url"], config.app["translator"]["lt_api_key"])
"ar": _(u"Arabic"), vars = dict(q=self.text, target=self.target_language)
"hy": _(u"Armenian"), return translator.translate(**vars)
"az": _(u"Azerbaijani"), elif config.app["translator"]["engine"] == "DeepL" and config.app["translator"]["deepl_api_key"] != "":
"eu": _(u"Basque"), return deep_l.translate(text=self.text, target_language=self.target_language)
"be": _(u"Belarusian"),
"bn": _(u"Bengali"),
"bh": _(u"Bihari"),
"bg": _(u"Bulgarian"),
"my": _(u"Burmese"),
"ca": _(u"Catalan"),
"chr": _(u"Cherokee"),
"zh": _(u"Chinese"),
"zh-CN": _(u"Chinese_simplified"),
"zh-TW": _(u"Chinese_traditional"),
"hr": _(u"Croatian"),
"cs": _(u"Czech"),
"da": _(u"Danish"),
"dv": _(u"Dhivehi"),
"nl": _(u"Dutch"),
"en": _(u"English"),
"eo": _(u"Esperanto"),
"et": _(u"Estonian"),
"tl": _(u"Filipino"),
"fi": _(u"Finnish"),
"fr": _(u"French"),
"gl": _(u"Galician"),
"ka": _(u"Georgian"),
"de": _(u"German"),
"el": _(u"Greek"),
"gn": _(u"Guarani"),
"gu": _(u"Gujarati"),
"iw": _(u"Hebrew"),
"hi": _(u"Hindi"),
"hu": _(u"Hungarian"),
"is": _(u"Icelandic"),
"id": _(u"Indonesian"),
"iu": _(u"Inuktitut"),
"ga": _(u"Irish"),
"it": _(u"Italian"),
"ja": _(u"Japanese"),
"kn": _(u"Kannada"),
"kk": _(u"Kazakh"),
"km": _(u"Khmer"),
"ko": _(u"Korean"),
"ku": _(u"Kurdish"),
"ky": _(u"Kyrgyz"),
"lo": _(u"Laothian"),
"lv": _(u"Latvian"),
"lt": _(u"Lithuanian"),
"mk": _(u"Macedonian"),
"ms": _(u"Malay"),
"ml": _(u"Malayalam"),
"mt": _(u"Maltese"),
"mr": _(u"Marathi"),
"mn": _(u"Mongolian"),
"ne": _(u"Nepali"),
"no": _(u"Norwegian"),
"or": _(u"Oriya"),
"ps": _(u"Pashto"),
"fa": _(u"Persian"),
"pl": _(u"Polish"),
"pt": _(u"Portuguese"),
"pa": _(u"Punjabi"),
"ro": _(u"Romanian"),
"ru": _(u"Russian"),
"sa": _(u"Sanskrit"),
"sr": _(u"Serbian"),
"sd": _(u"Sindhi"),
"si": _(u"Sinhalese"),
"sk": _(u"Slovak"),
"sl": _(u"Slovenian"),
"es": _(u"Spanish"),
"sw": _(u"Swahili"),
"sv": _(u"Swedish"),
"tg": _(u"Tajik"),
"ta": _(u"Tamil"),
"tl": _(u"Tagalog"),
"te": _(u"Telugu"),
"th": _(u"Thai"),
"bo": _(u"Tibetan"),
"tr": _(u"Turkish"),
"uk": _(u"Ukrainian"),
"ur": _(u"Urdu"),
"uz": _(u"Uzbek"),
"ug": _(u"Uighur"),
"vi": _(u"Vietnamese"),
"cy": _(u"Welsh"),
"yi": _(u"Yiddish")
}
def available_languages(): def get_languages(self):
return dict(sorted(languages.items(), key=lambda x: x[1])) languages = {}
if config.app["translator"].get("engine") == "LibreTranslate":
translator = libre_translate.CustomLibreTranslateAPI(config.app["translator"]["lt_api_url"], config.app["translator"]["lt_api_key"])
languages = {l.get("code"): l.get("name") for l in translator.languages()}
elif config.app["translator"]["engine"] == "DeepL" and config.app["translator"]["deepl_api_key"] != "":
languages = {language.code: language.name for language in deep_l.languages()}
return dict(sorted(languages.items(), key=lambda x: x[1]))

View File

@@ -16,23 +16,26 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
############################################################ ############################################################
from . import translator
import wx import wx
from pubsub import pub
from wxUI.dialogs import baseDialog from wxUI.dialogs import baseDialog
class translateDialog(baseDialog.BaseWXDialog): class translateDialog(baseDialog.BaseWXDialog):
def __init__(self): def __init__(self):
languages = []
language_dict = translator.available_languages()
for k in language_dict:
languages.append(language_dict[k])
super(translateDialog, self).__init__(None, -1, title=_(u"Translate message")) super(translateDialog, self).__init__(None, -1, title=_(u"Translate message"))
self.engines = ["LibreTranslate", "DeepL"]
panel = wx.Panel(self) panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
staticEngine = wx.StaticText(panel, -1, _(u"Translation engine"))
self.engine_select = wx.ComboBox(panel, -1, choices=self.engines, style=wx.CB_READONLY)
self.engine_select.Bind(wx.EVT_COMBOBOX, lambda event: pub.sendMessage("translator.engine_changed", engine=self.engine_select.GetValue()))
staticDest = wx.StaticText(panel, -1, _(u"Target language")) staticDest = wx.StaticText(panel, -1, _(u"Target language"))
self.dest_lang = wx.ComboBox(panel, -1, choices=languages, style = wx.CB_READONLY) self.dest_lang = wx.ComboBox(panel, -1, style = wx.CB_READONLY)
self.dest_lang.SetFocus() self.dest_lang.SetFocus()
self.dest_lang.SetSelection(0) self.dest_lang.SetSelection(0)
engineSizer = wx.BoxSizer(wx.HORIZONTAL)
engineSizer.Add(staticEngine)
engineSizer.Add(self.engine_select)
listSizer = wx.BoxSizer(wx.HORIZONTAL) listSizer = wx.BoxSizer(wx.HORIZONTAL)
listSizer.Add(staticDest) listSizer.Add(staticDest)
listSizer.Add(self.dest_lang) listSizer.Add(self.dest_lang)
@@ -40,6 +43,14 @@ class translateDialog(baseDialog.BaseWXDialog):
ok.SetDefault() ok.SetDefault()
cancel = wx.Button(panel, wx.ID_CANCEL) cancel = wx.Button(panel, wx.ID_CANCEL)
self.SetEscapeId(wx.ID_CANCEL) self.SetEscapeId(wx.ID_CANCEL)
sizer.Add(engineSizer, 0, wx.EXPAND | wx.ALL, 5)
sizer.Add(listSizer, 0, wx.EXPAND | wx.ALL, 5)
sizer.Add(ok, 0, wx.ALIGN_CENTER | wx.ALL, 5)
sizer.Add(cancel, 0, wx.ALIGN_CENTER | wx.ALL, 5)
panel.SetSizer(sizer)
def set_languages(self, languages):
wx.CallAfter(self.dest_lang.SetItems, languages)
def get(self, control): def get(self, control):
return getattr(self, control).GetSelection() return getattr(self, control).GetSelection()

View File

@@ -35,4 +35,5 @@ accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+win+shift+u") update_buffer = string(default="control+win+shift+u")
ocr_image = string(default="win+alt+o") ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="") add_alias=string(default="")
vote=string(default="alt+win+shift+v")

View File

@@ -54,4 +54,5 @@ accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+win+shift+u") update_buffer = string(default="control+win+shift+u")
ocr_image = string(default="win+alt+o") ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="") add_alias=string(default="")
vote=string(default="alt+win+shift+v")

View File

@@ -57,3 +57,4 @@ ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="") add_alias=string(default="")
find = string(default="control+win+{") find = string(default="control+win+{")
vote=string(default="alt+win+shift+v")

View File

@@ -57,3 +57,4 @@ ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="") add_alias=string(default="")
find = string(default="control+win+{") find = string(default="control+win+{")
vote=string(default="alt+win+shift+v")

View File

@@ -57,4 +57,5 @@ accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+win+shift+u") update_buffer = string(default="control+win+shift+u")
ocr_image = string(default="win+alt+o") ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="") add_alias=string(default="")
vote=string(default="alt+win+shift+v")

View File

@@ -15,7 +15,7 @@ actions = {
"remove_from_favourites": _(u"Remove post from favorites"), "remove_from_favourites": _(u"Remove post from favorites"),
"toggle_like": _("Add/remove post from favorites"), "toggle_like": _("Add/remove post from favorites"),
"follow": _(u"Open the user actions dialogue"), "follow": _(u"Open the user actions dialogue"),
# "user_details": _(u"See user details"), "user_details": _(u"See user details"),
"view_item": _(u"Show post"), "view_item": _(u"Show post"),
"exit": _(u"Quit"), "exit": _(u"Quit"),
"open_timeline": _(u"Open user timeline"), "open_timeline": _(u"Open user timeline"),

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import httpcore
httpcore.SyncHTTPTransport = httpcore.AsyncHTTPProxy
import sys import sys
import os import os
import platform import platform
@@ -104,7 +102,7 @@ def proxy_setup():
def donation(): def donation():
dlg = commonMessageDialogs.donation() dlg = commonMessageDialogs.donation()
if dlg == widgetUtils.YES: if dlg == widgetUtils.YES:
webbrowser.open_new_tab(_("https://twblue.es/donate")) webbrowser.open_new_tab(_("https://twblue.mcvsoftware.com/donate"))
config.app["app-settings"]["donation_dialog_displayed"] = True config.app["app-settings"]["donation_dialog_displayed"] = True
def check_pid(): def check_pid():

Some files were not shown because too many files have changed in this diff Show More