Merge branch 'v0.6' into qt5-wip #2237

Open
kashikoibumi wants to merge 38 commits from kashikoibumi/qt5-wip into v0.6
381 changed files with 13916 additions and 2638 deletions
Showing only changes of commit aa9edcba2e - Show all commits

102
.buildbot/android/Dockerfile Executable file
View File

@ -0,0 +1,102 @@
# A container for buildbot
FROM ubuntu:focal AS android
ENV DEBIAN_FRONTEND=noninteractive
ENV ANDROID_HOME="/opt/android"
RUN apt-get update -qq > /dev/null \
&& apt-get -y install -qq --no-install-recommends locales \
&& locale-gen en_US.UTF-8
ENV LANG="en_US.UTF-8" \
LANGUAGE="en_US.UTF-8" \
LC_ALL="en_US.UTF-8"
# install system/build dependencies
RUN apt-get -y update -qq \
&& apt-get -y install -qq --no-install-recommends \
curl autoconf automake build-essential cmake git nano libtool \
libltdl-dev libffi-dev libssl-dev \
patch pkg-config python-is-python3 python3-dev python3-pip unzip zip
RUN apt-get -y install -qq --no-install-recommends openjdk-17-jdk \
&& apt-get -y autoremove
RUN pip install pip install buildozer cython virtualenv
ENV ANDROID_NDK_HOME="${ANDROID_HOME}/android-ndk"
ENV ANDROID_NDK_VERSION="25b"
ENV ANDROID_NDK_HOME_V="${ANDROID_NDK_HOME}-r${ANDROID_NDK_VERSION}"
# get the latest version from https://developer.android.com/ndk/downloads/index.html
ENV ANDROID_NDK_ARCHIVE="android-ndk-r${ANDROID_NDK_VERSION}-linux.zip"
ENV ANDROID_NDK_DL_URL="https://dl.google.com/android/repository/${ANDROID_NDK_ARCHIVE}"
# download and install Android NDK
RUN curl "${ANDROID_NDK_DL_URL}" --output "${ANDROID_NDK_ARCHIVE}" \
&& mkdir -p "${ANDROID_NDK_HOME_V}" \
&& unzip -q "${ANDROID_NDK_ARCHIVE}" -d "${ANDROID_HOME}" \
&& ln -sfn "${ANDROID_NDK_HOME_V}" "${ANDROID_NDK_HOME}" \
&& rm -rf "${ANDROID_NDK_ARCHIVE}"
ENV ANDROID_SDK_HOME="${ANDROID_HOME}/android-sdk"
# get the latest version from https://developer.android.com/studio/index.html
ENV ANDROID_SDK_TOOLS_VERSION="11076708"
ENV ANDROID_SDK_BUILD_TOOLS_VERSION="34.0.0"
ENV ANDROID_SDK_CMDLINE_TOOLS_VERSION="12.0"
ENV ANDROID_SDK_TOOLS_ARCHIVE="commandlinetools-linux-${ANDROID_SDK_TOOLS_VERSION}_latest.zip"
ENV ANDROID_SDK_TOOLS_DL_URL="https://dl.google.com/android/repository/${ANDROID_SDK_TOOLS_ARCHIVE}"
ENV ANDROID_CMDLINE_TOOLS_DIR="${ANDROID_SDK_HOME}/cmdline-tools/${ANDROID_SDK_CMDLINE_TOOLS_VERSION}"
ENV ANDROID_SDK_MANAGER="${ANDROID_CMDLINE_TOOLS_DIR}/bin/sdkmanager --sdk_root=${ANDROID_SDK_HOME}"
# download and install Android SDK
RUN curl "${ANDROID_SDK_TOOLS_DL_URL}" --output "${ANDROID_SDK_TOOLS_ARCHIVE}" \
&& mkdir -p "${ANDROID_SDK_HOME}/cmdline-tools" \
&& unzip -q "${ANDROID_SDK_TOOLS_ARCHIVE}" \
-d "${ANDROID_SDK_HOME}/cmdline-tools" \
&& mv "${ANDROID_SDK_HOME}/cmdline-tools/cmdline-tools" \
${ANDROID_CMDLINE_TOOLS_DIR} \
&& ln -sfn ${ANDROID_CMDLINE_TOOLS_DIR} "${ANDROID_SDK_HOME}/tools" \
&& rm -rf "${ANDROID_SDK_TOOLS_ARCHIVE}"
# update Android SDK, install Android API, Build Tools...
RUN mkdir -p "${ANDROID_SDK_HOME}/.android/" \
&& echo '### User Sources for Android SDK Manager' \
> "${ANDROID_SDK_HOME}/.android/repositories.cfg"
# accept Android licenses (JDK necessary!)
RUN yes | ${ANDROID_SDK_MANAGER} --licenses > /dev/null
# download platforms, API, build tools
RUN ${ANDROID_SDK_MANAGER} "platforms;android-30" > /dev/null \
&& ${ANDROID_SDK_MANAGER} "platforms;android-28" > /dev/null \
&& ${ANDROID_SDK_MANAGER} "platform-tools" > /dev/null \
&& ${ANDROID_SDK_MANAGER} "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" \
> /dev/null \
&& ${ANDROID_SDK_MANAGER} "extras;android;m2repository" > /dev/null \
&& chmod +x "${ANDROID_CMDLINE_TOOLS_DIR}/bin/avdmanager"
# download ANT
ENV APACHE_ANT_VERSION="1.9.4"
ENV APACHE_ANT_ARCHIVE="apache-ant-${APACHE_ANT_VERSION}-bin.tar.gz"
ENV APACHE_ANT_DL_URL="https://archive.apache.org/dist/ant/binaries/${APACHE_ANT_ARCHIVE}"
ENV APACHE_ANT_HOME="${ANDROID_HOME}/apache-ant"
ENV APACHE_ANT_HOME_V="${APACHE_ANT_HOME}-${APACHE_ANT_VERSION}"
RUN curl "${APACHE_ANT_DL_URL}" --output "${APACHE_ANT_ARCHIVE}" \
&& tar -xf "${APACHE_ANT_ARCHIVE}" -C "${ANDROID_HOME}" \
&& ln -sfn "${APACHE_ANT_HOME_V}" "${APACHE_ANT_HOME}" \
&& rm -rf "${APACHE_ANT_ARCHIVE}"
RUN useradd -m -U builder && mkdir /android
WORKDIR /android
RUN chown -R builder.builder /android "${ANDROID_SDK_HOME}" \
&& chmod -R go+w "${ANDROID_SDK_HOME}"
USER builder
ADD . .

9
.buildbot/android/build.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
pushd packages/android
buildozer android debug || exit $?
popd
mkdir -p ../out
cp packages/android/bin/*.apk ../out

6
.buildbot/android/test.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
unzip -p packages/android/bin/*.apk assets/private.tar \
| tar --list -z > package.list
cat package.list
cat package.list | grep '\.sql$' || exit 1

View File

@ -0,0 +1,26 @@
FROM ubuntu:bionic
RUN apt-get update
RUN apt-get install -yq --no-install-suggests --no-install-recommends \
ca-certificates software-properties-common \
build-essential libcap-dev libssl-dev \
python-all-dev python-setuptools wget \
git gtk-update-icon-cache \
binutils-multiarch crossbuild-essential-armhf crossbuild-essential-arm64
RUN dpkg --add-architecture armhf
RUN dpkg --add-architecture arm64
RUN sed -iE "s|deb |deb [arch=amd64] |g" /etc/apt/sources.list \
&& echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ bionic main universe" >> /etc/apt/sources.list \
&& echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main universe" >> /etc/apt/sources.list
RUN apt-get update | true
RUN apt-get install -yq libssl-dev:armhf libssl-dev:arm64
RUN wget -qO appimage-builder-x86_64.AppImage \
https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage
ADD . .

58
.buildbot/appimage/build.sh Executable file
View File

@ -0,0 +1,58 @@
#!/bin/bash
export APPIMAGE_EXTRACT_AND_RUN=1
BUILDER=appimage-builder-x86_64.AppImage
RECIPE=packages/AppImage/AppImageBuilder.yml
export APP_VERSION=$(git describe --tags | cut -d- -f1,3 | tr -d v)
function set_sourceline {
if [ ${ARCH} == amd64 ]; then
export SOURCELINE="deb http://archive.ubuntu.com/ubuntu/ bionic main universe"
else
export SOURCELINE="deb [arch=${ARCH}] http://ports.ubuntu.com/ubuntu-ports/ bionic main universe"
fi
}
function build_appimage {
set_sourceline
./${BUILDER} --recipe ${RECIPE} || exit 1
rm -rf build
}
[ -f ${BUILDER} ] || wget -qO ${BUILDER} \
https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage \
&& chmod +x ${BUILDER}
chmod 1777 /tmp
export ARCH=amd64
export APPIMAGE_ARCH=x86_64
export RUNTIME=${APPIMAGE_ARCH}
build_appimage
export ARCH=armhf
export APPIMAGE_ARCH=${ARCH}
export RUNTIME=gnueabihf
export CC=arm-linux-gnueabihf-gcc
export CXX=${CC}
build_appimage
export ARCH=arm64
export APPIMAGE_ARCH=aarch64
export RUNTIME=${APPIMAGE_ARCH}
export CC=aarch64-linux-gnu-gcc
export CXX=${CC}
build_appimage
EXISTING_OWNER=$(stat -c %u ../out) || mkdir -p ../out
sha256sum PyBitmessage*.AppImage >> ../out/SHA256SUMS
cp PyBitmessage*.AppImage ../out
if [ ${EXISTING_OWNER} ]; then
chown ${EXISTING_OWNER} ../out/PyBitmessage*.AppImage ../out/SHA256SUMS
fi

6
.buildbot/appimage/test.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
export APPIMAGE_EXTRACT_AND_RUN=1
chmod +x PyBitmessage-*-x86_64.AppImage
./PyBitmessage-*-x86_64.AppImage -t

18
.buildbot/kivy/Dockerfile Normal file
View File

@ -0,0 +1,18 @@
# A container for buildbot
FROM ubuntu:focal AS kivy
ENV DEBIAN_FRONTEND=noninteractive
ENV SKIPCACHE=2022-08-29
RUN apt-get update
RUN apt-get install -yq \
build-essential libcap-dev libssl-dev \
libmtdev-dev libpq-dev \
python3-dev python3-pip python3-virtualenv \
xvfb ffmpeg xclip xsel
RUN ln -sf /usr/bin/python3 /usr/bin/python
RUN pip3 install --upgrade setuptools pip

7
.buildbot/kivy/build.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/sh
pip3 install -r kivy-requirements.txt
export INSTALL_TESTS=True
pip3 install .

4
.buildbot/kivy/test.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
export INSTALL_TESTS=True
xvfb-run --server-args="-screen 0, 720x1280x24" python3 tests-kivy.py

View File

@ -0,0 +1,7 @@
FROM ubuntu:bionic
ENV SKIPCACHE=2022-07-17
RUN apt-get update
RUN apt-get install -yq --no-install-suggests --no-install-recommends snapcraft

16
.buildbot/snap/build.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/bash
git remote add -f upstream https://github.com/Bitmessage/PyBitmessage.git
HEAD="$(git rev-parse HEAD)"
UPSTREAM="$(git merge-base --fork-point upstream/v0.6)"
SNAP_DIFF="$(git diff upstream/v0.6 -- packages/snap .buildbot/snap)"
[ -z "${SNAP_DIFF}" ] && [ $HEAD != $UPSTREAM ] && exit 0
pushd packages && snapcraft || exit 1
popd
mkdir -p ../out
mv packages/pybitmessage*.snap ../out
cd ../out
sha256sum pybitmessage*.snap > SHA256SUMS

View File

@ -0,0 +1,22 @@
FROM ubuntu:bionic
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get install -yq --no-install-suggests --no-install-recommends \
software-properties-common build-essential libcap-dev libffi-dev \
libssl-dev python-all-dev python-setuptools \
python3-dev python3-pip python3.8 python3.8-dev python3.8-venv \
python-msgpack python-qt4 language-pack-en qt5dxcb-plugin tor xvfb
RUN apt-get install -yq sudo
RUN echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
RUN python3.8 -m pip install setuptools wheel
RUN python3.8 -m pip install --upgrade pip tox virtualenv
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

3
.buildbot/tox-bionic/build.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
sudo service tor start

4
.buildbot/tox-bionic/test.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
tox -e lint-basic || exit 1
tox

View File

@ -0,0 +1,15 @@
FROM ubuntu:focal
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get install -yq --no-install-suggests --no-install-recommends \
software-properties-common build-essential libcap-dev libffi-dev \
libssl-dev python-all-dev python-setuptools \
python3-dev python3-pip python3.9 python3.9-dev python3.9-venv \
language-pack-en qt5dxcb-plugin tor xvfb
RUN python3.9 -m pip install --upgrade pip tox virtualenv
ADD . .

1
.buildbot/tox-focal/test.sh Symbolic link
View File

@ -0,0 +1 @@
../tox-bionic/test.sh

View File

@ -0,0 +1,12 @@
FROM ubuntu:jammy
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get install -yq --no-install-suggests --no-install-recommends \
software-properties-common build-essential libcap-dev libffi-dev \
libssl-dev python-all-dev python-is-python3 python-setuptools \
python3-dev python3-pip language-pack-en qt5dxcb-plugin tor xvfb
RUN pip install tox

4
.buildbot/tox-jammy/test.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
tox -e lint || exit 1
tox -e py310

View File

@ -0,0 +1,14 @@
FROM ubuntu:bionic
ENV DEBIAN_FRONTEND=noninteractive
RUN dpkg --add-architecture i386
RUN apt-get update
RUN apt-get install -yq --no-install-suggests --no-install-recommends \
software-properties-common build-essential libcap-dev libffi-dev \
libssl-dev python-all-dev python-setuptools xvfb \
mingw-w64 wine-stable winetricks wine32 wine64
ADD . .

8
.buildbot/winebuild/build.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/sh
xvfb-run -a buildscripts/winbuild.sh || exit 1
mkdir -p ../out
mv packages/pyinstaller/dist/Bitmessage*.exe ../out
cd ../out
sha256sum Bitmessage*.exe > SHA256SUMS

41
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1,41 @@
FROM ubuntu:jammy
ARG USERNAME=user
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y \
curl \
flake8 \
gh \
git \
gnupg2 \
jq \
libcap-dev \
libssl-dev \
pylint \
python-setuptools \
python2.7 \
python2.7-dev \
python3 \
python3-dev \
python3-flake8 \
python3-pip \
python3-pycodestyle \
software-properties-common \
sudo \
zsh
RUN apt-add-repository ppa:deadsnakes/ppa
RUN pip install 'tox<4' 'virtualenv<20.22.0'
RUN groupadd --gid $USER_GID $USERNAME \
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
&& chsh -s /usr/bin/zsh user \
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME
USER $USERNAME
WORKDIR /home/$USERNAME

View File

@ -0,0 +1,35 @@
{
"name": "Codespaces Python3",
"extensions": [
"cschleiden.vscode-github-actions",
"eamodio.gitlens",
"github.vscode-pull-request-github",
"ms-azuretools.vscode-docker",
"ms-python.flake8",
"ms-python.pylint",
"ms-python.python",
"ms-vsliveshare.vsliveshare",
"nwgh.bandit",
"the-compiler.python-tox",
"vscode-icons-team.vscode-icons",
"visualstudioexptteam.vscodeintellicode"
],
"dockerFile": "Dockerfile",
"postCreateCommand": "pip3 install -r requirements.txt",
"updateContentCommand": "python2.7 setup.py install --user",
"remoteEnv": {
"PATH": "${containerEnv:PATH}:/home/user/.local/bin"
},
"settings": {
"flake8.args": ["--config=setup.cfg"],
"pylint.args": ["--rcfile=setup.cfg"],
"terminal.integrated.shell.linux": "/usr/bin/zsh",
"terminal.integrated.defaultProfile.linux": "zsh",
"terminal.integrated.fontFamily": "'SourceCodePro+Powerline+Awesome Regular'",
"terminal.integrated.fontSize": 14,
"files.exclude": {
"**/CODE_OF_CONDUCT.md": true,
"**/LICENSE": true
}
}
}

7
.dockerignore Normal file
View File

@ -0,0 +1,7 @@
bin
build
dist
__pycache__
.buildozer
.tox
mprofile_*

View File

