140 Commits

Author SHA1 Message Date
ebbaeaf76a update big NGC 2022-09-27 13:37:52 +00:00
62c6dbfb34 build.sh and docs update 2018-09-29 16:50:17 +03:00
cf4cfa979c Fixed crash on reconnection, file transfers fixes 2018-09-24 22:06:30 +03:00
ae4eae92ae minor bug fixes 2018-09-23 13:04:27 +03:00
ad3bbb5e45 profile settings screen converted 2018-09-14 20:38:18 +03:00
02b2d07b6d profile backup - initial infrastructure 2018-09-14 19:10:35 +03:00
9f7de204d4 fixes for smileys selection and file transfers 2018-09-14 18:35:07 +03:00
9a58082496 bug fixes 2018-09-13 23:04:22 +03:00
5e788a543d fixed messages caching and drag n drop 2018-08-30 00:36:07 +03:00
a4ceeccfd8 various bug fixes 2018-08-27 00:51:40 +03:00
ee994973db toxav kill fixed 2018-08-25 14:45:58 +03:00
6e07d3e3d4 contacts menu history fixes 2018-08-25 14:23:59 +03:00
531fa81bba fixed bugs with plugin reloading and toxav_kill 2018-08-25 13:31:43 +03:00
0f9aa4f515 refactoring and is_muted fix 2018-08-23 23:51:05 +03:00
ce19efe340 ban fixes 2018-08-23 16:02:29 +03:00
c0a34d3e14 groups - nicks auto complete 2018-08-22 13:36:22 +03:00
0ee8a0ec21 gc settings screen added 2018-08-22 12:08:00 +03:00
85ea9ab6e8 fixes for messaging, contacts filtering etc 2018-08-11 00:30:33 +03:00
4ecf666b2f various fixes 2018-08-09 23:30:05 +03:00
318c9c942d ngc bug fixes 2018-08-07 00:41:27 +03:00
1a0bd9deee dockerfile for linux builds 2018-08-06 00:52:01 +03:00
741adcdf18 bans - untested 2018-08-05 21:05:18 +03:00
37541db07d bans - wrapper 2018-08-05 16:33:51 +03:00
33052f8a98 group moderation screen and all callbacks 2018-08-05 12:34:11 +03:00
8f9b573253 settings.py refactoring 2018-08-05 11:35:24 +03:00
9f702339dd dockerfile for building - initial version 2018-08-05 11:19:07 +03:00
bc9dfd1bc4 interface settings screen converted. 2018-08-04 17:46:02 +03:00
5f56d630ce messenger refactoring 2018-08-03 21:07:18 +03:00
25de4fa2ef wrapper update and minor fixes 2018-08-03 18:04:28 +03:00
c7a83055b1 various fixes 2018-08-01 00:47:57 +03:00
dd323e3cbb minor fixes for group invites screen 2018-07-29 21:26:53 +03:00
c66dcb0ca2 contact selection fixes 2018-07-29 16:11:34 +03:00
250551e752 fixed group numbers restoring. contact selection fixed 2018-07-29 13:36:16 +03:00
f38df24947 filtering fixed 2018-07-29 11:16:03 +03:00
10a77960dc friends column converted to .ui. added gc invites button 2018-07-29 00:06:33 +03:00
603dfd40b5 tray notification on gc invite 2018-07-28 18:16:26 +03:00
184ba55aed group invites page 2018-07-28 13:14:16 +03:00
1728a45cf3 peers screen refactoring 2018-07-26 21:27:20 +03:00
3272617403 join group with different credentials 2018-07-26 00:38:25 +03:00
850c3b1ca3 self peer screen added 2018-07-24 23:40:29 +03:00
27d24ecaf4 group - privacy state added 2018-07-23 00:50:53 +03:00
20f36e06ad roles support - callbacks, peer screen 2018-07-23 00:35:52 +03:00
5e1f060fac private messages - types 2018-07-22 19:39:42 +03:00
eba7e0c0dc group peer context menu 2018-07-22 14:08:47 +03:00
5521b768bc private messages support 2018-07-22 12:59:52 +03:00
e15620c3ad str to bytes convert moved to wrapper 2018-07-21 20:43:16 +03:00
7e08be71e0 group topic support 2018-07-21 20:25:10 +03:00
820b5a0253 reconnection - clear peers list 2018-07-21 17:16:01 +03:00
6538cedcf2 reconnect/disconnect functionality 2018-07-19 00:00:01 +03:00
329ab23f89 api changes - new methods and renaming 2018-07-17 20:52:42 +03:00
9c742d10de plugins refactoring 2018-07-16 21:29:15 +03:00
2a97beb5af minor bug fixes 2018-07-15 17:04:51 +03:00
7aac248bf9 identicons fixes. sending messages button fixed 2018-07-10 00:41:08 +03:00
d09609a5e5 fixes after revert. identicons update 2018-07-05 00:26:05 +03:00
e8193afedf fixes after revert 2018-07-02 22:53:07 +03:00
bc48537209 Revert "avatars support fixed"
This reverts commit 47c115e699.
2018-07-02 22:50:46 +03:00
0adb9c1e52 fixed wrong avatars directory path 2018-06-30 20:02:44 +03:00
595c35a6b8 network settings screen converted 2018-06-30 19:54:08 +03:00
a0cae14727 travis.yml updated 2018-06-30 18:57:41 +03:00
04f0aef3df Threads fixed 2018-06-30 18:49:25 +03:00
8411f08348 bug with avatars fixed. bug with contacts statuses during reconnection was fixed 2018-06-30 15:23:04 +03:00
47c115e699 avatars support fixed 2018-06-30 14:56:41 +03:00
b2ecf5314e gc invite - support of gc name added 2018-06-23 00:20:13 +03:00
8809ef1f6e minor ui fixes 2018-06-05 23:58:14 +03:00
41de315496 Filtration fixed 2018-06-03 21:18:22 +03:00
56731be79d minor ui issues fixed 2018-06-02 20:20:57 +03:00
1c80b4fd7d process group creation fail 2018-05-26 16:27:53 +03:00
fa3529f5f2 fixed broken ft callback 2018-05-26 16:27:53 +03:00
74a5f95a56 rebased ngc - initial commit 2018-05-26 16:27:53 +03:00
03e2fa4cb8 add friend screen coverted 2018-05-25 11:48:47 +03:00
423bda93c6 video settings screen converted 2018-05-25 11:26:22 +03:00
238f7e367a update settings screen converted 2018-05-25 00:16:21 +03:00
13b2d17786 notifications and audio settings views converted 2018-05-24 23:58:39 +03:00
370716015b groups numbers update 2018-05-24 21:55:44 +03:00
439ce30e6e reconnection fixes 2018-05-24 21:43:34 +03:00
486c13a3d3 Login screen converted, create profile screen fixed 2018-05-24 21:22:12 +03:00
c97fb6b467 minor stickers and smileys window fixes 2018-05-24 15:20:21 +03:00
eb9ab56c6e fix for deleting last contact in list 2018-05-24 15:01:17 +03:00
43302b0130 test import fixed 2018-05-23 21:32:14 +03:00
0a9939f33b Tests cleanup 2018-05-23 21:23:51 +03:00
c6b67452ed peers - more callback and peers list refactoring 2018-05-20 17:22:44 +03:00
b8fa8df41a various fixes - peers list, resize event, tox instance recreation 2018-05-20 15:57:08 +03:00
02af0f7671 broken peers list 2018-05-20 13:33:56 +03:00
dcc3a3dcfa group peers list - base commit 2018-05-19 23:59:39 +03:00
f67de1ba91 minor tray fixes 2018-05-19 23:20:37 +03:00
77bdabb993 minor fixes - history 2018-05-19 21:25:57 +03:00
206c5c4905 unsent files fixes - part 1 2018-05-19 21:04:40 +03:00
6495aa9920 name changing fixes 2018-05-19 20:07:42 +03:00
b591ac13ba utf-8 decoding moved from contacts 2018-05-19 19:38:54 +03:00
a935d602f8 minimal working ngc version - sending messages, invites, char creation 2018-05-19 19:27:27 +03:00
ef4a1b18fd ngc - invites, gc menu, callbacks etc 2018-05-19 18:08:25 +03:00
eed31bf61b wrapper update - ngc 2018-05-19 16:07:16 +03:00
dfe7601dc1 groups - service, chat, callbacks 2018-05-19 16:00:28 +03:00
acf75a6818 groups initial commit 2018-05-19 00:07:49 +03:00
88786b0398 setup.py fixes 2018-05-18 21:07:59 +03:00
a575312167 messages refactoring and fixes, calls fixes 2018-05-18 19:40:34 +03:00
42049d6a44 messaing fixes - receipts, faux offline messages 2018-05-18 18:40:41 +03:00
ec5bcbddec calls manager fixes 2018-05-18 13:23:48 +03:00
e8a0a3f5be file transfers fixes - part 8 (unsent files minor fixes) 2018-05-18 12:54:00 +03:00
bde69bd417 file transfers fixes - part 7 2018-05-18 12:26:02 +03:00
1b8241eee9 profile minor fixes 2018-05-18 00:06:14 +03:00
a3103f6fb9 file transfers fixes - part 6 2018-05-17 23:31:48 +03:00
9365ca2913 file transfers fixes - part 5 2018-05-17 21:45:35 +03:00
bfa91df927 fixed deps in main_screen.py 2018-05-17 19:28:44 +03:00
0b1e899931 various fixes - file transfers, friend exit callback 2018-05-17 19:03:58 +03:00
bcefe9bc79 friend menu fixes - correct ordering, submenus fixes 2018-05-17 16:59:46 +03:00
9294c3e779 file transfers fixes - part 4 2018-05-17 15:20:47 +03:00
a96f6d2928 file transfers fixes - part 3 2018-05-17 00:02:22 +03:00
c0a143c817 identicons basic support 2018-05-16 20:25:21 +03:00
f3aa0aeda3 file transfers fixes - part 2 2018-05-16 19:31:08 +03:00
bfd2a92dde initial fixes for file transfers - messages, widgets 2018-05-16 19:04:02 +03:00
7209dfae72 minor refactoring and todo's for file transfers 2018-05-16 14:47:14 +03:00
2883ce5c4c messaging - db and saving fixes 2018-05-16 14:10:24 +03:00
eef02a1173 history fixes - db cleanup 2018-05-15 22:51:42 +03:00
f1c63bb4e8 history loading after friend switching. refactoring 2018-05-15 17:00:12 +03:00
98dbe6a493 widgets fixes 2018-05-15 13:40:59 +03:00
e21a9355e7 minor fixes - context menu 2018-05-11 22:02:03 +03:00
c6192de9dd new context menu generation - builder, generators 2018-05-11 21:27:46 +03:00
7898363dcb contact context menu fixes 2018-05-11 00:35:56 +03:00
25dbb85ef0 various fixes. profile settings and add account fixes 2018-05-10 23:54:51 +03:00
729bd84d2b utils refactoring 2018-05-10 20:47:34 +03:00
ae903cf405 statuses fixed. events added. 2018-05-05 00:09:33 +03:00
c8443b56dd messages - minimal working version 2018-05-04 00:17:48 +03:00
ad351030d9 messenger fixes, refactoring (history) 2018-05-01 21:40:29 +03:00
6ebafbda44 messenger created. callbacks fixes. contacts refactoring 2018-05-01 16:39:09 +03:00
ddf6cd8328 contact list loading 2018-04-30 22:28:33 +03:00
c81d9a3696 images path fixes, all screens loading fixed 2018-04-30 20:46:44 +03:00
5ebfa702ec screens creation improvements. bug fixes 2018-04-30 00:33:25 +03:00
e9272eee2a deps creation - improvements. db and history fixes. widgets creation - factory 2018-04-29 00:52:42 +03:00
a9d2d3d809 more refacrtoring - contact provider, deps creation 2018-04-26 23:54:39 +03:00
68328d9846 Merge branch 'develop' into next_gen 2018-04-19 20:05:14 +03:00
dec4990d32 contacts minor refactoring 2018-04-18 23:55:51 +03:00
0ba1aadf70 app.py and main.py refactoring and fixes 2018-04-17 21:08:22 +03:00
8a2665ed4d refactoring - app.py, files moved to different folders 2018-04-17 15:14:05 +03:00
91d3f885c0 create profile screen, main screen opens now 2018-04-16 23:35:55 +03:00
85467e1885 refactoring - login screen, incorrect refs 2018-04-16 00:11:51 +03:00
1bead7d55d history improvements 2018-03-12 00:32:46 +03:00
20bb694c7e refactoring - correct namespaces, logic from profile.py moved to different files, calbacks partially fixed 2018-03-10 18:42:53 +03:00
593e25efe5 more project structure updates 2018-02-14 20:36:59 +03:00
2de4eea357 initial commit - rewriting. ngc wrapper added, project structure updated 2018-01-26 23:21:46 +03:00
1327 changed files with 5414 additions and 16464 deletions

View File

@ -1,43 +0,0 @@
name: CI
on:
- push
- pull_request
jobs:
build:
strategy:
matrix:
python-version:
- "3.7"
- "3.8"
- "3.9"
- "3.10"
name: Python ${{ matrix.python-version }}
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install bandit flake8 pylint
- name: Lint with flake8
run: make flake8
# - name: Lint with pylint
# run: make pylint
- name: Lint with bandit
run: make bandit

3
.gitignore vendored
View File

@ -25,5 +25,4 @@ Toxygen.egg-info
*.tox
.cache
*.db
*~
Makefile

0
.rsync
View File

View File

