Compare commits
80 Commits
cc40380488
...
main
Author | SHA1 | Date | |
---|---|---|---|
476436ab78 | |||
ffceffdc4b | |||
cbac502cbf | |||
8b3b9c87c1 | |||
283d0d2f95 | |||
6e3dd26dad | |||
11f8169e2b | |||
de1a8bdd0d | |||
3a09db186c | |||
479ae92da7 | |||
eb7287e659 | |||
4fc9a23961 | |||
fa7790eadf | |||
7759dbce8d | |||
db070348b5 | |||
477d315890 | |||
75ac0b2e07 | |||
74a29f7ce3 | |||
6aca75d2cb | |||
054e9ce8ce | |||
aef6f0d9d7 | |||
4987618fba | |||
6deea64979 | |||
a3470c1bbe | |||
1e42592027 | |||
22d8d8d8d9 | |||
6e04bdc163 | |||
c87f4a18e3 | |||
7338667e7e | |||
09502f6fc7 | |||
3efe800664 | |||
7326ed926f | |||
3acee2404a | |||
012c7ea56e | |||
b934928fe3 | |||
daee891825 | |||
bc35421760 | |||
9c9ce9fec8 | |||
da3602293a | |||
ec39c353d6 | |||
cd3b636393 | |||
d034bafd4a | |||
aedf36cda2 | |||
9111a1def8 | |||
18195f287c | |||
8ab22f32bd | |||
48b4555a04 | |||
c6282d17c6 | |||
1aa831cbd6 | |||
ad59434927 | |||
7764404b9a | |||
33734e1efd | |||
b7f6d09761 | |||
0b4eda648e | |||
9144fa536f | |||
c4a31b8a56 | |||
41d9fe95c1 | |||
a3ad33495a | |||
564e24fba5 | |||
14c7156308 | |||
048caad578 | |||
4e44a559e7 | |||
bccd262a68 | |||
b5a9361eb3 | |||
cd661dd819 | |||
4d51ddbeb3 | |||
8018e5a89e | |||
4f1f68e438 | |||
afc8a0f9a0 | |||
68c193cbd1 | |||
f9fc1c25ae | |||
3fcb0d582f | |||
5a938b40b4 | |||
2187568ef8 | |||
e7ef810508 | |||
83d0e1998f | |||
ffac4e4f52 | |||
702929615d | |||
403f5e7468 | |||
c8e0adcb3e |
19
.gitea/workflows/demo.yaml
Normal file
19
.gitea/workflows/demo.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
name: Gitea Actions Demo
|
||||||
|
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Explore-Gitea-Actions:
|
||||||
|
runs-on: linux-amd64
|
||||||
|
steps:
|
||||||
|
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
|
||||||
|
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
|
||||||
|
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||||
|
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
||||||
|
- name: List files in the repository
|
||||||
|
run: |
|
||||||
|
ls ${{ gitea.workspace }}
|
||||||
|
- run: echo "🍏 This job's status is ${{ job.status }}."
|
12
.gitignore
vendored
12
.gitignore
vendored
@ -3,6 +3,11 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
*.diff
|
||||||
|
*.good
|
||||||
|
*.bad
|
||||||
|
*.junk
|
||||||
|
*.bak
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.so
|
||||||
@ -162,3 +167,10 @@ cython_debug/
|
|||||||
|
|
||||||
.pylint.err
|
.pylint.err
|
||||||
.pylint.log
|
.pylint.log
|
||||||
|
.pylint.out
|
||||||
|
|
||||||
|
*.dst
|
||||||
|
|
||||||
|
*~
|
||||||
|
.rsync.sh
|
||||||
|
.pyanal.out
|
||||||
|
8
.pyanal.sh
Normal file
8
.pyanal.sh
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
ROLE=logging
|
||||||
|
|
||||||
|
PYTHONPATH=$PWD/src /var/local/bin/python3.bash `which pyanalyze` \
|
||||||
|
src/tox_wrapper/tox.py src/tox_wrapper/tests/tests_wrapper.py \
|
||||||
|
> .pyanal.out 2>&1
|
||||||
|
|
385
.pylint.rc
Normal file
385
.pylint.rc
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
# is file was generated by edx-lint: https://github.com/edx/edx-lint
|
||||||
|
#
|
||||||
|
# If you want to change this file, you have two choices, depending on whether
|
||||||
|
# you want to make a local change that applies only to this repo, or whether
|
||||||
|
# you want to make a central change that applies to all repos using edx-lint.
|
||||||
|
#
|
||||||
|
# Note: If your pylintrc file is simply out-of-date relative to the latest
|
||||||
|
# pylintrc in edx-lint, ensure you have the latest edx-lint installed
|
||||||
|
# and then follow the steps for a "LOCAL CHANGE".
|
||||||
|
#
|
||||||
|
# LOCAL CHANGE:
|
||||||
|
#
|
||||||
|
# 1. Edit the local pylintrc_tweaks file to add changes just to this
|
||||||
|
# repo's file.
|
||||||
|
#
|
||||||
|
# 2. Run:
|
||||||
|
#
|
||||||
|
# $ edx_lint write pylintrc
|
||||||
|
#
|
||||||
|
# 3. This will modify the local file. Submit a pull request to get it
|
||||||
|
# checked in so that others will benefit.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# CENTRAL CHANGE:
|
||||||
|
#
|
||||||
|
# 1. Edit the pylintrc file in the edx-lint repo at
|
||||||
|
# https://github.com/edx/edx-lint/blob/master/edx_lint/files/pylintrc
|
||||||
|
#
|
||||||
|
# 2. install the updated version of edx-lint (in edx-lint):
|
||||||
|
#
|
||||||
|
# $ pip install .
|
||||||
|
#
|
||||||
|
# 3. Run (in edx-lint):
|
||||||
|
#
|
||||||
|
# $ edx_lint write pylintrc
|
||||||
|
#
|
||||||
|
# 4. Make a new version of edx_lint, submit and review a pull request with the
|
||||||
|
# pylintrc update, and after merging, update the edx-lint version and
|
||||||
|
# publish the new version.
|
||||||
|
#
|
||||||
|
# 5. In your local repo, install the newer version of edx-lint.
|
||||||
|
#
|
||||||
|
# 6. Run:
|
||||||
|
#
|
||||||
|
# $ edx_lint write pylintrc
|
||||||
|
#
|
||||||
|
# 7. This will modify the local file. Submit a pull request to get it
|
||||||
|
# checked in so that others will benefit.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# STAY AWAY FROM THIS FILE!
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# SERIOUSLY.
|
||||||
|
#
|
||||||
|
# ------------------------------
|
||||||
|
# Generated by edx-lint version: 5.2.3
|
||||||
|
# ------------------------------
|
||||||
|
[MASTER]
|
||||||
|
ignore = ,input
|
||||||
|
persistent = yes
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
enable =
|
||||||
|
blacklisted-name,
|
||||||
|
# line-too-long,
|
||||||
|
|
||||||
|
abstract-class-instantiated,
|
||||||
|
abstract-method,
|
||||||
|
access-member-before-definition,
|
||||||
|
anomalous-backslash-in-string,
|
||||||
|
anomalous-unicode-escape-in-string,
|
||||||
|
arguments-differ,
|
||||||
|
assert-on-tuple,
|
||||||
|
assigning-non-slot,
|
||||||
|
assignment-from-no-return,
|
||||||
|
assignment-from-none,
|
||||||
|
attribute-defined-outside-init,
|
||||||
|
bad-except-order,
|
||||||
|
bad-format-character,
|
||||||
|
bad-format-string-key,
|
||||||
|
bad-format-string,
|
||||||
|
bad-open-mode,
|
||||||
|
bad-reversed-sequence,
|
||||||
|
bad-staticmethod-argument,
|
||||||
|
bad-str-strip-call,
|
||||||
|
bad-super-call,
|
||||||
|
binary-op-exception,
|
||||||
|
boolean-datetime,
|
||||||
|
catching-non-exception,
|
||||||
|
cell-var-from-loop,
|
||||||
|
confusing-with-statement,
|
||||||
|
continue-in-finally,
|
||||||
|
dangerous-default-value,
|
||||||
|
duplicate-argument-name,
|
||||||
|
duplicate-bases,
|
||||||
|
duplicate-except,
|
||||||
|
duplicate-key,
|
||||||
|
expression-not-assigned,
|
||||||
|
format-combined-specification,
|
||||||
|
format-needs-mapping,
|
||||||
|
function-redefined,
|
||||||
|
global-variable-undefined,
|
||||||
|
import-error,
|
||||||
|
import-self,
|
||||||
|
inconsistent-mro,
|
||||||
|
inherit-non-class,
|
||||||
|
init-is-generator,
|
||||||
|
invalid-all-object,
|
||||||
|
invalid-format-index,
|
||||||
|
invalid-length-returned,
|
||||||
|
invalid-sequence-index,
|
||||||
|
invalid-slice-index,
|
||||||
|
invalid-slots-object,
|
||||||
|
invalid-slots,
|
||||||
|
invalid-unary-operand-type,
|
||||||
|
logging-too-few-args,
|
||||||
|
logging-too-many-args,
|
||||||
|
logging-unsupported-format,
|
||||||
|
lost-exception,
|
||||||
|
method-hidden,
|
||||||
|
misplaced-bare-raise,
|
||||||
|
misplaced-future,
|
||||||
|
missing-format-argument-key,
|
||||||
|
missing-format-attribute,
|
||||||
|
missing-format-string-key,
|
||||||
|
no-member,
|
||||||
|
no-method-argument,
|
||||||
|
no-name-in-module,
|
||||||
|
no-self-argument,
|
||||||
|
no-value-for-parameter,
|
||||||
|
non-iterator-returned,
|
||||||
|
nonexistent-operator,
|
||||||
|
not-a-mapping,
|
||||||
|
not-an-iterable,
|
||||||
|
not-callable,
|
||||||
|
not-context-manager,
|
||||||
|
not-in-loop,
|
||||||
|
pointless-statement,
|
||||||
|
pointless-string-statement,
|
||||||
|
raising-bad-type,
|
||||||
|
raising-non-exception,
|
||||||
|
redefined-builtin,
|
||||||
|
redefined-outer-name,
|
||||||
|
redundant-keyword-arg,
|
||||||
|
repeated-keyword,
|
||||||
|
return-arg-in-generator,
|
||||||
|
return-in-init,
|
||||||
|
return-outside-function,
|
||||||
|
signature-differs,
|
||||||
|
super-init-not-called,
|
||||||
|
syntax-error,
|
||||||
|
too-few-format-args,
|
||||||
|
too-many-format-args,
|
||||||
|
too-many-function-args,
|
||||||
|
truncated-format-string,
|
||||||
|
undefined-all-variable,
|
||||||
|
undefined-loop-variable,
|
||||||
|
undefined-variable,
|
||||||
|
unexpected-keyword-arg,
|
||||||
|
unexpected-special-method-signature,
|
||||||
|
unpacking-non-sequence,
|
||||||
|
unreachable,
|
||||||
|
unsubscriptable-object,
|
||||||
|
unsupported-binary-operation,
|
||||||
|
unsupported-membership-test,
|
||||||
|
unused-format-string-argument,
|
||||||
|
unused-format-string-key,
|
||||||
|
used-before-assignment,
|
||||||
|
using-constant-test,
|
||||||
|
yield-outside-function,
|
||||||
|
|
||||||
|
astroid-error,
|
||||||
|
fatal,
|
||||||
|
method-check-failed,
|
||||||
|
parse-error,
|
||||||
|
raw-checker-failed,
|
||||||
|
|
||||||
|
empty-docstring,
|
||||||
|
invalid-characters-in-docstring,
|
||||||
|
# missing-docstring,
|
||||||
|
# wrong-spelling-in-comment,
|
||||||
|
# wrong-spelling-in-docstring,
|
||||||
|
|
||||||
|
unused-argument,
|
||||||
|
unused-import,
|
||||||
|
unused-variable,
|
||||||
|
|
||||||
|
eval-used,
|
||||||
|
exec-used,
|
||||||
|
|
||||||
|
bad-classmethod-argument,
|
||||||
|
bad-mcs-classmethod-argument,
|
||||||
|
bad-mcs-method-argument,
|
||||||
|
bare-except,
|
||||||
|
broad-except,
|
||||||
|
consider-iterating-dictionary,
|
||||||
|
consider-using-enumerate,
|
||||||
|
global-variable-not-assigned,
|
||||||
|
logging-format-interpolation,
|
||||||
|
# logging-not-lazy,
|
||||||
|
multiple-imports,
|
||||||
|
multiple-statements,
|
||||||
|
no-classmethod-decorator,
|
||||||
|
no-staticmethod-decorator,
|
||||||
|
protected-access,
|
||||||
|
redundant-unittest-assert,
|
||||||
|
reimported,
|
||||||
|
simplifiable-if-statement,
|
||||||
|
singleton-comparison,
|
||||||
|
superfluous-parens,
|
||||||
|
unidiomatic-typecheck,
|
||||||
|
unnecessary-lambda,
|
||||||
|
unnecessary-pass,
|
||||||
|
unnecessary-semicolon,
|
||||||
|
unneeded-not,
|
||||||
|
useless-else-on-loop,
|
||||||
|
|
||||||
|
deprecated-method,
|
||||||
|
deprecated-module,
|
||||||
|
|
||||||
|
too-many-boolean-expressions,
|
||||||
|
too-many-nested-blocks,
|
||||||
|
too-many-statements,
|
||||||
|
|
||||||
|
# wildcard-import,
|
||||||
|
# wrong-import-order,
|
||||||
|
# wrong-import-position,
|
||||||
|
|
||||||
|
missing-final-newline,
|
||||||
|
mixed-line-endings,
|
||||||
|
trailing-newlines,
|
||||||
|
# trailing-whitespace,
|
||||||
|
unexpected-line-ending-format,
|
||||||
|
|
||||||
|
bad-inline-option,
|
||||||
|
bad-option-value,
|
||||||
|
deprecated-pragma,
|
||||||
|
unrecognized-inline-option,
|
||||||
|
useless-suppression,
|
||||||
|
disable =
|
||||||
|
global-at-module-level,
|
||||||
|
useless-return,
|
||||||
|
#
|
||||||
|
bad-indentation,
|
||||||
|
consider-using-f-string,
|
||||||
|
duplicate-code,
|
||||||
|
f-string-without-interpolation,
|
||||||
|
file-ignored,
|
||||||
|
fixme,
|
||||||
|
global-statement,
|
||||||
|
invalid-name,
|
||||||
|
locally-disabled,
|
||||||
|
no-else-return,
|
||||||
|
## no-self-use,
|
||||||
|
suppressed-message,
|
||||||
|
too-few-public-methods,
|
||||||
|
too-many-ancestors,
|
||||||
|
too-many-arguments,
|
||||||
|
too-many-branches,
|
||||||
|
too-many-instance-attributes,
|
||||||
|
too-many-lines,
|
||||||
|
too-many-locals,
|
||||||
|
too-many-public-methods,
|
||||||
|
too-many-return-statements,
|
||||||
|
ungrouped-imports,
|
||||||
|
unspecified-encoding,
|
||||||
|
unused-wildcard-import,
|
||||||
|
use-maxsplit-arg,
|
||||||
|
wrong-import-order,
|
||||||
|
wrong-import-position,
|
||||||
|
|
||||||
|
logging-not-lazy,
|
||||||
|
line-too-long,
|
||||||
|
import-outside-toplevel,
|
||||||
|
logging-fstring-interpolation,
|
||||||
|
# new
|
||||||
|
missing-module-docstring,
|
||||||
|
missing-class-docstring,
|
||||||
|
missing-function-docstring,
|
||||||
|
use-dict-literal,
|
||||||
|
|
||||||
|
[REPORTS]
|
||||||
|
output-format = text
|
||||||
|
##files-output = no
|
||||||
|
reports = no
|
||||||
|
score = no
|
||||||
|
|
||||||
|
[BASIC]
|
||||||
|
##bad-functions = map,filter,apply,input
|
||||||
|
module-rgx = (([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
|
||||||
|
const-rgx = (([A-Z_][A-Z0-9_]*)|(__.*__)|log|urlpatterns)$
|
||||||
|
class-rgx = [A-Z_][a-zA-Z0-9]+$
|
||||||
|
function-rgx = ([a-z_][a-z0-9_]{2,40}|test_[a-z0-9_]+)$
|
||||||
|
method-rgx = ([a-z_][a-z0-9_]{2,40}|setUp|set[Uu]pClass|tearDown|tear[Dd]ownClass|assert[A-Z]\w*|maxDiff|test_[a-z0-9_]+)$
|
||||||
|
attr-rgx = [a-z_][a-z0-9_]{2,30}$
|
||||||
|
argument-rgx = [a-z_][a-z0-9_]{2,30}$
|
||||||
|
variable-rgx = [a-z_][a-z0-9_]{2,30}$
|
||||||
|
class-attribute-rgx = ([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
|
||||||
|
inlinevar-rgx = [A-Za-z_][A-Za-z0-9_]*$
|
||||||
|
good-names = f,i,j,k,db,ex,Run,_,__
|
||||||
|
bad-names = foo,bar,baz,toto,tutu,tata
|
||||||
|
no-docstring-rgx = __.*__$|test_.+|setUp$|setUpClass$|tearDown$|tearDownClass$|Meta$
|
||||||
|
docstring-min-length = 5
|
||||||
|
|
||||||
|
[FORMAT]
|
||||||
|
max-line-length = 120
|
||||||
|
ignore-long-lines = ^\s*(# )?((<?https?://\S+>?)|(\.\. \w+: .*))$
|
||||||
|
single-line-if-stmt = no
|
||||||
|
##no-space-check = trailing-comma,dict-separator
|
||||||
|
max-module-lines = 1000
|
||||||
|
indent-string = ' '
|
||||||
|
|
||||||
|
[MISCELLANEOUS]
|
||||||
|
notes = FIXME,XXX,TODO
|
||||||
|
|
||||||
|
[SIMILARITIES]
|
||||||
|
min-similarity-lines = 4
|
||||||
|
ignore-comments = yes
|
||||||
|
ignore-docstrings = yes
|
||||||
|
ignore-imports = no
|
||||||
|
|
||||||
|
[TYPECHECK]
|
||||||
|
ignore-mixin-members = yes
|
||||||
|
ignored-classes = SQLObject
|
||||||
|
unsafe-load-any-extension = yes
|
||||||
|
generated-members =
|
||||||
|
REQUEST,
|
||||||
|
acl_users,
|
||||||
|
aq_parent,
|
||||||
|
objects,
|
||||||
|
DoesNotExist,
|
||||||
|
can_read,
|
||||||
|
can_write,
|
||||||
|
get_url,
|
||||||
|
size,
|
||||||
|
content,
|
||||||
|
status_code,
|
||||||
|
create,
|
||||||
|
build,
|
||||||
|
fields,
|
||||||
|
tag,
|
||||||
|
org,
|
||||||
|
course,
|
||||||
|
category,
|
||||||
|
name,
|
||||||
|
revision,
|
||||||
|
_meta,
|
||||||
|
|
||||||
|
[VARIABLES]
|
||||||
|
init-import = no
|
||||||
|
dummy-variables-rgx = _|dummy|unused|.*_unused
|
||||||
|
additional-builtins =
|
||||||
|
|
||||||
|
[CLASSES]
|
||||||
|
defining-attr-methods = __init__,__new__,setUp
|
||||||
|
valid-classmethod-first-arg = cls
|
||||||
|
valid-metaclass-classmethod-first-arg = mcs
|
||||||
|
|
||||||
|
[DESIGN]
|
||||||
|
max-args = 5
|
||||||
|
ignored-argument-names = _.*
|
||||||
|
max-locals = 15
|
||||||
|
max-returns = 9
|
||||||
|
max-branches = 12
|
||||||
|
max-statements = 50
|
||||||
|
max-parents = 7
|
||||||
|
max-attributes = 7
|
||||||
|
min-public-methods = 2
|
||||||
|
max-public-methods = 20
|
||||||
|
|
||||||
|
[IMPORTS]
|
||||||
|
deprecated-modules = regsub,TERMIOS,Bastion,rexec
|
||||||
|
import-graph =
|
||||||
|
ext-import-graph =
|
||||||
|
int-import-graph =
|
||||||
|
|
||||||
|
[EXCEPTIONS]
|
||||||
|
overgeneral-exceptions = BaseException
|
22
.pylint.sh
Normal file
22
.pylint.sh
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
EXE=/usr/local/bin/toxcore_pylint.bash
|
||||||
|
ROLE=toxcore
|
||||||
|
|
||||||
|
$EXE --recursive y --verbose --py-version 3.11 \
|
||||||
|
--output-format colorized --rcfile .pylint.rc \
|
||||||
|
-E -f text src/tox_wrapper/*py src/tox_wrapper/tests/*py > .pylint.err
|
||||||
|
retval=$?
|
||||||
|
|
||||||
|
$EXE --recursive y --verbose --py-version 3.11 \
|
||||||
|
--output-format colorized --rcfile .pylint.rc \
|
||||||
|
src/tox_wrapper/*py src/tox_wrapper/tests/*py > .pylint.out
|
||||||
|
|
||||||
|
sed -e "/Module 'os' has no/d" \
|
||||||
|
-e "/Undefined variable 'app'/d" \
|
||||||
|
-e '/tests\//d' \
|
||||||
|
-e "/Instance of 'Curl' has no /d" \
|
||||||
|
-e "/No name 'path' in module 'os' /d" \
|
||||||
|
-e "/ in module 'os'/d" \
|
||||||
|
-e "/.bak\//d" \
|
||||||
|
-i .pylint.err .pylint.out
|
674
LICENSE.md
Normal file
674
LICENSE.md
Normal file
@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
{one line to give the program's name and a brief idea of what it does.}
|
||||||
|
Copyright (C) {year} {name of author}
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
{project} Copyright (C) {year} {fullname}
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
61
Makefile
Normal file
61
Makefile
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
PREFIX=/usr/local
|
||||||
|
PYTHON_EXE_MSYS=${PREFIX}/bin/python3.sh
|
||||||
|
PIP_EXE_MSYS=${PREFIX}/bin/pip3.sh
|
||||||
|
|
||||||
|
iTEST_TIMEOUT=60
|
||||||
|
fSOCKET_TIMEOUT=15.0
|
||||||
|
PYTHON_MINOR=`python3 --version 2>&1 | sed -e 's@^.* @@' -e 's@\.[0-9]*$$@@'`
|
||||||
|
|
||||||
|
prepare::
|
||||||
|
bash .pylint.sh
|
||||||
|
|
||||||
|
check::
|
||||||
|
PYTHONPATH=$${PWD}/src pyanalyze \
|
||||||
|
src/toxygen_wrapper/tox.py src/toxygen_wrapper/tests/tests_wrapper.py \
|
||||||
|
> .pyanal.out 2>&1
|
||||||
|
|
||||||
|
install::
|
||||||
|
${PIP_EXE_MSYS} --python ${PYTHON_EXE_MSYS} install \
|
||||||
|
--no-deps \
|
||||||
|
--target ${PREFIX}/lib/python${PYTHON_MINOR}/site-packages/ \
|
||||||
|
--upgrade .
|
||||||
|
sed -i -e "1s@/usr/bin/python${PYTHON_MINOR}@${PYTHON_EXE_MSYS}@" \
|
||||||
|
${PREFIX}/lib/python${PYTHON_MINOR}/site-packages/bin/*
|
||||||
|
|
||||||
|
rsync::
|
||||||
|
bash .rsync.sh
|
||||||
|
|
||||||
|
help::
|
||||||
|
PYTHONPATH=$${PWD}/src \
|
||||||
|
$(PYTHON) src/toxygen_wrapper/tests/tests_wrapper.py --help
|
||||||
|
|
||||||
|
test:: test_proxy
|
||||||
|
test_direct::
|
||||||
|
cp -p ${HOME}/.config/tox/DHTnodes.json /tmp/toxygen_nodes.json||true
|
||||||
|
PYTHONPATH=$${PWD}/src \
|
||||||
|
TOR_CONTROLLER_PASSWORD=${PASS} \
|
||||||
|
$(PYTHON_EXE_MSYS) src/toxygen_wrapper/tests/tests_wrapper.py \
|
||||||
|
--norequest=True \
|
||||||
|
--socket_timeout=10.0 \
|
||||||
|
--test_timeout=${iTEST_TIMEOUT} \
|
||||||
|
--nodes_json=/tmp/toxygen_nodes.json \
|
||||||
|
--udp_enabled=True \
|
||||||
|
--trace_enabled=False --loglevel=10
|
||||||
|
|
||||||
|
test_proxy::
|
||||||
|
PYTHONPATH=$${PWD}/src \
|
||||||
|
TOR_CONTROLLER_PASSWORD=${PASS} \
|
||||||
|
${PYTHON_EXE_MSYS} src/toxygen_wrapper/tests/tests_wrapper.py \
|
||||||
|
--norequest=True \
|
||||||
|
--socket_timeout=15.0 \
|
||||||
|
--test_timeout=${iTEST_TIMEOUT} \
|
||||||
|
--proxy_host=127.0.0.1 \
|
||||||
|
--proxy_port=9050 \
|
||||||
|
--proxy_type=2 \
|
||||||
|
--nodes_json=$$HOME/.config/tox/DHTnodes.json \
|
||||||
|
--trace_enabled=False \
|
||||||
|
--loglevel=10
|
||||||
|
|
||||||
|
clean::
|
||||||
|
rm -f .[a-z]*~ *~ */*~ */*/*~
|
||||||
|
rm -rf *.egg-info
|
124
README.md
124
README.md
@ -1,29 +1,127 @@
|
|||||||
# toxygen_wrapper
|
# toxygen_wrapper
|
||||||
|
|
||||||
ctypes wrapping of libtoxcore <https://github.com/TokTok/c-toxcore>
|
[ctypes](https://docs.python.org/3/library/ctypes.html)
|
||||||
into Python. Taken from the now abandoned
|
wrapping of [Tox](https://tox.chat/)
|
||||||
<https://github.com/toxygen-project/toxygen> `next_gen` branch.
|
[```libtoxcore```](https://github.com/TokTok/c-toxcore) into Python.
|
||||||
|
Taken from the ```wrapper``` directory of the now abandoned
|
||||||
|
<https://github.com/toxygen-project/toxygen> ```next_gen``` branch by Ingvar.
|
||||||
|
|
||||||
The basics of NGC groups are supported.
|
There is not complete coverage of the c-toxcore api - they're written to support
|
||||||
|
the [toxygeb](https://git.plastiras.org/emdee/toxygen) client.
|
||||||
|
The basics of NGC groups are supported, as well as AV and toxencryptsave.
|
||||||
|
There is no coverage of conferences as they are not used in ```toxygen```
|
||||||
|
and the list of still unwrapped calls as of Feb. 2024 can be found in
|
||||||
|
```docs/tox.c-toxcore.missing```. The code is typed so that every call in
|
||||||
|
```tox*.py``` should have the right signature, and it runs ```toxygen```
|
||||||
|
with no apparent issues.
|
||||||
|
|
||||||
|
It has been tested with UDP and TCP proxy (Tor). It has ***not*** been
|
||||||
|
tested on Windows, and there may be some minor breakage, which should be
|
||||||
|
easy to fix. There is a good coverage integration testsuite in ```toxygen_wrapper/tests```.
|
||||||
|
Change to that directory and run ```tests_wrapper.py --help```; the test
|
||||||
|
suite gives a good set of examples of usage.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Put the parent of the wrapper directory on your PYTHONPATH and
|
Edit the Makefile and run ```make install``` or ```cd src```
|
||||||
touch a file called `__init__.py` in the parent directory.
|
and run ```toxygen_wrapper/tests/tests_wrapper.py```
|
||||||
|
|
||||||
|
Then you need a ```libs``` directory beside the ```toxygen_wrapper``` directory
|
||||||
|
and you need to link your ```libtoxcore.so``` and ```libtoxav.so```
|
||||||
|
and ```libtoxencryptsave.so``` into it. Link all 3 filenames
|
||||||
|
to ```libtoxcore.so``` if you have only ```libtoxcore.so```
|
||||||
|
(which is usually the case if you built ```c-toxcore``` with ```cmake```
|
||||||
|
rather than ```autogen/configure```). If you want to be different,
|
||||||
|
the environment variable TOXCORE_LIBS overrides the location of ```libs```;
|
||||||
|
look in the file ```toxygen_wrapper/libtox.py``` for the details.
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
|
||||||
|
To test, run ```python3 src/toxygen_wrapper/tests/tests_wrapper.py --help```
|
||||||
|
|
||||||
|
As is, the code in ```tox.py``` is very verbose. Edit the file to change
|
||||||
|
```
|
||||||
|
def LOG_ERROR(a): print('EROR> '+a)
|
||||||
|
def LOG_WARN(a): print('WARN> '+a)
|
||||||
|
def LOG_INFO(a): print('INFO> '+a)
|
||||||
|
def LOG_DEBUG(a): print('DBUG> '+a)
|
||||||
|
def LOG_TRACE(a): pass # print('TRAC> '+a)
|
||||||
|
```
|
||||||
|
to all ```pass #``` or use ```logging.logger``` to suite your tastes.
|
||||||
|
```logging.logger``` can be dangerous in callbacks in ```Qt``` applications,
|
||||||
|
so we use simple print statements as default. The same applies to
|
||||||
|
```toxygen_wrapper/tests/tests_wrapper.py```.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
No prerequisites.
|
No prerequisites in Python3.
|
||||||
|
|
||||||
# Other wrappers
|
Because this is using Ctypes, you can run it under a python-enabled gdb,
|
||||||
|
which if you compiled the c-toxcore library ```-DCMAKE_BUILD_TYPE="Debug"```
|
||||||
|
means that you can run both the Python and the C under gdb. This is HUGE!
|
||||||
|
The incantation is something like this:
|
||||||
|
```
|
||||||
|
gdb -ex r --args /usr/bin/python3 src/toxygen_wrapper/tests/tests_wrapper.py
|
||||||
|
```
|
||||||
|
with some suitable settings of PYTHONPATH and maybe LD_LIBRARY_PATH.
|
||||||
|
|
||||||
|
## Other wrappers
|
||||||
|
|
||||||
There are a number of other wrappings into Python of Tox core.
|
There are a number of other wrappings into Python of Tox core.
|
||||||
This one uses CTYPES which has its merits - there is no need to
|
This one uses [ctypes](https://docs.python.org/3/library/ctypes.html)
|
||||||
recompile anything as with Cython - change the Python file and it's done.
|
which has its merits - there is no need to recompile anything as with
|
||||||
|
Cython - change the Python file and it's done. And you can follow things
|
||||||
|
in a Python debugger, or with the utterly stupendous Python feature of
|
||||||
|
```gdb``` (```gdb -ex r --args /usr/bin/python3.9 <pyfile>```).
|
||||||
|
|
||||||
|
CTYPES code can be brittle, segfaulting if you've got things wrong,
|
||||||
|
but if your wrapping is right, it is very efficient and easy to work on.
|
||||||
|
The [faulthandler](https://docs.python.org/3/library/faulthandler.html)
|
||||||
|
module can be helpful in debugging crashes
|
||||||
|
(e.g. from segmentation faults produced by erroneous C library wrapping).
|
||||||
|
|
||||||
Others include:
|
Others include:
|
||||||
|
|
||||||
* <https://github.com/TokTok/py-toxcore-c> Incomplete and not really
|
* <https://github.com/TokTok/py-toxcore-c> Cython bindings.
|
||||||
actively supported. Maybe it will get worked on in the future,
|
Incomplete and not really actively supported. Maybe it will get
|
||||||
but TokTok seems to be working on java, rust, go, etc. bindings instead.
|
worked on in the future, but TokTok seems to be working on
|
||||||
|
java, go, etc. bindings instead, based on a homebrew generator written
|
||||||
|
in undocumented, uncommented code in a language almost nobody knows, that
|
||||||
|
nobody has installed, written by anonymous coders that are not open to suggestions.
|
||||||
|
There's no active support by ```gdb`` for debugging Cython and python together
|
||||||
|
like there is for cmake and ```gdb```. These bindings have no support for NGC
|
||||||
|
groups; and no significant tests.
|
||||||
|
|
||||||
|
* <https://github.com/oxij/PyTox>
|
||||||
|
forked from https://github.com/aitjcize/PyTox
|
||||||
|
by Wei-Ning Huang <aitjcize@gmail.com>.
|
||||||
|
Hardcore C wrapping which is not easy to keep up to date.
|
||||||
|
No support for NGC but good tests. Abandonned.
|
||||||
|
This was the basis for the TokTok/py-toxcore-c code until recently,
|
||||||
|
and a version is on the 0.2.0 branch of
|
||||||
|
[TokTok/py-toxcore-c](https://github.com/TokTok/py-toxcore-c)
|
||||||
|
|
||||||
|
To our point of view, the ability of these ```ctypes``` to follow code python and C
|
||||||
|
code in the debugger is a crucial advantage.
|
||||||
|
|
||||||
|
## Updates
|
||||||
|
|
||||||
|
Although Tox works over Tor, we do not recommend its usage for
|
||||||
|
anonymity as it leaks DNS requests due to a 6-year old known security
|
||||||
|
issue: https://github.com/TokTok/c-toxcore/issues/469 unless your Tox client
|
||||||
|
does hostname lookups before calling Tox (like toxygen does). Otherwise,
|
||||||
|
do not use it for anonymous communication unless you have a firewall in place.
|
||||||
|
|
||||||
|
The Tox project does not follow semantic versioning of its main structures
|
||||||
|
or setters so the project may break the underlying ctypes wrapper at any time;
|
||||||
|
it's not possible to use Tox version numbers to tell what the API will be.
|
||||||
|
In which case you may have to go into the tox.py file in
|
||||||
|
https://git.plastiras.org/emdee/toxygen_wrapper to fix it yourself.
|
||||||
|
The last tested git commit is 5dd9ee3f65423a4095cacb8396a5d406a27610c7
|
||||||
|
2024-02-10
|
||||||
|
|
||||||
|
Up-to-date code is on https://git.plastiras.org/emdee/toxygen_wrapper
|
||||||
|
|
||||||
|
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!
|
||||||
|
|
||||||
|
0
__init__.py
Normal file
0
__init__.py
Normal file
116
docs/c-toxcore.missing
Normal file
116
docs/c-toxcore.missing
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
tox_callback_conference_connected
|
||||||
|
tox_callback_conference_invite
|
||||||
|
tox_callback_conference_message
|
||||||
|
tox_callback_conference_peer_list_changed
|
||||||
|
tox_callback_conference_peer_name
|
||||||
|
tox_callback_conference_title
|
||||||
|
tox_callback_group_custom_private_packet
|
||||||
|
tox_conference_by_id
|
||||||
|
tox_conference_by_uid
|
||||||
|
tox_conference_delete
|
||||||
|
tox_conference_get_chatlist
|
||||||
|
tox_conference_get_chatlist_size
|
||||||
|
tox_conference_get_id
|
||||||
|
tox_conference_get_title
|
||||||
|
tox_conference_get_title_size
|
||||||
|
tox_conference_get_type
|
||||||
|
tox_conference_get_uid
|
||||||
|
tox_conference_id_size
|
||||||
|
tox_conference_invite
|
||||||
|
tox_conference_join
|
||||||
|
tox_conference_new
|
||||||
|
tox_conference_offline_peer_count
|
||||||
|
tox_conference_offline_peer_get_last_active
|
||||||
|
tox_conference_offline_peer_get_name
|
||||||
|
tox_conference_offline_peer_get_name_size
|
||||||
|
tox_conference_offline_peer_get_public_key
|
||||||
|
tox_conference_peer_count
|
||||||
|
tox_conference_peer_get_name
|
||||||
|
tox_conference_peer_get_name_size
|
||||||
|
tox_conference_peer_get_public_key
|
||||||
|
tox_conference_peer_number_is_ours
|
||||||
|
tox_conference_send_message
|
||||||
|
tox_conference_set_max_offline
|
||||||
|
tox_conference_set_title
|
||||||
|
tox_conference_uid_size
|
||||||
|
tox_file_seek
|
||||||
|
tox_get_salt
|
||||||
|
tox_group_send_custom_private_packet
|
||||||
|
tox_is_data_encrypted
|
||||||
|
tox_options_get_dht_announcements_enabled
|
||||||
|
tox_options_get_end_port
|
||||||
|
tox_options_get_experimental_groups_persistence
|
||||||
|
tox_options_get_experimental_thread_safety
|
||||||
|
tox_options_get_hole_punching_enabled
|
||||||
|
tox_options_get_ipv6_enabled
|
||||||
|
tox_options_get_local_discovery_enabled
|
||||||
|
tox_options_get_log_callback
|
||||||
|
tox_options_get_log_user_data
|
||||||
|
tox_options_get_operating_system
|
||||||
|
tox_options_get_proxy_host
|
||||||
|
tox_options_get_proxy_port
|
||||||
|
tox_options_get_proxy_type
|
||||||
|
tox_options_get_savedata_data
|
||||||
|
tox_options_get_savedata_length
|
||||||
|
tox_options_get_savedata_type
|
||||||
|
tox_options_get_start_port
|
||||||
|
tox_options_get_tcp_port
|
||||||
|
tox_options_get_udp_enabled
|
||||||
|
tox_options_set_dht_announcements_enabled
|
||||||
|
tox_options_set_end_port
|
||||||
|
tox_options_set_experimental_groups_persistence
|
||||||
|
tox_options_set_experimental_thread_safety
|
||||||
|
tox_options_set_hole_punching_enabled
|
||||||
|
tox_options_set_ipv6_enabled
|
||||||
|
tox_options_set_local_discovery_enabled
|
||||||
|
tox_options_set_log_callback
|
||||||
|
tox_options_set_log_user_data
|
||||||
|
tox_options_set_operating_system
|
||||||
|
tox_options_set_proxy_host
|
||||||
|
tox_options_set_proxy_port
|
||||||
|
tox_options_set_proxy_type
|
||||||
|
tox_options_set_savedata_data
|
||||||
|
tox_options_set_savedata_length
|
||||||
|
tox_options_set_savedata_type
|
||||||
|
tox_options_set_start_port
|
||||||
|
tox_options_set_tcp_port
|
||||||
|
tox_options_set_udp_enabled
|
||||||
|
tox_pass_decrypt
|
||||||
|
tox_pass_encrypt
|
||||||
|
tox_pass_encryption_extra_length
|
||||||
|
tox_pass_key_decrypt
|
||||||
|
tox_pass_key_derive
|
||||||
|
tox_pass_key_derive_with_salt
|
||||||
|
tox_pass_key_encrypt
|
||||||
|
tox_pass_key_free
|
||||||
|
tox_pass_key_length
|
||||||
|
tox_pass_salt_length
|
||||||
|
tox_version_is_compatible
|
||||||
|
toxav_add_av_groupchat
|
||||||
|
toxav_answer
|
||||||
|
toxav_audio_iterate
|
||||||
|
toxav_audio_iteration_interval
|
||||||
|
toxav_audio_send_frame
|
||||||
|
toxav_audio_set_bit_rate
|
||||||
|
toxav_call
|
||||||
|
toxav_call_control
|
||||||
|
toxav_callback_audio_bit_rate
|
||||||
|
toxav_callback_audio_receive_frame
|
||||||
|
toxav_callback_call
|
||||||
|
toxav_callback_call_state
|
||||||
|
toxav_callback_video_bit_rate
|
||||||
|
toxav_callback_video_receive_frame
|
||||||
|
toxav_get_tox
|
||||||
|
toxav_group_send_audio
|
||||||
|
toxav_groupchat_av_enabled
|
||||||
|
toxav_groupchat_disable_av
|
||||||
|
toxav_groupchat_enable_av
|
||||||
|
toxav_iterate
|
||||||
|
toxav_iteration_interval
|
||||||
|
toxav_join_av_groupchat
|
||||||
|
toxav_kill
|
||||||
|
toxav_new
|
||||||
|
toxav_video_iterate
|
||||||
|
toxav_video_iteration_interval
|
||||||
|
toxav_video_send_frame
|
||||||
|
toxav_video_set_bit_rate
|
8947
docs/toktok.ltd/spec.html
Normal file
8947
docs/toktok.ltd/spec.html
Normal file
File diff suppressed because it is too large
Load Diff
42
pyproject.toml
Normal file
42
pyproject.toml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
[project]
|
||||||
|
name = "toxygen_wrapper"
|
||||||
|
description = "A Python3 ctypes wrapping of c-toxcore into Python."
|
||||||
|
authors = [{ name = "Ingvar", email = "Ingvar@gitgub.com" } ]
|
||||||
|
requires-python = ">3.7"
|
||||||
|
keywords = ["tox", "python3", "ctypes"]
|
||||||
|
classifiers = [
|
||||||
|
"License :: OSI Approved",
|
||||||
|
"Operating System :: POSIX :: BSD :: FreeBSD",
|
||||||
|
"Operating System :: POSIX :: Linux",
|
||||||
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
|
"Programming Language :: Python :: 3.6",
|
||||||
|
"Programming Language :: Python :: 3.7",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
|
]
|
||||||
|
dynamic = ["version", "readme", ] # cannot be dynamic ['license']
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
toxygen_wrapper_tests = "toxygen_wrapper.tests.tests_wrapper:main"
|
||||||
|
toxygen_echo = "toxygen_wrapper.toxygen_echo:main"
|
||||||
|
|
||||||
|
[tool.setuptools.dynamic]
|
||||||
|
version = {attr = "toxygen_wrapper.__version__"}
|
||||||
|
readme = {file = ["README.md"]}
|
||||||
|
|
||||||
|
[project.license]
|
||||||
|
file = "LICENSE.md"
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
repository = "https://git.plastiras.org/emdee/toxygen_wrapper"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools >= 61.0"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
packages = ["toxygen_wrapper", "toxygen_wrapper.tests"]
|
||||||
|
|
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# the versions are the current ones tested - may work with earlier versions
|
||||||
|
toxygen_wrapper >= 1.0.0
|
||||||
|
yaml
|
||||||
|
msgpack
|
||||||
|
coloredlogs
|
||||||
|
# nmap
|
57
setup.cfg
Normal file
57
setup.cfg
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
[metadata]
|
||||||
|
classifiers =
|
||||||
|
License :: OSI Approved
|
||||||
|
Intended Audience :: Web Developers
|
||||||
|
Operating System :: POSIX :: BSD :: FreeBSD
|
||||||
|
Operating System :: POSIX :: Linux
|
||||||
|
Programming Language :: Python :: 3 :: Only
|
||||||
|
Programming Language :: Python :: 3.6
|
||||||
|
Programming Language :: Python :: 3.7
|
||||||
|
Programming Language :: Python :: 3.8
|
||||||
|
Programming Language :: Python :: 3.9
|
||||||
|
Programming Language :: Python :: 3.10
|
||||||
|
Programming Language :: Python :: 3.11
|
||||||
|
Programming Language :: Python :: Implementation :: CPython
|
||||||
|
description='Tox ctypes wrapping into Python'
|
||||||
|
long_description='Tox ctypes wrapping of c-toxcore into Python3'
|
||||||
|
url='https://git.plastiras.org/emdee/toxygen_wrapper/'
|
||||||
|
keywords='ctypes Tox messenger'
|
||||||
|
|
||||||
|
[options]
|
||||||
|
zip_safe = false
|
||||||
|
python_requires = >=3.6
|
||||||
|
package_dir=
|
||||||
|
=src
|
||||||
|
packages = ["toxygen_wrapper", "toxygen_wrapper.tests"]
|
||||||
|
include_package_data =
|
||||||
|
"*" = ["*.txt"]
|
||||||
|
install_requires =
|
||||||
|
coloredlogs
|
||||||
|
|
||||||
|
[easy_install]
|
||||||
|
zip_ok = false
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
jobs = 1
|
||||||
|
max-line-length = 88
|
||||||
|
ignore =
|
||||||
|
E111
|
||||||
|
E114
|
||||||
|
E128
|
||||||
|
E225
|
||||||
|
E261
|
||||||
|
E302
|
||||||
|
E305
|
||||||
|
E402
|
||||||
|
E501
|
||||||
|
E502
|
||||||
|
E541
|
||||||
|
E701
|
||||||
|
E702
|
||||||
|
E704
|
||||||
|
E722
|
||||||
|
E741
|
||||||
|
F508
|
||||||
|
F541
|
||||||
|
W503
|
||||||
|
W601
|
33
setup.py
Normal file
33
setup.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from setuptools import setup
|
||||||
|
from setuptools.command.install import install
|
||||||
|
|
||||||
|
version = '1.0.0'
|
||||||
|
|
||||||
|
setup(name='tox_wrapper',
|
||||||
|
version=version,
|
||||||
|
description='Tox ctypes wrapping into Python',
|
||||||
|
long_description='Tox ctypes wrapping of c-toxcore into Python3',
|
||||||
|
url='https://git.plastiras.org/emdee/toxygen/',
|
||||||
|
keywords='ctypes Tox messenger',
|
||||||
|
author='Ingvar',
|
||||||
|
maintainer='',
|
||||||
|
license='GPL3',
|
||||||
|
packages=['tox_wrapper'],
|
||||||
|
install_requires=['ctypes'],
|
||||||
|
include_package_data=True,
|
||||||
|
# dont run directly if you need a proxy
|
||||||
|
# run python3 tox_wrapper/tests/tests_wrapper.py --help
|
||||||
|
test_suite="tox_wrapper.tests.tests_wrapper.main",
|
||||||
|
classifiers=[
|
||||||
|
"Environment :: Console",
|
||||||
|
"Topic :: Internet",
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"License :: OSI Approved",
|
||||||
|
],
|
||||||
|
zip_safe=False
|
||||||
|
)
|
5
src/__init__.py
Normal file
5
src/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
# You need a libs directory beside this directory
|
||||||
|
# and you need to link your libtoxcore.so and libtoxav.so
|
||||||
|
# and libtoxencryptsave.so into ../libs/
|
||||||
|
# Link all 3 to libtoxcore.so if you have only libtoxcore.so
|
7
src/toxygen_wrapper/__init__.py
Normal file
7
src/toxygen_wrapper/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
# You need a libs directory beside this directory
|
||||||
|
# and you need to link your libtoxcore.so and libtoxav.so
|
||||||
|
# and libtoxencryptsave.so into ../libs/
|
||||||
|
# Link all 3 to libtoxcore.so if you have only libtoxcore.so
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
@ -14,6 +14,14 @@ except ImportError:
|
|||||||
sLIBS_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
sLIBS_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
||||||
'libs')
|
'libs')
|
||||||
|
|
||||||
|
# environment variable TOXCORE_LIBS overrides
|
||||||
|
d = os.environ.get('TOXCORE_LIBS', '')
|
||||||
|
if d and os.path.exists(d):
|
||||||
|
sLIBS_DIR = d
|
||||||
|
if os.environ.get('DEBUG', ''):
|
||||||
|
print ('DBUG: Setting TOXCORE_LIBS to ' +d)
|
||||||
|
del d
|
||||||
|
|
||||||
class LibToxCore:
|
class LibToxCore:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -28,7 +36,6 @@ class LibToxCore:
|
|||||||
# libtoxcore and libsodium may be installed in your os
|
# libtoxcore and libsodium may be installed in your os
|
||||||
# give libs/ precedence
|
# give libs/ precedence
|
||||||
libFile = os.path.join(sLIBS_DIR, libtoxcore)
|
libFile = os.path.join(sLIBS_DIR, libtoxcore)
|
||||||
assert os.path.isfile(libFile), libFile
|
|
||||||
if os.path.isfile(libFile):
|
if os.path.isfile(libFile):
|
||||||
self._libtoxcore = CDLL(libFile)
|
self._libtoxcore = CDLL(libFile)
|
||||||
else:
|
else:
|
||||||
@ -48,7 +55,6 @@ class LibToxAV:
|
|||||||
self._libtoxav = CDLL('libtoxcore.dylib')
|
self._libtoxav = CDLL('libtoxcore.dylib')
|
||||||
else:
|
else:
|
||||||
libFile = os.path.join(sLIBS_DIR, 'libtoxav.so')
|
libFile = os.path.join(sLIBS_DIR, 'libtoxav.so')
|
||||||
assert os.path.isfile(libFile), libFile
|
|
||||||
if os.path.isfile(libFile):
|
if os.path.isfile(libFile):
|
||||||
self._libtoxav = CDLL(libFile)
|
self._libtoxav = CDLL(libFile)
|
||||||
else:
|
else:
|
||||||
@ -70,7 +76,6 @@ class LibToxEncryptSave:
|
|||||||
self._lib_tox_encrypt_save = CDLL('libtoxcore.dylib')
|
self._lib_tox_encrypt_save = CDLL('libtoxcore.dylib')
|
||||||
else:
|
else:
|
||||||
libFile = os.path.join(sLIBS_DIR, 'libtoxencryptsave.so')
|
libFile = os.path.join(sLIBS_DIR, 'libtoxencryptsave.so')
|
||||||
assert os.path.isfile(libFile), libFile
|
|
||||||
if os.path.isfile(libFile):
|
if os.path.isfile(libFile):
|
||||||
self._lib_tox_encrypt_save = CDLL(libFile)
|
self._lib_tox_encrypt_save = CDLL(libFile)
|
||||||
else:
|
else:
|
0
src/toxygen_wrapper/tests/__init__.py
Normal file
0
src/toxygen_wrapper/tests/__init__.py
Normal file
391
src/toxygen_wrapper/tests/socks.py
Normal file
391
src/toxygen_wrapper/tests/socks.py
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
"""SocksiPy - Python SOCKS module.
|
||||||
|
Version 1.00
|
||||||
|
|
||||||
|
Copyright 2006 Dan-Haim. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
3. Neither the name of Dan Haim nor the names of his contributors may be used
|
||||||
|
to endorse or promote products derived from this software without specific
|
||||||
|
prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||||
|
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||||
|
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
|
||||||
|
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||||
|
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
|
||||||
|
|
||||||
|
|
||||||
|
This module provides a standard socket-like interface for Python
|
||||||
|
for tunneling connections through SOCKS proxies.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
|
||||||
|
for use in PyLoris (http://pyloris.sourceforge.net/)
|
||||||
|
|
||||||
|
Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
|
||||||
|
mainly to merge bug fixes found in Sourceforge
|
||||||
|
|
||||||
|
Minor modifications made by Eugene Dementiev (http://www.dementiev.eu/)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PROXY_TYPE_SOCKS4 = 1
|
||||||
|
PROXY_TYPE_SOCKS5 = 2
|
||||||
|
PROXY_TYPE_HTTP = 3
|
||||||
|
|
||||||
|
_defaultproxy = None
|
||||||
|
_orgsocket = socket.socket
|
||||||
|
|
||||||
|
class ProxyError(Exception): pass
|
||||||
|
class GeneralProxyError(ProxyError): pass
|
||||||
|
class Socks5AuthError(ProxyError): pass
|
||||||
|
class Socks5Error(ProxyError): pass
|
||||||
|
class Socks4Error(ProxyError): pass
|
||||||
|
class HTTPError(ProxyError): pass
|
||||||
|
|
||||||
|
_generalerrors = ("success",
|
||||||
|
"invalid data",
|
||||||
|
"not connected",
|
||||||
|
"not available",
|
||||||
|
"bad proxy type",
|
||||||
|
"bad input")
|
||||||
|
|
||||||
|
_socks5errors = ("succeeded",
|
||||||
|
"general SOCKS server failure",
|
||||||
|
"connection not allowed by ruleset",
|
||||||
|
"Network unreachable",
|
||||||
|
"Host unreachable",
|
||||||
|
"Connection refused",
|
||||||
|
"TTL expired",
|
||||||
|
"Command not supported",
|
||||||
|
"Address type not supported",
|
||||||
|
"Unknown error")
|
||||||
|
|
||||||
|
_socks5autherrors = ("succeeded",
|
||||||
|
"authentication is required",
|
||||||
|
"all offered authentication methods were rejected",
|
||||||
|
"unknown username or invalid password",
|
||||||
|
"unknown error")
|
||||||
|
|
||||||
|
_socks4errors = ("request granted",
|
||||||
|
"request rejected or failed",
|
||||||
|
"request rejected because SOCKS server cannot connect to identd on the client",
|
||||||
|
"request rejected because the client program and identd report different user-ids",
|
||||||
|
"unknown error")
|
||||||
|
|
||||||
|
def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None) -> None:
|
||||||
|
"""setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
|
||||||
|
Sets a default proxy which all further socksocket objects will use,
|
||||||
|
unless explicitly changed.
|
||||||
|
"""
|
||||||
|
global _defaultproxy
|
||||||
|
_defaultproxy = (proxytype, addr, port, rdns, username, password)
|
||||||
|
|
||||||
|
def wrapmodule(module) -> None:
|
||||||
|
"""wrapmodule(module)
|
||||||
|
Attempts to replace a module's socket library with a SOCKS socket. Must set
|
||||||
|
a default proxy using setdefaultproxy(...) first.
|
||||||
|
This will only work on modules that import socket directly into the namespace;
|
||||||
|
most of the Python Standard Library falls into this category.
|
||||||
|
"""
|
||||||
|
if _defaultproxy != None:
|
||||||
|
module.socket.socket = socksocket
|
||||||
|
else:
|
||||||
|
raise GeneralProxyError((4, "no proxy specified"))
|
||||||
|
|
||||||
|
class socksocket(socket.socket):
|
||||||
|
"""socksocket([family[, type[, proto]]]) -> socket object
|
||||||
|
Open a SOCKS enabled socket. The parameters are the same as
|
||||||
|
those of the standard socket init. In order for SOCKS to work,
|
||||||
|
you must specify family=AF_INET, type=SOCK_STREAM and proto=0.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None):
|
||||||
|
_orgsocket.__init__(self, family, type, proto, _sock)
|
||||||
|
if _defaultproxy != None:
|
||||||
|
self.__proxy = _defaultproxy
|
||||||
|
else:
|
||||||
|
self.__proxy = (None, None, None, None, None, None)
|
||||||
|
self.__proxysockname = None
|
||||||
|
self.__proxypeername = None
|
||||||
|
|
||||||
|
def __recvall(self, count):
|
||||||
|
"""__recvall(count) -> data
|
||||||
|
Receive EXACTLY the number of bytes requested from the socket.
|
||||||
|
Blocks until the required number of bytes have been received.
|
||||||
|
"""
|
||||||
|
data = self.recv(count)
|
||||||
|
while len(data) < count:
|
||||||
|
d = self.recv(count-len(data))
|
||||||
|
if not d: raise GeneralProxyError((0, "connection closed unexpectedly"))
|
||||||
|
data = data + d
|
||||||
|
return data
|
||||||
|
|
||||||
|
def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
|
||||||
|
"""setproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
|
||||||
|
Sets the proxy to be used.
|
||||||
|
proxytype - The type of the proxy to be used. Three types
|
||||||
|
are supported: PROXY_TYPE_SOCKS4 (including socks4a),
|
||||||
|
PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
|
||||||
|
addr - The address of the server (IP or DNS).
|
||||||
|
port - The port of the server. Defaults to 1080 for SOCKS
|
||||||
|
servers and 8080 for HTTP proxy servers.
|
||||||
|
rdns - Should DNS queries be preformed on the remote side
|
||||||
|
(rather than the local side). The default is True.
|
||||||
|
Note: This has no effect with SOCKS4 servers.
|
||||||
|
username - Username to authenticate with to the server.
|
||||||
|
The default is no authentication.
|
||||||
|
password - Password to authenticate with to the server.
|
||||||
|
Only relevant when username is also provided.
|
||||||
|
"""
|
||||||
|
self.__proxy = (proxytype, addr, port, rdns, username, password)
|
||||||
|
|
||||||
|
def __negotiatesocks5(self, destaddr, destport):
|
||||||
|
"""__negotiatesocks5(self,destaddr,destport)
|
||||||
|
Negotiates a connection through a SOCKS5 server.
|
||||||
|
"""
|
||||||
|
# First we'll send the authentication packages we support.
|
||||||
|
if (self.__proxy[4]!=None) and (self.__proxy[5]!=None):
|
||||||
|
# The username/password details were supplied to the
|
||||||
|
# setproxy method so we support the USERNAME/PASSWORD
|
||||||
|
# authentication (in addition to the standard none).
|
||||||
|
self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02))
|
||||||
|
else:
|
||||||
|
# No username/password were entered, therefore we
|
||||||
|
# only support connections with no authentication.
|
||||||
|
self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00))
|
||||||
|
# We'll receive the server's response to determine which
|
||||||
|
# method was selected
|
||||||
|
chosenauth = self.__recvall(2)
|
||||||
|
if chosenauth[0:1] != chr(0x05).encode():
|
||||||
|
self.close()
|
||||||
|
raise GeneralProxyError((1, _generalerrors[1]))
|
||||||
|
# Check the chosen authentication method
|
||||||
|
if chosenauth[1:2] == chr(0x00).encode():
|
||||||
|
# No authentication is required
|
||||||
|
pass
|
||||||
|
elif chosenauth[1:2] == chr(0x02).encode():
|
||||||
|
# Okay, we need to perform a basic username/password
|
||||||
|
# authentication.
|
||||||
|
self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5])
|
||||||
|
authstat = self.__recvall(2)
|
||||||
|
if authstat[0:1] != chr(0x01).encode():
|
||||||
|
# Bad response
|
||||||
|
self.close()
|
||||||
|
raise GeneralProxyError((1, _generalerrors[1]))
|
||||||
|
if authstat[1:2] != chr(0x00).encode():
|
||||||
|
# Authentication failed
|
||||||
|
self.close()
|
||||||
|
raise Socks5AuthError((3, _socks5autherrors[3]))
|
||||||
|
# Authentication succeeded
|
||||||
|
else:
|
||||||
|
# Reaching here is always bad
|
||||||
|
self.close()
|
||||||
|
if chosenauth[1] == chr(0xFF).encode():
|
||||||
|
raise Socks5AuthError((2, _socks5autherrors[2]))
|
||||||
|
else:
|
||||||
|
raise GeneralProxyError((1, _generalerrors[1]))
|
||||||
|
# Now we can request the actual connection
|
||||||
|
req = struct.pack('BBB', 0x05, 0x01, 0x00)
|
||||||
|
# If the given destination address is an IP address, we'll
|
||||||
|
# use the IPv4 address request even if remote resolving was specified.
|
||||||
|
try:
|
||||||
|
ipaddr = socket.inet_aton(destaddr)
|
||||||
|
req = req + chr(0x01).encode() + ipaddr
|
||||||
|
except socket.error:
|
||||||
|
# Well it's not an IP number, so it's probably a DNS name.
|
||||||
|
if self.__proxy[3]:
|
||||||
|
# Resolve remotely
|
||||||
|
ipaddr = None
|
||||||
|
if type(destaddr) != type(b''): # python3
|
||||||
|
destaddr_bytes = destaddr.encode(encoding='idna')
|
||||||
|
else:
|
||||||
|
destaddr_bytes = destaddr
|
||||||
|
req = req + chr(0x03).encode() + chr(len(destaddr_bytes)).encode() + destaddr_bytes
|
||||||
|
else:
|
||||||
|
# Resolve locally
|
||||||
|
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||||
|
req = req + chr(0x01).encode() + ipaddr
|
||||||
|
req = req + struct.pack(">H", destport)
|
||||||
|
self.sendall(req)
|
||||||
|
# Get the response
|
||||||
|
resp = self.__recvall(4)
|
||||||
|
if resp[0:1] != chr(0x05).encode():
|
||||||
|
self.close()
|
||||||
|
raise GeneralProxyError((1, _generalerrors[1]))
|
||||||
|
elif resp[1:2] != chr(0x00).encode():
|
||||||
|
# Connection failed
|
||||||
|
self.close()
|
||||||
|
if ord(resp[1:2])<=8:
|
||||||
|
raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])]))
|
||||||
|
else:
|
||||||
|
raise Socks5Error((9, _socks5errors[9]))
|
||||||
|
# Get the bound address/port
|
||||||
|
elif resp[3:4] == chr(0x01).encode():
|
||||||
|
boundaddr = self.__recvall(4)
|
||||||
|
elif resp[3:4] == chr(0x03).encode():
|
||||||
|
resp = resp + self.recv(1)
|
||||||
|
boundaddr = self.__recvall(ord(resp[4:5]))
|
||||||
|
else:
|
||||||
|
self.close()
|
||||||
|
raise GeneralProxyError((1,_generalerrors[1]))
|
||||||
|
boundport = struct.unpack(">H", self.__recvall(2))[0]
|
||||||
|
self.__proxysockname = (boundaddr, boundport)
|
||||||
|
if ipaddr != None:
|
||||||
|
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
|
||||||
|
else:
|
||||||
|
self.__proxypeername = (destaddr, destport)
|
||||||
|
|
||||||
|
def getproxysockname(self):
|
||||||
|
"""getsockname() -> address info
|
||||||
|
Returns the bound IP address and port number at the proxy.
|
||||||
|
"""
|
||||||
|
return self.__proxysockname
|
||||||
|
|
||||||
|
def getproxypeername(self):
|
||||||
|
"""getproxypeername() -> address info
|
||||||
|
Returns the IP and port number of the proxy.
|
||||||
|
"""
|
||||||
|
return _orgsocket.getpeername(self)
|
||||||
|
|
||||||
|
def getpeername(self):
|
||||||
|
"""getpeername() -> address info
|
||||||
|
Returns the IP address and port number of the destination
|
||||||
|
machine (note: getproxypeername returns the proxy)
|
||||||
|
"""
|
||||||
|
return self.__proxypeername
|
||||||
|
|
||||||
|
def __negotiatesocks4(self,destaddr,destport) -> None:
|
||||||
|
"""__negotiatesocks4(self,destaddr,destport)
|
||||||
|
Negotiates a connection through a SOCKS4 server.
|
||||||
|
"""
|
||||||
|
# Check if the destination address provided is an IP address
|
||||||
|
rmtrslv = False
|
||||||
|
try:
|
||||||
|
ipaddr = socket.inet_aton(destaddr)
|
||||||
|
except socket.error:
|
||||||
|
# It's a DNS name. Check where it should be resolved.
|
||||||
|
if self.__proxy[3]:
|
||||||
|
ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01)
|
||||||
|
rmtrslv = True
|
||||||
|
else:
|
||||||
|
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||||
|
# Construct the request packet
|
||||||
|
req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr
|
||||||
|
# The username parameter is considered userid for SOCKS4
|
||||||
|
if self.__proxy[4] != None:
|
||||||
|
req = req + self.__proxy[4]
|
||||||
|
req = req + chr(0x00).encode()
|
||||||
|
# DNS name if remote resolving is required
|
||||||
|
# NOTE: This is actually an extension to the SOCKS4 protocol
|
||||||
|
# called SOCKS4A and may not be supported in all cases.
|
||||||
|
if rmtrslv:
|
||||||
|
req = req + destaddr + chr(0x00).encode()
|
||||||
|
self.sendall(req)
|
||||||
|
# Get the response from the server
|
||||||
|
resp = self.__recvall(8)
|
||||||
|
if resp[0:1] != chr(0x00).encode():
|
||||||
|
# Bad data
|
||||||
|
self.close()
|
||||||
|
raise GeneralProxyError((1,_generalerrors[1]))
|
||||||
|
if resp[1:2] != chr(0x5A).encode():
|
||||||
|
# Server returned an error
|
||||||
|
self.close()
|
||||||
|
if ord(resp[1:2]) in (91, 92, 93):
|
||||||
|
self.close()
|
||||||
|
raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90]))
|
||||||
|
else:
|
||||||
|
raise Socks4Error((94, _socks4errors[4]))
|
||||||
|
# Get the bound address/port
|
||||||
|
self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0])
|
||||||
|
if rmtrslv != None:
|
||||||
|
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
|
||||||
|
else:
|
||||||
|
self.__proxypeername = (destaddr, destport)
|
||||||
|
|
||||||
|
def __negotiatehttp(self, destaddr, destport) -> None:
|
||||||
|
"""__negotiatehttp(self,destaddr,destport)
|
||||||
|
Negotiates a connection through an HTTP server.
|
||||||
|
"""
|
||||||
|
# If we need to resolve locally, we do this now
|
||||||
|
if not self.__proxy[3]:
|
||||||
|
addr = socket.gethostbyname(destaddr)
|
||||||
|
else:
|
||||||
|
addr = destaddr
|
||||||
|
self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode())
|
||||||
|
# We read the response until we get the string "\r\n\r\n"
|
||||||
|
resp = self.recv(1)
|
||||||
|
while resp.find("\r\n\r\n".encode()) == -1:
|
||||||
|
recv = self.recv(1)
|
||||||
|
if not recv:
|
||||||
|
raise GeneralProxyError((1, _generalerrors[1]))
|
||||||
|
resp = resp + recv
|
||||||
|
# We just need the first line to check if the connection
|
||||||
|
# was successful
|
||||||
|
statusline = resp.splitlines()[0].split(" ".encode(), 2)
|
||||||
|
if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()):
|
||||||
|
self.close()
|
||||||
|
raise GeneralProxyError((1, _generalerrors[1]))
|
||||||
|
try:
|
||||||
|
statuscode = int(statusline[1])
|
||||||
|
except ValueError:
|
||||||
|
self.close()
|
||||||
|
raise GeneralProxyError((1, _generalerrors[1]))
|
||||||
|
if statuscode != 200:
|
||||||
|
self.close()
|
||||||
|
raise HTTPError((statuscode, statusline[2]))
|
||||||
|
self.__proxysockname = ("0.0.0.0", 0)
|
||||||
|
self.__proxypeername = (addr, destport)
|
||||||
|
|
||||||
|
def connect(self, destpair) -> None:
|
||||||
|
"""connect(self, despair)
|
||||||
|
Connects to the specified destination through a proxy.
|
||||||
|
destpar - A tuple of the IP/DNS address and the port number.
|
||||||
|
(identical to socket's connect).
|
||||||
|
To select the proxy server use setproxy().
|
||||||
|
"""
|
||||||
|
# Do a minimal input check first
|
||||||
|
if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int):
|
||||||
|
raise GeneralProxyError((5, _generalerrors[5]))
|
||||||
|
if self.__proxy[0] == PROXY_TYPE_SOCKS5:
|
||||||
|
if self.__proxy[2] != None:
|
||||||
|
portnum = int(self.__proxy[2])
|
||||||
|
else:
|
||||||
|
portnum = 1080
|
||||||
|
_orgsocket.connect(self, (self.__proxy[1], portnum))
|
||||||
|
self.__negotiatesocks5(destpair[0], destpair[1])
|
||||||
|
elif self.__proxy[0] == PROXY_TYPE_SOCKS4:
|
||||||
|
if self.__proxy[2] != None:
|
||||||
|
portnum = self.__proxy[2]
|
||||||
|
else:
|
||||||
|
portnum = 1080
|
||||||
|
_orgsocket.connect(self,(self.__proxy[1], portnum))
|
||||||
|
self.__negotiatesocks4(destpair[0], destpair[1])
|
||||||
|
elif self.__proxy[0] == PROXY_TYPE_HTTP:
|
||||||
|
if self.__proxy[2] != None:
|
||||||
|
portnum = self.__proxy[2]
|
||||||
|
else:
|
||||||
|
portnum = 8080
|
||||||
|
_orgsocket.connect(self,(self.__proxy[1], portnum))
|
||||||
|
self.__negotiatehttp(destpair[0], destpair[1])
|
||||||
|
elif self.__proxy[0] == None:
|
||||||
|
_orgsocket.connect(self, (destpair[0], destpair[1]))
|
||||||
|
else:
|
||||||
|
raise GeneralProxyError((4, _generalerrors[4]))
|
163
src/toxygen_wrapper/tests/support_http.py
Normal file
163
src/toxygen_wrapper/tests/support_http.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
from io import BytesIO
|
||||||
|
import urllib
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
global LOG
|
||||||
|
LOG = logging.getLogger('TestS')
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pycurl
|
||||||
|
except ImportError:
|
||||||
|
pycurl = None
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
except ImportError:
|
||||||
|
requests = None
|
||||||
|
|
||||||
|
lNO_PROXY = ['localhost', '127.0.0.1']
|
||||||
|
CONNECT_TIMEOUT = 20.0
|
||||||
|
|
||||||
|
def bAreWeConnected() -> bool:
|
||||||
|
# FixMe: Linux only
|
||||||
|
sFile = f"/proc/{os.getpid()}/net/route"
|
||||||
|
if not os.path.isfile(sFile): return None
|
||||||
|
i = 0
|
||||||
|
for elt in open(sFile, "r").readlines():
|
||||||
|
if elt.startswith('Iface'): continue
|
||||||
|
if elt.startswith('lo'): continue
|
||||||
|
i += 1
|
||||||
|
return i > 0
|
||||||
|
|
||||||
|
def pick_up_proxy_from_environ() -> dict:
|
||||||
|
retval = dict()
|
||||||
|
if os.environ.get('socks_proxy', ''):
|
||||||
|
# socks_proxy takes precedence over https/http
|
||||||
|
proxy = os.environ.get('socks_proxy', '')
|
||||||
|
i = proxy.find('//')
|
||||||
|
if i >= 0: proxy = proxy[i+2:]
|
||||||
|
retval['proxy_host'] = proxy.split(':')[0]
|
||||||
|
retval['proxy_port'] = proxy.split(':')[-1]
|
||||||
|
retval['proxy_type'] = 2
|
||||||
|
retval['udp_enabled'] = False
|
||||||
|
elif os.environ.get('https_proxy', ''):
|
||||||
|
# https takes precedence over http
|
||||||
|
proxy = os.environ.get('https_proxy', '')
|
||||||
|
i = proxy.find('//')
|
||||||
|
if i >= 0: proxy = proxy[i+2:]
|
||||||
|
retval['proxy_host'] = proxy.split(':')[0]
|
||||||
|
retval['proxy_port'] = proxy.split(':')[-1]
|
||||||
|
retval['proxy_type'] = 1
|
||||||
|
retval['udp_enabled'] = False
|
||||||
|
elif os.environ.get('http_proxy', ''):
|
||||||
|
proxy = os.environ.get('http_proxy', '')
|
||||||
|
i = proxy.find('//')
|
||||||
|
if i >= 0: proxy = proxy[i+2:]
|
||||||
|
retval['proxy_host'] = proxy.split(':')[0]
|
||||||
|
retval['proxy_port'] = proxy.split(':')[-1]
|
||||||
|
retval['proxy_type'] = 1
|
||||||
|
retval['udp_enabled'] = False
|
||||||
|
else:
|
||||||
|
retval['proxy_host'] = ''
|
||||||
|
retval['proxy_port'] = ''
|
||||||
|
retval['proxy_type'] = 0
|
||||||
|
retval['udp_enabled'] = True
|
||||||
|
return retval
|
||||||
|
|
||||||
|
def download_url(url:str, settings:str = None) -> None:
|
||||||
|
if not bAreWeConnected(): return ''
|
||||||
|
|
||||||
|
if settings is None:
|
||||||
|
settings = pick_up_proxy_from_environ()
|
||||||
|
|
||||||
|
if pycurl:
|
||||||
|
LOG.debug('Downloading with pycurl: ' + str(url))
|
||||||
|
buffer = BytesIO()
|
||||||
|
c = pycurl.Curl()
|
||||||
|
c.setopt(c.URL, url)
|
||||||
|
c.setopt(c.WRITEDATA, buffer)
|
||||||
|
# Follow redirect.
|
||||||
|
c.setopt(c.FOLLOWLOCATION, True)
|
||||||
|
|
||||||
|
# cookie jar
|
||||||
|
cjar = os.path.join(os.environ['HOME'], '.local', 'jar.cookie')
|
||||||
|
if os.path.isfile(cjar):
|
||||||
|
c.setopt(c.COOKIEFILE, cjar)
|
||||||
|
# LARGS+=( --cookie-jar --junk-session-cookies )
|
||||||
|
|
||||||
|
#? c.setopt(c.ALTSVC_CTRL, 16)
|
||||||
|
|
||||||
|
c.setopt(c.NOPROXY, ','.join(lNO_PROXY))
|
||||||
|
#? c.setopt(c.CAINFO, certifi.where())
|
||||||
|
if settings['proxy_type'] == 2 and settings['proxy_host']:
|
||||||
|
socks_proxy = 'socks5h://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||||
|
settings['udp_enabled'] = False
|
||||||
|
c.setopt(c.PROXY, socks_proxy)
|
||||||
|
c.setopt(c.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5_HOSTNAME)
|
||||||
|
elif settings['proxy_type'] == 1 and settings['proxy_host']:
|
||||||
|
https_proxy = 'https://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||||
|
c.setopt(c.PROXY, https_proxy)
|
||||||
|
elif settings['proxy_type'] == 1 and settings['proxy_host']:
|
||||||
|
http_proxy = 'http://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||||
|
c.setopt(c.PROXY, http_proxy)
|
||||||
|
c.setopt(c.PROTOCOLS, c.PROTO_HTTPS)
|
||||||
|
try:
|
||||||
|
c.perform()
|
||||||
|
c.close()
|
||||||
|
#? assert c.getinfo(c.RESPONSE_CODE) < 300
|
||||||
|
result = buffer.getvalue()
|
||||||
|
# Body is a byte string.
|
||||||
|
LOG.info('nodes loaded with pycurl: ' + str(url))
|
||||||
|
return result
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error('TOX Downloading error with pycurl: ' + str(ex))
|
||||||
|
LOG.error('\n' + traceback.format_exc())
|
||||||
|
# drop through
|
||||||
|
|
||||||
|
if requests:
|
||||||
|
LOG.debug('Downloading with requests: ' + str(url))
|
||||||
|
try:
|
||||||
|
headers = dict()
|
||||||
|
headers['Content-Type'] = 'application/json'
|
||||||
|
proxies = dict()
|
||||||
|
if settings['proxy_type'] == 2 and settings['proxy_host']:
|
||||||
|
socks_proxy = 'socks5://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||||
|
settings['udp_enabled'] = False
|
||||||
|
proxies['https'] = socks_proxy
|
||||||
|
elif settings['proxy_type'] == 1 and settings['proxy_host']:
|
||||||
|
https_proxy = 'https://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||||
|
proxies['https'] = https_proxy
|
||||||
|
elif settings['proxy_type'] == 1 and settings['proxy_host']:
|
||||||
|
http_proxy = 'http://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||||
|
proxies['http'] = http_proxy
|
||||||
|
req = requests.get(url,
|
||||||
|
headers=headers,
|
||||||
|
proxies=proxies,
|
||||||
|
timeout=CONNECT_TIMEOUT)
|
||||||
|
# max_retries=3
|
||||||
|
assert req.status_code < 300
|
||||||
|
result = req.content
|
||||||
|
LOG.info('nodes loaded with requests: ' + str(url))
|
||||||
|
return result
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error('TOX Downloading error with requests: ' + str(ex))
|
||||||
|
# drop through
|
||||||
|
|
||||||
|
if not settings['proxy_type']: # no proxy
|
||||||
|
LOG.debug('Downloading with urllib no proxy: ' + str(url))
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(url)
|
||||||
|
req.add_header('Content-Type', 'application/json')
|
||||||
|
response = urllib.request.urlopen(req)
|
||||||
|
result = response.read()
|
||||||
|
LOG.info('nodes loaded with no proxy: ' + str(url))
|
||||||
|
return result
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error('TOX Downloading ' + str(ex))
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return ''
|
584
src/toxygen_wrapper/tests/support_onions.py
Normal file
584
src/toxygen_wrapper/tests/support_onions.py
Normal file
@ -0,0 +1,584 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Code to interact with a running to using the stem library.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import getpass
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import select
|
||||||
|
import shutil
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from typing import Union, Callable, Optional
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import stem
|
||||||
|
from stem.connection import MissingPassword
|
||||||
|
from stem.control import Controller
|
||||||
|
from stem.util.tor_tools import is_valid_fingerprint
|
||||||
|
|
||||||
|
global LOG
|
||||||
|
from toxygen_wrapper.tests.support_http import bAreWeConnected
|
||||||
|
|
||||||
|
warnings.filterwarnings('ignore')
|
||||||
|
LOG = logging.getLogger('TestS')
|
||||||
|
|
||||||
|
bHAVE_TORR = shutil.which('tor-resolve')
|
||||||
|
oSTEM_CONTROLER = None
|
||||||
|
yKNOWN_ONIONS = """
|
||||||
|
- facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd # facebook
|
||||||
|
- duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad # ddg
|
||||||
|
- zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad # keys.openpgp
|
||||||
|
"""
|
||||||
|
# grep -B 1 '<li><a href="' /tmp/tor.html |sed -e 's/<li><a href="http:../ - /' -e 's/.onion.*//' -e 's/<li id=./ # /' -e 's/".*//' -e '/^--/d' -e '/<li id/d'
|
||||||
|
# This will slow things down 1-2 min each
|
||||||
|
yKNOWN_ONIONS_TOR = """
|
||||||
|
# 2019.www.torproject.org
|
||||||
|
- jqyzxhjk6psc6ul5jnfwloamhtyh7si74b4743k2qgpskwwxrzhsxmad
|
||||||
|
# api.donate.torproject.org
|
||||||
|
- rbi3fpvpz4vlrx67scoqef2zxz7k4xyiludszg655favvkygjmhz6wyd
|
||||||
|
# archive.torproject.org
|
||||||
|
- uy3qxvwzwoeztnellvvhxh7ju7kfvlsauka7avilcjg7domzxptbq7qd
|
||||||
|
# aus1.torproject.org
|
||||||
|
- ot3ivcdxmalbsbponeeq5222hftpf3pqil24q3s5ejwo5t52l65qusid
|
||||||
|
# aus2.torproject.org
|
||||||
|
- b5t7emfr2rn3ixr4lvizpi3stnni4j4p6goxho7lldf4qg4hz5hvpqid
|
||||||
|
# blog.torproject.org
|
||||||
|
- pzhdfe7jraknpj2qgu5cz2u3i4deuyfwmonvzu5i3nyw4t4bmg7o5pad
|
||||||
|
# bridges.torproject.org
|
||||||
|
- yq5jjvr7drkjrelzhut7kgclfuro65jjlivyzfmxiq2kyv5lickrl4qd
|
||||||
|
# cloud.torproject.org
|
||||||
|
- ui3cpcohcoko6aydhuhlkwqqtvadhaflcc5zb7mwandqmcal7sbwzwqd
|
||||||
|
# collector.torproject.org
|
||||||
|
- pgmrispjerzzf2tdzbfp624cg5vpbvdw2q5a3hvtsbsx25vnni767yad
|
||||||
|
# collector2.torproject.org
|
||||||
|
- 3srlmjzbyyzz62jvdfqwn5ldqmh6mwnqxre2zamxveb75uz2qrqkrkyd
|
||||||
|
# community.torproject.org
|
||||||
|
- xmrhfasfg5suueegrnc4gsgyi2tyclcy5oz7f5drnrodmdtob6t2ioyd
|
||||||
|
# consensus-health.torproject.org
|
||||||
|
- tkskz5dkjel4xqyw5d5l3k52kgglotwn6vgb5wrl2oa5yi2szvywiyid
|
||||||
|
# crm.torproject.org
|
||||||
|
- 6ojylpznauimd2fga3m7g24vd7ebkzlemxdprxckevqpzs347ugmynqd
|
||||||
|
# deb.torproject.org
|
||||||
|
- apow7mjfryruh65chtdydfmqfpj5btws7nbocgtaovhvezgccyjazpqd
|
||||||
|
# dev.crm.torproject.org
|
||||||
|
- eewp4iydzyu2a5d6bvqadadkozxdbhsdtmsoczu2joexfrjjsheaecad
|
||||||
|
# dist.torproject.org
|
||||||
|
- scpalcwstkydpa3y7dbpkjs2dtr7zvtvdbyj3dqwkucfrwyixcl5ptqd
|
||||||
|
# donate-api.torproject.org
|
||||||
|
- lkfkuhcx62yfvzuz5o3ju4divuf4bsh2bybwd3oierq47kyp2ig2gvid
|
||||||
|
# donate.torproject.org
|
||||||
|
- yoaenchicimox2qdc47p36zm3cuclq7s7qxx6kvxqaxjodigfifljqqd
|
||||||
|
# exonerator.torproject.org
|
||||||
|
- pm46i5h2lfewyx6l7pnicbxhts2sxzacvsbmqiemqaspredf2gm3dpad
|
||||||
|
# extra.torproject.org
|
||||||
|
- kkr72iohlfix5ipjg776eyhplnl2oiv5tz4h2y2bkhjix3quafvjd5ad
|
||||||
|
# gettor.torproject.org
|
||||||
|
- ueghr2hzndecdntou33mhymbbxj7pir74nwzhqr6drhxpbz3j272p4id
|
||||||
|
# git.torproject.org
|
||||||
|
- xtlfhaspqtkeeqxk6umggfbr3gyfznvf4jhrge2fujz53433i2fcs3id
|
||||||
|
# gitlab.torproject.org
|
||||||
|
- eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad
|
||||||
|
# gitweb.torproject.org
|
||||||
|
- gzgme7ov25seqjbphab4fkcph3jkobfwwpivt5kzbv3kqx2y2qttl4yd
|
||||||
|
# grafana1.torproject.org
|
||||||
|
- 7zjnw5lx2x27rwiocxkqdquo7fawj46mf2wiu2l7e6z6ng6nivmdxnad
|
||||||
|
# grafana2.torproject.org
|
||||||
|
- f3vd6fyiccuppybkxiblgigej3pfvvqzjnhd3wyv7h4ee5asawf2fhqd
|
||||||
|
# ircbouncer.torproject.org
|
||||||
|
- moz5kotsnjony4oxccxfo4lwk3pvoxmdoljibhgoonzgzjs5oemtjmqd
|
||||||
|
# metabase.metrics.torproject.org
|
||||||
|
- gr5pseamigereei4c6654hafzhid5z2c3oqzn6cfnx7yfyelt47znhad
|
||||||
|
# metrics.torproject.org
|
||||||
|
- hctxrvjzfpvmzh2jllqhgvvkoepxb4kfzdjm6h7egcwlumggtktiftid
|
||||||
|
# moat.torproject.org
|
||||||
|
- z7m7ogzdhu43nosvjtsuplfmuqa3ge5obahixydhmzdox6owwxfoxzid
|
||||||
|
# nagios.torproject.org
|
||||||
|
- w6vizvw4ckesva5fvlkrepynemxdq6pgo5sh4r76ec6msq5notkhqryd
|
||||||
|
# newsletter.torproject.org
|
||||||
|
- a4ygisnerpgtc5ayerl22pll6cls3oyj54qgpm7qrmb66xrxts6y3lyd
|
||||||
|
# nightlies.tbb.torproject.org
|
||||||
|
- umj4zbqdfcyevlkgqgpq6foxk3z75zzxsbgt5jqmfxofrbrjh3crbnad
|
||||||
|
# nyx.torproject.org
|
||||||
|
- 3ewfgrt4gzfccp6bnquhqb266r3zepiqpnsk3falwygkegtluwuyevid
|
||||||
|
- xao2lxsmia2edq2n5zxg6uahx6xox2t7bfjw6b5vdzsxi7ezmqob6qid
|
||||||
|
- dud2sxm6feahhuwj4y4lzktduy7v3qpaqsfkggtj2ojmzathttkegoid
|
||||||
|
# openpgpkey.torproject.org
|
||||||
|
- 2yldcptk56shc7lwieozoglw3t5ghty7m6mf2faysvfnzccqavbu2mad
|
||||||
|
# people.torproject.org
|
||||||
|
- 5ecey6oe4rocdsfoigr4idu42cecm2j7zfogc3xc7kfn4uriehwrs6qd
|
||||||
|
# prometheus1.torproject.org
|
||||||
|
- ydok5jiruh3ak6hcfdlm2g7iuraaxcomeckj2nucjsxif6qmrrda2byd
|
||||||
|
# prometheus2.torproject.org
|
||||||
|
- vyo6yrqhl3by7d6n5t6hjkflaqbarjpqjnvapr5u5rafk4imnfrmcjyd
|
||||||
|
# rbm.torproject.org
|
||||||
|
- nkuz2tpok7ctwd5ueer5bytj3bm42vp7lgjcsnznal3stotg6vyaakyd
|
||||||
|
# research.torproject.org
|
||||||
|
- xhqthou6scpfnwjyzc3ekdgcbvj76ccgyjyxp6cgypxjlcuhnxiktnqd
|
||||||
|
# review.torproject.net
|
||||||
|
- zhkhhhnppc5k6xju7n25rjba3wuip73jnodicxl65qdpchrwvvsilcyd
|
||||||
|
# rpm.torproject.org
|
||||||
|
- 4ayyzfoh5qdrokqaejis3rdredhvf22n3migyxfudpkpunngfc7g4lqd
|
||||||
|
# snowflake.torproject.org
|
||||||
|
- oljlphash3bpqtrvqpr5gwzrhroziw4mddidi5d2qa4qjejcbrmoypqd
|
||||||
|
# spec.torproject.org
|
||||||
|
- i3xi5qxvbrngh3g6o7czwjfxwjzigook7zxzjmgwg5b7xnjcn5hzciad
|
||||||
|
# staging-api.donate.torproject.org
|
||||||
|
- vorwws6g6mx23djlznmlqva4t5olulpnet6fxyiyytcu5dorp3fstdqd
|
||||||
|
# staging.crm.torproject.org
|
||||||
|
- pt34uujusar4arrvsqljndqlt7tck2d5cosaav5xni4nh7bmvshyp2yd
|
||||||
|
# staging.donate-api.torproject.org
|
||||||
|
- 7niqsyixinnhxvh33zh5dqnplxnc2yd6ktvats3zmtbbpzcphpbsa6qd
|
||||||
|
# status.torproject.org
|
||||||
|
- eixoaclv7qvnmu5rolbdwba65xpdiditdoyp6edsre3fitad777jr3ad
|
||||||
|
# stem.torproject.org
|
||||||
|
- mf34jlghauz5pxjcmdymdqbe5pva4v24logeys446tdrgd5lpsrocmqd
|
||||||
|
# styleguide.torproject.org
|
||||||
|
- 7khzpw47s35pwo3lvtctwf2szvnq3kgglvzc22elx7of2awdzpovqmqd
|
||||||
|
# submission.torproject.org
|
||||||
|
- givpjczyrb5jjseful3o5tn3tg7tidbu4gydl4sa5ekpcipivqaqnpad
|
||||||
|
# support.torproject.org
|
||||||
|
- rzuwtpc4wb3xdzrj3yeajsvm3fkq4vbeubm2tdxaqruzzzgs5dwemlad
|
||||||
|
# survey.torproject.org
|
||||||
|
- eh5esdnd6fkbkapfc6nuyvkjgbtnzq2is72lmpwbdbxepd2z7zbgzsqd
|
||||||
|
# svn-archive.torproject.org
|
||||||
|
- b63iq6es4biaawfilwftlfkw6a6putogxh4iakei2ioppb7dsfucekyd
|
||||||
|
# tb-manual.torproject.org
|
||||||
|
- dsbqrprgkqqifztta6h3w7i2htjhnq7d3qkh3c7gvc35e66rrcv66did
|
||||||
|
# test-api.donate.torproject.org
|
||||||
|
- wiofesr5qt2k7qrlljpk53isgedxi6ddw6z3o7iay2l7ne3ziyagxaid
|
||||||
|
# test-data.tbb.torproject.org
|
||||||
|
- umbk3kbgov4ekg264yulvbrpykfye7ohguqbds53qn547mdpt6o4qkad
|
||||||
|
# test.crm.torproject.org
|
||||||
|
- a4d52y2erv4eijii66cpnyqn7rsnnq3gmtrsdxzt2laoutvu4gz7fwid
|
||||||
|
# test.donate-api.torproject.org
|
||||||
|
- i4zhrn4md3ucd5dfgeo5lnqd3jy2z2kzp3lt4tdisvivzoqqtlrymkid
|
||||||
|
# www
|
||||||
|
- tttyx2vwp7ihml3vkhywwcizv6nbwrikpgeciy3qrow7l7muak2pnhad
|
||||||
|
# www.torproject.org
|
||||||
|
- 2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid
|
||||||
|
"""
|
||||||
|
|
||||||
|
# we check these each time but we got them by sorting bad relays
|
||||||
|
# in the wild we'll keep a copy here so we can avoid restesting
|
||||||
|
yKNOWN_NODNS = """
|
||||||
|
- 0x0.is
|
||||||
|
- a9.wtf
|
||||||
|
- apt96.com
|
||||||
|
- axims.net
|
||||||
|
- backup.spekadyon.org
|
||||||
|
- dfri.se
|
||||||
|
- dotsrc.org
|
||||||
|
- dtf.contact
|
||||||
|
- ezyn.de
|
||||||
|
- for-privacy.net
|
||||||
|
- galtland.network
|
||||||
|
- heraldonion.org
|
||||||
|
- interfesse.net
|
||||||
|
- kryptonit.org
|
||||||
|
- linkspartei.org
|
||||||
|
- mkg20001.io
|
||||||
|
- nicdex.com
|
||||||
|
- nx42.de
|
||||||
|
- pineapple.cx
|
||||||
|
- privacylayer.xyz
|
||||||
|
- privacysvcs.net
|
||||||
|
- prsv.ch
|
||||||
|
- sebastian-elisa-pfeifer.eu
|
||||||
|
- thingtohide.nl
|
||||||
|
- tor-exit-2.aa78i2efsewr0neeknk.xyz
|
||||||
|
- tor-exit-3.aa78i2efsewr0neeknk.xyz
|
||||||
|
- tor.dlecan.com
|
||||||
|
- tor.skankhunt42.pw
|
||||||
|
- transliberation.today
|
||||||
|
- tuxli.org
|
||||||
|
- unzane.com
|
||||||
|
- verification-for-nusenu.net
|
||||||
|
- www.defcon.org
|
||||||
|
"""
|
||||||
|
# - aklad5.com
|
||||||
|
# - artikel5ev.de
|
||||||
|
# - arvanode.net
|
||||||
|
# - dodo.pm
|
||||||
|
# - erjan.net
|
||||||
|
# - galtland.network
|
||||||
|
# - lonet.sh
|
||||||
|
# - moneneis.de
|
||||||
|
# - olonet.sh
|
||||||
|
# - or-exit-2.aa78i2efsewr0neeknk.xyz
|
||||||
|
# - or.wowplanet.de
|
||||||
|
# - ormycloud.org
|
||||||
|
# - plied-privacy.net
|
||||||
|
# - rivacysvcs.net
|
||||||
|
# - redacted.org
|
||||||
|
# - rofl.cat
|
||||||
|
# - sv.ch
|
||||||
|
# - tikel10.org
|
||||||
|
# - tor.wowplanet.de
|
||||||
|
# - torix-relays.org
|
||||||
|
# - tse.com
|
||||||
|
# - w.digidow.eu
|
||||||
|
# - w.cccs.de
|
||||||
|
|
||||||
|
def oMakeController(sSock:str = '', port:int = 9051, password:[str,None] = None):
|
||||||
|
global oSTEM_CONTROLER
|
||||||
|
from getpass import unix_getpass
|
||||||
|
if sSock and os.path.exists(sSock):
|
||||||
|
controller = Controller.from_socket_file(path=sSock)
|
||||||
|
else:
|
||||||
|
controller = Controller.from_port(port=port)
|
||||||
|
try:
|
||||||
|
controller.authenticate()
|
||||||
|
except (Exception, MissingPassword):
|
||||||
|
if password is None:
|
||||||
|
password = os.environ.get('TOR_CONTROLLER_PASSWORD', '')
|
||||||
|
if password:
|
||||||
|
p = password
|
||||||
|
else:
|
||||||
|
sys.stdout.flush()
|
||||||
|
p = unix_getpass(prompt='TOR_CONTROLLER_PASSWORD: ', stream=sys.stderr)
|
||||||
|
controller.authenticate(p)
|
||||||
|
oSTEM_CONTROLER = controller
|
||||||
|
return controller
|
||||||
|
|
||||||
|
def oGetStemController(log_level:int = 10, sock_or_pair:str = '/run/tor/control', password:[str,None] = None):
|
||||||
|
|
||||||
|
global oSTEM_CONTROLER
|
||||||
|
if oSTEM_CONTROLER: return oSTEM_CONTROLER
|
||||||
|
import stem.util.log
|
||||||
|
# stem.util.log.Runlevel = 'DEBUG' = 20 # log_level
|
||||||
|
|
||||||
|
if os.path.exists(sock_or_pair):
|
||||||
|
LOG.info(f"controller from socket {sock_or_pair}")
|
||||||
|
controller = Controller.from_socket_file(path=sock_or_pair)
|
||||||
|
else:
|
||||||
|
if type(sock_or_pair) == int:
|
||||||
|
port = sock_or_pair
|
||||||
|
elif ':' in sock_or_pair:
|
||||||
|
port = sock_or_pair.split(':')[1]
|
||||||
|
else:
|
||||||
|
port = sock_or_pair
|
||||||
|
try:
|
||||||
|
port = int(port)
|
||||||
|
except: port = 9051
|
||||||
|
LOG.info(f"controller from port {port}")
|
||||||
|
controller = Controller.from_port(port=port)
|
||||||
|
try:
|
||||||
|
controller.authenticate()
|
||||||
|
except (Exception, MissingPassword):
|
||||||
|
if password is None:
|
||||||
|
password = os.environ.get('TOR_CONTROLLER_PASSWORD', '')
|
||||||
|
if password:
|
||||||
|
p = password
|
||||||
|
else:
|
||||||
|
sys.stdout.flush()
|
||||||
|
p = getpass.unix_getpass(prompt='TOR_CONTROLLER_PASSWORD: ', stream=sys.stderr)
|
||||||
|
controller.authenticate(p)
|
||||||
|
oSTEM_CONTROLER = controller
|
||||||
|
LOG.debug(f"{controller}")
|
||||||
|
return oSTEM_CONTROLER
|
||||||
|
|
||||||
|
def sMapaddressResolv(target:str, iPort:int = 9051, log_level:int = 10, password:[str,None] = None) -> str:
|
||||||
|
if not stem:
|
||||||
|
LOG.warn('please install the stem Python package')
|
||||||
|
return ''
|
||||||
|
|
||||||
|
try:
|
||||||
|
controller = oGetStemController(log_level=log_level, password=password)
|
||||||
|
map_dict = {"0.0.0.0": target}
|
||||||
|
map_ret = controller.map_address(map_dict)
|
||||||
|
return map_ret
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def vwait_for_controller(controller, wait_boot:int = 10) -> None:
|
||||||
|
if bAreWeConnected() is False:
|
||||||
|
raise RuntimeError("we are not connected")
|
||||||
|
percent = i = 0
|
||||||
|
# You can call this while boostrapping
|
||||||
|
while percent < 100 and i < wait_boot:
|
||||||
|
bootstrap_status = controller.get_info("status/bootstrap-phase")
|
||||||
|
progress_percent = re.match('.* PROGRESS=([0-9]+).*', bootstrap_status)
|
||||||
|
percent = int(progress_percent.group(1))
|
||||||
|
LOG.info(f"Bootstrapping {percent}%")
|
||||||
|
time.sleep(5)
|
||||||
|
i += 5
|
||||||
|
|
||||||
|
def bin_to_hex(raw_id:int, length: Optional[int] = None) -> str:
|
||||||
|
if length is None: length = len(raw_id)
|
||||||
|
res = ''.join('{:02x}'.format(raw_id[i]) for i in range(length))
|
||||||
|
return res.upper()
|
||||||
|
|
||||||
|
def lIntroductionPoints(controller=None, lOnions:[list,None] = None, itimeout:int = 120, log_level:int = 10, password:[str,None] = None):
|
||||||
|
"""now working !!! stem 1.8.x timeout must be huge >120
|
||||||
|
'Provides the descriptor for a hidden service. The **address** is the
|
||||||
|
'.onion' address of the hidden service '
|
||||||
|
What about Services?
|
||||||
|
"""
|
||||||
|
if lOnions is None: lOnions = []
|
||||||
|
try:
|
||||||
|
from cryptography.utils import int_from_bytes
|
||||||
|
except ImportError:
|
||||||
|
import cryptography.utils
|
||||||
|
# guessing - not in the current cryptography but stem expects it
|
||||||
|
def int_from_bytes(**args): return int.to_bytes(*args)
|
||||||
|
cryptography.utils.int_from_bytes = int_from_bytes
|
||||||
|
# this will fai if the trick above didnt work
|
||||||
|
from stem.prereq import is_crypto_available
|
||||||
|
is_crypto_available(ed25519=True)
|
||||||
|
|
||||||
|
from queue import Empty
|
||||||
|
|
||||||
|
from stem import Timeout
|
||||||
|
from stem.client.datatype import LinkByFingerprint
|
||||||
|
from stem.descriptor.hidden_service import HiddenServiceDescriptorV3
|
||||||
|
|
||||||
|
if type(lOnions) not in [set, tuple, list]:
|
||||||
|
lOnions = list(lOnions)
|
||||||
|
if controller is None:
|
||||||
|
controller = oGetStemController(log_level=log_level, password=password)
|
||||||
|
l = []
|
||||||
|
for elt in lOnions:
|
||||||
|
LOG.info(f"controller.get_hidden_service_descriptor {elt}")
|
||||||
|
try:
|
||||||
|
desc = controller.get_hidden_service_descriptor(elt,
|
||||||
|
await_result=True,
|
||||||
|
timeout=itimeout)
|
||||||
|
# LOG.log(40, f"{dir(desc)} get_hidden_service_descriptor")
|
||||||
|
# timeouts 20 sec
|
||||||
|
# mistakenly a HSv2 descriptor
|
||||||
|
hs_address = HiddenServiceDescriptorV3.from_str(str(desc)) # reparse as HSv3
|
||||||
|
oInnerLayer = hs_address.decrypt(elt)
|
||||||
|
# LOG.log(40, f"{dir(oInnerLayer)}")
|
||||||
|
|
||||||
|
# IntroductionPointV3
|
||||||
|
n = oInnerLayer.introduction_points
|
||||||
|
if not n:
|
||||||
|
LOG.warn(f"NO introduction points for {elt}")
|
||||||
|
continue
|
||||||
|
LOG.info(f"{elt} {len(n)} introduction points")
|
||||||
|
lp = []
|
||||||
|
for introduction_point in n:
|
||||||
|
for linkspecifier in introduction_point.link_specifiers:
|
||||||
|
if isinstance(linkspecifier, LinkByFingerprint):
|
||||||
|
# LOG.log(40, f"Getting fingerprint for {linkspecifier}")
|
||||||
|
if hasattr(linkspecifier, 'fingerprint'):
|
||||||
|
assert len(linkspecifier.value) == 20
|
||||||
|
lp += [bin_to_hex(linkspecifier.value)]
|
||||||
|
LOG.info(f"{len(lp)} introduction points for {elt}")
|
||||||
|
l += lp
|
||||||
|
except (Empty, Timeout,) as e: # noqa
|
||||||
|
LOG.warn(f"Timed out getting introduction points for {elt}")
|
||||||
|
except stem.DescriptorUnavailable as e:
|
||||||
|
LOG.error(e)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
return l
|
||||||
|
|
||||||
|
def zResolveDomain(domain:str) -> int:
|
||||||
|
try:
|
||||||
|
ip = sTorResolve(domain)
|
||||||
|
except Exception as e: # noqa
|
||||||
|
ip = ''
|
||||||
|
if ip == '':
|
||||||
|
try:
|
||||||
|
lpair = getaddrinfo(domain, 443)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warn(f"{e}")
|
||||||
|
lpair = None
|
||||||
|
if lpair is None:
|
||||||
|
LOG.warn(f"TorResolv and getaddrinfo failed for {domain}")
|
||||||
|
return ''
|
||||||
|
ip = lpair[0]
|
||||||
|
return ip
|
||||||
|
|
||||||
|
def sTorResolve(target:str,
|
||||||
|
verbose:bool = False,
|
||||||
|
sHost:str = '127.0.0.1',
|
||||||
|
iPort:int = 9050,
|
||||||
|
SOCK_TIMEOUT_SECONDS:float = 10.0,
|
||||||
|
SOCK_TIMEOUT_TRIES:int = 3,
|
||||||
|
) -> str:
|
||||||
|
MAX_INFO_RESPONSE_PACKET_LENGTH = 8
|
||||||
|
if '@' in target:
|
||||||
|
LOG.warn(f"sTorResolve failed invalid hostname {target}")
|
||||||
|
return ''
|
||||||
|
target = target.strip('/')
|
||||||
|
seb = b"\x04\xf0\x00\x00\x00\x00\x00\x01\x00"
|
||||||
|
seb += bytes(target, 'US-ASCII') + b"\x00"
|
||||||
|
assert len(seb) == 10 + len(target), str(len(seb)) + repr(seb)
|
||||||
|
|
||||||
|
# LOG.debug(f"0 Sending {len(seb)} to The TOR proxy {seb}")
|
||||||
|
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.connect((sHost, iPort))
|
||||||
|
|
||||||
|
sock.settimeout(SOCK_TIMEOUT_SECONDS)
|
||||||
|
oRet = sock.sendall(seb) # noqa
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
data = ''
|
||||||
|
while i < SOCK_TIMEOUT_TRIES:
|
||||||
|
i += 1
|
||||||
|
time.sleep(3)
|
||||||
|
lReady = select.select([sock.fileno()], [], [],
|
||||||
|
SOCK_TIMEOUT_SECONDS)
|
||||||
|
if not lReady[0]: continue
|
||||||
|
try:
|
||||||
|
flags = socket.MSG_WAITALL
|
||||||
|
data = sock.recv(MAX_INFO_RESPONSE_PACKET_LENGTH, flags)
|
||||||
|
except socket.timeout:
|
||||||
|
LOG.warn(f"4 The TOR proxy {(sHost, iPort)}" \
|
||||||
|
+" didnt reply in " + str(SOCK_TIMEOUT_SECONDS) + " sec."
|
||||||
|
+" #" +str(i))
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("4 The TOR proxy " \
|
||||||
|
+repr((sHost, iPort)) \
|
||||||
|
+" errored with " + str(e)
|
||||||
|
+" #" +str(i))
|
||||||
|
sock.close()
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
if len(data) > 0: break
|
||||||
|
|
||||||
|
if len(data) == 0:
|
||||||
|
if i > SOCK_TIMEOUT_TRIES:
|
||||||
|
sLabel = "5 No reply #"
|
||||||
|
else:
|
||||||
|
sLabel = "5 No data #"
|
||||||
|
LOG.warn(f"sTorResolve: {sLabel} {i} on {sHost}:{iPort}")
|
||||||
|
sock.close()
|
||||||
|
return ''
|
||||||
|
|
||||||
|
assert len(data) >= 8
|
||||||
|
packet_sf = data[1]
|
||||||
|
if packet_sf == 90:
|
||||||
|
# , "%d" % packet_sf
|
||||||
|
assert f"{packet_sf}" == "90", f"packet_sf = {packet_sf}"
|
||||||
|
return f"{data[4]}.{data[5]}.{data[6]}.{data[7]}"
|
||||||
|
else:
|
||||||
|
# 91
|
||||||
|
LOG.warn(f"tor-resolve failed for {target} on {sHost}:{iPort}")
|
||||||
|
sout = f"/tmp/{os.getpid}.tmp"
|
||||||
|
iRet = os.system(f"tor-resolve -4 {target} > {sout} 2>/dev/null")
|
||||||
|
# os.system("strace tor-resolve -4 "+target+" 2>&1|grep '^sen\|^rec'")
|
||||||
|
if iRet == 0:
|
||||||
|
sAns = open(sout, 'rt').read().strip()
|
||||||
|
return sAns
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def getaddrinfo(sHost:str, sPort:str) -> list:
|
||||||
|
# do this the explicit way = Ive seen the compact connect fail
|
||||||
|
# >>> sHost, sPort = 'l27.0.0.1', 33446
|
||||||
|
# >>> sock.connect((sHost, sPort))
|
||||||
|
# socket.gaierror: [Errno -2] Name or service not known
|
||||||
|
try:
|
||||||
|
lElts = socket.getaddrinfo(sHost, int(sPort), socket.AF_INET)
|
||||||
|
lElts = list(filter(lambda elt: elt[1] == socket.SOCK_DGRAM, lElts))
|
||||||
|
assert len(lElts) == 1, repr(lElts)
|
||||||
|
lPair = lElts[0][-1]
|
||||||
|
assert len(lPair) == 2, repr(lPair)
|
||||||
|
assert type(lPair[1]) == int, repr(lPair)
|
||||||
|
except (socket.gaierror, OSError, BaseException) as e:
|
||||||
|
LOG.error(e)
|
||||||
|
return None
|
||||||
|
return lPair
|
||||||
|
|
||||||
|
def icheck_torrc(sFile:str, oArgs) -> int:
|
||||||
|
l = open(sFile, 'rt').readlines()
|
||||||
|
a = {}
|
||||||
|
for elt in l:
|
||||||
|
elt = elt.strip()
|
||||||
|
if not elt or ' ' not in elt: continue
|
||||||
|
(k, v,) = elt.split(' ', 1)
|
||||||
|
a[k] = v
|
||||||
|
keys = a
|
||||||
|
|
||||||
|
if 'HashedControlPassword' not in keys:
|
||||||
|
LOG.info('Add HashedControlPassword for security')
|
||||||
|
print('run: tor --hashcontrolpassword <TopSecretWord>')
|
||||||
|
if 'ExcludeExitNodes' in keys:
|
||||||
|
elt = 'BadNodes.ExcludeExitNodes.BadExit'
|
||||||
|
LOG.warn(f"Remove ExcludeNodes and move then to {oArgs.bad_nodes}")
|
||||||
|
print(f"move to the {elt} section as a list")
|
||||||
|
if 'GuardNodes' in keys:
|
||||||
|
elt = 'GoodNodes.GuardNodes'
|
||||||
|
LOG.warn(f"Remove GuardNodes and move then to {oArgs.good_nodes}")
|
||||||
|
print(f"move to the {elt} section as a list")
|
||||||
|
if 'ExcludeNodes' in keys:
|
||||||
|
elt = 'BadNodes.ExcludeNodes.BadExit'
|
||||||
|
LOG.warn(f"Remove ExcludeNodes and move then to {oArgs.bad_nodes}")
|
||||||
|
print(f"move to the {elt} section as a list")
|
||||||
|
if 'ControlSocket' not in keys and os.path.exists('/run/tor/control'):
|
||||||
|
LOG.info('Add ControlSocket /run/tor/control for us')
|
||||||
|
print('ControlSocket /run/tor/control GroupWritable RelaxDirModeCheck')
|
||||||
|
if 'UseMicrodescriptors' not in keys or keys['UseMicrodescriptors'] != '1':
|
||||||
|
LOG.info('Add UseMicrodescriptors 0 for us')
|
||||||
|
print('UseMicrodescriptors 0')
|
||||||
|
if 'AutomapHostsSuffixes' not in keys:
|
||||||
|
LOG.info('Add AutomapHostsSuffixes for onions')
|
||||||
|
print('AutomapHostsSuffixes .exit,.onion')
|
||||||
|
if 'AutoMapHostsOnResolve' not in keys:
|
||||||
|
LOG.info('Add AutoMapHostsOnResolve for onions')
|
||||||
|
print('AutoMapHostsOnResolve 1')
|
||||||
|
if 'VirtualAddrNetworkIPv4' not in keys:
|
||||||
|
LOG.info('Add VirtualAddrNetworkIPv4 for onions')
|
||||||
|
print('VirtualAddrNetworkIPv4 172.16.0.0/12')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def lExitExcluder(oArgs, iPort:int = 9051, log_level:int = 10, password:[str,None] = None) -> list:
|
||||||
|
"""
|
||||||
|
https://raw.githubusercontent.com/nusenu/noContactInfo_Exit_Excluder/main/exclude_noContactInfo_Exits.py
|
||||||
|
"""
|
||||||
|
if not stem:
|
||||||
|
LOG.warn('please install the stem Python package')
|
||||||
|
return ''
|
||||||
|
LOG.debug('lExcludeExitNodes')
|
||||||
|
|
||||||
|
try:
|
||||||
|
controller = oGetStemController(log_level=log_level, password=password)
|
||||||
|
# generator
|
||||||
|
relays = controller.get_server_descriptors()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(f'Failed to get relay descriptors {e}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
if controller.is_set('ExcludeExitNodes'):
|
||||||
|
LOG.info('ExcludeExitNodes is in use already.')
|
||||||
|
return None
|
||||||
|
|
||||||
|
exit_excludelist=[]
|
||||||
|
LOG.debug("Excluded exit relays:")
|
||||||
|
for relay in relays:
|
||||||
|
if relay.exit_policy.is_exiting_allowed() and not relay.contact:
|
||||||
|
if is_valid_fingerprint(relay.fingerprint):
|
||||||
|
exit_excludelist.append(relay.fingerprint)
|
||||||
|
LOG.debug("https://metrics.torproject.org/rs.html#details/%s" % relay.fingerprint)
|
||||||
|
else:
|
||||||
|
LOG.warn('Invalid Fingerprint: %s' % relay.fingerprint)
|
||||||
|
|
||||||
|
try:
|
||||||
|
controller.set_conf('ExcludeExitNodes', exit_excludelist)
|
||||||
|
LOG.info('Excluded a total of %s exit relays without ContactInfo from the exit position.' % len(exit_excludelist))
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception('ExcludeExitNodes ' +str(e))
|
||||||
|
return exit_excludelist
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if len(sys.argv) <= 1:
|
||||||
|
targets = ['zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad', # keys.openpgp.org
|
||||||
|
'facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd', # facebook
|
||||||
|
'libera75jm6of4wxpxt4aynol3xjmbtxgfyjpu34ss4d7r7q2v5zrpyd', # libera
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
targets = sys.argv[1:]
|
||||||
|
|
||||||
|
controller = oGetStemController(log_level=10)
|
||||||
|
for target in targets:
|
||||||
|
print(target, lIntroductionPoints(controller, [target], itimeout=120))
|
||||||
|
|
||||||
|
|
1028
src/toxygen_wrapper/tests/support_testing.py
Normal file
1028
src/toxygen_wrapper/tests/support_testing.py
Normal file
File diff suppressed because it is too large
Load Diff
1774
src/toxygen_wrapper/tests/tests_wrapper.py
Normal file
1774
src/toxygen_wrapper/tests/tests_wrapper.py
Normal file
File diff suppressed because it is too large
Load Diff
662
src/toxygen_wrapper/tests/wrapper_mixin.py
Normal file
662
src/toxygen_wrapper/tests/wrapper_mixin.py
Normal file
@ -0,0 +1,662 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from ctypes import *
|
||||||
|
from typing import Union, Callable, Optional
|
||||||
|
|
||||||
|
import toxygen_wrapper.toxcore_enums_and_consts as enums
|
||||||
|
from toxygen_wrapper.tox import Tox, UINT32_MAX, ToxError
|
||||||
|
|
||||||
|
from toxygen_wrapper.toxcore_enums_and_consts import (TOX_ADDRESS_SIZE, TOX_CONNECTION,
|
||||||
|
TOX_FILE_CONTROL,
|
||||||
|
TOX_MESSAGE_TYPE,
|
||||||
|
TOX_SECRET_KEY_SIZE,
|
||||||
|
TOX_USER_STATUS)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import support_testing as ts
|
||||||
|
except ImportError:
|
||||||
|
import toxygen_wrapper.tests.support_testing as ts
|
||||||
|
sleep = time.sleep
|
||||||
|
|
||||||
|
ADDR_SIZE = 38 * 2
|
||||||
|
CLIENT_ID_SIZE = 32 * 2
|
||||||
|
THRESHOLD = 120 # >25
|
||||||
|
fSOCKET_TIMEOUT = 15.0
|
||||||
|
iLOOP_N = 50
|
||||||
|
|
||||||
|
iN = 6
|
||||||
|
|
||||||
|
global LOG
|
||||||
|
LOG = logging.getLogger('TestS')
|
||||||
|
if False:
|
||||||
|
def LOG_ERROR(l: str) -> None: LOG.error('+ '+l)
|
||||||
|
def LOG_WARN(l: str) -> None: LOG.warn('+ '+l)
|
||||||
|
def LOG_INFO(l: str) -> None: LOG.info('+ '+l)
|
||||||
|
def LOG_DEBUG(l: str) -> None: LOG.debug('+ '+l)
|
||||||
|
def LOG_TRACE(l: str) -> None: pass # print('+ '+l)
|
||||||
|
else:
|
||||||
|
# just print to stdout so there is NO complications from logging.
|
||||||
|
def LOG_ERROR(l: str) -> None: print('EROR+ '+l)
|
||||||
|
def LOG_WARN(l: str) -> None: print('WARN+ '+l)
|
||||||
|
def LOG_INFO(l: str) -> None: print('INFO+ '+l)
|
||||||
|
def LOG_DEBUG(l: str) -> None: print('DEBUG+ '+l)
|
||||||
|
def LOG_TRACE(l: str) -> None: pass # print('TRAC+ '+l)
|
||||||
|
|
||||||
|
class WrapperMixin():
|
||||||
|
|
||||||
|
def bBobNeedAlice(self) -> bool:
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
if hasattr(self, 'baid') and self.baid >= 0 and \
|
||||||
|
self.baid in self.bob.self_get_friend_list():
|
||||||
|
LOG.warn(f"setUp ALICE IS ALREADY IN BOBS FRIEND LIST")
|
||||||
|
return False
|
||||||
|
elif self.bob.self_get_friend_list_size() >= 1:
|
||||||
|
LOG.warn(f"setUp BOB STILL HAS A FRIEND LIST")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def bAliceNeedAddBob (self) -> bool:
|
||||||
|
if hasattr(self, 'abid') and self.abid >= 0 and \
|
||||||
|
self.abid in self.alice.self_get_friend_list():
|
||||||
|
LOG.warn(f"setUp BOB IS ALREADY IN ALICES FRIEND LIST")
|
||||||
|
return False
|
||||||
|
if self.alice.self_get_friend_list_size() >= 1:
|
||||||
|
LOG.warn(f"setUp ALICE STILL HAS A FRIEND LIST")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_connection_status(self) -> bool:
|
||||||
|
if self.bob.mycon_time <= 1 or self.alice.mycon_time <= 1:
|
||||||
|
pass
|
||||||
|
# drop through
|
||||||
|
elif self.bob.dht_connected == TOX_CONNECTION['NONE']:
|
||||||
|
return False
|
||||||
|
elif self.alice.dht_connected == TOX_CONNECTION['NONE']:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# if not self.connected
|
||||||
|
if self.bob.self_get_connection_status() == TOX_CONNECTION['NONE']:
|
||||||
|
return False
|
||||||
|
if self.alice.self_get_connection_status() == TOX_CONNECTION['NONE']:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def loop(self, n) -> None:
|
||||||
|
"""
|
||||||
|
t:iterate
|
||||||
|
t:iteration_interval
|
||||||
|
"""
|
||||||
|
interval = self.bob.iteration_interval()
|
||||||
|
for i in range(n):
|
||||||
|
self.alice.iterate()
|
||||||
|
self.bob.iterate()
|
||||||
|
sleep(interval / 1000.0)
|
||||||
|
|
||||||
|
def call_bootstrap(self, num: Optional[int] = None, lToxes:Union[list[int], None] =None, i:int =0, fsocket_timeout:float = fSOCKET_TIMEOUT) -> None:
|
||||||
|
if num == None: num=ts.iNODES
|
||||||
|
if lToxes is None:
|
||||||
|
lToxes = [self.alice, self.bob]
|
||||||
|
# LOG.debug(f"call_bootstrap network={oTOX_OARGS.network}")
|
||||||
|
otox = lToxes[0]
|
||||||
|
if otox._args.network in ['new', 'newlocal', 'localnew']:
|
||||||
|
ts.bootstrap_local(self.lUdp, lToxes)
|
||||||
|
elif not ts.bAreWeConnected():
|
||||||
|
LOG.warning('we are NOT CONNECTED')
|
||||||
|
else:
|
||||||
|
random.shuffle(self.lUdp)
|
||||||
|
if otox._args.proxy_port > 0:
|
||||||
|
lElts = self.lUdp[:1]
|
||||||
|
else:
|
||||||
|
lElts = self.lUdp[:num+i]
|
||||||
|
LOG.debug(f"call_bootstrap ts.bootstrap_udp {len(lElts)}")
|
||||||
|
ts.bootstrap_udp(lElts, lToxes, fsocket_timeout=fsocket_timeout)
|
||||||
|
random.shuffle(self.lTcp)
|
||||||
|
lElts = self.lTcp[:num+i]
|
||||||
|
LOG.debug(f"call_bootstrap ts.bootstrap_tcp {len(lElts)}")
|
||||||
|
ts.bootstrap_tcp(lElts, lToxes, fsocket_timeout=fsocket_timeout)
|
||||||
|
|
||||||
|
def group_until_connected(self, otox, group_number:int, num: Optional[int] = None, iMax:int = THRESHOLD, fsocket_timeout:float = fSOCKET_TIMEOUT) -> bool:
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
i = 0
|
||||||
|
bRet = None
|
||||||
|
while i <= iMax :
|
||||||
|
i += 1
|
||||||
|
iRet = otox.group_is_connected(group_number)
|
||||||
|
if iRet == True:
|
||||||
|
bRet = True
|
||||||
|
break
|
||||||
|
if i % 5 == 0:
|
||||||
|
j = i//5 + 1
|
||||||
|
self.call_bootstrap(num, lToxes=None, i=j, fsocket_timeout=fsocket_timeout)
|
||||||
|
s = ''
|
||||||
|
if i == 0: s = '\n'
|
||||||
|
LOG.info(s+f"group_until_connected #{i} iRet={iRet}" \
|
||||||
|
+f" BOBS={otox.self_conn_status}" \
|
||||||
|
+f" last={int(otox.mycon_time)}" )
|
||||||
|
self.loop(iLOOP_N)
|
||||||
|
else:
|
||||||
|
bRet = False
|
||||||
|
|
||||||
|
if bRet:
|
||||||
|
LOG.info(f"group_until_connected True i={i} iMax={iMax}" \
|
||||||
|
+f" BOB={otox.self_get_connection_status()}" \
|
||||||
|
+f" last={int(otox.mycon_time)}" )
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
LOG.warning(f"group_until_connected False i={i}" \
|
||||||
|
+f" iMax={iMax}" \
|
||||||
|
+f" BOB={otox.self_get_connection_status()}" \
|
||||||
|
+f" last={int(otox.mycon_time)}" )
|
||||||
|
return False
|
||||||
|
|
||||||
|
def loop_until_connected(self, otox=None, num: Optional[int] = None, fsocket_timeout:float = fSOCKET_TIMEOUT) -> bool:
|
||||||
|
"""
|
||||||
|
t:on_self_connection_status
|
||||||
|
t:self_get_connection_status
|
||||||
|
"""
|
||||||
|
i = 0
|
||||||
|
num = 4
|
||||||
|
bRet = None
|
||||||
|
if otox is None: otox = self.bob
|
||||||
|
while i <= otox._args.test_timeout :
|
||||||
|
i += 1
|
||||||
|
if (self.alice.self_conn_status and self.bob.self_conn_status):
|
||||||
|
bRet = True
|
||||||
|
break
|
||||||
|
if i % 5 == 0:
|
||||||
|
j = i//5 + 1
|
||||||
|
self.call_bootstrap(num, lToxes=None, i=j, fsocket_timeout=fsocket_timeout)
|
||||||
|
s = ''
|
||||||
|
if i == 0: s = '\n'
|
||||||
|
LOG.info(s+"loop_until_connected " \
|
||||||
|
+" #" + str(i) \
|
||||||
|
+" BOB=" +repr(self.bob.self_get_connection_status()) \
|
||||||
|
+" ALICE=" +repr(self.alice.self_get_connection_status())
|
||||||
|
+f" BOBS={self.bob.self_conn_status}" \
|
||||||
|
+f" ALICES={self.alice.self_conn_status}" \
|
||||||
|
+f" last={int(self.bob.mycon_time)}" )
|
||||||
|
if (self.alice.self_conn_status and self.bob.self_conn_status):
|
||||||
|
bRet = True
|
||||||
|
break
|
||||||
|
if (self.alice.self_get_connection_status() and
|
||||||
|
self.bob.self_get_connection_status()):
|
||||||
|
LOG_WARN(f"loop_until_connected disagree status() DISAGREE" \
|
||||||
|
+f' self.bob.self_conn_status={self.bob.self_conn_status}' \
|
||||||
|
+f' alice.self_conn_status={self.alice.self_conn_status}' \
|
||||||
|
+f" last={int(self.bob.mycon_time)}" )
|
||||||
|
bRet = True
|
||||||
|
break
|
||||||
|
self.loop(iLOOP_N)
|
||||||
|
else:
|
||||||
|
bRet = False
|
||||||
|
|
||||||
|
if bRet or \
|
||||||
|
( self.bob.self_get_connection_status() != TOX_CONNECTION['NONE'] and \
|
||||||
|
self.alice.self_get_connection_status() != TOX_CONNECTION['NONE'] ):
|
||||||
|
LOG.info(f"loop_until_connected returning True i={i}" \
|
||||||
|
+f" BOB={self.bob.self_get_connection_status()}" \
|
||||||
|
+f" ALICE={self.alice.self_get_connection_status()}" \
|
||||||
|
+f" last={int(self.bob.mycon_time)}" )
|
||||||
|
return True
|
||||||
|
|
||||||
|
otox._args.test_timeout += 5
|
||||||
|
LOG.warning(f"loop_until_connected returning False i={i}" \
|
||||||
|
+f" BOB={self.bob.self_get_connection_status()}" \
|
||||||
|
+f" ALICE={self.alice.self_get_connection_status()}" \
|
||||||
|
+f" last={int(self.bob.mycon_time)}" )
|
||||||
|
return False
|
||||||
|
|
||||||
|
def wait_objs_attr(self, objs: list, attr: str, fsocket_timeout:float = fSOCKET_TIMEOUT) -> bool:
|
||||||
|
i = 0
|
||||||
|
otox = objs[0]
|
||||||
|
while i <= otox._args.test_timeout:
|
||||||
|
i += 1
|
||||||
|
if i % 5 == 0:
|
||||||
|
num = None
|
||||||
|
j = 0
|
||||||
|
j = i//5
|
||||||
|
self.call_bootstrap(num, lToxes=objs, i=j, fsocket_timeout=fsocket_timeout)
|
||||||
|
LOG.debug(f"wait_objs_attr {objs} for {attr} {i}")
|
||||||
|
if all([getattr(obj, attr) for obj in objs]):
|
||||||
|
return True
|
||||||
|
self.loop(iLOOP_N)
|
||||||
|
else:
|
||||||
|
otox._args.test_timeout += 1
|
||||||
|
LOG.warn(f"wait_objs_attr for {attr} i >= {otox._args.test_timeout}")
|
||||||
|
|
||||||
|
return all([getattr(obj, attr) is not None for obj in objs])
|
||||||
|
|
||||||
|
def wait_otox_attrs(self, obj, attrs: list[str], fsocket_timeout:float = fSOCKET_TIMEOUT) -> bool:
|
||||||
|
assert all(attrs), f"wait_otox_attrs {attrs}"
|
||||||
|
i = 0
|
||||||
|
otox = obj
|
||||||
|
while i <= otox._args.test_timeout:
|
||||||
|
i += 1
|
||||||
|
if i % 5 == 0:
|
||||||
|
num = None
|
||||||
|
j = 0
|
||||||
|
if obj.mycon_time == 1:
|
||||||
|
# start with 4 random nodes ti bootstrap
|
||||||
|
num = 4
|
||||||
|
# every 10 sec add another random nodes to bootstrap
|
||||||
|
j = i//10 + 1
|
||||||
|
if obj.self_get_connection_status() == TOX_CONNECTION['NONE']:
|
||||||
|
self.call_bootstrap(num, lToxes=[obj], i=j, fsocket_timeout=fsocket_timeout)
|
||||||
|
LOG.debug(f"wait_otox_attrs {obj.name} for {attrs} {i}" \
|
||||||
|
+f" last={int(obj.mycon_time)}")
|
||||||
|
if all([getattr(obj, attr) is not None for attr in attrs]):
|
||||||
|
return True
|
||||||
|
self.loop(iLOOP_N)
|
||||||
|
else:
|
||||||
|
LOG.warning(f"wait_otox_attrs i >= {otox._args.test_timeout} attrs={attrs} results={[getattr(obj, attr) for attr in attrs]}")
|
||||||
|
|
||||||
|
return all([getattr(obj, attr) for attr in attrs])
|
||||||
|
|
||||||
|
def wait_ensure_exec(self, method, args:list, fsocket_timeout:float = fSOCKET_TIMEOUT) -> bool:
|
||||||
|
i = 0
|
||||||
|
oRet = None
|
||||||
|
while i <= self.bob._args.test_timeout:
|
||||||
|
i += 1
|
||||||
|
if i % 5 == 0:
|
||||||
|
# every 10 sec add another random nodes to bootstrap
|
||||||
|
j = i//10 + 1
|
||||||
|
self.call_bootstrap(num=None, lToxes=None, i=j, fsocket_timeout=fsocket_timeout)
|
||||||
|
LOG.debug("wait_ensure_exec " \
|
||||||
|
+" " +str(method)
|
||||||
|
+" " +str(i))
|
||||||
|
try:
|
||||||
|
oRet = method(*args)
|
||||||
|
if oRet:
|
||||||
|
LOG.info(f"wait_ensure_exec oRet {oRet!r}")
|
||||||
|
return True
|
||||||
|
except ArgumentError as e:
|
||||||
|
# ArgumentError('This client is currently NOT CONNECTED to the friend.')
|
||||||
|
# dunno
|
||||||
|
LOG.warning(f"wait_ensure_exec ArgumentError {e}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning(f"wait_ensure_exec EXCEPTION {e}")
|
||||||
|
return False
|
||||||
|
sleep(3)
|
||||||
|
else:
|
||||||
|
LOG.error(f"wait_ensure_exec i >= {1*self.bob._args.test_timeout}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return oRet
|
||||||
|
|
||||||
|
def bob_add_alice_as_friend_norequest(self) -> bool:
|
||||||
|
if not self.bBobNeedAlice(): return True
|
||||||
|
|
||||||
|
apk = self.alice.self_get_public_key()
|
||||||
|
iRet = self.bob.friend_add_norequest(apk)
|
||||||
|
if iRet < 0:
|
||||||
|
return False
|
||||||
|
self.baid = self.bob.friend_by_public_key(apk)
|
||||||
|
assert self.baid >= 0, self.baid
|
||||||
|
assert self.bob.friend_exists(self.baid), "bob.friend_exists"
|
||||||
|
assert not self.bob.friend_exists(self.baid + 1)
|
||||||
|
assert self.baid in self.bob.self_get_friend_list()
|
||||||
|
assert self.bob.self_get_friend_list_size() >= 1
|
||||||
|
return True
|
||||||
|
|
||||||
|
def alice_add_bob_as_friend_norequest(self) -> bool:
|
||||||
|
if not self.bAliceNeedAddBob(): return True
|
||||||
|
|
||||||
|
bpk = self.bob.self_get_public_key()
|
||||||
|
iRet = self.alice.friend_add_norequest(bpk)
|
||||||
|
if iRet < 0:
|
||||||
|
return False
|
||||||
|
self.abid = self.alice.friend_by_public_key(bpk)
|
||||||
|
assert self.abid >= 0, self.abid
|
||||||
|
assert self.abid in self.alice.self_get_friend_list()
|
||||||
|
assert self.alice.friend_exists(self.abid), "alice.friend_exists"
|
||||||
|
assert not self.alice.friend_exists(self.abid + 1)
|
||||||
|
assert self.alice.self_get_friend_list_size() >= 1
|
||||||
|
return True
|
||||||
|
|
||||||
|
def both_add_as_friend(self) -> bool:
|
||||||
|
if self.bob._args.norequest:
|
||||||
|
assert self.bob_add_alice_as_friend_norequest()
|
||||||
|
assert self.alice_add_bob_as_friend_norequest()
|
||||||
|
else:
|
||||||
|
assert self.bob_add_alice_as_friend()
|
||||||
|
assert self.alice_add_bob_as_friend()
|
||||||
|
if not hasattr(self, 'baid') or self.baid < 0:
|
||||||
|
LOG.warn("both_add_as_friend no bob, baid")
|
||||||
|
if not hasattr(self, 'abid') or self.abid < 0:
|
||||||
|
LOG.warn("both_add_as_friend no alice, abid")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def both_add_as_friend_norequest(self) -> bool:
|
||||||
|
if self.bBobNeedAlice():
|
||||||
|
assert self.bob_add_alice_as_friend_norequest()
|
||||||
|
if self.bAliceNeedAddBob():
|
||||||
|
assert self.alice_add_bob_as_friend_norequest()
|
||||||
|
if not hasattr(self.bob, 'baid') or self.bob.baid < 0:
|
||||||
|
LOG.warn("both_add_as_friend_norequest no bob, baid")
|
||||||
|
if not hasattr(self.alice, 'abid') or self.alice.abid < 0:
|
||||||
|
LOG.warn("both_add_as_friend_norequest no alice, abid")
|
||||||
|
|
||||||
|
#: Test last online
|
||||||
|
#? assert self.alice.friend_get_last_online(self.abid) is not None
|
||||||
|
#? assert self.bob.friend_get_last_online(self.baid) is not None
|
||||||
|
return True
|
||||||
|
|
||||||
|
def bob_add_alice_as_friend(self) -> bool:
|
||||||
|
"""
|
||||||
|
t:friend_add
|
||||||
|
t:on_friend_request
|
||||||
|
t:friend_by_public_key
|
||||||
|
"""
|
||||||
|
MSG = 'Alice, this is Bob.'
|
||||||
|
sSlot = 'friend_request'
|
||||||
|
if not self.bBobNeedAlice(): return True
|
||||||
|
|
||||||
|
def alices_on_friend_request(iTox,
|
||||||
|
public_key,
|
||||||
|
message_data,
|
||||||
|
message_data_size,
|
||||||
|
*largs) -> None:
|
||||||
|
try:
|
||||||
|
assert str(message_data, 'UTF-8') == MSG
|
||||||
|
LOG_INFO(f"alices_on_friend_request: {sSlot} = True ")
|
||||||
|
except Exception as e:
|
||||||
|
LOG_WARN(f"alices_on_friend_request: EXCEPTION {e}")
|
||||||
|
# return
|
||||||
|
setattr(self.bob, sSlot, True)
|
||||||
|
|
||||||
|
setattr(self.bob, sSlot, None)
|
||||||
|
apk = self.alice.self_get_public_key()
|
||||||
|
inum = -1
|
||||||
|
try:
|
||||||
|
inum = self.bob.friend_add(self.alice._address, bytes(MSG, 'UTF-8'))
|
||||||
|
assert inum >= 0, f"bob_add_alice_as_friend !>= 0 {inum}"
|
||||||
|
# need a friend connected?
|
||||||
|
if not self.get_connection_status():
|
||||||
|
LOG.warning(f"test_groups_join NOT CONNECTED")
|
||||||
|
self.loop_until_connected(self.bob)
|
||||||
|
self.alice.callback_friend_request(alices_on_friend_request)
|
||||||
|
if not self.wait_otox_attrs(self.bob, [sSlot]):
|
||||||
|
LOG_WARN(f"bob_add_alice_as_friend NO setting {sSlot}")
|
||||||
|
return False
|
||||||
|
self.baid = self.bob.friend_by_public_key(apk)
|
||||||
|
assert self.baid >= 0, self.baid
|
||||||
|
assert self.bob.friend_exists(self.baid)
|
||||||
|
assert not self.bob.friend_exists(self.baid + 1)
|
||||||
|
assert self.bob.self_get_friend_list_size() >= 1
|
||||||
|
assert self.baid in self.bob.self_get_friend_list()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(f"bob_add_alice_as_friend EXCEPTION {e}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
self.bob.callback_friend_message(None)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def alice_add_bob_as_friend(self) -> bool:
|
||||||
|
"""
|
||||||
|
t:friend_add
|
||||||
|
t:on_friend_request
|
||||||
|
t:friend_by_public_key
|
||||||
|
"""
|
||||||
|
MSG = 'Bob, this is Alice.'
|
||||||
|
sSlot = 'friend_request'
|
||||||
|
if not self.bAliceNeedAddBob(): return True
|
||||||
|
|
||||||
|
def bobs_on_friend_request(iTox,
|
||||||
|
public_key,
|
||||||
|
message_data,
|
||||||
|
message_data_size,
|
||||||
|
*largs) -> None:
|
||||||
|
LOG_DEBUG(f"bobs_on_friend_request: " +repr(message_data))
|
||||||
|
try:
|
||||||
|
assert str(message_data, 'UTF-8') == MSG
|
||||||
|
except Exception as e:
|
||||||
|
LOG_WARN(f"bobs_on_friend_request: Exception {e}")
|
||||||
|
# return
|
||||||
|
setattr(self.alice, sSlot, True)
|
||||||
|
|
||||||
|
LOG_INFO(f"bobs_on_friend_request: {sSlot} = True ")
|
||||||
|
setattr(self.alice, sSlot, None)
|
||||||
|
bpk = self.bob.self_get_public_key()
|
||||||
|
inum = -1
|
||||||
|
try:
|
||||||
|
inum = self.alice.friend_add(self.bob._address, bytes(MSG, 'UTF-8'))
|
||||||
|
assert inum >= 0, f"alice.friend_add !>= 0 {inum}"
|
||||||
|
self.bob.callback_friend_request(bobs_on_friend_request)
|
||||||
|
if not self.wait_otox_attrs(self.alice, [sSlot]):
|
||||||
|
LOG_WARN(f"alice.friend_add NO wait {sSlot}")
|
||||||
|
return False
|
||||||
|
self.abid = self.alice.friend_by_public_key(bpk)
|
||||||
|
assert self.abid >= 0, self.abid
|
||||||
|
assert self.alice.friend_exists(self.abid), "not exists"
|
||||||
|
assert not self.alice.friend_exists(self.abid + 1), "exists +1"
|
||||||
|
assert self.abid in self.alice.self_get_friend_list(), "not in list"
|
||||||
|
assert self.alice.self_get_friend_list_size() >= 1, "list size"
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(f"alice.friend_add EXCEPTION {e}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
self.bob.callback_friend_message(None)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def bob_add_alice_as_friend_and_status(self) -> bool:
|
||||||
|
if self.bob._args.norequest:
|
||||||
|
assert self.bob_add_alice_as_friend_norequest()
|
||||||
|
else:
|
||||||
|
assert self.bob_add_alice_as_friend()
|
||||||
|
|
||||||
|
#: Wait until both are online
|
||||||
|
sSlot = 'friend_status'
|
||||||
|
setattr(self.bob, sSlot, None)
|
||||||
|
def bobs_on_friend_status(iTox, friend_id, iStatus, *largs) -> None:
|
||||||
|
LOG_INFO(f"bobs_on_friend_status {friend_id} ?>=0 iS={iStatus}")
|
||||||
|
setattr(self.bob, sSlot, False)
|
||||||
|
|
||||||
|
|
||||||
|
sSlot = 'friend_status'
|
||||||
|
setattr(self.alice, sSlot, None)
|
||||||
|
def alices_on_friend_status(iTox, friend_id, iStatus, *largs) -> None:
|
||||||
|
LOG_INFO(f"alices_on_friend_status {friend_id} ?>=0 iS={iStatus}")
|
||||||
|
setattr(self.alice, sSlot, False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# need a friend connected?
|
||||||
|
if not self.get_connection_status():
|
||||||
|
self.loop_until_connected(self.bob)
|
||||||
|
LOG.info("bob_add_alice_as_friend_and_status waiting for alice connections")
|
||||||
|
if not self.wait_otox_attrs(self.alice,
|
||||||
|
[ # 'friend_conn_status',
|
||||||
|
'friend_status']):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# self.bob.callback_friend_connection_status(bobs_on_friend_connection_status)
|
||||||
|
self.bob.callback_friend_status(bobs_on_friend_status)
|
||||||
|
# self.alice.callback_friend_connection_status(alices_on_friend_connection_status)
|
||||||
|
self.alice.callback_friend_status(alices_on_friend_status)
|
||||||
|
|
||||||
|
LOG.info("bob_add_alice_as_friend_and_status waiting for bob connections")
|
||||||
|
if not self.wait_otox_attrs(self.bob,
|
||||||
|
[ # 'friend_conn_status',
|
||||||
|
'friend_status']):
|
||||||
|
LOG_WARN('bob_add_alice_as_friend_and_status NO')
|
||||||
|
# return False
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(f"bob_add_alice_as_friend_and_status ERROR {e}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
# self.alice.callback_friend_connection_status(None)
|
||||||
|
# self.bob.callback_friend_connection_status(None)
|
||||||
|
self.alice.callback_friend_status(None)
|
||||||
|
self.bob.callback_friend_status(None)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def otox_test_groups_create(self,
|
||||||
|
otox,
|
||||||
|
group_name='test_group',
|
||||||
|
nick='test_nick',
|
||||||
|
topic='Test Topic', # str
|
||||||
|
) -> int:
|
||||||
|
privacy_state = enums.TOX_GROUP_PRIVACY_STATE['PUBLIC']
|
||||||
|
|
||||||
|
iGrp = otox.group_new(privacy_state, group_name, nick)
|
||||||
|
assert iGrp >= 0
|
||||||
|
LOG.info(f"group iGrp={iGrp}")
|
||||||
|
|
||||||
|
otox.group_set_topic(iGrp, topic)
|
||||||
|
assert otox.group_get_topic(iGrp) == topic
|
||||||
|
assert otox.group_get_topic_size(iGrp) == len(topic)
|
||||||
|
|
||||||
|
name = otox.group_get_name(iGrp)
|
||||||
|
if type(name) == bytes:
|
||||||
|
name = str(name, 'utf-8')
|
||||||
|
assert name == group_name, name
|
||||||
|
assert otox.group_get_name_size(iGrp) == len(group_name)
|
||||||
|
|
||||||
|
sPk = otox.group_self_get_public_key(iGrp)
|
||||||
|
i = otox.group_get_password_size(iGrp)
|
||||||
|
assert otox.group_get_password_size(iGrp) >= 0
|
||||||
|
sP = otox.group_get_password(iGrp)
|
||||||
|
assert len(sP) == i, sP
|
||||||
|
assert otox.group_get_privacy_state(iGrp) == privacy_state
|
||||||
|
i = otox.tox_group_get_topic_lock(iGrp)
|
||||||
|
|
||||||
|
assert otox.group_get_number_groups() > 0, "numg={otox.group_get_number_groups()}"
|
||||||
|
LOG.info(f"group pK={sPk} iGrp={iGrp} numg={otox.group_get_number_groups()}")
|
||||||
|
return iGrp
|
||||||
|
|
||||||
|
def otox_verify_group(self, otox, iGrp) -> None:
|
||||||
|
"""
|
||||||
|
group_self_get_name
|
||||||
|
group_self_get_peer_id
|
||||||
|
group_self_get_public_key
|
||||||
|
group_self_get_role
|
||||||
|
group_self_get_status
|
||||||
|
group_self_set_name
|
||||||
|
"""
|
||||||
|
|
||||||
|
group_number = iGrp
|
||||||
|
try:
|
||||||
|
assert type(iGrp) == int, "otox_test_groups_join iGrp not an int"
|
||||||
|
assert iGrp < UINT32_MAX, "otox_test_groups_join iGrp failure UINT32_MAX"
|
||||||
|
assert iGrp >= 0, f"otox_test_groups_join iGrp={iGrp} < 0"
|
||||||
|
sGrp = otox.group_get_chat_id(iGrp)
|
||||||
|
assert len(sGrp) == enums.TOX_GROUP_CHAT_ID_SIZE * 2, \
|
||||||
|
f"group sGrp={sGrp} {len(sGrp)} != {enums.TOX_GROUP_CHAT_ID_SIZE * 2}"
|
||||||
|
sPk = otox.group_self_get_public_key(iGrp)
|
||||||
|
LOG.info(f"otox_verify_group sPk={sPk} iGrp={iGrp} n={otox.group_get_number_groups()}")
|
||||||
|
|
||||||
|
sName = otox.group_self_get_name(iGrp)
|
||||||
|
iStat = otox.group_self_get_status(iGrp)
|
||||||
|
iId = otox.group_self_get_peer_id(iGrp)
|
||||||
|
iRole = otox.group_self_get_role(iGrp)
|
||||||
|
iStat = otox.group_self_get_status(iGrp)
|
||||||
|
LOG.info(f"otox_verify_group sName={sName} iStat={iStat} iId={iId} iRole={iRole} iStat={iStat}")
|
||||||
|
|
||||||
|
assert otox.group_self_set_name(iGrp, "NewName")
|
||||||
|
|
||||||
|
bRet = otox.group_is_connected(iGrp)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warn(f"group_is_connected EXCEPTION {e}")
|
||||||
|
return -1
|
||||||
|
# chat->connection_state == CS_CONNECTED || chat->connection_state == CS_CONNECTING;
|
||||||
|
if not bRet:
|
||||||
|
LOG.warn(f"group_is_connected WARN not connected iGrp={iGrp} n={otox.group_get_number_groups()}")
|
||||||
|
else:
|
||||||
|
LOG.info(f"group_is_connected SUCCESS connected iGrp={iGrp} n={otox.group_get_number_groups()}")
|
||||||
|
try:
|
||||||
|
bRet = self.group_until_connected(otox, iGrp, iMax=2*otox._args.test_timeout)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(f"group_until_connected EXCEPTION {e}")
|
||||||
|
return -1
|
||||||
|
# chat->connection_state == CS_CONNECTED || chat->connection_state == CS_CONNECTING;
|
||||||
|
if bRet:
|
||||||
|
LOG.warn(f"group_until_connected WARN not connected iGrp={iGrp} n={otox.group_get_number_groups()}")
|
||||||
|
else:
|
||||||
|
LOG.info(f"group_until_connected SUCCESS connected iGrp={iGrp} n={otox.group_get_number_groups()}")
|
||||||
|
|
||||||
|
message = bytes('hello', 'utf-8')
|
||||||
|
bRet = otox.group_send_message(iGrp, TOX_MESSAGE_TYPE['NORMAL'], message)
|
||||||
|
if not bRet:
|
||||||
|
LOG.warn(f"group_send_message {bRet}")
|
||||||
|
else:
|
||||||
|
LOG.debug(f"group_send_message {bRet}")
|
||||||
|
|
||||||
|
# 360497DA684BCE2A500C1AF9B3A5CE949BBB9F6FB1F91589806FB04CA039E313
|
||||||
|
# 75D2163C19FEFFE51508046398202DDC321E6F9B6654E99BAE45FFEC134F05DE
|
||||||
|
def otox_test_groups_join(self, otox,
|
||||||
|
chat_id="75d2163c19feffe51508046398202ddc321e6f9b6654e99bae45ffec134f05de",
|
||||||
|
nick='nick',
|
||||||
|
topic='Test Topic', # str
|
||||||
|
):
|
||||||
|
status = ''
|
||||||
|
password = ''
|
||||||
|
LOG.debug(f"group_join nick={nick} chat_id={chat_id}")
|
||||||
|
try:
|
||||||
|
iGrp = otox.group_join(chat_id, password, nick, status)
|
||||||
|
LOG.info(f"otox_test_groups_join SUCCESS iGrp={iGrp} chat_id={chat_id}")
|
||||||
|
self.otox_verify_group(otox, iGrp)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# gui
|
||||||
|
LOG.error(f"otox_test_groups_join EXCEPTION {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
return iGrp
|
||||||
|
|
||||||
|
def otox_test_groups(self,
|
||||||
|
otox,
|
||||||
|
group_name='test_group',
|
||||||
|
nick='test_nick',
|
||||||
|
topic='Test Topic', # str
|
||||||
|
) -> int:
|
||||||
|
|
||||||
|
try:
|
||||||
|
iGrp = self.otox_test_groups_create(otox, group_name, nick, topic)
|
||||||
|
self.otox_verify_group(otox, iGrp)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(f"otox_test_groups ERROR {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# unfinished
|
||||||
|
# tox_group_peer_exit_cb
|
||||||
|
# tox_callback_group_peer_join
|
||||||
|
# tox.callback_group_peer_status
|
||||||
|
# tox.callback_group_peer_name
|
||||||
|
# tox.callback_group_peer_exit
|
||||||
|
# tox.callback_group_peer_join
|
||||||
|
return iGrp
|
||||||
|
|
||||||
|
def wait_friend_get_connection_status(self, otox, fid:int, n:int = iN) -> int:
|
||||||
|
i = 0
|
||||||
|
while i < n:
|
||||||
|
i += 1
|
||||||
|
iRet = otox.friend_get_connection_status(fid)
|
||||||
|
if iRet == TOX_CONNECTION['NONE']:
|
||||||
|
LOG.debug(f"wait_friend_get_connection_status NOT CONNECTED i={i} fid={fid} {iRet}")
|
||||||
|
self.loop_until_connected(otox)
|
||||||
|
else:
|
||||||
|
LOG.info(f"wait_friend_get_connection_status fid={fid} {iRet}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
LOG.error(f"wait_friend_get_connection_status fid={fid} n={n}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def warn_if_no_cb(self, alice, sSlot:str) -> None:
|
||||||
|
if not hasattr(alice, sSlot+'_cb') or \
|
||||||
|
not getattr(alice, sSlot+'_cb'):
|
||||||
|
LOG.warning(f"self.bob.{sSlot}_cb NOT EXIST")
|
||||||
|
|
||||||
|
def warn_if_cb(self, alice, sSlot:str) -> None:
|
||||||
|
if hasattr(self.bob, sSlot+'_cb') and \
|
||||||
|
getattr(self.bob, sSlot+'_cb'):
|
||||||
|
LOG.warning(f"self.bob.{sSlot}_cb EXIST")
|
File diff suppressed because it is too large
Load Diff
@ -1,18 +1,23 @@
|
|||||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
import logging
|
from ctypes import (CFUNCTYPE, POINTER, ArgumentError, byref, c_bool, c_char_p,
|
||||||
from ctypes import c_int, POINTER, c_void_p, byref, ArgumentError, c_uint32, CFUNCTYPE, c_size_t, c_uint8, c_uint16
|
c_int, c_int32, c_size_t, c_uint8, c_uint16, c_uint32,
|
||||||
from ctypes import c_char_p, c_int32, c_bool, cast
|
c_void_p, cast)
|
||||||
|
from typing import Union, Callable
|
||||||
|
|
||||||
from wrapper.libtox import LibToxAV
|
try:
|
||||||
from wrapper.toxav_enums import *
|
from toxygen_wrapper.libtox import LibToxAV
|
||||||
|
import toxygen_wrapper.toxav_enums as enum
|
||||||
|
except:
|
||||||
|
from libtox import LibToxAV
|
||||||
|
import toxav_enums as enum
|
||||||
|
class ToxError(ArgumentError): pass
|
||||||
|
|
||||||
LOG = logging.getLogger('app.'+__name__)
|
def LOG_ERROR(a: str) -> None: print('EROR> '+a)
|
||||||
def LOG_ERROR(a): print('EROR> '+a)
|
def LOG_WARN(a: str) -> None: print('WARN> '+a)
|
||||||
def LOG_WARN(a): print('WARN> '+a)
|
def LOG_INFO(a: str) -> None: print('INFO> '+a)
|
||||||
def LOG_INFO(a): print('INFO> '+a)
|
def LOG_DEBUG(a: str) -> None: print('DBUG> '+a)
|
||||||
def LOG_DEBUG(a): print('DBUG> '+a)
|
def LOG_TRACE(a: str) -> None: pass # print('DEBUGx: '+a)
|
||||||
def LOG_TRACE(a): pass # print('DEBUGx: '+a)
|
|
||||||
|
|
||||||
class ToxAV:
|
class ToxAV:
|
||||||
"""
|
"""
|
||||||
@ -22,9 +27,7 @@ class ToxAV:
|
|||||||
peers.
|
peers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Creation and destruction
|
# Creation and destruction
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def __init__(self, tox_pointer):
|
def __init__(self, tox_pointer):
|
||||||
"""
|
"""
|
||||||
@ -38,20 +41,20 @@ class ToxAV:
|
|||||||
f.restype = POINTER(c_void_p)
|
f.restype = POINTER(c_void_p)
|
||||||
self._toxav_pointer = f(tox_pointer, byref(toxav_err_new))
|
self._toxav_pointer = f(tox_pointer, byref(toxav_err_new))
|
||||||
toxav_err_new = toxav_err_new.value
|
toxav_err_new = toxav_err_new.value
|
||||||
if toxav_err_new == TOXAV_ERR_NEW['NULL']:
|
if toxav_err_new == enum.TOXAV_ERR_NEW['NULL']:
|
||||||
raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
|
raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
|
||||||
elif toxav_err_new == TOXAV_ERR_NEW['MALLOC']:
|
if toxav_err_new == enum.TOXAV_ERR_NEW['MALLOC']:
|
||||||
raise MemoryError('Memory allocation failure while trying to allocate structures required for the A/V '
|
raise MemoryError('Memory allocation failure while trying to allocate structures required for the A/V '
|
||||||
'session.')
|
'session.')
|
||||||
elif toxav_err_new == TOXAV_ERR_NEW['MULTIPLE']:
|
if toxav_err_new == enum.TOXAV_ERR_NEW['MULTIPLE']:
|
||||||
raise RuntimeError('Attempted to create a second session for the same Tox instance.')
|
raise ToxError('Attempted to create a second session for the same Tox instance.')
|
||||||
|
|
||||||
self.call_state_cb = None
|
self.call_state_cb = None
|
||||||
self.audio_receive_frame_cb = None
|
self.audio_receive_frame_cb = None
|
||||||
self.video_receive_frame_cb = None
|
self.video_receive_frame_cb = None
|
||||||
self.call_cb = None
|
self.call_cb = None
|
||||||
|
|
||||||
def kill(self):
|
def kill(self) -> None:
|
||||||
"""
|
"""
|
||||||
Releases all resources associated with the A/V session.
|
Releases all resources associated with the A/V session.
|
||||||
|
|
||||||
@ -69,31 +72,27 @@ class ToxAV:
|
|||||||
self.libtoxav.toxav_get_tox.restype = POINTER(c_void_p)
|
self.libtoxav.toxav_get_tox.restype = POINTER(c_void_p)
|
||||||
return self.libtoxav.toxav_get_tox(self._toxav_pointer)
|
return self.libtoxav.toxav_get_tox(self._toxav_pointer)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# A/V event loop
|
# A/V event loop
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def iteration_interval(self):
|
def iteration_interval(self) -> int:
|
||||||
"""
|
"""
|
||||||
Returns the interval in milliseconds when the next toxav_iterate call should be. If no call is active at the
|
Returns the interval in milliseconds when the next toxav_iterate call should be. If no call is active at the
|
||||||
moment, this function returns 200.
|
moment, this function returns 200.
|
||||||
|
|
||||||
:return: interval in milliseconds
|
:return: interval in milliseconds
|
||||||
"""
|
"""
|
||||||
return self.libtoxav.toxav_iteration_interval(self._toxav_pointer)
|
return int(self.libtoxav.toxav_iteration_interval(self._toxav_pointer))
|
||||||
|
|
||||||
def iterate(self):
|
def iterate(self) -> None:
|
||||||
"""
|
"""
|
||||||
Main loop for the session. This function needs to be called in intervals of toxav_iteration_interval()
|
Main loop for the session. This function needs to be called in intervals of toxav_iteration_interval()
|
||||||
milliseconds. It is best called in the separate thread from tox_iterate.
|
milliseconds. It is best called in the separate thread from tox_iterate.
|
||||||
"""
|
"""
|
||||||
self.libtoxav.toxav_iterate(self._toxav_pointer)
|
self.libtoxav.toxav_iterate(self._toxav_pointer)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Call setup
|
# Call setup
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def call(self, friend_number, audio_bit_rate, video_bit_rate):
|
def call(self, friend_number: int, audio_bit_rate: int, video_bit_rate: int) -> bool:
|
||||||
"""
|
"""
|
||||||
Call a friend. This will start ringing the friend.
|
Call a friend. This will start ringing the friend.
|
||||||
|
|
||||||
@ -111,23 +110,24 @@ class ToxAV:
|
|||||||
result = self.libtoxav.toxav_call(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
|
result = self.libtoxav.toxav_call(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
|
||||||
c_uint32(video_bit_rate), byref(toxav_err_call))
|
c_uint32(video_bit_rate), byref(toxav_err_call))
|
||||||
toxav_err_call = toxav_err_call.value
|
toxav_err_call = toxav_err_call.value
|
||||||
if toxav_err_call == TOXAV_ERR_CALL['OK']:
|
if toxav_err_call == enum.TOXAV_ERR_CALL['OK']:
|
||||||
return bool(result)
|
return bool(result)
|
||||||
elif toxav_err_call == TOXAV_ERR_CALL['MALLOC']:
|
if toxav_err_call == enum.TOXAV_ERR_CALL['MALLOC']:
|
||||||
raise MemoryError('A resource allocation error occurred while trying to create the structures required for '
|
raise MemoryError('A resource allocation error occurred while trying to create the structures required for '
|
||||||
'the call.')
|
'the call.')
|
||||||
elif toxav_err_call == TOXAV_ERR_CALL['SYNC']:
|
if toxav_err_call == enum.TOXAV_ERR_CALL['SYNC']:
|
||||||
raise RuntimeError('Synchronization error occurred.')
|
raise ToxError('Synchronization error occurred.')
|
||||||
elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_NOT_FOUND']:
|
if toxav_err_call == enum.TOXAV_ERR_CALL['FRIEND_NOT_FOUND']:
|
||||||
raise ArgumentError('The friend number did not designate a valid friend.')
|
raise ArgumentError('The friend number did not designate a valid friend.')
|
||||||
elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_NOT_CONNECTED']:
|
if toxav_err_call == enum.TOXAV_ERR_CALL['FRIEND_NOT_CONNECTED']:
|
||||||
raise ArgumentError('The friend was valid, but not currently connected.')
|
raise ArgumentError('The friend was valid, but not currently connected.')
|
||||||
elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_ALREADY_IN_CALL']:
|
if toxav_err_call == enum.TOXAV_ERR_CALL['FRIEND_ALREADY_IN_CALL']:
|
||||||
raise ArgumentError('Attempted to call a friend while already in an audio or video call with them.')
|
raise ArgumentError('Attempted to call a friend while already in an audio or video call with them.')
|
||||||
elif toxav_err_call == TOXAV_ERR_CALL['INVALID_BIT_RATE']:
|
if toxav_err_call == enum.TOXAV_ERR_CALL['INVALID_BIT_RATE']:
|
||||||
raise ArgumentError('Audio or video bit rate is invalid.')
|
raise ArgumentError('Audio or video bit rate is invalid.')
|
||||||
|
raise ArgumentError('The function did not return OK')
|
||||||
|
|
||||||
def callback_call(self, callback, user_data):
|
def callback_call(self, callback: Union[Callable,None], user_data) -> None:
|
||||||
"""
|
"""
|
||||||
Set the callback for the `call` event. Pass None to unset.
|
Set the callback for the `call` event. Pass None to unset.
|
||||||
|
|
||||||
@ -149,7 +149,7 @@ class ToxAV:
|
|||||||
self.call_cb = c_callback(callback)
|
self.call_cb = c_callback(callback)
|
||||||
self.libtoxav.toxav_callback_call(self._toxav_pointer, self.call_cb, user_data)
|
self.libtoxav.toxav_callback_call(self._toxav_pointer, self.call_cb, user_data)
|
||||||
|
|
||||||
def answer(self, friend_number, audio_bit_rate, video_bit_rate):
|
def answer(self, friend_number: int, audio_bit_rate: int, video_bit_rate: int) -> bool:
|
||||||
"""
|
"""
|
||||||
Accept an incoming call.
|
Accept an incoming call.
|
||||||
|
|
||||||
@ -163,29 +163,31 @@ class ToxAV:
|
|||||||
"""
|
"""
|
||||||
toxav_err_answer = c_int()
|
toxav_err_answer = c_int()
|
||||||
LOG_DEBUG(f"toxav_answer")
|
LOG_DEBUG(f"toxav_answer")
|
||||||
result = self.libtoxav.toxav_answer(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
|
result = self.libtoxav.toxav_answer(self._toxav_pointer,
|
||||||
c_uint32(video_bit_rate), byref(toxav_err_answer))
|
c_uint32(friend_number),
|
||||||
|
c_uint32(audio_bit_rate),
|
||||||
|
c_uint32(video_bit_rate),
|
||||||
|
byref(toxav_err_answer))
|
||||||
toxav_err_answer = toxav_err_answer.value
|
toxav_err_answer = toxav_err_answer.value
|
||||||
if toxav_err_answer == TOXAV_ERR_ANSWER['OK']:
|
if toxav_err_answer == enum.TOXAV_ERR_ANSWER['OK']:
|
||||||
return bool(result)
|
return bool(result)
|
||||||
elif toxav_err_answer == TOXAV_ERR_ANSWER['SYNC']:
|
if toxav_err_answer == enum.TOXAV_ERR_ANSWER['SYNC']:
|
||||||
raise RuntimeError('Synchronization error occurred.')
|
raise ToxError('Synchronization error occurred.')
|
||||||
elif toxav_err_answer == TOXAV_ERR_ANSWER['CODEC_INITIALIZATION']:
|
if toxav_err_answer == enum.TOXAV_ERR_ANSWER['CODEC_INITIALIZATION']:
|
||||||
raise RuntimeError('Failed to initialize codecs for call session. Note that codec initiation will fail if '
|
raise ToxError('Failed to initialize codecs for call session. Note that codec initiation will fail if '
|
||||||
'there is no receive callback registered for either audio or video.')
|
'there is no receive callback registered for either audio or video.')
|
||||||
elif toxav_err_answer == TOXAV_ERR_ANSWER['FRIEND_NOT_FOUND']:
|
if toxav_err_answer == enum.TOXAV_ERR_ANSWER['FRIEND_NOT_FOUND']:
|
||||||
raise ArgumentError('The friend number did not designate a valid friend.')
|
raise ArgumentError('The friend number did not designate a valid friend.')
|
||||||
elif toxav_err_answer == TOXAV_ERR_ANSWER['FRIEND_NOT_CALLING']:
|
if toxav_err_answer == enum.TOXAV_ERR_ANSWER['FRIEND_NOT_CALLING']:
|
||||||
raise ArgumentError('The friend was valid, but they are not currently trying to initiate a call. This is '
|
raise ArgumentError('The friend was valid, but they are not currently trying to initiate a call. This is '
|
||||||
'also returned if this client is already in a call with the friend.')
|
'also returned if this client is already in a call with the friend.')
|
||||||
elif toxav_err_answer == TOXAV_ERR_ANSWER['INVALID_BIT_RATE']:
|
if toxav_err_answer == enum.TOXAV_ERR_ANSWER['INVALID_BIT_RATE']:
|
||||||
raise ArgumentError('Audio or video bit rate is invalid.')
|
raise ArgumentError('Audio or video bit rate is invalid.')
|
||||||
|
raise ToxError('The function did not return OK')
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Call state graph
|
# Call state graph
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def callback_call_state(self, callback, user_data):
|
def callback_call_state(self, callback: Union[Callable,None], user_data) -> None:
|
||||||
"""
|
"""
|
||||||
Set the callback for the `call_state` event. Pass None to unset.
|
Set the callback for the `call_state` event. Pass None to unset.
|
||||||
|
|
||||||
@ -208,11 +210,9 @@ class ToxAV:
|
|||||||
self.call_state_cb = c_callback(callback)
|
self.call_state_cb = c_callback(callback)
|
||||||
self.libtoxav.toxav_callback_call_state(self._toxav_pointer, self.call_state_cb, user_data)
|
self.libtoxav.toxav_callback_call_state(self._toxav_pointer, self.call_state_cb, user_data)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# Call control
|
# Call control
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def call_control(self, friend_number, control):
|
def call_control(self, friend_number: int, control: int) -> bool:
|
||||||
"""
|
"""
|
||||||
Sends a call control command to a friend.
|
Sends a call control command to a friend.
|
||||||
|
|
||||||
@ -222,31 +222,30 @@ class ToxAV:
|
|||||||
"""
|
"""
|
||||||
toxav_err_call_control = c_int()
|
toxav_err_call_control = c_int()
|
||||||
LOG_DEBUG(f"call_control")
|
LOG_DEBUG(f"call_control")
|
||||||
result = self.libtoxav.toxav_call_control(self._toxav_pointer, c_uint32(friend_number), c_int(control),
|
result = self.libtoxav.toxav_call_control(self._toxav_pointer,
|
||||||
|
c_uint32(friend_number),
|
||||||
|
c_int(control),
|
||||||
byref(toxav_err_call_control))
|
byref(toxav_err_call_control))
|
||||||
toxav_err_call_control = toxav_err_call_control.value
|
toxav_err_call_control = toxav_err_call_control.value
|
||||||
if toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['OK']:
|
if toxav_err_call_control == enum.TOXAV_ERR_CALL_CONTROL['OK']:
|
||||||
return bool(result)
|
return True
|
||||||
elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['SYNC']:
|
if toxav_err_call_control == enum.TOXAV_ERR_CALL_CONTROL['SYNC']:
|
||||||
raise RuntimeError('Synchronization error occurred.')
|
raise ToxError('Synchronization error occurred.')
|
||||||
elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_FOUND']:
|
if toxav_err_call_control == enum.TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_FOUND']:
|
||||||
raise ArgumentError('The friend_number passed did not designate a valid friend.')
|
raise ArgumentError('The friend_number passed did not designate a valid friend.')
|
||||||
elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_IN_CALL']:
|
if toxav_err_call_control == enum.TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_IN_CALL']:
|
||||||
raise RuntimeError('This client is currently not in a call with the friend. Before the call is answered, '
|
raise ToxError('This client is currently not in a call with the friend. Before the call is answered, '
|
||||||
'only CANCEL is a valid control.')
|
'only CANCEL is a valid control.')
|
||||||
elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['INVALID_TRANSITION']:
|
if toxav_err_call_control == enum.TOXAV_ERR_CALL_CONTROL['INVALID_TRANSITION']:
|
||||||
raise RuntimeError('Happens if user tried to pause an already paused call or if trying to resume a call '
|
raise ToxError('Happens if user tried to pause an already paused call or if trying to resume a call '
|
||||||
'that is not paused.')
|
'that is not paused.')
|
||||||
|
raise ToxError('The function did not return OK.')
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# TODO Controlling bit rates
|
# TODO Controlling bit rates
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# A/V sending
|
# A/V sending
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def audio_send_frame(self, friend_number, pcm, sample_count, channels, sampling_rate):
|
def audio_send_frame(self, friend_number: int, pcm, sample_count: int, channels: int, sampling_rate: int) -> bool:
|
||||||
"""
|
"""
|
||||||
Send an audio frame to a friend.
|
Send an audio frame to a friend.
|
||||||
|
|
||||||
@ -264,7 +263,7 @@ class ToxAV:
|
|||||||
24000, or 48000.
|
24000, or 48000.
|
||||||
"""
|
"""
|
||||||
toxav_err_send_frame = c_int()
|
toxav_err_send_frame = c_int()
|
||||||
LOG_DEBUG(f"toxav_audio_send_frame")
|
LOG_TRACE(f"toxav_audio_send_frame")
|
||||||
assert sampling_rate in [8000, 12000, 16000, 24000, 48000]
|
assert sampling_rate in [8000, 12000, 16000, 24000, 48000]
|
||||||
result = self.libtoxav.toxav_audio_send_frame(self._toxav_pointer,
|
result = self.libtoxav.toxav_audio_send_frame(self._toxav_pointer,
|
||||||
c_uint32(friend_number),
|
c_uint32(friend_number),
|
||||||
@ -272,26 +271,27 @@ class ToxAV:
|
|||||||
c_size_t(sample_count), c_uint8(channels),
|
c_size_t(sample_count), c_uint8(channels),
|
||||||
c_uint32(sampling_rate), byref(toxav_err_send_frame))
|
c_uint32(sampling_rate), byref(toxav_err_send_frame))
|
||||||
toxav_err_send_frame = toxav_err_send_frame.value
|
toxav_err_send_frame = toxav_err_send_frame.value
|
||||||
if toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['OK']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['OK']:
|
||||||
return bool(result)
|
return bool(result)
|
||||||
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['NULL']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['NULL']:
|
||||||
raise ArgumentError('The samples data pointer was NULL.')
|
raise ArgumentError('The samples data pointer was NULL.')
|
||||||
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']:
|
||||||
raise ArgumentError('The friend_number passed did not designate a valid friend.')
|
raise ArgumentError('The friend_number passed did not designate a valid friend.')
|
||||||
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']:
|
||||||
raise RuntimeError('This client is currently not in a call with the friend.')
|
raise ToxError('This client is currently not in a call with the friend.')
|
||||||
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['SYNC']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['SYNC']:
|
||||||
raise RuntimeError('Synchronization error occurred.')
|
raise ToxError('Synchronization error occurred.')
|
||||||
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['INVALID']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['INVALID']:
|
||||||
raise ArgumentError('One of the frame parameters was invalid. E.g. the resolution may be too small or too '
|
raise ArgumentError('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.')
|
'large, or the audio sampling rate may be unsupported.')
|
||||||
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']:
|
||||||
raise RuntimeError('Either friend turned off audio or video receiving or we turned off sending for the said'
|
raise ToxError('Either friend turned off audio or video receiving or we turned off sending for the said'
|
||||||
'payload.')
|
'payload.')
|
||||||
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['RTP_FAILED']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['RTP_FAILED']:
|
||||||
RuntimeError('Failed to push frame through rtp interface.')
|
ToxError('Failed to push frame through rtp interface.')
|
||||||
|
raise ToxError('The function did not return OK.')
|
||||||
|
|
||||||
def video_send_frame(self, friend_number, width, height, y, u, v):
|
def video_send_frame(self, friend_number: int, width: int, height: int, y, u, v) -> bool:
|
||||||
"""
|
"""
|
||||||
Send a video frame to a friend.
|
Send a video frame to a friend.
|
||||||
|
|
||||||
@ -307,35 +307,39 @@ class ToxAV:
|
|||||||
:param v: V (Chroma) plane data.
|
:param v: V (Chroma) plane data.
|
||||||
"""
|
"""
|
||||||
toxav_err_send_frame = c_int()
|
toxav_err_send_frame = c_int()
|
||||||
LOG_DEBUG(f"toxav_video_send_frame")
|
LOG_TRACE(f"toxav_video_send_frame")
|
||||||
result = self.libtoxav.toxav_video_send_frame(self._toxav_pointer, c_uint32(friend_number), c_uint16(width),
|
result = self.libtoxav.toxav_video_send_frame(self._toxav_pointer,
|
||||||
c_uint16(height), c_char_p(y), c_char_p(u), c_char_p(v),
|
c_uint32(friend_number),
|
||||||
|
c_uint16(width),
|
||||||
|
c_uint16(height),
|
||||||
|
c_char_p(y),
|
||||||
|
c_char_p(u),
|
||||||
|
c_char_p(v),
|
||||||
byref(toxav_err_send_frame))
|
byref(toxav_err_send_frame))
|
||||||
toxav_err_send_frame = toxav_err_send_frame.value
|
toxav_err_send_frame = toxav_err_send_frame.value
|
||||||
if toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['OK']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['OK']:
|
||||||
return bool(result)
|
return bool(result)
|
||||||
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['NULL']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['NULL']:
|
||||||
raise ArgumentError('One of Y, U, or V was NULL.')
|
raise ArgumentError('One of Y, U, or V was NULL.')
|
||||||
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']:
|
||||||
raise ArgumentError('The friend_number passed did not designate a valid friend.')
|
raise ArgumentError('The friend_number passed did not designate a valid friend.')
|
||||||
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']:
|
||||||
raise RuntimeError('This client is currently not in a call with the friend.')
|
raise ToxError('This client is currently not in a call with the friend.')
|
||||||
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['SYNC']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['SYNC']:
|
||||||
raise RuntimeError('Synchronization error occurred.')
|
raise ToxError('Synchronization error occurred.')
|
||||||
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['INVALID']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['INVALID']:
|
||||||
raise ArgumentError('One of the frame parameters was invalid. E.g. the resolution may be too small or too '
|
raise ArgumentError('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.')
|
'large, or the audio sampling rate may be unsupported.')
|
||||||
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']:
|
||||||
raise RuntimeError('Either friend turned off audio or video receiving or we turned off sending for the said'
|
raise ToxError('Either friend turned off audio or video receiving or we turned off sending for the said'
|
||||||
'payload.')
|
'payload.')
|
||||||
elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['RTP_FAILED']:
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['RTP_FAILED']:
|
||||||
RuntimeError('Failed to push frame through rtp interface.')
|
ToxError('Failed to push frame through rtp interface.')
|
||||||
|
raise ToxError('The function did not return OK.')
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
# A/V receiving
|
# A/V receiving
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def callback_audio_receive_frame(self, callback, user_data):
|
def callback_audio_receive_frame(self, callback: Union[Callable,None], user_data) -> None:
|
||||||
"""
|
"""
|
||||||
Set the callback for the `audio_receive_frame` event. Pass None to unset.
|
Set the callback for the `audio_receive_frame` event. Pass None to unset.
|
||||||
|
|
||||||
@ -354,7 +358,9 @@ class ToxAV:
|
|||||||
:param user_data: pointer (c_void_p) to user data
|
:param user_data: pointer (c_void_p) to user data
|
||||||
"""
|
"""
|
||||||
if callback is None:
|
if callback is None:
|
||||||
self.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, POINTER(None)(), user_data)
|
self.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer,
|
||||||
|
POINTER(None)(),
|
||||||
|
user_data)
|
||||||
self.audio_receive_frame_cb = None
|
self.audio_receive_frame_cb = None
|
||||||
return
|
return
|
||||||
LOG_DEBUG(f"toxav_callback_audio_receive_frame")
|
LOG_DEBUG(f"toxav_callback_audio_receive_frame")
|
||||||
@ -362,7 +368,7 @@ class ToxAV:
|
|||||||
self.audio_receive_frame_cb = c_callback(callback)
|
self.audio_receive_frame_cb = c_callback(callback)
|
||||||
self.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, self.audio_receive_frame_cb, user_data)
|
self.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, self.audio_receive_frame_cb, user_data)
|
||||||
|
|
||||||
def callback_video_receive_frame(self, callback, user_data):
|
def callback_video_receive_frame(self, callback: Union[Callable,None], user_data) -> None:
|
||||||
"""
|
"""
|
||||||
Set the callback for the `video_receive_frame` event. Pass None to unset.
|
Set the callback for the `video_receive_frame` event. Pass None to unset.
|
||||||
|
|
@ -59,8 +59,15 @@ TOX_ERR_SET_INFO = {
|
|||||||
'OK': 0,
|
'OK': 0,
|
||||||
'NULL': 1,
|
'NULL': 1,
|
||||||
'TOO_LONG': 2,
|
'TOO_LONG': 2,
|
||||||
|
# The function returned successfully.
|
||||||
|
'TOX_ERR_SET_INFO_OK': 0,
|
||||||
|
# One of the arguments to the function was NULL when it was not expected.
|
||||||
|
'TOX_ERR_SET_INFO_NULL': 1,
|
||||||
|
# Information length exceeded maximum permissible size.
|
||||||
|
'TOX_ERR_SET_INFO_TOO_LONG': 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TOX_ERR_FRIEND_ADD = {
|
TOX_ERR_FRIEND_ADD = {
|
||||||
'OK': 0,
|
'OK': 0,
|
||||||
'NULL': 1,
|
'NULL': 1,
|
||||||
@ -302,6 +309,11 @@ TOX_ERR_GROUP_JOIN = {
|
|||||||
'TOX_ERR_GROUP_JOIN_TOO_LONG': 3,
|
'TOX_ERR_GROUP_JOIN_TOO_LONG': 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_IS_CONNECTED = {
|
||||||
|
'TOX_ERR_GROUP_IS_CONNECTED_OK': 0,
|
||||||
|
'TOX_ERR_GROUP_IS_CONNECTED_GROUP_NOT_FOUND': 1
|
||||||
|
}
|
||||||
|
|
||||||
TOX_ERR_GROUP_RECONNECT = {
|
TOX_ERR_GROUP_RECONNECT = {
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -315,6 +327,19 @@ TOX_ERR_GROUP_RECONNECT = {
|
|||||||
'TOX_ERR_GROUP_RECONNECT_GROUP_NOT_FOUND': 1,
|
'TOX_ERR_GROUP_RECONNECT_GROUP_NOT_FOUND': 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_DISCONNECT = {
|
||||||
|
|
||||||
|
# The function returned successfully.
|
||||||
|
'TOX_ERR_GROUP_DISCONNECT_OK': 0,
|
||||||
|
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
'TOX_ERR_GROUP_DISCONNECT_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
# The group is already disconnected.
|
||||||
|
'TOX_ERR_GROUP_DISCONNECT_ALREADY_DISCONNECTED': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
TOX_ERR_GROUP_LEAVE = {
|
TOX_ERR_GROUP_LEAVE = {
|
||||||
|
|
||||||
#
|
#
|
@ -1,75 +1,91 @@
|
|||||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
from ctypes import c_size_t, create_string_buffer, byref, c_int, ArgumentError, c_char_p, c_bool
|
|
||||||
|
|
||||||
from wrapper import libtox
|
try:
|
||||||
from wrapper.toxencryptsave_enums_and_consts import *
|
from toxygen_wrapper import libtox
|
||||||
|
import toxygen_wrapper.toxencryptsave_enums_and_consts as enum
|
||||||
|
except:
|
||||||
|
import libtox
|
||||||
|
import toxencryptsave_enums_and_consts as enum
|
||||||
|
|
||||||
|
from typing import Union, Callable
|
||||||
|
from ctypes import (ArgumentError, byref, c_bool, c_char_p, c_int, c_size_t,
|
||||||
|
create_string_buffer, Array)
|
||||||
|
def ToxError(ArgumentError): pass
|
||||||
|
|
||||||
class ToxEncryptSave:
|
class ToxEncryptSave:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.libtoxencryptsave = libtox.LibToxEncryptSave()
|
self.libtoxencryptsave = libtox.LibToxEncryptSave()
|
||||||
|
|
||||||
def is_data_encrypted(self, data):
|
def is_data_encrypted(self, data: bytes) -> bool:
|
||||||
"""
|
"""
|
||||||
Checks if given data is encrypted
|
Checks if given data is encrypted
|
||||||
"""
|
"""
|
||||||
func = self.libtoxencryptsave.tox_is_data_encrypted
|
func = self.libtoxencryptsave.tox_is_data_encrypted
|
||||||
func.restype = c_bool
|
func.restype = c_bool
|
||||||
result = func(c_char_p(bytes(data)))
|
result = func(c_char_p(bytes(data)))
|
||||||
return result
|
return bool(result)
|
||||||
|
|
||||||
def pass_encrypt(self, data, password):
|
def pass_encrypt(self, data: bytes, password: Union[str,bytes]) -> bytes:
|
||||||
"""
|
"""
|
||||||
Encrypts the given data with the given password.
|
Encrypts the given data with the given password.
|
||||||
|
|
||||||
:return: output array
|
:return: output array
|
||||||
"""
|
"""
|
||||||
out = create_string_buffer(len(data) + TOX_PASS_ENCRYPTION_EXTRA_LENGTH)
|
out = create_string_buffer(len(data) + enum.TOX_PASS_ENCRYPTION_EXTRA_LENGTH)
|
||||||
tox_err_encryption = c_int()
|
tox_err_encryption = c_int()
|
||||||
|
assert password
|
||||||
|
if type(password) != bytes:
|
||||||
|
password = bytes(password, 'utf-8')
|
||||||
self.libtoxencryptsave.tox_pass_encrypt(c_char_p(data),
|
self.libtoxencryptsave.tox_pass_encrypt(c_char_p(data),
|
||||||
c_size_t(len(data)),
|
c_size_t(len(data)),
|
||||||
c_char_p(bytes(password, 'utf-8')),
|
c_char_p(password),
|
||||||
c_size_t(len(password)),
|
c_size_t(len(password)),
|
||||||
out,
|
out,
|
||||||
byref(tox_err_encryption))
|
byref(tox_err_encryption))
|
||||||
tox_err_encryption = tox_err_encryption.value
|
tox_err_encryption = tox_err_encryption.value
|
||||||
if tox_err_encryption == TOX_ERR_ENCRYPTION['OK']:
|
if tox_err_encryption == enum.TOX_ERR_ENCRYPTION['OK']:
|
||||||
return out[:]
|
return bytes(out[:])
|
||||||
elif tox_err_encryption == TOX_ERR_ENCRYPTION['NULL']:
|
if tox_err_encryption == enum.TOX_ERR_ENCRYPTION['NULL']:
|
||||||
raise ArgumentError('Some input data, or maybe the output pointer, was null.')
|
raise ArgumentError('Some input data, or maybe the output pointer, was null.')
|
||||||
elif tox_err_encryption == TOX_ERR_ENCRYPTION['KEY_DERIVATION_FAILED']:
|
if tox_err_encryption == enum.TOX_ERR_ENCRYPTION['KEY_DERIVATION_FAILED']:
|
||||||
raise RuntimeError('The crypto lib was unable to derive a key from the given passphrase, which is usually a'
|
raise RuntimeError('The crypto lib was unable to derive a key from the given passphrase, which is usually a'
|
||||||
' lack of memory issue. The functions accepting keys do not produce this error.')
|
' lack of memory issue. The functions accepting keys do not produce this error.')
|
||||||
elif tox_err_encryption == TOX_ERR_ENCRYPTION['FAILED']:
|
if tox_err_encryption == enum.TOX_ERR_ENCRYPTION['FAILED']:
|
||||||
raise RuntimeError('The encryption itself failed.')
|
raise RuntimeError('The encryption itself failed.')
|
||||||
|
raise ToxError('The function did not return OK.')
|
||||||
|
|
||||||
def pass_decrypt(self, data, password):
|
def pass_decrypt(self, data: bytes, password: Union[str,bytes]) -> bytes:
|
||||||
"""
|
"""
|
||||||
Decrypts the given data with the given password.
|
Decrypts the given data with the given password.
|
||||||
|
|
||||||
:return: output array
|
:return: output array
|
||||||
"""
|
"""
|
||||||
out = create_string_buffer(len(data) - TOX_PASS_ENCRYPTION_EXTRA_LENGTH)
|
out = create_string_buffer(len(data) - enum.TOX_PASS_ENCRYPTION_EXTRA_LENGTH)
|
||||||
tox_err_decryption = c_int()
|
tox_err_decryption = c_int()
|
||||||
|
assert password
|
||||||
|
if type(password) != bytes:
|
||||||
|
password = bytes(password, 'utf-8')
|
||||||
self.libtoxencryptsave.tox_pass_decrypt(c_char_p(bytes(data)),
|
self.libtoxencryptsave.tox_pass_decrypt(c_char_p(bytes(data)),
|
||||||
c_size_t(len(data)),
|
c_size_t(len(data)),
|
||||||
c_char_p(bytes(password, 'utf-8')),
|
c_char_p(password),
|
||||||
c_size_t(len(password)),
|
c_size_t(len(password)),
|
||||||
out,
|
out,
|
||||||
byref(tox_err_decryption))
|
byref(tox_err_decryption))
|
||||||
tox_err_decryption = tox_err_decryption.value
|
tox_err_decryption = tox_err_decryption.value
|
||||||
if tox_err_decryption == TOX_ERR_DECRYPTION['OK']:
|
if tox_err_decryption == enum.TOX_ERR_DECRYPTION['OK']:
|
||||||
return out[:]
|
return bytes(out[:])
|
||||||
elif tox_err_decryption == TOX_ERR_DECRYPTION['NULL']:
|
if tox_err_decryption == enum.TOX_ERR_DECRYPTION['NULL']:
|
||||||
raise ArgumentError('Some input data, or maybe the output pointer, was null.')
|
raise ArgumentError('Some input data, or maybe the output pointer, was null.')
|
||||||
elif tox_err_decryption == TOX_ERR_DECRYPTION['INVALID_LENGTH']:
|
if tox_err_decryption == enum.TOX_ERR_DECRYPTION['INVALID_LENGTH']:
|
||||||
raise ArgumentError('The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes')
|
raise ArgumentError('The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes')
|
||||||
elif tox_err_decryption == TOX_ERR_DECRYPTION['BAD_FORMAT']:
|
if tox_err_decryption == enum.TOX_ERR_DECRYPTION['BAD_FORMAT']:
|
||||||
raise ArgumentError('The input data is missing the magic number (i.e. wasn\'t created by this module, or is'
|
raise ArgumentError('The input data is missing the magic number (i.e. wasn\'t created by this module, or is'
|
||||||
' corrupted)')
|
' corrupted)')
|
||||||
elif tox_err_decryption == TOX_ERR_DECRYPTION['KEY_DERIVATION_FAILED']:
|
if tox_err_decryption == enum.TOX_ERR_DECRYPTION['KEY_DERIVATION_FAILED']:
|
||||||
raise RuntimeError('The crypto lib was unable to derive a key from the given passphrase, which is usually a'
|
raise RuntimeError('The crypto lib was unable to derive a key from the given passphrase, which is usually a'
|
||||||
' lack of memory issue. The functions accepting keys do not produce this error.')
|
' lack of memory issue. The functions accepting keys do not produce this error.')
|
||||||
elif tox_err_decryption == TOX_ERR_DECRYPTION['FAILED']:
|
if tox_err_decryption == enum.TOX_ERR_DECRYPTION['FAILED']:
|
||||||
raise RuntimeError('The encrypted byte array could not be decrypted. Either the data was corrupt or the '
|
raise RuntimeError('The encrypted byte array could not be decrypted. Either the data was corrupt or the '
|
||||||
'password/key was incorrect.')
|
'password/key was incorrect.')
|
||||||
|
raise ToxError('The function did not return OK.')
|
455
src/toxygen_wrapper/toxygen_echo.py
Normal file
455
src/toxygen_wrapper/toxygen_echo.py
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
#!/var/local/bin/python3.bash
|
||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
# A work in progress - chat works, but I don't think AV does.
|
||||||
|
|
||||||
|
""" echo.py a basic Tox echo service. Features:
|
||||||
|
- accept friend request
|
||||||
|
- echo back friend message
|
||||||
|
# - accept and answer friend call request
|
||||||
|
# - send back friend audio/video data
|
||||||
|
# - send back files friend sent
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
import threading
|
||||||
|
import random
|
||||||
|
from ctypes import *
|
||||||
|
import time
|
||||||
|
from typing import Union, Callable
|
||||||
|
|
||||||
|
# LOG=util.log
|
||||||
|
global LOG
|
||||||
|
import logging
|
||||||
|
# log = lambda x: LOG.info(x)
|
||||||
|
LOG = logging.getLogger('app')
|
||||||
|
def LOG_error(a): print('EROR_ '+a)
|
||||||
|
def LOG_warn(a): print('WARN_ '+a)
|
||||||
|
def LOG_info(a): print('INFO_ '+a)
|
||||||
|
def LOG_debug(a): print('DBUG_ '+a)
|
||||||
|
def LOG_trace(a): pass # print('TRAC_ '+a)
|
||||||
|
|
||||||
|
from toxygen_wrapper import tox
|
||||||
|
import toxygen_wrapper.toxcore_enums_and_consts as enums
|
||||||
|
from toxygen_wrapper.tox import Tox, UINT32_MAX
|
||||||
|
from toxygen_wrapper.toxcore_enums_and_consts import TOX_CONNECTION, TOX_USER_STATUS, \
|
||||||
|
TOX_MESSAGE_TYPE, TOX_PUBLIC_KEY_SIZE, TOX_FILE_CONTROL, TOX_FILE_KIND
|
||||||
|
|
||||||
|
import toxygen_wrapper.tests.support_testing as ts
|
||||||
|
from toxygen_wrapper.tests.support_testing import oMainArgparser
|
||||||
|
|
||||||
|
def sleep(fSec) -> None:
|
||||||
|
if 'QtCore' in globals():
|
||||||
|
if fSec > .000001: QtCore.QThread.msleep(fSec)
|
||||||
|
QtCore.QCoreApplication.processEvents()
|
||||||
|
else:
|
||||||
|
time.sleep(fSec)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import coloredlogs
|
||||||
|
if 'COLOREDLOGS_LEVEL_STYLES' not in os.environ:
|
||||||
|
os.environ['COLOREDLOGS_LEVEL_STYLES'] = 'spam=22;debug=28;verbose=34;notice=220;warning=202;success=118,bold;error=124;critical=background=red'
|
||||||
|
except ImportError as e:
|
||||||
|
# logging.log(logging.DEBUG, f"coloredlogs not available: {e}")
|
||||||
|
coloredlogs = None
|
||||||
|
|
||||||
|
if 'USER' in os.environ:
|
||||||
|
sDATA_FILE = '/tmp/logging_toxygen_' +os.environ['USER'] +'.tox'
|
||||||
|
elif 'USERNAME' in os.environ:
|
||||||
|
sDATA_FILE = '/tmp/logging_toxygen_' +os.environ['USERNAME'] +'.tox'
|
||||||
|
else:
|
||||||
|
sDATA_FILE = '/tmp/logging_toxygen_' +'data' +'.tox'
|
||||||
|
|
||||||
|
bHAVE_AV = False
|
||||||
|
iDHT_TRIES = 100
|
||||||
|
iDHT_TRY = 0
|
||||||
|
|
||||||
|
#?SERVER = lLOCAL[-1]
|
||||||
|
|
||||||
|
if not bHAVE_AV:
|
||||||
|
class AV(): pass
|
||||||
|
else:
|
||||||
|
class AV(tox.ToxAV):
|
||||||
|
def __init__(self, core):
|
||||||
|
super(AV, self).__init__(core)
|
||||||
|
self.core = self.get_tox()
|
||||||
|
|
||||||
|
def on_call(self, fid:int, audio_enabled:bool, video_enabled:bool) -> None:
|
||||||
|
LOG.info("Incoming %s call from %d:%s ..." % (
|
||||||
|
"video" if video_enabled else "audio",
|
||||||
|
fid,
|
||||||
|
self.core.friend_get_name(fid)))
|
||||||
|
bret = self.answer(fid, 48, 64)
|
||||||
|
LOG.info(f"Answered, in call... {bret}")
|
||||||
|
|
||||||
|
def on_call_state(self, fid:int, state:int) -> None:
|
||||||
|
LOG.info('call state:fn=%d, state=%d' % (fid, state))
|
||||||
|
|
||||||
|
def on_audio_bit_rate(self, fid:int, audio_bit_rate:int) -> None:
|
||||||
|
LOG.info('audio bit rate status: fn=%d, abr=%d' %
|
||||||
|
(fid, audio_bit_rate))
|
||||||
|
|
||||||
|
def on_video_bit_rate(self, fid:int, video_bit_rate:int) -> None:
|
||||||
|
LOG.info('video bit rate status: fn=%d, vbr=%d' %
|
||||||
|
(fid, video_bit_rate))
|
||||||
|
|
||||||
|
def on_audio_receive_frame(self, fid:int,
|
||||||
|
pcm:int,
|
||||||
|
sample_count:int,
|
||||||
|
channels:int,
|
||||||
|
sampling_rate:int) -> None:
|
||||||
|
# LOG.info('audio frame: %d, %d, %d, %d' %
|
||||||
|
# (fid, sample_count, channels, sampling_rate))
|
||||||
|
# LOG.info('pcm len:%d, %s' % (len(pcm), str(type(pcm))))
|
||||||
|
sys.stdout.write('.')
|
||||||
|
sys.stdout.flush()
|
||||||
|
bret = self.audio_send_frame(fid, pcm, sample_count,
|
||||||
|
channels, sampling_rate)
|
||||||
|
if bret is False:
|
||||||
|
LOG.error('on_audio_receive_frame error.')
|
||||||
|
|
||||||
|
def on_video_receive_frame(self, fid:int, width:int, height:int, frame, u, v) -> None:
|
||||||
|
LOG.info('video frame: %d, %d, %d, ' % (fid, width, height))
|
||||||
|
sys.stdout.write('*')
|
||||||
|
sys.stdout.flush()
|
||||||
|
bret = self.video_send_frame(fid, width, height, frame, u, v)
|
||||||
|
if bret is False:
|
||||||
|
LOG.error('on_video_receive_frame error.')
|
||||||
|
|
||||||
|
def witerate(self) -> None:
|
||||||
|
self.iterate()
|
||||||
|
|
||||||
|
|
||||||
|
def save_to_file(tox, fname: str) -> None:
|
||||||
|
data = tox.get_savedata()
|
||||||
|
with open(fname, 'wb') as f:
|
||||||
|
f.write(data)
|
||||||
|
|
||||||
|
def load_from_file(fname: str) -> bytes:
|
||||||
|
assert os.path.exists(fname)
|
||||||
|
return open(fname, 'rb').read()
|
||||||
|
|
||||||
|
class EchoBot():
|
||||||
|
def __init__(self, oTox):
|
||||||
|
self._tox = oTox
|
||||||
|
self._tox.self_set_name("PyEchoBot")
|
||||||
|
LOG.info(f'ID: {self._tox.self_get_address()}')
|
||||||
|
|
||||||
|
self.files = {}
|
||||||
|
self.av = None
|
||||||
|
self.on_connection_status = None
|
||||||
|
|
||||||
|
def start(self) -> None:
|
||||||
|
self.connect()
|
||||||
|
if bHAVE_AV:
|
||||||
|
# RuntimeError: Attempted to create a second session for the same Tox instance.
|
||||||
|
|
||||||
|
self.av = True # AV(self._tox_pointer)
|
||||||
|
def bobs_on_friend_request(iTox,
|
||||||
|
public_key,
|
||||||
|
message_data,
|
||||||
|
message_data_size,
|
||||||
|
*largs) -> None:
|
||||||
|
key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
|
||||||
|
sPk = tox.bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
|
||||||
|
sMd = str(message_data, 'UTF-8')
|
||||||
|
LOG.debug('on_friend_request ' +sPk +' ' +sMd)
|
||||||
|
self.on_friend_request(sPk, sMd)
|
||||||
|
LOG.info('setting bobs_on_friend_request')
|
||||||
|
self._tox.callback_friend_request(bobs_on_friend_request)
|
||||||
|
|
||||||
|
def bobs_on_friend_message(iTox,
|
||||||
|
iFriendNum,
|
||||||
|
iMessageType,
|
||||||
|
message_data,
|
||||||
|
message_data_size,
|
||||||
|
*largs) -> None:
|
||||||
|
sMd = str(message_data, 'UTF-8')
|
||||||
|
LOG_debug(f"on_friend_message {iFriendNum}" +' ' +sMd)
|
||||||
|
self.on_friend_message(iFriendNum, iMessageType, sMd)
|
||||||
|
LOG.info('setting bobs_on_friend_message')
|
||||||
|
self._tox.callback_friend_message(bobs_on_friend_message)
|
||||||
|
|
||||||
|
def bobs_on_file_chunk_request(iTox, fid, filenumber, position, length, *largs) -> None:
|
||||||
|
if length == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
data = self.files[(fid, filenumber)]['f'][position:(position + length)]
|
||||||
|
self._tox.file_send_chunk(fid, filenumber, position, data)
|
||||||
|
self._tox.callback_file_chunk_request(bobs_on_file_chunk_request)
|
||||||
|
|
||||||
|
def bobs_on_file_recv(iTox, fid, filenumber, kind, size, filename, *largs):
|
||||||
|
LOG_info(f"on_file_recv {fid} {filenumber} {kind} {size} {filename}")
|
||||||
|
if size == 0:
|
||||||
|
return
|
||||||
|
self.files[(fid, filenumber)] = {
|
||||||
|
'f': bytes(),
|
||||||
|
'filename': filename,
|
||||||
|
'size': size
|
||||||
|
}
|
||||||
|
self._tox.file_control(fid, filenumber, TOX_FILE_CONTROL['RESUME'])
|
||||||
|
|
||||||
|
|
||||||
|
def connect(self) -> None:
|
||||||
|
if not self.on_connection_status:
|
||||||
|
def on_connection_status(iTox, iCon, *largs) -> None:
|
||||||
|
LOG_info('ON_CONNECTION_STATUS - CONNECTED ' + repr(iCon))
|
||||||
|
self._tox.callback_self_connection_status(on_connection_status)
|
||||||
|
LOG.info('setting on_connection_status callback ')
|
||||||
|
self.on_connection_status = on_connection_status
|
||||||
|
if self._oargs.network in ['newlocal', 'local']:
|
||||||
|
LOG.info('connecting on the new network ')
|
||||||
|
sNet = 'newlocal'
|
||||||
|
elif self._oargs.network == 'new':
|
||||||
|
LOG.info('connecting on the new network ')
|
||||||
|
sNet = 'new'
|
||||||
|
else: # main old
|
||||||
|
LOG.info('connecting on the old network ')
|
||||||
|
sNet = 'old'
|
||||||
|
sFile = self._oargs.nodes_json
|
||||||
|
lNodes = ts.generate_nodes_from_file(sFile)
|
||||||
|
lElts = lNodes
|
||||||
|
random.shuffle(lElts)
|
||||||
|
for lElt in lElts[:10]:
|
||||||
|
status = self._tox.self_get_connection_status()
|
||||||
|
try:
|
||||||
|
if self._tox.bootstrap(*lElt):
|
||||||
|
LOG.info('connected to ' + lElt[0]+' '+repr(status))
|
||||||
|
else:
|
||||||
|
LOG.warn('failed connecting to ' + lElt[0])
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warn('error connecting to ' + lElt[0])
|
||||||
|
|
||||||
|
if self._oargs.proxy_type > 0:
|
||||||
|
random.shuffle(lElts)
|
||||||
|
for lElt in lElts[:10]:
|
||||||
|
status = self._tox.self_get_connection_status()
|
||||||
|
try:
|
||||||
|
if self._tox.add_tcp_relay(*lElt):
|
||||||
|
LOG.info('relayed to ' + lElt[0] +' '+repr(status))
|
||||||
|
else:
|
||||||
|
LOG.warn('failed relay to ' + lElt[0])
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warn('error relay to ' + lElt[0])
|
||||||
|
|
||||||
|
def loop(self) -> None:
|
||||||
|
if not self.av:
|
||||||
|
self.start()
|
||||||
|
checked = False
|
||||||
|
save_to_file(self._tox, sDATA_FILE)
|
||||||
|
|
||||||
|
LOG.info('Starting loop.')
|
||||||
|
while True:
|
||||||
|
|
||||||
|
status = self._tox.self_get_connection_status()
|
||||||
|
if not checked and status:
|
||||||
|
LOG.info('Connected to DHT.')
|
||||||
|
checked = True
|
||||||
|
if not checked and not status:
|
||||||
|
global iDHT_TRY
|
||||||
|
iDHT_TRY += 10
|
||||||
|
self.connect()
|
||||||
|
self.iterate(100)
|
||||||
|
if iDHT_TRY >= iDHT_TRIES:
|
||||||
|
raise RuntimeError("Failed to connect to the DHT.")
|
||||||
|
LOG.warn(f"NOT Connected to DHT. {iDHT_TRY}")
|
||||||
|
checked = True
|
||||||
|
if checked and not status:
|
||||||
|
LOG.info('Disconnected from DHT.')
|
||||||
|
self.connect()
|
||||||
|
checked = False
|
||||||
|
|
||||||
|
if bHAVE_AV:
|
||||||
|
True # self.av.witerate()
|
||||||
|
self.iterate(100)
|
||||||
|
|
||||||
|
LOG.info('Ending loop.')
|
||||||
|
|
||||||
|
def iterate(self, n:int = 100) -> None:
|
||||||
|
interval = self._tox.iteration_interval()
|
||||||
|
for i in range(n):
|
||||||
|
self._tox.iterate()
|
||||||
|
sleep(interval / 1000.0)
|
||||||
|
self._tox.iterate()
|
||||||
|
|
||||||
|
def on_friend_request(self, pk: Union[bytes,str], message: Union[bytes,str]) -> None:
|
||||||
|
LOG.debug('Friend request from %s: %s' % (pk, message))
|
||||||
|
self._tox.friend_add_norequest(pk)
|
||||||
|
LOG.info('on_friend_request Accepted.')
|
||||||
|
save_to_file(self._tox, sDATA_FILE)
|
||||||
|
|
||||||
|
def on_friend_message(self, friendId: int, message_type: int, message: Union[bytes,str]) -> None:
|
||||||
|
name = self._tox.friend_get_name(friendId)
|
||||||
|
LOG.debug(f"{name}, {message}, {message_type}")
|
||||||
|
yMessage = bytes(message, 'UTF-8')
|
||||||
|
self._tox.friend_send_message(friendId, TOX_MESSAGE_TYPE['NORMAL'], yMessage)
|
||||||
|
LOG.info('EchoBot sent: %s' % message)
|
||||||
|
|
||||||
|
def on_file_recv_chunk(self, fid: int, filenumber, position, data) -> None:
|
||||||
|
filename = self.files[(fid, filenumber)]['filename']
|
||||||
|
size = self.files[(fid, filenumber)]['size']
|
||||||
|
LOG.debug(f"on_file_recv_chunk {fid} {filenumber} {filename} {position/float(size)*100}")
|
||||||
|
|
||||||
|
if data is None:
|
||||||
|
msg = "I got '{}', sending it back right away!".format(filename)
|
||||||
|
self._tox.friend_send_message(fid, TOX_MESSAGE_TYPE['NORMAL'], msg)
|
||||||
|
|
||||||
|
self.files[(fid, 0)] = self.files[(fid, filenumber)]
|
||||||
|
|
||||||
|
length = self.files[(fid, filenumber)]['size']
|
||||||
|
self._tox.file_send(fid, TOX_FILE_KIND['DATA'], length, filename)
|
||||||
|
|
||||||
|
del self.files[(fid, filenumber)]
|
||||||
|
return
|
||||||
|
|
||||||
|
self.files[(fid, filenumber)]['f'] += data
|
||||||
|
|
||||||
|
class App():
|
||||||
|
def __init__(self):
|
||||||
|
self.mode = 0
|
||||||
|
oAPP = App()
|
||||||
|
|
||||||
|
class EchobotTox(Tox):
|
||||||
|
|
||||||
|
def __init__(self, opts, app=None):
|
||||||
|
|
||||||
|
super().__init__(opts, app=app)
|
||||||
|
self._address = self.self_get_address()
|
||||||
|
self.name = 'pyechobot'
|
||||||
|
self._opts = opts
|
||||||
|
self._app = app
|
||||||
|
|
||||||
|
class BaseThread(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self, name=None, target=None):
|
||||||
|
if name:
|
||||||
|
super().__init__(name=name, target=target)
|
||||||
|
else:
|
||||||
|
super().__init__(target=target)
|
||||||
|
self._stop_thread = False
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def stop_thread(self, timeout=-1) -> None:
|
||||||
|
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.warning(f"{self.name} BLOCKED")
|
||||||
|
|
||||||
|
class ToxIterateThread(BaseThread):
|
||||||
|
|
||||||
|
def __init__(self, tox):
|
||||||
|
super().__init__(name='ToxIterateThread')
|
||||||
|
self._tox = tox
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
while not self._stop_thread:
|
||||||
|
self._tox.iterate()
|
||||||
|
sleep(self._tox.iteration_interval() / 1000)
|
||||||
|
|
||||||
|
def oArgparse(lArgv):
|
||||||
|
parser = ts.oMainArgparser()
|
||||||
|
parser.add_argument('profile', type=str, nargs='?', default=None,
|
||||||
|
help='Path to Tox profile')
|
||||||
|
oArgs = parser.parse_args(lArgv)
|
||||||
|
ts.clean_booleans(oArgs)
|
||||||
|
|
||||||
|
if hasattr(oArgs, 'sleep'):
|
||||||
|
if oArgs.sleep == 'qt':
|
||||||
|
pass # broken or gevent.sleep(idle_period)
|
||||||
|
elif oArgs.sleep == 'gevent':
|
||||||
|
pass # broken or gevent.sleep(idle_period)
|
||||||
|
else:
|
||||||
|
oArgs.sleep = 'time'
|
||||||
|
|
||||||
|
return oArgs
|
||||||
|
|
||||||
|
def iMain(oArgs) -> int:
|
||||||
|
global sDATA_FILE
|
||||||
|
# oTOX_OPTIONS = ToxOptions()
|
||||||
|
global oTOX_OPTIONS
|
||||||
|
oMainArgparser
|
||||||
|
oTOX_OPTIONS = ts.oToxygenToxOptions(oArgs)
|
||||||
|
opts = oTOX_OPTIONS
|
||||||
|
if coloredlogs:
|
||||||
|
coloredlogs.install(
|
||||||
|
level=oArgs.loglevel,
|
||||||
|
logger=LOG,
|
||||||
|
# %(asctime)s,%(msecs)03d %(hostname)s [%(process)d]
|
||||||
|
fmt='%(name)s %(levelname)s %(message)s'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if 'logfile' in oArgs:
|
||||||
|
logging.basicConfig(filename=oArgs.logfile,
|
||||||
|
level=oArgs.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
else:
|
||||||
|
logging.basicConfig(level=oArgs.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
iRet = 0
|
||||||
|
if hasattr(oArgs,'profile') and oArgs.profile and os.path.isfile(oArgs.profile):
|
||||||
|
sDATA_FILE = oArgs.profile
|
||||||
|
LOG.info(f"loading from {sDATA_FILE}")
|
||||||
|
opts.savedata_data = load_from_file(sDATA_FILE)
|
||||||
|
opts.savedata_length = len(opts.savedata_data)
|
||||||
|
opts.savedata_type = enums.TOX_SAVEDATA_TYPE['TOX_SAVE']
|
||||||
|
else:
|
||||||
|
opts.savedata_data = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
oTox = EchobotTox(opts, app=oAPP)
|
||||||
|
t = EchoBot(oTox)
|
||||||
|
t._oargs = oArgs
|
||||||
|
t.start()
|
||||||
|
t.loop()
|
||||||
|
save_to_file(t._tox, sDATA_FILE)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
save_to_file(t._tox, sDATA_FILE)
|
||||||
|
except RuntimeError as e:
|
||||||
|
LOG.error(f"ERROR {e}")
|
||||||
|
iRet = 1
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(f"EXCEPTION {e}")
|
||||||
|
LOG.warn(' iMain(): ' \
|
||||||
|
+'\n' + traceback.format_exc())
|
||||||
|
iRet = 1
|
||||||
|
return iRet
|
||||||
|
|
||||||
|
def main(lArgs=None) -> int:
|
||||||
|
global oTOX_OARGS
|
||||||
|
global oTOX_OPTIONS
|
||||||
|
global bIS_LOCAL
|
||||||
|
if lArgs is None: lArgs = []
|
||||||
|
oArgs = oArgparse(lArgs)
|
||||||
|
bIS_LOCAL = oArgs.network in ['newlocal', 'localnew', 'local']
|
||||||
|
oTOX_OARGS = oArgs
|
||||||
|
setattr(oTOX_OARGS, 'bIS_LOCAL', bIS_LOCAL)
|
||||||
|
oTOX_OPTIONS = ts.oToxygenToxOptions(oArgs)
|
||||||
|
if coloredlogs:
|
||||||
|
# https://pypi.org/project/coloredlogs/
|
||||||
|
coloredlogs.install(level=oArgs.loglevel,
|
||||||
|
logger=LOG,
|
||||||
|
# %(asctime)s,%(msecs)03d %(hostname)s [%(process)d]
|
||||||
|
fmt='%(name)s %(levelname)s %(message)s'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logging.basicConfig(level=oArgs.loglevel) # logging.INFO
|
||||||
|
|
||||||
|
return iMain(oArgs)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
i = main(sys.argv[1:])
|
||||||
|
except KeyboardInterrupt as e:
|
||||||
|
i = 0
|
||||||
|
except Exception as e:
|
||||||
|
i = 1
|
||||||
|
sys.exit(i)
|
116
tox.c-toxcore.missing
Normal file
116
tox.c-toxcore.missing
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
tox_callback_conference_connected
|
||||||
|
tox_callback_conference_invite
|
||||||
|
tox_callback_conference_message
|
||||||
|
tox_callback_conference_peer_list_changed
|
||||||
|
tox_callback_conference_peer_name
|
||||||
|
tox_callback_conference_title
|
||||||
|
tox_callback_group_custom_private_packet
|
||||||
|
tox_conference_by_id
|
||||||
|
tox_conference_by_uid
|
||||||
|
tox_conference_delete
|
||||||
|
tox_conference_get_chatlist
|
||||||
|
tox_conference_get_chatlist_size
|
||||||
|
tox_conference_get_id
|
||||||
|
tox_conference_get_title
|
||||||
|
tox_conference_get_title_size
|
||||||
|
tox_conference_get_type
|
||||||
|
tox_conference_get_uid
|
||||||
|
tox_conference_id_size
|
||||||
|
tox_conference_invite
|
||||||
|
tox_conference_join
|
||||||
|
tox_conference_new
|
||||||
|
tox_conference_offline_peer_count
|
||||||
|
tox_conference_offline_peer_get_last_active
|
||||||
|
tox_conference_offline_peer_get_name
|
||||||
|
tox_conference_offline_peer_get_name_size
|
||||||
|
tox_conference_offline_peer_get_public_key
|
||||||
|
tox_conference_peer_count
|
||||||
|
tox_conference_peer_get_name
|
||||||
|
tox_conference_peer_get_name_size
|
||||||
|
tox_conference_peer_get_public_key
|
||||||
|
tox_conference_peer_number_is_ours
|
||||||
|
tox_conference_send_message
|
||||||
|
tox_conference_set_max_offline
|
||||||
|
tox_conference_set_title
|
||||||
|
tox_conference_uid_size
|
||||||
|
tox_file_seek
|
||||||
|
tox_get_salt
|
||||||
|
tox_group_send_custom_private_packet
|
||||||
|
tox_is_data_encrypted
|
||||||
|
tox_options_get_dht_announcements_enabled
|
||||||
|
tox_options_get_end_port
|
||||||
|
tox_options_get_experimental_groups_persistence
|
||||||
|
tox_options_get_experimental_thread_safety
|
||||||
|
tox_options_get_hole_punching_enabled
|
||||||
|
tox_options_get_ipv6_enabled
|
||||||
|
tox_options_get_local_discovery_enabled
|
||||||
|
tox_options_get_log_callback
|
||||||
|
tox_options_get_log_user_data
|
||||||
|
tox_options_get_operating_system
|
||||||
|
tox_options_get_proxy_host
|
||||||
|
tox_options_get_proxy_port
|
||||||
|
tox_options_get_proxy_type
|
||||||
|
tox_options_get_savedata_data
|
||||||
|
tox_options_get_savedata_length
|
||||||
|
tox_options_get_savedata_type
|
||||||
|
tox_options_get_start_port
|
||||||
|
tox_options_get_tcp_port
|
||||||
|
tox_options_get_udp_enabled
|
||||||
|
tox_options_set_dht_announcements_enabled
|
||||||
|
tox_options_set_end_port
|
||||||
|
tox_options_set_experimental_groups_persistence
|
||||||
|
tox_options_set_experimental_thread_safety
|
||||||
|
tox_options_set_hole_punching_enabled
|
||||||
|
tox_options_set_ipv6_enabled
|
||||||
|
tox_options_set_local_discovery_enabled
|
||||||
|
tox_options_set_log_callback
|
||||||
|
tox_options_set_log_user_data
|
||||||
|
tox_options_set_operating_system
|
||||||
|
tox_options_set_proxy_host
|
||||||
|
tox_options_set_proxy_port
|
||||||
|
tox_options_set_proxy_type
|
||||||
|
tox_options_set_savedata_data
|
||||||
|
tox_options_set_savedata_length
|
||||||
|
tox_options_set_savedata_type
|
||||||
|
tox_options_set_start_port
|
||||||
|
tox_options_set_tcp_port
|
||||||
|
tox_options_set_udp_enabled
|
||||||
|
tox_pass_decrypt
|
||||||
|
tox_pass_encrypt
|
||||||
|
tox_pass_encryption_extra_length
|
||||||
|
tox_pass_key_decrypt
|
||||||
|
tox_pass_key_derive
|
||||||
|
tox_pass_key_derive_with_salt
|
||||||
|
tox_pass_key_encrypt
|
||||||
|
tox_pass_key_free
|
||||||
|
tox_pass_key_length
|
||||||
|
tox_pass_salt_length
|
||||||
|
tox_version_is_compatible
|
||||||
|
toxav_add_av_groupchat
|
||||||
|
toxav_answer
|
||||||
|
toxav_audio_iterate
|
||||||
|
toxav_audio_iteration_interval
|
||||||
|
toxav_audio_send_frame
|
||||||
|
toxav_audio_set_bit_rate
|
||||||
|
toxav_call
|
||||||
|
toxav_call_control
|
||||||
|
toxav_callback_audio_bit_rate
|
||||||
|
toxav_callback_audio_receive_frame
|
||||||
|
toxav_callback_call
|
||||||
|
toxav_callback_call_state
|
||||||
|
toxav_callback_video_bit_rate
|
||||||
|
toxav_callback_video_receive_frame
|
||||||
|
toxav_get_tox
|
||||||
|
toxav_group_send_audio
|
||||||
|
toxav_groupchat_av_enabled
|
||||||
|
toxav_groupchat_disable_av
|
||||||
|
toxav_groupchat_enable_av
|
||||||
|
toxav_iterate
|
||||||
|
toxav_iteration_interval
|
||||||
|
toxav_join_av_groupchat
|
||||||
|
toxav_kill
|
||||||
|
toxav_new
|
||||||
|
toxav_video_iterate
|
||||||
|
toxav_video_iteration_interval
|
||||||
|
toxav_video_send_frame
|
||||||
|
toxav_video_set_bit_rate
|
Reference in New Issue
Block a user