@ -1,21 +1,18 @@
## Repository contributions to the PyBitmessage project ## Repository contributions to the PyBitmessage project
- You can get paid for merged commits if you register at [Tip4Commit](https://tip4commit.com/github/Bitmessage/PyBitmessage)
### Code ### Code
- Try to refer to github issue tracker or other permanent sources of discussion about the issue. - Try to refer to github issue tracker or other permanent sources of discussion about the issue.
- It is clear from the diff *what* you have done, it may be less clear *why* you have done it so explain why this change is necessary rather than what it does - It is clear from the diff *what* you have done, it may be less clear *why* you have done it so explain why this change is necessary rather than what it does.
### Documentation ### Documentation
- If there has been a change to the code, there's a good possibility there should be a corresponding change to the documentation Use `tox -e py27-doc` to build a local copy of the documentation.
- If you can't run `fab build_docs` successfully, ask for someone to run it against your branch
### Tests ### Tests
- If there has been a change to the code, there's a good possibility there should be a corresponding change to the tests - If there has been a change to the code, there's a good possibility there should be a corresponding change to the tests
- If you can't run `fab tests` successfully, ask for someone to run it against your branch - To run tests locally use `tox` or `./run-tests-in-docker.sh`
## Translations ## Translations

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

@ -0,0 +1,7 @@
# Basic dependabot.yml for kivymd
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "daily"

5
.gitignore vendored
View File

@ -12,6 +12,7 @@ src/**/*.so
src/**/a.out src/**/a.out
build/lib.* build/lib.*
build/temp.* build/temp.*
bin
dist dist
*.egg-info *.egg-info
docs/_*/* docs/_*/*
@ -19,6 +20,8 @@ docs/autodoc/
build build
pyan/ pyan/
**.coverage **.coverage
coverage.xml
**htmlcov* **htmlcov*
**coverage.json **coverage.json
.buildozer
.tox

View File

@ -1,9 +1,12 @@
version: 2 version: 2
build:
os: ubuntu-20.04
tools:
python: "2.7"
python: python:
version: 2.7
install: install:
- requirements: docs/requirements.txt - requirements: docs/requirements.txt
- method: setuptools - method: pip
path: . path: .
system_packages: false

View File

@ -1,18 +0,0 @@
language: python3.7
cache: pip3
dist: bionic
python:
- "3.7"
addons:
apt:
packages:
- build-essential
- libcap-dev
- libmtdev-dev
- xvfb
install:
- pip3 install -r kivy-requirements.txt
- python3 setup.py install
- export PYTHONWARNINGS=all
script:
- xvfb-run python3 tests-kivy.py

View File

@ -1,22 +0,0 @@
language: python
cache: pip
dist: bionic
python:
- "2.7_with_system_site_packages"
- "3.7"
addons:
apt:
packages:
- build-essential
- libcap-dev
- python-pyqt5
- tor
- xvfb
install:
- pip install -r requirements.txt
- python setup.py install
- export PYTHONWARNINGS=all
script:
- python checkdeps.py
- python src/bitmessagemain.py -t
- python -bm tests

View File

@ -1,5 +1,5 @@
Copyright (c) 2012-2016 Jonathan Warren Copyright (c) 2012-2016 Jonathan Warren
Copyright (c) 2012-2020 The Bitmessage Developers Copyright (c) 2012-2022 The Bitmessage Developers
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,6 +1,6 @@
# A container for PyBitmessage daemon # A container for PyBitmessage daemon
FROM ubuntu:xenial FROM ubuntu:bionic
RUN apt-get update RUN apt-get update
@ -9,8 +9,6 @@ RUN apt-get install -yq --no-install-suggests --no-install-recommends \
build-essential libcap-dev libssl-dev \ build-essential libcap-dev libssl-dev \
python-all-dev python-msgpack python-pip python-setuptools python-all-dev python-msgpack python-pip python-setuptools
RUN pip2 install --upgrade pip
EXPOSE 8444 8442 EXPOSE 8444 8442
ENV HOME /home/bitmessage ENV HOME /home/bitmessage
@ -18,26 +16,22 @@ ENV BITMESSAGE_HOME ${HOME}
WORKDIR ${HOME} WORKDIR ${HOME}
ADD . ${HOME} ADD . ${HOME}
COPY packages/docker/launcher.sh /usr/bin/
# Install tests dependencies
RUN pip2 install -r requirements.txt
# Install # Install
RUN python2 setup.py install RUN pip2 install jsonrpclib .
# Cleanup
RUN rm -rf /var/lib/apt/lists/*
RUN rm -rf ${HOME}
# Create a user # Create a user
RUN useradd bitmessage && chown -R bitmessage ${HOME} RUN useradd -r bitmessage && chown -R bitmessage ${HOME}
USER bitmessage USER bitmessage
# Clean HOME
RUN rm -rf ${HOME}/*
# Generate default config # Generate default config
RUN pybitmessage -t RUN pybitmessage -t
# Setup environment ENTRYPOINT ["launcher.sh"]
RUN APIPASS=$(tr -dc a-zA-Z0-9 < /dev/urandom | head -c32 && echo) \ CMD ["-d"]
&& echo "\napiusername: api\napipassword: $APIPASS" \
&& echo "apienabled = true\napiinterface = 0.0.0.0\napiusername = api\napipassword = $APIPASS" >> keys.dat
CMD ["pybitmessage", "-d"]

View File

@ -1,40 +1,46 @@
# PyBitmessage Installation Instructions # PyBitmessage Installation Instructions
- Binary (no separate installation of dependencies required) - Binary (64bit, no separate installation of dependencies required)
- windows (32bit only): https://download.bitmessage.org/snapshots/ - Windows: https://download.bitmessage.org/snapshots/
- linux (64bit): https://appimage.bitmessage.org/releases/ - Linux AppImages: https://artifacts.bitmessage.at/appimage/
- mac (64bit, not up to date): https://github.com/Bitmessage/PyBitmessage/releases/tag/v0.6.3 - Linux snaps: https://artifacts.bitmessage.at/snap/
- Mac (not up to date): https://github.com/Bitmessage/PyBitmessage/releases/tag/v0.6.1
- Source - Source
git clone git://github.com/Bitmessage/PyBitmessage.git `git clone git://github.com/Bitmessage/PyBitmessage.git`
## Helper Script for building from source ## Helper Script for building from source
Go to the directory with PyBitmessage source code and run: Go to the directory with PyBitmessage source code and run:
``` ```
python checkdeps.py python checkdeps.py
``` ```
If there are missing dependencies, it will explain you what is missing and for many Unix-like systems also what you have to do to resolve it. You need to repeat calling the script until you get nothing mandatory missing. How you then run setuptools depends on whether you want to install it to user's directory or system. If there are missing dependencies, it will explain you what is missing
and for many Unix-like systems also what you have to do to resolve it. You need
to repeat calling the script until you get nothing mandatory missing. How you
then run setuptools depends on whether you want to install it to
user's directory or system.
### If checkdeps fails, then verify manually which dependencies are missing from below ### If checkdeps fails, then verify manually which dependencies are missing from below
Before running PyBitmessage, make sure you have all the necessary dependencies Before running PyBitmessage, make sure you have all the necessary dependencies
installed on your system. installed on your system.
These dependencies may not be available on a recent OS and PyBitmessage may not build on such systems. These dependencies may not be available on a recent OS and PyBitmessage may not
Here's a list of dependencies needed for PyBitmessage based on operating system build on such systems. Here's a list of dependencies needed for PyBitmessage
based on operating system
For Debian-based (Ubuntu, Raspbian, PiBang, others) For Debian-based (Ubuntu, Raspbian, PiBang, others)
``` ```
python2.7 openssl libssl-dev git python-msgpack python-qt4 python-six python2.7 openssl libssl-dev python-msgpack python-qt4 python-six
``` ```
For Arch Linux For Arch Linux
``` ```
python2 openssl git python2-pyqt4 python-six python2 openssl python2-pyqt4 python-six
``` ```
For Fedora For Fedora
``` ```
python python-qt4 git openssl-compat-bitcoin-libs python-six python python-qt4 openssl-compat-bitcoin-libs python-six
``` ```
For Red Hat Enterprise Linux (RHEL) For Red Hat Enterprise Linux (RHEL)
``` ```
python python-qt4 git openssl-compat-bitcoin-libs python-six python python-qt4 openssl-compat-bitcoin-libs python-six
``` ```
For GNU Guix For GNU Guix
``` ```
@ -42,9 +48,10 @@ python2-msgpack python2-pyqt@4.11.4 python2-sip openssl python-six
``` ```
## setuptools ## setuptools
This is now the recommended and in most cases the easiest procedure for installing PyBitmessage. This is now the recommended and in most cases the easiest way for
installing PyBitmessage.
There are 3 options for running setuptools: root, user, venv There are 2 options for installing with setuptools: root and user.
### as root: ### as root:
``` ```
@ -58,7 +65,7 @@ python setup.py install --user
~/.local/bin/pybitmessage ~/.local/bin/pybitmessage
``` ```
### as venv: ## pip venv (daemon):
Create virtualenv with Python 2.x version Create virtualenv with Python 2.x version
``` ```
virtualenv -p python2 env virtualenv -p python2 env
@ -69,19 +76,11 @@ Activate env
source env/bin/activate source env/bin/activate
``` ```
Install requirements.txt
```
pip install -r requirements.txt
```
Build & run pybitmessage Build & run pybitmessage
``` ```
python setup.py install pip install .
pybitmessage pybitmessage -d
``` ```
## Alternative way to run PyBitmessage, without setuptools (this isn't recommended) ## Alternative way to run PyBitmessage, without setuptools (this isn't recommended)
run `src/bitmessagemain.py`. run `./start.sh`.
```
cd PyBitmessage/ && python src/bitmessagemain.py
```

View File

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2012-2016 Jonathan Warren Copyright (c) 2012-2016 Jonathan Warren
Copyright (c) 2012-2020 The Bitmessage Developers Copyright (c) 2012-2022 The Bitmessage Developers
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in this software and associated documentation files (the "Software"), to deal in

View File

@ -2,3 +2,4 @@ include COPYING
include README.md include README.md
include requirements.txt include requirements.txt
recursive-include desktop * recursive-include desktop *
recursive-include packages/apparmor *

View File

@ -22,7 +22,7 @@ Feel welcome to join chan "bitmessage", BM-2cWy7cvHoq3f1rYMerRJp8PT653jjSuEdY
References References
---------- ----------
* [Project Website](https://bitmessage.org) * [Project Website](https://bitmessage.org)
* [Protocol Specification](https://bitmessage.org/wiki/Protocol_specification) * [Protocol Specification](https://pybitmessage.rtfd.io/en/v0.6/protocol.html)
* [Whitepaper](https://bitmessage.org/bitmessage.pdf) * [Whitepaper](https://bitmessage.org/bitmessage.pdf)
* [Installation](https://bitmessage.org/wiki/Compiling_instructions) * [Installation](https://bitmessage.org/wiki/Compiling_instructions)
* [Discuss on Reddit](https://www.reddit.com/r/bitmessage) * [Discuss on Reddit](https://www.reddit.com/r/bitmessage)

26
buildscripts/androiddev.sh Normal file → Executable file
View File

@ -58,23 +58,14 @@ install_ndk()
} }
# INSTALL SDK # INSTALL SDK
function install_sdk() install_sdk()
{ {
if [[ "$get_python_version" -eq " 2 " ]];
then
ANDROID_SDK_BUILD_TOOLS_VERSION="28.0.3"
elif [[ "$get_python_version" -eq " 3 " ]];
then
ANDROID_SDK_BUILD_TOOLS_VERSION="29.0.2" ANDROID_SDK_BUILD_TOOLS_VERSION="29.0.2"
else
exit
fi
ANDROID_SDK_HOME="${ANDROID_HOME}/android-sdk" ANDROID_SDK_HOME="${ANDROID_HOME}/android-sdk"
# get the latest version from https://developer.android.com/studio/index.html # get the latest version from https://developer.android.com/studio/index.html
ANDROID_SDK_TOOLS_VERSION="4333796" ANDROID_SDK_TOOLS_VERSION="4333796"
ANDROID_SDK_TOOLS_ARCHIVE="sdk-tools-linux-${ANDROID_SDK_TOOLS_VERSION}.zip" ANDROID_SDK_TOOLS_ARCHIVE="sdk-tools-linux-${ANDROID_SDK_TOOLS_VERSION}.zip"
ANDROID_SDK_TOOLS_DL_URL="https://dl.google.com/android/repository/${ANDROID_SDK_TOOLS_ARCHIVE}" ANDROID_SDK_TOOLS_DL_URL="https://dl.google.com/android/repository/${ANDROID_SDK_TOOLS_ARCHIVE}"
echo "Downloading sdk.........................................................................."
wget -nc ${ANDROID_SDK_TOOLS_DL_URL} wget -nc ${ANDROID_SDK_TOOLS_DL_URL}
mkdir --parents "${ANDROID_SDK_HOME}" mkdir --parents "${ANDROID_SDK_HOME}"
unzip -q "${ANDROID_SDK_TOOLS_ARCHIVE}" -d "${ANDROID_SDK_HOME}" unzip -q "${ANDROID_SDK_TOOLS_ARCHIVE}" -d "${ANDROID_SDK_HOME}"
@ -84,7 +75,7 @@ function install_sdk()
echo '### Sources for Android SDK Manager' > "${ANDROID_SDK_HOME}/.android/repositories.cfg" echo '### Sources for Android SDK Manager' > "${ANDROID_SDK_HOME}/.android/repositories.cfg"
# accept Android licenses (JDK necessary!) # accept Android licenses (JDK necessary!)
apt -y update -qq apt -y update -qq
apt -y install -qq --no-install-recommends openjdk-8-jdk apt -y install -qq --no-install-recommends openjdk-11-jdk
apt -y autoremove apt -y autoremove
yes | "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" > /dev/null yes | "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" > /dev/null
# download platforms, API, build tools # download platforms, API, build tools
@ -98,23 +89,14 @@ function install_sdk()
} }
# INSTALL APACHE-ANT # INSTALL APACHE-ANT
function install_ant() install_ant()
{ {
if [[ "$get_python_version" -eq " 2 " ]]; APACHE_ANT_VERSION="1.10.12"
then
APACHE_ANT_VERSION="1.9.4"
elif [[ "$get_python_version" -eq " 3 " ]];
then
APACHE_ANT_VERSION="1.10.7"
else
exit
fi
APACHE_ANT_ARCHIVE="apache-ant-${APACHE_ANT_VERSION}-bin.tar.gz" APACHE_ANT_ARCHIVE="apache-ant-${APACHE_ANT_VERSION}-bin.tar.gz"
APACHE_ANT_DL_URL="http://archive.apache.org/dist/ant/binaries/${APACHE_ANT_ARCHIVE}" APACHE_ANT_DL_URL="http://archive.apache.org/dist/ant/binaries/${APACHE_ANT_ARCHIVE}"
APACHE_ANT_HOME="${ANDROID_HOME}/apache-ant" APACHE_ANT_HOME="${ANDROID_HOME}/apache-ant"
APACHE_ANT_HOME_V="${APACHE_ANT_HOME}-${APACHE_ANT_VERSION}" APACHE_ANT_HOME_V="${APACHE_ANT_HOME}-${APACHE_ANT_VERSION}"
echo "Downloading ant.........................................................................."
wget -nc ${APACHE_ANT_DL_URL} wget -nc ${APACHE_ANT_DL_URL}
tar -xf "${APACHE_ANT_ARCHIVE}" -C "${ANDROID_HOME}" tar -xf "${APACHE_ANT_ARCHIVE}" -C "${ANDROID_HOME}"
ln -sfn "${APACHE_ANT_HOME_V}" "${APACHE_ANT_HOME}" ln -sfn "${APACHE_ANT_HOME_V}" "${APACHE_ANT_HOME}"

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
# Cleanup # Cleanup
rm -rf PyBitmessage rm -rf PyBitmessage
@ -18,9 +18,19 @@ fi
./pkg2appimage packages/AppImage/PyBitmessage.yml ./pkg2appimage packages/AppImage/PyBitmessage.yml
if [ -f "out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage" ]; then ./pkg2appimage --appimage-extract
. ./squashfs-root/usr/share/pkg2appimage/functions.sh
GLIBC=$(glibc_needed)
VERSION_EXPANDED=${VERSION}.glibc${GLIBC}-${SYSTEM_ARCH}
if [ -f "out/PyBitmessage-${VERSION_EXPANDED}.AppImage" ]; then
echo "Build Successful"; echo "Build Successful";
echo "Run out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage" echo "Run out/PyBitmessage-${VERSION_EXPANDED}.AppImage";
out/PyBitmessage-${VERSION_EXPANDED}.AppImage -t
else else
echo "Build Failed" echo "Build Failed";
exit 1
fi fi

View File

@ -0,0 +1,5 @@
#!/bin/sh
xgettext -Lpython --output=src/translations/messages.pot \
src/bitmessagekivy/mpybit.py src/bitmessagekivy/main.kv \
src/bitmessagekivy/baseclass/*.py src/bitmessagekivy/kv/*.kv

View File

@ -104,7 +104,9 @@ function install_pyinstaller()
echo "Installing PyInstaller" echo "Installing PyInstaller"
if [ "${MACHINE_TYPE}" == 'x86_64' ]; then if [ "${MACHINE_TYPE}" == 'x86_64' ]; then
# 3.6 is the last version to support python 2.7 # 3.6 is the last version to support python 2.7
wine python -m pip install -I pyinstaller==3.6 # but the resulting executable cannot run in wine
# see https://github.com/pyinstaller/pyinstaller/issues/4628
wine python -m pip install -I pyinstaller==3.5
else else
# 3.2.1 is the last version to work on XP # 3.2.1 is the last version to work on XP
# see https://github.com/pyinstaller/pyinstaller/issues/2931 # see https://github.com/pyinstaller/pyinstaller/issues/2931
@ -138,13 +140,13 @@ function build_dll(){
cd src/bitmsghash || exit 1 cd src/bitmsghash || exit 1
if [ "${MACHINE_TYPE}" == 'x86_64' ]; then if [ "${MACHINE_TYPE}" == 'x86_64' ]; then
echo "Create dll" echo "Create dll"
x86_64-w64-mingw32-g++ -D_WIN32 -Wall -O3 -march=native \ x86_64-w64-mingw32-g++ -D_WIN32 -Wall -O3 -march=x86-64 \
"-I$HOME/.wine64/drive_c/OpenSSL-Win64/include" \ "-I$HOME/.wine64/drive_c/OpenSSL-Win64/include" \
-I/usr/x86_64-w64-mingw32/include \ -I/usr/x86_64-w64-mingw32/include \
"-L$HOME/.wine64/drive_c/OpenSSL-Win64/lib" \ "-L$HOME/.wine64/drive_c/OpenSSL-Win64/lib" \
-c bitmsghash.cpp -c bitmsghash.cpp
x86_64-w64-mingw32-g++ -static-libgcc -shared bitmsghash.o \ x86_64-w64-mingw32-g++ -static-libgcc -shared bitmsghash.o \
-D_WIN32 -O3 -march=native \ -D_WIN32 -O3 -march=x86-64 \
"-I$HOME/.wine64/drive_c/OpenSSL-Win64/include" \ "-I$HOME/.wine64/drive_c/OpenSSL-Win64/include" \
"-L$HOME/.wine64/drive_c/OpenSSL-Win64" \ "-L$HOME/.wine64/drive_c/OpenSSL-Win64" \
-L/usr/lib/x86_64-linux-gnu/wine \ -L/usr/lib/x86_64-linux-gnu/wine \
@ -152,13 +154,13 @@ function build_dll(){
-o bitmsghash64.dll -Wl,--out-implib,bitmsghash.a -o bitmsghash64.dll -Wl,--out-implib,bitmsghash.a
else else
echo "Create dll" echo "Create dll"
i686-w64-mingw32-g++ -D_WIN32 -Wall -m32 -O3 -march=native \ i686-w64-mingw32-g++ -D_WIN32 -Wall -m32 -O3 -march=i686 \
"-I$HOME/.wine32/drive_c/OpenSSL-Win32/include" \ "-I$HOME/.wine32/drive_c/OpenSSL-Win32/include" \
-I/usr/i686-w64-mingw32/include \ -I/usr/i686-w64-mingw32/include \
"-L$HOME/.wine32/drive_c/OpenSSL-Win32/lib" \ "-L$HOME/.wine32/drive_c/OpenSSL-Win32/lib" \
-c bitmsghash.cpp -c bitmsghash.cpp
i686-w64-mingw32-g++ -static-libgcc -shared bitmsghash.o \ i686-w64-mingw32-g++ -static-libgcc -shared bitmsghash.o \
-D_WIN32 -O3 -march=native \ -D_WIN32 -O3 -march=i686 \
"-I$HOME/.wine32/drive_c/OpenSSL-Win32/include" \ "-I$HOME/.wine32/drive_c/OpenSSL-Win32/include" \
"-L$HOME/.wine32/drive_c/OpenSSL-Win32/lib/MinGW" \ "-L$HOME/.wine32/drive_c/OpenSSL-Win32/lib/MinGW" \
-fPIC -shared -lcrypt32 -leay32 -lwsock32 \ -fPIC -shared -lcrypt32 -leay32 -lwsock32 \
@ -174,10 +176,13 @@ function build_exe(){
function dryrun_exe(){ function dryrun_exe(){
cd "${BASE_DIR}" || exit 1 cd "${BASE_DIR}" || exit 1
if [ ! "${MACHINE_TYPE}" == 'x86_64' ]; then
local VERSION=$(python setup.py --version) local VERSION=$(python setup.py --version)
wine packages/pyinstaller/dist/Bitmessage_x86_$VERSION.exe -t if [ "${MACHINE_TYPE}" == 'x86_64' ]; then
EXE=Bitmessage_x64_$VERSION.exe
else
EXE=Bitmessage_x86_$VERSION.exe
fi fi
wine packages/pyinstaller/dist/$EXE -t
} }
# prepare on ubuntu # prepare on ubuntu

106
docs/address.rst Normal file
View File

@ -0,0 +1,106 @@
Address
=======
Bitmessage adresses are Base58 encoded public key hashes. An address looks like
``BM-BcbRqcFFSQUUmXFKsPJgVQPSiFA3Xash``. All Addresses start with ``BM-``,
however clients should accept addresses without the prefix. PyBitmessage does
this. The reason behind this idea is the fact, that when double clicking on an
address for copy and paste, the prefix is usually not selected due to the dash
being a common separator.
Public Key usage
----------------
Addresses may look complicated but they fulfill the purpose of verifying the
sender. A Message claiming to be from a specific address can simply be checked by
decoding a special field in the data packet with the public key, that represents
the address. If the decryption succeeds, the message is from the address it
claims to be.
Length
------
Without the ``BM-`` prefix, an address is usually 32-34 chars long. Since an
address is a hash it can be calculated by the client in a way, that the first
bytes are zero (``\0``) and bitmessage strips these. This causes the client to do
much more work to be lucky and find such an address. This is an optional checkbox
in address generation dialog.
Versions
--------
* v1 addresses used a single RSA key pair
* v2 addresses use 2 ECC key pairs
* v3 addresses extends v2 addresses to allow specifying the proof of work
requirements. The pubkey object is signed to mitigate against
forgery/tampering.
* v4 addresses protect against harvesting addresses from getpubkey and pubkey
objects
Address Types
-------------
There are two address types the user can generate in PyBitmessage. The resulting
addresses have no difference, but the method how they are created differs.
Random Address
^^^^^^^^^^^^^^
Random addresses are generated from a randomly chosen number. The resulting
address cannot be regenerated without knowledge of the number and therefore the
keys.dat should be backed up. Generating random addresses takes slightly longer
due to the POW required for the public key broadcast.
Usage
"""""
* Generate unique addresses
* Generate one time addresses.
Deterministic Address
^^^^^^^^^^^^^^^^^^^^^
For this type of Address a passphrase is required, that is used to seed the
random generator. Using the same passphrase creates the same addresses.
Using deterministic addresses should be done with caution, using a word from a
dictionary or a common number can lead to others generating the same address and
thus being able to receive messages not intended for them. Generating a
deterministic address will not publish the public key. The key is sent in case
somebody requests it. This saves :doc:`pow` time, when generating a bunch of
addresses.
Usage
"""""
* Create the same address on multiple systems without the need of copying
keys.dat or an Address Block.
* create a Channel. (Use the *Join/create chan* option in the file menu instead)
* Being able to restore the address in case of address database corruption or
deletation.
Address generation
------------------
1. Create a private and a public key for encryption and signing (resulting in
4 keys)
2. Merge the public part of the signing key and the encryption key together.
(encoded in uncompressed X9.62 format) (A)
3. Take the SHA512 hash of A. (B)
4. Take the RIPEMD160 of B. (C)
5. Repeat step 1-4 until you have a result that starts with a zero
(Or two zeros, if you want a short address). (D)
6. Remove the zeros at the beginning of D. (E)
7. Put the stream number (as a var_int) in front of E. (F)
8. Put the address version (as a var_int) in front of F. (G)
9. Take a double SHA512 (hash of a hash) of G and use the first four bytes as a
checksum, that you append to the end. (H)
10. base58 encode H. (J)
11. Put "BM-" in front J. (K)
K is your full address
.. note:: Bitmessage's base58 encoding uses the following sequence
(the same as Bitcoin's):
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".
Many existing libraries for base58 do not use this ordering.

View File

@ -19,7 +19,7 @@ import version # noqa:E402
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
project = u'PyBitmessage' project = u'PyBitmessage'
copyright = u'2019, The Bitmessage Team' # pylint: disable=redefined-builtin copyright = u'2019-2022, The Bitmessage Team' # pylint: disable=redefined-builtin
author = u'The Bitmessage Team' author = u'The Bitmessage Team'
# The short X.Y version # The short X.Y version
@ -203,7 +203,7 @@ autodoc_mock_imports = [
'pybitmessage.bitmessagekivy', 'pybitmessage.bitmessagekivy',
'pybitmessage.bitmessageqt.foldertree', 'pybitmessage.bitmessageqt.foldertree',
'pybitmessage.helper_startup', 'pybitmessage.helper_startup',
'pybitmessage.mock', 'pybitmessage.mockbm',
'pybitmessage.network.httpd', 'pybitmessage.network.httpd',
'pybitmessage.network.https', 'pybitmessage.network.https',
'ctypes', 'ctypes',
@ -232,7 +232,7 @@ apidoc_excluded_paths = [
'bitmessageqt/addressvalidator.py', 'bitmessageqt/foldertree.py', 'bitmessageqt/addressvalidator.py', 'bitmessageqt/foldertree.py',
'bitmessageqt/migrationwizard.py', 'bitmessageqt/newaddresswizard.py', 'bitmessageqt/migrationwizard.py', 'bitmessageqt/newaddresswizard.py',
'helper_startup.py', 'helper_startup.py',
'kivymd', 'mock', 'main.py', 'navigationdrawer', 'network/http*', 'kivymd', 'mockbm', 'main.py', 'navigationdrawer', 'network/http*',
'src', 'tests', 'version.py' 'src', 'tests', 'version.py'
] ]
apidoc_module_first = True apidoc_module_first = True

View File

@ -0,0 +1,19 @@
+------------+-------------+-----------+--------------------------------------------+
| Field Size | Description | Data type | Comments |
+============+=============+===========+============================================+
| 16 | IV | uchar[] | Initialization Vector used for AES-256-CBC |
+------------+-------------+-----------+--------------------------------------------+
| 2 | Curve type | uint16_t | Elliptic Curve type 0x02CA (714) |
+------------+-------------+-----------+--------------------------------------------+
| 2 | X length | uint16_t | Length of X component of public key R |
+------------+-------------+-----------+--------------------------------------------+
| X length | X | uchar[] | X component of public key R |
+------------+-------------+-----------+--------------------------------------------+
| 2 | Y length | uint16_t | Length of Y component of public key R |
+------------+-------------+-----------+--------------------------------------------+
| Y length | Y | uchar[] | Y component of public key R |
+------------+-------------+-----------+--------------------------------------------+
| ? | encrypted | uchar[] | Cipher text |
+------------+-------------+-----------+--------------------------------------------+
| 32 | MAC | uchar[] | HMACSHA256 Message Authentication Code |
+------------+-------------+-----------+--------------------------------------------+

257
docs/encryption.rst Normal file
View File

@ -0,0 +1,257 @@
Encryption
==========
Bitmessage uses the Elliptic Curve Integrated Encryption Scheme
`(ECIES) <http://en.wikipedia.org/wiki/Integrated_Encryption_Scheme>`_
to encrypt the payload of the Message and Broadcast objects.
The scheme uses Elliptic Curve Diffie-Hellman
`(ECDH) <http://en.wikipedia.org/wiki/ECDH>`_ to generate a shared secret used
to generate the encryption parameters for Advanced Encryption Standard with
256bit key and Cipher-Block Chaining
`(AES-256-CBC) <http://en.wikipedia.org/wiki/Advanced_Encryption_Standard>`_.
The encrypted data will be padded to a 16 byte boundary in accordance to
`PKCS7 <http://en.wikipedia.org/wiki/Cryptographic_Message_Syntax>`_. This
means that the data is padded with N bytes of value N.
The Key Derivation Function
`(KDF) <http://en.wikipedia.org/wiki/Key_derivation_function>`_ used to
generate the key material for AES is
`SHA512 <http://en.wikipedia.org/wiki/Sha512>`_. The Message Authentication
Code (MAC) scheme used is `HMACSHA256 <http://en.wikipedia.org/wiki/Hmac>`_.
Format
------
(See also: :doc:`protocol`)
.. include:: encrypted_payload.rst
In order to reconstitute a usable (65 byte) public key (starting with 0x04),
the X and Y components need to be expanded by prepending them with 0x00 bytes
until the individual component lengths are 32 bytes.
Encryption
----------
1. The destination public key is called K.
2. Generate 16 random bytes using a secure random number generator.
Call them IV.
3. Generate a new random EC key pair with private key called r and public key
called R.
4. Do an EC point multiply with public key K and private key r. This gives you
public key P.
5. Use the X component of public key P and calculate the SHA512 hash H.
6. The first 32 bytes of H are called key_e and the last 32 bytes are called
key_m.
7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7. [#f1]_
8. Encrypt the data with AES-256-CBC, using IV as initialization vector,
key_e as encryption key and the padded input text as payload. Call the
output cipher text.
9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and
IV + R [#f2]_ + cipher text as data. Call the output MAC.
The resulting data is: IV + R + cipher text + MAC
Decryption
----------
1. The private key used to decrypt is called k.
2. Do an EC point multiply with private key k and public key R. This gives you
public key P.
3. Use the X component of public key P and calculate the SHA512 hash H.
4. The first 32 bytes of H are called key_e and the last 32 bytes are called
key_m.
5. Calculate MAC' with HMACSHA256, using key_m as salt and
IV + R + cipher text as data.
6. Compare MAC with MAC'. If not equal, decryption will fail.
7. Decrypt the cipher text with AES-256-CBC, using IV as initialization
vector, key_e as decryption key and the cipher text as payload. The output
is the padded input text.
.. highlight:: nasm
Partial Example
---------------
.. list-table:: Public key K:
:header-rows: 1
:widths: auto
* - Data
- Comments
* -
::
04
09 d4 e5 c0 ab 3d 25 fe
04 8c 64 c9 da 1a 24 2c
7f 19 41 7e 95 17 cd 26
69 50 d7 2c 75 57 13 58
5c 61 78 e9 7f e0 92 fc
89 7c 9a 1f 17 20 d5 77
0a e8 ea ad 2f a8 fc bd
08 e9 32 4a 5d de 18 57
- Public key, 0x04 prefix, then 32 bytes X and 32 bytes Y.
.. list-table:: Initialization Vector IV:
:header-rows: 1
:widths: auto
* - Data
- Comments
* -
::
bd db 7c 28 29 b0 80 38
75 30 84 a2 f3 99 16 81
- 16 bytes generated with a secure random number generator.
.. list-table:: Randomly generated key pair with private key r and public key R:
:header-rows: 1
:widths: auto
* - Data
- Comments
* -
::
5b e6 fa cd 94 1b 76 e9
d3 ea d0 30 29 fb db 6b
6e 08 09 29 3f 7f b1 97
d0 c5 1f 84 e9 6b 8b a4
- Private key r
* -
::
02 ca 00 20
02 93 21 3d cf 13 88 b6
1c 2a e5 cf 80 fe e6 ff
ff c0 49 a2 f9 fe 73 65
fe 38 67 81 3c a8 12 92
00 20
df 94 68 6c 6a fb 56 5a
c6 14 9b 15 3d 61 b3 b2
87 ee 2c 7f 99 7c 14 23
87 96 c1 2b 43 a3 86 5a
- Public key R
.. list-table:: Derived public key P (point multiply r with K):
:header-rows: 1
:widths: auto
* - Data
- Comments
* -
::
04
0d b8 e3 ad 8c 0c d7 3f
a2 b3 46 71 b7 b2 47 72
9b 10 11 41 57 9d 19 9e
0d c0 bd 02 4e ae fd 89
ca c8 f5 28 dc 90 b6 68
11 ab ac 51 7d 74 97 be
52 92 93 12 29 be 0b 74
3e 05 03 f4 43 c3 d2 96
- Public key P
* -
::
0d b8 e3 ad 8c 0c d7 3f
a2 b3 46 71 b7 b2 47 72
9b 10 11 41 57 9d 19 9e
0d c0 bd 02 4e ae fd 89
- X component of public key P
.. list-table:: SHA512 of public key P X component (H):
:header-rows: 1
:widths: auto
* - Data
- Comments
* -
::
17 05 43 82 82 67 86 71
05 26 3d 48 28 ef ff 82
d9 d5 9c bf 08 74 3b 69
6b cc 5d 69 fa 18 97 b4
- First 32 bytes of H called key_e
* -
::
f8 3f 1e 9c c5 d6 b8 44
8d 39 dc 6a 9d 5f 5b 7f
46 0e 4a 78 e9 28 6e e8
d9 1c e1 66 0a 53 ea cd
- Last 32 bytes of H called key_m
.. list-table:: Padded input:
:header-rows: 1
:widths: auto
* - Data
- Comments
* -
::
54 68 65 20 71 75 69 63
6b 20 62 72 6f 77 6e 20
66 6f 78 20 6a 75 6d 70
73 20 6f 76 65 72 20 74
68 65 20 6c 61 7a 79 20
64 6f 67 2e 04 04 04 04
- The quick brown fox jumps over the lazy dog.0x04,0x04,0x04,0x04
.. list-table:: Cipher text:
:header-rows: 1
:widths: auto
* - Data
- Comments
* -
::
64 20 3d 5b 24 68 8e 25
47 bb a3 45 fa 13 9a 5a
1d 96 22 20 d4 d4 8a 0c
f3 b1 57 2c 0d 95 b6 16
43 a6 f9 a0 d7 5a f7 ea
cc 1b d9 57 14 7b f7 23
- 3 blocks of 16 bytes of encrypted data.
.. list-table:: MAC:
:header-rows: 1
:widths: auto
* - Data
- Comments
* -
::
f2 52 6d 61 b4 85 1f b2
34 09 86 38 26 fd 20 61
65 ed c0 21 36 8c 79 46
57 1c ea d6 90 46 e6 19
- 32 bytes hash
.. rubric:: Footnotes
.. [#f1] The pyelliptic implementation used in PyBitmessage takes unpadded data,
see :obj:`.pyelliptic.Cipher.ciphering`.
.. [#f2] The pyelliptic encodes the pubkey with curve and length,
see :obj:`.pyelliptic.ECC.get_pubkey`

View File

@ -0,0 +1,55 @@
Extended encoding
=================
Extended encoding is an attempt to create a standard for transmitting structured
data. The goals are flexibility, wide platform support and extensibility. It is
currently available in the v0.6 branch and can be enabled by holding "Shift"
while clicking on Send. It is planned that v5 addresses will have to support
this. It's a work in progress, the basic plain text message works but don't
expect anthing else at this time.
The data structure is in msgpack, then compressed with zlib. The top level is
a key/value store, and the "" key (empty string) contains the value of the type
of object, which can then have its individual format and standards.
Text fields are encoded using UTF-8.
Types
-----
You can find the implementations in the ``src/messagetypes`` directory of
PyBitmessage. Each type has its own file which includes one class, and they are
dynamically loaded on startup. It's planned that this will also contain
initialisation, rendering and so on, so that developers can simply add a new
object type by adding a single file in the messagetypes directory and not have
to change any other part of the code.
message
^^^^^^^
The replacement for the old messages. Mandatory keys are ``body`` and
``subject``, others are currently not implemented and not mandatory. Proposed
other keys:
``parents``:
array of msgids referring to messages that logically precede it in a
conversation. Allows to create a threaded conversation view
``files``:
array of files (which is a key/value pair):
``name``:
file name, mandatory
``data``:
the binary data of the file
``type``:
MIME content type
``disposition``:
MIME content disposition, possible values are "inline" and "attachment"
vote
^^^^
Dummy code available in the repository. Supposed to serve voting in a chan
(thumbs up/down) for decentralised moderation. Does not actually do anything at
the moment and specification can change.

View File

@ -1,7 +1,18 @@
.. mdinclude:: ../README.md .. mdinclude:: ../README.md
:end-line: 20
Documentation Protocol documentation
------------- ----------------------
.. toctree::
:maxdepth: 2
protocol
address
encryption
pow
Code documentation
------------------
.. toctree:: .. toctree::
:maxdepth: 3 :maxdepth: 3
@ -14,3 +25,6 @@ Indices and tables
* :ref:`genindex` * :ref:`genindex`
* :ref:`modindex` * :ref:`modindex`
* :ref:`search` * :ref:`search`
.. mdinclude:: ../README.md
:start-line: 21

77
docs/pow.rst Normal file
View File

@ -0,0 +1,77 @@
Proof of work
=============
This page describes Bitmessage's Proof of work ("POW") mechanism as it exists in
Protocol Version 3. In this document, hash() means SHA512(). SHA512 was chosen
as it is widely supported and so that Bitcoin POW hardware cannot trivially be
used for Bitmessage POWs. The author acknowledges that they are essentially the
same algorithm with a different key size.
Both ``averageProofOfWorkNonceTrialsPerByte`` and ``payloadLengthExtraBytes``
are set by the owner of a Bitmessage address. The default and minimum for each
is 1000. (This is the same as difficulty 1. If the difficulty is 2, then this
value is 2000). The purpose of ``payloadLengthExtraBytes`` is to add some extra
weight to small messages.
Do a POW
--------
Let us use a ``msg`` message as an example::
payload = embeddedTime + encodedObjectVersion + encodedStreamNumber + encrypted
``payloadLength``
the length of payload, in bytes, + 8
(to account for the nonce which we will append later)
``TTL``
the number of seconds in between now and the object expiresTime.
.. include:: pow_formula.rst
::
initialHash = hash(payload)
start with ``trialValue = 99999999999999999999``
also start with ``nonce = 0`` where nonce is 8 bytes in length and can be
hashed as if it is a string.
::
while trialValue > target:
nonce = nonce + 1
resultHash = hash(hash( nonce || initialHash ))
trialValue = the first 8 bytes of resultHash, converted to an integer
When this loop finishes, you will have your 8 byte nonce value which you can
prepend onto the front of the payload. The message is then ready to send.
Check a POW
-----------
Let us assume that ``payload`` contains the payload for a msg message (the nonce
down through the encrypted message data).
``nonce``
the first 8 bytes of payload
``dataToCheck``
the ninth byte of payload on down (thus it is everything except the nonce)
::
initialHash = hash(dataToCheck)
resultHash = hash(hash( nonce || initialHash ))
``POWValue``
the first eight bytes of resultHash converted to an integer
``TTL``
the number of seconds in between now and the object ``expiresTime``.
.. include:: pow_formula.rst
If ``POWValue`` is less than or equal to ``target``, then the POW check passes.

7
docs/pow_formula.rst Normal file
View File

@ -0,0 +1,7 @@
.. math::
target = \frac{2^{64}}{{\displaystyle
nonceTrialsPerByte (payloadLength + payloadLengthExtraBytes + \frac{
TTL (payloadLength + payloadLengthExtraBytes)}{2^{16}})
}}

997
docs/protocol.rst Normal file
View File

@ -0,0 +1,997 @@
Protocol specification
======================
.. warning:: All objects sent on the network should support protocol v3
starting on Sun, 16 Nov 2014 22:00:00 GMT.
.. toctree::
:maxdepth: 2
Common standards
----------------
Hashes
^^^^^^
Most of the time `SHA-512 <http://en.wikipedia.org/wiki/SHA-2>`_ hashes are
used, however `RIPEMD-160 <http://en.wikipedia.org/wiki/RIPEMD>`_ is also used
when creating an address.
A double-round of SHA-512 is used for the Proof Of Work. Example of
double-SHA-512 encoding of string "hello":
.. highlight:: nasm
::
hello
9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043(first round of sha-512)
0592a10584ffabf96539f3d780d776828c67da1ab5b169e9e8aed838aaecc9ed36d49ff1423c55f019e050c66c6324f53588be88894fef4dcffdb74b98e2b200(second round of sha-512)
For Bitmessage addresses (RIPEMD-160) this would give:
::
hello
9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043(first round is sha-512)
79a324faeebcbf9849f310545ed531556882487e (with ripemd-160)
Common structures
-----------------
All integers are encoded in big endian. (This is different from Bitcoin).
.. list-table:: Message structure
:header-rows: 1
:widths: auto
* - Field Size
- Description
- Data type
- Comments
* - 4
- magic
- uint32_t
- Magic value indicating message origin network, and used to seek to next
message when stream state is unknown
* - 12
- command
- char[12]
- ASCII string identifying the packet content, NULL padded (non-NULL
padding results in packet rejected)
* - 4
- length
- uint32_t
- Length of payload in number of bytes. Because of other restrictions,
there is no reason why this length would ever be larger than 1600003
bytes. Some clients include a sanity-check to avoid processing messages
which are larger than this.
* - 4
- checksum
- uint32_t
- First 4 bytes of sha512(payload)
* - ?
- message_payload
- uchar[]
- The actual data, a :ref:`message <msg-types>` or an object_.
Not to be confused with objectPayload.
Known magic values:
+-------------+-------------------+
| Magic value | Sent over wire as |
+=============+===================+
| 0xE9BEB4D9 | E9 BE B4 D9 |
+-------------+-------------------+
.. _varint:
Variable length integer
^^^^^^^^^^^^^^^^^^^^^^^
Integer can be encoded depending on the represented value to save space.
Variable length integers always precede an array/vector of a type of data that
may vary in length. Varints **must** use the minimum possible number of bytes to
encode a value. For example, the value 6 can be encoded with one byte therefore
a varint that uses three bytes to encode the value 6 is malformed and the
decoding task must be aborted.
+---------------+----------------+------------------------------------------+
| Value | Storage length | Format |
+===============+================+==========================================+
| < 0xfd | 1 | uint8_t |
+---------------+----------------+------------------------------------------+
| <= 0xffff | 3 | 0xfd followed by the integer as uint16_t |
+---------------+----------------+------------------------------------------+
| <= 0xffffffff | 5 | 0xfe followed by the integer as uint32_t |
+---------------+----------------+------------------------------------------+
| - | 9 | 0xff followed by the integer as uint64_t |
+---------------+----------------+------------------------------------------+
Variable length string
^^^^^^^^^^^^^^^^^^^^^^
Variable length string can be stored using a variable length integer followed by
the string itself.
+------------+-------------+------------+----------------------------------+
| Field Size | Description | Data type | Comments |
+============+=============+============+==================================+
| 1+ | length | |var_int| | Length of the string |
+------------+-------------+------------+----------------------------------+
| ? | string | char[] | The string itself (can be empty) |
+------------+-------------+------------+----------------------------------+
Variable length list of integers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
n integers can be stored using n+1 :ref:`variable length integers <varint>`
where the first var_int equals n.
+------------+-------------+-----------+----------------------------+
| Field Size | Description | Data type | Comments |
+============+=============+===========+============================+
| 1+ | count | |var_int| | Number of var_ints below |
+------------+-------------+-----------+----------------------------+
| 1+ | | var_int | The first value stored |
+------------+-------------+-----------+----------------------------+
| 1+ | | var_int | The second value stored... |
+------------+-------------+-----------+----------------------------+
| 1+ | | var_int | etc... |
+------------+-------------+-----------+----------------------------+
.. |var_int| replace:: :ref:`var_int <varint>`
Network address
^^^^^^^^^^^^^^^
When a network address is needed somewhere, this structure is used. Network
addresses are not prefixed with a timestamp or stream in the version_ message.
.. list-table::
:header-rows: 1
:widths: auto
* - Field Size
- Description
- Data type
- Comments
* - 8
- time
- uint64
- the Time.
* - 4
- stream
- uint32
- Stream number for this node
* - 8
- services
- uint64_t
- same service(s) listed in version_
* - 16
- IPv6/4
- char[16]
- IPv6 address. IPv4 addresses are written into the message as a 16 byte
`IPv4-mapped IPv6 address <http://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses>`_
(12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of
the IPv4 address).
* - 2
- port
- uint16_t
- port number
Inventory Vectors
^^^^^^^^^^^^^^^^^
Inventory vectors are used for notifying other nodes about objects they have or
data which is being requested. Two rounds of SHA-512 are used, resulting in a
64 byte hash. Only the first 32 bytes are used; the later 32 bytes are ignored.
Inventory vectors consist of the following data format:
+------------+-------------+-----------+--------------------+
| Field Size | Description | Data type | Comments |
+============+=============+===========+====================+
| 32 | hash | char[32] | Hash of the object |
+------------+-------------+-----------+--------------------+
Encrypted payload
^^^^^^^^^^^^^^^^^
Bitmessage uses `ECIES <https://en.wikipedia.org/wiki/Integrated_Encryption_Scheme>`_ to encrypt its messages. For more information see :doc:`encryption`
.. include:: encrypted_payload.rst
Unencrypted Message Data
^^^^^^^^^^^^^^^^^^^^^^^^
.. list-table::
:header-rows: 1
:widths: auto
* - Field Size
- Description
- Data type
- Comments
* - 1+
- msg_version
- var_int
- Message format version. **This field is not included after the
protocol v3 upgrade period**.
* - 1+
- address_version
- var_int
- Sender's address version number. This is needed in order to calculate
the sender's address to show in the UI, and also to allow for forwards
compatible changes to the public-key data included below.
* - 1+
- stream
- var_int
- Sender's stream number
* - 4
- behavior bitfield
- uint32_t
- A bitfield of optional behaviors and features that can be expected from
the node with this pubkey included in this msg message (the sender's
pubkey).
* - 64
- public signing key
- uchar[]
- The ECC public key used for signing (uncompressed format;
normally prepended with \x04)
* - 64
- public encryption key
- uchar[]
- The ECC public key used for encryption (uncompressed format;
normally prepended with \x04)
* - 1+
- nonce_trials_per_byte
- var_int
- Used to calculate the difficulty target of messages accepted by this
node. The higher this value, the more difficult the Proof of Work must
be before this individual will accept the message. This number is the
average number of nonce trials a node will have to perform to meet the
Proof of Work requirement. 1000 is the network minimum so any lower
values will be automatically raised to 1000. **This field is new and is
only included when the address_version >= 3**.
* - 1+
- extra_bytes
- var_int
- Used to calculate the difficulty target of messages accepted by this
node. The higher this value, the more difficult the Proof of Work must
be before this individual will accept the message. This number is added
to the data length to make sending small messages more difficult.
1000 is the network minimum so any lower values will be automatically
raised to 1000. **This field is new and is only included when the
address_version >= 3**.
* - 20
- destination ripe
- uchar[]
- The ripe hash of the public key of the receiver of the message
* - 1+
- encoding
- var_int
- :ref:`Message Encoding <msg-encodings>` type
* - 1+
- message_length
- var_int
- Message Length
* - message_length
- message
- uchar[]
- The message.
* - 1+
- ack_length
- var_int
- Length of the acknowledgement data
* - ack_length
- ack_data
- uchar[]
- The acknowledgement data to be transmitted. This takes the form of a
Bitmessage protocol message, like another msg message. The POW therein
must already be completed.
* - 1+
- sig_length
- var_int
- Length of the signature
* - sig_length
- signature
- uchar[]
- The ECDSA signature which covers the object header starting with the
time, appended with the data described in this table down to the
ack_data.
.. _msg-encodings:
Message Encodings
"""""""""""""""""
.. list-table::
:header-rows: 1
:widths: auto
* - Value
- Name
- Description
* - 0
- IGNORE
- Any data with this number may be ignored. The sending node might simply
be sharing its public key with you.
* - 1
- TRIVIAL
- UTF-8. No 'Subject' or 'Body' sections. Useful for simple strings
of data, like URIs or magnet links.
* - 2
- SIMPLE
- UTF-8. Uses 'Subject' and 'Body' sections. No MIME is used.
::
messageToTransmit = 'Subject:' + subject + '\n' + 'Body:' + message
* - 3
- EXTENDED
- See :doc:`extended_encoding`
Further values for the message encodings can be decided upon by the community.
Any MIME or MIME-like encoding format, should they be used, should make use of
Bitmessage's 8-bit bytes.
.. _behavior-bitfield:
Pubkey bitfield features
""""""""""""""""""""""""
.. list-table::
:header-rows: 1
:widths: auto
* - Bit
- Name
- Description
* - 0
- undefined
- The most significant bit at the beginning of the structure. Undefined
* - 1
- undefined
- The next most significant bit. Undefined
* - ...
- ...
- ...
* - 27
- onion_router
- (**Proposal**) Node can be used to onion-route messages. In theory any
node can onion route, but since it requires more resources, they may have
the functionality disabled. This field will be used to indicate that the
node is willing to do this.
* - 28
- forward_secrecy
- (**Proposal**) Receiving node supports a forward secrecy encryption
extension. The exact design is pending.
* - 29
- chat
- (**Proposal**) Address if for chatting rather than messaging.
* - 30
- include_destination
- (**Proposal**) Receiving node expects that the RIPE hash encoded in their
address preceedes the encrypted message data of msg messages bound for
them.
.. note:: since hardly anyone implements this, this will be redesigned as
`simple recipient verification <https://github.com/Bitmessage/PyBitmessage/pull/808#issuecomment-170189856>`_
* - 31
- does_ack
- If true, the receiving node does send acknowledgements (rather than
dropping them).
.. _msg-types:
Message types
-------------
Undefined messages received on the wire must be ignored.
version
^^^^^^^
When a node creates an outgoing connection, it will immediately advertise its
version. The remote node will respond with its version. No futher communication
is possible until both peers have exchanged their version.
.. list-table:: Payload
:header-rows: 1
:widths: auto
* - Field Size
- Description
- Data type
- Comments
* - 4
- version
- int32_t
- Identifies protocol version being used by the node. Should equal 3.
Nodes should disconnect if the remote node's version is lower but
continue with the connection if it is higher.
* - 8
- services
- uint64_t
- bitfield of features to be enabled for this connection
* - 8
- timestamp
- int64_t
- standard UNIX timestamp in seconds
* - 26
- addr_recv
- net_addr
- The network address of the node receiving this message (not including the
time or stream number)
* - 26
- addr_from
- net_addr
- The network address of the node emitting this message (not including the
time or stream number and the ip itself is ignored by the receiver)
* - 8
- nonce
- uint64_t
- Random nonce used to detect connections to self.
* - 1+
- user_agent
- var_str
- :doc:`useragent` (0x00 if string is 0 bytes long). Sending nodes must not
include a user_agent longer than 5000 bytes.
* - 1+
- stream_numbers
- var_int_list
- The stream numbers that the emitting node is interested in. Sending nodes
must not include more than 160000 stream numbers.
A "verack" packet shall be sent if the version packet was accepted. Once you
have sent and received a verack messages with the remote node, send an addr
message advertising up to 1000 peers of which you are aware, and one or more
inv messages advertising all of the valid objects of which you are aware.
.. list-table:: The following services are currently assigned
:header-rows: 1
:widths: auto
* - Value
- Name
- Description
* - 1
- NODE_NETWORK
- This is a normal network node.
* - 2
- NODE_SSL
- This node supports SSL/TLS in the current connect (python < 2.7.9 only
supports a SSL client, so in that case it would only have this on when
the connection is a client).
* - 3
- NODE_POW
- (**Proposal**) This node may do PoW on behalf of some its peers (PoW
offloading/delegating), but it doesn't have to. Clients may have to meet
additional requirements (e.g. TLS authentication)
* - 4
- NODE_DANDELION
- Node supports `dandelion <https://github.com/gfanti/bips/blob/master/bip-dandelion.mediawiki>`_
verack
^^^^^^
The *verack* message is sent in reply to *version*. This message consists of
only a :ref:`message header <Message structure>` with the command string
"verack". The TCP timeout starts out at 20 seconds; after verack messages are
exchanged, the timeout is raised to 10 minutes.
If both sides announce that they support SSL, they **must** perform an SSL
handshake immediately after they both send and receive verack. During this SSL
handshake, the TCP client acts as an SSL client, and the TCP server acts as an
SSL server. The current implementation (v0.5.4 or later) requires the
AECDH-AES256-SHA cipher over TLSv1 protocol, and prefers the secp256k1 curve
(but other curves may be accepted, depending on the version of python and
OpenSSL used).
addr
^^^^
Provide information on known nodes of the network. Non-advertised nodes should
be forgotten after typically 3 hours
Payload:
+------------+-------------+-----------+---------------------------------------+
| Field Size | Description | Data type | Comments |
+============+=============+===========+=======================================+
| 1+ | count | |var_int| | Number of address entries (max: 1000) |
+------------+-------------+-----------+---------------------------------------+
| 38 | addr_list | net_addr | Address of other nodes on the network.|
+------------+-------------+-----------+---------------------------------------+
inv
^^^
Allows a node to advertise its knowledge of one or more objects. Payload
(maximum payload length: 50000 items):
+------------+-------------+------------+-----------------------------+
| Field Size | Description | Data type | Comments |
+============+=============+============+=============================+
| ? | count | |var_int| | Number of inventory entries |
+------------+-------------+------------+-----------------------------+
| 32x? | inventory | inv_vect[] | Inventory vectors |
+------------+-------------+------------+-----------------------------+
getdata
^^^^^^^
getdata is used in response to an inv message to retrieve the content of a
specific object after filtering known elements.
Payload (maximum payload length: 50000 entries):
+------------+-------------+------------+-----------------------------+
| Field Size | Description | Data type | Comments |
+============+=============+============+=============================+
| ? | count | |var_int| | Number of inventory entries |
+------------+-------------+------------+-----------------------------+
| 32x? | inventory | inv_vect[] | Inventory vectors |
+------------+-------------+------------+-----------------------------+
error
^^^^^
.. note:: New in version 3
This message may be silently ignored (and therefor handled like any other
"unknown" message).
The message is intended to inform the other node about protocol errors and
can be used for debugging and improving code.
.. list-table::
:header-rows: 1
:widths: auto
* - Field Size
- Description
- Data type
- Comments
* - 1+
- fatal
- |var_int|
- This qualifies the error. If set to 0, than its just a "warning".
You can expect, everything still worked fine. If set to 1, than
it's an error, so you may expect, something was going wrong
(e.g. an object got lost). If set to 2, it's a fatal error. The node
will drop the line for that error and maybe ban you for some time.
* - 1+
- ban time
- var_int
- If the error is fatal, you can specify the ban time in seconds, here.
You inform the other node, that you will not accept further connections
for this number of seconds. For non fatal errors this field has
no meaning and should be zero.
* - 1+
- inventory vector
- var_str
- If the error is related to an object, this Variable length string
contains the inventory vector of that object. If the error is not
related to an object, this string is empty.
* - 1+
- error text
- var_str
- A human readable string in English, which describes the error.
object
^^^^^^
An object is a message which is shared throughout a stream. It is the only
message which propagates; all others are only between two nodes. Objects have a
type, like 'msg', or 'broadcast'. To be a valid object, the
:doc:`pow` must be done. The maximum allowable length of an object
(not to be confused with the ``objectPayload``) is |2^18| bytes.
.. |2^18| replace:: 2\ :sup:`18`\
.. list-table:: Message structure
:header-rows: 1
:widths: auto
* - Field Size
- Description
- Data type
- Comments
* - 8
- nonce
- uint64_t
- Random nonce used for the :doc:`pow`
* - 8
- expiresTime
- uint64_t
- The "end of life" time of this object (be aware, in version 2 of the
protocol this was the generation time). Objects shall be shared with
peers until its end-of-life time has been reached. The node should store
the inventory vector of that object for some extra period of time to
avoid reloading it from another node with a small time delay. The time
may be no further than 28 days + 3 hours in the future.
* - 4
- objectType
- uint32_t
- Four values are currently defined: 0-"getpubkey", 1-"pubkey", 2-"msg",
3-"broadcast". All other values are reserved. Nodes should relay objects
even if they use an undefined object type.
* - 1+
- version
- var_int
- The object's version. Note that msg objects won't contain a version
until Sun, 16 Nov 2014 22:00:00 GMT.
* - 1+
- stream number
- var_int
- The stream number in which this object may propagate
* - ?
- objectPayload
- uchar[]
- This field varies depending on the object type; see below.
Unsupported messages
^^^^^^^^^^^^^^^^^^^^
If a node receives an unknown message it **must** silently ignore it. This is
for further extensions of the protocol with other messages. Nodes that don't
understand such a new message type shall be able to work correct with the
message types they understand.
Maybe some version 2 nodes did already implement it that way, but in version 3
it is **part of the protocol specification**, that a node **must**
silently ignore unsupported messages.
Object types
------------
Here are the payloads for various object types.
getpubkey
^^^^^^^^^
When a node has the hash of a public key (from an address) but not the public
key itself, it must send out a request for the public key.
.. list-table::
:header-rows: 1
:widths: auto
* - Field Size
- Description
- Data type
- Comments
* - 20
- ripe
- uchar[]
- The ripemd hash of the public key. This field is only included when the
address version is <= 3.
* - 32
- tag
- uchar[]
- The tag derived from the address version, stream number, and ripe. This
field is only included when the address version is >= 4.
pubkey
^^^^^^
A version 2 pubkey. This is still in use and supported by current clients but
*new* v2 addresses are not generated by clients.
.. list-table::
:header-rows: 1
:widths: auto
* - Field Size
- Description
- Data type
- Comments
* - 4
- |behavior_bitfield|
- uint32_t
- A bitfield of optional behaviors and features that can be expected from
the node receiving the message.
* - 64
- public signing key
- uchar[]
- The ECC public key used for signing (uncompressed format;
normally prepended with \x04 )
* - 64
- public encryption key
- uchar[]
- The ECC public key used for encryption (uncompressed format;
normally prepended with \x04 )
.. list-table:: A version 3 pubkey
:header-rows: 1
:widths: auto
* - Field Size
- Description
- Data type
- Comments
* - 4
- |behavior_bitfield|
- uint32_t
- A bitfield of optional behaviors and features that can be expected from
the node receiving the message.
* - 64
- public signing key
- uchar[]
- The ECC public key used for signing (uncompressed format;
normally prepended with \x04 )
* - 64
- public encryption key
- uchar[]
- The ECC public key used for encryption (uncompressed format;
normally prepended with \x04 )
* - 1+
- nonce_trials_per_byte
- var_int
- Used to calculate the difficulty target of messages accepted by this
node. The higher this value, the more difficult the Proof of Work must
be before this individual will accept the message. This number is the
average number of nonce trials a node will have to perform to meet the
Proof of Work requirement. 1000 is the network minimum so any lower
values will be automatically raised to 1000.
* - 1+
- extra_bytes
- var_int
- Used to calculate the difficulty target of messages accepted by this
node. The higher this value, the more difficult the Proof of Work must
be before this individual will accept the message. This number is added
to the data length to make sending small messages more difficult.
1000 is the network minimum so any lower values will be automatically
raised to 1000.
* - 1+
- sig_length
- var_int
- Length of the signature
* - sig_length
- signature
- uchar[]
- The ECDSA signature which, as of protocol v3, covers the object
header starting with the time, appended with the data described in this
table down to the extra_bytes.
.. list-table:: A version 4 pubkey
:header-rows: 1
:widths: auto
* - Field Size
- Description
- Data type
- Comments
* - 32
- tag
- uchar[]
- The tag, made up of bytes 32-64 of the double hash of the address data
(see example python code below)
* - ?
- encrypted
- uchar[]
- Encrypted pubkey data.
When version 4 pubkeys are created, most of the data in the pubkey is encrypted.
This is done in such a way that only someone who has the Bitmessage address
which corresponds to a pubkey can decrypt and use that pubkey. This prevents
people from gathering pubkeys sent around the network and using the data from
them to create messages to be used in spam or in flooding attacks.
In order to encrypt the pubkey data, a double SHA-512 hash is calculated from
the address version number, stream number, and ripe hash of the Bitmessage
address that the pubkey corresponds to. The first 32 bytes of this hash are used
to create a public and private key pair with which to encrypt and decrypt the
pubkey data, using the same algorithm as message encryption
(see :doc:`encryption`). The remaining 32 bytes of this hash are added to the
unencrypted part of the pubkey and used as a tag, as above. This allows nodes to
determine which pubkey to decrypt when they wish to send a message.
In PyBitmessage, the double hash of the address data is calculated using the
python code below:
.. code-block:: python
doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(
encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + hash
).digest()).digest()
.. list-table:: Encrypted data in version 4 pubkeys:
:header-rows: 1
:widths: auto
* - Field Size
- Description
- Data type
- Comments
* - 4
- |behavior_bitfield|
- uint32_t
- A bitfield of optional behaviors and features that can be expected from
the node receiving the message.
* - 64
- public signing key
- uchar[]
- The ECC public key used for signing (uncompressed format;
normally prepended with \x04 )
* - 64
- public encryption key
- uchar[]
- The ECC public key used for encryption (uncompressed format;
normally prepended with \x04 )
* - 1+
- nonce_trials_per_byte
- var_int
- Used to calculate the difficulty target of messages accepted by this
node. The higher this value, the more difficult the Proof of Work must
be before this individual will accept the message. This number is the
average number of nonce trials a node will have to perform to meet the
Proof of Work requirement. 1000 is the network minimum so any lower
values will be automatically raised to 1000.
* - 1+
- extra_bytes
- var_int
- Used to calculate the difficulty target of messages accepted by this
node. The higher this value, the more difficult the Proof of Work must
be before this individual will accept the message. This number is added
to the data length to make sending small messages more difficult.
1000 is the network minimum so any lower values will be automatically
raised to 1000.
* - 1+
- sig_length
- var_int
- Length of the signature
* - sig_length
- signature
- uchar[]
- The ECDSA signature which covers everything from the object header
starting with the time, then appended with the decrypted data down to
the extra_bytes. This was changed in protocol v3.
msg
^^^
Used for person-to-person messages. Note that msg objects won't contain a
version in the object header until Sun, 16 Nov 2014 22:00:00 GMT.
.. list-table::
:header-rows: 1
:widths: auto
* - Field Size
- Description
- Data type
- Comments
* - ?
- encrypted
- uchar[]
- Encrypted data. See `Encrypted payload`_.
See also `Unencrypted Message Data`_
broadcast
^^^^^^^^^
Users who are subscribed to the sending address will see the message appear in
their inbox. Broadcasts are version 4 or 5.
Pubkey objects and v5 broadcast objects are encrypted the same way: The data
encoded in the sender's Bitmessage address is hashed twice. The first 32 bytes
of the resulting hash constitutes the "private" encryption key and the last
32 bytes constitute a **tag** so that anyone listening can easily decide if
this particular message is interesting. The sender calculates the public key
from the private key and then encrypts the object with this public key. Thus
anyone who knows the Bitmessage address of the sender of a broadcast or pubkey
object can decrypt it.
The version of broadcast objects was previously 2 or 3 but was changed to 4 or
5 for protocol v3. Having a broadcast version of 5 indicates that a tag is used
which, in turn, is used when the sender's address version is >=4.
.. list-table::
:header-rows: 1
:widths: auto
* - Field Size
- Description
- Data type
- Comments
* - 32
- tag
- uchar[]
- The tag. This field is new and only included when the broadcast version
is >= 5. Changed in protocol v3
* - ?
- encrypted
- uchar[]
- Encrypted broadcast data. The keys are derived as described in the
paragraph above. See Encrypted payload for details about the encryption
algorithm itself.
Unencrypted data format:
.. list-table::
:header-rows: 1
:widths: auto
* - Field Size
- Description
- Data type
- Comments
* - 1+
- broadcast version
- var_int
- The version number of this broadcast protocol message which is equal
to 2 or 3. This is included here so that it can be signed. This is
no longer included in protocol v3
* - 1+
- address version
- var_int
- The sender's address version
* - 1+
- stream number
- var_int
- The sender's stream number
* - 4
- |behavior_bitfield|
- uint32_t
- A bitfield of optional behaviors and features that can be expected from
the owner of this pubkey.
* - 64
- public signing key
- uchar[]
- The ECC public key used for signing (uncompressed format;
normally prepended with \x04)
* - 64
- public encryption key
- uchar[]
- The ECC public key used for encryption (uncompressed format;
normally prepended with \x04)
* - 1+
- nonce_trials_per_byte
- var_int
- Used to calculate the difficulty target of messages accepted by this
node. The higher this value, the more difficult the Proof of Work must
be before this individual will accept the message. This number is the
average number of nonce trials a node will have to perform to meet the
Proof of Work requirement. 1000 is the network minimum so any lower
values will be automatically raised to 1000. This field is new and is
only included when the address_version >= 3.
* - 1+
- extra_bytes
- var_int
- Used to calculate the difficulty target of messages accepted by this
node. The higher this value, the more difficult the Proof of Work must
be before this individual will accept the message. This number is added
to the data length to make sending small messages more difficult.
1000 is the network minimum so any lower values will be automatically
raised to 1000. This field is new and is only included when the
address_version >= 3.
* - 1+
- encoding
- var_int
- The encoding type of the message
* - 1+
- messageLength
- var_int
- The message length in bytes
* - messageLength
- message
- uchar[]
- The message
* - 1+
- sig_length
- var_int
- Length of the signature
* - sig_length
- signature
- uchar[]
- The signature which did cover the unencrypted data from the broadcast
version down through the message. In protocol v3, it covers the
unencrypted object header starting with the time, all appended with
the decrypted data.
.. |behavior_bitfield| replace:: :ref:`behavior bitfield <behavior-bitfield>`

View File

@ -1,3 +1,5 @@
m2r mistune<=0.8.4
m2r<=0.2.1
sphinx_rtd_theme
sphinxcontrib-apidoc sphinxcontrib-apidoc
docutils<=0.17.1 docutils<=0.17.1

53
docs/useragent.rst Normal file
View File

@ -0,0 +1,53 @@
User Agent
==========
Bitmessage user agents are a modified browser user agent with more structure
to aid parsers and provide some coherence. The user agent strings are arranged
in a stack with the most underlying software listed first.
Basic format::
/Name:Version/Name:Version/.../
Example::
/PyBitmessage:0.2.2/Corporate Mail System:0.8/
/Surdo:5.64/surdo-qt:0.4/
The version numbers are not defined to any strict format, although this guide
recommends:
* Version numbers in the form of Major.Minor.Revision (2.6.41)
* Repository builds using a date in the format of YYYYMMDD (20110128)
For git repository builds, implementations are free to use the git commitish.
However the issue lies in that it is not immediately obvious without the
repository which version preceeds another. For this reason, we lightly
recommend dates in the format specified above, although this is by no means
a requirement.
Optional ``-r1``, ``-r2``, ... can be appended to user agent version numbers.
This is another light recommendation, but not a requirement. Implementations
are free to specify version numbers in whatever format needed insofar as it
does not include ``(``, ``)``, ``:`` or ``/`` to interfere with the user agent
syntax.
An optional comments field after the version number is also allowed. Comments
should be delimited by parenthesis ``(...)``. The contents of comments is
entirely implementation defined although this document recommends the use of
semi-colons ``;`` as a delimiter between pieces of information.
Example::
/cBitmessage:0.2(iPad; U; CPU OS 3_2_1)/AndroidBuild:0.8/
Reserved symbols are therefore: ``/ : ( )``
They should not be misused beyond what is specified in this section.
``/``
separates the code-stack
``:``
specifies the implementation version of the particular stack
``( and )``
delimits a comment which optionally separates data using ``;``

View File

@ -1,5 +1,11 @@
kivy-garden.qrcode kivy-garden.qrcode
-e git+https://github.com/kivymd/KivyMD#egg=kivymd kivymd==1.0.2
kivy==2.1.0
opencv-python opencv-python
pyzbar pyzbar
telenium git+https://github.com/tito/telenium@9b54ff1#egg=telenium
Pillow==9.4.0
jaraco.collections==3.8.0
jaraco.classes==3.2.3
pytz==2022.7.1
pydantic==1.10.6

View File

@ -0,0 +1,81 @@
version: 1
script:
# Remove any previous build
- rm -rf AppDir | true
- python setup.py install --prefix=/usr --root=AppDir
AppDir:
path: ./AppDir
app_info:
id: pybitmessage
name: PyBitmessage
icon: pybitmessage
version: !ENV ${APP_VERSION}
# Set the python executable as entry point
exec: usr/bin/python
# Set the application main script path as argument.
# Use '$@' to forward CLI parameters
exec_args: "$APPDIR/usr/bin/pybitmessage $@"
after_runtime:
- sed -i "s|GTK_.*||g" AppDir/AppRun.env
- cp packages/AppImage/qt.conf AppDir/usr/bin/
apt:
arch: !ENV '${ARCH}'
sources:
- sourceline: !ENV '${SOURCELINE}'
key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3b4fe6acc0b21f32'
include:
- python-defusedxml
- python-jsonrpclib
- python-msgpack
- python-qrcode
- python-qt4
- python-setuptools
- python-sip
- python-six
- python-xdg
- sni-qt
exclude:
- libdb5.3
- libdbus-1-3
- libfontconfig1
- libfreetype6
- libglib2.0-0
- libice6
- libmng2
- libncursesw5
- libqt4-declarative
- libqt4-designer
- libqt4-help
- libqt4-script
- libqt4-scripttools
- libqt4-sql
- libqt4-test
- libqt4-xmlpatterns
- libqtassistantclient4
- libsm6
- libsystemd0
- libreadline7
files:
exclude:
- usr/lib/x86_64-linux-gnu/gconv
- usr/share/man
- usr/share/doc
runtime:
arch: [ !ENV '${RUNTIME}' ]
env:
# Set python home
# See https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHOME
PYTHONHOME: '${APPDIR}/usr'
# Path to the site-packages dir or other modules dirs
# See https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH
PYTHONPATH: '${APPDIR}/usr/lib/python2.7/site-packages'
AppImage:
arch: !ENV '${APPIMAGE_ARCH}'

View File

@ -15,9 +15,12 @@ ingredients:
- python-setuptools - python-setuptools
- python-sip - python-sip
- python-six - python-six
- python-xdg
- sni-qt - sni-qt
- xkb-data - xkb-data
exclude: exclude:
- libdb5.3
- libglib2.0-0
- libmng2 - libmng2
- libncursesw5 - libncursesw5
- libqt4-declarative - libqt4-declarative
@ -26,6 +29,7 @@ ingredients:
- libqt4-script - libqt4-script
- libqt4-scripttools - libqt4-scripttools
- libqt4-sql - libqt4-sql
- libqt4-test
- libqt4-xmlpatterns - libqt4-xmlpatterns
- libqtassistantclient4 - libqtassistantclient4
- libreadline7 - libreadline7
@ -33,5 +37,6 @@ ingredients:
- ../deb_dist/pybitmessage_*_amd64.deb - ../deb_dist/pybitmessage_*_amd64.deb
script: script:
- rm -rf usr/share/glib-2.0/schemas
- cp usr/share/icons/hicolor/scalable/apps/pybitmessage.svg . - cp usr/share/icons/hicolor/scalable/apps/pybitmessage.svg .
- mv usr/bin/python2.7 usr/bin/python2 - mv usr/bin/python2.7 usr/bin/python2

View File

@ -0,0 +1,2 @@
[Paths]
Prefix = ../lib/x86_64-linux-gnu/qt4

View File

@ -0,0 +1,357 @@
[app]
# (str) Title of your application
title = mockone
# (str) Package name
package.name = mock
# (str) Package domain (needed for android/ios packaging)
package.domain = org.mock
# (str) Source code where the main.py live
source.dir = ../../src
# (list) Source files to include (let empty to include all the files)
source.include_exts = py,png,jpg,kv,atlas,tflite,sql
# (list) List of inclusions using pattern matching
#source.include_patterns = assets/*,images/*.png
# (list) Source files to exclude (let empty to not exclude anything)
#source.exclude_exts = spec
# (list) List of directory to exclude (let empty to not exclude anything)
#source.exclude_dirs = tests, bin, venv
# (list) List of exclusions using pattern matching
#source.exclude_patterns = license,images/*/*.jpg
# (str) Application versioning (method 1)
version = 0.1
# (str) Application versioning (method 2)
# version.regex = __version__ = ['"](.*)['"]
# version.filename = %(source.dir)s/main.py
# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy
# (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes
# requirements.source.kivy = ../../kivy
# (str) Presplash of the application
#presplash.filename = %(source.dir)s/data/presplash.png
# (str) Icon of the application
#icon.filename = %(source.dir)s/data/icon.png
# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
orientation = portrait
# (list) List of service to declare
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY
#
# OSX Specific
#
#
# author = © Copyright Info
# change the major version of python used by the app
osx.python_version = 3
# Kivy version to use
osx.kivy_version = 1.9.1
#
# Android specific
#
# (bool) Indicate if the application should be fullscreen or not
fullscreen = 0
# (string) Presplash background color (for android toolchain)
# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
# olive, purple, silver, teal.
#android.presplash_color = #FFFFFF
# (string) Presplash animation using Lottie format.
# see https://lottiefiles.com/ for examples and https://airbnb.design/lottie/
# for general documentation.
# Lottie files can be created using various tools, like Adobe After Effect or Synfig.
#android.presplash_lottie = "path/to/lottie/file.json"
# (list) Permissions
#android.permissions = INTERNET
# (int) Android API to use (targetSdkVersion AND compileSdkVersion)
# note: when changing, Dockerfile also needs to be changed to install corresponding build tools
android.api = 28
# (int) Minimum API required. You will need to set the android.ndk_api to be as low as this value.
android.minapi = 21
# (str) Android NDK version to use
android.ndk = 25b
# (int) Android NDK API to use (optional). This is the minimum API your app will support.
android.ndk_api = 21
# (bool) Use --private data storage (True) or --dir public storage (False)
android.private_storage = True
# (str) Android NDK directory (if empty, it will be automatically downloaded.)
android.ndk_path = /opt/android/android-ndk
# (str) Android SDK directory (if empty, it will be automatically downloaded.)
android.sdk_path = /opt/android/android-sdk
# (str) ANT directory (if empty, it will be automatically downloaded.)
android.ant_path = /opt/android/apache-ant
# (bool) If True, then skip trying to update the Android sdk
# This can be useful to avoid excess Internet downloads or save time
# when an update is due and you just want to test/build your package
# android.skip_update = False
# (bool) If True, then automatically accept SDK license
# agreements. This is intended for automation only. If set to False,
# the default, you will be shown the license when first running
# buildozer.
# android.accept_sdk_license = False
# (str) Android entry point, default is ok for Kivy-based app
#android.entrypoint = org.renpy.android.PythonActivity
# (str) Android app theme, default is ok for Kivy-based app
# android.apptheme = "@android:style/Theme.NoTitleBar"
# (list) Pattern to whitelist for the whole project
#android.whitelist =
# (str) Path to a custom whitelist file
#android.whitelist_src =
# (str) Path to a custom blacklist file
#android.blacklist_src =
# (list) List of Java .jar files to add to the libs so that pyjnius can access
# their classes. Don't add jars that you do not need, since extra jars can slow
# down the build process. Allows.build wildcards matching, for example:
# OUYA-ODK/libs/*.jar
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
#android.add_src =
# (list) Android AAR archives to add
#android.add_aars =
# (list) Gradle dependencies to add
#android.gradle_dependencies =
# (list) add java compile options
# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option
# see https://developer.android.com/studio/write/java8-support for further information
#android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"
# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
# please enclose in double quotes
# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }"
#android.add_gradle_repositories =
#android.gradle_dependencies = "org.tensorflow:tensorflow-lite:+","org.tensorflow:tensorflow-lite-support:0.0.0-nightly"
# (list) packaging options to add
# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
# can be necessary to solve conflicts in gradle_dependencies
# please enclose in double quotes
# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'"
#android.add_packaging_options =
# (list) Java classes to add as activities to the manifest.
#android.add_activities = com.example.ExampleActivity
# (str) OUYA Console category. Should be one of GAME or APP
# If you leave this blank, OUYA support will not be enabled
#android.ouya.category = GAME
# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =
# (str) launchMode to set for the main activity
#android.manifest.launch_mode = standard
# (list) Android additional libraries to copy into libs/armeabi
#android.add_libs_armeabi = libs/android/*.so
#android.add_libs_armeabi_v7a = libs/android-v7/*.so
#android.add_libs_arm64_v8a = libs/android-v8/*.so
#android.add_libs_x86 = libs/android-x86/*.so
#android.add_libs_mips = libs/android-mips/*.so
# (bool) Indicate whether the screen should stay on
# Don't forget to add the WAKE_LOCK permission if you set this to True
#android.wakelock = False
# (list) Android application meta-data to set (key=value format)
#android.meta_data =
# (list) Android library project to add (will be added in the
# project.properties automatically.)
#android.library_references =
# (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
#android.uses_library =
# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D
# (bool) Android logcat only display log for activity's pid
#android.logcat_pid_only = False
# (str) Android additional adb arguments
#android.adb_args = -H host.docker.internal
# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1
# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
android.archs = armeabi-v7a, arm64-v8a, x86_64
# (int) overrides automatic versionCode computation (used in build.gradle)
# this is not the same as app version and should only be edited if you know what you're doing
# android.numeric_version = 1
# (bool) enables Android auto backup feature (Android API >=23)
android.allow_backup = True
# (str) XML file for custom backup rules (see official auto backup documentation)
# android.backup_rules =
# (str) If you need to insert variables into your AndroidManifest.xml file,
# you can do so with the manifestPlaceholders property.
# This property takes a map of key-value pairs. (via a string)
# Usage example : android.manifest_placeholders = [myCustomUrl:\"org.kivy.customurl\"]
# android.manifest_placeholders = [:]
#
# Python for android (p4a) specific
#
# (str) python-for-android fork to use, defaults to upstream (kivy)
#p4a.fork = kivy
# (str) python-for-android branch to use, defaults to master
#p4a.branch = master
# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
#p4a.source_dir =
# (str) The directory in which python-for-android should look for your own build recipes (if any)
#p4a.local_recipes =
# (str) Filename to the hook for p4a
#p4a.hook =
# (str) Bootstrap to use for android builds
# p4a.bootstrap = sdl2
# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
#p4a.port =
# Control passing the --use-setup-py vs --ignore-setup-py to p4a
# "in the future" --use-setup-py is going to be the default behaviour in p4a, right now it is not
# Setting this to false will pass --ignore-setup-py, true will pass --use-setup-py
# NOTE: this is general setuptools integration, having pyproject.toml is enough, no need to generate
# setup.py if you're using Poetry, but you need to add "toml" to source.include_exts.
#p4a.setup_py = false
#
# iOS specific
#
# (str) Path to a custom kivy-ios folder
#ios.kivy_ios_dir = ../kivy-ios
# Alternately, specify the URL and branch of a git checkout:
ios.kivy_ios_url = https://github.com/kivy/kivy-ios
ios.kivy_ios_branch = master
# Another platform dependency: ios-deploy
# Uncomment to use a custom checkout
#ios.ios_deploy_dir = ../ios_deploy
# Or specify URL and branch
ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
ios.ios_deploy_branch = 1.10.0
# (bool) Whether or not to sign the code
ios.codesign.allowed = false
# (str) Name of the certificate to use for signing the debug version
# Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"
# (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s
[buildozer]
# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
log_level = 2
# (int) Display warning if buildozer is run as root (0 = False, 1 = True)
warn_on_root = 1
# (str) Path to build artifact storage, absolute or relative to spec file
# build_dir = ./.buildozer
# (str) Path to build output (i.e. .apk, .ipa) storage
# bin_dir = ./bin
# -----------------------------------------------------------------------------
# List as sections
#
# You can define all the "list" as [section:key].
# Each line will be considered as a option to the list.
# Let's take [app] / source.exclude_patterns.
# Instead of doing:
#
#[app]
#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
#
# This can be translated into:
#
#[app:source.exclude_patterns]
#license
#data/audio/*.wav
#data/images/original/*
#
# -----------------------------------------------------------------------------
# Profiles
#
# You can extend section / key with a profile
# For example, you want to deploy a demo version of your application without
# HD content. You could first change the title to add "(demo)" in the name
# and extend the excluded directories to remove the HD content.
#
#[app@demo]
#title = My Application (demo)
#
#[app:source.exclude_patterns@demo]
#images/hd/*
#
# Then, invoke the command line with the "demo" profile:
#
#buildozer --profile demo android debug

View File

@ -1,62 +1,45 @@
FROM ubuntu:bionic AS base FROM ubuntu:bionic AS base
ENV DEBIAN_FRONTEND noninteractive ENV DEBIAN_FRONTEND noninteractive
ENV TRAVIS_SKIP_APT_UPDATE 1
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
RUN apt-get update RUN apt-get update
# Common apt packages
RUN apt-get install -yq --no-install-suggests --no-install-recommends \ RUN apt-get install -yq --no-install-suggests --no-install-recommends \
software-properties-common software-properties-common build-essential libcap-dev libssl-dev \
python-all-dev python-setuptools wget xvfb
RUN dpkg --add-architecture i386 ###############################################################################
RUN add-apt-repository ppa:deadsnakes/ppa FROM base AS appimage
RUN apt-get -y install sudo
RUN apt-get install -yq --no-install-suggests --no-install-recommends \ RUN apt-get install -yq --no-install-suggests --no-install-recommends \
# travis xenial bionic debhelper dh-apparmor dh-python python-stdeb fakeroot
python-setuptools libssl-dev python-prctl \
python-dev python-virtualenv python-pip virtualenv \
# dpkg
python-minimal python-all python openssl libssl-dev \
dh-apparmor debhelper dh-python python-msgpack python-qt4 git python-stdeb \
python-all-dev python-crypto python-psutil \
fakeroot python-pytest python3-wheel \
libglib2.0-dev \
# Code quality
pylint python-pycodestyle python3-pycodestyle pycodestyle python-flake8 \
python3-flake8 flake8 python-pyflakes python3-pyflakes pyflakes pyflakes3 \
curl \
# Wine
python python-pip wget wine-stable winetricks mingw-w64 wine32 wine64 xvfb \
# Buildbot
python3-dev libffi-dev python3-setuptools \
python3-pip \
# python 3.7
python3.7 python3.7-dev \
# .travis.yml
build-essential libcap-dev tor \
language-pack-en
COPY . /home/builder/src
# cleanup WORKDIR /home/builder/src
RUN rm -rf /var/lib/apt/lists/*
##################################################################################################### CMD python setup.py sdist \
&& python setup.py --command-packages=stdeb.command bdist_deb \
&& dpkg-deb -I deb_dist/*.deb \
&& cp deb_dist/*.deb /dist/ \
&& ln -s /dist out \
&& buildscripts/appimage.sh
FROM base AS travis ###############################################################################
# travis2bash FROM base AS tox
RUN wget -O /usr/local/bin/travis2bash.sh https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh
RUN chmod +x /usr/local/bin/travis2bash.sh RUN apt-get install -yq --no-install-suggests --no-install-recommends \
language-pack-en \
libffi-dev python3-dev python3-pip python3.8 python3.8-dev python3.8-venv \
python-msgpack python-pip python-qt4 python-six qt5dxcb-plugin tor
RUN python3.8 -m pip install setuptools wheel
RUN python3.8 -m pip install --upgrade pip tox virtualenv
RUN useradd -m -U builder RUN useradd -m -U builder
RUN echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
# copy sources # copy sources
COPY . /home/builder/src COPY . /home/builder/src
@ -64,14 +47,51 @@ RUN chown -R builder.builder /home/builder/src
USER builder USER builder
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
WORKDIR /home/builder/src WORKDIR /home/builder/src
ENTRYPOINT /usr/local/bin/travis2bash.sh ENTRYPOINT ["tox"]
##################################################################################################### ###############################################################################
FROM base AS snap
RUN apt-get install -yq --no-install-suggests --no-install-recommends snapcraft
COPY . /home/builder/src
WORKDIR /home/builder/src
CMD cd packages && snapcraft && cp *.snap /dist/
###############################################################################
FROM base AS winebuild
RUN dpkg --add-architecture i386
RUN apt-get update
RUN apt-get install -yq --no-install-suggests --no-install-recommends \
mingw-w64 wine-stable winetricks wine32 wine64
COPY . /home/builder/src
WORKDIR /home/builder/src
# xvfb-run -a buildscripts/winbuild.sh
CMD xvfb-run -a i386 buildscripts/winbuild.sh \
&& cp packages/pyinstaller/dist/*.exe /dist/
###############################################################################
FROM base AS buildbot FROM base AS buildbot
# cleanup
RUN rm -rf /var/lib/apt/lists/*
# travis2bash # travis2bash
RUN wget -O /usr/local/bin/travis2bash.sh https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh RUN wget -O /usr/local/bin/travis2bash.sh https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh
RUN chmod +x /usr/local/bin/travis2bash.sh RUN chmod +x /usr/local/bin/travis2bash.sh
@ -87,22 +107,7 @@ USER buildbot
ENTRYPOINT /entrypoint.sh "$BUILDMASTER" "$WORKERNAME" "$WORKERPASS" ENTRYPOINT /entrypoint.sh "$BUILDMASTER" "$WORKERNAME" "$WORKERPASS"
################################################################################################# ###############################################################################
FROM base AS appimage
COPY . /home/builder/src
WORKDIR /home/builder/src
RUN VERSION=$(python setup.py -V) \
&& python setup.py sdist \
&& python setup.py --command-packages=stdeb.command bdist_deb \
&& dpkg-deb -I deb_dist/pybitmessage_${VERSION}-1_amd64.deb
RUN buildscripts/appimage.sh
RUN VERSION=$(python setup.py -V) \
&& out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage --appimage-extract-and-run -t
FROM base AS appandroid FROM base AS appandroid

16
packages/docker/launcher.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/sh
# Setup the environment for docker container
APIUSER=${USER:-api}
APIPASS=${PASSWORD:-$(tr -dc a-zA-Z0-9 < /dev/urandom | head -c32 && echo)}
echo "\napiusername: $APIUSER\napipassword: $APIPASS"
sed -i -e "s|\(apiinterface = \).*|\10\.0\.0\.0|g" \
-e "s|\(apivariant = \).*|\1json|g" \
-e "s|\(apiusername = \).*|\1$APIUSER|g" \
-e "s|\(apipassword = \).*|\1$APIPASS|g" \
-e "s|apinotifypath = .*||g" ${BITMESSAGE_HOME}/keys.dat
# Run
exec pybitmessage "$@"

View File

@ -6,6 +6,8 @@ import time
from PyInstaller.utils.hooks import copy_metadata from PyInstaller.utils.hooks import copy_metadata
DEBUG = False
site_root = os.path.abspath(HOMEPATH) site_root = os.path.abspath(HOMEPATH)
spec_root = os.path.abspath(SPECPATH) spec_root = os.path.abspath(SPECPATH)
arch = 32 if ctypes.sizeof(ctypes.c_voidp) == 4 else 64 arch = 32 if ctypes.sizeof(ctypes.c_voidp) == 4 else 64
@ -24,6 +26,10 @@ snapshot = False
hookspath = os.path.join(spec_root, 'hooks') hookspath = os.path.join(spec_root, 'hooks')
excludes = ['bsddb', 'bz2', 'tcl', 'tk', 'Tkinter', 'tests']
if not DEBUG:
excludes += ['pybitmessage.tests', 'pyelliptic.tests']
a = Analysis( a = Analysis(
[os.path.join(srcPath, 'bitmessagemain.py')], [os.path.join(srcPath, 'bitmessagemain.py')],
datas=[ datas=[
@ -43,11 +49,10 @@ a = Analysis(
'pyinstaller_rthook_pyqt4.py', 'pyinstaller_rthook_pyqt4.py',
'pyinstaller_rthook_plugins.py' 'pyinstaller_rthook_plugins.py'
)], )],
excludes=[ excludes += [
'bsddb', 'bz2', 'PyQt4.QtOpenGL','PyQt4.QtSql',
'PyQt4.QtOpenGL', 'PyQt4.QtOpenGL', 'PyQt4.QtSql',
'PyQt4.QtSvg', 'PyQt4.QtTest', 'PyQt4.QtWebKit', 'PyQt4.QtXml', 'PyQt4.QtSvg', 'PyQt4.QtTest', 'PyQt4.QtWebKit', 'PyQt4.QtXml',
'tcl', 'tk', 'Tkinter', 'win32ui', 'tests'] 'win32ui']
) )
@ -81,9 +86,16 @@ a.datas += [
for file_ in os.listdir(dir_append) if file_.endswith('.ui') for file_ in os.listdir(dir_append) if file_.endswith('.ui')
] ]
sql_dir = os.path.join(srcPath, 'sql')
a.datas += [
(os.path.join('sql', file_), os.path.join(sql_dir, file_), 'DATA')
for file_ in os.listdir(sql_dir) if file_.endswith('.sql')
]
# append the translations directory # append the translations directory
a.datas += addTranslations() a.datas += addTranslations()
a.datas += [('default.ini', os.path.join(srcPath, 'default.ini'), 'DATA')]
excluded_binaries = [ excluded_binaries = [
'QtOpenGL4.dll', 'QtSql4.dll', 'QtSvg4.dll', 'QtTest4.dll', 'QtOpenGL4.dll', 'QtSql4.dll', 'QtSvg4.dll', 'QtTest4.dll',
@ -105,7 +117,6 @@ a.binaries += [
os.path.join(srcPath, 'sslkeys', 'key.pem'), 'BINARY') os.path.join(srcPath, 'sslkeys', 'key.pem'), 'BINARY')
] ]
from version import softwareVersion from version import softwareVersion
today = time.strftime("%Y%m%d") today = time.strftime("%Y%m%d")
@ -123,10 +134,10 @@ exe = EXE(
a.zipfiles, a.zipfiles,
a.datas, a.datas,
name=fname, name=fname,
debug=False, debug=DEBUG,
strip=None, strip=None,
upx=False, upx=False,
console=False, icon=os.path.join(srcPath, 'images', 'can-icon.ico') console=DEBUG, icon=os.path.join(srcPath, 'images', 'can-icon.ico')
) )
coll = COLLECT( coll = COLLECT(

View File

@ -0,0 +1,76 @@
name: pybitmessage
base: core18
grade: devel
confinement: strict
summary: Reference client for Bitmessage, a P2P communications protocol
description: |
Bitmessage is a P2P communication protocol used to send encrypted messages to
another person or to many subscribers. It is decentralized and trustless,
meaning that you need-not inherently trust any entities like root certificate
authorities. It uses strong authentication, which means that the sender of a
message cannot be spoofed. BM aims to hide metadata from passive
eavesdroppers like those ongoing warrantless wiretapping programs. Hence
the sender and receiver of Bitmessages stay anonymous.
adopt-info: pybitmessage
apps:
pybitmessage:
command: desktop-launch pybitmessage
plugs: [desktop, home, network-bind, unity7]
desktop: share/applications/pybitmessage.desktop
passthrough:
autostart: pybitmessage.desktop
parts:
pybitmessage:
# https://wiki.ubuntu.com/snapcraft/parts
after: [qt4conf, desktop-qt4, indicator-qt4, tor]
source: https://github.com/Bitmessage/PyBitmessage.git
override-pull: |
snapcraftctl pull
snapcraftctl set-version $(git describe --tags | cut -d- -f1,3 | tr -d v)
plugin: python
python-version: python2
build-packages:
- libssl-dev
- python-all-dev
python-packages:
- jsonrpclib
- qrcode
- pyxdg
- stem
stage-packages:
- python-qt4
- python-sip
# parse-info: [setup.py]
tor:
source: https://dist.torproject.org/tor-0.4.6.9.tar.gz
source-checksum: sha256/c7e93380988ce20b82aa19c06cdb2f10302b72cfebec7c15b5b96bcfc94ca9a9
source-type: tar
plugin: autotools
build-packages:
- libssl-dev
- zlib1g-dev
after: [libevent]
libevent:
source: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz
source-checksum: sha256/92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb
source-type: tar
plugin: autotools
cleanup:
after: [pybitmessage]
plugin: nil
override-prime: |
set -eux
sed -ie \
's|.*Icon=.*|Icon=${SNAP}/share/icons/hicolor/scalable/apps/pybitmessage.svg|g' \
$SNAPCRAFT_PRIME/share/applications/pybitmessage.desktop
rm -rf $SNAPCRAFT_PRIME/lib/python2.7/site-packages/pip
for DIR in doc man icons themes fonts mime; do
rm -rf $SNAPCRAFT_PRIME/usr/share/$DIR/*
done
LIBS="libQtDeclarative libQtDesigner libQtHelp libQtScript libQtSql \
libQtXmlPatterns libdb-5 libicu libgdk libgio libglib libcairo"
for LIBGLOB in $LIBS; do
rm $SNAPCRAFT_PRIME/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/${LIBGLOB}*
done

View File

@ -1,7 +1,8 @@
coverage coverage
psutil psutil
pycrypto pycryptodome
PyQt5;python_version>="3.7" PyQt5;python_version>="3.7"
mock;python_version<="2.7"
python_prctl;platform_system=="Linux" python_prctl;platform_system=="Linux"
six six
xvfbwrapper;platform_system=="Linux" xvfbwrapper;platform_system=="Linux"

View File

@ -1,5 +1,13 @@
#!/bin/bash #!/bin/sh
docker build --target travis -t pybm -f packages/docker/Dockerfile.bionic . DOCKERFILE=packages/docker/Dockerfile.bionic
docker run pybm
# explicitly mark appimage stage because it builds in any case
docker build --target appimage -t pybm/appimage -f $DOCKERFILE .
if [ $? -gt 0 ]; then
docker build --no-cache --target appimage -t pybm/appimage -f $DOCKERFILE .
fi
docker build --target tox -t pybm/tox -f $DOCKERFILE .
docker run --rm -t pybm/tox

View File

@ -8,6 +8,7 @@ max-line-length = 119
[flake8] [flake8]
max-line-length = 119 max-line-length = 119
exclude = bitmessagecli.py,bitmessagecurses,bitmessageqt,plugins,tests,umsgpack
ignore = E722,F841,W503 ignore = E722,F841,W503
# E722: pylint is preferred for bare-except # E722: pylint is preferred for bare-except
# F841: pylint is preferred for unused-variable # F841: pylint is preferred for unused-variable

View File

@ -13,7 +13,7 @@ from src.version import softwareVersion
EXTRAS_REQUIRE = { EXTRAS_REQUIRE = {
'docs': ['sphinx', 'sphinx_rtd_theme'], 'docs': ['sphinx'],
'gir': ['pygobject'], 'gir': ['pygobject'],
'json': ['jsonrpclib'], 'json': ['jsonrpclib'],
'notify2': ['notify2'], 'notify2': ['notify2'],
@ -72,11 +72,28 @@ if __name__ == "__main__":
'pybitmessage.network', 'pybitmessage.network',
'pybitmessage.plugins', 'pybitmessage.plugins',
'pybitmessage.pyelliptic', 'pybitmessage.pyelliptic',
'pybitmessage.storage', 'pybitmessage.storage'
] ]
package_data = {'': [
'bitmessageqt/*.ui', 'bitmsghash/*.cl', 'sslkeys/*.pem',
'translations/*.ts', 'translations/*.qm', 'default.ini', 'sql/*.sql',
'images/*.png', 'images/*.ico', 'images/*.icns',
'bitmessagekivy/main.kv', 'bitmessagekivy/screens_data.json',
'bitmessagekivy/kv/*.kv', 'images/kivy/payment/*.png', 'images/kivy/*.gif',
'images/kivy/text_images*.png'
]}
if sys.version_info[0] == 3: if sys.version_info[0] == 3:
packages.append('pybitmessage.bitmessagekivy') packages.extend(
[
'pybitmessage.bitmessagekivy',
'pybitmessage.bitmessagekivy.baseclass'
]
)
if os.environ.get('INSTALL_TESTS', False):
packages.extend(['pybitmessage.mockbm', 'pybitmessage.backend', 'pybitmessage.bitmessagekivy.tests'])
package_data[''].extend(['bitmessagekivy/tests/sampleData/*.dat'])
# this will silently accept alternative providers of msgpack # this will silently accept alternative providers of msgpack
# if they are already installed # if they are already installed
@ -134,11 +151,7 @@ if __name__ == "__main__":
], ],
package_dir={'pybitmessage': 'src'}, package_dir={'pybitmessage': 'src'},
packages=packages, packages=packages,
package_data={'': [ package_data=package_data,
'bitmessageqt/*.ui', 'bitmsghash/*.cl', 'sslkeys/*.pem',
'translations/*.ts', 'translations/*.qm',
'images/*.png', 'images/*.ico', 'images/*.icns'
]},
data_files=data_files, data_files=data_files,
ext_modules=[bitmsghash], ext_modules=[bitmsghash],
zip_safe=False, zip_safe=False,

View File

@ -2,11 +2,16 @@
Operations with addresses Operations with addresses
""" """
# pylint: disable=inconsistent-return-statements # pylint: disable=inconsistent-return-statements
import hashlib
import logging import logging
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from struct import pack, unpack from struct import pack, unpack
try:
from highlevelcrypto import double_sha512
except ImportError:
from .highlevelcrypto import double_sha512
logger = logging.getLogger('default') logger = logging.getLogger('default')
@ -134,15 +139,6 @@ def decodeVarint(data):
return (encodedValue, 9) return (encodedValue, 9)
def calculateInventoryHash(data):
"""Calculate inventory hash from object data"""
sha = hashlib.new('sha512')
sha2 = hashlib.new('sha512')
sha.update(data)
sha2.update(sha.digest())
return sha2.digest()[0:32]
def encodeAddress(version, stream, ripe): def encodeAddress(version, stream, ripe):
"""Convert ripe to address""" """Convert ripe to address"""
if version >= 2 and version < 4: if version >= 2 and version < 4:
@ -166,12 +162,7 @@ def encodeAddress(version, stream, ripe):
storedBinaryData = encodeVarint(version) + encodeVarint(stream) + ripe storedBinaryData = encodeVarint(version) + encodeVarint(stream) + ripe
# Generate the checksum # Generate the checksum
sha = hashlib.new('sha512') checksum = double_sha512(storedBinaryData)[0:4]
sha.update(storedBinaryData)
currentHash = sha.digest()
sha = hashlib.new('sha512')
sha.update(currentHash)
checksum = sha.digest()[0:4]
# FIXME: encodeBase58 should take binary data, to reduce conversions # FIXME: encodeBase58 should take binary data, to reduce conversions
# encodeBase58(storedBinaryData + checksum) # encodeBase58(storedBinaryData + checksum)
@ -207,13 +198,7 @@ def decodeAddress(address):
data = unhexlify(hexdata) data = unhexlify(hexdata)
checksum = data[-4:] checksum = data[-4:]
sha = hashlib.new('sha512') if checksum != double_sha512(data[:-4])[0:4]:
sha.update(data[:-4])
currentHash = sha.digest()
sha = hashlib.new('sha512')
sha.update(currentHash)
if checksum != sha.digest()[0:4]:
status = 'checksumfailed' status = 'checksumfailed'
return status, 0, 0, '' return status, 0, 0, ''

View File

@ -1,5 +1,5 @@
# Copyright (c) 2012-2016 Jonathan Warren # Copyright (c) 2012-2016 Jonathan Warren
# Copyright (c) 2012-2020 The Bitmessage developers # Copyright (c) 2012-2023 The Bitmessage developers
""" """
This is not what you run to start the Bitmessage API. This is not what you run to start the Bitmessage API.
@ -39,17 +39,18 @@ To use the API concider such simple example:
.. code-block:: python .. code-block:: python
import jsonrpclib from jsonrpclib import jsonrpc
from pybitmessage import bmconfigparser, helper_startup from pybitmessage import helper_startup
from pybitmessage.bmconfigparser import config
helper_startup.loadConfig() # find and load local config file helper_startup.loadConfig() # find and load local config file
conf = bmconfigparser.BMConfigParser() api_uri = "http://%s:%s@127.0.0.1:%s/" % (
api_uri = "http://%s:%s@127.0.0.1:8442/" % ( config.safeGet('bitmessagesettings', 'apiusername'),
conf.safeGet('bitmessagesettings', 'apiusername'), config.safeGet('bitmessagesettings', 'apipassword'),
conf.safeGet('bitmessagesettings', 'apipassword') config.safeGet('bitmessagesettings', 'apiport')
) )
api = jsonrpclib.ServerProxy(api_uri) api = jsonrpc.ServerProxy(api_uri)
print(api.clientStatus()) print(api.clientStatus())
@ -57,42 +58,49 @@ For further examples please reference `.tests.test_api`.
""" """
import base64 import base64
import ConfigParser
import errno import errno
import hashlib import hashlib
import httplib
import json import json
import random # nosec import random
import socket import socket
import subprocess import subprocess # nosec B404
import time import time
import xmlrpclib
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer from struct import pack, unpack
from struct import pack
import six
from six.moves import configparser, http_client, xmlrpc_server
import defaults
import helper_inbox import helper_inbox
import helper_sent import helper_sent
import network.stats import protocol
import proofofwork import proofofwork
import queues import queues
import shared import shared
import shutdown import shutdown
import state import state
from addresses import ( from addresses import (
addBMIfNotPresent, addBMIfNotPresent,
calculateInventoryHash,
decodeAddress, decodeAddress,
decodeVarint, decodeVarint,
varintDecodeError varintDecodeError
) )
from bmconfigparser import BMConfigParser from bmconfigparser import config
from debug import logger from debug import logger
from helper_sql import SqlBulkExecute, sqlExecute, sqlQuery, sqlStoredProcedure, sql_ready from defaults import (
from inventory import Inventory networkDefaultProofOfWorkNonceTrialsPerByte,
from network.threads import StoppableThread networkDefaultPayloadLengthExtraBytes)
from six.moves import queue from helper_sql import (
SqlBulkExecute, sqlExecute, sqlQuery, sqlStoredProcedure, sql_ready)
from highlevelcrypto import calculateInventoryHash
try:
from network import connectionpool
except ImportError:
connectionpool = None
from network import stats, StoppableThread
from version import softwareVersion from version import softwareVersion
try: # TODO: write tests for XML vulnerabilities try: # TODO: write tests for XML vulnerabilities
@ -154,7 +162,7 @@ class ErrorCodes(type):
def __new__(mcs, name, bases, namespace): def __new__(mcs, name, bases, namespace):
result = super(ErrorCodes, mcs).__new__(mcs, name, bases, namespace) result = super(ErrorCodes, mcs).__new__(mcs, name, bases, namespace)
for code in mcs._CODES.iteritems(): for code in six.iteritems(mcs._CODES):
# beware: the formatting is adjusted for list-table # beware: the formatting is adjusted for list-table
result.__doc__ += """ * - %04i result.__doc__ += """ * - %04i
- %s - %s
@ -162,7 +170,7 @@ class ErrorCodes(type):
return result return result
class APIError(xmlrpclib.Fault): class APIError(xmlrpc_server.Fault):
""" """
APIError exception class APIError exception class
@ -190,8 +198,8 @@ class singleAPI(StoppableThread):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try: try:
s.connect(( s.connect((
BMConfigParser().get('bitmessagesettings', 'apiinterface'), config.get('bitmessagesettings', 'apiinterface'),
BMConfigParser().getint('bitmessagesettings', 'apiport') config.getint('bitmessagesettings', 'apiport')
)) ))
s.shutdown(socket.SHUT_RDWR) s.shutdown(socket.SHUT_RDWR)
s.close() s.close()
@ -204,15 +212,15 @@ class singleAPI(StoppableThread):
:class:`jsonrpclib.SimpleJSONRPCServer` is created and started here :class:`jsonrpclib.SimpleJSONRPCServer` is created and started here
with `BMRPCDispatcher` dispatcher. with `BMRPCDispatcher` dispatcher.
""" """
port = BMConfigParser().getint('bitmessagesettings', 'apiport') port = config.getint('bitmessagesettings', 'apiport')
try: try:
getattr(errno, 'WSAEADDRINUSE') getattr(errno, 'WSAEADDRINUSE')
except AttributeError: except AttributeError:
errno.WSAEADDRINUSE = errno.EADDRINUSE errno.WSAEADDRINUSE = errno.EADDRINUSE
RPCServerBase = SimpleXMLRPCServer RPCServerBase = xmlrpc_server.SimpleXMLRPCServer
ct = 'text/xml' ct = 'text/xml'
if BMConfigParser().safeGet( if config.safeGet(
'bitmessagesettings', 'apivariant') == 'json': 'bitmessagesettings', 'apivariant') == 'json':
try: try:
from jsonrpclib.SimpleJSONRPCServer import ( from jsonrpclib.SimpleJSONRPCServer import (
@ -240,9 +248,9 @@ class singleAPI(StoppableThread):
if attempt > 0: if attempt > 0:
logger.warning( logger.warning(
'Failed to start API listener on port %s', port) 'Failed to start API listener on port %s', port)
port = random.randint(32767, 65535) port = random.randint(32767, 65535) # nosec B311
se = StoppableRPCServer( se = StoppableRPCServer(
(BMConfigParser().get( (config.get(
'bitmessagesettings', 'apiinterface'), 'bitmessagesettings', 'apiinterface'),
port), port),
BMXMLRPCRequestHandler, True, encoding='UTF-8') BMXMLRPCRequestHandler, True, encoding='UTF-8')
@ -252,26 +260,26 @@ class singleAPI(StoppableThread):
else: else:
if attempt > 0: if attempt > 0:
logger.warning('Setting apiport to %s', port) logger.warning('Setting apiport to %s', port)
BMConfigParser().set( config.set(
'bitmessagesettings', 'apiport', str(port)) 'bitmessagesettings', 'apiport', str(port))
BMConfigParser().save() config.save()
break break
se.register_instance(BMRPCDispatcher()) se.register_instance(BMRPCDispatcher())
se.register_introspection_functions() se.register_introspection_functions()
apiNotifyPath = BMConfigParser().safeGet( apiNotifyPath = config.safeGet(
'bitmessagesettings', 'apinotifypath') 'bitmessagesettings', 'apinotifypath')
if apiNotifyPath: if apiNotifyPath:
logger.info('Trying to call %s', apiNotifyPath) logger.info('Trying to call %s', apiNotifyPath)
try: try:
subprocess.call([apiNotifyPath, "startingUp"]) subprocess.call([apiNotifyPath, "startingUp"]) # nosec B603
except OSError: except OSError:
logger.warning( logger.warning(
'Failed to call %s, removing apinotifypath setting', 'Failed to call %s, removing apinotifypath setting',
apiNotifyPath) apiNotifyPath)
BMConfigParser().remove_option( config.remove_option(
'bitmessagesettings', 'apinotifypath') 'bitmessagesettings', 'apinotifypath')
se.serve_forever() se.serve_forever()
@ -286,7 +294,7 @@ class CommandHandler(type):
# pylint: disable=protected-access # pylint: disable=protected-access
result = super(CommandHandler, mcs).__new__( result = super(CommandHandler, mcs).__new__(
mcs, name, bases, namespace) mcs, name, bases, namespace)
result.config = BMConfigParser() result.config = config
result._handlers = {} result._handlers = {}
apivariant = result.config.safeGet('bitmessagesettings', 'apivariant') apivariant = result.config.safeGet('bitmessagesettings', 'apivariant')
for func in namespace.values(): for func in namespace.values():
@ -325,7 +333,7 @@ class command(object): # pylint: disable=too-few-public-methods
def __call__(self, func): def __call__(self, func):
if BMConfigParser().safeGet( if config.safeGet(
'bitmessagesettings', 'apivariant') == 'legacy': 'bitmessagesettings', 'apivariant') == 'legacy':
def wrapper(*args): def wrapper(*args):
""" """
@ -351,7 +359,7 @@ class command(object): # pylint: disable=too-few-public-methods
# Modified by Jonathan Warren (Atheros). # Modified by Jonathan Warren (Atheros).
# Further modified by the Bitmessage developers # Further modified by the Bitmessage developers
# http://code.activestate.com/recipes/501148 # http://code.activestate.com/recipes/501148
class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): class BMXMLRPCRequestHandler(xmlrpc_server.SimpleXMLRPCRequestHandler):
"""The main API handler""" """The main API handler"""
# pylint: disable=protected-access # pylint: disable=protected-access
@ -382,17 +390,21 @@ class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
L = [] L = []
while size_remaining: while size_remaining:
chunk_size = min(size_remaining, max_chunk_size) chunk_size = min(size_remaining, max_chunk_size)
L.append(self.rfile.read(chunk_size)) chunk = self.rfile.read(chunk_size)
if not chunk:
break
L.append(chunk)
size_remaining -= len(L[-1]) size_remaining -= len(L[-1])
data = ''.join(L) data = b''.join(L)
# data = self.decode_request_content(data)
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init
self.cookies = [] self.cookies = []
validuser = self.APIAuthenticateClient() validuser = self.APIAuthenticateClient()
if not validuser: if not validuser:
time.sleep(2) time.sleep(2)
self.send_response(httplib.UNAUTHORIZED) self.send_response(http_client.UNAUTHORIZED)
self.end_headers() self.end_headers()
return return
# "RPC Username or password incorrect or HTTP header" # "RPC Username or password incorrect or HTTP header"
@ -409,11 +421,11 @@ class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
) )
except Exception: # This should only happen if the module is buggy except Exception: # This should only happen if the module is buggy
# internal error, report as HTTP server error # internal error, report as HTTP server error
self.send_response(httplib.INTERNAL_SERVER_ERROR) self.send_response(http_client.INTERNAL_SERVER_ERROR)
self.end_headers() self.end_headers()
else: else:
# got a valid XML RPC response # got a valid XML RPC response
self.send_response(httplib.OK) self.send_response(http_client.OK)
self.send_header("Content-type", self.server.content_type) self.send_header("Content-type", self.server.content_type)
self.send_header("Content-length", str(len(response))) self.send_header("Content-length", str(len(response)))
@ -442,11 +454,12 @@ class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
if 'Authorization' in self.headers: if 'Authorization' in self.headers:
# handle Basic authentication # handle Basic authentication
encstr = self.headers.get('Authorization').split()[1] encstr = self.headers.get('Authorization').split()[1]
emailid, password = encstr.decode('base64').split(':') emailid, password = base64.b64decode(
encstr).decode('utf-8').split(':')
return ( return (
emailid == BMConfigParser().get( emailid == config.get(
'bitmessagesettings', 'apiusername' 'bitmessagesettings', 'apiusername'
) and password == BMConfigParser().get( ) and password == config.get(
'bitmessagesettings', 'apipassword')) 'bitmessagesettings', 'apipassword'))
else: else:
logger.warning( logger.warning(
@ -458,9 +471,9 @@ class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
# pylint: disable=no-self-use,no-member,too-many-public-methods # pylint: disable=no-self-use,no-member,too-many-public-methods
@six.add_metaclass(CommandHandler)
class BMRPCDispatcher(object): class BMRPCDispatcher(object):
"""This class is used to dispatch API commands""" """This class is used to dispatch API commands"""
__metaclass__ = CommandHandler
@staticmethod @staticmethod
def _decode(text, decode_type): def _decode(text, decode_type):
@ -645,13 +658,11 @@ class BMRPCDispatcher(object):
nonceTrialsPerByte = self.config.get( nonceTrialsPerByte = self.config.get(
'bitmessagesettings', 'defaultnoncetrialsperbyte' 'bitmessagesettings', 'defaultnoncetrialsperbyte'
) if not totalDifficulty else int( ) if not totalDifficulty else int(
defaults.networkDefaultProofOfWorkNonceTrialsPerByte networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty)
* totalDifficulty)
payloadLengthExtraBytes = self.config.get( payloadLengthExtraBytes = self.config.get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes' 'bitmessagesettings', 'defaultpayloadlengthextrabytes'
) if not smallMessageDifficulty else int( ) if not smallMessageDifficulty else int(
defaults.networkDefaultPayloadLengthExtraBytes networkDefaultPayloadLengthExtraBytes * smallMessageDifficulty)
* smallMessageDifficulty)
if not isinstance(eighteenByteRipe, bool): if not isinstance(eighteenByteRipe, bool):
raise APIError( raise APIError(
@ -693,13 +704,11 @@ class BMRPCDispatcher(object):
nonceTrialsPerByte = self.config.get( nonceTrialsPerByte = self.config.get(
'bitmessagesettings', 'defaultnoncetrialsperbyte' 'bitmessagesettings', 'defaultnoncetrialsperbyte'
) if not totalDifficulty else int( ) if not totalDifficulty else int(
defaults.networkDefaultProofOfWorkNonceTrialsPerByte networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty)
* totalDifficulty)
payloadLengthExtraBytes = self.config.get( payloadLengthExtraBytes = self.config.get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes' 'bitmessagesettings', 'defaultpayloadlengthextrabytes'
) if not smallMessageDifficulty else int( ) if not smallMessageDifficulty else int(
defaults.networkDefaultPayloadLengthExtraBytes networkDefaultPayloadLengthExtraBytes * smallMessageDifficulty)
* smallMessageDifficulty)
if not passphrase: if not passphrase:
raise APIError(1, 'The specified passphrase is blank.') raise APIError(1, 'The specified passphrase is blank.')
@ -858,7 +867,7 @@ class BMRPCDispatcher(object):
' Use deleteAddress API call instead.') ' Use deleteAddress API call instead.')
try: try:
self.config.remove_section(address) self.config.remove_section(address)
except ConfigParser.NoSectionError: except configparser.NoSectionError:
raise APIError( raise APIError(
13, 'Could not find this address in your keys.dat file.') 13, 'Could not find this address in your keys.dat file.')
self.config.save() self.config.save()
@ -875,7 +884,7 @@ class BMRPCDispatcher(object):
address = addBMIfNotPresent(address) address = addBMIfNotPresent(address)
try: try:
self.config.remove_section(address) self.config.remove_section(address)
except ConfigParser.NoSectionError: except configparser.NoSectionError:
raise APIError( raise APIError(
13, 'Could not find this address in your keys.dat file.') 13, 'Could not find this address in your keys.dat file.')
self.config.save() self.config.save()
@ -883,6 +892,16 @@ class BMRPCDispatcher(object):
shared.reloadMyAddressHashes() shared.reloadMyAddressHashes()
return "success" return "success"
@command('enableAddress')
def HandleEnableAddress(self, address, enable=True):
"""Enable or disable the address depending on the *enable* value"""
self._verifyAddress(address)
address = addBMIfNotPresent(address)
config.set(address, 'enabled', str(enable))
self.config.save()
shared.reloadMyAddressHashes()
return "success"
@command('getAllInboxMessages') @command('getAllInboxMessages')
def HandleGetAllInboxMessages(self): def HandleGetAllInboxMessages(self):
""" """
@ -1118,9 +1137,8 @@ class BMRPCDispatcher(object):
fromAddress = addBMIfNotPresent(fromAddress) fromAddress = addBMIfNotPresent(fromAddress)
self._verifyAddress(fromAddress) self._verifyAddress(fromAddress)
try: try:
fromAddressEnabled = self.config.getboolean( fromAddressEnabled = self.config.getboolean(fromAddress, 'enabled')
fromAddress, 'enabled') except configparser.NoSectionError:
except BaseException:
raise APIError( raise APIError(
13, 'Could not find your fromAddress in the keys.dat file.') 13, 'Could not find your fromAddress in the keys.dat file.')
if not fromAddressEnabled: if not fromAddressEnabled:
@ -1164,10 +1182,13 @@ class BMRPCDispatcher(object):
fromAddress = addBMIfNotPresent(fromAddress) fromAddress = addBMIfNotPresent(fromAddress)
self._verifyAddress(fromAddress) self._verifyAddress(fromAddress)
try: try:
self.config.getboolean(fromAddress, 'enabled') fromAddressEnabled = self.config.getboolean(fromAddress, 'enabled')
except BaseException: except configparser.NoSectionError:
raise APIError( raise APIError(
13, 'Could not find your fromAddress in the keys.dat file.') 13, 'Could not find your fromAddress in the keys.dat file.')
if not fromAddressEnabled:
raise APIError(14, 'Your fromAddress is disabled. Cannot send.')
toAddress = str_broadcast_subscribers toAddress = str_broadcast_subscribers
ackdata = helper_sent.insert( ackdata = helper_sent.insert(
@ -1260,34 +1281,53 @@ class BMRPCDispatcher(object):
}) })
return {'subscriptions': data} return {'subscriptions': data}
@command('disseminatePreEncryptedMsg') @command('disseminatePreEncryptedMsg', 'disseminatePreparedObject')
def HandleDisseminatePreEncryptedMsg( def HandleDisseminatePreparedObject(
self, encryptedPayload, requiredAverageProofOfWorkNonceTrialsPerByte, self, encryptedPayload,
requiredPayloadLengthExtraBytes): nonceTrialsPerByte=networkDefaultProofOfWorkNonceTrialsPerByte,
"""Handle a request to disseminate an encrypted message""" payloadLengthExtraBytes=networkDefaultPayloadLengthExtraBytes
):
"""
Handle a request to disseminate an encrypted message.
# The device issuing this command to PyBitmessage supplies a msg The device issuing this command to PyBitmessage supplies an object
# object that has already been encrypted but which still needs the POW that has already been encrypted but which may still need the PoW
# to be done. PyBitmessage accepts this msg object and sends it out to be done. PyBitmessage accepts this object and sends it out
# to the rest of the Bitmessage network as if it had generated to the rest of the Bitmessage network as if it had generated
# the message itself. Please do not yet add this to the api doc. the message itself.
*encryptedPayload* is a hex encoded string starting with the nonce,
8 zero bytes in case of no PoW done.
"""
encryptedPayload = self._decode(encryptedPayload, "hex") encryptedPayload = self._decode(encryptedPayload, "hex")
nonce, = unpack('>Q', encryptedPayload[:8])
objectType, toStreamNumber, expiresTime = \
protocol.decodeObjectParameters(encryptedPayload)
if nonce == 0: # Let us do the POW and attach it to the front
encryptedPayload = encryptedPayload[8:]
TTL = expiresTime - time.time() + 300 # a bit of extra padding
# Let us do the POW and attach it to the front # Let us do the POW and attach it to the front
target = 2**64 / ( logger.debug("expiresTime: %s", expiresTime)
( logger.debug("TTL: %s", TTL)
len(encryptedPayload) logger.debug("objectType: %s", objectType)
+ requiredPayloadLengthExtraBytes
+ 8
) * requiredAverageProofOfWorkNonceTrialsPerByte)
logger.info( logger.info(
'(For msg message via API) Doing proof of work. Total required' '(For msg message via API) Doing proof of work. Total required'
' difficulty: %s\nRequired small message difficulty: %s', ' difficulty: %s\nRequired small message difficulty: %s',
float(requiredAverageProofOfWorkNonceTrialsPerByte) float(nonceTrialsPerByte)
/ defaults.networkDefaultProofOfWorkNonceTrialsPerByte, / networkDefaultProofOfWorkNonceTrialsPerByte,
float(requiredPayloadLengthExtraBytes) float(payloadLengthExtraBytes)
/ defaults.networkDefaultPayloadLengthExtraBytes, / networkDefaultPayloadLengthExtraBytes,
) )
powStartTime = time.time() powStartTime = time.time()
target = 2**64 / (
nonceTrialsPerByte * (
len(encryptedPayload) + 8 + payloadLengthExtraBytes + ((
TTL * (
len(encryptedPayload) + 8 + payloadLengthExtraBytes
)) / (2 ** 16))
))
initialHash = hashlib.sha512(encryptedPayload).digest() initialHash = hashlib.sha512(encryptedPayload).digest()
trialValue, nonce = proofofwork.run(target, initialHash) trialValue, nonce = proofofwork.run(target, initialHash)
logger.info( logger.info(
@ -1297,18 +1337,17 @@ class BMRPCDispatcher(object):
nonce / (time.time() - powStartTime) nonce / (time.time() - powStartTime)
) )
encryptedPayload = pack('>Q', nonce) + encryptedPayload encryptedPayload = pack('>Q', nonce) + encryptedPayload
toStreamNumber = decodeVarint(encryptedPayload[16:26])[0]
inventoryHash = calculateInventoryHash(encryptedPayload) inventoryHash = calculateInventoryHash(encryptedPayload)
objectType = 2 state.Inventory[inventoryHash] = (
TTL = 2.5 * 24 * 60 * 60
Inventory()[inventoryHash] = (
objectType, toStreamNumber, encryptedPayload, objectType, toStreamNumber, encryptedPayload,
int(time.time()) + TTL, '' expiresTime, b''
) )
logger.info( logger.info(
'Broadcasting inv for msg(API disseminatePreEncryptedMsg' 'Broadcasting inv for msg(API disseminatePreEncryptedMsg'
' command): %s', hexlify(inventoryHash)) ' command): %s', hexlify(inventoryHash))
queues.invQueue.put((toStreamNumber, inventoryHash)) queues.invQueue.put((toStreamNumber, inventoryHash))
return hexlify(inventoryHash).decode()
@command('trashSentMessageByAckData') @command('trashSentMessageByAckData')
def HandleTrashSentMessageByAckDAta(self, ackdata): def HandleTrashSentMessageByAckDAta(self, ackdata):
@ -1331,8 +1370,8 @@ class BMRPCDispatcher(object):
# Let us do the POW # Let us do the POW
target = 2 ** 64 / (( target = 2 ** 64 / ((
len(payload) + defaults.networkDefaultPayloadLengthExtraBytes + 8 len(payload) + networkDefaultPayloadLengthExtraBytes + 8
) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte) ) * networkDefaultProofOfWorkNonceTrialsPerByte)
logger.info('(For pubkey message via API) Doing proof of work...') logger.info('(For pubkey message via API) Doing proof of work...')
initialHash = hashlib.sha512(payload).digest() initialHash = hashlib.sha512(payload).digest()
trialValue, nonce = proofofwork.run(target, initialHash) trialValue, nonce = proofofwork.run(target, initialHash)
@ -1356,7 +1395,7 @@ class BMRPCDispatcher(object):
inventoryHash = calculateInventoryHash(payload) inventoryHash = calculateInventoryHash(payload)
objectType = 1 # .. todo::: support v4 pubkeys objectType = 1 # .. todo::: support v4 pubkeys
TTL = 28 * 24 * 60 * 60 TTL = 28 * 24 * 60 * 60
Inventory()[inventoryHash] = ( state.Inventory[inventoryHash] = (
objectType, pubkeyStreamNumber, payload, int(time.time()) + TTL, '' objectType, pubkeyStreamNumber, payload, int(time.time()) + TTL, ''
) )
logger.info( logger.info(
@ -1411,7 +1450,8 @@ class BMRPCDispatcher(object):
or "connectedAndReceivingIncomingConnections". or "connectedAndReceivingIncomingConnections".
""" """
connections_num = len(network.stats.connectedHostsList()) connections_num = len(stats.connectedHostsList())
if connections_num == 0: if connections_num == 0:
networkStatus = 'notConnected' networkStatus = 'notConnected'
elif state.clientHasReceivedIncomingConnections: elif state.clientHasReceivedIncomingConnections:
@ -1423,12 +1463,41 @@ class BMRPCDispatcher(object):
'numberOfMessagesProcessed': state.numberOfMessagesProcessed, 'numberOfMessagesProcessed': state.numberOfMessagesProcessed,
'numberOfBroadcastsProcessed': state.numberOfBroadcastsProcessed, 'numberOfBroadcastsProcessed': state.numberOfBroadcastsProcessed,
'numberOfPubkeysProcessed': state.numberOfPubkeysProcessed, 'numberOfPubkeysProcessed': state.numberOfPubkeysProcessed,
'pendingDownload': network.stats.pendingDownload(), 'pendingDownload': stats.pendingDownload(),
'networkStatus': networkStatus, 'networkStatus': networkStatus,
'softwareName': 'PyBitmessage', 'softwareName': 'PyBitmessage',
'softwareVersion': softwareVersion 'softwareVersion': softwareVersion
} }
@command('listConnections')
def HandleListConnections(self):
"""
Returns bitmessage connection information as dict with keys *inbound*,
*outbound*.
"""
if connectionpool is None:
raise APIError(21, 'Could not import BMConnectionPool.')
inboundConnections = []
outboundConnections = []
for i in connectionpool.pool.inboundConnections.values():
inboundConnections.append({
'host': i.destination.host,
'port': i.destination.port,
'fullyEstablished': i.fullyEstablished,
'userAgent': str(i.userAgent)
})
for i in connectionpool.pool.outboundConnections.values():
outboundConnections.append({
'host': i.destination.host,
'port': i.destination.port,
'fullyEstablished': i.fullyEstablished,
'userAgent': str(i.userAgent)
})
return {
'inbound': inboundConnections,
'outbound': outboundConnections
}
@command('helloWorld') @command('helloWorld')
def HandleHelloWorld(self, a, b): def HandleHelloWorld(self, a, b):
"""Test two string params""" """Test two string params"""
@ -1439,25 +1508,11 @@ class BMRPCDispatcher(object):
"""Test two numeric params""" """Test two numeric params"""
return a + b return a + b
@testmode('clearUISignalQueue')
def HandleclearUISignalQueue(self):
"""clear UISignalQueue"""
queues.UISignalQueue.queue.clear()
return "success"
@command('statusBar') @command('statusBar')
def HandleStatusBar(self, message): def HandleStatusBar(self, message):
"""Update GUI statusbar message""" """Update GUI statusbar message"""
queues.UISignalQueue.put(('updateStatusBar', message)) queues.UISignalQueue.put(('updateStatusBar', message))
return "success"
@testmode('getStatusBar')
def HandleGetStatusBar(self):
"""Get GUI statusbar message"""
try:
_, data = queues.UISignalQueue.get(block=False)
except queue.Empty:
return None
return data
@testmode('undeleteMessage') @testmode('undeleteMessage')
def HandleUndeleteMessage(self, msgid): def HandleUndeleteMessage(self, msgid):

View File

@ -0,0 +1,49 @@
"""
Common methods and functions for kivy and qt.
"""
from pybitmessage import queues
from pybitmessage.bmconfigparser import config
from pybitmessage.defaults import (
networkDefaultProofOfWorkNonceTrialsPerByte,
networkDefaultPayloadLengthExtraBytes
)
class AddressGenerator(object):
""""Base class for address generation and validation"""
def __init__(self):
pass
@staticmethod
def random_address_generation(
label, streamNumberForAddress=1, eighteenByteRipe=False,
nonceTrialsPerByte=networkDefaultProofOfWorkNonceTrialsPerByte,
payloadLengthExtraBytes=networkDefaultPayloadLengthExtraBytes
):
"""Start address generation and return whether validation was successful"""
labels = [config.get(obj, 'label')
for obj in config.addresses()]
if label and label not in labels:
queues.addressGeneratorQueue.put((
'createRandomAddress', 4, streamNumberForAddress, label, 1,
"", eighteenByteRipe, nonceTrialsPerByte,
payloadLengthExtraBytes))
return True
return False
@staticmethod
def address_validation(instance, label):
"""Checking address validation while creating"""
labels = [config.get(obj, 'label') for obj in config.addresses()]
if label in labels:
instance.error = True
instance.helper_text = 'it is already exist you'\
' can try this Ex. ( {0}_1, {0}_2 )'.format(
label)
elif label:
instance.error = False
else:
instance.error = True
instance.helper_text = 'This field is required'

View File

@ -23,7 +23,7 @@ import sys
import time import time
import xmlrpclib import xmlrpclib
from bmconfigparser import BMConfigParser from bmconfigparser import config
api = '' api = ''
@ -86,15 +86,15 @@ def lookupAppdataFolder():
def configInit(): def configInit():
"""Initialised the configuration""" """Initialised the configuration"""
BMConfigParser().add_section('bitmessagesettings') config.add_section('bitmessagesettings')
# Sets the bitmessage port to stop the warning about the api not properly # Sets the bitmessage port to stop the warning about the api not properly
# being setup. This is in the event that the keys.dat is in a different # being setup. This is in the event that the keys.dat is in a different
# directory or is created locally to connect to a machine remotely. # directory or is created locally to connect to a machine remotely.
BMConfigParser().set('bitmessagesettings', 'port', '8444') config.set('bitmessagesettings', 'port', '8444')
BMConfigParser().set('bitmessagesettings', 'apienabled', 'true') # Sets apienabled to true in keys.dat config.set('bitmessagesettings', 'apienabled', 'true') # Sets apienabled to true in keys.dat
with open(keysName, 'wb') as configfile: with open(keysName, 'wb') as configfile:
BMConfigParser().write(configfile) config.write(configfile)
print('\n ' + str(keysName) + ' Initalized in the same directory as daemon.py') print('\n ' + str(keysName) + ' Initalized in the same directory as daemon.py')
print(' You will now need to configure the ' + str(keysName) + ' file.\n') print(' You will now need to configure the ' + str(keysName) + ' file.\n')
@ -104,15 +104,15 @@ def apiInit(apiEnabled):
"""Initialise the API""" """Initialise the API"""
global usrPrompt global usrPrompt
BMConfigParser().read(keysPath) config.read(keysPath)
if apiEnabled is False: # API information there but the api is disabled. if apiEnabled is False: # API information there but the api is disabled.
uInput = userInput("The API is not enabled. Would you like to do that now, (Y)es or (N)o?").lower() uInput = userInput("The API is not enabled. Would you like to do that now, (Y)es or (N)o?").lower()
if uInput == "y": if uInput == "y":
BMConfigParser().set('bitmessagesettings', 'apienabled', 'true') # Sets apienabled to true in keys.dat config.set('bitmessagesettings', 'apienabled', 'true') # Sets apienabled to true in keys.dat
with open(keysPath, 'wb') as configfile: with open(keysPath, 'wb') as configfile:
BMConfigParser().write(configfile) config.write(configfile)
print('Done') print('Done')
restartBmNotify() restartBmNotify()
@ -158,15 +158,15 @@ def apiInit(apiEnabled):
# sets the bitmessage port to stop the warning about the api not properly # sets the bitmessage port to stop the warning about the api not properly
# being setup. This is in the event that the keys.dat is in a different # being setup. This is in the event that the keys.dat is in a different
# directory or is created locally to connect to a machine remotely. # directory or is created locally to connect to a machine remotely.
BMConfigParser().set('bitmessagesettings', 'port', '8444') config.set('bitmessagesettings', 'port', '8444')
BMConfigParser().set('bitmessagesettings', 'apienabled', 'true') config.set('bitmessagesettings', 'apienabled', 'true')
BMConfigParser().set('bitmessagesettings', 'apiport', apiPort) config.set('bitmessagesettings', 'apiport', apiPort)
BMConfigParser().set('bitmessagesettings', 'apiinterface', '127.0.0.1') config.set('bitmessagesettings', 'apiinterface', '127.0.0.1')
BMConfigParser().set('bitmessagesettings', 'apiusername', apiUsr) config.set('bitmessagesettings', 'apiusername', apiUsr)
BMConfigParser().set('bitmessagesettings', 'apipassword', apiPwd) config.set('bitmessagesettings', 'apipassword', apiPwd)
BMConfigParser().set('bitmessagesettings', 'daemon', daemon) config.set('bitmessagesettings', 'daemon', daemon)
with open(keysPath, 'wb') as configfile: with open(keysPath, 'wb') as configfile:
BMConfigParser().write(configfile) config.write(configfile)
print('\n Finished configuring the keys.dat file with API information.\n') print('\n Finished configuring the keys.dat file with API information.\n')
restartBmNotify() restartBmNotify()
@ -191,19 +191,19 @@ def apiData():
global keysPath global keysPath
global usrPrompt global usrPrompt
BMConfigParser().read(keysPath) # First try to load the config file (the keys.dat file) from the program directory config.read(keysPath) # First try to load the config file (the keys.dat file) from the program directory
try: try:
BMConfigParser().get('bitmessagesettings', 'port') config.get('bitmessagesettings', 'port')
appDataFolder = '' appDataFolder = ''
except: # noqa:E722 except: # noqa:E722
# Could not load the keys.dat file in the program directory. Perhaps it is in the appdata directory. # Could not load the keys.dat file in the program directory. Perhaps it is in the appdata directory.
appDataFolder = lookupAppdataFolder() appDataFolder = lookupAppdataFolder()
keysPath = appDataFolder + keysPath keysPath = appDataFolder + keysPath
BMConfigParser().read(keysPath) config.read(keysPath)
try: try:
BMConfigParser().get('bitmessagesettings', 'port') config.get('bitmessagesettings', 'port')
except: # noqa:E722 except: # noqa:E722
# keys.dat was not there either, something is wrong. # keys.dat was not there either, something is wrong.
print('\n ******************************************************************') print('\n ******************************************************************')
@ -230,24 +230,24 @@ def apiData():
main() main()
try: # checks to make sure that everyting is configured correctly. Excluding apiEnabled, it is checked after try: # checks to make sure that everyting is configured correctly. Excluding apiEnabled, it is checked after
BMConfigParser().get('bitmessagesettings', 'apiport') config.get('bitmessagesettings', 'apiport')
BMConfigParser().get('bitmessagesettings', 'apiinterface') config.get('bitmessagesettings', 'apiinterface')
BMConfigParser().get('bitmessagesettings', 'apiusername') config.get('bitmessagesettings', 'apiusername')
BMConfigParser().get('bitmessagesettings', 'apipassword') config.get('bitmessagesettings', 'apipassword')
except: # noqa:E722 except: # noqa:E722
apiInit("") # Initalize the keys.dat file with API information apiInit("") # Initalize the keys.dat file with API information
# keys.dat file was found or appropriately configured, allow information retrieval # keys.dat file was found or appropriately configured, allow information retrieval
# apiEnabled = # apiEnabled =
# apiInit(BMConfigParser().safeGetBoolean('bitmessagesettings','apienabled')) # apiInit(config.safeGetBoolean('bitmessagesettings','apienabled'))
# #if false it will prompt the user, if true it will return true # #if false it will prompt the user, if true it will return true
BMConfigParser().read(keysPath) # read again since changes have been made config.read(keysPath) # read again since changes have been made
apiPort = int(BMConfigParser().get('bitmessagesettings', 'apiport')) apiPort = int(config.get('bitmessagesettings', 'apiport'))
apiInterface = BMConfigParser().get('bitmessagesettings', 'apiinterface') apiInterface = config.get('bitmessagesettings', 'apiinterface')
apiUsername = BMConfigParser().get('bitmessagesettings', 'apiusername') apiUsername = config.get('bitmessagesettings', 'apiusername')
apiPassword = BMConfigParser().get('bitmessagesettings', 'apipassword') apiPassword = config.get('bitmessagesettings', 'apipassword')
print('\n API data successfully imported.\n') print('\n API data successfully imported.\n')
@ -277,28 +277,28 @@ def bmSettings():
keysPath = 'keys.dat' keysPath = 'keys.dat'
BMConfigParser().read(keysPath) # Read the keys.dat config.read(keysPath) # Read the keys.dat
try: try:
port = BMConfigParser().get('bitmessagesettings', 'port') port = config.get('bitmessagesettings', 'port')
except: # noqa:E722 except: # noqa:E722
print('\n File not found.\n') print('\n File not found.\n')
usrPrompt = 0 usrPrompt = 0
main() main()
startonlogon = BMConfigParser().safeGetBoolean('bitmessagesettings', 'startonlogon') startonlogon = config.safeGetBoolean('bitmessagesettings', 'startonlogon')
minimizetotray = BMConfigParser().safeGetBoolean('bitmessagesettings', 'minimizetotray') minimizetotray = config.safeGetBoolean('bitmessagesettings', 'minimizetotray')
showtraynotifications = BMConfigParser().safeGetBoolean('bitmessagesettings', 'showtraynotifications') showtraynotifications = config.safeGetBoolean('bitmessagesettings', 'showtraynotifications')
startintray = BMConfigParser().safeGetBoolean('bitmessagesettings', 'startintray') startintray = config.safeGetBoolean('bitmessagesettings', 'startintray')
defaultnoncetrialsperbyte = BMConfigParser().get('bitmessagesettings', 'defaultnoncetrialsperbyte') defaultnoncetrialsperbyte = config.get('bitmessagesettings', 'defaultnoncetrialsperbyte')
defaultpayloadlengthextrabytes = BMConfigParser().get('bitmessagesettings', 'defaultpayloadlengthextrabytes') defaultpayloadlengthextrabytes = config.get('bitmessagesettings', 'defaultpayloadlengthextrabytes')
daemon = BMConfigParser().safeGetBoolean('bitmessagesettings', 'daemon') daemon = config.safeGetBoolean('bitmessagesettings', 'daemon')
socksproxytype = BMConfigParser().get('bitmessagesettings', 'socksproxytype') socksproxytype = config.get('bitmessagesettings', 'socksproxytype')
sockshostname = BMConfigParser().get('bitmessagesettings', 'sockshostname') sockshostname = config.get('bitmessagesettings', 'sockshostname')
socksport = BMConfigParser().get('bitmessagesettings', 'socksport') socksport = config.get('bitmessagesettings', 'socksport')
socksauthentication = BMConfigParser().safeGetBoolean('bitmessagesettings', 'socksauthentication') socksauthentication = config.safeGetBoolean('bitmessagesettings', 'socksauthentication')
socksusername = BMConfigParser().get('bitmessagesettings', 'socksusername') socksusername = config.get('bitmessagesettings', 'socksusername')
sockspassword = BMConfigParser().get('bitmessagesettings', 'sockspassword') sockspassword = config.get('bitmessagesettings', 'sockspassword')
print('\n -----------------------------------') print('\n -----------------------------------')
print(' | Current Bitmessage Settings |') print(' | Current Bitmessage Settings |')
@ -333,60 +333,60 @@ def bmSettings():
if uInput == "port": if uInput == "port":
print(' Current port number: ' + port) print(' Current port number: ' + port)
uInput = userInput("Enter the new port number.") uInput = userInput("Enter the new port number.")
BMConfigParser().set('bitmessagesettings', 'port', str(uInput)) config.set('bitmessagesettings', 'port', str(uInput))
elif uInput == "startonlogon": elif uInput == "startonlogon":
print(' Current status: ' + str(startonlogon)) print(' Current status: ' + str(startonlogon))
uInput = userInput("Enter the new status.") uInput = userInput("Enter the new status.")
BMConfigParser().set('bitmessagesettings', 'startonlogon', str(uInput)) config.set('bitmessagesettings', 'startonlogon', str(uInput))
elif uInput == "minimizetotray": elif uInput == "minimizetotray":
print(' Current status: ' + str(minimizetotray)) print(' Current status: ' + str(minimizetotray))
uInput = userInput("Enter the new status.") uInput = userInput("Enter the new status.")
BMConfigParser().set('bitmessagesettings', 'minimizetotray', str(uInput)) config.set('bitmessagesettings', 'minimizetotray', str(uInput))
elif uInput == "showtraynotifications": elif uInput == "showtraynotifications":
print(' Current status: ' + str(showtraynotifications)) print(' Current status: ' + str(showtraynotifications))
uInput = userInput("Enter the new status.") uInput = userInput("Enter the new status.")
BMConfigParser().set('bitmessagesettings', 'showtraynotifications', str(uInput)) config.set('bitmessagesettings', 'showtraynotifications', str(uInput))
elif uInput == "startintray": elif uInput == "startintray":
print(' Current status: ' + str(startintray)) print(' Current status: ' + str(startintray))
uInput = userInput("Enter the new status.") uInput = userInput("Enter the new status.")
BMConfigParser().set('bitmessagesettings', 'startintray', str(uInput)) config.set('bitmessagesettings', 'startintray', str(uInput))
elif uInput == "defaultnoncetrialsperbyte": elif uInput == "defaultnoncetrialsperbyte":
print(' Current default nonce trials per byte: ' + defaultnoncetrialsperbyte) print(' Current default nonce trials per byte: ' + defaultnoncetrialsperbyte)
uInput = userInput("Enter the new defaultnoncetrialsperbyte.") uInput = userInput("Enter the new defaultnoncetrialsperbyte.")
BMConfigParser().set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(uInput)) config.set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(uInput))
elif uInput == "defaultpayloadlengthextrabytes": elif uInput == "defaultpayloadlengthextrabytes":
print(' Current default payload length extra bytes: ' + defaultpayloadlengthextrabytes) print(' Current default payload length extra bytes: ' + defaultpayloadlengthextrabytes)
uInput = userInput("Enter the new defaultpayloadlengthextrabytes.") uInput = userInput("Enter the new defaultpayloadlengthextrabytes.")
BMConfigParser().set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str(uInput)) config.set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str(uInput))
elif uInput == "daemon": elif uInput == "daemon":
print(' Current status: ' + str(daemon)) print(' Current status: ' + str(daemon))
uInput = userInput("Enter the new status.").lower() uInput = userInput("Enter the new status.").lower()
BMConfigParser().set('bitmessagesettings', 'daemon', str(uInput)) config.set('bitmessagesettings', 'daemon', str(uInput))
elif uInput == "socksproxytype": elif uInput == "socksproxytype":
print(' Current socks proxy type: ' + socksproxytype) print(' Current socks proxy type: ' + socksproxytype)
print("Possibilities: 'none', 'SOCKS4a', 'SOCKS5'.") print("Possibilities: 'none', 'SOCKS4a', 'SOCKS5'.")
uInput = userInput("Enter the new socksproxytype.") uInput = userInput("Enter the new socksproxytype.")
BMConfigParser().set('bitmessagesettings', 'socksproxytype', str(uInput)) config.set('bitmessagesettings', 'socksproxytype', str(uInput))
elif uInput == "sockshostname": elif uInput == "sockshostname":
print(' Current socks host name: ' + sockshostname) print(' Current socks host name: ' + sockshostname)
uInput = userInput("Enter the new sockshostname.") uInput = userInput("Enter the new sockshostname.")
BMConfigParser().set('bitmessagesettings', 'sockshostname', str(uInput)) config.set('bitmessagesettings', 'sockshostname', str(uInput))
elif uInput == "socksport": elif uInput == "socksport":
print(' Current socks port number: ' + socksport) print(' Current socks port number: ' + socksport)
uInput = userInput("Enter the new socksport.") uInput = userInput("Enter the new socksport.")
BMConfigParser().set('bitmessagesettings', 'socksport', str(uInput)) config.set('bitmessagesettings', 'socksport', str(uInput))
elif uInput == "socksauthentication": elif uInput == "socksauthentication":
print(' Current status: ' + str(socksauthentication)) print(' Current status: ' + str(socksauthentication))
uInput = userInput("Enter the new status.") uInput = userInput("Enter the new status.")
BMConfigParser().set('bitmessagesettings', 'socksauthentication', str(uInput)) config.set('bitmessagesettings', 'socksauthentication', str(uInput))
elif uInput == "socksusername": elif uInput == "socksusername":
print(' Current socks username: ' + socksusername) print(' Current socks username: ' + socksusername)
uInput = userInput("Enter the new socksusername.") uInput = userInput("Enter the new socksusername.")
BMConfigParser().set('bitmessagesettings', 'socksusername', str(uInput)) config.set('bitmessagesettings', 'socksusername', str(uInput))
elif uInput == "sockspassword": elif uInput == "sockspassword":
print(' Current socks password: ' + sockspassword) print(' Current socks password: ' + sockspassword)
uInput = userInput("Enter the new password.") uInput = userInput("Enter the new password.")
BMConfigParser().set('bitmessagesettings', 'sockspassword', str(uInput)) config.set('bitmessagesettings', 'sockspassword', str(uInput))
else: else:
print("\n Invalid input. Please try again.\n") print("\n Invalid input. Please try again.\n")
invalidInput = True invalidInput = True
@ -397,7 +397,7 @@ def bmSettings():
if uInput != "y": if uInput != "y":
print('\n Changes Made.\n') print('\n Changes Made.\n')
with open(keysPath, 'wb') as configfile: with open(keysPath, 'wb') as configfile:
BMConfigParser().write(configfile) config.write(configfile)
restartBmNotify() restartBmNotify()
break break

View File

@ -28,9 +28,8 @@ import shutdown
import state import state
from addresses import addBMIfNotPresent, decodeAddress from addresses import addBMIfNotPresent, decodeAddress
from bmconfigparser import BMConfigParser from bmconfigparser import config
from helper_sql import sqlExecute, sqlQuery from helper_sql import sqlExecute, sqlQuery
from inventory import Inventory
# pylint: disable=global-statement # pylint: disable=global-statement
@ -145,8 +144,8 @@ def scrollbox(d, text, height=None, width=None):
def resetlookups(): def resetlookups():
"""Reset the Inventory Lookups""" """Reset the Inventory Lookups"""
global inventorydata global inventorydata
inventorydata = Inventory().numberOfInventoryLookupsPerformed inventorydata = state.Inventory.numberOfInventoryLookupsPerformed
Inventory().numberOfInventoryLookupsPerformed = 0 state.Inventory.numberOfInventoryLookupsPerformed = 0
Timer(1, resetlookups, ()).start() Timer(1, resetlookups, ()).start()
@ -618,19 +617,19 @@ def handlech(c, stdscr):
r, t = d.inputbox("New address label", init=label) r, t = d.inputbox("New address label", init=label)
if r == d.DIALOG_OK: if r == d.DIALOG_OK:
label = t label = t
BMConfigParser().set(a, "label", label) config.set(a, "label", label)
# Write config # Write config
BMConfigParser().save() config.save()
addresses[addrcur][0] = label addresses[addrcur][0] = label
elif t == "4": # Enable address elif t == "4": # Enable address
a = addresses[addrcur][2] a = addresses[addrcur][2]
BMConfigParser().set(a, "enabled", "true") # Set config config.set(a, "enabled", "true") # Set config
# Write config # Write config
BMConfigParser().save() config.save()
# Change color # Change color
if BMConfigParser().safeGetBoolean(a, 'chan'): if config.safeGetBoolean(a, 'chan'):
addresses[addrcur][3] = 9 # orange addresses[addrcur][3] = 9 # orange
elif BMConfigParser().safeGetBoolean(a, 'mailinglist'): elif config.safeGetBoolean(a, 'mailinglist'):
addresses[addrcur][3] = 5 # magenta addresses[addrcur][3] = 5 # magenta
else: else:
addresses[addrcur][3] = 0 # black addresses[addrcur][3] = 0 # black
@ -638,26 +637,26 @@ def handlech(c, stdscr):
shared.reloadMyAddressHashes() # Reload address hashes shared.reloadMyAddressHashes() # Reload address hashes
elif t == "5": # Disable address elif t == "5": # Disable address
a = addresses[addrcur][2] a = addresses[addrcur][2]
BMConfigParser().set(a, "enabled", "false") # Set config config.set(a, "enabled", "false") # Set config
addresses[addrcur][3] = 8 # Set color to gray addresses[addrcur][3] = 8 # Set color to gray
# Write config # Write config
BMConfigParser().save() config.save()
addresses[addrcur][1] = False addresses[addrcur][1] = False
shared.reloadMyAddressHashes() # Reload address hashes shared.reloadMyAddressHashes() # Reload address hashes
elif t == "6": # Delete address elif t == "6": # Delete address
r, t = d.inputbox("Type in \"I want to delete this address\"", width=50) r, t = d.inputbox("Type in \"I want to delete this address\"", width=50)
if r == d.DIALOG_OK and t == "I want to delete this address": if r == d.DIALOG_OK and t == "I want to delete this address":
BMConfigParser().remove_section(addresses[addrcur][2]) config.remove_section(addresses[addrcur][2])
BMConfigParser().save() config.save()
del addresses[addrcur] del addresses[addrcur]
elif t == "7": # Special address behavior elif t == "7": # Special address behavior
a = addresses[addrcur][2] a = addresses[addrcur][2]
set_background_title(d, "Special address behavior") set_background_title(d, "Special address behavior")
if BMConfigParser().safeGetBoolean(a, "chan"): if config.safeGetBoolean(a, "chan"):
scrollbox(d, unicode( scrollbox(d, unicode(
"This is a chan address. You cannot use it as a pseudo-mailing list.")) "This is a chan address. You cannot use it as a pseudo-mailing list."))
else: else:
m = BMConfigParser().safeGetBoolean(a, "mailinglist") m = config.safeGetBoolean(a, "mailinglist")
r, t = d.radiolist( r, t = d.radiolist(
"Select address behavior", "Select address behavior",
choices=[ choices=[
@ -665,24 +664,24 @@ def handlech(c, stdscr):
("2", "Behave as a pseudo-mailing-list address", m)]) ("2", "Behave as a pseudo-mailing-list address", m)])
if r == d.DIALOG_OK: if r == d.DIALOG_OK:
if t == "1" and m: if t == "1" and m:
BMConfigParser().set(a, "mailinglist", "false") config.set(a, "mailinglist", "false")
if addresses[addrcur][1]: if addresses[addrcur][1]:
addresses[addrcur][3] = 0 # Set color to black addresses[addrcur][3] = 0 # Set color to black
else: else:
addresses[addrcur][3] = 8 # Set color to gray addresses[addrcur][3] = 8 # Set color to gray
elif t == "2" and m is False: elif t == "2" and m is False:
try: try:
mn = BMConfigParser().get(a, "mailinglistname") mn = config.get(a, "mailinglistname")
except ConfigParser.NoOptionError: except ConfigParser.NoOptionError:
mn = "" mn = ""
r, t = d.inputbox("Mailing list name", init=mn) r, t = d.inputbox("Mailing list name", init=mn)
if r == d.DIALOG_OK: if r == d.DIALOG_OK:
mn = t mn = t
BMConfigParser().set(a, "mailinglist", "true") config.set(a, "mailinglist", "true")
BMConfigParser().set(a, "mailinglistname", mn) config.set(a, "mailinglistname", mn)
addresses[addrcur][3] = 6 # Set color to magenta addresses[addrcur][3] = 6 # Set color to magenta
# Write config # Write config
BMConfigParser().save() config.save()
elif menutab == 5: elif menutab == 5:
set_background_title(d, "Subscriptions Dialog Box") set_background_title(d, "Subscriptions Dialog Box")
if len(subscriptions) <= subcur: if len(subscriptions) <= subcur:
@ -1002,7 +1001,7 @@ def loadInbox():
if toaddr == BROADCAST_STR: if toaddr == BROADCAST_STR:
tolabel = BROADCAST_STR tolabel = BROADCAST_STR
else: else:
tolabel = BMConfigParser().get(toaddr, "label") tolabel = config.get(toaddr, "label")
except: # noqa:E722 except: # noqa:E722
tolabel = "" tolabel = ""
if tolabel == "": if tolabel == "":
@ -1011,8 +1010,8 @@ def loadInbox():
# Set label for from address # Set label for from address
fromlabel = "" fromlabel = ""
if BMConfigParser().has_section(fromaddr): if config.has_section(fromaddr):
fromlabel = BMConfigParser().get(fromaddr, "label") fromlabel = config.get(fromaddr, "label")
if fromlabel == "": # Check Address Book if fromlabel == "": # Check Address Book
qr = sqlQuery("SELECT label FROM addressbook WHERE address=?", fromaddr) qr = sqlQuery("SELECT label FROM addressbook WHERE address=?", fromaddr)
if qr != []: if qr != []:
@ -1062,15 +1061,15 @@ def loadSent():
for r in qr: for r in qr:
tolabel, = r tolabel, = r
if tolabel == "": if tolabel == "":
if BMConfigParser().has_section(toaddr): if config.has_section(toaddr):
tolabel = BMConfigParser().get(toaddr, "label") tolabel = config.get(toaddr, "label")
if tolabel == "": if tolabel == "":
tolabel = toaddr tolabel = toaddr
# Set label for from address # Set label for from address
fromlabel = "" fromlabel = ""
if BMConfigParser().has_section(fromaddr): if config.has_section(fromaddr):
fromlabel = BMConfigParser().get(fromaddr, "label") fromlabel = config.get(fromaddr, "label")
if fromlabel == "": if fromlabel == "":
fromlabel = fromaddr fromlabel = fromaddr
@ -1146,7 +1145,7 @@ def loadSubscriptions():
def loadBlackWhiteList(): def loadBlackWhiteList():
"""load black/white list""" """load black/white list"""
global bwtype global bwtype
bwtype = BMConfigParser().get("bitmessagesettings", "blackwhitelist") bwtype = config.get("bitmessagesettings", "blackwhitelist")
if bwtype == "black": if bwtype == "black":
ret = sqlQuery("SELECT label, address, enabled FROM blacklist") ret = sqlQuery("SELECT label, address, enabled FROM blacklist")
else: else:
@ -1205,16 +1204,16 @@ def run(stdscr):
curses.init_pair(9, curses.COLOR_YELLOW, curses.COLOR_BLACK) # orangish curses.init_pair(9, curses.COLOR_YELLOW, curses.COLOR_BLACK) # orangish
# Init list of address in 'Your Identities' tab # Init list of address in 'Your Identities' tab
configSections = BMConfigParser().addresses() configSections = config.addresses()
for addressInKeysFile in configSections: for addressInKeysFile in configSections:
isEnabled = BMConfigParser().getboolean(addressInKeysFile, "enabled") isEnabled = config.getboolean(addressInKeysFile, "enabled")
addresses.append([BMConfigParser().get(addressInKeysFile, "label"), isEnabled, addressInKeysFile]) addresses.append([config.get(addressInKeysFile, "label"), isEnabled, addressInKeysFile])
# Set address color # Set address color
if not isEnabled: if not isEnabled:
addresses[len(addresses) - 1].append(8) # gray addresses[len(addresses) - 1].append(8) # gray
elif BMConfigParser().safeGetBoolean(addressInKeysFile, 'chan'): elif config.safeGetBoolean(addressInKeysFile, 'chan'):
addresses[len(addresses) - 1].append(9) # orange addresses[len(addresses) - 1].append(9) # orange
elif BMConfigParser().safeGetBoolean(addressInKeysFile, 'mailinglist'): elif config.safeGetBoolean(addressInKeysFile, 'mailinglist'):
addresses[len(addresses) - 1].append(5) # magenta addresses[len(addresses) - 1].append(5) # magenta
else: else:
addresses[len(addresses) - 1].append(0) # black addresses[len(addresses) - 1].append(0) # black

View File

@ -0,0 +1,110 @@
# pylint: disable=unused-argument, no-name-in-module, too-few-public-methods
"""
Base class for Navigation Drawer
"""
from kivy.lang import Observable
from kivy.properties import (
BooleanProperty,
NumericProperty,
StringProperty
)
from kivy.metrics import dp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.spinner import Spinner
from kivy.clock import Clock
from kivy.core.window import Window
from kivymd.uix.list import (
OneLineAvatarIconListItem,
OneLineListItem
)
from pybitmessage.bmconfigparser import config
class BaseLanguage(Observable):
"""UI Language"""
observers = []
lang = None
def __init__(self, defaultlang):
super(BaseLanguage, self).__init__()
self.ugettext = None
self.lang = defaultlang
@staticmethod
def _(text):
return text
class BaseNavigationItem(OneLineAvatarIconListItem):
"""NavigationItem class for kivy Ui"""
badge_text = StringProperty()
icon = StringProperty()
active = BooleanProperty(False)
def currentlyActive(self):
"""Currenly active"""
for nav_obj in self.parent.children:
nav_obj.active = False
self.active = True
class BaseNavigationDrawerDivider(OneLineListItem):
"""
A small full-width divider that can be placed
in the :class:`MDNavigationDrawer`
"""
disabled = True
divider = None
_txt_top_pad = NumericProperty(dp(8))
_txt_bot_pad = NumericProperty(dp(8))
def __init__(self, **kwargs):
super(BaseNavigationDrawerDivider, self).__init__(**kwargs)
self.height = dp(16)
class BaseNavigationDrawerSubheader(OneLineListItem):
"""
A subheader for separating content in :class:`MDNavigationDrawer`
Works well alongside :class:`NavigationDrawerDivider`
"""
disabled = True
divider = None
theme_text_color = 'Secondary'
class BaseContentNavigationDrawer(BoxLayout):
"""ContentNavigationDrawer class for kivy Uir"""
def __init__(self, *args, **kwargs):
"""Method used for contentNavigationDrawer"""
super(BaseContentNavigationDrawer, self).__init__(*args, **kwargs)
Clock.schedule_once(self.init_ui, 0)
def init_ui(self, dt=0):
"""Clock Schdule for class contentNavigationDrawer"""
self.ids.scroll_y.bind(scroll_y=self.check_scroll_y)
def check_scroll_y(self, instance, somethingelse):
"""show data on scroll down"""
if self.ids.identity_dropdown.is_open:
self.ids.identity_dropdown.is_open = False
class BaseIdentitySpinner(Spinner):
"""Base Class for Identity Spinner(Dropdown)"""
def __init__(self, *args, **kwargs):
"""Method used for setting size of spinner"""
super(BaseIdentitySpinner, self).__init__(*args, **kwargs)
self.dropdown_cls.max_height = Window.size[1] / 3
self.values = list(addr for addr in config.addresses()
if config.getboolean(str(addr), 'enabled'))

View File

@ -0,0 +1,164 @@
# pylint: disable=unused-argument, consider-using-f-string, import-error
# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module
"""
addressbook.py
==============
All saved addresses are managed in Addressbook
"""
import os
import logging
from functools import partial
from kivy.properties import (
ListProperty,
StringProperty
)
from kivy.uix.screenmanager import Screen
from kivy.app import App
from pybitmessage.bitmessagekivy.get_platform import platform
from pybitmessage.bitmessagekivy import kivy_helper_search
from pybitmessage.bitmessagekivy.baseclass.common import (
avatar_image_first_letter, toast, empty_screen_label,
ThemeClsColor, SwipeToDeleteItem, kivy_state_variables
)
from pybitmessage.bitmessagekivy.baseclass.popup import SavedAddressDetailPopup
from pybitmessage.bitmessagekivy.baseclass.addressbook_widgets import HelperAddressBook
from pybitmessage.helper_sql import sqlExecute
logger = logging.getLogger('default')
class AddressBook(Screen, HelperAddressBook):
"""AddressBook Screen class for kivy Ui"""
queryreturn = ListProperty()
has_refreshed = True
address_label = StringProperty()
address = StringProperty()
label_str = "No contact Address found yet......"
no_search_res_found = "No search result found"
def __init__(self, *args, **kwargs):
"""Getting AddressBook Details"""
super(AddressBook, self).__init__(*args, **kwargs)
self.addbook_popup = None
self.kivy_state = kivy_state_variables()
def loadAddresslist(self, account, where="", what=""):
"""Clock Schdule for method AddressBook"""
if self.kivy_state.searching_text:
self.ids.scroll_y.scroll_y = 1.0
where = ['label', 'address']
what = self.kivy_state.searching_text
xAddress = ''
self.ids.tag_label.text = ''
self.queryreturn = kivy_helper_search.search_sql(
xAddress, account, "addressbook", where, what, False)
self.queryreturn = [obj for obj in reversed(self.queryreturn)]
if self.queryreturn:
self.ids.tag_label.text = 'Address Book'
self.has_refreshed = True
self.set_mdList(0, 20)
self.ids.scroll_y.bind(scroll_y=self.check_scroll_y)
else:
self.ids.ml.add_widget(empty_screen_label(self.label_str, self.no_search_res_found))
def set_mdList(self, start_index, end_index):
"""Creating the mdList"""
for item in self.queryreturn[start_index:end_index]:
message_row = SwipeToDeleteItem(
text=item[0],
)
listItem = message_row.ids.content
listItem.secondary_text = item[1]
listItem.theme_text_color = "Custom"
listItem.text_color = ThemeClsColor
image = os.path.join(
self.kivy_state.imageDir, "text_images", "{}.png".format(avatar_image_first_letter(item[0].strip()))
)
message_row.ids.avater_img.source = image
listItem.bind(on_release=partial(
self.addBook_detail, item[1], item[0], message_row))
message_row.ids.delete_msg.bind(on_press=partial(self.delete_address, item[1]))
self.ids.ml.add_widget(message_row)
def check_scroll_y(self, instance, somethingelse):
"""Load data on scroll"""
if self.ids.scroll_y.scroll_y <= -0.0 and self.has_refreshed:
self.ids.scroll_y.scroll_y = 0.06
exist_addresses = len(self.ids.ml.children)
if exist_addresses != len(self.queryreturn):
self.update_addressBook_on_scroll(exist_addresses)
self.has_refreshed = (
True if exist_addresses != len(self.queryreturn) else False
)
def update_addressBook_on_scroll(self, exist_addresses):
"""Load more data on scroll down"""
self.set_mdList(exist_addresses, exist_addresses + 5)
@staticmethod
def refreshs(*args):
"""Refresh the Widget"""
# @staticmethod
def addBook_detail(self, address, label, instance, *args):
"""Addressbook details"""
if instance.state == 'closed':
instance.ids.delete_msg.disabled = True
if instance.open_progress == 0.0:
obj = SavedAddressDetailPopup()
self.address_label = obj.address_label = label
self.address = obj.address = address
width = .9 if platform == 'android' else .8
self.addbook_popup = self.address_detail_popup(
obj, self.send_message_to, self.update_addbook_label,
self.close_pop, width)
self.addbook_popup.auto_dismiss = False
self.addbook_popup.open()
else:
instance.ids.delete_msg.disabled = False
def delete_address(self, address, instance, *args):
"""Delete inbox mail from inbox listing"""
self.ids.ml.remove_widget(instance.parent.parent)
# if len(self.ids.ml.children) == 0:
if self.ids.ml.children is not None:
self.ids.tag_label.text = ''
sqlExecute(
"DELETE FROM addressbook WHERE address = ?", address)
toast('Address Deleted')
def close_pop(self, instance):
"""Pop is Canceled"""
self.addbook_popup.dismiss()
toast('Canceled')
def update_addbook_label(self, instance):
"""Updating the label of address book address"""
address_list = kivy_helper_search.search_sql(folder="addressbook")
stored_labels = [labels[0] for labels in address_list]
add_dict = dict(address_list)
label = str(self.addbook_popup.content_cls.ids.add_label.text)
if label in stored_labels and self.address == add_dict[label]:
stored_labels.remove(label)
if label and label not in stored_labels:
sqlExecute("""
UPDATE addressbook
SET label = ?
WHERE address = ?""", label, self.addbook_popup.content_cls.address)
App.get_running_app().root.ids.id_addressbook.ids.ml.clear_widgets()
App.get_running_app().root.ids.id_addressbook.loadAddresslist(None, 'All', '')
self.addbook_popup.dismiss()
toast('Saved')
def send_message_to(self, instance):
"""Method used to fill to_address of composer autofield"""
App.get_running_app().set_navbar_for_composer()
self.compose_message(None, self.address)
self.addbook_popup.dismiss()

View File

@ -0,0 +1,50 @@
# pylint: disable=no-member, too-many-arguments, too-few-public-methods
"""
Addressbook widgets are here.
"""
from kivy.app import App
from kivymd.uix.button import MDRaisedButton
from kivymd.uix.dialog import MDDialog
class HelperAddressBook(object):
"""Widget used in Addressbook are here"""
@staticmethod
def address_detail_popup(obj, send_message, update_address, close_popup, width):
"""This function shows the address's details and opens the popup."""
show_dialogue = MDDialog(
type="custom",
size_hint=(width, .25),
content_cls=obj,
buttons=[
MDRaisedButton(
text="Send message to",
on_release=send_message,
),
MDRaisedButton(
text="Save",
on_release=update_address,
),
MDRaisedButton(
text="Cancel",
on_release=close_popup,
),
],
)
return show_dialogue
@staticmethod
def compose_message(from_addr=None, to_addr=None):
"""This UI independent method for message sending to reciever"""
window_obj = App.get_runnint_app().root.ids
if to_addr:
window_obj.id_create.children[1].ids.txt_input.text = to_addr
if from_addr:
window_obj.id_create.children[1].ids.txt_input.text = from_addr
window_obj.id_create.children[1].ids.ti.text = ''
window_obj.id_create.children[1].ids.composer_dropdown.text = 'Select'
window_obj.id_create.children[1].ids.subject.text = ''
window_obj.id_create.children[1].ids.body.text = ''
window_obj.scr_mngr.current = 'create'

View File

@ -0,0 +1,67 @@
# pylint: disable=import-error, no-name-in-module
# pylint: disable=unused-argument, no-member, attribute-defined-outside-init
"""
allmail.py
==============
All mails are managed in allmail screen
"""
from kivy.clock import Clock
from kivy.properties import (
ListProperty,
StringProperty
)
from kivy.uix.screenmanager import Screen
from kivy.app import App
from pybitmessage.bitmessagekivy.baseclass.common import (
show_limited_cnt, empty_screen_label, kivy_state_variables,
)
import logging
logger = logging.getLogger('default')
class Allmails(Screen):
"""Allmails Screen for kivy Ui"""
data = ListProperty()
has_refreshed = True
all_mails = ListProperty()
account = StringProperty()
label_str = 'yet no message for this account!!!!!!!!!!!!!'
def __init__(self, *args, **kwargs):
"""Method Parsing the address"""
super(Allmails, self).__init__(*args, **kwargs)
self.kivy_state = kivy_state_variables()
if self.kivy_state.selected_address == '':
if App.get_running_app().identity_list:
self.kivy_state.selected_address = App.get_running_app().identity_list[0]
Clock.schedule_once(self.init_ui, 0)
def init_ui(self, dt=0):
"""Clock Schdule for method all mails"""
self.loadMessagelist()
logger.debug(dt)
def loadMessagelist(self):
"""Load Inbox, Sent anf Draft list of messages"""
self.account = self.kivy_state.selected_address
self.ids.tag_label.text = ''
if self.all_mails:
self.ids.tag_label.text = 'All Mails'
self.kivy_state.all_count = str(
int(self.kivy_state.sent_count) + int(self.kivy_state.inbox_count))
self.set_AllmailCnt(self.kivy_state.all_count)
else:
self.set_AllmailCnt('0')
self.ids.ml.add_widget(empty_screen_label(self.label_str))
@staticmethod
def set_AllmailCnt(Count):
"""This method is used to set allmails message count"""
allmailCnt_obj = App.get_running_app().root.ids.content_drawer.ids.allmail_cnt
allmailCnt_obj.ids.badge_txt.text = show_limited_cnt(int(Count))

View File

@ -0,0 +1,11 @@
# pylint: disable=import-error, no-name-in-module, too-few-public-methods, too-many-ancestors
'''
Chats are managed in this screen
'''
from kivy.uix.screenmanager import Screen
class Chat(Screen):
"""Chat Screen class for kivy Ui"""

View File

@ -0,0 +1,236 @@
# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error, unused-argument
"""
All Common widgets of kivy are managed here.
"""
import os
from datetime import datetime
from kivy.core.window import Window
from kivy.metrics import dp
from kivy.uix.image import Image
from kivy.properties import (
NumericProperty,
StringProperty,
ListProperty
)
from kivy.app import App
from kivymd.uix.list import (
ILeftBody,
IRightBodyTouch,
)
from kivymd.uix.label import MDLabel
from kivymd.toast import kivytoast
from kivymd.uix.card import MDCardSwipe
from kivymd.uix.chip import MDChip
from kivymd.uix.dialog import MDDialog
from kivymd.uix.button import MDFlatButton
from pybitmessage.bitmessagekivy.get_platform import platform
from pybitmessage.bmconfigparser import config
ThemeClsColor = [0.12, 0.58, 0.95, 1]
data_screens = {
"MailDetail": {
"kv_string": "maildetail",
"Factory": "MailDetail()",
"name_screen": "mailDetail",
"object": 0,
"Import": "from pybitmessage.bitmessagekivy.baseclass.maildetail import MailDetail",
},
}
def load_image_path():
"""Return the path of kivy images"""
image_path = os.path.abspath(os.path.join('pybitmessage', 'images', 'kivy'))
return image_path
def get_identity_list():
"""Get list of identities and access 'identity_list' variable in .kv file"""
identity_list = ListProperty(
addr for addr in config.addresses() if config.getboolean(str(addr), 'enabled')
)
return identity_list
def kivy_state_variables():
"""Return kivy_state variable"""
kivy_running_app = App.get_running_app()
kivy_state = kivy_running_app.kivy_state_obj
return kivy_state
def chip_tag(text):
"""Create a new ChipTag"""
obj = MDChip()
# obj.size_hint = (None, None)
obj.size_hint = (0.16 if platform == "android" else 0.08, None)
obj.text = text
obj.icon = ""
obj.pos_hint = {
"center_x": 0.91 if platform == "android" else 0.94,
"center_y": 0.3
}
obj.height = dp(18)
obj.text_color = (1, 1, 1, 1)
obj.radius = [8]
return obj
def toast(text):
"""Method will display the toast message"""
kivytoast.toast(text)
def show_limited_cnt(total_msg):
"""This method set the total count limit in badge_text"""
max_msg_count = '99+'
total_msg_limit = 99
return max_msg_count if total_msg > total_msg_limit else str(total_msg)
def avatar_image_first_letter(letter_string):
"""Returns first letter for the avatar image"""
try:
image_letter = letter_string.title()[0]
if image_letter.isalnum():
return image_letter
return '!'
except IndexError:
return '!'
def add_time_widget(time): # pylint: disable=redefined-outer-name, W0201
"""This method is used to create TimeWidget"""
action_time = TimeTagRightSampleWidget(
text=str(show_time_history(time)),
font_style="Caption",
size=[120, 140] if platform == "android" else [64, 80],
)
action_time.font_size = "11sp"
return action_time
def show_time_history(act_time):
"""This method is used to return the message sent or receive time"""
action_time = datetime.fromtimestamp(int(act_time))
crnt_date = datetime.now()
duration = crnt_date - action_time
if duration.days < 1:
return action_time.strftime("%I:%M %p")
if duration.days < 365:
return action_time.strftime("%d %b")
return action_time.strftime("%d/%m/%Y")
# pylint: disable=too-few-public-methods
class AvatarSampleWidget(ILeftBody, Image):
"""AvatarSampleWidget class for kivy Ui"""
class TimeTagRightSampleWidget(IRightBodyTouch, MDLabel):
"""TimeTagRightSampleWidget class for Ui"""
class SwipeToDeleteItem(MDCardSwipe):
"""Swipe delete class for App UI"""
text = StringProperty()
cla = Window.size[0] / 2
# cla = 800
swipe_distance = NumericProperty(cla)
opening_time = NumericProperty(0.5)
class CustomSwipeToDeleteItem(MDCardSwipe):
"""Custom swipe delete class for App UI"""
text = StringProperty()
cla = Window.size[0] / 2
swipe_distance = NumericProperty(cla)
opening_time = NumericProperty(0.5)
def empty_screen_label(label_str=None, no_search_res_found=None):
"""Returns default text on screen when no address is there."""
kivy_state = kivy_state_variables()
content = MDLabel(
font_style='Caption',
theme_text_color='Primary',
text=no_search_res_found if kivy_state.searching_text else label_str,
halign='center',
size_hint_y=None,
valign='top')
return content
def retrieve_secondary_text(mail):
"""Retriving mail details"""
secondary_txt_len = 10
third_txt_len = 25
dot_str = '...........'
dot_str2 = '...!'
third_text = mail[3].replace('\n', ' ')
if len(third_text) > third_txt_len:
if len(mail[2]) > secondary_txt_len: # pylint: disable=no-else-return
return mail[2][:secondary_txt_len] + dot_str
else:
return mail[2] + '\n' + " " + (third_text[:third_txt_len] + dot_str2)
else:
return third_text
def set_mail_details(mail):
"""Setting mail details"""
mail_details_data = {
'text': mail[1].strip(),
'secondary_text': retrieve_secondary_text(mail),
'ackdata': mail[5],
'senttime': mail[6]
}
return mail_details_data
def mdlist_message_content(queryreturn, data):
"""Set Mails details in MD_list"""
for mail in queryreturn:
mdlist_data = set_mail_details(mail)
data.append(mdlist_data)
def msg_content_length(body, subject, max_length=50):
"""This function concatinate body and subject if len(subject) > 50"""
continue_str = '........'
if len(subject) >= max_length:
subject = subject[:max_length] + continue_str
else:
subject = ((subject + ',' + body)[0:50] + continue_str).replace('\t', '').replace(' ', '')
return subject
def composer_common_dialog(alert_msg):
"""Common alert popup for message composer"""
is_android_width = .8
other_platform_width = .55
dialog_height = .25
width = is_android_width if platform == 'android' else other_platform_width
dialog_box = MDDialog(
text=alert_msg,
size_hint=(width, dialog_height),
buttons=[
MDFlatButton(
text="Ok", on_release=lambda x: callback_for_menu_items("Ok")
),
],
)
dialog_box.open()
def callback_for_menu_items(text_item, *arg):
"""Callback of alert box"""
dialog_box.dismiss()
toast(text_item)

View File

@ -0,0 +1,22 @@
# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error
"""
All Common widgets of kivy are managed here.
"""
from pybitmessage.bitmessagekivy.baseclass.maildetail import MailDetail
from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables
def mail_detail_screen(screen_name, msg_id, instance, folder, *args): # pylint: disable=unused-argument
"""Common function for all screens to open Mail detail."""
kivy_state = kivy_state_variables()
if instance.open_progress == 0.0:
kivy_state.detail_page_type = folder
kivy_state.mail_id = msg_id
if screen_name.manager:
src_mng_obj = screen_name.manager
else:
src_mng_obj = screen_name.parent.parent
src_mng_obj.screens[11].clear_widgets()
src_mng_obj.screens[11].add_widget(MailDetail())
src_mng_obj.current = "mailDetail"

View File

@ -0,0 +1,58 @@
# pylint: disable=unused-argument, import-error, too-many-arguments
# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module
"""
draft.py
==============
Draft screen
"""
from kivy.clock import Clock
from kivy.properties import (
ListProperty,
StringProperty
)
from kivy.uix.screenmanager import Screen
from kivy.app import App
from pybitmessage.bitmessagekivy.baseclass.common import (
show_limited_cnt, empty_screen_label,
kivy_state_variables
)
import logging
logger = logging.getLogger('default')
class Draft(Screen):
"""Draft screen class for kivy Ui"""
data = ListProperty()
account = StringProperty()
queryreturn = ListProperty()
has_refreshed = True
label_str = "yet no message for this account!!!!!!!!!!!!!"
def __init__(self, *args, **kwargs):
"""Method used for storing draft messages"""
super(Draft, self).__init__(*args, **kwargs)
self.kivy_state = kivy_state_variables()
if self.kivy_state.selected_address == '':
if App.get_running_app().identity_list:
self.kivy_state.selected_address = App.get_running_app().identity_list[0]
Clock.schedule_once(self.init_ui, 0)
def init_ui(self, dt=0):
"""Clock Schedule for method draft accounts"""
self.load_draft()
logger.debug(dt)
def load_draft(self, where="", what=""):
"""Load draft list for Draft messages"""
self.set_draft_count('0')
self.ids.ml.add_widget(empty_screen_label(self.label_str))
@staticmethod
def set_draft_count(Count):
"""Set the count of draft mails"""
draftCnt_obj = App.get_running_app().root.ids.content_drawer.ids.draft_cnt
draftCnt_obj.ids.badge_txt.text = show_limited_cnt(int(Count))

View File

@ -0,0 +1,64 @@
# pylint: disable=unused-import, too-many-public-methods, unused-variable, too-many-ancestors
# pylint: disable=no-name-in-module, too-few-public-methods, import-error, unused-argument, too-many-arguments
# pylint: disable=attribute-defined-outside-init, global-variable-not-assigned, too-many-instance-attributes
"""
Kivy UI for inbox screen
"""
from kivy.clock import Clock
from kivy.properties import (
ListProperty,
StringProperty
)
from kivy.app import App
from kivy.uix.screenmanager import Screen
from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables, load_image_path
class Inbox(Screen):
"""Inbox Screen class for kivy Ui"""
queryreturn = ListProperty()
has_refreshed = True
account = StringProperty()
no_search_res_found = "No search result found"
label_str = "Yet no message for this account!"
def __init__(self, *args, **kwargs):
"""Initialize kivy variables"""
super(Inbox, self).__init__(*args, **kwargs)
self.kivy_running_app = App.get_running_app()
self.kivy_state = kivy_state_variables()
self.image_dir = load_image_path()
Clock.schedule_once(self.init_ui, 0)
def set_defaultAddress(self):
"""Set default address"""
if self.kivy_state.selected_address == "":
if self.kivy_running_app.identity_list:
self.kivy_state.selected_address = self.kivy_running_app.identity_list[0]
def init_ui(self, dt=0):
"""loadMessagelist() call at specific interval"""
self.loadMessagelist()
def loadMessagelist(self, where="", what=""):
"""Load inbox list for inbox messages"""
self.set_defaultAddress()
self.account = self.kivy_state.selected_address
def refresh_callback(self, *args):
"""Load inbox messages while wating-loader spins & called in inbox.kv"""
def refresh_on_scroll_down(interval):
"""Reset fields and load data on scrolling upside down"""
self.kivy_state.searching_text = ""
self.children[2].children[1].ids.search_field.text = ""
self.ids.ml.clear_widgets()
self.loadMessagelist(self.kivy_state.selected_address)
self.has_refreshed = True
self.ids.refresh_layout.refresh_done()
self.tick = 0
Clock.schedule_once(refresh_on_scroll_down, 1)

View File

@ -0,0 +1,97 @@
# pylint: disable=no-member, too-many-arguments, too-few-public-methods
# pylint: disable=no-name-in-module, unused-argument, arguments-differ
"""
Login screen appears when the App is first time starts and when new Address is generated.
"""
from kivy.clock import Clock
from kivy.properties import StringProperty, BooleanProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import Screen
from kivy.app import App
from kivymd.uix.behaviors.elevation import RectangularElevationBehavior
from pybitmessage.backend.address_generator import AddressGenerator
from pybitmessage.bitmessagekivy.baseclass.common import toast
from pybitmessage.bmconfigparser import config
class Login(Screen):
"""Login Screeen class for kivy Ui"""
log_text1 = (
'You may generate addresses by using either random numbers'
' or by using a passphrase If you use a passphrase, the address'
' is called a deterministic; address The Random Number option is'
' selected by default but deterministic addresses have several pros'
' and cons:')
log_text2 = ('If talk about pros You can recreate your addresses on any computer'
' from memory, You need-not worry about backing up your keys.dat file'
' as long as you can remember your passphrase and aside talk about cons'
' You must remember (or write down) your You must remember the address'
' version number and the stream number along with your passphrase If you'
' choose a weak passphrase and someone on the Internet can brute-force it,'
' they can read your messages and send messages as you')
class Random(Screen):
"""Random Screen class for Ui"""
is_active = BooleanProperty(False)
checked = StringProperty("")
def generateaddress(self):
"""Method for Address Generator"""
entered_label = str(self.ids.add_random_bx.children[0].ids.lab.text).strip()
if not entered_label:
self.ids.add_random_bx.children[0].ids.lab.focus = True
is_address = AddressGenerator.random_address_generation(
entered_label, streamNumberForAddress=1, eighteenByteRipe=False,
nonceTrialsPerByte=1000, payloadLengthExtraBytes=1000
)
if is_address:
toast('Creating New Address ...')
self.parent.parent.ids.toolbar.opacity = 1
self.parent.parent.ids.toolbar.disabled = False
App.get_running_app().loadMyAddressScreen(True)
self.manager.current = 'myaddress'
Clock.schedule_once(self.address_created_callback, 6)
def address_created_callback(self, dt=0):
"""New address created"""
App.get_running_app().loadMyAddressScreen(False)
App.get_running_app().root.ids.id_myaddress.ids.ml.clear_widgets()
App.get_running_app().root.ids.id_myaddress.is_add_created = True
App.get_running_app().root.ids.id_myaddress.init_ui()
self.reset_address_spinner()
toast('New address created')
def reset_address_spinner(self):
"""reseting spinner address and UI"""
addresses = [addr for addr in config.addresses()
if config.getboolean(str(addr), 'enabled')]
self.manager.parent.ids.content_drawer.ids.identity_dropdown.values = []
self.manager.parent.ids.content_drawer.ids.identity_dropdown.values = addresses
self.manager.parent.ids.id_create.children[1].ids.composer_dropdown.values = []
self.manager.parent.ids.id_create.children[1].ids.composer_dropdown.values = addresses
@staticmethod
def add_validation(instance):
"""Retrieve created labels and validate"""
entered_label = str(instance.text.strip())
AddressGenerator.address_validation(instance, entered_label)
def reset_address_label(self):
"""Resetting address labels"""
if not self.ids.add_random_bx.children:
self.ids.add_random_bx.add_widget(RandomBoxlayout())
class InfoLayout(BoxLayout, RectangularElevationBehavior):
"""InfoLayout class for kivy Ui"""
class RandomBoxlayout(BoxLayout):
"""RandomBoxlayout class for BoxLayout behaviour"""

View File

@ -0,0 +1,242 @@
# pylint: disable=unused-argument, consider-using-f-string, import-error, attribute-defined-outside-init
# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module, too-few-public-methods
"""
Maildetail screen for inbox, sent, draft and trash.
"""
import os
from datetime import datetime
from kivy.core.clipboard import Clipboard
from kivy.clock import Clock
from kivy.properties import (
StringProperty,
NumericProperty
)
from kivy.uix.screenmanager import Screen
from kivy.factory import Factory
from kivy.app import App
from kivymd.uix.button import MDFlatButton, MDIconButton
from kivymd.uix.dialog import MDDialog
from kivymd.uix.list import (
OneLineListItem,
IRightBodyTouch
)
from pybitmessage.bitmessagekivy.baseclass.common import (
toast, avatar_image_first_letter, show_time_history, kivy_state_variables
)
from pybitmessage.bitmessagekivy.baseclass.popup import SenderDetailPopup
from pybitmessage.bitmessagekivy.get_platform import platform
from pybitmessage.helper_sql import sqlQuery
class OneLineListTitle(OneLineListItem):
"""OneLineListTitle class for kivy Ui"""
__events__ = ('on_long_press', )
long_press_time = NumericProperty(1)
def on_state(self, instance, value):
"""On state"""
if value == 'down':
lpt = self.long_press_time
self._clockev = Clock.schedule_once(self._do_long_press, lpt)
else:
self._clockev.cancel()
def _do_long_press(self, dt):
"""Do long press"""
self.dispatch('on_long_press')
def on_long_press(self, *largs):
"""On long press"""
self.copymessageTitle(self.text)
def copymessageTitle(self, title_text):
"""this method is for displaying dialog box"""
self.title_text = title_text
width = .8 if platform == 'android' else .55
self.dialog_box = MDDialog(
text=title_text,
size_hint=(width, .25),
buttons=[
MDFlatButton(
text="Copy", on_release=self.callback_for_copy_title
),
MDFlatButton(
text="Cancel", on_release=self.callback_for_copy_title,
),
],)
self.dialog_box.open()
def callback_for_copy_title(self, instance):
"""Callback of alert box"""
if instance.text == 'Copy':
Clipboard.copy(self.title_text)
self.dialog_box.dismiss()
toast(instance.text)
class IconRightSampleWidget(IRightBodyTouch, MDIconButton):
"""IconRightSampleWidget class for kivy Ui"""
class MailDetail(Screen): # pylint: disable=too-many-instance-attributes
"""MailDetail Screen class for kivy Ui"""
to_addr = StringProperty()
from_addr = StringProperty()
subject = StringProperty()
message = StringProperty()
status = StringProperty()
page_type = StringProperty()
time_tag = StringProperty()
avatarImg = StringProperty()
no_subject = '(no subject)'
def __init__(self, *args, **kwargs):
"""Mail Details method"""
super(MailDetail, self).__init__(*args, **kwargs)
self.kivy_state = kivy_state_variables()
Clock.schedule_once(self.init_ui, 0)
def init_ui(self, dt=0):
"""Clock Schdule for method MailDetail mails"""
self.page_type = self.kivy_state.detail_page_type if self.kivy_state.detail_page_type else ''
try:
if self.kivy_state.detail_page_type in ('sent', 'draft'):
App.get_running_app().set_mail_detail_header()
elif self.kivy_state.detail_page_type == 'inbox':
data = sqlQuery(
"select toaddress, fromaddress, subject, message, received from inbox"
" where msgid = ?", self.kivy_state.mail_id)
self.assign_mail_details(data)
App.get_running_app().set_mail_detail_header()
except Exception as e: # pylint: disable=unused-variable
print('Something wents wrong!!')
def assign_mail_details(self, data):
"""Assigning mail details"""
subject = data[0][2].decode() if isinstance(data[0][2], bytes) else data[0][2]
body = data[0][3].decode() if isinstance(data[0][2], bytes) else data[0][3]
self.to_addr = data[0][0] if len(data[0][0]) > 4 else ' '
self.from_addr = data[0][1]
self.subject = subject.capitalize(
) if subject.capitalize() else self.no_subject
self.message = body
if len(data[0]) == 7:
self.status = data[0][4]
self.time_tag = show_time_history(data[0][4]) if self.kivy_state.detail_page_type == 'inbox' \
else show_time_history(data[0][6])
self.avatarImg = os.path.join(self.kivy_state.imageDir, 'draft-icon.png') \
if self.kivy_state.detail_page_type == 'draft' \
else (os.path.join(self.kivy_state.imageDir, 'text_images', '{0}.png'.format(avatar_image_first_letter(
self.subject.strip()))))
self.timeinseconds = data[0][4] if self.kivy_state.detail_page_type == 'inbox' else data[0][6]
def delete_mail(self):
"""Method for mail delete"""
msg_count_objs = App.get_running_app().root.ids.content_drawer.ids
self.kivy_state.searching_text = ''
self.children[0].children[0].active = True
if self.kivy_state.detail_page_type == 'sent':
App.get_running_app().root.ids.id_sent.ids.sent_search.ids.search_field.text = ''
msg_count_objs.send_cnt.ids.badge_txt.text = str(int(self.kivy_state.sent_count) - 1)
self.kivy_state.sent_count = str(int(self.kivy_state.sent_count) - 1)
self.parent.screens[2].ids.ml.clear_widgets()
self.parent.screens[2].loadSent(self.kivy_state.selected_address)
elif self.kivy_state.detail_page_type == 'inbox':
App.get_running_app().root.ids.id_inbox.ids.inbox_search.ids.search_field.text = ''
msg_count_objs.inbox_cnt.ids.badge_txt.text = str(
int(self.kivy_state.inbox_count) - 1)
self.kivy_state.inbox_count = str(int(self.kivy_state.inbox_count) - 1)
self.parent.screens[0].ids.ml.clear_widgets()
self.parent.screens[0].loadMessagelist(self.kivy_state.selected_address)
elif self.kivy_state.detail_page_type == 'draft':
msg_count_objs.draft_cnt.ids.badge_txt.text = str(
int(self.kivy_state.draft_count) - 1)
self.kivy_state.draft_count = str(int(self.kivy_state.draft_count) - 1)
self.parent.screens[13].clear_widgets()
self.parent.screens[13].add_widget(Factory.Draft())
if self.kivy_state.detail_page_type != 'draft':
msg_count_objs.trash_cnt.ids.badge_txt.text = str(
int(self.kivy_state.trash_count) + 1)
msg_count_objs.allmail_cnt.ids.badge_txt.text = str(
int(self.kivy_state.all_count) - 1)
self.kivy_state.trash_count = str(int(self.kivy_state.trash_count) + 1)
self.kivy_state.all_count = str(int(self.kivy_state.all_count) - 1) if \
int(self.kivy_state.all_count) else '0'
self.parent.screens[3].clear_widgets()
self.parent.screens[3].add_widget(Factory.Trash())
self.parent.screens[14].clear_widgets()
self.parent.screens[14].add_widget(Factory.Allmails())
Clock.schedule_once(self.callback_for_delete, 4)
def callback_for_delete(self, dt=0):
"""Delete method from allmails"""
if self.kivy_state.detail_page_type:
self.children[0].children[0].active = False
App.get_running_app().set_common_header()
self.parent.current = 'allmails' \
if self.kivy_state.is_allmail else self.kivy_state.detail_page_type
self.kivy_state.detail_page_type = ''
toast('Deleted')
def get_message_details_to_reply(self, data):
"""Getting message details and fill into fields when reply"""
sender_address = ' wrote:--------------\n'
message_time = '\n\n --------------On '
composer_obj = self.parent.screens[1].children[1].ids
composer_obj.ti.text = data[0][0]
composer_obj.composer_dropdown.text = data[0][0]
composer_obj.txt_input.text = data[0][1]
split_subject = data[0][2].split('Re:', 1)
composer_obj.subject.text = 'Re: ' + (split_subject[1] if len(split_subject) > 1 else split_subject[0])
time_obj = datetime.fromtimestamp(int(data[0][4]))
time_tag = time_obj.strftime("%d %b %Y, %I:%M %p")
sender_name = data[0][1]
composer_obj.body.text = (
message_time + time_tag + ', ' + sender_name + sender_address + data[0][3])
composer_obj.body.focus = True
composer_obj.body.cursor = (0, 0)
def inbox_reply(self):
"""Reply inbox messages"""
self.kivy_state.in_composer = True
App.get_running_app().root.ids.id_create.children[1].ids.rv.data = ''
App.get_running_app().root.ids.sc3.children[1].ids.rv.data = ''
self.parent.current = 'create'
App.get_running_app().set_navbar_for_composer()
def get_message_details_for_draft_reply(self, data):
"""Getting and setting message details fill into fields when draft reply"""
composer_ids = (
self.parent.parent.ids.id_create.children[1].ids)
composer_ids.ti.text = data[0][1]
composer_ids.btn.text = data[0][1]
composer_ids.txt_input.text = data[0][0]
composer_ids.subject.text = data[0][2] if data[0][2] != self.no_subject else ''
composer_ids.body.text = data[0][3]
def write_msg(self, navApp):
"""Write on draft mail"""
self.kivy_state.send_draft_mail = self.kivy_state.mail_id
self.parent.current = 'create'
navApp.set_navbar_for_composer()
def detailedPopup(self):
"""Detailed popup"""
obj = SenderDetailPopup()
obj.open()
arg = (self.to_addr, self.from_addr, self.timeinseconds)
obj.assignDetail(*arg)
@staticmethod
def callback_for_menu_items(text_item, *arg):
"""Callback of alert box"""
toast(text_item)

View File

@ -0,0 +1,188 @@
# pylint: disable=unused-argument, consider-using-f-string, too-many-ancestors
# pylint: disable=no-member, no-name-in-module, too-few-public-methods, no-name-in-module
"""
Message composer screen UI
"""
import logging
from kivy.app import App
from kivy.properties import (
BooleanProperty,
ListProperty,
NumericProperty,
ObjectProperty,
)
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.screenmanager import Screen
from kivymd.uix.textfield import MDTextField
from pybitmessage import state
from pybitmessage.bitmessagekivy.get_platform import platform
from pybitmessage.bitmessagekivy.baseclass.common import (
toast, kivy_state_variables, composer_common_dialog
)
logger = logging.getLogger('default')
class Create(Screen):
"""Creates Screen class for kivy Ui"""
def __init__(self, **kwargs):
"""Getting Labels and address from addressbook"""
super(Create, self).__init__(**kwargs)
self.kivy_running_app = App.get_running_app()
self.kivy_state = kivy_state_variables()
self.dropdown_widget = DropDownWidget()
self.dropdown_widget.ids.txt_input.starting_no = 2
self.add_widget(self.dropdown_widget)
self.children[0].ids.id_scroll.bind(scroll_y=self.check_scroll_y)
def check_scroll_y(self, instance, somethingelse): # pylint: disable=unused-argument
"""show data on scroll down"""
if self.children[1].ids.composer_dropdown.is_open:
self.children[1].ids.composer_dropdown.is_open = False
class RV(RecycleView):
"""Recycling View class for kivy Ui"""
def __init__(self, **kwargs):
"""Recycling Method"""
super(RV, self).__init__(**kwargs)
class SelectableRecycleBoxLayout(
FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout
):
"""Adds selection and focus behaviour to the view"""
# pylint: disable = duplicate-bases
class DropDownWidget(BoxLayout):
"""DropDownWidget class for kivy Ui"""
# pylint: disable=too-many-statements
txt_input = ObjectProperty()
rv = ObjectProperty()
def __init__(self, **kwargs):
super(DropDownWidget, self).__init__(**kwargs)
self.kivy_running_app = App.get_running_app()
self.kivy_state = kivy_state_variables()
@staticmethod
def callback_for_msgsend(dt=0): # pylint: disable=unused-argument
"""Callback method for messagesend"""
state.kivyapp.root.ids.id_create.children[0].active = False
state.in_sent_method = True
state.kivyapp.back_press()
toast("sent")
def reset_composer(self):
"""Method will reset composer"""
self.ids.ti.text = ""
self.ids.composer_dropdown.text = "Select"
self.ids.txt_input.text = ""
self.ids.subject.text = ""
self.ids.body.text = ""
toast("Reset message")
def auto_fill_fromaddr(self):
"""Fill the text automatically From Address"""
self.ids.ti.text = self.ids.composer_dropdown.text
self.ids.ti.focus = True
def is_camara_attached(self):
"""Checks the camera availability in device"""
self.parent.parent.parent.ids.id_scanscreen.check_camera()
is_available = self.parent.parent.parent.ids.id_scanscreen.camera_available
return is_available
@staticmethod
def camera_alert():
"""Show camera availability alert message"""
feature_unavailable = 'Currently this feature is not available!'
cam_not_available = 'Camera is not available!'
alert_text = feature_unavailable if platform == 'android' else cam_not_available
composer_common_dialog(alert_text)
class MyTextInput(MDTextField):
"""MyTextInput class for kivy Ui"""
txt_input = ObjectProperty()
flt_list = ObjectProperty()
word_list = ListProperty()
starting_no = NumericProperty(3)
suggestion_text = ''
def __init__(self, **kwargs):
"""Getting Text Input."""
super(MyTextInput, self).__init__(**kwargs)
self.__lineBreak__ = 0
def on_text(self, instance, value): # pylint: disable=unused-argument
"""Find all the occurrence of the word"""
self.parent.parent.parent.parent.parent.ids.rv.data = []
max_recipient_len = 10
box_height = 250
box_max_height = 400
matches = [self.word_list[i] for i in range(
len(self.word_list)) if self.word_list[
i][:self.starting_no] == value[:self.starting_no]]
display_data = []
for i in matches:
display_data.append({'text': i})
self.parent.parent.parent.parent.parent.ids.rv.data = display_data
if len(matches) <= max_recipient_len:
self.parent.height = (box_height + (len(matches) * 20))
else:
self.parent.height = box_max_height
def keyboard_on_key_down(self, window, keycode, text, modifiers):
"""Keyboard on key Down"""
if self.suggestion_text and keycode[1] == 'tab' and modifiers is None:
self.insert_text(self.suggestion_text + ' ')
return True
return super(MyTextInput, self).keyboard_on_key_down(
window, keycode, text, modifiers)
class SelectableLabel(RecycleDataViewBehavior, Label):
"""Add selection support to the Label"""
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
"""Catch and handle the view changes"""
self.index = index
return super(SelectableLabel, self).refresh_view_attrs(rv, index, data)
def on_touch_down(self, touch): # pylint: disable=inconsistent-return-statements
"""Add selection on touch down"""
if super(SelectableLabel, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
"""Respond to the selection of items in the view"""
self.selected = is_selected
if is_selected:
logger.debug("selection changed to %s", rv.data[index])
rv.parent.txt_input.text = rv.parent.txt_input.text.replace(
rv.parent.txt_input.text, rv.data[index]["text"]
)

View File

@ -0,0 +1,230 @@
# pylint: disable=unused-argument, import-error, no-member, attribute-defined-outside-init
# pylint: disable=no-name-in-module, too-few-public-methods, too-many-instance-attributes
"""
myaddress.py
==============
All generated addresses are managed in MyAddress
"""
import os
from functools import partial
from kivy.clock import Clock
from kivy.properties import (
ListProperty,
StringProperty
)
from kivy.uix.screenmanager import Screen, ScreenManagerException
from kivy.app import App
from kivymd.uix.list import (
IRightBodyTouch,
TwoLineAvatarIconListItem,
)
from kivymd.uix.selectioncontrol import MDSwitch
from pybitmessage.bmconfigparser import config
from pybitmessage.bitmessagekivy.get_platform import platform
from pybitmessage.bitmessagekivy.baseclass.common import (
avatar_image_first_letter, AvatarSampleWidget, ThemeClsColor,
toast, empty_screen_label, load_image_path
)
from pybitmessage.bitmessagekivy.baseclass.popup import MyaddDetailPopup
from pybitmessage.bitmessagekivy.baseclass.myaddress_widgets import HelperMyAddress
class ToggleBtn(IRightBodyTouch, MDSwitch):
"""ToggleBtn class for kivy UI"""
class CustomTwoLineAvatarIconListItem(TwoLineAvatarIconListItem):
"""CustomTwoLineAvatarIconListItem class for kivy Ui"""
class MyAddress(Screen, HelperMyAddress):
"""MyAddress screen class for kivy Ui"""
address_label = StringProperty()
text_address = StringProperty()
addresses_list = ListProperty()
has_refreshed = True
is_add_created = False
label_str = "Yet no address is created by user!!!!!!!!!!!!!"
no_search_res_found = "No search result found"
min_scroll_y_limit = -0.0
scroll_y_step = 0.06
number_of_addresses = 20
addresses_at_a_time = 15
canvas_color_black = [0, 0, 0, 0]
canvas_color_gray = [0.5, 0.5, 0.5, 0.5]
is_android_width = .9
other_platform_width = .6
disabled_addr_width = .8
other_platform_disabled_addr_width = .55
max_scroll_limit = 1.0
def __init__(self, *args, **kwargs):
"""Clock schdule for method Myaddress accounts"""
super(MyAddress, self).__init__(*args, **kwargs)
self.image_dir = load_image_path()
self.kivy_running_app = App.get_running_app()
self.kivy_state = self.kivy_running_app.kivy_state_obj
Clock.schedule_once(self.init_ui, 0)
def init_ui(self, dt=0):
"""Clock schdule for method Myaddress accounts"""
self.addresses_list = config.addresses()
if self.kivy_state.searching_text:
self.ids.refresh_layout.scroll_y = self.max_scroll_limit
filtered_list = [
x for x in config.addresses()
if self.filter_address(x)
]
self.addresses_list = filtered_list
self.addresses_list = [obj for obj in reversed(self.addresses_list)]
self.ids.tag_label.text = ''
if self.addresses_list:
self.ids.tag_label.text = 'My Addresses'
self.has_refreshed = True
self.set_mdList(0, self.addresses_at_a_time)
self.ids.refresh_layout.bind(scroll_y=self.check_scroll_y)
else:
self.ids.ml.add_widget(empty_screen_label(self.label_str, self.no_search_res_found))
if not self.kivy_state.searching_text and not self.is_add_created:
try:
self.manager.current = 'login'
except ScreenManagerException:
pass
def get_address_list(self, first_index, last_index, data):
"""Getting address and append to the list"""
for address in self.addresses_list[first_index:last_index]:
data.append({
'text': config.get(address, 'label'),
'secondary_text': address}
)
return data
def set_address_to_widget(self, item):
"""Setting address to the widget"""
is_enable = config.getboolean(item['secondary_text'], 'enabled')
meny = CustomTwoLineAvatarIconListItem(
text=item['text'], secondary_text=item['secondary_text'],
theme_text_color='Custom' if is_enable else 'Primary',
text_color=ThemeClsColor,)
meny.canvas.children[3].rgba = \
self.canvas_color_black if is_enable else self.canvas_color
meny.add_widget(AvatarSampleWidget(
source=os.path.join(
self.image_dir, "text_images", "{}.png".format(avatar_image_first_letter(
item["text"].strip())))
))
meny.bind(on_press=partial(
self.myadd_detail, item['secondary_text'], item['text']))
self.set_address_status(item, meny, is_enable)
def set_address_status(self, item, meny, is_enable):
"""Setting the identity status enable/disable on UI"""
if self.kivy_state.selected_address == item['secondary_text'] and is_enable:
meny.add_widget(self.is_active_badge())
else:
meny.add_widget(ToggleBtn(active=True if is_enable else False))
self.ids.ml.add_widget(meny)
def set_mdList(self, first_index, last_index):
"""Creating the mdlist"""
data = []
self.get_address_list(first_index, last_index, data)
for item in data:
self.set_address_to_widget(item)
def check_scroll_y(self, instance, somethingelse):
"""Load data on Scroll down"""
if self.ids.refresh_layout.scroll_y <= self.min_scroll_y_limit and self.has_refreshed:
self.ids.refresh_layout.scroll_y = self.scroll_y_step
my_addresses = len(self.ids.ml.children)
if my_addresses != len(self.addresses_list):
self.update_addressBook_on_scroll(my_addresses)
self.has_refreshed = (
True if my_addresses != len(self.addresses_list) else False
)
def update_addressBook_on_scroll(self, my_addresses):
"""Loads more data on scroll down"""
self.set_mdList(my_addresses, my_addresses + self.number_of_addresses)
def myadd_detail(self, fromaddress, label, *args):
"""Load myaddresses details"""
if config.getboolean(fromaddress, 'enabled'):
obj = MyaddDetailPopup()
self.address_label = obj.address_label = label
self.text_address = obj.address = fromaddress
width = self.is_android_width if platform == 'android' else self.other_platform_width
self.myadddetail_popup = self.myaddress_detail_popup(obj, width)
self.myadddetail_popup.auto_dismiss = False
self.myadddetail_popup.open()
else:
width = self.disabled_addr_width if platform == 'android' else self.other_platform_disabled_addr_width
self.dialog_box = self.inactive_address_popup(width, self.callback_for_menu_items)
self.dialog_box.open()
def callback_for_menu_items(self, text_item, *arg):
"""Callback of inactive address alert box"""
self.dialog_box.dismiss()
toast(text_item)
def refresh_callback(self, *args):
"""Method updates the state of application,
While the spinner remains on the screen"""
def refresh_callback(interval):
"""Method used for loading the myaddress screen data"""
self.kivy_state.searching_text = ''
self.ids.search_bar.ids.search_field.text = ''
self.has_refreshed = True
self.ids.ml.clear_widgets()
self.init_ui()
self.ids.refresh_layout.refresh_done()
Clock.schedule_once(self.address_permision_callback, 0)
Clock.schedule_once(refresh_callback, 1)
@staticmethod
def filter_address(address):
"""It will return True if search is matched"""
searched_text = App.get_running_app().kivy_state_obj.searching_text.lower()
return bool(config.search_addresses(address, searched_text))
def disable_address_ui(self, address, instance):
"""This method is used to disable addresses from UI"""
config.disable_address(address)
instance.parent.parent.theme_text_color = 'Primary'
instance.parent.parent.canvas.children[3].rgba = MyAddress.canvas_color_gray
toast('Address disabled')
Clock.schedule_once(self.address_permision_callback, 0)
def enable_address_ui(self, address, instance):
"""This method is used to enable addresses from UI"""
config.enable_address(address)
instance.parent.parent.theme_text_color = 'Custom'
instance.parent.parent.canvas.children[3].rgba = MyAddress.canvas_color_black
toast('Address Enabled')
Clock.schedule_once(self.address_permision_callback, 0)
def address_permision_callback(self, dt=0):
"""callback for enable or disable addresses"""
addresses = [addr for addr in config.addresses()
if config.getboolean(str(addr), 'enabled')]
self.parent.parent.ids.content_drawer.ids.identity_dropdown.values = addresses
self.parent.parent.ids.id_create.children[1].ids.composer_dropdown.values = addresses
self.kivy_running_app.identity_list = addresses
def toggleAction(self, instance):
"""This method is used for enable or disable address"""
addr = instance.parent.parent.secondary_text
if instance.active:
self.enable_address_ui(addr, instance)
else:
self.disable_address_ui(addr, instance)

View File

@ -0,0 +1,64 @@
# pylint: disable=too-many-arguments, no-name-in-module, import-error
# pylint: disable=too-few-public-methods, no-member, too-many-ancestors
"""
MyAddress widgets are here.
"""
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
from kivymd.uix.label import MDLabel
from kivymd.uix.list import IRightBodyTouch
from pybitmessage.bitmessagekivy.get_platform import platform
from pybitmessage.bitmessagekivy.baseclass.common import ThemeClsColor
class BadgeText(IRightBodyTouch, MDLabel):
"""BadgeText class for kivy UI"""
class HelperMyAddress(object):
"""Widget used in MyAddress are here"""
dialog_height = .25
@staticmethod
def is_active_badge():
"""This function show the 'active' label of active Address."""
active_status = 'Active'
is_android_width = 90
width = 50
height = 60
badge_obj = BadgeText(
size_hint=(None, None),
size=[is_android_width if platform == 'android' else width, height],
text=active_status, halign='center',
font_style='Body1', theme_text_color='Custom',
text_color=ThemeClsColor, font_size='13sp'
)
return badge_obj
@staticmethod
def myaddress_detail_popup(obj, width):
"""This method show the details of address as popup opens."""
show_myaddress_dialogue = MDDialog(
type="custom",
size_hint=(width, HelperMyAddress.dialog_height),
content_cls=obj,
)
return show_myaddress_dialogue
@staticmethod
def inactive_address_popup(width, callback_for_menu_items):
"""This method shows the warning popup if the address is inactive"""
dialog_text = 'Address is not currently active. Please click on Toggle button to active it.'
dialog_box = MDDialog(
text=dialog_text,
size_hint=(width, HelperMyAddress.dialog_height),
buttons=[
MDFlatButton(
text="Ok", on_release=lambda x: callback_for_menu_items("Ok")
),
],
)
return dialog_box

View File

@ -0,0 +1,54 @@
# pylint: disable=unused-argument, consider-using-f-string
# pylint: disable=no-name-in-module, too-few-public-methods
"""
Network status
"""
import os
from kivy.clock import Clock
from kivy.properties import StringProperty
from kivy.uix.screenmanager import Screen
from pybitmessage import state
if os.environ.get('INSTALL_TESTS', False) and not state.backend_py3_compatible:
from pybitmessage.mockbm import kivy_main
stats = kivy_main.network.stats
objectracker = kivy_main.network.objectracker
else:
from pybitmessage.network import stats, objectracker
class NetworkStat(Screen):
"""NetworkStat class for kivy Ui"""
text_variable_1 = StringProperty(
'{0}::{1}'.format('Total Connections', '0'))
text_variable_2 = StringProperty(
'Processed {0} per-to-per messages'.format('0'))
text_variable_3 = StringProperty(
'Processed {0} brodcast messages'.format('0'))
text_variable_4 = StringProperty(
'Processed {0} public keys'.format('0'))
text_variable_5 = StringProperty(
'Processed {0} object to be synced'.format('0'))
def __init__(self, *args, **kwargs):
"""Init method for network stat"""
super(NetworkStat, self).__init__(*args, **kwargs)
Clock.schedule_interval(self.init_ui, 1)
def init_ui(self, dt=0):
"""Clock Schdule for method networkstat screen"""
self.text_variable_1 = '{0} :: {1}'.format(
'Total Connections', str(len(stats.connectedHostsList())))
self.text_variable_2 = 'Processed {0} per-to-per messages'.format(
str(state.numberOfMessagesProcessed))
self.text_variable_3 = 'Processed {0} brodcast messages'.format(
str(state.numberOfBroadcastsProcessed))
self.text_variable_4 = 'Processed {0} public keys'.format(
str(state.numberOfPubkeysProcessed))
self.text_variable_5 = '{0} object to be synced'.format(
len(objectracker.missingObjects))

View File

@ -0,0 +1,66 @@
# pylint: disable=import-error, no-name-in-module, too-few-public-methods, too-many-ancestors
'''
Payment/subscription frontend
'''
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import Screen
from kivy.app import App
from kivymd.uix.behaviors.elevation import RectangularElevationBehavior
from kivymd.uix.label import MDLabel
from kivymd.uix.list import (
IRightBodyTouch,
OneLineAvatarIconListItem
)
from pybitmessage.bitmessagekivy.baseclass.common import toast, kivy_state_variables
class Payment(Screen):
"""Payment Screen class for kivy Ui"""
def __init__(self, *args, **kwargs):
"""Instantiate kivy state variable"""
super(Payment, self).__init__(*args, **kwargs)
self.kivy_state = kivy_state_variables()
# TODO: get_free_credits() is not used anywhere, will be used later for Payment/subscription.
def get_free_credits(self, instance): # pylint: disable=unused-argument
"""Get the available credits"""
# pylint: disable=no-self-use
self.kivy_state.available_credit = 0
existing_credits = 0
if existing_credits > 0:
toast(
'We already have added free credit'
' for the subscription to your account!')
else:
toast('Credit added to your account!')
# TODO: There is no sc18 screen id is available,
# need to create sc18 for Credits screen inside main.kv
App.get_running_app().root.ids.sc18.ids.cred.text = '{0}'.format(
self.kivy_state.available_credit)
class Category(BoxLayout, RectangularElevationBehavior):
"""Category class for kivy Ui"""
elevation_normal = .01
class ProductLayout(BoxLayout, RectangularElevationBehavior):
"""ProductLayout class for kivy Ui"""
elevation_normal = .01
class PaymentMethodLayout(BoxLayout):
"""PaymentMethodLayout class for kivy Ui"""
class ListItemWithLabel(OneLineAvatarIconListItem):
"""ListItemWithLabel class for kivy Ui"""
class RightLabel(IRightBodyTouch, MDLabel):
"""RightLabel class for kivy Ui"""

View File

@ -0,0 +1,231 @@
# pylint: disable=import-error, attribute-defined-outside-init
# pylint: disable=no-member, no-name-in-module, unused-argument, too-few-public-methods
"""
All the popup are managed here.
"""
import logging
from datetime import datetime
from kivy.clock import Clock
from kivy.metrics import dp
from kivy.properties import StringProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.app import App
from pybitmessage.bitmessagekivy import kivy_helper_search
from pybitmessage.bitmessagekivy.get_platform import platform
from pybitmessage.bitmessagekivy.baseclass.common import toast
from pybitmessage.addresses import decodeAddress
logger = logging.getLogger('default')
class AddressChangingLoader(Popup):
"""Run a Screen Loader when changing the Identity for kivy UI"""
def __init__(self, **kwargs):
super(AddressChangingLoader, self).__init__(**kwargs)
Clock.schedule_once(self.dismiss_popup, 0.5)
def dismiss_popup(self, dt):
"""Dismiss popups"""
self.dismiss()
class AddAddressPopup(BoxLayout):
"""Popup for adding new address to addressbook"""
validation_dict = {
"missingbm": "The address should start with ''BM-''",
"checksumfailed": "The address is not typed or copied correctly",
"versiontoohigh": "The version number of this address is higher than this"
" software can support. Please upgrade Bitmessage.",
"invalidcharacters": "The address contains invalid characters.",
"ripetooshort": "Some data encoded in the address is too short.",
"ripetoolong": "Some data encoded in the address is too long.",
"varintmalformed": "Some data encoded in the address is malformed."
}
valid = False
def __init__(self, **kwargs):
super(AddAddressPopup, self).__init__(**kwargs)
def checkAddress_valid(self, instance):
"""Checking address is valid or not"""
my_addresses = (
App.get_running_app().root.ids.content_drawer.ids.identity_dropdown.values)
add_book = [addr[1] for addr in kivy_helper_search.search_sql(
folder="addressbook")]
entered_text = str(instance.text).strip()
if entered_text in add_book:
text = 'Address is already in the addressbook.'
elif entered_text in my_addresses:
text = 'You can not save your own address.'
elif entered_text:
text = self.addressChanged(entered_text)
if entered_text in my_addresses or entered_text in add_book:
self.ids.address.error = True
self.ids.address.helper_text = text
elif entered_text and self.valid:
self.ids.address.error = False
elif entered_text:
self.ids.address.error = True
self.ids.address.helper_text = text
else:
self.ids.address.error = True
self.ids.address.helper_text = 'This field is required'
def checkLabel_valid(self, instance):
"""Checking address label is unique or not"""
entered_label = instance.text.strip()
addr_labels = [labels[0] for labels in kivy_helper_search.search_sql(
folder="addressbook")]
if entered_label in addr_labels:
self.ids.label.error = True
self.ids.label.helper_text = 'Label name already exists.'
elif entered_label:
self.ids.label.error = False
else:
self.ids.label.error = True
self.ids.label.helper_text = 'This field is required'
def _onSuccess(self, addressVersion, streamNumber, ripe):
pass
def addressChanged(self, addr):
"""Address validation callback, performs validation and gives feedback"""
status, addressVersion, streamNumber, ripe = decodeAddress(
str(addr))
self.valid = status == 'success'
if self.valid:
text = "Address is valid."
self._onSuccess(addressVersion, streamNumber, ripe)
return text
return self.validation_dict.get(status)
class SavedAddressDetailPopup(BoxLayout):
"""Pop-up for Saved Address details for kivy UI"""
address_label = StringProperty()
address = StringProperty()
def __init__(self, **kwargs):
"""Set screen of address detail page"""
super(SavedAddressDetailPopup, self).__init__(**kwargs)
def checkLabel_valid(self, instance):
"""Checking address label is unique of not"""
entered_label = str(instance.text.strip())
address_list = kivy_helper_search.search_sql(folder="addressbook")
addr_labels = [labels[0] for labels in address_list]
add_dict = dict(address_list)
if self.address and entered_label in addr_labels \
and self.address != add_dict[entered_label]:
self.ids.add_label.error = True
self.ids.add_label.helper_text = 'label name already exists.'
elif entered_label:
self.ids.add_label.error = False
else:
self.ids.add_label.error = True
self.ids.add_label.helper_text = 'This field is required'
class MyaddDetailPopup(BoxLayout):
"""MyaddDetailPopup class for kivy Ui"""
address_label = StringProperty()
address = StringProperty()
def __init__(self, **kwargs):
"""My Address Details screen setting"""
super(MyaddDetailPopup, self).__init__(**kwargs)
def send_message_from(self):
"""Method used to fill from address of composer autofield"""
App.get_running_app().set_navbar_for_composer()
window_obj = App.get_running_app().root.ids
window_obj.id_create.children[1].ids.ti.text = self.address
window_obj.id_create.children[1].ids.composer_dropdown.text = self.address
window_obj.id_create.children[1].ids.txt_input.text = ''
window_obj.id_create.children[1].ids.subject.text = ''
window_obj.id_create.children[1].ids.body.text = ''
window_obj.scr_mngr.current = 'create'
self.parent.parent.parent.dismiss()
def close_pop(self):
"""Pop is Cancelled"""
self.parent.parent.parent.dismiss()
toast('Cancelled')
class AppClosingPopup(Popup):
"""AppClosingPopup class for kivy Ui"""
def __init__(self, **kwargs):
super(AppClosingPopup, self).__init__(**kwargs)
def closingAction(self, text):
"""Action on closing window"""
exit_message = "*******************EXITING FROM APPLICATION*******************"
if text == 'Yes':
logger.debug(exit_message)
import shutdown
shutdown.doCleanShutdown()
else:
self.dismiss()
toast(text)
class SenderDetailPopup(Popup):
"""SenderDetailPopup class for kivy Ui"""
to_addr = StringProperty()
from_addr = StringProperty()
time_tag = StringProperty()
def __init__(self, **kwargs):
"""this metthod initialized the send message detial popup"""
super(SenderDetailPopup, self).__init__(**kwargs)
def assignDetail(self, to_addr, from_addr, timeinseconds):
"""Detailes assigned"""
self.to_addr = to_addr
self.from_addr = from_addr
time_obj = datetime.fromtimestamp(int(timeinseconds))
self.time_tag = time_obj.strftime("%d %b %Y, %I:%M %p")
device_type = 2 if platform == 'android' else 1.5
pop_height = 1.2 * device_type * (self.ids.sd_label.height + self.ids.dismiss_btn.height)
if len(to_addr) > 3:
self.height = pop_height
self.ids.to_addId.size_hint_y = None
self.ids.to_addId.height = 50
self.ids.to_addtitle.add_widget(ToAddressTitle())
frmaddbox = ToAddrBoxlayout()
frmaddbox.set_toAddress(to_addr)
self.ids.to_addId.add_widget(frmaddbox)
else:
self.ids.space_1.height = dp(0)
self.ids.space_2.height = dp(0)
self.ids.myadd_popup_box.spacing = dp(8 if platform == 'android' else 3)
self.height = pop_height / 1.2
class ToAddrBoxlayout(BoxLayout):
"""ToAddrBoxlayout class for kivy Ui"""
to_addr = StringProperty()
def set_toAddress(self, to_addr):
"""This method is use to set to address"""
self.to_addr = to_addr
class ToAddressTitle(BoxLayout):
"""ToAddressTitle class for BoxLayout behaviour"""

View File

@ -0,0 +1,34 @@
# pylint: disable=import-error, no-name-in-module, too-few-public-methods
"""
Generate QRcode of saved addresses in addressbook.
"""
import logging
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.properties import StringProperty
from kivy_garden.qrcode import QRCodeWidget
logger = logging.getLogger('default')
class ShowQRCode(Screen):
"""ShowQRCode Screen class for kivy Ui"""
address = StringProperty()
def __init__(self, *args, **kwargs):
"""Instantiate kivy state variable"""
super(ShowQRCode, self).__init__(*args, **kwargs)
self.kivy_running_app = App.get_running_app()
def qrdisplay(self, instance, address):
"""Method used for showing QR Code"""
self.ids.qr.clear_widgets()
self.kivy_running_app.set_toolbar_for_QrCode()
self.address = address # used for label
self.ids.qr.add_widget(QRCodeWidget(data=self.address))
self.ids.qr.children[0].show_border = False
instance.parent.parent.parent.dismiss()
logger.debug('Show QR code')

View File

@ -0,0 +1,105 @@
# pylint: disable=no-member, too-many-arguments, too-few-public-methods
# pylint: disable=no-name-in-module, unused-argument, arguments-differ
"""
QR code Scan Screen used in message composer to get recipient address
"""
import os
import logging
import cv2
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import (
BooleanProperty,
ObjectProperty,
StringProperty
)
from kivy.uix.screenmanager import Screen
from pybitmessage.bitmessagekivy.get_platform import platform
logger = logging.getLogger('default')
class ScanScreen(Screen):
"""ScanScreen is for scaning Qr code"""
# pylint: disable=W0212
camera_available = BooleanProperty(False)
previous_open_screen = StringProperty()
pop_up_instance = ObjectProperty()
def __init__(self, *args, **kwargs):
"""Getting AddressBook Details"""
super(ScanScreen, self).__init__(*args, **kwargs)
self.check_camera()
def check_camera(self):
"""This method is used for checking camera avaibility"""
if platform != "android":
cap = cv2.VideoCapture(0)
is_cam_open = cap.isOpened()
while is_cam_open:
logger.debug('Camera is available!')
self.camera_available = True
break
else:
logger.debug("Camera is not available!")
self.camera_available = False
else:
self.camera_available = True
def get_screen(self, screen_name, instance=None):
"""This method is used for getting previous screen name"""
self.previous_open_screen = screen_name
if screen_name != 'composer':
self.pop_up_instance = instance
def on_pre_enter(self):
"""
on_pre_enter works little better on android
It affects screen transition on linux
"""
if not self.children:
tmp = Builder.load_file(
os.path.join(
os.path.dirname(os.path.dirname(__file__)), "kv", "{}.kv").format("scanner")
)
self.add_widget(tmp)
if platform == "android":
Clock.schedule_once(self.start_camera, 0)
def on_enter(self):
"""
on_enter works better on linux
It creates a black screen on android until camera gets loaded
"""
if platform != "android":
Clock.schedule_once(self.start_camera, 0)
def on_leave(self):
"""This method will call on leave"""
Clock.schedule_once(self.stop_camera, 0)
def start_camera(self, *args):
"""Its used for starting camera for scanning qrcode"""
# pylint: disable=attribute-defined-outside-init
self.xcam = self.children[0].ids.zbarcam.ids.xcamera
if platform == "android":
self.xcam.play = True
else:
Clock.schedule_once(self.open_cam, 0)
def stop_camera(self, *args):
"""Its used for stop the camera"""
self.xcam.play = False
if platform != "android":
self.xcam._camera._device.release()
def open_cam(self, *args):
"""It will open up the camera"""
if not self.xcam._camera._device.isOpened():
self.xcam._camera._device.open(self.xcam._camera._index)
self.xcam.play = True

View File

@ -0,0 +1,47 @@
# pylint: disable=import-error, attribute-defined-outside-init, too-many-arguments
# pylint: disable=no-member, no-name-in-module, unused-argument, too-few-public-methods
"""
Sent screen; All sent message managed here.
"""
from kivy.properties import StringProperty, ListProperty
from kivy.uix.screenmanager import Screen
from kivy.app import App
from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables
class Sent(Screen):
"""Sent Screen class for kivy UI"""
queryreturn = ListProperty()
account = StringProperty()
has_refreshed = True
no_search_res_found = "No search result found"
label_str = "Yet no message for this account!"
def __init__(self, *args, **kwargs):
"""Association with the screen"""
super(Sent, self).__init__(*args, **kwargs)
self.kivy_state = kivy_state_variables()
if self.kivy_state.selected_address == '':
if App.get_running_app().identity_list:
self.kivy_state.selected_address = App.get_running_app().identity_list[0]
def init_ui(self, dt=0):
"""Clock Schdule for method sent accounts"""
self.loadSent()
print(dt)
def set_defaultAddress(self):
"""Set default address"""
if self.kivy_state.selected_address == "":
if self.kivy_running_app.identity_list:
self.kivy_state.selected_address = self.kivy_running_app.identity_list[0]
def loadSent(self, where="", what=""):
"""Load Sent list for Sent messages"""
self.set_defaultAddress()
self.account = self.kivy_state.selected_address

View File

@ -0,0 +1,10 @@
# pylint: disable=unused-argument, no-name-in-module, too-few-public-methods
"""
Settings screen UI
"""
from kivy.uix.screenmanager import Screen
class Setting(Screen):
"""Setting Screen for kivy Ui"""

View File

@ -0,0 +1,33 @@
# pylint: disable=unused-argument, consider-using-f-string, import-error, attribute-defined-outside-init
# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module, too-few-public-methods
"""
Trash screen
"""
from kivy.properties import (
ListProperty,
StringProperty
)
from kivy.uix.screenmanager import Screen
from kivy.app import App
from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables
class Trash(Screen):
"""Trash Screen class for kivy Ui"""
trash_messages = ListProperty()
has_refreshed = True
delete_index = None
table_name = StringProperty()
no_msg_found_str = "Yet no trashed message for this account!"
def __init__(self, *args, **kwargs):
"""Trash method, delete sent message and add in Trash"""
super(Trash, self).__init__(*args, **kwargs)
self.kivy_state = kivy_state_variables()
if self.kivy_state.selected_address == '':
if App.get_running_app().identity_list:
self.kivy_state.selected_address = App.get_running_app().identity_list[0]

View File

@ -0,0 +1,31 @@
# pylint: disable=no-else-return, too-many-return-statements
"""To check the platform"""
from sys import platform as _sys_platform
from os import environ
def _get_platform():
kivy_build = environ.get("KIVY_BUILD", "")
if kivy_build in {"android", "ios"}:
return kivy_build
elif "P4A_BOOTSTRAP" in environ:
return "android"
elif "ANDROID_ARGUMENT" in environ:
return "android"
elif _sys_platform in ("win32", "cygwin"):
return "win"
elif _sys_platform == "darwin":
return "macosx"
elif _sys_platform.startswith("linux"):
return "linux"
elif _sys_platform.startswith("freebsd"):
return "linux"
return "unknown"
platform = _get_platform()
if platform not in ("android", "unknown"):
environ["KIVY_CAMERA"] = "opencv"

View File

@ -0,0 +1,80 @@
"""
Core classes for loading images and converting them to a Texture.
The raw image data can be keep in memory for further access
"""
import hashlib
from io import BytesIO
from PIL import Image
from kivy.core.image import Image as CoreImage
from kivy.uix.image import Image as kiImage
# constants
RESOLUTION = 300, 300
V_RESOLUTION = 7, 7
BACKGROUND_COLOR = 255, 255, 255, 255
MODE = "RGB"
def generate(Generate_string=None):
"""Generating string"""
hash_string = generate_hash(Generate_string)
color = random_color(hash_string)
image = Image.new(MODE, V_RESOLUTION, BACKGROUND_COLOR)
image = generate_image(image, color, hash_string)
image = image.resize(RESOLUTION, 0)
data = BytesIO()
image.save(data, format='png')
data.seek(0)
# yes you actually need this
im = CoreImage(BytesIO(data.read()), ext='png')
beeld = kiImage()
# only use this line in first code instance
beeld.texture = im.texture
return beeld
def generate_hash(string):
"""Generating hash"""
try:
# make input case insensitive
string = str.lower(string)
hash_object = hashlib.md5( # nosec B324, B303
str.encode(string))
print(hash_object.hexdigest())
# returned object is a hex string
return hash_object.hexdigest()
except IndexError:
print("Error: Please enter a string as an argument.")
def random_color(hash_string):
"""Getting random color"""
# remove first three digits from hex string
split = 6
rgb = hash_string[:split]
split = 2
r = rgb[:split]
g = rgb[split:2 * split]
b = rgb[2 * split:3 * split]
color = (int(r, 16), int(g, 16), int(b, 16), 0xFF)
return color
def generate_image(image, color, hash_string):
"""Generating images"""
hash_string = hash_string[6:]
lower_x = 1
lower_y = 1
upper_x = int(V_RESOLUTION[0] / 2) + 1
upper_y = V_RESOLUTION[1] - 1
limit_x = V_RESOLUTION[0] - 1
index = 0
for x in range(lower_x, upper_x):
for y in range(lower_y, upper_y):
if int(hash_string[index], 16) % 2 == 0:
image.putpixel((x, y), color)
image.putpixel((limit_x - x, y), color)
index = index + 1
return image

View File

@ -0,0 +1,71 @@
"""
Sql queries for bitmessagekivy
"""
from pybitmessage.helper_sql import sqlQuery
def search_sql(
xAddress="toaddress", account=None, folder="inbox", where=None,
what=None, unreadOnly=False, start_indx=0, end_indx=20):
# pylint: disable=too-many-arguments, too-many-branches
"""Method helping for searching mails"""
if what is not None and what != "":
what = "%" + what + "%"
else:
what = None
if folder in ("sent", "draft"):
sqlStatementBase = (
'''SELECT toaddress, fromaddress, subject, message, status,'''
''' ackdata, senttime FROM sent '''
)
elif folder == "addressbook":
sqlStatementBase = '''SELECT label, address From addressbook '''
else:
sqlStatementBase = (
'''SELECT folder, msgid, toaddress, message, fromaddress,'''
''' subject, received, read FROM inbox '''
)
sqlStatementParts = []
sqlArguments = []
if account is not None:
if xAddress == 'both':
sqlStatementParts.append("(fromaddress = ? OR toaddress = ?)")
sqlArguments.append(account)
sqlArguments.append(account)
else:
sqlStatementParts.append(xAddress + " = ? ")
sqlArguments.append(account)
if folder != "addressbook":
if folder is not None:
if folder == "new":
folder = "inbox"
unreadOnly = True
sqlStatementParts.append("folder = ? ")
sqlArguments.append(folder)
else:
sqlStatementParts.append("folder != ?")
sqlArguments.append("trash")
if what is not None:
for colmns in where:
if len(where) > 1:
if where[0] == colmns:
filter_col = "(%s LIKE ?" % (colmns)
else:
filter_col += " or %s LIKE ? )" % (colmns)
else:
filter_col = "%s LIKE ?" % (colmns)
sqlArguments.append(what)
sqlStatementParts.append(filter_col)
if unreadOnly:
sqlStatementParts.append("read = 0")
if sqlStatementParts:
sqlStatementBase += "WHERE " + " AND ".join(sqlStatementParts)
if folder in ("sent", "draft"):
sqlStatementBase += \
"ORDER BY senttime DESC limit {0}, {1}".format(
start_indx, end_indx)
elif folder == "inbox":
sqlStatementBase += \
"ORDER BY received DESC limit {0}, {1}".format(
start_indx, end_indx)
return sqlQuery(sqlStatementBase, sqlArguments)

View File

@ -0,0 +1,42 @@
# pylint: disable=too-many-instance-attributes, too-few-public-methods
"""
Kivy State variables are assigned here, they are separated from state.py
=================================
"""
import os
import threading
class KivyStateVariables(object):
"""This Class hold all the kivy state variables"""
def __init__(self):
self.selected_address = ''
self.navinstance = None
self.mail_id = 0
self.my_address_obj = None
self.detail_page_type = None
self.ackdata = None
self.status = None
self.screen_density = None
self.msg_counter_objs = None
self.check_sent_acc = None
self.sent_count = 0
self.inbox_count = 0
self.trash_count = 0
self.draft_count = 0
self.all_count = 0
self.searching_text = ''
self.search_screen = ''
self.send_draft_mail = None
self.is_allmail = False
self.in_composer = False
self.available_credit = 0
self.in_sent_method = False
self.in_search_mode = False
self.image_dir = os.path.abspath(os.path.join('images', 'kivy'))
self.kivyui_ready = threading.Event()
self.file_manager = None
self.manager_open = False

View File

@ -0,0 +1,26 @@
<AddressBook>:
name: 'addressbook'
BoxLayout:
orientation: 'vertical'
spacing: dp(5)
SearchBar:
id: address_search
GridLayout:
id: identi_tag
padding: [20, 0, 0, 5]
cols: 1
size_hint_y: None
height: self.minimum_height
MDLabel:
id: tag_label
text: ''
font_style: 'Subtitle2'
BoxLayout:
orientation:'vertical'
ScrollView:
id: scroll_y
do_scroll_x: False
MDList:
id: ml
Loader:
ComposerButton:

View File

@ -0,0 +1,25 @@
<Allmails>:
name: 'allmails'
BoxLayout:
orientation: 'vertical'
spacing: dp(5)
GridLayout:
id: identi_tag
padding: [20, 20, 0, 5]
spacing: dp(5)
cols: 1
size_hint_y: None
height: self.minimum_height
MDLabel:
id: tag_label
text: ''
font_style: 'Subtitle2'
BoxLayout:
orientation:'vertical'
ScrollView:
id: scroll_y
do_scroll_x: False
MDList:
id: ml
Loader:
ComposerButton:

View File

@ -0,0 +1,82 @@
#:import C kivy.utils.get_color_from_hex
#:import MDTextField kivymd.uix.textfield.MDTextField
<Chat>:
name: 'chat'
BoxLayout:
orientation: 'vertical'
canvas.before:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
ScrollView:
Label:
id: chat_logs
text: ''
color: C('#101010')
text_size: (self.width, None)
halign: 'left'
valign: 'top'
padding: (0, 0) # fixed in Kivy 1.8.1
size_hint: (1, None)
height: self.texture_size[1]
markup: True
font_size: sp(20)
MDBoxLayout:
size_hint_y: None
spacing:5
orientation: 'horizontal'
pos_hint: {'center_y': 1, 'center_x': 1}
halign: 'right'
pos_hint: {'left': 0}
pos_hint: {'x':.8}
height: dp(50) + self.minimum_height
MDFillRoundFlatButton:
text: app.tr._("First message")
opposite_colors: True
pos_hint: {'center_x':0.8,'center_y':0.7}
BoxLayout:
height: 50
orientation: 'horizontal'
padding: 0
size_hint: (1, None)
MDTextField:
id:'id_message_body'
hint_text: 'Empty field'
icon_left: "message"
hint_text: "please enter your text"
mode: "fill"
fill_color: 1/255, 144/255, 254/255, 0.1
multiline: True
font_color_normal: 0, 0, 0, .4
icon_right: 'grease-pencil'
icon_right_color: app.theme_cls.primary_light
pos_hint: {'center_x':0.2,'center_y':0.7}
MDIconButton:
id: file_manager
icon: "attachment"
opposite_colors: True
on_release: app.file_manager_open()
theme_text_color: "Custom"
text_color: app.theme_cls.primary_color
MDIconButton:
icon: 'camera'
opposite_colors: True
theme_text_color: "Custom"
text_color: app.theme_cls.primary_color
MDIconButton:
id: send_message
icon: "send"
# x: root.parent.x + dp(10)
# pos_hint: {"top": 1, 'left': 1}
color: [1,0,0,1]
on_release: app.rest_default_avatar_img()
theme_text_color: "Custom"
text_color: app.theme_cls.primary_color

View File

@ -0,0 +1,58 @@
<ChatList>:
name: 'chlist'
canvas.before:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
MDTabs:
id: chat_panel
tab_display_mode:'text'
Tab:
text: app.tr._("Chats")
BoxLayout:
id: chat_box
orientation: 'vertical'
ScrollView:
id: scroll_y
do_scroll_x: False
MDList:
id: ml
MDLabel:
font_style: 'Caption'
theme_text_color: 'Primary'
text: app.tr._('No Chat')
halign: 'center'
size_hint_y: None
bold: True
valign: 'top'
# OneLineAvatarListItem:
# text: "Single-line item with avatar"
# divider: None
# _no_ripple_effect: True
# ImageLeftWidget:
# source: './images/text_images/A.png'
# OneLineAvatarListItem:
# text: "Single-line item with avatar"
# divider: None
# _no_ripple_effect: True
# ImageLeftWidget:
# source: './images/text_images/B.png'
# OneLineAvatarListItem:
# text: "Single-line item with avatar"
# divider: None
# _no_ripple_effect: True
# ImageLeftWidget:
# source: './images/text_images/A.png'
Tab:
text: app.tr._("Contacts")
BoxLayout:
id: contact_box
orientation: 'vertical'
ScrollView:
id: scroll_y
do_scroll_x: False
MDList:
id: ml

View File

@ -0,0 +1,45 @@
#:import C kivy.utils.get_color_from_hex
<ChatRoom>:
name: 'chroom'
BoxLayout:
orientation: 'vertical'
canvas.before:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
ScrollView:
Label:
id: chat_logs
text: ''
color: C('#101010')
text_size: (self.width, None)
halign: 'left'
valign: 'top'
padding: (0, 0) # fixed in Kivy 1.8.1
size_hint: (1, None)
height: self.texture_size[1]
markup: True
font_size: sp(20)
BoxLayout:
height: 50
orientation: 'horizontal'
padding: 0
size_hint: (1, None)
TextInput:
id: message
size_hint: (1, 1)
multiline: False
font_size: sp(20)
on_text_validate: root.send_msg()
MDRaisedButton:
text: app.tr._("Send")
elevation_normal: 2
opposite_colors: True
size_hint: (0.3, 1)
pos_hint: {"center_x": .5}
on_press: root.send_msg()

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