@ -2,7 +2,13 @@
Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pure Python3.
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md)
[![Release](https://img.shields.io/github/release/toxygen-project/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/releases/latest)
[![Stars](https://img.shields.io/github/stars/toxygen-project/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/stargazers)
[![Open issues](https://img.shields.io/github/issues/toxygen-project/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/issues)
[![License](https://img.shields.io/badge/license-GPLv3-blue.svg?style=flat)](https://raw.githubusercontent.com/toxygen-project/toxygen/master/LICENSE.md)
[![Build Status](https://travis-ci.org/toxygen-project/toxygen.svg?branch=master)](https://travis-ci.org/toxygen-project/toxygen)
### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md) - [Updater](https://github.com/toxygen-project/toxygen_updater)
### Supported OS: Linux and Windows
@ -37,47 +43,22 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu
- Changing nospam
- File resuming
- Read receipts
- NGC groups
### Downloads
[Releases](https://github.com/toxygen-project/toxygen/releases)
[Download last stable version](https://github.com/toxygen-project/toxygen/archive/master.zip)
[Download develop version](https://github.com/toxygen-project/toxygen/archive/develop.zip)
### Screenshots
*Toxygen on Ubuntu and Windows*
![Ubuntu](/docs/ubuntu.png)
![Windows](/docs/windows.png)
## Forked
### Docs
[Check /docs/ for more info](/docs/)
This hard-forked from the dead https://github.com/toxygen-project/toxygen
```next_gen``` branch.
https://git.plastiras.org/emdee/toxygen_wrapper needs packaging
is making a dependency. Just download it and copy the two directories
```wrapper``` and ```wrapper_tests``` into ```toxygen/toxygen```.
Also visit [pythonhosted.org/Toxygen/](http://pythonhosted.org/Toxygen/)
See ToDo.md to the current ToDo list.
You can have a [weechat](https://github.com/weechat/qweechat)
console so that you can have IRC and jabber in a window as well as Tox.
There's a copy of qweechat in ```thirdparty/qweechat``` backported to
PyQt5 and integrated into toxygen. Follow the normal instructions for
adding a ```relay``` to [weechat](https://github.com/weechat/weechat)
```
/relay add ipv4.ssl.weechat 9001
/relay start ipv4.ssl.weechat
```
or
```
/relay add weechat 9000
/relay start weechat
```
and use the Plugins/Weechat Console to start weechat under Toxygen.
Then use th File/Connect menu item of the console to connect to weechat.
Weechat has a Jabber plugin to enable XMPP:
```
/python load jabber.el
/help jabber
```
so you can have Tox, IRC and XMPP in the same application!
Work on Tox on this project is suspended until the
[MultiDevice](https://git.plastiras.org/emdee/tox_profile/wiki/MultiDevice-Announcements-POC) problem is solved. Fork me!
[Wiki](https://wiki.tox.chat/clients/toxygen)

52
ToDo.md
View File

@ -1,52 +0,0 @@
# Toxygen ToDo List
## Bugs
1. There is an agravating bug where new messages are not put in the
current window, and a messages waiting indicator appears. You have
to focus out of the window and then back in the window.
## Fix history
The code is in there but it's not working.
## Fix Audio
The code is in there but it's not working. It looks like audio input
is working but not output. The code is all in there; I may have broken
it trying to wire up the ability to set the audio device from the
command line.
## Fix Video
The code is in there but it's not working. I may have broken it
trying to wire up the ability to set the video device from the command
line.
## Groups
1. peer_id There has been a change of API on a field named
```group.peer_id``` The code is broken in places because I have not
seen the path to change from the old API ro the new one.
## Plugin system
1. Needs better documentation and checking.
2. There's something broken in the way some of them plug into Qt menus.
3. Should the plugins be in toxygen or a separate repo?
4. There needs to be a uniform way for plugins to wire into callbacks.
## check toxygen_wrapper
1. I've broken out toxygen_wrapper to be standalone,
https://git.plastiras.org/emdee/toxygen_wrapper but the tox.py
needs each call double checking.
2. https://git.plastiras.org/emdee/toxygen_wrapper needs packaging
and making a dependency.

View File

@ -1,130 +0,0 @@
0
TRAC> network.c#1748:net_connect connecting socket 58 to 127.0.0.1:9050
TRAC> Messenger.c#2709:do_messenger Friend num in DHT 2 != friend num in msger 14
TRAC> Messenger.c#2723:do_messenger F[--: 0] D3385007C28852C5398393E3338E6AABE5F86EF249BF724E7404233207D4D927
TRAC> Messenger.c#2723:do_messenger F[--: 1] 98984E104B8A97CC43AF03A27BE159AC1F4CF35FADCC03D6CD5F8D67B5942A56
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 185.87.49.189:3389 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 185.87.49.189:3389 (0: OK) | 010001b95731bd0d...3d
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 37.221.66.161:443 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 37.221.66.161:443 (0: OK) | 01000125dd42a101...bb
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 172.93.52.70:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 139.162.110.188:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 37.59.63.150:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 130.133.110.14:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 37.97.185.116:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 85.143.221.42:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 104.244.74.69:38445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 49.12.229.145:3389 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 168.119.209.10:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 81.169.136.229:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 91.219.59.156:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 46.101.197.175:3389 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 198.199.98.108:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 130.133.110.14:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 49.12.229.145:3389 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 188.225.9.167:33445 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 5.19.249.240:38296 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 3= 94.156.35.247:3389 (0: OK) | 0000000000000000...00
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 2 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 172.93.52.70:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 172.93.52.70:33445 (0: OK) | 010001ac5d344682...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 139.162.110.188:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 139.162.110.188:33445 (0: OK) | 0100018ba26ebc82...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 37.59.63.150:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 37.59.63.150:33445 (0: OK) | 010001253b3f9682...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 130.133.110.14:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 130.133.110.14:33445 (0: OK) | 01000182856e0e82...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 37.97.185.116:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 37.97.185.116:33445 (0: OK) | 0100012561b97482...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 85.143.221.42:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 85.143.221.42:33445 (0: OK) | 010001558fdd2a82...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 104.244.74.69:38445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 104.244.74.69:38445 (0: OK) | 01000168f44a4596...2d
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 49.12.229.145:3389 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 49.12.229.145:3389 (0: OK) | 010001310ce5910d...3d
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 168.119.209.10:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 168.119.209.10:33445 (0: OK) | 010001a877d10a82...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 81.169.136.229:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 81.169.136.229:33445 (0: OK) | 01000151a988e582...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 91.219.59.156:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 91.219.59.156:33445 (0: OK) | 0100015bdb3b9c82...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 46.101.197.175:3389 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 46.101.197.175:3389 (0: OK) | 0100012e65c5af0d...3d
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 198.199.98.108:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 198.199.98.108:33445 (0: OK) | 010001c6c7626c82...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 130.133.110.14:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 130.133.110.14:33445 (0: OK) | 01000182856e0e82...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 49.12.229.145:3389 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 49.12.229.145:3389 (0: OK) | 010001310ce5910d...3d
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 188.225.9.167:33445 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 188.225.9.167:33445 (0: OK) | 010001bce109a782...a5
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 5.19.249.240:38296 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 5.19.249.240:38296 (0: OK) | 0100010513f9f095...98
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> network.c#789:loglogdata [05 = <unknown> ] =>T 2= 94.156.35.247:3389 (0: OK) | 0000000000000000...00
TRAC> network.c#789:loglogdata [05 = <unknown> ] T=> 10= 94.156.35.247:3389 (0: OK) | 0100015e9c23f70d...3d
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
app.contacts.contacts_manager INFO update_groups_numbers len(groups)={len(groups)}
Thread 76 "ToxIterateThrea" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffedcb6b640 (LWP 2950427)]

View File

@ -1,11 +0,0 @@
ping tox.abilinski.com
ping: socket: Address family not supported by protocol
PING tox.abilinski.com (172.103.226.229) 56(84) bytes of data.
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=1 ttl=48 time=86.6 ms
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=2 ttl=48 time=83.1 ms
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=3 ttl=48 time=82.9 ms
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=4 ttl=48 time=83.4 ms
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=5 ttl=48 time=102 ms
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=6 ttl=48 time=87.4 ms
64 bytes from 172.103.226.229.cable.tpia.cipherkey.com (172.103.226.229): icmp_seq=7 ttl=48 time=84.9 ms
^C

View File

@ -1,6 +0,0 @@
PyQt5
PyAudio
numpy
opencv-python
pydenticon
cv2

View File

@ -12,14 +12,17 @@ version = main.__version__ + '.0'
if system() == 'Windows':
MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python', 'pydenticon', 'cv2']
MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python', 'pydenticon']
else:
MODULES = ['pydenticon']
MODULES.append('PyQt5')
MODULES = []
try:
import pyaudio
except ImportError:
MODULES.append('PyAudio')
try:
import PyQt5
except ImportError:
MODULES.append('PyQt5')
try:
import numpy
except ImportError:
@ -29,13 +32,9 @@ else:
except ImportError:
MODULES.append('opencv-python')
try:
import coloredlogs
import pydenticon
except ImportError:
MODULES.append('coloredlogs')
try:
import pyqtconsole
except ImportError:
MODULES.append('pyqtconsole')
MODULES.append('pydenticon')
def get_packages():
@ -43,8 +42,10 @@ def get_packages():
for root, dirs, files in os.walk(directory):
packages = map(lambda d: 'toxygen.' + d, dirs)
packages = ['toxygen'] + list(packages)
return packages
class InstallScript(install):
"""This class configures Toxygen after installation"""
@ -71,23 +72,22 @@ setup(name='Toxygen',
version=version,
description='Toxygen - Tox client',
long_description='Toxygen is powerful Tox client written in Python3',
url='https://git.plastiras.org/emdee/toxygen/',
keywords='toxygen Tox messenger',
url='https://github.com/toxygen-project/toxygen/',
keywords='toxygen tox messenger',
author='Ingvar',
maintainer='',
maintainer='Ingvar',
license='GPL3',
packages=get_packages(),
install_requires=MODULES,
include_package_data=True,
classifiers=[
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
],
entry_points={
'console_scripts': ['toxygen=toxygen.main:main']
},
cmdclass={
'install': InstallScript
},
zip_safe=False
)
})

View File

@ -1,5 +0,0 @@
#!/bin/sh
ROLE=logging
/var/local/bin/pydev_pylint.bash -E -f text *py [a-nr-z]*/*py >.pylint.err
/var/local/bin/pydev_pylint.bash *py [a-nr-z]*/*py >.pylint.out

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,9 @@ class Call:
is_active = property(get_is_active, set_is_active)
# -----------------------------------------------------------------------------------------------------------------
# Audio
# -----------------------------------------------------------------------------------------------------------------
def get_in_audio(self):
return self._in_audio
@ -35,7 +37,9 @@ class Call:
out_audio = property(get_out_audio, set_out_audio)
# -----------------------------------------------------------------------------------------------------------------
# Video
# -----------------------------------------------------------------------------------------------------------------
def get_in_video(self):
return self._in_video

View File

@ -1,54 +1,21 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import pyaudio
import time
import threading
import itertools
from wrapper.toxav_enums import *
import cv2
import itertools
import numpy as np
from av import screen_sharing
from av.call import Call
import common.tox_save
from utils import ui as util_ui
import wrapper_tests.support_testing as ts
from middleware.threads import invoke_in_main_thread
from main import sleep
from middleware.threads import BaseThread
global LOG
import logging
LOG = logging.getLogger('app.'+__name__)
# callbacks can be called in any thread so were being careful
def LOG_ERROR(l): print('EROR< '+l)
def LOG_WARN(l): print('WARN< '+l)
def LOG_INFO(l):
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 20-1
if bIsVerbose: print('INFO< '+l)
def LOG_DEBUG(l):
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 10-1
if bIsVerbose: print('DBUG< '+l)
def LOG_TRACE(l):
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel < 10-1
pass # print('TRACE+ '+l)
TIMER_TIMEOUT = 30.0
bSTREAM_CALLBACK = False
iFPS = 25
class AV(common.tox_save.ToxAvSave):
def __init__(self, toxav, settings):
super().__init__(toxav)
self._toxav = toxav
self._settings = settings
self._running = True
s = settings
if 'video' not in s:
LOG.warn("AV.__init__ 'video' not in s" )
LOG.debug(f"AV.__init__ {s!r}" )
elif 'device' not in s['video']:
LOG.warn("AV.__init__ 'device' not in s.video" )
LOG.debug(f"AV.__init__ {s['video']!r}" )
self._calls = {} # dict: key - friend number, value - Call instance
@ -58,30 +25,17 @@ class AV(common.tox_save.ToxAvSave):
self._audio_running = False
self._out_stream = None
self._audio_rate = 8000
self._audio_channels = 1
self._audio_duration = 60
self._audio_rate_pa = 48000
self._audio_rate_tox = 48000
self._audio_rate_pa = 48000
self._audio_krate_tox_audio = self._audio_rate_tox // 1000
self._audio_krate_tox_video = 5000
self._audio_sample_count_pa = self._audio_rate_pa * self._audio_channels * self._audio_duration // 1000
self._audio_sample_count_tox = self._audio_rate_tox * self._audio_channels * self._audio_duration // 1000
self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000
self._video = None
self._video_thread = None
self._video_running = None
self._video_running = False
self._video_width = 320
self._video_height = 240
# was iOutput = self._settings._args.audio['output']
iInput = self._settings['audio']['input']
self.lPaSampleratesI = ts.lSdSamplerates(iInput)
iOutput = self._settings['audio']['output']
self.lPaSampleratesO = ts.lSdSamplerates(iOutput)
global oPYA
oPYA = self._audio = pyaudio.PyAudio()
self._video_width = 640
self._video_height = 480
def stop(self):
self._running = False
@ -91,74 +45,33 @@ class AV(common.tox_save.ToxAvSave):
def __contains__(self, friend_number):
return friend_number in self._calls
# -----------------------------------------------------------------------------------------------------------------
# Calls
# -----------------------------------------------------------------------------------------------------------------
def __call__(self, friend_number, audio, video):
"""Call friend with specified number"""
if friend_number in self._calls:
LOG.warn(f"__call__ already has {friend_number}")
return
if self._audio_krate_tox_audio not in ts.lToxSampleratesK:
LOG.warn(f"__call__ {self._audio_krate_tox_audio} not in {ts.lToxSampleratesK}")
try:
self._toxav.call(friend_number,
self._audio_krate_tox_audio if audio else 0,
self._audio_krate_tox_video if video else 0)
except Exception as e:
LOG.warn(f"_toxav.call already has {friend_number}")
return
self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0)
self._calls[friend_number] = Call(audio, video)
threading.Timer(TIMER_TIMEOUT,
lambda: self.finish_not_started_call(friend_number)).start()
threading.Timer(30.0, lambda: self.finish_not_started_call(friend_number)).start()
def accept_call(self, friend_number, audio_enabled, video_enabled):
# obsolete
return self.call_accept_call(friend_number, audio_enabled, video_enabled)
def call_accept_call(self, friend_number, audio_enabled, video_enabled):
LOG.debug(f"call_accept_call from {friend_number} {self._running}" +
f"{audio_enabled} {video_enabled}")
# import pdb; pdb.set_trace() - gets into q Qt exec_ problem
# ts.trepan_handler()
if self._audio_krate_tox_audio not in ts.lToxSampleratesK:
LOG.warn(f"__call__ {self._audio_krate_tox_audio} not in {ts.lToxSampleratesK}")
if self._running:
self._calls[friend_number] = Call(audio_enabled, video_enabled)
# audio_bit_rate: Audio bit rate in Kb/sec. Set this to 0 to disable audio sending.
# video_bit_rate: Video bit rate in Kb/sec. Set this to 0 to disable video sending.
try:
self._toxav.answer(friend_number,
self._audio_krate_tox_audio if audio_enabled else 0,
self._audio_krate_tox_video if video_enabled else 0)
except ArgumentError as e:
LOG.debug(f"AV accept_call error from {friend_number} {self._running}" +
f"{e}")
raise
self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0)
if audio_enabled:
# may raise
self.start_audio_thread()
if video_enabled:
# may raise
self.start_video_thread()
def finish_call(self, friend_number, by_friend=False):
LOG.debug(f"finish_call {friend_number}")
if not by_friend:
self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
if friend_number in self._calls:
del self._calls[friend_number]
try:
# AttributeError: 'int' object has no attribute 'out_audio'
if not len(list(filter(lambda c: c.out_audio, self._calls))):
self.stop_audio_thread()
if not len(list(filter(lambda c: c.out_video, self._calls))):
self.stop_video_thread()
except Exception as e:
LOG.error(f"finish_call FixMe: {e}")
# dunno
if not len(list(filter(lambda c: c.out_audio, self._calls))):
self.stop_audio_thread()
if not len(list(filter(lambda c: c.out_video, self._calls))):
self.stop_video_thread()
def finish_not_started_call(self, friend_number):
@ -171,7 +84,6 @@ class AV(common.tox_save.ToxAvSave):
"""
New call state
"""
LOG.debug(f"toxav_call_state_cb {friend_number}")
call = self._calls[friend_number]
call.is_active = True
@ -187,90 +99,39 @@ class AV(common.tox_save.ToxAvSave):
def is_video_call(self, number):
return number in self and self._calls[number].in_video
# -----------------------------------------------------------------------------------------------------------------
# Threads
# -----------------------------------------------------------------------------------------------------------------
def start_audio_thread(self):
"""
Start audio sending
from a callback
"""
global oPYA
# was iInput = self._settings._args.audio['input']
iInput = self._settings['audio']['input']
if self._audio_thread is not None:
LOG_WARN(f"start_audio_thread device={iInput}")
return
LOG_DEBUG(f"start_audio_thread device={iInput}")
lPaSamplerates = ts.lSdSamplerates(iInput)
if not(len(lPaSamplerates)):
e = f"No supported sample rates for device: audio[input]={iInput!r}"
LOG_ERROR(f"start_audio_thread {e}")
#?? dunno - cancel call?
return
if not self._audio_rate_pa in lPaSamplerates:
LOG_WARN(f"{self._audio_rate_pa} not in {lPaSamplerates!r}")
if False:
self._audio_rate_pa = oPYA.get_device_info_by_index(iInput)['defaultSampleRate']
else:
LOG_WARN(f"Setting audio_rate to: {lPaSamplerates[0]}")
self._audio_rate_pa = lPaSamplerates[0]
try:
LOG_DEBUG( f"start_audio_thread framerate: {self._audio_rate_pa}" \
+f" device: {iInput}"
+f" supported: {lPaSamplerates!r}")
if self._audio_rate_pa not in lPaSamplerates:
LOG_WARN(f"PAudio sampling rate was {self._audio_rate_pa} changed to {lPaSamplerates[0]}")
self._audio_rate_pa = lPaSamplerates[0]
self._audio_running = True
if bSTREAM_CALLBACK:
self._audio_stream = oPYA.open(format=pyaudio.paInt16,
rate=self._audio_rate_pa,
channels=self._audio_channels,
input=True,
input_device_index=iInput,
frames_per_buffer=self._audio_sample_count_pa * 10,
stream_callback=self.send_audio_data)
self._audio_running = True
self._audio_stream.start_stream()
while self._audio_stream.is_active():
sleep(0.1)
self._audio_stream.stop_stream()
self._audio_stream.close()
self._audio = pyaudio.PyAudio()
self._audio_stream = self._audio.open(format=pyaudio.paInt16,
rate=self._audio_rate,
channels=self._audio_channels,
input=True,
input_device_index=self._settings.audio['input'],
frames_per_buffer=self._audio_sample_count * 10)
else:
self._audio_stream = oPYA.open(format=pyaudio.paInt16,
rate=self._audio_rate_pa,
channels=self._audio_channels,
input=True,
input_device_index=iInput,
frames_per_buffer=self._audio_sample_count_pa * 10)
self._audio_running = True
self._audio_thread = BaseThread(target=self.send_audio,
name='_audio_thread')
self._audio_thread.start()
except Exception as e:
LOG.error(f"Starting self._audio.open {e}")
LOG.debug(repr(dict(format=pyaudio.paInt16,
rate=self._audio_rate_pa,
channels=self._audio_channels,
input=True,
input_device_index=iInput,
frames_per_buffer=self._audio_sample_count_pa * 10)))
# catcher in place in calls_manager? not if from a callback
# calls_manager._call.toxav_call_state_cb(friend_number, mask)
# raise RuntimeError(e)
return
else:
LOG_DEBUG(f"start_audio_thread {self._audio_stream!r}")
self._audio_thread = threading.Thread(target=self.send_audio)
self._audio_thread.start()
def stop_audio_thread(self):
if self._audio_thread is None:
return
self._audio_running = False
self._audio_thread.join()
self._audio_thread = None
self._audio_stream = None
self._audio = None
@ -283,48 +144,21 @@ class AV(common.tox_save.ToxAvSave):
def start_video_thread(self):
if self._video_thread is not None:
return
s = self._settings
if 'video' not in s:
LOG.warn("AV.__init__ 'video' not in s" )
LOG.debug(f"start_video_thread {s!r}" )
raise RuntimeError("start_video_thread not 'video' in s)" )
elif 'device' not in s['video']:
LOG.error("start_video_thread not 'device' in s['video']" )
LOG.debug(f"start_video_thread {s['video']!r}" )
raise RuntimeError("start_video_thread not 'device' ins s['video']" )
self._video_width = s['video']['width']
self._video_height = s['video']['height']
# dunno
if True or s['video']['device'] == -1:
self._video = screen_sharing.DesktopGrabber(s['video']['x'],
s['video']['y'],
s['video']['width'],
s['video']['height'])
else:
with ts.ignoreStdout():
import cv2
if s['video']['device'] == 0:
# webcam
self._video = cv2.VideoCapture(s['video']['device'], cv2.DSHOW)
else:
self._video = cv2.VideoCapture(s['video']['device'])
self._video.set(cv2.CAP_PROP_FPS, iFPS)
self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width)
self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height)
# self._video.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
if self._video is None:
LOG.error("start_video_thread " \
+f" device: {s['video']['device']}" \
+f" supported: {s['video']['width']} {s['video']['height']}")
return
LOG.info("start_video_thread " \
+f" device: {s['video']['device']}" \
+f" supported: {s['video']['width']} {s['video']['height']}")
self._video_running = True
self._video_thread = BaseThread(target=self.send_video,
name='_video_thread')
self._video_width = s.video['width']
self._video_height = s.video['height']
if s.video['device'] == -1:
self._video = screen_sharing.DesktopGrabber(self._settings.video['x'], self._settings.video['y'],
self._settings.video['width'], self._settings.video['height'])
else:
self._video = cv2.VideoCapture(self._settings.video['device'])
self._video.set(cv2.CAP_PROP_FPS, 25)
self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width)
self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height)
self._video_thread = threading.Thread(target=self.send_video)
self._video_thread.start()
def stop_video_thread(self):
@ -332,21 +166,13 @@ class AV(common.tox_save.ToxAvSave):
return
self._video_running = False
i = 0
while i < ts.iTHREAD_JOINS:
self._video_thread.join(ts.iTHREAD_TIMEOUT)
try:
if not self._video_thread.is_alive(): break
except:
# AttributeError: 'NoneType' object has no attribute 'join'
break
i = i + 1
else:
LOG.warn("self._video_thread.is_alive BLOCKED")
self._video_thread.join()
self._video_thread = None
self._video = None
# -----------------------------------------------------------------------------------------------------------------
# Incoming chunks
# -----------------------------------------------------------------------------------------------------------------
def audio_chunk(self, samples, channels_count, rate):
"""
@ -354,124 +180,58 @@ class AV(common.tox_save.ToxAvSave):
"""
if self._out_stream is None:
# was iOutput = self._settings._args.audio['output']
iOutput = self._settings['audio']['output']
if not rate in self.lPaSampleratesO:
LOG.warn(f"{rate} not in {self.lPaSampleratesO!r}")
if False:
rate = oPYA.get_device_info_by_index(iOutput)['defaultSampleRate']
LOG.warn(f"Setting audio_rate to: {self.lPaSampleratesO[0]}")
rate = self.lPaSampleratesO[0]
try:
with ts.ignoreStderr():
self._out_stream = oPYA.open(format=pyaudio.paInt16,
channels=channels_count,
rate=rate,
output_device_index=iOutput,
output=True)
except Exception as e:
LOG.error(f"Error playing audio_chunk creating self._out_stream {e}")
invoke_in_main_thread(util_ui.message_box,
str(e),
util_ui.tr("Error Chunking audio"))
# dunno
self.stop()
return
iOutput = self._settings['audio']['output']
LOG.debug(f"audio_chunk output_device_index={iOutput} rate={rate} channels={channels_count}")
self._out_stream = self._audio.open(format=pyaudio.paInt16,
channels=channels_count,
rate=rate,
output_device_index=self._settings.audio['output'],
output=True)
self._out_stream.write(samples)
# -----------------------------------------------------------------------------------------------------------------
# AV sending
def send_audio_data(self, data, count, *largs, **kwargs):
pcm = data
# :param sampling_rate: Audio sampling rate used in this frame.
if self._toxav is None:
raise RuntimeError("_toxav not initialized")
if self._audio_rate_tox not in ts.lToxSamplerates:
LOG.warn(f"ToxAudio sampling rate was {self._audio_rate_tox} changed to {ts.lToxSamplerates[0]}")
self._audio_rate_tox = ts.lToxSamplerates[0]
for friend_num in self._calls:
if self._calls[friend_num].out_audio:
try:
# app.av.calls ERROR Error send_audio: One of the frame parameters was invalid. E.g. the resolution may be too small or too large, or the audio sampling rate may be unsupported
# app.av.calls ERROR Error send_audio audio_send_frame: This client is currently not in a call with the friend.
self._toxav.audio_send_frame(friend_num,
pcm,
count,
self._audio_channels,
self._audio_rate_tox)
except Exception as e:
LOG.error(f"Error send_audio audio_send_frame: {e}")
LOG.debug(f"send_audio self._audio_rate_tox={self._audio_rate_tox} self._audio_channels={self._audio_channels}")
# invoke_in_main_thread(util_ui.message_box,
# str(e),
# util_ui.tr("Error send_audio audio_send_frame"))
pass
# -----------------------------------------------------------------------------------------------------------------
def send_audio(self):
"""
This method sends audio to friends
"""
i=0
count = self._audio_sample_count_tox
LOG.debug(f"send_audio stream={self._audio_stream}")
while self._audio_running:
try:
pcm = self._audio_stream.read(count, exception_on_overflow=False)
if not pcm:
sleep(0.1)
else:
self.send_audio_data(pcm, count)
pcm = self._audio_stream.read(self._audio_sample_count)
if pcm:
for friend_num in self._calls:
if self._calls[friend_num].out_audio:
try:
self._toxav.audio_send_frame(friend_num, pcm, self._audio_sample_count,
self._audio_channels, self._audio_rate)
except:
pass
except:
LOG_DEBUG(f"error send_audio {i}")
else:
LOG_TRACE(f"send_audio {i}")
i += 1
sleep(0.01)
pass
time.sleep(0.01)
def send_video(self):
"""
This method sends video to friends
"""
LOG.debug(f"send_video thread={threading.current_thread().name}"
+f" self._video_running={self._video_running}"
+f" device: {self._settings['video']['device']}" )
while self._video_running:
try:
result, frame = self._video.read()
if not result:
LOG.warn(f"send_video video_send_frame _video.read result={result}")
break
if frame is None:
LOG.warn(f"send_video video_send_frame _video.read result={result} frame={frame}")
continue
else:
LOG_TRACE(f"send_video video_send_frame _video.read result={result}")
if result:
height, width, channels = frame.shape
friends = []
for friend_num in self._calls:
if self._calls[friend_num].out_video:
friends.append(friend_num)
if len(friends) == 0:
LOG.warn(f"send_video video_send_frame no friends")
else:
LOG_TRACE(f"send_video video_send_frame {friends}")
friend_num = friends[0]
try:
y, u, v = self.convert_bgr_to_yuv(frame)
self._toxav.video_send_frame(friend_num, width, height, y, u, v)
except Exception as e:
LOG.debug(f"send_video video_send_frame ERROR {e}")
pass
except Exception as e:
LOG.error(f"send_video video_send_frame {e}")
try:
y, u, v = self.convert_bgr_to_yuv(frame)
self._toxav.video_send_frame(friend_num, width, height, y, u, v)
except:
pass
except:
pass
sleep( 1.0/iFPS)
time.sleep(0.01)
def convert_bgr_to_yuv(self, frame):
"""
@ -504,14 +264,11 @@ class AV(common.tox_save.ToxAvSave):
Y, U, V can be extracted using slices and joined in one list using itertools.chain.from_iterable()
Function returns bytes(y), bytes(u), bytes(v), because it is required for ctypes
"""
with ts.ignoreStdout():
import cv2
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420)
y = frame[:self._video_height, :]
y = list(itertools.chain.from_iterable(y))
import numpy as np
u = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int)
u[::2, :] = frame[self._video_height:self._video_height * 5 // 4, :self._video_width // 2]
u[1::2, :] = frame[self._video_height:self._video_height * 5 // 4, self._video_width // 2:]

View File

@ -1,36 +1,29 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import sys
import threading
import cv2
import av.calls
from messenger.messages import *
from ui import av_widgets
import common.event as event
import utils.ui as util_ui
global LOG
import logging
LOG = logging.getLogger('app.'+__name__)
class CallsManager:
def __init__(self, toxav, settings, main_screen, contacts_manager, app=None):
self._callav = av.calls.AV(toxav, settings) # object with data about calls
self._call = self._callav
def __init__(self, toxav, settings, screen, contacts_manager):
self._call = av.calls.AV(toxav, settings) # object with data about calls
self._call_widgets = {} # dict of incoming call widgets
self._incoming_calls = set()
self._settings = settings
self._main_screen = main_screen
self._screen = screen
self._contacts_manager = contacts_manager
self._call_started_event = event.Event() # friend_number, audio, video, is_outgoing
self._call_finished_event = event.Event() # friend_number, is_declined
self._app = app
def set_toxav(self, toxav):
self._callav.set_toxav(toxav)
self._call.set_toxav(toxav)
# -----------------------------------------------------------------------------------------------------------------
# Events
# -----------------------------------------------------------------------------------------------------------------
def get_call_started_event(self):
return self._call_started_event
@ -42,33 +35,35 @@ class CallsManager:
call_finished_event = property(get_call_finished_event)
# -----------------------------------------------------------------------------------------------------------------
# AV support
# -----------------------------------------------------------------------------------------------------------------
def call_click(self, audio=True, video=False):
"""User clicked audio button in main window"""
num = self._contacts_manager.get_active_number()
if not self._contacts_manager.is_active_a_friend():
return
if num not in self._callav and self._contacts_manager.is_active_online(): # start call
if not self._settings['audio']['enabled']:
if num not in self._call and self._contacts_manager.is_active_online(): # start call
if not self._settings.audio['enabled']:
return
self._callav(num, audio, video)
self._main_screen.active_call()
self._call(num, audio, video)
self._screen.active_call()
self._call_started_event(num, audio, video, True)
elif num in self._callav: # finish or cancel call if you call with active friend
elif num in self._call: # finish or cancel call if you call with active friend
self.stop_call(num, False)
def incoming_call(self, audio, video, friend_number):
"""
Incoming call from friend.
"""
LOG.debug(__name__ +f" incoming_call {friend_number}")
# if not self._settings['audio']['enabled']: return
if not self._settings.audio['enabled']:
return
friend = self._contacts_manager.get_friend_by_number(friend_number)
self._call_started_event(friend_number, audio, video, False)
self._incoming_calls.add(friend_number)
if friend_number == self._contacts_manager.get_active_number():
self._main_screen.incoming_call()
self._screen.incoming_call()
else:
friend.actions = True
text = util_ui.tr("Incoming video call") if video else util_ui.tr("Incoming audio call")
@ -79,83 +74,43 @@ class CallsManager:
def accept_call(self, friend_number, audio, video):
"""
Accept incoming call with audio or video
Called from a thread
"""
LOG.debug(f"CM accept_call from {friend_number} {audio} {video}")
sys.stdout.flush()
try:
self._callav.call_accept_call(friend_number, audio, video)
except Exception as e:
LOG.error(f"accept_call _call.accept_call ERROR for {friend_number} {e}")
self._main_screen.call_finished()
if hasattr(self._main_screen, '_settings') and \
'audio' in self._main_screen._settings and \
'input' in self._main_screen._settings['audio']:
iInput = self._settings['audio']['input']
iOutput = self._settings['audio']['output']
iVideo = self._settings['video']['device']
LOG.debug(f"iInput={iInput} iOutput={iOutput} iVideo={iVideo}")
elif hasattr(self._main_screen, '_settings') and \
hasattr(self._main_screen._settings, 'audio') and \
'input' not in self._main_screen._settings['audio']:
LOG.warn(f"'audio' not in {self._main_screen._settings!r}")
elif hasattr(self._main_screen, '_settings') and \
hasattr(self._main_screen._settings, 'audio') and \
'input' not in self._main_screen._settings['audio']:
LOG.warn(f"'audio' not in {self._main_screen._settings!r}")
else:
LOG.warn(f"_settings not in self._main_screen")
util_ui.message_box(str(e),
util_ui.tr('ERROR Accepting call from {friend_number}'))
else:
self._main_screen.active_call()
finally:
# does not terminate call - just the av_widget
if friend_number in self._incoming_calls:
self._incoming_calls.remove(friend_number)
try:
self._call_widgets[friend_number].close()
del self._call_widgets[friend_number]
except:
# RuntimeError: wrapped C/C++ object of type IncomingCallWidget has been deleted
pass
LOG.debug(f" closed self._call_widgets[{friend_number}]")
self._call.accept_call(friend_number, audio, video)
self._screen.active_call()
if friend_number in self._incoming_calls:
self._incoming_calls.remove(friend_number)
del self._call_widgets[friend_number]
def stop_call(self, friend_number, by_friend):
"""
Stop call with friend
"""
LOG.debug(__name__+f" stop_call {friend_number}")
if friend_number in self._incoming_calls:
self._incoming_calls.remove(friend_number)
is_declined = True
else:
is_declined = False
self._main_screen.call_finished()
self._callav.finish_call(friend_number, by_friend) # finish or decline call
self._screen.call_finished()
is_video = self._call.is_video_call(friend_number)
self._call.finish_call(friend_number, by_friend) # finish or decline call
if friend_number in self._call_widgets:
self._call_widgets[friend_number].close()
del self._call_widgets[friend_number]
def destroy_window():
#??? FixMed
is_video = self._callav.is_video_call(friend_number)
if is_video:
import cv2
cv2.destroyWindow(str(friend_number))
threading.Timer(2.0, destroy_window).start()
self._call_finished_event(friend_number, is_declined)
def friend_exit(self, friend_number):
if friend_number in self._callav:
self._callav.finish_call(friend_number, True)
if friend_number in self._call:
self._call.finish_call(friend_number, True)
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _get_incoming_call_widget(self, friend_number, text, friend_name):
return av_widgets.IncomingCallWidget(self._settings, self, friend_number, text, friend_name)

View File

@ -1,3 +1,4 @@
import numpy as np
from PyQt5 import QtWidgets
@ -16,7 +17,6 @@ class DesktopGrabber:
pixmap = self._screen.grabWindow(0, self._x, self._y, self._width, self._height)
image = pixmap.toImage()
s = image.bits().asstring(self._width * self._height * 4)
import numpy as np
arr = np.fromstring(s, dtype=np.uint8).reshape((self._height, self._width, 4))
return True, arr

View File

@ -1,48 +1,83 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import random
import urllib.request
from utils.util import *
from PyQt5 import QtNetwork
from PyQt5 import QtCore
try:
import certifi
from io import BytesIO
except ImportError:
certifi = None
from PyQt5 import QtNetwork, QtCore
import json
from user_data.settings import get_user_config_path
from wrapper_tests.support_testing import _get_nodes_path
from wrapper_tests.support_http import download_url
import wrapper_tests.support_testing as ts
global LOG
import logging
LOG = logging.getLogger('app.'+'bootstrap')
DEFAULT_NODES_COUNT = 4
def download_nodes_list(settings, oArgs):
class Node:
def __init__(self, node):
self._ip, self._port, self._tox_key = node['ipv4'], node['port'], node['public_key']
self._priority = random.randint(1, 1000000) if node['status_tcp'] and node['status_udp'] else 0
def get_priority(self):
return self._priority
priority = property(get_priority)
def get_data(self):
return self._ip, self._port, self._tox_key
def generate_nodes(nodes_count=DEFAULT_NODES_COUNT):
with open(_get_nodes_path(), 'rt') as fl:
json_nodes = json.loads(fl.read())['nodes']
nodes = map(lambda json_node: Node(json_node), json_nodes)
nodes = filter(lambda n: n.priority > 0, nodes)
sorted_nodes = sorted(nodes, key=lambda x: x.priority)
if nodes_count is not None:
sorted_nodes = sorted_nodes[-DEFAULT_NODES_COUNT:]
for node in sorted_nodes:
yield node.get_data()
def download_nodes_list(settings):
url = 'https://nodes.tox.chat/json'
if not settings['download_nodes_list']:
return ''
if not ts.bAreWeConnected():
return ''
url = settings['download_nodes_url']
path = _get_nodes_path(oArgs=oArgs)
# dont download blindly so we can edit the file and not block on startup
if os.path.isfile(path):
with open(path, 'rt') as fl:
result = fl.read()
return result
LOG.debug("downloading list of nodes")
result = download_url(url, settings._app._settings)
if not result:
LOG.warn("failed downloading list of nodes")
return ''
LOG.info("downloaded list of nodes")
_save_nodes(result, settings._app)
return result
return
def _save_nodes(nodes, app):
if not settings['proxy_type']: # no proxy
try:
req = urllib.request.Request(url)
req.add_header('Content-Type', 'application/json')
response = urllib.request.urlopen(req)
result = response.read()
_save_nodes(result)
except Exception as ex:
log('TOX nodes loading error: ' + str(ex))
else: # proxy
netman = QtNetwork.QNetworkAccessManager()
proxy = QtNetwork.QNetworkProxy()
proxy.setType(
QtNetwork.QNetworkProxy.Socks5Proxy if settings['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy)
proxy.setHostName(settings['proxy_host'])
proxy.setPort(settings['proxy_port'])
netman.setProxy(proxy)
try:
request = QtNetwork.QNetworkRequest()
request.setUrl(QtCore.QUrl(url))
reply = netman.get(request)
while not reply.isFinished():
QtCore.QThread.msleep(1)
QtCore.QCoreApplication.processEvents()
data = bytes(reply.readAll().data())
_save_nodes(data)
except Exception as ex:
log('TOX nodes loading error: ' + str(ex))
def _get_nodes_path():
return join_path(curr_directory(__file__), 'nodes.json')
def _save_nodes(nodes):
if not nodes:
return
with open(_get_nodes_path(app._args), 'wb') as fl:
LOG.info("Saving nodes to " +_get_nodes_path(app._args))
print('Saving nodes...')
with open(_get_nodes_path(), 'wb') as fl:
fl.write(nodes)

View File

@ -1,4 +1,3 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
from user_data.settings import *
from PyQt5 import QtCore, QtGui
from wrapper.toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
@ -15,27 +14,26 @@ class BaseContact:
Base class for all contacts.
"""
def __init__(self, profile_manager, name, status_message, widget, tox_id, kind=''):
def __init__(self, profile_manager, name, status_message, widget, tox_id):
"""
:param name: name, example: 'Toxygen user'
:param status_message: status message, example: 'Toxing on Toxygen'
:param widget: ContactItem instance
:param tox_id: tox id of contact
:param kind: one of ['bot', 'friend', 'group', 'invite', 'grouppeer', '']
"""
self._profile_manager = profile_manager
self._name, self._status_message = name, status_message
self._kind = kind
self._status, self._widget = None, widget
self._tox_id = tox_id
self._name_changed_event = event.Event()
self._status_message_changed_event = event.Event()
self._status_changed_event = event.Event()
self._avatar_changed_event = event.Event()
self.init_widget()
# -----------------------------------------------------------------------------------------------------------------
# Name - current name or alias of user
# -----------------------------------------------------------------------------------------------------------------
def get_name(self):
return self._name
@ -55,7 +53,9 @@ class BaseContact:
name_changed_event = property(get_name_changed_event)
# -----------------------------------------------------------------------------------------------------------------
# Status message
# -----------------------------------------------------------------------------------------------------------------
def get_status_message(self):
return self._status_message
@ -75,7 +75,9 @@ class BaseContact:
status_message_changed_event = property(get_status_message_changed_event)
# -----------------------------------------------------------------------------------------------------------------
# Status
# -----------------------------------------------------------------------------------------------------------------
def get_status(self):
return self._status
@ -94,30 +96,31 @@ class BaseContact:
status_changed_event = property(get_status_changed_event)
# -----------------------------------------------------------------------------------------------------------------
# TOX ID. WARNING: for friend it will return public key, for profile - full address
# -----------------------------------------------------------------------------------------------------------------
def get_tox_id(self):
return self._tox_id
tox_id = property(get_tox_id)
# -----------------------------------------------------------------------------------------------------------------
# Avatars
# -----------------------------------------------------------------------------------------------------------------
def load_avatar(self):
"""
Tries to load avatar of contact or uses default avatar
"""
try:
avatar_path = self.get_avatar_path()
width = self._widget.avatar_label.width()
pixmap = QtGui.QPixmap(avatar_path)
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation))
self._widget.avatar_label.repaint()
self._avatar_changed_event(avatar_path)
except Exception as e:
pass
avatar_path = self.get_avatar_path()
width = self._widget.avatar_label.width()
pixmap = QtGui.QPixmap(avatar_path)
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation))
self._widget.avatar_label.repaint()
self._avatar_changed_event(avatar_path)
def reset_avatar(self, generate_new):
avatar_path = self.get_avatar_path()
if os.path.isfile(avatar_path) and not avatar_path == self._get_default_avatar_path():
@ -158,17 +161,19 @@ class BaseContact:
avatar_changed_event = property(get_avatar_changed_event)
# -----------------------------------------------------------------------------------------------------------------
# Widgets
# -----------------------------------------------------------------------------------------------------------------
def init_widget(self):
self._widget.name.setText(self._name)
self._widget.status_message.setText(self._status_message)
if hasattr(self._widget, 'kind'):
self._widget.kind.setText(self._kind)
self._widget.connection_status.update(self._status)
self.load_avatar()
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
@staticmethod
def _get_default_avatar_path():

View File

@ -2,7 +2,9 @@ from pydenticon import Generator
import hashlib
# -----------------------------------------------------------------------------------------------------------------
# Typing notifications
# -----------------------------------------------------------------------------------------------------------------
class BaseTypingNotificationHandler:
@ -28,7 +30,9 @@ class FriendTypingNotificationHandler(BaseTypingNotificationHandler):
BaseTypingNotificationHandler.DEFAULT_HANDLER = BaseTypingNotificationHandler()
# -----------------------------------------------------------------------------------------------------------------
# Identicons support
# -----------------------------------------------------------------------------------------------------------------
def generate_avatar(public_key):

View File

@ -1,17 +1,10 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
from history.database import TIMEOUT, \
SAVE_MESSAGES, MESSAGE_AUTHOR
from history.database import *
from contacts import basecontact, common
from messenger.messages import *
from contacts.contact_menu import *
from file_transfers import file_transfers as ft
import re
# LOG=util.log
global LOG
import logging
LOG = logging.getLogger('app.'+__name__)
class Contact(basecontact.BaseContact):
"""
@ -42,7 +35,9 @@ class Contact(basecontact.BaseContact):
if hasattr(self, '_message_getter'):
del self._message_getter
# -----------------------------------------------------------------------------------------------------------------
# History support
# -----------------------------------------------------------------------------------------------------------------
def load_corr(self, first_time=True):
"""
@ -119,7 +114,9 @@ class Contact(basecontact.BaseContact):
return TextMessage(message, author, unix_time, message_type, unique_id)
# -----------------------------------------------------------------------------------------------------------------
# Unsent messages
# -----------------------------------------------------------------------------------------------------------------
def get_unsent_messages(self):
"""
@ -132,11 +129,8 @@ class Contact(basecontact.BaseContact):
"""
:return list of unsent messages for saving
"""
# and m.tox_message_id == tox_message_id,
messages = filter(lambda m: m.author is not None
and m.author.type == MESSAGE_AUTHOR['NOT_SENT']
self._corr)
# was message = list(...)[0]
messages = filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION'])
and m.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr)
return list(messages)
def mark_as_sent(self, tox_message_id):
@ -145,10 +139,11 @@ class Contact(basecontact.BaseContact):
and m.tox_message_id == tox_message_id, self._corr))[0]
message.mark_as_sent()
except Exception as ex:
# wrapped C/C++ object of type QLabel has been deleted
LOG.error(f"Mark as sent: {ex!s}")
util.log('Mark as sent ex: ' + str(ex))
# -----------------------------------------------------------------------------------------------------------------
# Message deletion
# -----------------------------------------------------------------------------------------------------------------
def delete_message(self, message_id):
elem = list(filter(lambda m: m.message_id == message_id, self._corr))[0]
@ -194,7 +189,9 @@ class Contact(basecontact.BaseContact):
self._corr))
self._unsaved_messages = len(self.get_unsent_messages())
# -----------------------------------------------------------------------------------------------------------------
# Chat history search
# -----------------------------------------------------------------------------------------------------------------
def search_string(self, search_string):
self._search_string, self._search_index = search_string, 0
@ -227,7 +224,9 @@ class Contact(basecontact.BaseContact):
return i
return None # not found
# -----------------------------------------------------------------------------------------------------------------
# Current text - text from message area
# -----------------------------------------------------------------------------------------------------------------
def get_curr_text(self):
return self._curr_text
@ -237,7 +236,9 @@ class Contact(basecontact.BaseContact):
curr_text = property(get_curr_text, set_curr_text)
# -----------------------------------------------------------------------------------------------------------------
# Alias support
# -----------------------------------------------------------------------------------------------------------------
def set_name(self, value):
"""
@ -253,7 +254,9 @@ class Contact(basecontact.BaseContact):
def has_alias(self):
return self._alias
# -----------------------------------------------------------------------------------------------------------------
# Visibility in friends' list
# -----------------------------------------------------------------------------------------------------------------
def get_visibility(self):
return self._visible
@ -263,7 +266,9 @@ class Contact(basecontact.BaseContact):
visibility = property(get_visibility, set_visibility)
# -----------------------------------------------------------------------------------------------------------------
# Unread messages and other actions from friend
# -----------------------------------------------------------------------------------------------------------------
def get_actions(self):
return self._new_actions
@ -291,7 +296,9 @@ class Contact(basecontact.BaseContact):
messages = property(get_messages)
# -----------------------------------------------------------------------------------------------------------------
# Friend's or group's number (can be used in toxcore)
# -----------------------------------------------------------------------------------------------------------------
def get_number(self):
return self._number
@ -301,19 +308,25 @@ class Contact(basecontact.BaseContact):
number = property(get_number, set_number)
# -----------------------------------------------------------------------------------------------------------------
# Typing notifications
# -----------------------------------------------------------------------------------------------------------------
def get_typing_notification_handler(self):
return common.BaseTypingNotificationHandler.DEFAULT_HANDLER
typing_notification_handler = property(get_typing_notification_handler)
# -----------------------------------------------------------------------------------------------------------------
# Context menu support
# -----------------------------------------------------------------------------------------------------------------
def get_context_menu_generator(self):
return BaseContactMenuGenerator(self)
# -----------------------------------------------------------------------------------------------------------------
# Filtration support
# -----------------------------------------------------------------------------------------------------------------
def set_widget(self, widget):
self._widget = widget

View File

@ -1,14 +1,10 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
from PyQt5 import QtWidgets
import utils.ui as util_ui
from wrapper.toxcore_enums_and_consts import *
global LOG
import logging
LOG = logging.getLogger('app')
# -----------------------------------------------------------------------------------------------------------------
# Builder
# -----------------------------------------------------------------------------------------------------------------
def _create_menu(menu_name, parent):
menu_name = menu_name or ''
@ -81,7 +77,9 @@ class ContactMenuBuilder:
self._actions[self._index] = (text, handler)
self._index += 1
# -----------------------------------------------------------------------------------------------------------------
# Generators
# -----------------------------------------------------------------------------------------------------------------
class BaseContactMenuGenerator:
@ -92,15 +90,17 @@ class BaseContactMenuGenerator:
def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader):
return ContactMenuBuilder().build()
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _generate_copy_menu_builder(self, main_screen):
copy_menu_builder = ContactMenuBuilder()
(copy_menu_builder
.with_name(util_ui.tr('Copy'))
.with_action(util_ui.tr('Name'), lambda: main_screen.copy_text(self._contact.name))
.with_action(util_ui.tr("Status message"), lambda: main_screen.copy_text(self._contact.status_message))
.with_action(util_ui.tr("Public key"), lambda: main_screen.copy_text(self._contact.tox_id))
.with_action(util_ui.tr('Status message'), lambda: main_screen.copy_text(self._contact.status_message))
.with_action(util_ui.tr('Public key'), lambda: main_screen.copy_text(self._contact.tox_id))
)
return copy_menu_builder
@ -108,11 +108,11 @@ class BaseContactMenuGenerator:
def _generate_history_menu_builder(self, history_loader, main_screen):
history_menu_builder = ContactMenuBuilder()
(history_menu_builder
.with_name(util_ui.tr("Chat history"))
.with_action(util_ui.tr("Clear history"), lambda: history_loader.clear_history(self._contact)
.with_name(util_ui.tr('Chat history'))
.with_action(util_ui.tr('Clear history'), lambda: history_loader.clear_history(self._contact)
or main_screen.messages.clear())
.with_action(util_ui.tr("Export as text"), lambda: history_loader.export_history(self._contact))
.with_action(util_ui.tr("Export as HTML"), lambda: history_loader.export_history(self._contact, False))
.with_action(util_ui.tr('Export as text'), lambda: history_loader.export_history(self._contact))
.with_action(util_ui.tr('Export as HTML'), lambda: history_loader.export_history(self._contact, False))
)
return history_menu_builder
@ -127,16 +127,16 @@ class FriendMenuGenerator(BaseContactMenuGenerator):
groups_menu_builder = self._generate_groups_menu(contacts_manager, groups_service)
allowed = self._contact.tox_id in settings['auto_accept_from_friends']
auto = util_ui.tr("Disallow auto accept") if allowed else util_ui.tr('Allow auto accept')
auto = util_ui.tr('Disallow auto accept') if allowed else util_ui.tr('Allow auto accept')
builder = ContactMenuBuilder()
menu = (builder
.with_action(util_ui.tr("Set alias"), lambda: main_screen.set_alias(number))
.with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number))
.with_submenu(history_menu_builder)
.with_submenu(copy_menu_builder)
.with_action(auto, lambda: main_screen.auto_accept(number, not allowed))
.with_action(util_ui.tr("Remove friend"), lambda: main_screen.remove_friend(number))
.with_action(util_ui.tr("Block friend"), lambda: main_screen.block_friend(number))
.with_action(util_ui.tr('Remove friend'), lambda: main_screen.remove_friend(number))
.with_action(util_ui.tr('Block friend'), lambda: main_screen.block_friend(number))
.with_action(util_ui.tr('Notes'), lambda: main_screen.show_note(self._contact))
.with_optional_submenu(plugins_menu_builder)
.with_optional_submenu(groups_menu_builder)
@ -144,7 +144,9 @@ class FriendMenuGenerator(BaseContactMenuGenerator):
return menu
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
@staticmethod
def _generate_plugins_menu_builder(plugin_loader, number):
@ -163,13 +165,11 @@ class FriendMenuGenerator(BaseContactMenuGenerator):
def _generate_groups_menu(self, contacts_manager, groups_service):
chats = contacts_manager.get_group_chats()
LOG.debug(f"_generate_groups_menu len(chats)={len(chats)} or self._contact.status={self._contact.status}")
if not len(chats) or self._contact.status is None:
#? return None
pass
return None
groups_menu_builder = ContactMenuBuilder()
(groups_menu_builder
.with_name(util_ui.tr("Invite to group"))
.with_name(util_ui.tr('Invite to group'))
.with_actions([(g.name, lambda: groups_service.invite_friend(self._contact.number, g.number)) for g in chats])
)
@ -184,26 +184,26 @@ class GroupMenuGenerator(BaseContactMenuGenerator):
builder = ContactMenuBuilder()
menu = (builder
.with_action(util_ui.tr("Set alias"), lambda: main_screen.set_alias(number))
.with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number))
.with_submenu(copy_menu_builder)
.with_submenu(history_menu_builder)
.with_optional_action(util_ui.tr("Manage group"),
.with_optional_action(util_ui.tr('Manage group'),
lambda: groups_service.show_group_management_screen(self._contact),
self._contact.is_self_founder())
.with_optional_action(util_ui.tr("Group settings"),
.with_optional_action(util_ui.tr('Group settings'),
lambda: groups_service.show_group_settings_screen(self._contact),
not self._contact.is_self_founder())
.with_optional_action(util_ui.tr("Set topic"),
.with_optional_action(util_ui.tr('Set topic'),
lambda: groups_service.set_group_topic(self._contact),
self._contact.is_self_moderator_or_founder())
# .with_action(util_ui.tr("Bans list"),
# lambda: groups_service.show_bans_list(self._contact))
.with_action(util_ui.tr("Reconnect to group"),
.with_action(util_ui.tr('Bans list'),
lambda: groups_service.show_bans_list(self._contact))
.with_action(util_ui.tr('Reconnect to group'),
lambda: groups_service.reconnect_to_group(self._contact.number))
.with_optional_action(util_ui.tr("Disconnect from group"),
.with_optional_action(util_ui.tr('Disconnect from group'),
lambda: groups_service.disconnect_from_group(self._contact.number),
self._contact.status is not None)
.with_action(util_ui.tr("Leave group"), lambda: groups_service.leave_group(self._contact.number))
.with_action(util_ui.tr('Leave group'), lambda: groups_service.leave_group(self._contact.number))
.with_action(util_ui.tr('Notes'), lambda: main_screen.show_note(self._contact))
).build()
@ -218,10 +218,10 @@ class GroupPeerMenuGenerator(BaseContactMenuGenerator):
builder = ContactMenuBuilder()
menu = (builder
.with_action(util_ui.tr("Set alias"), lambda: main_screen.set_alias(number))
.with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number))
.with_submenu(copy_menu_builder)
.with_submenu(history_menu_builder)
.with_action(util_ui.tr("Quit chat"),
.with_action(util_ui.tr('Quit chat'),
lambda: contacts_manager.remove_group_peer(self._contact))
.with_action(util_ui.tr('Notes'), lambda: main_screen.show_note(self._contact))
).build()

View File

@ -1,23 +1,5 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import common.tox_save as tox_save
global LOG
import logging
LOG = logging.getLogger(__name__)
# callbacks can be called in any thread so were being careful
def LOG_ERROR(l): print('EROR< '+l)
def LOG_WARN(l): print('WARN< '+l)
def LOG_INFO(l):
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 20-1
if bIsVerbose: print('INFO< '+l)
def LOG_DEBUG(l):
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 10-1
if bIsVerbose: print('DBUG< '+l)
def LOG_TRACE(l):
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel < 10-1
pass # print('TRACE+ '+l)
class ContactProvider(tox_save.ToxSave):
@ -28,14 +10,13 @@ class ContactProvider(tox_save.ToxSave):
self._group_peer_factory = group_peer_factory
self._cache = {} # key - contact's public key, value - contact instance
# -----------------------------------------------------------------------------------------------------------------
# Friends
# -----------------------------------------------------------------------------------------------------------------
def get_friend_by_number(self, friend_number):
try:
public_key = self._tox.friend_get_public_key(friend_number)
except Exception as e:
LOG_WARN(f"CP.get_friend_by_number NO {friend_number} {e} ")
return None
public_key = self._tox.friend_get_public_key(friend_number)
return self.get_friend_by_public_key(public_key)
def get_friend_by_public_key(self, public_key):
@ -44,110 +25,66 @@ class ContactProvider(tox_save.ToxSave):
return friend
friend = self._friend_factory.create_friend_by_public_key(public_key)
self._add_to_cache(public_key, friend)
LOG_INFO(f"CP.get_friend_by_public_key ADDED {friend} ")
return friend
def get_all_friends(self):
try:
friend_numbers = self._tox.self_get_friend_list()
except Exception as e:
LOG_WARN(f"CP.get_all_friends NO {friend_numbers} {e} ")
return None
friend_numbers = self._tox.self_get_friend_list()
friends = map(lambda n: self.get_friend_by_number(n), friend_numbers)
return list(friends)
# -----------------------------------------------------------------------------------------------------------------
# Groups
# -----------------------------------------------------------------------------------------------------------------
def get_all_groups(self):
"""from callbacks"""
try:
len_groups = self._tox.group_get_number_groups()
group_numbers = range(len_groups)
except Exception as e:
return None
groups = list(map(lambda n: self.get_group_by_number(n), group_numbers))
# failsafe in case there are bogus None groups?
fgroups = list(filter(lambda x: x, groups))
if len(fgroups) != len_groups:
LOG_WARN(f"CP.are there are bogus None groups in libtoxcore? {len(fgroups)} != {len_groups}")
for group_num in group_numbers:
group = self.get_group_by_number(group_num)
if group is None:
LOG_ERROR(f"There are bogus None groups in libtoxcore {group_num}!")
# fixme: do something
groups = fgroups
return groups
group_numbers = range(self._tox.group_get_number_groups())
groups = map(lambda n: self.get_group_by_number(n), group_numbers)
return list(groups)
def get_group_by_number(self, group_number):
group = None
try:
LOG_INFO(f"CP.CP.group_get_number {group_number} ")
# original code
chat_id = self._tox.group_get_chat_id(group_number)
if chat_id is None:
LOG_ERROR(f"get_group_by_number NULL chat_id ({group_number})")
elif chat_id == '-1':
LOG_ERROR(f"get_group_by_number <0 chat_id ({group_number})")
else:
LOG_INFO(f"CP.group_get_number {group_number} {chat_id}")
group = self.get_group_by_chat_id(chat_id)
if group is None or group == '-1':
LOG_WARN(f"CP.get_group_by_number leaving {group} ({group_number})")
#? iRet = self._tox.group_leave(group_number)
# invoke in main thread?
# self._contacts_manager.delete_group(group_number)
return group
except Exception as e:
LOG_WARN(f"CP.group_get_number {group_number} {e}")
return None
public_key = self._tox.group_get_chat_id(group_number)
def get_group_by_chat_id(self, chat_id):
group = self._get_contact_from_cache(chat_id)
if group is not None:
return group
group = self._group_factory.create_group_by_chat_id(chat_id)
if group is None:
LOG_ERROR(f"get_group_by_chat_id NULL chat_id={chat_id}")
else:
self._add_to_cache(chat_id, group)
return group
return self.get_group_by_public_key(public_key)
def get_group_by_public_key(self, public_key):
group = self._get_contact_from_cache(public_key)
if group is not None:
return group
group = self._group_factory.create_group_by_public_key(public_key)
if group is None:
LOG_ERROR(f"get_group_by_public_key NULL group public_key={get_group_by_chat_id}")
else:
self._add_to_cache(public_key, group)
self._add_to_cache(public_key, group)
return group
# -----------------------------------------------------------------------------------------------------------------
# Group peers
# -----------------------------------------------------------------------------------------------------------------
def get_all_group_peers(self):
return list()
def get_group_peer_by_id(self, group, peer_id):
peer = group.get_peer_by_id(peer_id)
if peer:
return self._get_group_peer(group, peer)
return self._get_group_peer(group, peer)
def get_group_peer_by_public_key(self, group, public_key):
peer = group.get_peer_by_public_key(public_key)
return self._get_group_peer(group, peer)
# -----------------------------------------------------------------------------------------------------------------
# All contacts
# -----------------------------------------------------------------------------------------------------------------
def get_all(self):
return self.get_all_friends() + self.get_all_groups() + self.get_all_group_peers()
# -----------------------------------------------------------------------------------------------------------------
# Caching
# -----------------------------------------------------------------------------------------------------------------
def clear_cache(self):
self._cache.clear()
@ -156,7 +93,9 @@ class ContactProvider(tox_save.ToxSave):
if contact_public_key in self._cache:
del self._cache[contact_public_key]
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _get_contact_from_cache(self, public_key):
return self._cache[public_key] if public_key in self._cache else None

View File

@ -1,42 +1,9 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import traceback
from contacts.friend import Friend
from contacts.group_chat import GroupChat
from messenger.messages import *
from common.tox_save import ToxSave
from contacts.group_peer_contact import GroupPeerContact
from groups.group_peer import GroupChatPeer
# LOG=util.log
global LOG
import logging
LOG = logging.getLogger('app.'+__name__)
def LOG_ERROR(l): print('ERROR_: '+l)
def LOG_WARN(l): print('WARN_: '+l)
def LOG_INFO(l): print('INFO_: '+l)
def LOG_DEBUG(l): print('DEBUG_: '+l)
def LOG_TRACE(l): pass # print('TRACE+ '+l)
UINT32_MAX = 2 ** 32 -1
def set_contact_kind(contact):
bInvite = len(contact.name) == TOX_PUBLIC_KEY_SIZE * 2 and \
contact.status_message == ''
bBot = not bInvite and contact.name.lower().endswith(' bot')
if type(contact) == Friend and bInvite:
contact._kind = 'invite'
elif type(contact) == Friend and bBot:
contact._kind = 'bot'
elif type(contact) == Friend:
contact._kind = 'friend'
elif type(contact) == GroupChat:
contact._kind = 'group'
elif type(contact) == GroupChatPeer:
contact._kind = 'grouppeer'
class ContactsManager(ToxSave):
"""
@ -48,14 +15,12 @@ class ContactsManager(ToxSave):
super().__init__(tox)
self._settings = settings
self._screen = screen
self._ms = screen
self._profile_manager = profile_manager
self._contact_provider = contact_provider
self._tox_dns = tox_dns
self._messages_items_factory = messages_items_factory
self._messages = screen.messages
self._contacts = []
self._active_contact = -1
self._contacts, self._active_contact = [], -1
self._active_contact_changed = Event()
self._sorting = settings['sorting']
self._filter_string = ''
@ -63,11 +28,6 @@ class ContactsManager(ToxSave):
self._history = history
self._load_contacts()
def _log(self, s):
try:
self._ms._log(s)
except: pass
def get_contact(self, num):
if num < 0 or num >= len(self._contacts):
return None
@ -93,25 +53,19 @@ class ContactsManager(ToxSave):
return self.get_curr_contact().number == group_number
def is_contact_active(self, contact):
if self._active_contact == -1:
# LOG.debug("No self._active_contact")
return False
if self._active_contact >= len(self._contacts):
LOG.warn(f"ERROR _active_contact={self._active_contact} >= contacts len={len(self._contacts)}")
return False
if not self._contacts[self._active_contact]:
LOG.warn(f"ERROR NULL {self._contacts[self._active_contact]} {contact.tox_id}")
return False
return self._contacts[self._active_contact].tox_id == contact.tox_id
# -----------------------------------------------------------------------------------------------------------------
# Reconnection support
# -----------------------------------------------------------------------------------------------------------------
def reset_contacts_statuses(self):
for contact in self._contacts:
contact.status = None
# -----------------------------------------------------------------------------------------------------------------
# Work with active friend
# -----------------------------------------------------------------------------------------------------------------
def get_active(self):
return self._active_contact
@ -141,12 +95,11 @@ class ContactsManager(ToxSave):
current_contact.remove_messages_widgets() # TODO: if required
self._unsubscribe_from_events(current_contact)
if self._active_contact >= 0 and self._active_contact != value:
if self._active_contact + 1 and self._active_contact != value:
try:
current_contact.curr_text = self._screen.messageEdit.toPlainText()
except:
pass
# IndexError: list index out of range
contact = self._contacts[value]
self._subscribe_to_events(contact)
contact.remove_invalid_unsent_files()
@ -176,9 +129,9 @@ class ContactsManager(ToxSave):
self._set_current_contact_data(contact)
self._active_contact_changed(contact)
except Exception as ex: # no friend found. ignore
LOG.warn(f"no friend found. Friend value: {value!s}")
LOG.error('in set active: ' + str(ex))
# gulp raise
util.log('Friend value: ' + str(value))
util.log('Error in set active: ' + str(ex))
raise
active_contact = property(get_active, set_active)
@ -200,23 +153,21 @@ class ContactsManager(ToxSave):
def is_active_a_group_chat_peer(self):
return type(self.get_curr_contact()) is GroupPeerContact
# -----------------------------------------------------------------------------------------------------------------
# Filtration
# -----------------------------------------------------------------------------------------------------------------
def filtration_and_sorting(self, sorting=0, filter_str=''):
"""
Filtration of friends list
:param sorting: 0 - no sorting, 1 - online only, 2 - online first, 3 - by name,
4 - online and by name, 5 - online first and by name, 6 kind
4 - online and by name, 5 - online first and by name
:param filter_str: show contacts which name contains this substring
"""
filter_str = filter_str.lower()
current_contact = self.get_curr_contact()
for index, contact in enumerate(self._contacts):
if not contact._kind:
set_contact_kind(contact)
if sorting > 6 or sorting < 0:
if sorting > 5 or sorting < 0:
sorting = 0
if sorting in (1, 2, 4, 5): # online first
@ -232,23 +183,14 @@ class ContactsManager(ToxSave):
part2 = sorted(part2, key=key_lambda)
self._contacts = part1 + part2
elif sorting == 0:
# AttributeError: 'NoneType' object has no attribute 'number'
for (i, contact) in enumerate(self._contacts):
if contact is None or not hasattr(contact, 'number'):
LOG.error(f"Contact {i} is None or not hasattr 'number'")
del self._contacts[i]
continue
contacts = sorted(self._contacts, key=lambda c: c.number)
friends = filter(lambda c: type(c) is Friend, contacts)
groups = filter(lambda c: type(c) is GroupChat, contacts)
group_peers = filter(lambda c: type(c) is GroupPeerContact, contacts)
self._contacts = list(friends) + list(groups) + list(group_peers)
elif sorting == 6:
self._contacts = sorted(self._contacts, key=lambda x: x._kind)
else:
self._contacts = sorted(self._contacts, key=lambda x: x.name.lower())
# change item widgets
for index, contact in enumerate(self._contacts):
list_item = self._screen.friends_list.item(index)
@ -280,7 +222,9 @@ class ContactsManager(ToxSave):
"""
self.filtration_and_sorting(self._sorting, self._filter_string)
# -----------------------------------------------------------------------------------------------------------------
# Contact getters
# -----------------------------------------------------------------------------------------------------------------
def get_friend_by_number(self, number):
return list(filter(lambda c: c.number == number and type(c) is Friend, self._contacts))[0]
@ -291,15 +235,10 @@ class ContactsManager(ToxSave):
def get_or_create_group_peer_contact(self, group_number, peer_id):
group = self.get_group_by_number(group_number)
peer = group.get_peer_by_id(peer_id)
if peer: # broken?
if not hasattr(peer, 'public_key') or not peer.public_key:
LOG.error(f'no peer public_key ' + repr(dir(peer)))
else:
if not self.check_if_contact_exists(peer.public_key):
self.add_group_peer(group, peer)
return self.get_contact_by_tox_id(peer.public_key)
else:
LOG.warn(f'no peer group_number={group_number} peer_id={peer_id}')
if not self.check_if_contact_exists(peer.public_key):
self.add_group_peer(group, peer)
return self.get_contact_by_tox_id(peer.public_key)
def check_if_contact_exists(self, tox_id):
return any(filter(lambda c: c.tox_id == tox_id, self._contacts))
@ -316,7 +255,9 @@ class ContactsManager(ToxSave):
def is_active_online(self):
return self._active_contact + 1 and self.get_curr_contact().status is not None
# -----------------------------------------------------------------------------------------------------------------
# Work with friends (remove, block, set alias, get public key)
# -----------------------------------------------------------------------------------------------------------------
def set_alias(self, num):
"""
@ -358,10 +299,7 @@ class ContactsManager(ToxSave):
"""
friend = self._contacts[num]
self._cleanup_contact_data(friend)
try:
self._tox.friend_delete(friend.number)
except Exception as e:
LOG.warn(f"'There was no friend with the given friend number {e}")
self._tox.friend_delete(friend.number)
self._delete_contact(num)
def add_friend(self, tox_id):
@ -377,7 +315,7 @@ class ContactsManager(ToxSave):
Block user with specified tox id (or public key) - delete from friends list and ignore friend requests
"""
tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2]
if tox_id == self._tox.self_get_address()[:TOX_PUBLIC_KEY_SIZE * 2]:
if tox_id == self._tox.self_get_address[:TOX_PUBLIC_KEY_SIZE * 2]:
return
if tox_id not in self._settings['blocked']:
self._settings['blocked'].append(tox_id)
@ -401,27 +339,21 @@ class ContactsManager(ToxSave):
self.add_friend(tox_id)
self.save_profile()
# -----------------------------------------------------------------------------------------------------------------
# Groups support
# -----------------------------------------------------------------------------------------------------------------
def get_group_chats(self):
return list(filter(lambda c: type(c) is GroupChat, self._contacts))
def add_group(self, group_number):
index = len(self._contacts)
group = self._contact_provider.get_group_by_number(group_number)
if group is None:
LOG.warn(f"CM.add_group: NULL group from group_number={group_number}")
elif type(group) == int and group < 0:
LOG.warn(f"CM.add_group: NO group from group={group} group_number={group_number}")
else:
LOG.info(f"CM.add_group: Adding group {group._name}")
self._contacts.append(group)
LOG.info(f"contacts_manager.add_group: saving profile")
self._save_profile()
group.reset_avatar(self._settings['identicons'])
LOG.info(f"contacts_manager.add_group: setting active")
self.set_active(index)
self.update_filtration()
index = len(self._contacts)
self._contacts.append(group)
group.reset_avatar(self._settings['identicons'])
self._save_profile()
self.set_active(index)
self.update_filtration()
def delete_group(self, group_number):
group = self.get_group_by_number(group_number)
@ -429,31 +361,30 @@ class ContactsManager(ToxSave):
num = self._contacts.index(group)
self._delete_contact(num)
# -----------------------------------------------------------------------------------------------------------------
# Groups private messaging
# -----------------------------------------------------------------------------------------------------------------
def add_group_peer(self, group, peer):
contact = self._contact_provider.get_group_peer_by_id(group, peer.id)
if self.check_if_contact_exists(contact.tox_id):
return
contact._kind = 'grouppeer'
self._contacts.append(contact)
contact.reset_avatar(self._settings['identicons'])
self._save_profile()
def remove_group_peer_by_id(self, group, peer_id):
peer = group.get_peer_by_id(peer_id)
if peer: # broken
if not self.check_if_contact_exists(peer.public_key):
return
contact = self.get_contact_by_tox_id(peer.public_key)
self.remove_group_peer(contact)
if not self.check_if_contact_exists(peer.public_key):
return
contact = self.get_contact_by_tox_id(peer.public_key)
self.remove_group_peer(contact)
def remove_group_peer(self, group_peer_contact):
contact = self.get_contact_by_tox_id(group_peer_contact.tox_id)
if contact:
self._cleanup_contact_data(contact)
num = self._contacts.index(contact)
self._delete_contact(num)
self._cleanup_contact_data(contact)
num = self._contacts.index(contact)
self._delete_contact(num)
def get_gc_peer_name(self, name):
group = self.get_curr_contact()
@ -471,48 +402,38 @@ class ContactsManager(ToxSave):
return suggested_names[0]
# -----------------------------------------------------------------------------------------------------------------
# Friend requests
# -----------------------------------------------------------------------------------------------------------------
def send_friend_request(self, sToxPkOrId, message):
def send_friend_request(self, tox_id, message):
"""
Function tries to send request to contact with specified id
:param sToxPkOrId: id of new contact or tox dns 4 value
:param tox_id: id of new contact or tox dns 4 value
:param message: additional message
:return: True on success else error string
"""
retval = ''
try:
message = message or 'Hello! Add me to your contact list please'
if len(sToxPkOrId) == TOX_PUBLIC_KEY_SIZE * 2: # public key
self.add_friend(sToxPkOrId)
title = 'Friend added'
text = 'Friend added without sending friend request'
if '@' in tox_id: # value like groupbot@toxme.io
tox_id = self._tox_dns.lookup(tox_id)
if tox_id is None:
raise Exception('TOX DNS lookup failed')
if len(tox_id) == TOX_PUBLIC_KEY_SIZE * 2: # public key
self.add_friend(tox_id)
title = util_ui.tr('Friend added')
text = util_ui.tr('Friend added without sending friend request')
util_ui.message_box(text, title)
else:
num = self._tox.friend_add(sToxPkOrId, message.encode('utf-8'))
if num < UINT32_MAX:
tox_pk = sToxPkOrId[:TOX_PUBLIC_KEY_SIZE * 2]
self._add_friend(tox_pk)
self.update_filtration()
title = 'Friend added'
text = 'Friend added by sending friend request'
self.save_profile()
retval = True
else:
title = 'Friend failed'
text = 'Friend failed sending friend request'
retval = text
self._tox.friend_add(tox_id, message.encode('utf-8'))
tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2]
self._add_friend(tox_id)
self.update_filtration()
self.save_profile()
return True
except Exception as ex: # wrong data
title = 'Friend add exception'
text = 'Friend request exception with ' + str(ex)
self._log(text)
LOG.exception(text)
LOG.warn(f"DELETE {sToxPkOrId} ?")
retval = str(ex)
title = util_ui.tr(title)
text = util_ui.tr(text)
util_ui.message_box(text, title)
return retval
util.log('Friend request failed with ' + str(ex))
return str(ex)
def process_friend_request(self, tox_id, message):
"""
@ -530,12 +451,14 @@ class ContactsManager(ToxSave):
data = self._tox.get_savedata()
self._profile_manager.save_profile(data)
except Exception as ex: # something is wrong
LOG.error('Accept friend request failed! ' + str(ex))
util.log('Accept friend request failed! ' + str(ex))
def can_send_typing_notification(self):
return self._settings['typing_notifications'] and not self.is_active_a_group_chat_peer()
# -----------------------------------------------------------------------------------------------------------------
# Contacts numbers update
# -----------------------------------------------------------------------------------------------------------------
def update_friends_numbers(self):
for friend in self._contact_provider.get_all_friends():
@ -544,17 +467,9 @@ class ContactsManager(ToxSave):
def update_groups_numbers(self):
groups = self._contact_provider.get_all_groups()
LOG.info(f"update_groups_numbers len(groups)={len(groups)}")
# Thread 76 "ToxIterateThrea" received signal SIGSEGV, Segmentation fault.
for i in range(len(groups)):
chat_id = self._tox.group_get_chat_id(i)
if not chat_id:
LOG.warn(f"update_groups_numbers {i} chat_id")
continue
group = self.get_contact_by_tox_id(chat_id)
if not group:
LOG.warn(f"update_groups_numbers {i} group")
continue
group.number = i
self.update_filtration()
@ -563,22 +478,16 @@ class ContactsManager(ToxSave):
for group in groups:
group.remove_all_peers_except_self()
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _load_contacts(self):
self._load_friends()
self._load_groups()
if len(self._contacts):
self.set_active(0)
# filter(lambda c: not c.has_avatar(), self._contacts)
for (i, contact) in enumerate(self._contacts):
if contact is None:
LOG.warn(f"_load_contacts NULL contact {i}")
LOG.info(f"_load_contacts deleting NULL {self._contacts[i]}")
del self._contacts[i]
#? self.save_profile()
continue
if contact.has_avatar(): continue
for contact in filter(lambda c: not c.has_avatar(), self._contacts):
contact.reset_avatar(self._settings['identicons'])
self.update_filtration()
@ -588,7 +497,9 @@ class ContactsManager(ToxSave):
def _load_groups(self):
self._contacts.extend(self._contact_provider.get_all_groups())
# -----------------------------------------------------------------------------------------------------------------
# Current contact subscriptions
# -----------------------------------------------------------------------------------------------------------------
def _subscribe_to_events(self, contact):
contact.name_changed_event.add_callback(self._current_contact_name_changed)

View File

@ -14,7 +14,9 @@ class Friend(contact.Contact):
self._receipts = 0
self._typing_notification_handler = common.FriendTypingNotificationHandler(number)
# -----------------------------------------------------------------------------------------------------------------
# File transfers support
# -----------------------------------------------------------------------------------------------------------------
def insert_inline(self, before_message_id, inline):
"""
@ -50,17 +52,23 @@ class Friend(contact.Contact):
self._corr = list(filter(lambda m: not (type(m) is UnsentFileMessage and m.message_id == message_id),
self._corr))
# -----------------------------------------------------------------------------------------------------------------
# Full status
# -----------------------------------------------------------------------------------------------------------------
def get_full_status(self):
return self._status_message
# -----------------------------------------------------------------------------------------------------------------
# Typing notifications
# -----------------------------------------------------------------------------------------------------------------
def get_typing_notification_handler(self):
return self._typing_notification_handler
# -----------------------------------------------------------------------------------------------------------------
# Context menu support
# -----------------------------------------------------------------------------------------------------------------
def get_context_menu_generator(self):
return FriendMenuGenerator(self)

View File

@ -13,26 +13,28 @@ class FriendFactory(ToxSave):
def create_friend_by_public_key(self, public_key):
friend_number = self._tox.friend_by_public_key(public_key)
return self.create_friend_by_number(friend_number)
def create_friend_by_number(self, friend_number):
aliases = self._settings['friends_aliases']
sToxPk = self._tox.friend_get_public_key(friend_number)
assert sToxPk, sToxPk
tox_id = self._tox.friend_get_public_key(friend_number)
try:
alias = list(filter(lambda x: x[0] == sToxPk, aliases))[0][1]
alias = list(filter(lambda x: x[0] == tox_id, aliases))[0][1]
except:
alias = ''
item = self._create_friend_item()
name = alias or self._tox.friend_get_name(friend_number) or sToxPk
name = alias or self._tox.friend_get_name(friend_number) or tox_id
status_message = self._tox.friend_get_status_message(friend_number)
message_getter = self._db.messages_getter(sToxPk)
friend = Friend(self._profile_manager, message_getter, friend_number, name, status_message, item, sToxPk)
message_getter = self._db.messages_getter(tox_id)
friend = Friend(self._profile_manager, message_getter, friend_number, name, status_message, item, tox_id)
friend.set_alias(alias)
return friend
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _create_friend_item(self):
"""

View File

@ -1,5 +1,3 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
from contacts import contact
from contacts.contact_menu import GroupMenuGenerator
import utils.util as util
@ -8,14 +6,6 @@ from wrapper import toxcore_enums_and_consts as constants
from common.tox_save import ToxSave
from groups.group_ban import GroupBan
global LOG
import logging
LOG = logging.getLogger(__name__)
def LOG_ERROR(l): print('ERROR_: '+l)
def LOG_WARN(l): print('WARN_: '+l)
def LOG_INFO(l): print('INFO_: '+l)
def LOG_DEBUG(l): print('DEBUG_: '+l)
def LOG_TRACE(l): pass # print('TRACE+ '+l)
class GroupChat(contact.Contact, ToxSave):
@ -35,7 +25,9 @@ class GroupChat(contact.Contact, ToxSave):
def get_context_menu_generator(self):
return GroupMenuGenerator(self)
# -----------------------------------------------------------------------------------------------------------------
# Properties
# -----------------------------------------------------------------------------------------------------------------
def get_is_private(self):
return self._is_private
@ -61,7 +53,9 @@ class GroupChat(contact.Contact, ToxSave):
peers_limit = property(get_peers_limit, set_peers_limit)
# -----------------------------------------------------------------------------------------------------------------
# Peers methods
# -----------------------------------------------------------------------------------------------------------------
def get_self_peer(self):
return self._peers[0]
@ -79,12 +73,6 @@ class GroupChat(contact.Contact, ToxSave):
return self.get_self_role() == constants.TOX_GROUP_ROLE['FOUNDER']
def add_peer(self, peer_id, is_current_user=False):
"called from callbacks"
if peer_id > self._peers_limit:
LOG_WARN(f"add_peer id={peer_id} > {self._peers_limit}")
return
LOG_TRACE(f"add_peer id={peer_id}")
peer = GroupChatPeer(peer_id,
self._tox.group_peer_get_name(self._number, peer_id),
self._tox.group_peer_get_status(self._number, peer_id),
@ -98,40 +86,25 @@ class GroupChat(contact.Contact, ToxSave):
self.remove_all_peers_except_self()
else:
peer = self.get_peer_by_id(peer_id)
if peer: # broken
self._peers.remove(peer)
else:
LOG_WARN(f"remove_peer empty peers for {peer_id}")
self._peers.remove(peer)
def get_peer_by_id(self, peer_id):
peers = list(filter(lambda p: p.id == peer_id, self._peers))
if peers:
return peers[0]
else:
LOG_WARN(f"get_peer_by_id empty peers for {peer_id}")
return []
return peers[0]
def get_peer_by_public_key(self, public_key):
peers = list(filter(lambda p: p.public_key == public_key, self._peers))
# DEBUGc: group_moderation #0 mod_id=4294967295 event_type=3
# WARN_: get_peer_by_id empty peers for 4294967295
if peers:
return peers[0]
else:
LOG_WARN(f"get_peer_by_public_key empty peers for {public_key}")
return []
return peers[0]
def remove_all_peers_except_self(self):
self._peers = self._peers[:1]
def get_peers_names(self):
peers_names = map(lambda p: p.name, self._peers)
if peers_names: # broken
return list(peers_names)
else:
LOG_WARN(f"get_peers_names empty peers")
#? broken
return []
return list(peers_names)
def get_peers(self):
return self._peers[:]
@ -139,20 +112,21 @@ class GroupChat(contact.Contact, ToxSave):
peers = property(get_peers)
def get_bans(self):
return []
# ban_ids = self._tox.group_ban_get_list(self._number)
# bans = []
# for ban_id in ban_ids:
# ban = GroupBan(ban_id,
# self._tox.group_ban_get_target(self._number, ban_id),
# self._tox.group_ban_get_time_set(self._number, ban_id))
# bans.append(ban)
#
# return bans
#
ban_ids = self._tox.group_ban_get_list(self._number)
bans = []
for ban_id in ban_ids:
ban = GroupBan(ban_id,
self._tox.group_ban_get_target(self._number, ban_id),
self._tox.group_ban_get_time_set(self._number, ban_id))
bans.append(ban)
return bans
bans = property(get_bans)
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
@staticmethod
def _get_default_avatar_path():

View File

@ -1,12 +1,7 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
from contacts.group_chat import GroupChat
from common.tox_save import ToxSave
import wrapper.toxcore_enums_and_consts as constants
global LOG
import logging
LOG = logging.getLogger(__name__)
class GroupFactory(ToxSave):
@ -17,15 +12,12 @@ class GroupFactory(ToxSave):
self._db = db
self._items_factory = items_factory
def create_group_by_chat_id(self, chat_id):
return self.create_group_by_public_key(chat_id)
def create_group_by_public_key(self, public_key):
group_number = self._get_group_number_by_chat_id(public_key)
return self.create_group_by_number(group_number)
def create_group_by_number(self, group_number):
LOG.info(f"create_group_by_number {group_number}")
aliases = self._settings['friends_aliases']
tox_id = self._tox.group_get_chat_id(group_number)
try:
@ -43,7 +35,9 @@ class GroupFactory(ToxSave):
return group
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _create_group_item(self):
"""
@ -53,7 +47,7 @@ class GroupFactory(ToxSave):
return self._items_factory.create_contact_item()
def _get_group_number_by_chat_id(self, chat_id):
for i in range(self._tox.group_get_number_groups()+100):
for i in range(self._tox.group_get_number_groups()):
if self._tox.group_get_chat_id(i) == chat_id:
return i
return -1

View File

@ -1,15 +1,9 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
from contacts import basecontact
import random
import threading
import common.tox_save as tox_save
from middleware.threads import invoke_in_main_thread
iUMAXINT = 4294967295
global LOG
import logging
LOG = logging.getLogger('app.'+__name__)
class Profile(basecontact.BaseContact, tox_save.ToxSave):
"""
@ -20,7 +14,6 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave):
:param tox: tox instance
:param screen: ref to main screen
"""
assert tox
basecontact.BaseContact.__init__(self,
profile_manager,
tox.self_get_name(),
@ -35,7 +28,9 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave):
self._waiting_for_reconnection = False
self._timer = None
# -----------------------------------------------------------------------------------------------------------------
# Edit current user's data
# -----------------------------------------------------------------------------------------------------------------
def change_status(self):
"""
@ -65,12 +60,14 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave):
def set_new_nospam(self):
"""Sets new nospam part of tox id"""
self._tox.self_set_nospam(random.randint(0, iUMAXINT)) # no spam - uint32
self._tox.self_set_nospam(random.randint(0, 4294967295)) # no spam - uint32
self._tox_id = self._tox.self_get_address()
self._sToxId = self._tox.self_get_address()
return self._sToxId
return self._tox_id
# -----------------------------------------------------------------------------------------------------------------
# Reset
# -----------------------------------------------------------------------------------------------------------------
def restart(self):
"""

View File

@ -1,11 +1,11 @@
from os import chdir, remove, rename
from wrapper.toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
from os.path import basename, getsize, exists, dirname
from os import remove, rename, chdir
from time import time
from wrapper.tox import Tox
from common.event import Event
from middleware.threads import invoke_in_main_thread
from wrapper.tox import Tox
from wrapper.toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
FILE_TRANSFER_STATE = {
'RUNNING': 0,
@ -78,9 +78,6 @@ class FileTransfer:
def get_file_id(self):
return self._file_id
# WTF
def get_file_id(self):
return self._tox.file_get_file_id(self._friend_number, self._file_number)
file_id = property(get_file_id)
@ -115,6 +112,9 @@ class FileTransfer:
if self._tox.file_control(self._friend_number, self._file_number, control):
self.set_state(control)
def get_file_id(self):
return self._tox.file_get_file_id(self._friend_number, self._file_number)
def _signal(self):
percentage = self._done / self._size if self._size else 0
if self._creation_time is None or not percentage:
@ -126,7 +126,9 @@ class FileTransfer:
def _finished(self):
self._finished_event(self._friend_number, self._file_number)
# -----------------------------------------------------------------------------------------------------------------
# Send file
# -----------------------------------------------------------------------------------------------------------------
class SendTransfer(FileTransfer):
@ -221,7 +223,9 @@ class SendFromFileBuffer(SendTransfer):
chdir(dirname(self._path))
remove(self._path)
# -----------------------------------------------------------------------------------------------------------------
# Receive file
# -----------------------------------------------------------------------------------------------------------------
class ReceiveTransfer(FileTransfer):

View File

@ -1,19 +1,11 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
from messenger.messages import *
from ui.contact_items import *
import utils.util as util
from common.tox_save import ToxSave
from wrapper_tests.support_testing import assert_main_thread
from copy import deepcopy
# LOG=util.log
global LOG
import logging
LOG = logging.getLogger('app.'+__name__)
log = lambda x: LOG.info(x)
class FileTransfersHandler(ToxSave):
lBlockAvatars = []
def __init__(self, tox, settings, contact_provider, file_transfers_message_service, profile):
super().__init__(tox)
self._settings = settings
@ -27,13 +19,14 @@ class FileTransfersHandler(ToxSave):
# key = (friend number, file number), value - message id
profile.avatar_changed_event.add_callback(self._send_avatar_to_contacts)
self. lBlockAvatars = []
def stop(self):
self._settings['paused_file_transfers'] = self._paused_file_transfers if self._settings['resend_files'] else {}
self._settings.save()
# -----------------------------------------------------------------------------------------------------------------
# File transfers support
# -----------------------------------------------------------------------------------------------------------------
def incoming_file_transfer(self, friend_number, file_number, size, file_name):
"""
@ -44,7 +37,6 @@ class FileTransfersHandler(ToxSave):
:param file_name: file name without path
"""
friend = self._get_friend_by_number(friend_number)
if friend is None: return None
auto = self._settings['allow_auto_accept'] and friend.tox_id in self._settings['auto_accept_from_friends']
inline = is_inline(file_name) and self._settings['allow_inline']
file_id = self._tox.file_get_file_id(friend_number, file_number)
@ -93,9 +85,7 @@ class FileTransfersHandler(ToxSave):
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
def cancel_not_started_transfer(self, friend_number, message_id):
friend = self._get_friend_by_number(friend_number)
if friend is None: return None
friend.delete_one_unsent_file(message_id)
self._get_friend_by_number(friend_number).delete_one_unsent_file(message_id)
def pause_transfer(self, friend_number, file_number, by_friend=False):
"""
@ -125,7 +115,6 @@ class FileTransfersHandler(ToxSave):
"""
path = self._generate_valid_path(path, from_position)
friend = self._get_friend_by_number(friend_number)
if friend is None: return None
if not inline:
rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number, from_position)
else:
@ -156,7 +145,6 @@ class FileTransfersHandler(ToxSave):
def send_inline(self, data, file_name, friend_number, is_resend=False):
friend = self._get_friend_by_number(friend_number)
if friend is None: return None
if friend.status is None and not is_resend:
self._file_transfers_message_service.add_unsent_file_message(friend, file_name, data)
return
@ -174,12 +162,11 @@ class FileTransfersHandler(ToxSave):
:param file_id: file id of transfer
"""
friend = self._get_friend_by_number(friend_number)
if friend is None: return None
if friend.status is None and not is_resend:
self._file_transfers_message_service.add_unsent_file_message(friend, path, None)
return
elif friend.status is None and is_resend:
LOG.error('Error in sending')
print('Error in sending')
return
st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id)
file_name = os.path.basename(path)
@ -199,27 +186,23 @@ class FileTransfersHandler(ToxSave):
def transfer_finished(self, friend_number, file_number):
transfer = self._file_transfers[(friend_number, file_number)]
friend = self._get_friend_by_number(friend_number)
if friend is None: return None
t = type(transfer)
if t is ReceiveAvatar:
friend.load_avatar()
self._get_friend_by_number(friend_number).load_avatar()
elif t is ReceiveToBuffer or (t is SendFromBuffer and self._settings['allow_inline']): # inline image
LOG.debug('inline')
print('inline')
inline = InlineImageMessage(transfer.data)
message_id = self._insert_inline_before[(friend_number, file_number)]
del self._insert_inline_before[(friend_number, file_number)]
if friend is None: return None
index = friend.insert_inline(message_id, inline)
index = self._get_friend_by_number(friend_number).insert_inline(message_id, inline)
self._file_transfers_message_service.add_inline_message(transfer, index)
del self._file_transfers[(friend_number, file_number)]
def send_files(self, friend_number):
friend = self._get_friend_by_number(friend_number)
friend.remove_invalid_unsent_files()
files = friend.get_unsent_files()
try:
friend = self._get_friend_by_number(friend_number)
if friend is None: return None
friend.remove_invalid_unsent_files()
files = friend.get_unsent_files()
for fl in files:
data, path = fl.data, fl.path
if data is not None:
@ -228,7 +211,6 @@ class FileTransfersHandler(ToxSave):
self.send_file(path, friend_number, True)
friend.clear_unsent_files()
for key in self._paused_file_transfers.keys():
# RuntimeError: dictionary changed size during iteration
(path, ft_friend_number, is_incoming, start_position) = self._paused_file_transfers[key]
if not os.path.exists(path):
del self._paused_file_transfers[key]
@ -236,16 +218,12 @@ class FileTransfersHandler(ToxSave):
self.send_file(path, friend_number, True, key)
del self._paused_file_transfers[key]
except Exception as ex:
LOG.error('Exception in file sending: ' + str(ex))
print('Exception in file sending: ' + str(ex))
def friend_exit(self, friend_number):
# RuntimeError: dictionary changed size during iteration
lMayChangeDynamically = self._file_transfers.copy()
for friend_num, file_num in lMayChangeDynamically:
for friend_num, file_num in self._file_transfers.keys():
if friend_num != friend_number:
continue
if (friend_num, file_num) not in self._file_transfers:
continue
ft = self._file_transfers[(friend_num, file_num)]
if type(ft) is SendTransfer:
self._paused_file_transfers[ft.file_id] = [ft.path, friend_num, False, -1]
@ -253,23 +231,17 @@ class FileTransfersHandler(ToxSave):
self._paused_file_transfers[ft.file_id] = [ft.path, friend_num, True, ft.total_size()]
self.cancel_transfer(friend_num, file_num, True)
# -----------------------------------------------------------------------------------------------------------------
# Avatars support
# -----------------------------------------------------------------------------------------------------------------
def send_avatar(self, friend_number, avatar_path=None):
"""
:param friend_number: number of friend who should get new avatar
:param avatar_path: path to avatar or None if reset
"""
if (avatar_path, friend_number,) in self.lBlockAvatars:
return
try:
sa = SendAvatar(avatar_path, self._tox, friend_number)
self._file_transfers[(friend_number, sa.file_number)] = sa
except Exception as e:
# ArgumentError('This client is currently not connected to the friend.')
LOG.error(f"send_avatar {e}")
self.lBlockAvatars.append( (avatar_path, friend_number,) )
sa = SendAvatar(avatar_path, self._tox, friend_number)
self._file_transfers[(friend_number, sa.file_number)] = sa
def incoming_avatar(self, friend_number, file_number, size):
"""
@ -279,7 +251,6 @@ class FileTransfersHandler(ToxSave):
:param size: size of avatar or 0 (default avatar)
"""
friend = self._get_friend_by_number(friend_number)
if friend is None: return None
ra = ReceiveAvatar(friend.get_contact_avatar_path(), self._tox, friend_number, size, file_number)
if ra.state != FILE_TRANSFER_STATE['CANCELLED']:
self._file_transfers[(friend_number, file_number)] = ra
@ -288,16 +259,16 @@ class FileTransfersHandler(ToxSave):
friend.reset_avatar(self._settings['identicons'])
def _send_avatar_to_contacts(self, _):
# from a callback
friends = self._get_all_friends()
for friend in filter(self._is_friend_online, friends):
self.send_avatar(friend.number)
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _is_friend_online(self, friend_number):
friend = self._get_friend_by_number(friend_number)
if friend is None: return None
return friend.status is not None

View File

@ -2,15 +2,6 @@ from messenger.messenger import *
import utils.util as util
from file_transfers.file_transfers import *
global LOG
import logging
LOG = logging.getLogger('app.'+__name__)
def LOG_ERROR(l): print('ERROR_: '+l)
def LOG_WARN(l): print('WARN_: '+l)
def LOG_INFO(l): print('INFO_: '+l)
def LOG_DEBUG(l): print('DEBUG_: '+l)
def LOG_TRACE(l): pass # print('TRACE+ '+l)
class FileTransfersMessagesService:
@ -21,7 +12,6 @@ class FileTransfersMessagesService:
self._messages = main_screen.messages
def add_incoming_transfer_message(self, friend, accepted, size, file_name, file_number):
assert friend
author = MessageAuthor(friend.name, MESSAGE_AUTHOR['FRIEND'])
status = FILE_TRANSFER_STATE['RUNNING'] if accepted else FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']
tm = TransferMessage(author, util.get_unix_time(), status, size, file_name, friend.number, file_number)
@ -37,7 +27,6 @@ class FileTransfersMessagesService:
return tm
def add_outgoing_transfer_message(self, friend, size, file_name, file_number):
assert friend
author = MessageAuthor(self._profile.name, MESSAGE_AUTHOR['ME'])
status = FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']
tm = TransferMessage(author, util.get_unix_time(), status, size, file_name, friend.number, file_number)
@ -51,19 +40,13 @@ class FileTransfersMessagesService:
return tm
def add_inline_message(self, transfer, index):
"""callback"""
if not self._is_friend_active(transfer.friend_number):
return
if transfer is None or not hasattr(transfer, 'data') or \
not transfer.data:
LOG_ERROR(f"add_inline_message empty data")
return
count = self._messages.count()
if count + index + 1 >= 0:
self._create_inline_item(transfer.data, count + index + 1)
def add_unsent_file_message(self, friend, file_path, data):
assert friend
author = MessageAuthor(self._profile.name, MESSAGE_AUTHOR['ME'])
size = os.path.getsize(file_path) if data is None else len(data)
tm = UnsentFileMessage(file_path, data, util.get_unix_time(), author, size, friend.number)
@ -75,7 +58,9 @@ class FileTransfersMessagesService:
return tm
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _is_friend_active(self, friend_number):
if not self._contacts_manager.is_active_a_friend():

View File

@ -13,9 +13,10 @@ class GroupChatPeer:
self._public_key = public_key
self._is_current_user = is_current_user
self._is_muted = is_muted
# unused?
self._kind = 'grouppeer'
# -----------------------------------------------------------------------------------------------------------------
# Readonly properties
# -----------------------------------------------------------------------------------------------------------------
def get_id(self):
return self._peer_id
@ -32,7 +33,9 @@ class GroupChatPeer:
is_current_user = property(get_is_current_user)
# -----------------------------------------------------------------------------------------------------------------
# Read-write properties
# -----------------------------------------------------------------------------------------------------------------
def get_name(self):
return self._name

View File

@ -1,16 +1,9 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import common.tox_save as tox_save
import utils.ui as util_ui
from groups.peers_list import PeersListGenerator
from groups.group_invite import GroupInvite
import wrapper.toxcore_enums_and_consts as constants
from wrapper.toxcore_enums_and_consts import *
from wrapper.tox import UINT32_MAX
global LOG
import logging
LOG = logging.getLogger('app.'+'gs')
class GroupsService(tox_save.ToxSave):
@ -23,22 +16,18 @@ class GroupsService(tox_save.ToxSave):
self._widgets_factory_provider = widgets_factory_provider
self._group_invites = []
self._screen = None
# maybe just use self
self._tox = tox
def set_tox(self, tox):
super().set_tox(tox)
for group in self._get_all_groups():
group.set_tox(tox)
# -----------------------------------------------------------------------------------------------------------------
# Groups creation
# -----------------------------------------------------------------------------------------------------------------
def create_new_gc(self, name, privacy_state, nick, status):
try:
group_number = self._tox.group_new(privacy_state, name, nick, status)
except Exception as e:
LOG.error(f"create_new_gc {e}")
return
group_number = self._tox.group_new(privacy_state, name, nick, status)
if group_number == -1:
return
@ -48,36 +37,16 @@ class GroupsService(tox_save.ToxSave):
self._contacts_manager.update_filtration()
def join_gc_by_id(self, chat_id, password, nick, status):
try:
group_number = self._tox.group_join(chat_id, password, nick, status)
assert type(group_number) == int, group_number
assert group_number < UINT32_MAX, group_number
except Exception as e:
# gui
title = f"join_gc_by_id {chat_id}"
util_ui.message_box(title +'\n' +str(e), title)
LOG.error(f"_join_gc_via_id {e}")
return
LOG.debug(f"_join_gc_via_id {group_number}")
group_number = self._tox.group_join(chat_id, password, nick, status)
self._add_new_group_by_number(group_number)
group = self._get_group_by_number(group_number)
try:
assert group and hasattr(group, 'status')
except Exception as e:
# gui
title = f"join_gc_by_id {chat_id}"
util_ui.message_box(title +'\n' +str(e), title)
LOG.error(f"_join_gc_via_id {e}")
return
group.status = constants.TOX_USER_STATUS['NONE']
self._contacts_manager.update_filtration()
# -----------------------------------------------------------------------------------------------------------------
# Groups reconnect and leaving
# -----------------------------------------------------------------------------------------------------------------
def leave_group(self, group_number):
if type(group_number) == int:
self._tox.group_leave(group_number)
self._contacts_manager.delete_group(group_number)
self._tox.group_leave(group_number)
self._contacts_manager.delete_group(group_number)
def disconnect_from_group(self, group_number):
self._tox.group_disconnect(group_number)
@ -91,25 +60,15 @@ class GroupsService(tox_save.ToxSave):
group.status = constants.TOX_USER_STATUS['NONE']
self._clear_peers_list(group)
# -----------------------------------------------------------------------------------------------------------------
# Group invites
# -----------------------------------------------------------------------------------------------------------------
def invite_friend(self, friend_number, group_number):
if self._tox.friend_get_connection_status(friend_number) == TOX_CONNECTION['NONE']:
title = f"Error in group_invite_friend {friend_number}"
e = f"Friend not connected friend_number={friend_number}"
util_ui.message_box(title +'\n' +str(e), title)
return
try:
self._tox.group_invite_friend(group_number, friend_number)
except Exception as e:
title = f"Error in group_invite_friend {group_number} {friend_number}"
util_ui.message_box(title +'\n' +str(e), title)
self._tox.group_invite_friend(group_number, friend_number)
def process_group_invite(self, friend_number, group_name, invite_data):
friend = self._get_friend_by_number(friend_number)
# binary {invite_data}
LOG.debug(f"process_group_invite {friend_number} {group_name}")
invite = GroupInvite(friend.tox_id, group_name, invite_data)
self._group_invites.append(invite)
self._update_invites_button_state()
@ -117,7 +76,6 @@ class GroupsService(tox_save.ToxSave):
def accept_group_invite(self, invite, name, status, password):
pk = invite.friend_public_key
friend = self._get_friend_by_public_key(pk)
LOG.debug(f"accept_group_invite {name}")
self._join_gc_via_invite(invite.invite_data, friend.number, name, status, password)
self._delete_group_invite(invite)
self._update_invites_button_state()
@ -136,7 +94,9 @@ class GroupsService(tox_save.ToxSave):
group_invites_count = property(get_group_invites_count)
# -----------------------------------------------------------------------------------------------------------------
# Group info methods
# -----------------------------------------------------------------------------------------------------------------
def update_group_info(self, group):
group.name = self._tox.group_get_name(group.number)
@ -182,7 +142,9 @@ class GroupsService(tox_save.ToxSave):
self._tox.group_founder_set_privacy_state(group.number, privacy_state)
group.is_private = is_private
# -----------------------------------------------------------------------------------------------------------------
# Peers list
# -----------------------------------------------------------------------------------------------------------------
def generate_peers_list(self):
if not self._contacts_manager.is_active_a_group():
@ -200,7 +162,9 @@ class GroupsService(tox_save.ToxSave):
self._screen = widgets_factory.create_self_peer_screen_window(group)
self._screen.show()
# -----------------------------------------------------------------------------------------------------------------
# Peers actions
# -----------------------------------------------------------------------------------------------------------------
def set_new_peer_role(self, group, peer, role):
self._tox.group_mod_set_role(group.number, peer.id, role)
@ -219,10 +183,11 @@ class GroupsService(tox_save.ToxSave):
self_peer.status = status
self.generate_peers_list()
# -----------------------------------------------------------------------------------------------------------------
# Bans support
# -----------------------------------------------------------------------------------------------------------------
def show_bans_list(self, group):
return
widgets_factory = self._get_widgets_factory()
self._screen = widgets_factory.create_groups_bans_screen(group)
self._screen.show()
@ -236,10 +201,11 @@ class GroupsService(tox_save.ToxSave):
def cancel_ban(self, group_number, ban_id):
self._tox.group_mod_remove_ban(group_number, ban_id)
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _add_new_group_by_number(self, group_number):
LOG.debug(f"_add_new_group_by_number group_number={group_number}")
self._contacts_manager.add_group(group_number)
def _get_group_by_number(self, group_number):
@ -265,24 +231,9 @@ class GroupsService(tox_save.ToxSave):
if invite in self._group_invites:
self._group_invites.remove(invite)
# status should be dropped
def _join_gc_via_invite(self, invite_data, friend_number, nick, status='', password=''):
LOG.debug(f"_join_gc_via_invite friend_number={friend_number} nick={nick} datalen={len(invite_data)}")
if nick is None:
nick = ''
if invite_data is None:
invite_data = b''
try:
# status should be dropped
group_number = self._tox.group_invite_accept(invite_data, friend_number, nick, password=password)
except Exception as e:
LOG.error(f"_join_gc_via_invite ERROR {e}")
return
try:
self._add_new_group_by_number(group_number)
except Exception as e:
LOG.error(f"_join_gc_via_invite group_number={group_number} {e}")
return
def _join_gc_via_invite(self, invite_data, friend_number, nick, status, password):
group_number = self._tox.group_invite_accept(invite_data, friend_number, nick, status, password)
self._add_new_group_by_number(group_number)
def _update_invites_button_state(self):
self._main_screen.update_gc_invites_button_state()

View File

@ -3,7 +3,9 @@ from wrapper.toxcore_enums_and_consts import *
from ui.widgets import *
# -----------------------------------------------------------------------------------------------------------------
# Builder
# -----------------------------------------------------------------------------------------------------------------
class PeerListBuilder:
@ -61,7 +63,9 @@ class PeerListBuilder:
self._peers[self._index] = peer
self._index += 1
# -----------------------------------------------------------------------------------------------------------------
# Generators
# -----------------------------------------------------------------------------------------------------------------
class PeersListGenerator:

View File

@ -1,20 +1,19 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
from sqlite3 import connect
import os.path
import utils.util as util
global LOG
import logging
LOG = logging.getLogger('app.db')
TIMEOUT = 11
SAVE_MESSAGES = 500
MESSAGE_AUTHOR = {
'ME': 0,
'FRIEND': 1,
'NOT_SENT': 2,
'GC_PEER': 3
}
CONTACT_TYPE = {
'FRIEND': 0,
'GC_PEER': 1,
@ -25,33 +24,23 @@ CONTACT_TYPE = {
class Database:
def __init__(self, path, toxes):
self._path = path
self._toxes = toxes
self._path, self._toxes = path, toxes
self._name = os.path.basename(path)
if os.path.exists(path):
try:
with open(path, 'rb') as fin:
data = fin.read()
if toxes.is_data_encrypted(data):
data = toxes.pass_decrypt(data)
with open(path, 'wb') as fout:
fout.write(data)
except Exception as ex:
util.log('Db reading error: ' + str(ex))
os.remove(path)
def open(self):
path = self._path
toxes = self._toxes
if not os.path.exists(path):
LOG.warn('Db not found: ' +path)
return
try:
with open(path, 'rb') as fin:
data = fin.read()
except Exception as ex:
LOG.error('Db reading error: ' +path +' ' +str(ex))
raise
try:
if toxes.is_data_encrypted(data):
data = toxes.pass_decrypt(data)
with open(path, 'wb') as fout:
fout.write(data)
except Exception as ex:
LOG.error('Db writing error: ' +path +' ' + str(ex))
os.remove(path)
LOG.info('Db opened: ' +path)
# -----------------------------------------------------------------------------------------------------------------
# Public methods
# -----------------------------------------------------------------------------------------------------------------
def save(self):
if self._toxes.has_password():
@ -69,7 +58,6 @@ class Database:
data = self._toxes.pass_encrypt(data)
with open(new_path, 'wb') as fout:
fout.write(data)
LOG.info('Db exported: ' +new_path)
def add_friend_to_db(self, tox_id):
db = self._connect()
@ -84,14 +72,11 @@ class Database:
' message_type INTEGER'
')')
db.commit()
return True
except Exception as e:
LOG.error("dd_friend_to_db " +self._name +' Database exception! ' +str(e))
except:
print('Database is locked!')
db.rollback()
return False
finally:
db.close()
LOG.debug(f"add_friend_to_db {tox_id}")
def delete_friend_from_db(self, tox_id):
db = self._connect()
@ -99,14 +84,11 @@ class Database:
cursor = db.cursor()
cursor.execute('DROP TABLE id' + tox_id + ';')
db.commit()
return True
except Exception as e:
LOG.error("delete_friend_from_db " +self._name +' Database exception! ' +str(e))
except:
print('Database is locked!')
db.rollback()
return False
finally:
db.close()
LOG.debug(f"delete_friend_from_db {tox_id}")
def save_messages_to_db(self, tox_id, messages_iter):
db = self._connect()
@ -114,16 +96,13 @@ class Database:
cursor = db.cursor()
cursor.executemany('INSERT INTO id' + tox_id +
'(message, author_name, author_type, unix_time, message_type) ' +
'VALUES (?, ?, ?, ?, ?);', messages_iter)
'VALUES (?, ?, ?, ?, ?, ?);', messages_iter)
db.commit()
return True
except Exception as e:
LOG.error("" +self._name +' Database exception! ' +str(e))
except:
print('Database is locked!')
db.rollback()
return False
finally:
db.close()
LOG.debug(f"save_messages_to_db {tox_id}")
def update_messages(self, tox_id, message_id):
db = self._connect()
@ -132,14 +111,11 @@ class Database:
cursor.execute('UPDATE id' + tox_id + ' SET author = 0 '
'WHERE id = ' + str(message_id) + ' AND author = 2;')
db.commit()
return True
except Exception as e:
LOG.error("" +self._name +' Database exception! ' +str(e))
except:
print('Database is locked!')
db.rollback()
return False
finally:
db.close()
LOG.debug(f"update_messages {tox_id}")
def delete_message(self, tox_id, unique_id):
db = self._connect()
@ -147,14 +123,11 @@ class Database:
cursor = db.cursor()
cursor.execute('DELETE FROM id' + tox_id + ' WHERE id = ' + str(unique_id) + ';')
db.commit()
return True
except Exception as e:
LOG.error("" +self._name +' Database exception! ' +str(e))
except:
print('Database is locked!')
db.rollback()
return False
finally:
db.close()
LOG.debug(f"delete_message {tox_id}")
def delete_messages(self, tox_id):
db = self._connect()
@ -162,21 +135,20 @@ class Database:
cursor = db.cursor()
cursor.execute('DELETE FROM id' + tox_id + ';')
db.commit()
return True
except Exception as e:
LOG.error("" +self._name +' Database exception! ' +str(e))
except:
print('Database is locked!')
db.rollback()
return False
finally:
db.close()
LOG.debug(f"delete_messages {tox_id}")
def messages_getter(self, tox_id):
self.add_friend_to_db(tox_id)
return Database.MessageGetter(self._path, tox_id)
# -----------------------------------------------------------------------------------------------------------------
# Messages loading
# -----------------------------------------------------------------------------------------------------------------
class MessageGetter:
@ -221,7 +193,9 @@ class Database:
def _disconnect(self):
self._db.close()
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _connect(self):
return connect(self._path, timeout=TIMEOUT)

View File

@ -1,9 +1,5 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
from history.history_logs_generators import *
global LOG
import logging
LOG = logging.getLogger('app.db')
class History:
@ -15,21 +11,22 @@ class History:
self._messages_items_factory = messages_items_factory
self._is_loading = False
self._contacts_manager = None
def __del__(self):
del self._db
def set_contacts_manager(self, contacts_manager):
self._contacts_manager = contacts_manager
# -----------------------------------------------------------------------------------------------------------------
# History support
# -----------------------------------------------------------------------------------------------------------------
def save_history(self):
"""
Save history to db
"""
# me a mistake? was _db not _history
if self._settings['save_history']:
if self._settings['save_db']:
for friend in self._contact_provider.get_all_friends():
self._db.add_friend_to_db(friend.tox_id)
if not self._settings['save_unsent_only']:
@ -60,10 +57,8 @@ class History:
file_name += '.' + extension
history = self.generate_history(contact, as_text)
assert history
with open(file_name, 'wt') as fl:
fl.write(history)
LOG.info(f"wrote history to {file_name}")
def delete_message(self, message):
contact = self._contacts_manager.get_curr_contact()
@ -126,7 +121,9 @@ class History:
return generator.generate()
# -----------------------------------------------------------------------------------------------------------------
# Items creation
# -----------------------------------------------------------------------------------------------------------------
def _create_message_item(self, message):
return self._messages_items_factory.create_message_item(message, False)

View File

@ -1,5 +1,5 @@
import utils.util as util
from messenger.messages import *
import utils.util as util
class HistoryLogsGenerator:

BIN
toxygen/images/accept.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 114 KiB

BIN
toxygen/images/accept_audio.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
toxygen/images/accept_video.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
toxygen/images/avatar.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 433 B

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 556 B

After

Width:  |  Height:  |  Size: 609 B

BIN
toxygen/images/call.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 816 B

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
toxygen/images/call_video.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
toxygen/images/decline.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 118 KiB

BIN
toxygen/images/decline_call.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 12 KiB

BIN
toxygen/images/file.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
toxygen/images/finish_call.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 816 B

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
toxygen/images/finish_call_video.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 B

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

BIN
toxygen/images/icon_new_messages.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 911 B

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 B

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 474 B

After

Width:  |  Height:  |  Size: 405 B

BIN
toxygen/images/incoming_call.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 816 B

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
toxygen/images/incoming_call_video.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 B

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
toxygen/images/menu.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 B

After

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 489 B

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 B

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 B

After

Width:  |  Height:  |  Size: 351 B

BIN
toxygen/images/pause.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 427 B

After

Width:  |  Height:  |  Size: 306 B

BIN
toxygen/images/resume.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
toxygen/images/screenshot.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 656 B

After

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 865 B

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
toxygen/images/send.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
toxygen/images/smiley.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
toxygen/images/sticker.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

BIN
toxygen/images/typing.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -1,357 +1,51 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import sys
import os
import app
import argparse
import logging
import signal
import faulthandler
faulthandler.enable()
import warnings
warnings.filterwarnings('ignore')
import wrapper_tests.support_testing as ts
try:
from trepan.interfaces import server as Mserver
from trepan.api import debug
except:
print('trepan3 TCP server NOT enabled.')
else:
import signal
try:
signal.signal(signal.SIGUSR1, ts.trepan_handler)
print('trepan3 TCP server enabled on port 6666.')
except: pass
from user_data.settings import *
from user_data.settings import Settings
from user_data import settings
import utils.util as util
with ts.ignoreStderr():
import pyaudio
import argparse
__maintainer__ = 'Ingvar'
__version__ = '0.5.0+'
__version__ = '0.5.0'
import time
sleep = time.sleep
def reset():
Settings.reset_auto_profile()
def clean():
"""Removes libs folder"""
directory = util.get_libs_directory()
util.remove(directory)
def reset():
Settings.reset_auto_profile()
def print_toxygen_version():
print('Toxygen ' + __version__)
print('Toxygen v' + __version__)
def setup_default_audio():
# need:
audio = ts.get_audio()
# unfinished
global oPYA
oPYA = pyaudio.PyAudio()
audio['output_devices'] = dict()
i = oPYA.get_device_count()
while i > 0:
i -= 1
if oPYA.get_device_info_by_index(i)['maxOutputChannels'] == 0:
continue
audio['output_devices'][i] = oPYA.get_device_info_by_index(i)['name']
i = oPYA.get_device_count()
audio['input_devices'] = dict()
while i > 0:
i -= 1
if oPYA.get_device_info_by_index(i)['maxInputChannels'] == 0:
continue
audio['input_devices'][i] = oPYA.get_device_info_by_index(i)['name']
return audio
def setup_video(oArgs):
video = setup_default_video()
if oArgs.video_input == '-1':
video['device'] = video['output_devices'][1]
else:
video['device'] = oArgs.video_input
return video
def setup_audio(oArgs):
global oPYA
audio = setup_default_audio()
for k,v in audio['input_devices'].items():
if v == 'default' and 'input' not in audio:
audio['input'] = k
if v == getattr(oArgs, 'audio_input'):
audio['input'] = k
LOG.debug(f"Setting audio['input'] {k} = {v} {k}")
break
for k,v in audio['output_devices'].items():
if v == 'default' and 'output' not in audio:
audio['output'] = k
if v == getattr(oArgs, 'audio_output'):
audio['output'] = k
LOG.debug(f"Setting audio['output'] {k} = {v} " +str(k))
break
if hasattr(oArgs, 'mode') and getattr(oArgs, 'mode') > 1:
audio['enabled'] = True
audio['audio_enabled'] = True
audio['video_enabled'] = True
elif hasattr(oArgs, 'mode') and getattr(oArgs, 'mode') > 0:
audio['enabled'] = True
audio['audio_enabled'] = False
audio['video_enabled'] = True
else:
audio['enabled'] = False
audio['audio_enabled'] = False
audio['video_enabled'] = False
return audio
i = getattr(oArgs, 'audio_output')
if i >= 0:
try:
elt = oPYA.get_device_info_by_index(i)
if i >= 0 and ( 'maxOutputChannels' not in elt or \
elt['maxOutputChannels'] == 0):
LOG.warn(f"Audio output device has no output channels: {i}")
oArgs.audio_output = -1
except OSError as e:
LOG.warn("Audio output device error looking for maxOutputChannels: " \
+str(i) +' ' +str(e))
oArgs.audio_output = -1
if getattr(oArgs, 'audio_output') < 0:
LOG.info("Choose an output device:")
i = oPYA.get_device_count()
while i > 0:
i -= 1
if oPYA.get_device_info_by_index(i)['maxOutputChannels'] == 0:
continue
LOG.info(str(i) \
+' ' +oPYA.get_device_info_by_index(i)['name'] \
+' ' +str(oPYA.get_device_info_by_index(i)['defaultSampleRate'])
)
return 0
i = getattr(oArgs, 'audio_input')
if i >= 0:
try:
elt = oPYA.get_device_info_by_index(i)
if i >= 0 and ( 'maxInputChannels' not in elt or \
elt['maxInputChannels'] == 0):
LOG.warn(f"Audio input device has no input channels: {i}")
setattr(oArgs, 'audio_input', -1)
except OSError as e:
LOG.warn("Audio input device error looking for maxInputChannels: " \
+str(i) +' ' +str(e))
setattr(oArgs, 'audio_input', -1)
if getattr(oArgs, 'audio_input') < 0:
LOG.info("Choose an input device:")
i = oPYA.get_device_count()
while i > 0:
i -= 1
if oPYA.get_device_info_by_index(i)['maxInputChannels'] == 0:
continue
LOG.info(str(i) \
+' ' +oPYA.get_device_info_by_index(i)['name']
+' ' +str(oPYA.get_device_info_by_index(i)['defaultSampleRate'])
)
return 0
def setup_default_video():
default_video = ["-1"]
default_video.extend(ts.get_video_indexes())
LOG.info(f"Video input choices: {default_video!r}")
video = {'device': -1, 'width': 320, 'height': 240, 'x': 0, 'y': 0}
video['output_devices'] = default_video
return video
def main_parser(_=None, iMode=2):
import cv2
if not os.path.exists('/proc/sys/net/ipv6'):
bIpV6 = 'False'
else:
bIpV6 = 'True'
lIpV6Choices=[bIpV6, 'False']
audio = setup_default_audio()
default_video = setup_default_video()
# parser = argparse.ArgumentParser()
parser = ts.oMainArgparser()
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--version', action='store_true', help='Prints Toxygen version')
parser.add_argument('--clean', action='store_true', help='Delete toxcore libs from libs folder')
parser.add_argument('--reset', action='store_true', help='Reset default profile')
parser.add_argument('--uri', type=str, default='',
help='Add specified Tox ID to friends')
parser.add_argument('--auto_accept_path', '--auto-accept-path', type=str,
default=os.path.join(os.environ['HOME'], 'Downloads'),
help="auto_accept_path")
# parser.add_argument('--mode', type=int, default=iMode,
# help='Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0')
parser.add_argument('--font', type=str, default="Courier",
help='Message font')
parser.add_argument('--message_font_size', type=int, default=15,
help='Font size in pixels')
parser.add_argument('--local_discovery_enabled',type=str,
default='False', choices=['True','False'],
help='Look on the local lan')
parser.add_argument('--compact_mode',type=str,
default='True', choices=['True','False'],
help='Compact mode')
parser.add_argument('--allow_inline',type=str,
default='False', choices=['True','False'],
help='Dis/Enable allow_inline')
parser.add_argument('--notifications',type=str,
default='True', choices=['True','False'],
help='Dis/Enable notifications')
parser.add_argument('--sound_notifications',type=str,
default='True', choices=['True','False'],
help='Enable sound notifications')
parser.add_argument('--calls_sound',type=str,
default='True', choices=['True','False'],
help='Enable calls_sound')
parser.add_argument('--core_logging',type=str,
default='False', choices=['True','False'],
help='Dis/Enable Toxcore notifications')
parser.add_argument('--save_history',type=str,
default='True', choices=['True','False'],
help='En/Disable save history')
parser.add_argument('--update', type=int, default=0,
choices=[0,0],
help='Update program (broken)')
parser.add_argument('--video_input', type=str,
default=-1,
choices=default_video['output_devices'],
help="Video input device number - /dev/video?")
parser.add_argument('--audio_input', type=str,
default=oPYA.get_default_input_device_info()['name'],
choices=audio['input_devices'].values(),
help="Audio input device name - aplay -L for help")
parser.add_argument('--audio_output', type=str,
default=oPYA.get_default_output_device_info()['index'],
choices=audio['output_devices'].values(),
help="Audio output device number - -1 for help")
parser.add_argument('--theme', type=str, default='default',
choices=['dark', 'default'],
help='Theme - style of UI')
parser.add_argument('--sleep', type=str, default='time',
# could expand this to tk, gtk, gevent...
choices=['qt','gevent','time'],
help='Sleep method - one of qt, gevent , time')
supported_languages = settings.supported_languages()
parser.add_argument('--language', type=str, default='English',
choices=supported_languages,
help='Languages')
parser.add_argument('profile', type=str, nargs='?', default=None,
help='Path to Tox profile')
return parser
parser.add_argument('--uri', help='Add specified Tox ID to friends')
parser.add_argument('profile', nargs='?', default=None, help='Path to Tox profile')
args = parser.parse_args()
# clean out the unchanged settings so these can override the profile
lKEEP_SETTINGS = ['uri',
'profile',
'loglevel',
'logfile',
'mode',
# dunno
'audio_input',
'audio_output',
'audio',
'video',
'ipv6_enabled',
'udp_enabled',
'local_discovery_enabled',
'theme',
'network',
'message_font_size',
'font',
'save_history',
'language',
'update',
'proxy_host',
'proxy_type',
'proxy_port',
'core_logging',
'audio',
'video'
] # , 'nodes_json'
class A(): pass
def main(lArgs):
global oPYA
from argparse import Namespace
parser = main_parser()
default_ns = parser.parse_args([])
oArgs = parser.parse_args(lArgs)
if oArgs.version:
if args.version:
print_toxygen_version()
return 0
return
if oArgs.clean:
if args.clean:
clean()
return 0
return
if oArgs.reset:
if args.reset:
reset()
return 0
return
# if getattr(oArgs, 'network') in ['newlocal', 'localnew']: oArgs.network = 'new'
toxygen = app.App(__version__, args.profile, args.uri)
toxygen.main()
# clean out the unchanged settings so these can override the profile
for key in default_ns.__dict__.keys():
if key in lKEEP_SETTINGS: continue
if not hasattr(oArgs, key): continue
if getattr(default_ns, key) == getattr(oArgs, key):
delattr(oArgs, key)
ts.clean_booleans(oArgs)
aArgs = A()
for key in oArgs.__dict__.keys():
setattr(aArgs, key, getattr(oArgs, key))
#setattr(aArgs, 'video', setup_video(oArgs))
aArgs.video = setup_video(oArgs)
assert 'video' in aArgs.__dict__
#setattr(aArgs, 'audio', setup_audio(oArgs))
aArgs.audio = setup_audio(oArgs)
assert 'audio' in aArgs.__dict__
oArgs = aArgs
toxygen = app.App(__version__, oArgs)
# for pyqtconsole
__builtins__.app = toxygen
i = toxygen.iMain()
return i
if __name__ == '__main__':
iRet = 0
try:
iRet = main(sys.argv[1:])
except KeyboardInterrupt:
iRet = 0
except SystemExit as e:
iRet = e
except Exception as e:
import traceback
sys.stderr.write(f"Exception from main {e}" \
+'\n' + traceback.format_exc() +'\n' )
iRet = 1
# Exception ignored in: <module 'threading' from '/usr/lib/python3.9/threading.py'>
# File "/usr/lib/python3.9/threading.py", line 1428, in _shutdown
# lock.acquire()
# gevent.exceptions.LoopExit as e:
# This operation would block forever
sys.stderr.write('Calling sys.exit' +'\n')
with ts.ignoreStdout():
sys.exit(iRet)
main()

View File

@ -1,8 +1,8 @@
import os.path
from history.database import MESSAGE_AUTHOR
import os.path
from ui.messages_widgets import *
MESSAGE_TYPE = {
'TEXT': 0,
'ACTION': 1,
@ -38,8 +38,8 @@ class Message:
MESSAGE_ID = 0
def __init__(self, message_type, author, iTime):
self._time = iTime
def __init__(self, message_type, author, time):
self._time = time
self._type = message_type
self._author = author
self._widget = None
@ -66,7 +66,6 @@ class Message:
message_id = property(get_message_id)
def get_widget(self, *args):
# FixMe
self._widget = self._create_widget(*args)
return self._widget
@ -82,7 +81,6 @@ class Message:
self._widget.mark_as_sent()
def _create_widget(self, *args):
# overridden
pass
@staticmethod
@ -97,8 +95,8 @@ class TextMessage(Message):
Plain text or action message
"""
def __init__(self, message, owner, iTime, message_type, message_id=0):
super().__init__(message_type, owner, iTime)
def __init__(self, message, owner, time, message_type, message_id=0):
super().__init__(message_type, owner, time)
self._message = message
self._id = message_id
@ -121,8 +119,8 @@ class TextMessage(Message):
class OutgoingTextMessage(TextMessage):
def __init__(self, message, owner, iTime, message_type, tox_message_id=0):
super().__init__(message, owner, iTime, message_type)
def __init__(self, message, owner, time, message_type, tox_message_id=0):
super().__init__(message, owner, time, message_type)
self._tox_message_id = tox_message_id
def get_tox_message_id(self):
@ -136,8 +134,8 @@ class OutgoingTextMessage(TextMessage):
class GroupChatMessage(TextMessage):
def __init__(self, id, message, owner, iTime, message_type, name):
super().__init__(id, message, owner, iTime, message_type)
def __init__(self, id, message, owner, time, message_type, name):
super().__init__(id, message, owner, time, message_type)
self._user_name = name
@ -146,8 +144,8 @@ class TransferMessage(Message):
Message with info about file transfer
"""
def __init__(self, author, iTime, state, size, file_name, friend_number, file_number):
super().__init__(MESSAGE_TYPE['FILE_TRANSFER'], author, iTime)
def __init__(self, author, time, state, size, file_name, friend_number, file_number):
super().__init__(MESSAGE_TYPE['FILE_TRANSFER'], author, time)
self._state = state
self._size = size
self._file_name = file_name
@ -187,10 +185,10 @@ class TransferMessage(Message):
file_name = property(get_file_name)
def transfer_updated(self, state, percentage, iTime):
def transfer_updated(self, state, percentage, time):
self._state = state
if self._widget is not None:
self._widget.update_transfer_state(state, percentage, iTime)
self._widget.update_transfer_state(state, percentage, time)
def _create_widget(self, *args):
return FileTransferItem(self, *args)
@ -198,9 +196,9 @@ class TransferMessage(Message):
class UnsentFileMessage(TransferMessage):
def __init__(self, path, data, iTime, author, size, friend_number):
def __init__(self, path, data, time, author, size, friend_number):
file_name = os.path.basename(path)
super().__init__(author, iTime, FILE_TRANSFER_STATE['UNSENT'], size, file_name, friend_number, -1)
super().__init__(author, time, FILE_TRANSFER_STATE['UNSENT'], size, file_name, friend_number, -1)
self._data, self._path = data, path
def get_data(self):
@ -237,5 +235,5 @@ class InlineImageMessage(Message):
class InfoMessage(TextMessage):
def __init__(self, message, iTime):
super().__init__(message, None, iTime, MESSAGE_TYPE['INFO_MESSAGE'])
def __init__(self, message, time):
super().__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE'])

View File

@ -1,15 +1,6 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import common.tox_save as tox_save
import utils.ui as util_ui
from messenger.messages import *
from wrapper_tests.support_testing import assert_main_thread
from wrapper.toxcore_enums_and_consts import TOX_MAX_MESSAGE_LENGTH
global LOG
import logging
LOG = logging.getLogger('app.'+__name__)
log = lambda x: LOG.info(x)
class Messenger(tox_save.ToxSave):
@ -28,9 +19,6 @@ class Messenger(tox_save.ToxSave):
calls_manager.call_started_event.add_callback(self._on_call_started)
calls_manager.call_finished_event.add_callback(self._on_call_finished)
def __repr__(self):
return "<Messenger>"
def get_last_message(self):
contact = self._contacts_manager.get_curr_contact()
if contact is None:
@ -38,7 +26,9 @@ class Messenger(tox_save.ToxSave):
return contact.get_last_message_text()
# -----------------------------------------------------------------------------------------------------------------
# Messaging - friends
# -----------------------------------------------------------------------------------------------------------------
def new_message(self, friend_number, message_type, message):
"""
@ -61,53 +51,33 @@ class Messenger(tox_save.ToxSave):
self._screen.messageEdit.clear()
return
message_type = TOX_MESSAGE_TYPE['NORMAL']
if False: # undocumented
action_message_prefix = '/me '
if text.startswith(action_message_prefix):
message_type = TOX_MESSAGE_TYPE['ACTION']
text = text[len(action_message_prefix):]
action_message_prefix = '/me '
if text.startswith(action_message_prefix):
message_type = TOX_MESSAGE_TYPE['ACTION']
text = text[len(action_message_prefix):]
else:
message_type = TOX_MESSAGE_TYPE['NORMAL']
if len(text) > TOX_MAX_MESSAGE_LENGTH:
text = text[:TOX_MAX_MESSAGE_LENGTH] # 1372
try:
if self._contacts_manager.is_active_a_friend():
self.send_message_to_friend(text, message_type)
elif self._contacts_manager.is_active_a_group():
self.send_message_to_group('~'+text, message_type)
elif self._contacts_manager.is_active_a_group_chat_peer():
self.send_message_to_group_peer(text, message_type)
else:
LOG.warn(f'Unknown friend type for Messenger send_message')
except Exception as e:
LOG.error(f'Messenger send_message {e}')
import traceback
LOG.warn(traceback.format_exc())
title = 'Messenger send_message Error'
text = 'Error: ' + str(e)
assert_main_thread()
util_ui.message_box(text, title)
if self._contacts_manager.is_active_a_friend():
self.send_message_to_friend(text, message_type)
elif self._contacts_manager.is_active_a_group():
self.send_message_to_group(text, message_type)
elif self._contacts_manager.is_active_a_group_chat_peer():
self.send_message_to_group_peer(text, message_type)
def send_message_to_friend(self, text, message_type, friend_number=None):
"""
Send message
:param text: message text
:param friend_number: number of friend
from Qt callback
"""
if not text:
return
if friend_number is None:
friend_number = self._contacts_manager.get_active_number()
if friend_number is None or friend_number < 0:
LOG.error(f"No _contacts_manager.get_active_number")
if not text or friend_number < 0:
return
assert_main_thread()
friend = self._get_friend_by_number(friend_number)
if not friend:
LOG.error(f"No self._get_friend_by_number")
return
messages = self._split_message(text.encode('utf-8'))
t = util.get_unix_time()
for message in messages:
@ -136,9 +106,11 @@ class Messenger(tox_save.ToxSave):
message_id = self._tox.friend_send_message(friend_number, message.type, message.text.encode('utf-8'))
message.tox_message_id = message_id
except Exception as ex:
LOG.warn('Sending pending messages failed with ' + str(ex))
util.log('Sending pending messages failed with ' + str(ex))
# -----------------------------------------------------------------------------------------------------------------
# Messaging - groups
# -----------------------------------------------------------------------------------------------------------------
def send_message_to_group(self, text, message_type, group_number=None):
if group_number is None:
@ -169,17 +141,13 @@ class Messenger(tox_save.ToxSave):
"""
t = util.get_unix_time()
group = self._get_group_by_number(group_number)
if not group:
LOG.error(f"FixMe new_group_message _get_group_by_number({group_number})")
return
peer = group.get_peer_by_id(peer_id)
if not peer:
LOG.error('FixMe new_group_message group.get_peer_by_id ' + str(peer_id))
return
text_message = TextMessage(message, MessageAuthor(peer.name, MESSAGE_AUTHOR['GC_PEER']), t, message_type)
self._add_message(text_message, group)
# -----------------------------------------------------------------------------------------------------------------
# Messaging - group peers
# -----------------------------------------------------------------------------------------------------------------
def send_message_to_group_peer(self, text, message_type, group_number=None, peer_id=None):
if group_number is None or peer_id is None:
@ -188,17 +156,10 @@ class Messenger(tox_save.ToxSave):
group = self._get_group_by_public_key(group_peer_contact.group_pk)
group_number = group.number
if not text:
return
if group.number < 0:
return
if peer_id and peer_id < 0:
if not text or group_number < 0 or peer_id < 0:
return
assert_main_thread()
# FixMe: peer_id is None?
group_peer_contact = self._contacts_manager.get_or_create_group_peer_contact(group_number, peer_id)
# group_peer_contact now may be None
group = self._get_group_by_number(group_number)
messages = self._split_message(text.encode('utf-8'))
t = util.get_unix_time()
@ -221,24 +182,22 @@ class Messenger(tox_save.ToxSave):
t = util.get_unix_time()
group = self._get_group_by_number(group_number)
peer = group.get_peer_by_id(peer_id)
if not peer:
LOG.warn('FixMe new_group_private_message group.get_peer_by_id ' + str(peer_id))
return
text_message = TextMessage(message, MessageAuthor(peer.name, MESSAGE_AUTHOR['GC_PEER']),
t, message_type)
group_peer_contact = self._contacts_manager.get_or_create_group_peer_contact(group_number, peer_id)
if not group_peer_contact:
LOG.warn('FixMe new_group_private_message group_peer_contact ' + str(peer_id))
return
self._add_message(text_message, group_peer_contact)
# -----------------------------------------------------------------------------------------------------------------
# Message receipts
# -----------------------------------------------------------------------------------------------------------------
def receipt(self, friend_number, message_id):
friend = self._get_friend_by_number(friend_number)
friend.mark_as_sent(message_id)
# -----------------------------------------------------------------------------------------------------------------
# Typing notifications
# -----------------------------------------------------------------------------------------------------------------
def send_typing(self, typing):
"""
@ -256,7 +215,9 @@ class Messenger(tox_save.ToxSave):
if self._contacts_manager.is_friend_active(friend_number):
self._screen.typing.setVisible(typing)
# -----------------------------------------------------------------------------------------------------------------
# Contact info updated
# -----------------------------------------------------------------------------------------------------------------
def new_friend_name(self, friend, old_name, new_name):
if old_name == new_name or friend.has_alias():
@ -267,7 +228,9 @@ class Messenger(tox_save.ToxSave):
friend.actions = True
self._add_info_message(friend.number, message)
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
@staticmethod
def _split_message(message):
@ -322,29 +285,21 @@ class Messenger(tox_save.ToxSave):
def _add_info_message(self, friend_number, text):
friend = self._get_friend_by_number(friend_number)
assert friend
message = InfoMessage(text, util.get_unix_time())
friend.append_message(message)
if self._contacts_manager.is_friend_active(friend_number):
self._create_info_message_item(message)
def _create_info_message_item(self, message):
assert_main_thread()
self._items_factory.create_message_item(message)
self._screen.messages.scrollToBottom()
def _add_message(self, text_message, contact):
assert_main_thread()
if not contact:
LOG.warn("_add_message null contact")
return
if self._contacts_manager.is_contact_active(contact): # add message to list
# LOG.debug("_add_message is_contact_active(contact)")
self._create_message_item(text_message)
self._screen.messages.scrollToBottom()
self._contacts_manager.get_curr_contact().append_message(text_message)
else:
# LOG.debug("_add_message not is_contact_active(contact)")
contact.inc_messages()
contact.append_message(text_message)
if not contact.visibility:

View File

@ -1,113 +1,48 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import sys
import os
import threading
from PyQt5 import QtGui
from wrapper.toxcore_enums_and_consts import *
from wrapper.toxav_enums import *
from wrapper.tox import bin_to_string
import utils.ui as util_ui
import utils.util as util
import cv2
import numpy as np
from middleware.threads import invoke_in_main_thread, execute
from notifications.tray import tray_notification
from notifications.sound import *
from datetime import datetime
iMAX_INT32 = 4294967295
# callbacks can be called in any thread so were being careful
def LOG_ERROR(l): print('EROR< '+l)
def LOG_WARN(l): print('WARN< '+l)
def LOG_INFO(l):
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 20-1
if bIsVerbose: print('INFO< '+l)
def LOG_DEBUG(l):
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 10-1
if bIsVerbose: print('DBUG< '+l)
def LOG_TRACE(l):
bIsVerbose = hasattr(__builtins__, 'app') and app.oArgs.loglevel < 10-1
pass # print('TRACE+ '+l)
global aTIMES
aTIMES=dict()
def bTooSoon(key, sSlot, fSec=10.0):
# rate limiting
global aTIMES
if sSlot not in aTIMES:
aTIMES[sSlot] = dict()
OTIME = aTIMES[sSlot]
now = datetime.now()
if key not in OTIME:
OTIME[key] = now
return False
delta = now - OTIME[key]
OTIME[key] = now
if delta.total_seconds() < fSec: return True
return False
import threading
# TODO: refactoring. Use contact provider instead of manager
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - current user
# -----------------------------------------------------------------------------------------------------------------
global iBYTES
iBYTES=0
def sProcBytes(sFile=None):
if sys.platform == 'win32': return ''
global iBYTES
if sFile is None:
pid = os.getpid()
sFile = f"/proc/{pid}/net/softnet_stat"
if os.path.exists(sFile):
total = 0
with open(sFile, 'r') as iFd:
for elt in iFd.readlines():
i = elt.find(' ')
p = int(elt[:i], 16)
total = total + p
if iBYTES == 0:
iBYTES = total
return ''
diff = total - iBYTES
s = f' {diff // 1024} Kbytes'
else:
s = ''
return s
def self_connection_status(tox, profile):
"""
Current user changed connection status (offline, TCP, UDP)
"""
sSlot = 'self connection status'
def wrapped(tox_link, connection, user_data):
key = f"connection {connection}"
if bTooSoon(key, sSlot, 10): return
s = sProcBytes()
try:
status = tox.self_get_status() if connection != TOX_CONNECTION['NONE'] else None
if status:
LOG_DEBUG(f"self_connection_status: connection={connection} status={status}" +' '+s)
invoke_in_main_thread(profile.set_status, status)
except Exception as e:
LOG_ERROR(f"self_connection_status: {e}")
pass
print('Connection status: ', str(connection))
status = tox.self_get_status() if connection != TOX_CONNECTION['NONE'] else None
invoke_in_main_thread(profile.set_status, status)
return wrapped
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - friends
# -----------------------------------------------------------------------------------------------------------------
def friend_status(contacts_manager, file_transfer_handler, profile, settings):
sSlot = 'friend status'
def wrapped(tox, friend_number, new_status, user_data):
"""
Check friend's status (none, busy, away)
"""
LOG_DEBUG(f"Friend's #{friend_number} status changed")
key = f"friend_number {friend_number}"
if bTooSoon(key, sSlot, 10): return
print("Friend's #{} status changed!".format(friend_number))
friend = contacts_manager.get_friend_by_number(friend_number)
if friend.status is None and settings['sound_notifications'] and \
profile.status != TOX_USER_STATUS['BUSY']:
if friend.status is None and settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
invoke_in_main_thread(friend.set_status, new_status)
@ -126,7 +61,7 @@ def friend_connection_status(contacts_manager, profile, settings, plugin_loader,
"""
Check friend's connection status (offline, udp, tcp)
"""
LOG_DEBUG(f"Friend #{friend_number} connection status: {new_status}")
print("Friend #{} connection status: {}".format(friend_number, new_status))
friend = contacts_manager.get_friend_by_number(friend_number)
if new_status == TOX_CONNECTION['NONE']:
invoke_in_main_thread(friend.set_status, None)
@ -144,35 +79,29 @@ def friend_connection_status(contacts_manager, profile, settings, plugin_loader,
def friend_name(contacts_provider, messenger):
sSlot = 'friend_name'
def wrapped(tox, friend_number, name, size, user_data):
"""
Friend changed his name
"""
key = f"friend_number={friend_number}"
if bTooSoon(key, sSlot, 60): return
print('New name friend #' + str(friend_number))
friend = contacts_provider.get_friend_by_number(friend_number)
old_name = friend.name
new_name = str(name, 'utf-8')
LOG_DEBUG(f"get_friend_by_number #{friend_number} {new_name}")
invoke_in_main_thread(friend.set_name, new_name)
invoke_in_main_thread(messenger.new_friend_name, friend, old_name, new_name)
return wrapped
def friend_status_message(contacts_manager, messenger):
sSlot = 'status_message'
def wrapped(tox, friend_number, status_message, size, user_data):
"""
:return: function for callback friend_status_message. It updates friend's status message
and calls window repaint
"""
friend = contacts_manager.get_friend_by_number(friend_number)
key = f"friend_number={friend_number}"
if bTooSoon(key, sSlot, 10): return
invoke_in_main_thread(friend.set_status_message, str(status_message, 'utf-8'))
LOG_DEBUG(f'User #{friend_number} has new status message')
print('User #{} has new status message'.format(friend_number))
invoke_in_main_thread(messenger.send_messages, friend_number)
return wrapped
@ -183,20 +112,16 @@ def friend_message(messenger, contacts_manager, profile, settings, window, tray)
"""
New message from friend
"""
LOG_DEBUG(f"friend_message #{friend_number}")
message = str(message, 'utf-8')
invoke_in_main_thread(messenger.new_message, friend_number, message_type, message)
if not window.isActiveWindow():
friend = contacts_manager.get_friend_by_number(friend_number)
if settings['notifications'] \
and profile.status != TOX_USER_STATUS['BUSY'] \
and not settings.locked:
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
invoke_in_main_thread(tray_notification, friend.name, message, tray, window)
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
icon = os.path.join(util.get_images_directory(), 'icon_new_messages.png')
if tray:
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
return wrapped
@ -206,7 +131,7 @@ def friend_request(contacts_manager):
"""
Called when user get new friend request
"""
LOG_DEBUG(f'Friend request')
print('Friend request')
key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
invoke_in_main_thread(contacts_manager.process_friend_request, tox_id, str(message, 'utf-8'))
@ -215,12 +140,9 @@ def friend_request(contacts_manager):
def friend_typing(messenger):
sSlot = "friend_typing"
def wrapped(tox, friend_number, typing, user_data):
key = f"friend_number={friend_number}"
if bTooSoon(key, sSlot, 10): return
LOG_DEBUG(f"friend_typing #{friend_number}")
invoke_in_main_thread(messenger.friend_typing, friend_number, typing)
return wrapped
@ -231,7 +153,9 @@ def friend_read_receipt(messenger):
return wrapped
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - file transfers
# -----------------------------------------------------------------------------------------------------------------
def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager, settings):
@ -240,7 +164,7 @@ def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager
"""
def wrapped(tox, friend_number, file_number, file_type, size, file_name, file_name_size, user_data):
if file_type == TOX_FILE_KIND['DATA']:
LOG_DEBUG(f'file_transfer_handler File')
print('File')
try:
file_name = str(file_name[:file_name_size], 'utf-8')
except:
@ -252,18 +176,15 @@ def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager
file_name)
if not window.isActiveWindow():
friend = contacts_manager.get_friend_by_number(friend_number)
if settings['notifications'] \
and profile.status != TOX_USER_STATUS['BUSY'] \
and not settings.locked:
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
file_from = util_ui.tr("File from")
invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window)
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER'])
if tray:
icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
else: # avatar
LOG_DEBUG(f'file_transfer_handler Avatar')
print('Avatar')
invoke_in_main_thread(file_transfer_handler.incoming_avatar,
friend_number,
file_number,
@ -306,7 +227,9 @@ def file_recv_control(file_transfer_handler):
return wrapped
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - custom packets
# -----------------------------------------------------------------------------------------------------------------
def lossless_packet(plugin_loader):
@ -331,20 +254,20 @@ def lossy_packet(plugin_loader):
return wrapped
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - audio
# -----------------------------------------------------------------------------------------------------------------
def call_state(calls_manager):
def wrapped(iToxav, friend_number, mask, user_data):
def wrapped(toxav, friend_number, mask, user_data):
"""
New call state
"""
LOG_DEBUG(f"call_state #{friend_number}")
print(friend_number, mask)
if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']:
invoke_in_main_thread(calls_manager.stop_call, friend_number, True)
else:
# guessing was calls_manager.
#? incoming_call
calls_manager._call.toxav_call_state_cb(friend_number, mask)
calls_manager.toxav_call_state_cb(friend_number, mask)
return wrapped
@ -354,7 +277,7 @@ def call(calls_manager):
"""
Incoming call from friend
"""
LOG_DEBUG(f"Incoming call from {friend_number} {audio} {video}")
print(friend_number, audio, video)
invoke_in_main_thread(calls_manager.incoming_call, audio, video, friend_number)
return wrapped
@ -365,16 +288,16 @@ def callback_audio(calls_manager):
"""
New audio chunk
"""
LOG_DEBUG(f"callback_audio #{friend_number}")
# dunno was .call
calls_manager._call.audio_chunk(
calls_manager.call.audio_chunk(
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
audio_channels_count,
rate)
return wrapped
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - video
# -----------------------------------------------------------------------------------------------------------------
def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data):
@ -401,9 +324,6 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u
It can be created from initial y, u, v using slices
"""
LOG_DEBUG(f"video_receive_frame from toxav_video_receive_frame_cb={friend_number}")
import cv2
import numpy as np
try:
y_size = abs(max(width, abs(ystride)))
u_size = abs(max(width // 2, abs(ustride)))
@ -429,10 +349,11 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u
invoke_in_main_thread(cv2.imshow, str(friend_number), frame)
except Exception as ex:
LOG_ERROR(f"video_receive_frame {ex} #{friend_number}")
pass
print(ex)
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - groups
# -----------------------------------------------------------------------------------------------------------------
def group_message(window, tray, tox, messenger, settings, profile):
@ -440,24 +361,18 @@ def group_message(window, tray, tox, messenger, settings, profile):
New message in group chat
"""
def wrapped(tox_link, group_number, peer_id, message_type, message, length, user_data):
LOG_DEBUG(f"group_message #{group_number}")
message = str(message[:length], 'utf-8')
invoke_in_main_thread(messenger.new_group_message, group_number, message_type, message, peer_id)
if window.isActiveWindow():
return
bl = settings['notify_all_gc'] or profile.name in message
name = tox.group_peer_get_name(group_number, peer_id)
if settings['sound_notifications'] and bl and \
profile.status != TOX_USER_STATUS['BUSY']:
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and (not settings.locked) and bl:
invoke_in_main_thread(tray_notification, name, message, tray, window)
if settings['sound_notifications'] and bl and profile.status != TOX_USER_STATUS['BUSY']:
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
if False and settings['tray_icon'] and tray:
if settings['notifications'] and \
profile.status != TOX_USER_STATUS['BUSY'] and \
(not settings.locked) and bl:
invoke_in_main_thread(tray_notification, name, message, tray, window)
if tray:
icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
return wrapped
@ -467,83 +382,54 @@ def group_private_message(window, tray, tox, messenger, settings, profile):
New private message in group chat
"""
def wrapped(tox_link, group_number, peer_id, message_type, message, length, user_data):
LOG_DEBUG(f"group_private_message #{group_number}")
message = str(message[:length], 'utf-8')
invoke_in_main_thread(messenger.new_group_private_message, group_number, message_type, message, peer_id)
if window.isActiveWindow():
return
bl = settings['notify_all_gc'] or profile.name in message
name = tox.group_peer_get_name(group_number, peer_id)
if settings['notifications'] and settings['tray_icon'] \
and profile.status != TOX_USER_STATUS['BUSY'] \
and (not settings.locked) and bl:
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and (not settings.locked) and bl:
invoke_in_main_thread(tray_notification, name, message, tray, window)
if settings['sound_notifications'] and bl and profile.status != TOX_USER_STATUS['BUSY']:
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
if tray and hasattr(tray, 'setIcon'):
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
return wrapped
# Exception ignored on calling ctypes callback function: <function group_invite.<locals>.wrapped at 0x7ffede910700>
def group_invite(window, settings, tray, profile, groups_service, contacts_provider):
def wrapped(tox, friend_number, invite_data, length, group_name, group_name_length, user_data):
LOG_DEBUG(f"group_invite friend_number={friend_number}")
group_name = str(bytes(group_name[:group_name_length]), 'utf-8')
invoke_in_main_thread(groups_service.process_group_invite,
friend_number, group_name,
bytes(invite_data[:length]))
if window.isActiveWindow():
return
bHasTray = tray and settings['tray_icon']
if settings['notifications'] \
and bHasTray \
and profile.status != TOX_USER_STATUS['BUSY'] \
and not settings.locked:
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
friend = contacts_provider.get_friend_by_number(friend_number)
title = util_ui.tr('New invite to group chat')
text = util_ui.tr('{} invites you to group "{}"').format(friend.name, group_name)
invoke_in_main_thread(tray_notification, title, text, tray, window)
if tray:
icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
return wrapped
def group_self_join(contacts_provider, contacts_manager, groups_service):
sSlot = 'group_self_join'
def wrapped(tox, group_number, user_data):
if group_number is None:
LOG_ERROR(f"group_self_join NULL group_number #{group_number}")
return
LOG_DEBUG(f"group_self_join #{group_number}")
key = f"group_number {group_number}"
if bTooSoon(key, sSlot, 10): return
group = contacts_provider.get_group_by_number(group_number)
if group is None:
LOG_ERROR(f"group_self_join NULL group #{group}")
return
invoke_in_main_thread(group.set_status, TOX_USER_STATUS['NONE'])
invoke_in_main_thread(groups_service.update_group_info, group)
invoke_in_main_thread(contacts_manager.update_filtration)
return wrapped
def group_peer_join(contacts_provider, groups_service):
sSlot = "group_peer_join"
def wrapped(tox, group_number, peer_id, user_data):
key = f"group_peer_join #{group_number} peer_id={peer_id}"
if bTooSoon(key, sSlot, 20): return
group = contacts_provider.get_group_by_number(group_number)
if group is None:
LOG_ERROR(f"group_peer_join NULL group #{group} group_number={group_number}")
return
if peer_id > group._peers_limit:
LOG_ERROR(key +f" {peer_id} > {group._peers_limit}")
return
LOG_DEBUG(f"group_peer_join group={group}")
group.add_peer(peer_id)
invoke_in_main_thread(groups_service.generate_peers_list)
invoke_in_main_thread(groups_service.update_group_info, group)
@ -552,49 +438,29 @@ def group_peer_join(contacts_provider, groups_service):
def group_peer_exit(contacts_provider, groups_service, contacts_manager):
def wrapped(tox,
group_number, peer_id,
exit_type, name, name_length,
message, length,
user_data):
def wrapped(tox, group_number, peer_id, message, length, user_data):
group = contacts_provider.get_group_by_number(group_number)
if group:
LOG_DEBUG(f"group_peer_exit #{group_number} peer_id={peer_id} exit_type={exit_type}")
group.remove_peer(peer_id)
invoke_in_main_thread(groups_service.generate_peers_list)
else:
LOG_WARN(f"group_peer_exit group not found #{group_number} peer_id={peer_id}")
group.remove_peer(peer_id)
invoke_in_main_thread(groups_service.generate_peers_list)
return wrapped
def group_peer_name(contacts_provider, groups_service):
def wrapped(tox, group_number, peer_id, name, length, user_data):
LOG_DEBUG(f"group_peer_name #{group_number} peer_id={peer_id}")
group = contacts_provider.get_group_by_number(group_number)
peer = group.get_peer_by_id(peer_id)
if peer:
peer.name = str(name[:length], 'utf-8')
invoke_in_main_thread(groups_service.generate_peers_list)
else:
# FixMe: known signal to revalidate roles...
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r")
return
peer.name = str(name[:length], 'utf-8')
invoke_in_main_thread(groups_service.generate_peers_list)
return wrapped
def group_peer_status(contacts_provider, groups_service):
def wrapped(tox, group_number, peer_id, peer_status, user_data):
LOG_DEBUG(f"group_peer_status #{group_number} peer_id={peer_id}")
group = contacts_provider.get_group_by_number(group_number)
peer = group.get_peer_by_id(peer_id)
if peer:
peer.status = peer_status
else:
# _peers = [(p._name, p._peer_id) for p in group.get_peers()]
LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r")
# TODO: add info message
peer.status = peer_status
invoke_in_main_thread(groups_service.generate_peers_list)
return wrapped
@ -602,62 +468,32 @@ def group_peer_status(contacts_provider, groups_service):
def group_topic(contacts_provider):
def wrapped(tox, group_number, peer_id, topic, length, user_data):
LOG_DEBUG(f"group_topic #{group_number} peer_id={peer_id}")
group = contacts_provider.get_group_by_number(group_number)
if group:
topic = str(topic[:length], 'utf-8')
invoke_in_main_thread(group.set_status_message, topic)
else:
_peers = [(p._name, p._peer_id) for p in group.get_peers()]
LOG_WARN(f"group_topic {group!r} has no peer_id={peer_id} in {_peers!r}")
# TODO: add info message
topic = str(topic[:length], 'utf-8')
invoke_in_main_thread(group.set_status_message, topic)
return wrapped
def group_moderation(groups_service, contacts_provider, contacts_manager, messenger):
def update_peer_role(group, mod_peer_id, peer_id, new_role):
peer = group.get_peer_by_id(peer_id)
if peer:
peer.role = new_role
# TODO: add info message
else:
# FixMe: known signal to revalidate roles...
# _peers = [(p._name, p._peer_id) for p in group.get_peers()]
LOG_TRACE(f"update_peer_role group {group!r} has no peer_id={peer_id} in _peers!r")
peer.role = new_role
# TODO: add info message
def remove_peer(group, mod_peer_id, peer_id, is_ban):
peer = group.get_peer_by_id(peer_id)
if peer:
contacts_manager.remove_group_peer_by_id(group, peer_id)
group.remove_peer(peer_id)
else:
# FixMe: known signal to revalidate roles...
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r")
contacts_manager.remove_group_peer_by_id(group, peer_id)
group.remove_peer(peer_id)
# TODO: add info message
# source_peer_number, target_peer_number,
def wrapped(tox, group_number, mod_peer_id, peer_id, event_type, user_data):
if mod_peer_id == iMAX_INT32 or peer_id == iMAX_INT32:
# FixMe: known signal to revalidate roles...
return
LOG_DEBUG(f"group_moderation #{group_number} mod_id={mod_peer_id} peer_id={peer_id} event_type={event_type}")
group = contacts_provider.get_group_by_number(group_number)
mod_peer = group.get_peer_by_id(mod_peer_id)
if not mod_peer:
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
LOG_TRACE(f"remove_peer group {group!r} has no mod_peer_id={mod_peer_id} in _peers!r")
return
peer = group.get_peer_by_id(peer_id)
if not peer:
# FixMe: known signal to revalidate roles...
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r")
return
if event_type == TOX_GROUP_MOD_EVENT['KICK']:
remove_peer(group, mod_peer_id, peer_id, False)
elif event_type == TOX_GROUP_MOD_EVENT['BAN']:
remove_peer(group, mod_peer_id, peer_id, True)
elif event_type == TOX_GROUP_MOD_EVENT['OBSERVER']:
update_peer_role(group, mod_peer_id, peer_id, TOX_GROUP_ROLE['OBSERVER'])
elif event_type == TOX_GROUP_MOD_EVENT['USER']:
@ -673,7 +509,6 @@ def group_moderation(groups_service, contacts_provider, contacts_manager, messen
def group_password(contacts_provider):
def wrapped(tox_link, group_number, password, length, user_data):
LOG_DEBUG(f"group_password #{group_number}")
password = str(password[:length], 'utf-8')
group = contacts_provider.get_group_by_number(group_number)
group.password = password
@ -684,7 +519,6 @@ def group_password(contacts_provider):
def group_peer_limit(contacts_provider):
def wrapped(tox_link, group_number, peer_limit, user_data):
LOG_DEBUG(f"group_peer_limit #{group_number}")
group = contacts_provider.get_group_by_number(group_number)
group.peer_limit = peer_limit
@ -694,18 +528,19 @@ def group_peer_limit(contacts_provider):
def group_privacy_state(contacts_provider):
def wrapped(tox_link, group_number, privacy_state, user_data):
LOG_DEBUG(f"group_privacy_state #{group_number}")
group = contacts_provider.get_group_by_number(group_number)
group.is_private = privacy_state == TOX_GROUP_PRIVACY_STATE['PRIVATE']
return wrapped
# -----------------------------------------------------------------------------------------------------------------
# Callbacks - initialization
# -----------------------------------------------------------------------------------------------------------------
def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager,
calls_manager, file_transfer_handler, main_window, tray, messenger, groups_service,
contacts_provider, ms=None):
contacts_provider):
"""
Initialization of all callbacks.
:param tox: Tox instance
@ -722,7 +557,6 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager,
:param groups_service: GroupsService instance
:param contacts_provider: ContactsProvider instance
"""
# self callbacks
tox.callback_self_connection_status(self_connection_status(tox, profile))

View File

@ -1,170 +1,115 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import sys
from bootstrap.bootstrap import *
import threading
import queue
from utils import util
import time
from PyQt5 import QtCore
from bootstrap.bootstrap import *
from bootstrap.bootstrap import download_nodes_list
from wrapper.toxcore_enums_and_consts import TOX_USER_STATUS, TOX_CONNECTION
import wrapper_tests.support_testing as ts
from utils import util
import time
sleep = time.sleep
# LOG=util.log
global LOG
import logging
LOG = logging.getLogger('app.'+'threads')
# log = lambda x: LOG.info(x)
def LOG_ERROR(l): print('EROR+ '+l)
def LOG_WARN(l): print('WARN+ '+l)
def LOG_INFO(l): print('INFO+ '+l)
def LOG_DEBUG(l): print('DBUG+ '+l)
def LOG_TRACE(l): pass # print('TRACE+ '+l)
iLAST_CONN = 0
iLAST_DELTA = 60
# -----------------------------------------------------------------------------------------------------------------
# Base threads
# -----------------------------------------------------------------------------------------------------------------
class BaseThread(threading.Thread):
def __init__(self, name=None, target=None):
def __init__(self):
super().__init__()
self._stop_thread = False
if name:
super().__init__(name=name, target=target)
else:
super().__init__(target=target)
def stop_thread(self, timeout=-1):
def stop_thread(self):
self._stop_thread = True
if timeout < 0:
timeout = ts.iTHREAD_TIMEOUT
i = 0
while i < ts.iTHREAD_JOINS:
self.join(timeout)
if not self.is_alive(): break
i = i + 1
else:
LOG_WARN(f"BaseThread {self.name} BLOCKED after {ts.iTHREAD_JOINS}")
self.join()
class BaseQThread(QtCore.QThread):
def __init__(self, name=None):
# NO name=name
def __init__(self):
super().__init__()
self._stop_thread = False
self.name = str(id(self))
def stop_thread(self, timeout=-1):
def stop_thread(self):
self._stop_thread = True
if timeout < 0:
timeout = ts.iTHREAD_TIMEOUT
i = 0
while i < ts.iTHREAD_JOINS:
self.wait(timeout)
if not self.isRunning(): break
i = i + 1
sleep(ts.iTHREAD_TIMEOUT)
else:
LOG_WARN(f"BaseQThread {self.name} BLOCKED")
self.wait()
# -----------------------------------------------------------------------------------------------------------------
# Toxcore threads
# -----------------------------------------------------------------------------------------------------------------
class InitThread(BaseThread):
def __init__(self, tox, plugin_loader, settings, app, is_first_start):
super().__init__(name='InitThread')
self._tox = tox
self._plugin_loader = plugin_loader
self._settings = settings
self._app = app
def __init__(self, tox, plugin_loader, settings, is_first_start):
super().__init__()
self._tox, self._plugin_loader, self._settings = tox, plugin_loader, settings
self._is_first_start = is_first_start
def run(self):
# DBUG+ InitThread run: ERROR name 'ts' is not defined
import wrapper_tests.support_testing as ts
LOG_DEBUG('InitThread run: ')
if self._is_first_start:
# download list of nodes if needed
download_nodes_list(self._settings)
# start plugins
self._plugin_loader.load()
# bootstrap
try:
if self._is_first_start and ts.bAreWeConnected() and \
self._settings['download_nodes_list']:
LOG_INFO(f"downloading list of nodes {self._settings['download_nodes_list']}")
download_nodes_list(self._settings, oArgs=self._app._args)
if ts.bAreWeConnected():
LOG_INFO(f"calling test_net nodes")
self._app.test_net(oThread=self, iMax=4)
if self._is_first_start:
LOG_INFO('starting plugins')
self._plugin_loader.load()
except Exception as e:
LOG_DEBUG(f"InitThread run: ERROR {e}")
for data in generate_nodes():
if self._stop_thread:
return
self._tox.bootstrap(*data)
self._tox.add_tcp_relay(*data)
except:
pass
for _ in range(ts.iTHREAD_JOINS):
for _ in range(10):
if self._stop_thread:
return
sleep(ts.iTHREAD_SLEEP)
return
time.sleep(1)
while not self._tox.self_get_connection_status():
try:
for data in generate_nodes(None):
if self._stop_thread:
return
self._tox.bootstrap(*data)
self._tox.add_tcp_relay(*data)
except:
pass
finally:
time.sleep(5)
class ToxIterateThread(BaseQThread):
def __init__(self, tox, app=None):
def __init__(self, tox):
super().__init__()
self._tox = tox
self._app = app
def run(self):
LOG_DEBUG('ToxIterateThread run: ')
while not self._stop_thread:
try:
iMsec = self._tox.iteration_interval()
self._tox.iterate()
except Exception as e:
# Fatal Python error: Segmentation fault
LOG_ERROR(f"ToxIterateThread run: {e}")
else:
sleep(iMsec / 1000.0)
global iLAST_CONN
if not iLAST_CONN:
iLAST_CONN = time.time()
# TRAC> TCP_common.c#203:read_TCP_packet recv buffer has 0 bytes, but requested 10 bytes
# and segv
if time.time() - iLAST_CONN > iLAST_DELTA and \
ts.bAreWeConnected() and \
self._tox.self_get_status() == TOX_USER_STATUS['NONE'] and \
self._tox.self_get_connection_status() == TOX_CONNECTION['NONE']:
iLAST_CONN = time.time()
LOG_INFO(f"ToxIterateThread calling test_net")
invoke_in_main_thread(
self._app.test_net, oThread=self, iMax=2)
self._tox.iterate()
time.sleep(self._tox.iteration_interval() / 1000)
class ToxAVIterateThread(BaseQThread):
def __init__(self, toxav):
super().__init__()
self._toxav = toxav
def run(self):
LOG_DEBUG('ToxAVIterateThread run: ')
while not self._stop_thread:
self._toxav.iterate()
sleep(self._toxav.iteration_interval() / 1000)
time.sleep(self._toxav.iteration_interval() / 1000)
# -----------------------------------------------------------------------------------------------------------------
# File transfers thread
# -----------------------------------------------------------------------------------------------------------------
class FileTransfersThread(BaseQThread):
def __init__(self):
super().__init__('FileTransfers')
super().__init__()
self._queue = queue.Queue()
self._timeout = 0.01
@ -179,12 +124,14 @@ class FileTransfersThread(BaseQThread):
except queue.Empty:
pass
except queue.Full:
LOG_WARN('Queue is full in _thread')
util.log('Queue is full in _thread')
except Exception as ex:
LOG_ERROR('in _thread: ' + str(ex))
util.log('Exception in _thread: ' + str(ex))
_thread = FileTransfersThread()
def start_file_transfer_thread():
_thread.start()
@ -197,7 +144,9 @@ def execute(func, *args, **kwargs):
_thread.execute(func, *args, **kwargs)
# -----------------------------------------------------------------------------------------------------------------
# Invoking in main thread
# -----------------------------------------------------------------------------------------------------------------
class InvokeEvent(QtCore.QEvent):
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())

View File

@ -1,125 +1,34 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import user_data.settings
import wrapper.tox
import wrapper.toxcore_enums_and_consts as enums
import ctypes
import traceback
import os
global LOG
import logging
LOG = logging.getLogger('app.'+'tox_factory')
from ctypes import *
from utils import util
from utils import ui as util_ui
# callbacks can be called in any thread so were being careful
# tox.py can be called by callbacks
def LOG_ERROR(a): print('EROR> '+a)
def LOG_WARN(a): print('WARN> '+a)
def LOG_INFO(a):
bVERBOSE = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 20
if bVERBOSE: print('INFO> '+a)
def LOG_DEBUG(a):
bVERBOSE = hasattr(__builtins__, 'app') and app.oArgs.loglevel <= 10
if bVERBOSE: print('DBUG> '+a)
def LOG_TRACE(a):
bVERBOSE = hasattr(__builtins__, 'app') and app.oArgs.loglevel < 10
if bVERBOSE: print('TRAC> '+a)
def LOG_LOG(a): print('TRAC> '+a)
def tox_log_cb(iTox, level, file, line, func, message, *args):
"""
* @param level The severity of the log message.
* @param file The source file from which the message originated.
* @param line The source line from which the message originated.
* @param func The function from which the message originated.
* @param message The log message.
* @param user_data The user data pointer passed to tox_new in options.
"""
try:
file = str(file, 'UTF-8')
# root WARNING 3network.c#944:b'send_packet'attempted to send message with network family 10 (probably IPv6) on IPv4 socket
if file == 'network.c' and line in [944, 660]: return
func = str(func, 'UTF-8')
message = str(message, 'UTF-8')
message = f"{file}#{line}:{func} {message}"
LOG_LOG(message)
except Exception as e:
LOG_ERROR(f"tox_log_cb {e}")
#tox_log_handler (context=0x24763d0,
# level=LOGGER_LEVEL_TRACE, file=0x7fffe599fb99 "TCP_common.c", line=203,
# func=0x7fffe599fc50 <__func__.2> "read_TCP_packet",
# message=0x7fffba7fabd0 "recv buffer has 0 bytes, but requested 10 bytes",
# userdata=0x0) at /var/local/src/c-toxcore/toxcore/tox.c:78
def tox_factory(data=None, settings=None, args=None, app=None):
def tox_factory(data=None, settings=None):
"""
:param data: user data from .tox file. None = no saved data, create new profile
:param settings: current profile settings. None = default settings will be used
:return: new tox instance
"""
if not settings:
LOG_WARN("tox_factory using get_default_settings")
if settings is None:
settings = user_data.settings.Settings.get_default_settings()
else:
user_data.settings.clean_settings(settings)
try:
tox_options = wrapper.tox.Tox.options_new()
tox_options.contents.ipv6_enabled = settings['ipv6_enabled']
tox_options.contents.udp_enabled = settings['udp_enabled']
tox_options.contents.proxy_type = int(settings['proxy_type'])
if type(settings['proxy_host']) == str:
tox_options.contents.proxy_host = bytes(settings['proxy_host'],'UTF-8')
elif type(settings['proxy_host']) == bytes:
tox_options.contents.proxy_host = settings['proxy_host']
else:
tox_options.contents.proxy_host = b''
tox_options.contents.proxy_port = int(settings['proxy_port'])
tox_options.contents.start_port = settings['start_port']
tox_options.contents.end_port = settings['end_port']
tox_options.contents.tcp_port = settings['tcp_port']
tox_options.contents.local_discovery_enabled = settings['local_discovery_enabled']
tox_options.contents.dht_announcements_enabled = settings['dht_announcements_enabled']
tox_options.contents.hole_punching_enabled = settings['hole_punching_enabled']
if data: # load existing profile
tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['TOX_SAVE']
tox_options.contents.savedata_data = ctypes.c_char_p(data)
tox_options.contents.savedata_length = len(data)
else: # create new profile
tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['NONE']
tox_options.contents.savedata_data = None
tox_options.contents.savedata_length = 0
tox_options = wrapper.tox.Tox.options_new()
tox_options.contents.udp_enabled = settings['udp_enabled']
tox_options.contents.proxy_type = settings['proxy_type']
tox_options.contents.proxy_host = bytes(settings['proxy_host'], 'UTF-8')
tox_options.contents.proxy_port = settings['proxy_port']
tox_options.contents.start_port = settings['start_port']
tox_options.contents.end_port = settings['end_port']
tox_options.contents.tcp_port = settings['tcp_port']
tox_options.contents.local_discovery_enabled = settings['lan_discovery']
if data: # load existing profile
tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['TOX_SAVE']
tox_options.contents.savedata_data = ctypes.c_char_p(data)
tox_options.contents.savedata_length = len(data)
else: # create new profile
tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['NONE']
tox_options.contents.savedata_data = None
tox_options.contents.savedata_length = 0
# overrides
tox_options.contents.local_discovery_enabled = False
tox_options.contents.ipv6_enabled = False
tox_options.contents.hole_punching_enabled = False
LOG.debug("wrapper.tox.Tox settings: " +repr(settings))
if 'trace_enabled' in settings and settings['trace_enabled']:
LOG_INFO("settings['trace_enabled' disabled" )
elif tox_options._options_pointer:
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_char_p, c_int, c_char_p, c_char_p, c_void_p)
tox_options.self_logger_cb = c_callback(tox_log_cb)
wrapper.tox.Tox.libtoxcore.tox_options_set_log_callback(
tox_options._options_pointer,
tox_options.self_logger_cb)
else:
LOG_WARN("No tox_options._options_pointer to add self_logger_cb" )
retval = wrapper.tox.Tox(tox_options)
except Exception as e:
if app and hasattr(app, '_log'):
pass
LOG_ERROR(f"wrapper.tox.Tox failed: {e}")
LOG_WARN(traceback.format_exc())
raise
if app and hasattr(app, '_log'):
app._log("DEBUG: wrapper.tox.Tox succeeded")
return retval
return wrapper.tox.Tox(tox_options)

View File

@ -1,40 +1,20 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import json
import urllib.request
import utils.util as util
from PyQt5 import QtNetwork, QtCore
try:
import requests
except ImportError:
requests = None
global LOG
import logging
LOG = logging.getLogger('app.'+__name__)
class ToxDns:
def __init__(self, settings, log=None):
def __init__(self, settings):
self._settings = settings
self._log = log
@staticmethod
def _send_request(url, data):
if requests:
LOG.info('send_request loading with requests: ' + str(url))
headers = dict()
headers['Content-Type'] = 'application/json'
req = requests.get(url, headers=headers)
if req.status_code < 300:
retval = req.content
else:
raise LookupError(str(req.status_code))
else:
req = urllib.request.Request(url)
req.add_header('Content-Type', 'application/json')
response = urllib.request.urlopen(req, bytes(json.dumps(data), 'utf-8'))
retval = response.read()
res = json.loads(str(retval, 'utf-8'))
req = urllib.request.Request(url)
req.add_header('Content-Type', 'application/json')
response = urllib.request.urlopen(req, bytes(json.dumps(data), 'utf-8'))
res = json.loads(str(response.read(), 'utf-8'))
if not res['c']:
return res['tox_id']
else:
@ -49,25 +29,12 @@ class ToxDns:
site = email.split('@')[1]
data = {"action": 3, "name": "{}".format(email)}
urls = ('https://{}/api'.format(site), 'http://{}/api'.format(site))
if requests:
for url in urls:
LOG.info('TOX nodes loading with requests: ' + str(url))
try:
headers = dict()
headers['Content-Type'] = 'application/json'
req = requests.get(url, headers=headers)
if req.status_code < 300:
result = req.content
return result
except Exception as ex:
LOG.error('ERROR: TOX DNS loading error with requests: ' + str(ex))
elif not self._settings['proxy_type']: # no proxy
if not self._settings['proxy_type']: # no proxy
for url in urls:
try:
return self._send_request(url, data)
except Exception as ex:
LOG.error('ERROR: TOX DNS ' + str(ex))
util.log('TOX DNS ERROR: ' + str(ex))
else: # proxy
netman = QtNetwork.QNetworkAccessManager()
proxy = QtNetwork.QNetworkProxy()
@ -93,6 +60,6 @@ class ToxDns:
if not result['c']:
return result['tox_id']
except Exception as ex:
LOG.error('ERROR: TOX DNS ' + str(ex))
util.log('TOX DNS ERROR: ' + str(ex))
return None # error

View File

@ -3,9 +3,6 @@ import wave
import pyaudio
import os.path
global LOG
import logging
LOG = logging.getLogger('app.'+__name__)
SOUND_NOTIFICATION = {
'MESSAGE': 0,
@ -28,19 +25,9 @@ class AudioFile:
def play(self):
data = self.wf.readframes(self.chunk)
try:
while data:
self.stream.write(data)
data = self.wf.readframes(self.chunk)
except Exception as e:
LOG.error(f"Error during AudioFile play {e!s}")
LOG.debug("Error during AudioFile play " \
+' rate=' +str(self.wf.getframerate()) \
+ 'format=' +str(self.p.get_format_from_width(self.wf.getsampwidth())) \
+' channels=' +str(self.wf.getnchannels()) \
)
raise
while data:
self.stream.write(data)
data = self.wf.readframes(self.chunk)
def close(self):
self.stream.close()

View File

@ -10,7 +10,7 @@ def tray_notification(title, text, tray, window):
:param tray: ref to tray icon
:param window: main window
"""
if tray and QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
if QtWidgets.QSystemTrayIcon.isSystemTrayAvailable():
if len(text) > 30:
text = text[:27] + '...'
tray.showMessage(title, text, QtWidgets.QSystemTrayIcon.NoIcon, 3000)

View File

@ -1,4 +1,3 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import utils.util as util
import os
import importlib
@ -6,14 +5,6 @@ import inspect
import plugins.plugin_super_class as pl
import sys
# LOG=util.log
global LOG
import logging
LOG = logging.getLogger('plugin_support')
def trace(msg, *args, **kwargs): LOG._log(0, msg, [])
LOG.trace = trace
log = lambda x: LOG.info(x)
class Plugin:
@ -55,49 +46,38 @@ class PluginLoader:
"""
path = util.get_plugins_directory()
if not os.path.exists(path):
self._app._LOG('WARN: Plugin directory not found: ' + path)
util.log('Plugin dir not found')
return
sys.path.append(path)
else:
sys.path.append(path)
files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
for fl in files:
if fl in ('plugin_super_class.py', '__init__.py') or not fl.endswith('.py'):
continue
base_name = fl[:-3] # module name without .py
name = fl[:-3] # module name without .py
try:
module = importlib.import_module(base_name) # import plugin
LOG.trace('Imported module: ' +base_name +' file: ' +fl)
except ImportError as e:
LOG.warn(f"Import error: {e}" +' file: ' +fl)
module = importlib.import_module(name) # import plugin
except ImportError:
util.log('Import error in module ' + name)
continue
except Exception as ex:
LOG.error('importing ' + base_name + ' Exception: ' + str(ex))
util.log('Exception in module ' + name + ' Exception: ' + str(ex))
continue
for elem in dir(module):
obj = getattr(module, elem)
# looking for plugin class in module
if not inspect.isclass(obj) or not hasattr(obj, 'is_plugin') or not obj.is_plugin:
continue
print('Plugin', elem)
try: # create instance of plugin class
instance = obj(self._app) # name, short_name, app
# needed by bday...
instance._profile=self._app._ms._profile
instance._settings=self._settings
short_name = instance.get_short_name()
is_active = short_name in self._settings['plugins']
instance = obj(self._app)
is_active = instance.get_short_name() in self._settings['plugins']
if is_active:
try:
instance.start()
self._app._log('INFO: Started Plugin ' +short_name)
except Exception as e:
self._app._log.error(f"Starting Plugin ' +short_name +' {e}")
# else: LOG.info('Defined Plugin ' +short_name)
instance.start()
except Exception as ex:
LOG.error('in module ' + short_name + ' Exception: ' + str(ex))
util.log('Exception in module ' + name + ' Exception: ' + str(ex))
continue
short_name = instance.get_short_name()
self._plugins[short_name] = Plugin(instance, is_active)
LOG.info('Added plugin: ' +short_name +' from file: ' +fl)
self._plugins[instance.get_short_name()] = Plugin(instance, is_active)
break
def callback_lossless(self, friend_number, data):
@ -134,7 +114,7 @@ class PluginLoader:
for plugin in self._plugins.values():
try:
result.append([plugin.instance.get_name(), # plugin full name
plugin.is_active, # is enabled
plugin.is_active, # is enabled
plugin.instance.get_description(), # plugin description
plugin.instance.get_short_name()]) # key - short unique name
except:
@ -146,13 +126,7 @@ class PluginLoader:
"""
Return window or None for specified plugin
"""
try:
if key in self._plugins and hasattr(self._plugins[key], 'instance'):
return self._plugins[key].instance.get_window()
except Exception as e:
self._app._log('WARN: ' +key +' _plugins no slot instance: ' +str(e))
return None
return self._plugins[key].instance.get_window()
def toggle_plugin(self, key):
"""
@ -188,7 +162,6 @@ class PluginLoader:
for plugin in self._plugins.values():
if not plugin.is_active:
continue
try:
result.extend(plugin.instance.get_menu(num))
except:
@ -200,10 +173,6 @@ class PluginLoader:
for plugin in self._plugins.values():
if not plugin.is_active:
continue
if not hasattr(plugin.instance, 'get_message_menu'):
name = plugin.instance.get_short_name()
self._app._log('WARN: get_message_menu not found: ' + name)
continue
try:
result.extend(plugin.instance.get_message_menu(menu, selected_text))
except:
@ -220,11 +189,6 @@ class PluginLoader:
del self._plugins[key]
def reload(self):
path = util.get_plugins_directory()
if not os.path.exists(path):
self._app._log('WARN: Plugin directory not found: ' + path)
return
print('Reloading plugins')
self.stop()
self._app._log('INFO: Reloading plugins from ' +path)
self.load()

View File

@ -1,27 +0,0 @@
# Plugins
Repo with plugins for [Toxygen](https://macaw.me/emdee/toxygen/)
For more info visit [plugins.md](https://macaw.me/emdee/toxygen/blob/master/docs/plugins.md) and [plugin_api.md](https://github.com/toxygen-project[/toxygen/blob/master/docs/plugin-api.md)
# Plugins list:
- ToxId - share your Tox ID and copy friend's Tox ID easily.
- MarqueeStatus - create ticker from your status message.
- BirthDay - get notifications on your friends' birthdays.
- Bot - bot which can communicate with your friends when you are away.
- SearchPlugin - select text in message and find it in search engine.
- AutoAwayStatusLinux - sets "Away" status when user is inactive (Linux only).
- AutoAwayStatusWindows - sets "Away" status when user is inactive (Windows only).
- Chess - play chess with your friends using Tox.
- Garland - changes your status like it's garland.
- AutoAnswer - calls auto answering.
- uToxInlineSending - send inlines with the same name as uTox does.
- AvatarEncryption - encrypt all avatars using profile password
## Hard fork
Not all of these are working...
Work on this project is suspended until the
[MultiDevice](https://git.plastiras.org/emdee/tox_profile/wiki/MultiDevice-Announcements-POC) problem is solved. Fork me!

View File

@ -1,83 +0,0 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import plugin_super_class
import json
from user_data import settings
import os
from bootstrap.bootstrap import get_user_config_path
class AvatarEncryption(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super(AvatarEncryption, self).__init__('AvatarEncryption', 'ae', *args)
self._path = os.path.join(get_user_config_path(), 'avatars')
self._app = args[0]
self._profile = self._app._ms._profile
self._window = None
#was self._contacts = self._profile._contacts[:]
self._contacts = self._profile._contacts_provider.get_all_friends()
def get_description(self):
return QApplication.translate("AvatarEncryption", 'Encrypt all avatars using profile password.')
def close(self):
if not self._encrypt_save.has_password():
return
i, data = 1, {}
self.save_contact_avatar(data, self._profile, 0)
for friend in self._contacts:
self.save_contact_avatar(data, friend, i)
i += 1
self.save_settings(json.dumps(data))
def start(self):
if not self._encrypt_save.has_password():
return
data = json.loads(self.load_settings())
self.load_contact_avatar(data, self._profile)
for friend in self._contacts:
self.load_contact_avatar(data, friend)
self._profile.update()
def save_contact_avatar(self, data, contact, i):
tox_id = contact.tox_id[:64]
data[str(tox_id)] = str(i)
path = os.path.join(self._path, tox_id + '.png')
if os.path.isfile(path):
with open(path, 'rb') as fl:
avatar = fl.read()
encr_avatar = self._encrypt_save.pass_encrypt(avatar)
with open(os.path.join(self._path, self._settings.name + '_' + str(i) + '.png'), 'wb') as fl:
fl.write(encr_avatar)
os.remove(path)
def load_contact_avatar(self, data, contact):
tox_id = str(contact.tox_id[:64])
if tox_id not in data:
return
path = os.path.join(self._path, self._settings.name + '_' + data[tox_id] + '.png')
if os.path.isfile(path):
with open(path, 'rb') as fl:
avatar = fl.read()
decr_avatar = self._encrypt_save.pass_decrypt(avatar)
with open(os.path.join(self._path, str(tox_id) + '.png'), 'wb') as fl:
fl.write(decr_avatar)
os.remove(path)
contact.load_avatar()
def load_settings(self):
try:
with open(plugin_super_class.path_to_data(self._short_name) + self._settings.name + '.json', 'rb') as fl:
data = fl.read()
return str(self._encrypt_save.pass_decrypt(data), 'utf-8') if data else '{}'
except:
return '{}'
def save_settings(self, data):
try:
data = self._encrypt_save.pass_encrypt(bytes(data, 'utf-8'))
with open(plugin_super_class.path_to_data(self._short_name) + self._settings.name + '.json', 'wb') as fl:
fl.write(data)
except:
pass

View File

@ -1,111 +0,0 @@
import plugin_super_class
import threading
import time
from PyQt5 import QtCore, QtWidgets
from subprocess import check_output
import json
class InvokeEvent(QtCore.QEvent):
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
def __init__(self, fn, *args, **kwargs):
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
self.fn = fn
self.args = args
self.kwargs = kwargs
class Invoker(QtCore.QObject):
def event(self, event):
event.fn(*event.args, **event.kwargs)
return True
_invoker = Invoker()
def invoke_in_main_thread(fn, *args, **kwargs):
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
class AutoAwayStatusLinux(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super().__init__('AutoAwayStatusLinux', 'awayl', *args)
self._thread = None
self._exec = None
self._active = False
self._time = json.loads(self.load_settings())['time']
self._prev_status = 0
self._app = args[0]
self._profile=self._app._ms._profile
self._window = None
def get_description(self):
return QApplication.translate("AutoAwayStatusLinux", 'sets "Away" status when user is inactive (Linux only).')
def close(self):
self.stop()
def stop(self):
self._exec = False
if self._active:
self._thread.join()
def start(self):
self._exec = True
self._thread = threading.Thread(target=self.loop)
self._thread.start()
def save(self):
self.save_settings('{"time": ' + str(self._time) + '}')
def change_status(self, status=1):
if self._profile.status in (0, 2):
self._prev_status = self._profile.status
if status is not None:
invoke_in_main_thread(self._profile.set_status, status)
def get_window(self):
inst = self
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(QtCore.QRect(450, 300, 350, 100))
self.label = QtWidgets.QLabel(self)
self.label.setGeometry(QtCore.QRect(20, 0, 310, 35))
self.label.setText(QtWidgets.QApplication.translate("AutoAwayStatusLinux", "Auto away time in minutes\n(0 - to disable)"))
self.time = QtWidgets.QLineEdit(self)
self.time.setGeometry(QtCore.QRect(20, 40, 310, 25))
self.time.setText(str(inst._time))
self.setWindowTitle("AutoAwayStatusLinux")
self.ok = QtWidgets.QPushButton(self)
self.ok.setGeometry(QtCore.QRect(20, 70, 310, 25))
self.ok.setText(
QtWidgets.QApplication.translate("AutoAwayStatusLinux", "Save"))
self.ok.clicked.connect(self.update)
def update(self):
try:
t = int(self.time.text())
except:
t = 0
inst._time = t
inst.save()
self.close()
return Window()
def loop(self):
self._active = True
while self._exec:
time.sleep(5)
d = check_output(['xprintidle'])
d = int(d) // 1000
if self._time:
if d > 60 * self._time:
self.change_status()
elif self._profile.status == 1:
self.change_status(self._prev_status)

View File

@ -1,115 +0,0 @@
import plugin_super_class
import threading
import time
from PyQt5 import QtCore, QtWidgets
from ctypes import Structure, windll, c_uint, sizeof, byref
import json
class LASTINPUTINFO(Structure):
_fields_ = [('cbSize', c_uint), ('dwTime', c_uint)]
def get_idle_duration():
lastInputInfo = LASTINPUTINFO()
lastInputInfo.cbSize = sizeof(lastInputInfo)
windll.user32.GetLastInputInfo(byref(lastInputInfo))
millis = windll.kernel32.GetTickCount() - lastInputInfo.dwTime
return millis / 1000.0
class InvokeEvent(QtCore.QEvent):
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
def __init__(self, fn, *args, **kwargs):
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
self.fn = fn
self.args = args
self.kwargs = kwargs
class Invoker(QtCore.QObject):
def event(self, event):
event.fn(*event.args, **event.kwargs)
return True
_invoker = Invoker()
def invoke_in_main_thread(fn, *args, **kwargs):
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
class AutoAwayStatusWindows(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super().__init__('AutoAwayStatusWindows', 'awayw', *args)
self._thread = None
self._exec = None
self._active = False
self._time = json.loads(self.load_settings())['time']
self._prev_status = 0
def close(self):
self.stop()
def stop(self):
self._exec = False
if self._active:
self._thread.join()
def start(self):
self._exec = True
self._thread = threading.Thread(target=self.loop)
self._thread.start()
def save(self):
self.save_settings('{"time": ' + str(self._time) + '}')
def change_status(self, status=1):
if self._profile.status != 1:
self._prev_status = self._profile.status
invoke_in_main_thread(self._profile.set_status, status)
def get_window(self):
inst = self
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(QtCore.QRect(450, 300, 350, 100))
self.label = QtWidgets.QLabel(self)
self.label.setGeometry(QtCore.QRect(20, 0, 310, 35))
self.label.setText(QtWidgets.QApplication.translate("AutoAwayStatusWindows", "Auto away time in minutes\n(0 - to disable)"))
self.time = QtWidgets.QLineEdit(self)
self.time.setGeometry(QtCore.QRect(20, 40, 310, 25))
self.time.setText(str(inst._time))
self.setWindowTitle("AutoAwayStatusWindows")
self.ok = QtWidgets.QPushButton(self)
self.ok.setGeometry(QtCore.QRect(20, 70, 310, 25))
self.ok.setText(
QtWidgets.QApplication.translate("AutoAwayStatusWindows", "Save"))
self.ok.clicked.connect(self.update)
def update(self):
try:
t = int(self.time.text())
except:
t = 0
inst._time = t
inst.save()
self.close()
return Window()
def loop(self):
self._active = True
while self._exec:
time.sleep(5)
d = get_idle_duration()
if self._time:
if d > 60 * self._time:
self.change_status()
elif self._profile.status == 1:
self.change_status(self._prev_status)

View File

@ -1,2 +0,0 @@
SOURCES = bday.py
TRANSLATIONS = bday/en_GB.ts bday/en_US.ts bday/ru_RU.ts

View File

@ -1,95 +0,0 @@
import plugin_super_class
from PyQt5 import QtWidgets, QtCore
import json
import importlib
class BirthDay(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
# Constructor. In plugin __init__ should take only 1 last argument
super(BirthDay, self).__init__('BirthDay', 'bday', *args)
self._data = json.loads(self.load_settings())
self._datetime = importlib.import_module('datetime')
self._timers = []
self._app = args[0]
self._profile=self._app._ms._profile
self._window = None
def start(self):
now = self._datetime.datetime.now()
today = {}
x = self._profile.tox_id[:64]
for key in self._data:
if key != x and key != 'send_date':
arr = self._data[key].split('.')
if int(arr[0]) == now.day and int(arr[1]) == now.month:
today[key] = now.year - int(arr[2])
if len(today):
msgbox = QtWidgets.QMessageBox()
title = QtWidgets.QApplication.translate('BirthDay', "Birthday!")
msgbox.setWindowTitle(title)
text = ', '.join(self._profile.get_friend_by_number(self._tox.friend_by_public_key(x)).name + ' ({})'.format(today[x]) for x in today)
msgbox.setText('Birthdays: ' + text)
msgbox.exec_()
def get_description(self):
return QApplication.translate("BirthDay", "Send and get notifications on your friends' birthdays.")
def get_window(self):
inst = self
x = self._profile.tox_id[:64]
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(QtCore.QRect(450, 300, 350, 150))
self.send = QtWidgets.QCheckBox(self)
self.send.setGeometry(QtCore.QRect(20, 10, 310, 25))
self.send.setText(QtWidgets.QApplication.translate('BirthDay', "Send my birthday date to contacts"))
self.setWindowTitle(QtWidgets.QApplication.translate('BirthDay', "Birthday"))
self.send.clicked.connect(self.update)
self.send.setChecked(inst._data['send_date'])
self.date = QtWidgets.QLineEdit(self)
self.date.setGeometry(QtCore.QRect(20, 50, 310, 25))
self.date.setPlaceholderText(QtWidgets.QApplication.translate('BirthDay', "Date in format dd.mm.yyyy"))
self.set_date = QtWidgets.QPushButton(self)
self.set_date.setGeometry(QtCore.QRect(20, 90, 310, 25))
self.set_date.setText(QtWidgets.QApplication.translate('BirthDay', "Save date"))
self.set_date.clicked.connect(self.save_curr_date)
self.date.setText(inst._data[x] if x in inst._data else '')
def save_curr_date(self):
inst._data[x] = self.date.text()
inst.save_settings(json.dumps(inst._data))
self.close()
def update(self):
inst._data['send_date'] = self.send.isChecked()
inst.save_settings(json.dumps(inst._data))
if not hasattr(self, '_window') or not self._window:
self._window = Window()
return self._window
def lossless_packet(self, data, friend_number):
if len(data):
friend = self._profile.get_friend_by_number(friend_number)
self._data[friend.tox_id] = data
self.save_settings(json.dumps(self._data))
elif self._data['send_date'] and self._profile.tox_id[:64] in self._data:
self.send_lossless(self._data[self._profile.tox_id[:64]], friend_number)
def friend_connected(self, friend_number):
timer = QtCore.QTimer()
timer.timeout.connect(lambda: self.timer(friend_number))
timer.start(10000)
self._timers.append(timer)
def timer(self, friend_number):
timer = self._timers.pop()
timer.stop()
if self._profile.get_friend_by_number(friend_number).tox_id not in self._data:
self.send_lossless('', friend_number)

View File

@ -1,81 +0,0 @@
import plugin_super_class
from PyQt5 import QtCore
import time
class InvokeEvent(QtCore.QEvent):
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
def __init__(self, fn, *args, **kwargs):
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
self.fn = fn
self.args = args
self.kwargs = kwargs
class Invoker(QtCore.QObject):
def event(self, event):
event.fn(*event.args, **event.kwargs)
return True
_invoker = Invoker()
def invoke_in_main_thread(fn, *args, **kwargs):
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
class Bot(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super(Bot, self).__init__('Bot', 'bot', *args)
self._callback = None
self._mode = 0
self._message = "I'm away, will back soon"
self._timer = QtCore.QTimer()
self._timer.timeout.connect(self.initialize)
self._app = args[0]
self._profile=self._app._ms._profile
self._window = None
def get_description(self):
return QApplication.translate("Bot", 'Plugin to answer bot to your friends.')
def start(self):
self._timer.start(10000)
def command(self, command):
if command.startswith('mode '):
self._mode = int(command.split(' ')[-1])
elif command.startswith('message '):
self._message = command[8:]
else:
super().command(command)
def initialize(self):
self._timer.stop()
self._callback = self._tox.friend_message_cb
def incoming_message(tox, friend_number, message_type, message, size, user_data):
self._callback(tox, friend_number, message_type, message, size, user_data)
if self._profile.status == 1: # TOX_USER_STATUS['AWAY']
self.answer(friend_number, str(message, 'utf-8'))
self._tox.callback_friend_message(incoming_message) # , None
def stop(self):
if not self._callback: return
try:
# TypeError: argument must be callable or integer function address
self._tox.callback_friend_message(self._callback) # , None
except: pass
def close(self):
self.stop()
def answer(self, friend_number, message):
if not self._mode:
message = self._message
invoke_in_main_thread(self._profile.send_message, message, friend_number)

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS><TS version="1.1">
<context>
<name>BirthDay</name>
<message>
<location filename="bday.py" line="28"/>
<source>Birthday!</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="44"/>
<source>Send my birthday date to contacts</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="45"/>
<source>Birthday</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="50"/>
<source>Date in format dd.mm.yyyy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="53"/>
<source>Save date</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS><TS version="1.1">
<context>
<name>BirthDay</name>
<message>
<location filename="bday.py" line="28"/>
<source>Birthday!</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="44"/>
<source>Send my birthday date to contacts</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="45"/>
<source>Birthday</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="50"/>
<source>Date in format dd.mm.yyyy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="bday.py" line="53"/>
<source>Save date</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -1,75 +0,0 @@
import plugin_super_class
import threading
import time
from PyQt5 import QtCore
class InvokeEvent(QtCore.QEvent):
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
def __init__(self, fn, *args, **kwargs):
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
self.fn = fn
self.args = args
self.kwargs = kwargs
class Invoker(QtCore.QObject):
def event(self, event):
event.fn(*event.args, **event.kwargs)
return True
_invoker = Invoker()
def invoke_in_main_thread(fn, *args, **kwargs):
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
class Garland(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super(Garland, self).__init__('Garland', 'grlnd', *args)
self._thread = None
self._exec = None
self._time = 3
self._app = args[0]
self._profile=self._app._ms._profile
self._window = None
def get_description(self):
return QApplication.translate("Garland", "Changes your status like it's garland.")
def close(self):
self.stop()
def stop(self):
self._exec = False
self._thread.join()
def start(self):
self._exec = True
self._thread = threading.Thread(target=self.change_status)
self._thread.start()
def command(self, command):
if command.startswith('time'):
self._time = max(int(command.split(' ')[1]), 300) / 1000
else:
super().command(command)
def update(self):
if hasattr(self, '_profile'):
if not hasattr(self._profile, 'status') or not self._profile.status:
retval = 0
else:
retval = (self._profile.status + 1) % 3
self._profile.set_status(retval)
def change_status(self):
time.sleep(5)
while self._exec:
invoke_in_main_thread(self.update)
time.sleep(self._time)

View File

@ -1,86 +0,0 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import plugin_super_class
import threading
import time
from PyQt5 import QtCore
class InvokeEvent(QtCore.QEvent):
EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
def __init__(self, fn, *args, **kwargs):
QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE)
self.fn = fn
self.args = args
self.kwargs = kwargs
class Invoker(QtCore.QObject):
def event(self, event):
event.fn(*event.args, **event.kwargs)
return True
_invoker = Invoker()
def invoke_in_main_thread(fn, *args, **kwargs):
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
class MarqueeStatus(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super(MarqueeStatus, self).__init__('MarqueeStatus', 'mrq', *args)
self._thread = None
self._exec = None
self.active = False
self.left = True
self._app = args[0]
self._profile=self._app._ms._profile
self._window = None
def get_description(self):
return QApplication.translate("MarqueeStatus", 'Create ticker from your status message.')
def close(self):
self.stop()
def stop(self):
self._exec = False
if self.active:
self._thread.join()
def start(self):
self._exec = True
self._thread = threading.Thread(target=self.change_status)
self._thread.start()
def command(self, command):
if command == 'rev':
self.left = not self.left
else:
super(MarqueeStatus, self).command(command)
def set_status_message(self):
message = str(self._profile.status_message)
if self.left:
self._profile.set_status_message(bytes(message[1:] + message[0], 'utf-8'))
else:
self._profile.set_status_message(bytes(message[-1] + message[:-1], 'utf-8'))
def init_status(self):
self._profile.status_message = bytes(self._profile.status_message.strip() + ' ', 'utf-8')
def change_status(self):
self.active = True
if hasattr(self, '_profile'):
tmp = self._profile.status_message
time.sleep(10)
invoke_in_main_thread(self.init_status)
while self._exec:
time.sleep(1)
if self._profile.status is not None:
invoke_in_main_thread(self.set_status_message)
invoke_in_main_thread(self._profile.set_status_message, bytes(tmp, 'utf-8'))
self.active = False

View File

@ -1,4 +1,3 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import os
from PyQt5 import QtCore, QtWidgets
import utils.ui as util_ui
@ -20,7 +19,7 @@ def path_to_data(name):
return os.path.dirname(os.path.realpath(__file__)) + '/' + name + '/'
def log(name, data=''):
def log(name, data):
"""
:param name: plugin unique name
:param data: data for saving in log
@ -48,12 +47,14 @@ class PluginSuperClass(tox_save.ToxSave):
name = name.strip()
short_name = short_name.strip()
if not name or not short_name:
raise NameError('Wrong name or not name or not short_name')
raise NameError('Wrong name')
self._name = name
self._short_name = short_name[:MAX_SHORT_NAME_LENGTH]
self._translator = None # translator for plugin's GUI
# -----------------------------------------------------------------------------------------------------------------
# Get methods
# -----------------------------------------------------------------------------------------------------------------
def get_name(self):
"""
@ -73,7 +74,7 @@ class PluginSuperClass(tox_save.ToxSave):
"""
return self.__doc__
def get_menu(self, menu, row_number=None):
def get_menu(self, row_number):
"""
This method creates items for menu which called on right click in list of friends
:param row_number: number of selected row in list of contacts
@ -96,7 +97,9 @@ class PluginSuperClass(tox_save.ToxSave):
"""
return None
# -----------------------------------------------------------------------------------------------------------------
# Plugin was stopped, started or new command received
# -----------------------------------------------------------------------------------------------------------------
def start(self):
"""
@ -126,7 +129,9 @@ class PluginSuperClass(tox_save.ToxSave):
title = util_ui.tr('List of commands for plugin {}').format(self._name)
util_ui.message_box(text, title)
# -----------------------------------------------------------------------------------------------------------------
# Translations support
# -----------------------------------------------------------------------------------------------------------------
def load_translator(self):
"""
@ -143,7 +148,9 @@ class PluginSuperClass(tox_save.ToxSave):
self._translator.load(path_to_data(self._short_name) + lang_path)
app.installTranslator(self._translator)
# -----------------------------------------------------------------------------------------------------------------
# Settings loading and saving
# -----------------------------------------------------------------------------------------------------------------
def load_settings(self):
"""
@ -162,7 +169,9 @@ class PluginSuperClass(tox_save.ToxSave):
with open(path_to_data(self._short_name) + 'settings.json', 'wb') as fl:
fl.write(bytes(data, 'utf-8'))
# -----------------------------------------------------------------------------------------------------------------
# Callbacks
# -----------------------------------------------------------------------------------------------------------------
def lossless_packet(self, data, friend_number):
"""
@ -186,7 +195,9 @@ class PluginSuperClass(tox_save.ToxSave):
"""
pass
# -----------------------------------------------------------------------------------------------------------------
# Custom packets sending
# -----------------------------------------------------------------------------------------------------------------
def send_lossless(self, data, friend_number):
"""

Binary file not shown.

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.0" language="ru_RU">
<context>
<name>BirthDay</name>
<message>
<location filename="bday.py" line="28"/>
<source>Birthday!</source>
<translation>День рождения!</translation>
</message>
<message>
<location filename="bday.py" line="44"/>
<source>Send my birthday date to contacts</source>
<translation>Отправлять дату моего рождения контактам</translation>
</message>
<message>
<location filename="bday.py" line="45"/>
<source>Birthday</source>
<translation>День рождения</translation>
</message>
<message>
<location filename="bday.py" line="50"/>
<source>Date in format dd.mm.yyyy</source>
<translation>Дата в формате дд.мм.гггг</translation>
</message>
<message>
<location filename="bday.py" line="53"/>
<source>Save date</source>
<translation>Сохранить дату</translation>
</message>
</context>
</TS>

View File

@ -1,2 +0,0 @@
SOURCES = srch.py
TRANSLATIONS = srch/en_GB.ts srch/en_US.ts srch/ru_RU.ts

View File

@ -1,54 +0,0 @@
import plugin_super_class
from PyQt5 import QtGui, QtCore, QtWidgets
class SearchPlugin(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super(SearchPlugin, self).__init__('SearchPlugin', 'srch', *args)
def get_description(self):
return QApplication.translate("SearchPlugin", 'Plugin search with search engines.')
def get_message_menu(self, menu, text):
google = QtWidgets.QAction(
QtWidgets.QApplication.translate("srch", "Find in Google"),
menu)
google.triggered.connect(lambda: self.google(text))
duck = QtWidgets.QAction(
QtWidgets.QApplication.translate("srch", "Find in DuckDuckGo"),
menu)
duck.triggered.connect(lambda: self.duck(text))
yandex = QtWidgets.QAction(
QtWidgets.QApplication.translate("srch", "Find in Yandex"),
menu)
yandex.triggered.connect(lambda: self.yandex(text))
bing = QtWidgets.QAction(
QtWidgets.QApplication.translate("srch", "Find in Bing"),
menu)
bing.triggered.connect(lambda: self.bing(text))
return [duck, google, yandex, bing]
def google(self, text):
url = QtCore.QUrl('https://www.google.com/search?q=' + text)
self.open_url(url)
def duck(self, text):
url = QtCore.QUrl('https://duckduckgo.com/?q=' + text)
self.open_url(url)
def yandex(self, text):
url = QtCore.QUrl('https://yandex.com/search/?text=' + text)
self.open_url(url)
def bing(self, text):
url = QtCore.QUrl('https://www.bing.com/search?q=' + text)
self.open_url(url)
def open_url(self, url):
QtGui.QDesktopServices.openUrl(url)

View File

@ -1,2 +0,0 @@
SOURCES = toxid.py
TRANSLATIONS = toxid/en_GB.ts toxid/en_US.ts toxid/ru_RU.ts

View File

@ -1,136 +0,0 @@
import plugin_super_class
from PyQt5 import QtCore, QtWidgets
import json
class CopyableToxId(plugin_super_class.PluginSuperClass):
def __init__(self, *args):
super(CopyableToxId, self).__init__('CopyableToxId', 'toxid', *args)
self._data = json.loads(self.load_settings())
self._copy = False
self._curr = -1
self._timer = QtCore.QTimer()
self._timer.timeout.connect(lambda: self.timer())
self.load_translator()
self._app = args[0]
self._profile=self._app._ms._profile
self._window = None
def get_description(self):
return QtWidgets.QApplication.translate("TOXID", 'Plugin which allows you to copy TOX ID of your friends easily.')
def get_window(self):
inst = self
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(QtCore.QRect(450, 300, 350, 100))
self.send = QtWidgets.QCheckBox(self)
self.send.setGeometry(QtCore.QRect(20, 10, 310, 25))
self.send.setText(QtWidgets.QApplication.translate("TOXID", "Send my TOX ID to contacts"))
self.setWindowTitle(QtWidgets.QApplication.translate("TOXID", "CopyableToxID"))
self.send.clicked.connect(self.update)
self.send.setChecked(inst._data['send_id'])
self.help = QtWidgets.QPushButton(self)
self.help.setGeometry(QtCore.QRect(20, 40, 200, 25))
self.help.setText(QtWidgets.QApplication.translate("TOXID", "List of commands"))
self.help.clicked.connect(lambda: inst.command('help'))
def update(self):
inst._data['send_id'] = self.send.isChecked()
inst.save_settings(json.dumps(inst._data))
if not hasattr(self, '_window') or not self._window:
self._window = Window()
return self._window
def lossless_packet(self, data, friend_number):
if len(data):
self._data['id'] = list(filter(lambda x: not x.startswith(data[:64]), self._data['id']))
self._data['id'].append(data)
if self._copy:
self._timer.stop()
self._copy = False
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(data)
self.save_settings(json.dumps(self._data))
elif self._data['send_id']:
self.send_lossless(self._tox.self_get_address(), friend_number)
def error(self):
msgbox = QtWidgets.QMessageBox()
title = QtWidgets.QApplication.translate("TOXID", "Error")
msgbox.setWindowTitle(title.format(self._name))
text = QtWidgets.QApplication.translate("TOXID", "Tox ID cannot be copied")
msgbox.setText(text)
msgbox.exec_()
def timer(self):
self._copy = False
if self._curr + 1:
public_key = self._tox.friend_get_public_key(self._curr)
self._curr = -1
arr = list(filter(lambda x: x.startswith(public_key), self._data['id']))
if len(arr):
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(arr[0])
else:
self.error()
else:
self.error()
self._timer.stop()
def friend_connected(self, friend_number):
self.send_lossless('', friend_number)
def command(self, text):
if text == 'copy':
num = self._profile.get_active_number()
if num == -1:
return
elif text.startswith('copy '):
num = int(text[5:])
if num < 0:
return
elif text == 'enable':
self._copy = True
return
elif text == 'disable':
self._copy = False
return
elif text == 'help':
msgbox = QtWidgets.QMessageBox()
title = QtWidgets.QApplication.translate("TOXID", "List of commands for plugin CopyableToxID")
msgbox.setWindowTitle(title)
text = QtWidgets.QApplication.translate("TOXID", """Commands:
copy: copy TOX ID of current friend
copy <friend_number>: copy TOX ID of friend with specified number
enable: allow send your TOX ID to friends
disable: disallow send your TOX ID to friends
help: show this help""")
msgbox.setText(text)
msgbox.exec_()
return
else:
return
public_key = self._tox.friend_get_public_key(num)
arr = list(filter(lambda x: x.startswith(public_key), self._data['id']))
if self._profile.get_friend_by_number(num).status is None and len(arr):
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(arr[0])
elif self._profile.get_friend_by_number(num).status is not None:
self._copy = True
self._curr = num
self.send_lossless('', num)
self._timer.start(2000)
else:
self.error()
def get_menu(self, menu, num):
act = QtWidgets.QAction(QtWidgets.QApplication.translate("TOXID", "Copy TOX ID"), menu)
friend = self._profile.get_friend(num)
act.connect(act, QtCore.SIGNAL("triggered()"), lambda: self.command('copy ' + str(friend.number)))
return [act]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

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