Merge branch 'v0.6' into qt5-wip
This commit is contained in:
commit
aa9edcba2e
102
.buildbot/android/Dockerfile
Executable file
102
.buildbot/android/Dockerfile
Executable 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
9
.buildbot/android/build.sh
Executable 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
6
.buildbot/android/test.sh
Executable 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
|
26
.buildbot/appimage/Dockerfile
Normal file
26
.buildbot/appimage/Dockerfile
Normal 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
58
.buildbot/appimage/build.sh
Executable 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
6
.buildbot/appimage/test.sh
Executable 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
18
.buildbot/kivy/Dockerfile
Normal 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
7
.buildbot/kivy/build.sh
Executable 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
4
.buildbot/kivy/test.sh
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
export INSTALL_TESTS=True
|
||||||
|
|
||||||
|
xvfb-run --server-args="-screen 0, 720x1280x24" python3 tests-kivy.py
|
7
.buildbot/snap/Dockerfile
Normal file
7
.buildbot/snap/Dockerfile
Normal 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
16
.buildbot/snap/build.sh
Executable 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
|
22
.buildbot/tox-bionic/Dockerfile
Normal file
22
.buildbot/tox-bionic/Dockerfile
Normal 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
3
.buildbot/tox-bionic/build.sh
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
sudo service tor start
|
4
.buildbot/tox-bionic/test.sh
Executable file
4
.buildbot/tox-bionic/test.sh
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
tox -e lint-basic || exit 1
|
||||||
|
tox
|
15
.buildbot/tox-focal/Dockerfile
Normal file
15
.buildbot/tox-focal/Dockerfile
Normal 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
1
.buildbot/tox-focal/test.sh
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../tox-bionic/test.sh
|
12
.buildbot/tox-jammy/Dockerfile
Normal file
12
.buildbot/tox-jammy/Dockerfile
Normal 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
4
.buildbot/tox-jammy/test.sh
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
tox -e lint || exit 1
|
||||||
|
tox -e py310
|
14
.buildbot/winebuild/Dockerfile
Normal file
14
.buildbot/winebuild/Dockerfile
Normal 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
8
.buildbot/winebuild/build.sh
Executable 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
41
.devcontainer/Dockerfile
Normal 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
|
35
.devcontainer/devcontainer.json
Normal file
35
.devcontainer/devcontainer.json
Normal 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
7
.dockerignore
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
bin
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
__pycache__
|
||||||
|
.buildozer
|
||||||
|
.tox
|
||||||
|
mprofile_*
|
|
@ -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
7
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Basic dependabot.yml for kivymd
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "pip"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
22
.travis.yml
22
.travis.yml
|
@ -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
|
|
2
COPYING
2
COPYING
|
@ -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
|
||||||
|
|
26
Dockerfile
26
Dockerfile
|
@ -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"]
|
|
||||||
|
|
51
INSTALL.md
51
INSTALL.md
|
@ -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
|
|
||||||
```
|
|
||||||
|
|
4
LICENSE
4
LICENSE
|
@ -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
|
||||||
|
@ -91,4 +91,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
|
@ -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 *
|
||||||
|
|
|
@ -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)
|
||||||
|
|
28
buildscripts/androiddev.sh
Normal file → Executable file
28
buildscripts/androiddev.sh
Normal file → Executable file
|
@ -58,23 +58,14 @@ install_ndk()
|
||||||
}
|
}
|
||||||
|
|
||||||
# INSTALL SDK
|
# INSTALL SDK
|
||||||
function install_sdk()
|
install_sdk()
|
||||||
{
|
{
|
||||||
if [[ "$get_python_version" -eq " 2 " ]];
|
ANDROID_SDK_BUILD_TOOLS_VERSION="29.0.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"
|
|
||||||
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}"
|
||||||
|
|
|
@ -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
|
||||||
|
|
5
buildscripts/update_translation_source.sh
Normal file
5
buildscripts/update_translation_source.sh
Normal 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
|
|
@ -100,16 +100,18 @@ function install_openssl(){
|
||||||
|
|
||||||
function install_pyinstaller()
|
function install_pyinstaller()
|
||||||
{
|
{
|
||||||
cd "${BASE_DIR}" || exit 1
|
cd "${BASE_DIR}" || exit 1
|
||||||
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
|
||||||
else
|
# see https://github.com/pyinstaller/pyinstaller/issues/4628
|
||||||
# 3.2.1 is the last version to work on XP
|
wine python -m pip install -I pyinstaller==3.5
|
||||||
# see https://github.com/pyinstaller/pyinstaller/issues/2931
|
else
|
||||||
wine python -m pip install -I pyinstaller==3.2.1
|
# 3.2.1 is the last version to work on XP
|
||||||
fi
|
# see https://github.com/pyinstaller/pyinstaller/issues/2931
|
||||||
|
wine python -m pip install -I pyinstaller==3.2.1
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function install_pip_depends()
|
function install_pip_depends()
|
||||||
|
@ -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 \
|
||||||
|
@ -173,11 +175,14 @@ 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)
|
if [ "${MACHINE_TYPE}" == 'x86_64' ]; then
|
||||||
wine packages/pyinstaller/dist/Bitmessage_x86_$VERSION.exe -t
|
EXE=Bitmessage_x64_$VERSION.exe
|
||||||
fi
|
else
|
||||||
|
EXE=Bitmessage_x86_$VERSION.exe
|
||||||
|
fi
|
||||||
|
wine packages/pyinstaller/dist/$EXE -t
|
||||||
}
|
}
|
||||||
|
|
||||||
# prepare on ubuntu
|
# prepare on ubuntu
|
||||||
|
|
106
docs/address.rst
Normal file
106
docs/address.rst
Normal 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.
|
|
@ -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
|
||||||
|
|
19
docs/encrypted_payload.rst
Normal file
19
docs/encrypted_payload.rst
Normal 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
257
docs/encryption.rst
Normal 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`
|
55
docs/extended_encoding.rst
Normal file
55
docs/extended_encoding.rst
Normal 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.
|
|
@ -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
77
docs/pow.rst
Normal 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
7
docs/pow_formula.rst
Normal 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
997
docs/protocol.rst
Normal 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>`
|
|
@ -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
53
docs/useragent.rst
Normal 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 ``;``
|
|
@ -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
|
||||||
|
|
81
packages/AppImage/AppImageBuilder.yml
Normal file
81
packages/AppImage/AppImageBuilder.yml
Normal 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}'
|
|
@ -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
|
||||||
|
|
2
packages/AppImage/qt.conf
Normal file
2
packages/AppImage/qt.conf
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[Paths]
|
||||||
|
Prefix = ../lib/x86_64-linux-gnu/qt4
|
357
packages/android/buildozer.spec
Normal file
357
packages/android/buildozer.spec
Normal 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
|
|
@ -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
16
packages/docker/launcher.sh
Executable 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 "$@"
|
|
@ -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
|
||||||
|
@ -22,13 +24,17 @@ os.chdir(srcPath)
|
||||||
|
|
||||||
snapshot = False
|
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=[
|
||||||
(os.path.join(spec_root[:-20], 'pybitmessage.egg-info') + '/*',
|
(os.path.join(spec_root[:-20], 'pybitmessage.egg-info') + '/*',
|
||||||
'pybitmessage.egg-info')
|
'pybitmessage.egg-info')
|
||||||
] + copy_metadata('msgpack-python') + copy_metadata('qrcode')
|
] + copy_metadata('msgpack-python') + copy_metadata('qrcode')
|
||||||
+ copy_metadata('six') + copy_metadata('stem'),
|
+ copy_metadata('six') + copy_metadata('stem'),
|
||||||
pathex=[outPath],
|
pathex=[outPath],
|
||||||
|
@ -38,16 +44,15 @@ a = Analysis(
|
||||||
'plugins.menu_qrcode', 'plugins.proxyconfig_stem'
|
'plugins.menu_qrcode', 'plugins.proxyconfig_stem'
|
||||||
],
|
],
|
||||||
# https://github.com/pyinstaller/pyinstaller/wiki/Recipe-PyQt4-API-Version
|
# https://github.com/pyinstaller/pyinstaller/wiki/Recipe-PyQt4-API-Version
|
||||||
runtime_hooks=[
|
runtime_hooks = [
|
||||||
os.path.join(hookspath, hook) for hook in (
|
os.path.join(hookspath, hook) for hook in (
|
||||||
'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',
|
||||||
|
@ -96,16 +108,15 @@ a.binaries += [
|
||||||
('libeay32.dll', os.path.join(openSSLPath, 'libeay32.dll'), 'BINARY'),
|
('libeay32.dll', os.path.join(openSSLPath, 'libeay32.dll'), 'BINARY'),
|
||||||
(os.path.join('bitmsghash', 'bitmsghash%i.dll' % arch),
|
(os.path.join('bitmsghash', 'bitmsghash%i.dll' % arch),
|
||||||
os.path.join(srcPath, 'bitmsghash', 'bitmsghash%i.dll' % arch),
|
os.path.join(srcPath, 'bitmsghash', 'bitmsghash%i.dll' % arch),
|
||||||
'BINARY'),
|
'BINARY'),
|
||||||
(os.path.join('bitmsghash', 'bitmsghash.cl'),
|
(os.path.join('bitmsghash', 'bitmsghash.cl'),
|
||||||
os.path.join(srcPath, 'bitmsghash', 'bitmsghash.cl'), 'BINARY'),
|
os.path.join(srcPath, 'bitmsghash', 'bitmsghash.cl'), 'BINARY'),
|
||||||
(os.path.join('sslkeys', 'cert.pem'),
|
(os.path.join('sslkeys', 'cert.pem'),
|
||||||
os.path.join(srcPath, 'sslkeys', 'cert.pem'), 'BINARY'),
|
os.path.join(srcPath, 'sslkeys', 'cert.pem'), 'BINARY'),
|
||||||
(os.path.join('sslkeys', 'key.pem'),
|
(os.path.join('sslkeys', 'key.pem'),
|
||||||
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(
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# https://github.com/pyinstaller/pyinstaller/wiki/Recipe-PyQt4-API-Version
|
# https://github.com/pyinstaller/pyinstaller/wiki/Recipe-PyQt4-API-Version
|
||||||
import sip
|
import sip
|
||||||
|
|
||||||
sip.setapi(u'QDate', 2)
|
sip.setapi(u'QDate', 2)
|
||||||
sip.setapi(u'QDateTime', 2)
|
sip.setapi(u'QDateTime', 2)
|
||||||
sip.setapi(u'QString', 2)
|
sip.setapi(u'QString', 2)
|
||||||
sip.setapi(u'QTextStream', 2)
|
sip.setapi(u'QTextStream', 2)
|
||||||
sip.setapi(u'QTime', 2)
|
sip.setapi(u'QTime', 2)
|
||||||
sip.setapi(u'QUrl', 2)
|
sip.setapi(u'QUrl', 2)
|
||||||
sip.setapi(u'QVariant', 2)
|
sip.setapi(u'QVariant', 2)
|
||||||
|
|
76
packages/snap/snapcraft.yaml
Normal file
76
packages/snap/snapcraft.yaml
Normal 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
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
29
setup.py
29
setup.py
|
@ -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,
|
||||||
|
|
|
@ -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, ''
|
||||||
|
|
||||||
|
|
303
src/api.py
303
src/api.py
|
@ -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,55 +1281,73 @@ 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")
|
||||||
# Let us do the POW and attach it to the front
|
|
||||||
target = 2**64 / (
|
nonce, = unpack('>Q', encryptedPayload[:8])
|
||||||
(
|
objectType, toStreamNumber, expiresTime = \
|
||||||
len(encryptedPayload)
|
protocol.decodeObjectParameters(encryptedPayload)
|
||||||
+ requiredPayloadLengthExtraBytes
|
|
||||||
+ 8
|
if nonce == 0: # Let us do the POW and attach it to the front
|
||||||
) * requiredAverageProofOfWorkNonceTrialsPerByte)
|
encryptedPayload = encryptedPayload[8:]
|
||||||
logger.info(
|
TTL = expiresTime - time.time() + 300 # a bit of extra padding
|
||||||
'(For msg message via API) Doing proof of work. Total required'
|
# Let us do the POW and attach it to the front
|
||||||
' difficulty: %s\nRequired small message difficulty: %s',
|
logger.debug("expiresTime: %s", expiresTime)
|
||||||
float(requiredAverageProofOfWorkNonceTrialsPerByte)
|
logger.debug("TTL: %s", TTL)
|
||||||
/ defaults.networkDefaultProofOfWorkNonceTrialsPerByte,
|
logger.debug("objectType: %s", objectType)
|
||||||
float(requiredPayloadLengthExtraBytes)
|
logger.info(
|
||||||
/ defaults.networkDefaultPayloadLengthExtraBytes,
|
'(For msg message via API) Doing proof of work. Total required'
|
||||||
)
|
' difficulty: %s\nRequired small message difficulty: %s',
|
||||||
powStartTime = time.time()
|
float(nonceTrialsPerByte)
|
||||||
initialHash = hashlib.sha512(encryptedPayload).digest()
|
/ networkDefaultProofOfWorkNonceTrialsPerByte,
|
||||||
trialValue, nonce = proofofwork.run(target, initialHash)
|
float(payloadLengthExtraBytes)
|
||||||
logger.info(
|
/ networkDefaultPayloadLengthExtraBytes,
|
||||||
'(For msg message via API) Found proof of work %s\nNonce: %s\n'
|
)
|
||||||
'POW took %s seconds. %s nonce trials per second.',
|
powStartTime = time.time()
|
||||||
trialValue, nonce, int(time.time() - powStartTime),
|
target = 2**64 / (
|
||||||
nonce / (time.time() - powStartTime)
|
nonceTrialsPerByte * (
|
||||||
)
|
len(encryptedPayload) + 8 + payloadLengthExtraBytes + ((
|
||||||
encryptedPayload = pack('>Q', nonce) + encryptedPayload
|
TTL * (
|
||||||
toStreamNumber = decodeVarint(encryptedPayload[16:26])[0]
|
len(encryptedPayload) + 8 + payloadLengthExtraBytes
|
||||||
|
)) / (2 ** 16))
|
||||||
|
))
|
||||||
|
initialHash = hashlib.sha512(encryptedPayload).digest()
|
||||||
|
trialValue, nonce = proofofwork.run(target, initialHash)
|
||||||
|
logger.info(
|
||||||
|
'(For msg message via API) Found proof of work %s\nNonce: %s\n'
|
||||||
|
'POW took %s seconds. %s nonce trials per second.',
|
||||||
|
trialValue, nonce, int(time.time() - powStartTime),
|
||||||
|
nonce / (time.time() - powStartTime)
|
||||||
|
)
|
||||||
|
encryptedPayload = pack('>Q', nonce) + encryptedPayload
|
||||||
|
|
||||||
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):
|
||||||
|
|
49
src/backend/address_generator.py
Normal file
49
src/backend/address_generator.py
Normal 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'
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
110
src/bitmessagekivy/base_navigation.py
Normal file
110
src/bitmessagekivy/base_navigation.py
Normal 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'))
|
164
src/bitmessagekivy/baseclass/addressbook.py
Normal file
164
src/bitmessagekivy/baseclass/addressbook.py
Normal 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()
|
50
src/bitmessagekivy/baseclass/addressbook_widgets.py
Normal file
50
src/bitmessagekivy/baseclass/addressbook_widgets.py
Normal 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'
|
67
src/bitmessagekivy/baseclass/allmail.py
Normal file
67
src/bitmessagekivy/baseclass/allmail.py
Normal 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))
|
11
src/bitmessagekivy/baseclass/chat.py
Normal file
11
src/bitmessagekivy/baseclass/chat.py
Normal 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"""
|
236
src/bitmessagekivy/baseclass/common.py
Normal file
236
src/bitmessagekivy/baseclass/common.py
Normal 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)
|
22
src/bitmessagekivy/baseclass/common_mail_detail.py
Normal file
22
src/bitmessagekivy/baseclass/common_mail_detail.py
Normal 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"
|
58
src/bitmessagekivy/baseclass/draft.py
Normal file
58
src/bitmessagekivy/baseclass/draft.py
Normal 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))
|
64
src/bitmessagekivy/baseclass/inbox.py
Normal file
64
src/bitmessagekivy/baseclass/inbox.py
Normal 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)
|
97
src/bitmessagekivy/baseclass/login.py
Normal file
97
src/bitmessagekivy/baseclass/login.py
Normal 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"""
|
242
src/bitmessagekivy/baseclass/maildetail.py
Normal file
242
src/bitmessagekivy/baseclass/maildetail.py
Normal 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)
|
188
src/bitmessagekivy/baseclass/msg_composer.py
Normal file
188
src/bitmessagekivy/baseclass/msg_composer.py
Normal 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"]
|
||||||
|
)
|
230
src/bitmessagekivy/baseclass/myaddress.py
Normal file
230
src/bitmessagekivy/baseclass/myaddress.py
Normal 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)
|
64
src/bitmessagekivy/baseclass/myaddress_widgets.py
Normal file
64
src/bitmessagekivy/baseclass/myaddress_widgets.py
Normal 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
|
54
src/bitmessagekivy/baseclass/network.py
Normal file
54
src/bitmessagekivy/baseclass/network.py
Normal 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))
|
66
src/bitmessagekivy/baseclass/payment.py
Normal file
66
src/bitmessagekivy/baseclass/payment.py
Normal 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"""
|
231
src/bitmessagekivy/baseclass/popup.py
Normal file
231
src/bitmessagekivy/baseclass/popup.py
Normal 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"""
|
34
src/bitmessagekivy/baseclass/qrcode.py
Normal file
34
src/bitmessagekivy/baseclass/qrcode.py
Normal 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')
|
105
src/bitmessagekivy/baseclass/scan_screen.py
Normal file
105
src/bitmessagekivy/baseclass/scan_screen.py
Normal 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
|
47
src/bitmessagekivy/baseclass/sent.py
Normal file
47
src/bitmessagekivy/baseclass/sent.py
Normal 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
|
10
src/bitmessagekivy/baseclass/settings.py
Normal file
10
src/bitmessagekivy/baseclass/settings.py
Normal 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"""
|
33
src/bitmessagekivy/baseclass/trash.py
Normal file
33
src/bitmessagekivy/baseclass/trash.py
Normal 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]
|
31
src/bitmessagekivy/get_platform.py
Normal file
31
src/bitmessagekivy/get_platform.py
Normal 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"
|
80
src/bitmessagekivy/identiconGeneration.py
Normal file
80
src/bitmessagekivy/identiconGeneration.py
Normal 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
|
71
src/bitmessagekivy/kivy_helper_search.py
Normal file
71
src/bitmessagekivy/kivy_helper_search.py
Normal 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)
|
42
src/bitmessagekivy/kivy_state.py
Normal file
42
src/bitmessagekivy/kivy_state.py
Normal 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
|
26
src/bitmessagekivy/kv/addressbook.kv
Normal file
26
src/bitmessagekivy/kv/addressbook.kv
Normal 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:
|
25
src/bitmessagekivy/kv/allmails.kv
Normal file
25
src/bitmessagekivy/kv/allmails.kv
Normal 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:
|
82
src/bitmessagekivy/kv/chat.kv
Normal file
82
src/bitmessagekivy/kv/chat.kv
Normal 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
|
58
src/bitmessagekivy/kv/chat_list.kv
Normal file
58
src/bitmessagekivy/kv/chat_list.kv
Normal 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
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user