Merge branch 'master' into papirus-support

This commit is contained in:
evilsocket 2019-09-29 21:24:40 +02:00 committed by GitHub
commit 7a6243a4e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1232 additions and 139 deletions

@ -92,6 +92,34 @@ The UI is available either via display if installed, or via http://pwnagotchi.lo
* **PWND**: Number of handshakes captured in this session and number of unique networks we own at least one handshake of, from the beginning. * **PWND**: Number of handshakes captured in this session and number of unique networks we own at least one handshake of, from the beginning.
* **AUTO**: This indicates that the algorithm is running with AI disabled (or still loading), it disappears once the AI dependencies have been bootrapped and the neural network loaded. * **AUTO**: This indicates that the algorithm is running with AI disabled (or still loading), it disappears once the AI dependencies have been bootrapped and the neural network loaded.
#### Languages
Pwnagotchi is able to speak multiple languages!! Currently supported is:
* **english** (default)
* german
If you want to add a language use the `language.sh` script.
If you want to add for example the language **italian** you would type:
```shell
./scripts/language.sh add it
# Now make your changes to the file
# sdcard/scripts/rootfs/pwnagotchi/scripts/pwnagotchi/locale/it/LC_MESSAGES/voice.po
./scripts/language.sh compile it
# DONE
```
If you changed the `voice.py`- File, the translations need an update. Do it like this:
```shell
./scripts/language.sh update it
# Now make your changes to the file (changed lines are marked with "fuzzy")
# sdcard/scripts/rootfs/pwnagotchi/scripts/pwnagotchi/locale/it/LC_MESSAGES/voice.po
./scripts/language.sh compile it
# DONE
```
### Random Info ### Random Info
- `hostname` sets the unit name. - `hostname` sets the unit name.

@ -195,6 +195,9 @@ function provision_raspbian() {
# slows down boot # slows down boot
systemctl disable apt-daily.timer apt-daily.service apt-daily-upgrade.timer apt-daily-upgrade.service systemctl disable apt-daily.timer apt-daily.service apt-daily-upgrade.timer apt-daily-upgrade.service
# unecessary services
systemctl disable triggerhappy bluetooth wpa_supplicant
EOF EOF
sed -i'' 's/^#//g' etc/ld.so.preload sed -i'' 's/^#//g' etc/ld.so.preload
cd "${REPO_DIR}" cd "${REPO_DIR}"
@ -213,7 +216,7 @@ usage: $0 [OPTIONS]
-i <file> # Provide the path of an already downloaded raspbian image -i <file> # Provide the path of an already downloaded raspbian image
-o <file> # Name of the img-file (default: pwnagotchi.img) -o <file> # Name of the img-file (default: pwnagotchi.img)
-s <size> # Size which should be added to second partition (in Gigabyte) (default: 4) -s <size> # Size which should be added to second partition (in Gigabyte) (default: 4)
-v <version> # Version of raspbian (Supported: $SUPPORTED_RASPBIAN_VERSIONS; default: latest) -v <version> # Version of raspbian (Supported: ${SUPPORTED_RASPBIAN_VERSIONS[*]}; default: latest)
-p # Only run provisioning (assumes the image is already mounted) -p # Only run provisioning (assumes the image is already mounted)
-d # Only run dependencies checks -d # Only run dependencies checks
-h # Show this help -h # Show this help

71
scripts/language.sh Executable file

@ -0,0 +1,71 @@
#!/bin/bash
set -eu
DEPENDENCIES=( 'xgettext' 'msgfmt' 'msgmerge' )
COMMANDS=( 'add' 'update' 'delete' 'compile' )
REPO_DIR="$(dirname "$(dirname "$(realpath "$0")")")"
LOCALE_DIR="${REPO_DIR}/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale"
VOICE_FILE="${REPO_DIR}/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/voice.py"
function usage() {
cat <<EOF
usage: $0 <command> [options]
Commands:
add <language>
delete <language>
compile <language>
update <language>
EOF
}
for REQ in "${DEPENDENCIES[@]}"; do
if ! type "$REQ" >/dev/null 2>&1; then
echo "Dependency check failed for ${REQ}"
exit 1
fi
done
if [[ ! "${COMMANDS[*]}" =~ $1 ]]; then
usage
fi
function add_lang() {
mkdir -p "$LOCALE_DIR/$1/LC_MESSAGES"
cp -n "$LOCALE_DIR/voice.pot" "$LOCALE_DIR/$1/LC_MESSAGES/voice.po"
}
function del_lang() {
# set -eu is present; so not dangerous
rm -rf "$LOCALE_DIR/$1"
}
function comp_lang() {
msgfmt -o "$LOCALE_DIR/$1/LC_MESSAGES/voice.mo" "$LOCALE_DIR/$1/LC_MESSAGES/voice.po"
}
function update_lang() {
xgettext -d voice -o "$LOCALE_DIR/voice.pot" "$VOICE_FILE"
msgmerge --update "$LOCALE_DIR/$1/LC_MESSAGES/voice.po" "$LOCALE_DIR/voice.pot"
}
case "$1" in
add)
add_lang "$2"
;;
delete)
del_lang "$2"
;;
compile)
comp_lang "$2"
;;
update)
update_lang "$2"
;;
esac

@ -7,12 +7,12 @@ USB_IFACE_NET=10.0.0.0/24
# host interface to use for upstream connection # host interface to use for upstream connection
UPSTREAM_IFACE=${2:-enxe4b97aa99867} UPSTREAM_IFACE=${2:-enxe4b97aa99867}
ip addr add $USB_IFACE_IP/24 dev $USB_IFACE ip addr add "$USB_IFACE_IP/24" dev "$USB_IFACE"
ip link set $USB_IFACE up ip link set "$USB_IFACE" up
iptables -A FORWARD -o $UPSTREAM_IFACE -i $USB_IFACE -s $USB_IFACE_NET -m conntrack --ctstate NEW -j ACCEPT iptables -A FORWARD -o "$UPSTREAM_IFACE" -i "$USB_IFACE" -s "$USB_IFACE_NET" -m conntrack --ctstate NEW -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -F POSTROUTING iptables -t nat -F POSTROUTING
iptables -t nat -A POSTROUTING -o $UPSTREAM_IFACE -j MASQUERADE iptables -t nat -A POSTROUTING -o "$UPSTREAM_IFACE" -j MASQUERADE
echo 1 > /proc/sys/net/ipv4/ip_forward echo 1 > /proc/sys/net/ipv4/ip_forward

@ -1195,3 +1195,8 @@ dtoverlay=dwc2
dtparam=spi=on dtparam=spi=on
dtoverlay=spi1-3cs dtoverlay=spi1-3cs
# Disable bluetooth
dtoverlay=pi3-disable-bt
# Disable audio
dtparam=audio=off

@ -5,6 +5,9 @@ iface lo inet loopback
allow-hotplug wlan0 allow-hotplug wlan0
iface wlan0 inet static iface wlan0 inet static
allow-hotplug eth0
iface eth0 inet dhcp
allow-hotplug usb0 allow-hotplug usb0
iface usb0 inet static iface usb0 inet static
address 10.0.0.2 address 10.0.0.2

@ -10,5 +10,7 @@
# bits. # bits.
# #
# By default this script does nothing. # By default this script does nothing.
# Powersave (Disable HDMI) ~30ma
/opt/vc/bin/tvservice -o
/root/pwnagotchi/scripts/startup.sh & /root/pwnagotchi/scripts/startup.sh &
exit 0 exit 0

@ -1,5 +1,7 @@
# main algorithm configuration # main algorithm configuration
main: main:
# currently implemented: en (default), de
lang: en
# monitor interface to use # monitor interface to use
iface: mon0 iface: mon0
# command to run to bring the mon interface up in case it's not up already # command to run to bring the mon interface up in case it's not up already
@ -93,9 +95,11 @@ ui:
enabled: true enabled: true
rotation: 180 rotation: 180
# Possible options inkyphat/inky, papirus/papi or waveshare/ws # Possible options inkyphat/inky, papirus/papi or waveshare/ws
type: 'waveshare' type: 'waveshare_2'
# Possible options red/yellow/black (black used for monocromatic displays) # Possible options red/yellow/black (black used for monocromatic displays)
color: 'black' color: 'black'
# How often to do a full refresh 0 all the time, -1 never
refresh: 50
video: video:
enabled: true enabled: true
address: '10.0.0.2' address: '10.0.0.2'

@ -5,6 +5,7 @@ defscrollback 1024
startup_message off startup_message off
altscreen on altscreen on
autodetach on autodetach on
zombie kr
activity "activity in %n (%t)" activity "activity in %n (%t)"
bell_msg "bell in %n (%t)" bell_msg "bell in %n (%t)"

@ -5,6 +5,7 @@ defscrollback 1024
startup_message off startup_message off
altscreen on altscreen on
autodetach on autodetach on
zombie kr
activity "activity in %n (%t)" activity "activity in %n (%t)"
bell_msg "bell in %n (%t)" bell_msg "bell in %n (%t)"

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
for i in `seq 1 $1`; for i in $(seq 1 "$1");
do do
echo 0 >/sys/class/leds/led0/brightness echo 0 >/sys/class/leds/led0/brightness
sleep 0.3 sleep 0.3
@ -10,3 +10,8 @@ done
echo 0 >/sys/class/leds/led0/brightness echo 0 >/sys/class/leds/led0/brightness
sleep 0.3 sleep 0.3
# Powersave options
# Disable power LED ~30ma
echo none >/sys/class/leds/led0/trigger
echo 1 >/sys/class/leds/led0/brightness

@ -8,7 +8,7 @@ import core
import pwnagotchi import pwnagotchi
from pwnagotchi.log import SessionParser from pwnagotchi.log import SessionParser
import pwnagotchi.voice as voice from pwnagotchi.voice import Voice
from pwnagotchi.agent import Agent from pwnagotchi.agent import Agent
from pwnagotchi.ui.display import Display from pwnagotchi.ui.display import Display
@ -74,7 +74,7 @@ if args.do_manual:
auth.set_access_token(config['twitter']['access_token_key'], config['twitter']['access_token_secret']) auth.set_access_token(config['twitter']['access_token_key'], config['twitter']['access_token_secret'])
api = tweepy.API(auth) api = tweepy.API(auth)
tweet = voice.on_log_tweet(log) tweet = Voice(lang=config['main']['lang']).on_log_tweet(log)
api.update_with_media(filename=picture, status=tweet) api.update_with_media(filename=picture, status=tweet)
log.save_session_id() log.save_session_id()

@ -54,7 +54,7 @@ class Stats(object):
def load(self): def load(self):
with self._lock: with self._lock:
if os.path.exists(self.path): if os.path.exists(self.path) and os.path.getsize(self.path) > 0:
core.log("[ai] loading %s" % self.path) core.log("[ai] loading %s" % self.path)
with open(self.path, 'rt') as fp: with open(self.path, 'rt') as fp:
obj = json.load(fp) obj = json.load(fp)

@ -0,0 +1,344 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-29 13:34+0200\n"
"PO-Revision-Date: 2019-09-29 14:00+0200\n"
"Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n"
"Language-Team: DE <33197631+dadav@users.noreply.github.com>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: voice.py:16
msgid "ZzzzZZzzzzZzzz"
msgstr ""
#: voice.py:21
msgid ""
"Hi, I'm Pwnagotchi!\n"
"Starting ..."
msgstr ""
"Hi, ich bin\n"
"ein Pwnagotchi!\n"
"Starte ..."
#: voice.py:22
msgid ""
"New day, new hunt,\n"
"new pwns!"
msgstr ""
"Neuer Tag, neue Jagd,\n"
"neue Pwns!"
#: voice.py:23
msgid "Hack the Planet!"
msgstr "Hack den Planet!"
#: voice.py:28
msgid "AI ready."
msgstr "KI bereit."
#: voice.py:29
msgid ""
"The neural network\n"
"is ready."
msgstr ""
"Das neurale Netz\n"
"ist bereit."
#: voice.py:39
#, python-brace-format
msgid ""
"Hey, channel {channel} is\n"
"free! Your AP will\n"
"say thanks."
msgstr ""
"Hey, Channel {channel} ist\n"
"frei! Dein AP wird\n"
"es dir danken."
#: voice.py:44
msgid "I'm bored ..."
msgstr "Mir ist langweilig..."
#: voice.py:45
msgid "Let's go for a walk!"
msgstr "Lass uns laufen gehen!"
#: voice.py:49
msgid ""
"This is the best\n"
"day of my life!"
msgstr ""
"Das ist der beste\n"
"Tag meines Lebens."
#: voice.py:53
msgid "Shitty day :/"
msgstr "Scheis Tag :/"
#: voice.py:58
msgid "I'm extremely bored ..."
msgstr "Mir ist sau langweilig..."
#: voice.py:59
msgid "I'm very sad ..."
msgstr "Ich bin sehr traurig..."
#: voice.py:60
msgid "I'm sad"
msgstr "Ich bin traurig"
#: voice.py:66
msgid "I'm living the life!"
msgstr "Ich lebe das Leben!"
#: voice.py:67
msgid "I pwn therefore I am."
msgstr "Ich pwne, also bin ich."
#: voice.py:68
msgid "So many networks!!!"
msgstr "So viele Netwerke!!!"
#: voice.py:69
msgid ""
"I'm having so much\n"
"fun!"
msgstr ""
"Ich habe sooo viel\n"
"Spaß!"
#: voice.py:70
msgid ""
"My crime is that of\n"
"curiosity ..."
msgstr ""
"Mein Verbrechen ist\n"
"das der Neugier ..."
#: voice.py:75
#, python-brace-format
msgid ""
"Hello\n"
"{name}!\n"
"Nice to meet you. {name}"
msgstr ""
"Hallo {name},\n"
"Nett Dich\n"
"kennenzulernen."
#: voice.py:76
#, python-brace-format
msgid ""
"Unit\n"
"{name}\n"
"is nearby! {name}"
msgstr ""
"Gerät {name}\n"
"ist in der\n"
"nähe!!"
#: voice.py:81
#, python-brace-format
msgid ""
"Uhm ...\n"
"goodbye\n"
"{name}"
msgstr ""
"Uhm ...\n"
"tschüß\n"
"{name}"
#: voice.py:82
#, python-brace-format
msgid ""
"{name}\n"
"is gone ..."
msgstr ""
"{name}\n"
"ist weg ..."
#: voice.py:87
#, python-brace-format
msgid ""
"Whoops ...\n"
"{name}\n"
"is gone."
msgstr ""
"Whoops ...\n"
"{name}\n"
"ist weg."
#: voice.py:88
#, python-brace-format
msgid ""
"{name}\n"
"missed!"
msgstr ""
"{name}\n"
"verpasst!"
#: voice.py:89
msgid "Missed!"
msgstr "Verpasst!"
#: voice.py:94
msgid ""
"Nobody wants to\n"
"play with me ..."
msgstr ""
"Niemand will mit\n"
"mir spielen ..."
#: voice.py:95
msgid "I feel so alone ..."
msgstr ""
"Ich fühl mich\n"
"so alleine ..."
#: voice.py:96
msgid "Where's everybody?!"
msgstr "Wo sind denn alle?"
#: voice.py:101
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Schlafe für {secs}s"
#: voice.py:102
msgid "Zzzzz"
msgstr ""
#: voice.py:103
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr ""
#: voice.py:112
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Warte für {secs}s ..."
#: voice.py:114
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Schaue mich um ({secs}s)"
#: voice.py:121
#, python-brace-format
msgid ""
"Hey\n"
"{what}\n"
"let's be friends!"
msgstr ""
"Hey\n"
"{what}\n"
"lass uns Freunde sein!"
#: voice.py:122
#, python-brace-format
msgid ""
"Associating to\n"
"{what}"
msgstr ""
"Verbinde mit\n"
"{what}"
#: voice.py:123
#, python-brace-format
msgid ""
"Yo\n"
"{what}!"
msgstr ""
#: voice.py:128
#, python-brace-format
msgid ""
"Just decided that\n"
"{mac}\n"
"needs no WiFi!"
msgstr ""
"Habe gerade entschieden,\n"
"dass {mac}\n"
"kein WiFi brauch!"
#: voice.py:129
#, python-brace-format
msgid ""
"Deauthenticating\n"
"{mac}"
msgstr ""
"Deauthentifiziere\n"
"{mac}"
#: voice.py:130
#, python-brace-format
msgid ""
"Kickbanning\n"
"{mac}!"
msgstr ""
"Kicke\n"
"{mac}!"
#: voice.py:135
#, python-brace-format
msgid ""
"Cool, we got {num}\n"
"new handshake{plural}!"
msgstr ""
"Cool, wir haben {num}\n"
"neue Handshake{plural}!"
#: voice.py:139
msgid ""
"Ops, something\n"
"went wrong ...\n"
"Rebooting ..."
msgstr ""
"Ops, da ist etwas\n"
"schief gelaufen ...\n"
"Starte neu ..."
#: voice.py:143
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "{num} Stationen gekicked\n"
#: voice.py:144
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "{num} Freunde gefunden\n"
#: voice.py:145
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "{num} Handshakes aufgez.\n"
#: voice.py:147
msgid "Met 1 peer"
msgstr "1 Peer getroffen."
#: voice.py:149
#, python-brace-format
msgid "Met {num} peers"
msgstr "{num} Peers getroffen"
#: voice.py:154
#, python-brace-format
msgid ""
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgstr ""
"Ich war {duration} am Pwnen und habe {deauthed} Clients gekickt! Außerdem habe ich "
"{associated} neue Freunde getroffen und {handshakes} Handshakes gefressen! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"

@ -0,0 +1,288 @@
# pwnigotchi voice data
# Copyright (C) 2019
# This file is distributed under the same license as the pwnagotchi package.
# FIRST AUTHOR <33197631+dadav@users.noreply.github.com>, 2019.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-29 13:42+0200\n"
"PO-Revision-Date: 2019-09-29 14:00+0200\n"
"Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n"
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
"Language: english\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: voice.py:16
msgid "ZzzzZZzzzzZzzz"
msgstr ""
#: voice.py:21
msgid ""
"Hi, I'm Pwnagotchi!\n"
"Starting ..."
msgstr ""
#: voice.py:22
msgid ""
"New day, new hunt,\n"
"new pwns!"
msgstr ""
#: voice.py:23
msgid "Hack the Planet!"
msgstr ""
#: voice.py:28
msgid "AI ready."
msgstr ""
#: voice.py:29
msgid ""
"The neural network\n"
"is ready."
msgstr ""
#: voice.py:39
#, python-brace-format
msgid ""
"Hey, channel {channel} is\n"
"free! Your AP will\n"
"say thanks."
msgstr ""
#: voice.py:44
msgid "I'm bored ..."
msgstr ""
#: voice.py:45
msgid "Let's go for a walk!"
msgstr ""
#: voice.py:49
msgid ""
"This is the best\n"
"day of my life!"
msgstr ""
#: voice.py:53
msgid "Shitty day :/"
msgstr ""
#: voice.py:58
msgid "I'm extremely bored ..."
msgstr ""
#: voice.py:59
msgid "I'm very sad ..."
msgstr ""
#: voice.py:60
msgid "I'm sad"
msgstr ""
#: voice.py:66
msgid "I'm living the life!"
msgstr ""
#: voice.py:67
msgid "I pwn therefore I am."
msgstr ""
#: voice.py:68
msgid "So many networks!!!"
msgstr ""
#: voice.py:69
msgid ""
"I'm having so much\n"
"fun!"
msgstr ""
#: voice.py:70
msgid ""
"My crime is that of\n"
"curiosity ..."
msgstr ""
#: voice.py:75
#, python-brace-format
msgid ""
"Hello\n"
"{name}!\n"
"Nice to meet you. {name}"
msgstr ""
#: voice.py:76
#, python-brace-format
msgid ""
"Unit\n"
"{name}\n"
"is nearby! {name}"
msgstr ""
#: voice.py:81
#, python-brace-format
msgid ""
"Uhm ...\n"
"goodbye\n"
"{name}"
msgstr ""
#: voice.py:82
#, python-brace-format
msgid ""
"{name}\n"
"is gone ..."
msgstr ""
#: voice.py:87
#, python-brace-format
msgid ""
"Whoops ...\n"
"{name}\n"
"is gone."
msgstr ""
#: voice.py:88
#, python-brace-format
msgid ""
"{name}\n"
"missed!"
msgstr ""
#: voice.py:89
msgid "Missed!"
msgstr ""
#: voice.py:94
msgid ""
"Nobody wants to\n"
"play with me ..."
msgstr ""
#: voice.py:95
msgid "I feel so alone ..."
msgstr ""
#: voice.py:96
msgid "Where's everybody?!"
msgstr ""
#: voice.py:101
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr ""
#: voice.py:102
msgid "Zzzzz"
msgstr ""
#: voice.py:103
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr ""
#: voice.py:112
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr ""
#: voice.py:114
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr ""
#: voice.py:121
#, python-brace-format
msgid ""
"Hey\n"
"{what}\n"
"let's be friends!"
msgstr ""
#: voice.py:122
#, python-brace-format
msgid ""
"Associating to\n"
"{what}"
msgstr ""
#: voice.py:123
#, python-brace-format
msgid ""
"Yo\n"
"{what}!"
msgstr ""
#: voice.py:128
#, python-brace-format
msgid ""
"Just decided that\n"
"{mac}\n"
"needs no WiFi!"
msgstr ""
#: voice.py:129
#, python-brace-format
msgid ""
"Deauthenticating\n"
"{mac}"
msgstr ""
#: voice.py:130
#, python-brace-format
msgid ""
"Kickbanning\n"
"{mac}!"
msgstr ""
#: voice.py:135
#, python-brace-format
msgid ""
"Cool, we got {num}\n"
"new handshake{plural}!"
msgstr ""
#: voice.py:139
msgid ""
"Ops, something\n"
"went wrong ...\n"
"Rebooting ..."
msgstr ""
#: voice.py:143
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr ""
#: voice.py:144
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr ""
#: voice.py:145
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr ""
#: voice.py:147
msgid "Met 1 peer"
msgstr ""
#: voice.py:149
#, python-brace-format
msgid "Met {num} peers"
msgstr ""
#: voice.py:154
#, python-brace-format
msgid ""
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgstr ""

@ -80,6 +80,9 @@ class Display(View):
self._video_address = config['ui']['display']['video']['address'] self._video_address = config['ui']['display']['video']['address']
self._display_type = config['ui']['display']['type'] self._display_type = config['ui']['display']['type']
self._display_color = config['ui']['display']['color'] self._display_color = config['ui']['display']['color']
self.full_refresh_count = 0
self.full_refresh_trigger = config['ui']['display']['refresh']
self._render_cb = None self._render_cb = None
self._display = None self._display = None
self._httpd = None self._httpd = None
@ -108,8 +111,11 @@ class Display(View):
def _is_papirus(self): def _is_papirus(self):
return self._display_type in ('papirus', 'papi') return self._display_type in ('papirus', 'papi')
def _is_waveshare(self): def _is_waveshare1(self):
return self._display_type in ('waveshare', 'ws') return self._display_type in ('waveshare_1', 'ws_1', 'waveshare1', 'ws1')
def _is_waveshare2(self):
return self._display_type in ('waveshare_2', 'ws_2', 'waveshare2', 'ws2')
def _init_display(self): def _init_display(self):
if self._is_inky(): if self._is_inky():
@ -117,20 +123,32 @@ class Display(View):
self._display = InkyPHAT(self._display_color) self._display = InkyPHAT(self._display_color)
self._display.set_border(InkyPHAT.BLACK) self._display.set_border(InkyPHAT.BLACK)
self._render_cb = self._inky_render self._render_cb = self._inky_render
elif self._is_papirus(): elif self._is_papirus():
from papirus import Papirus from papirus import Papirus
os.environ['EPD_SIZE'] = '2.0' os.environ['EPD_SIZE'] = '2.0'
self._display = Papirus() self._display = Papirus()
self._display.clear() self._display.clear()
self._render_cb = self._papirus_render self._render_cb = self._papirus_render
elif self._is_waveshare():
from pwnagotchi.ui.waveshare import EPD elif self._is_waveshare1():
from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD
# core.log("display module started")
self._display = EPD()
self._display.init(self._display.lut_full_update)
self._display.Clear(0xFF)
self._display.init(self._display.lut_partial_update)
self._render_cb = self._waveshare_render
elif self._is_waveshare2():
from pwnagotchi.ui.waveshare.v2.waveshare import EPD
# core.log("display module started") # core.log("display module started")
self._display = EPD() self._display = EPD()
self._display.init(self._display.FULL_UPDATE) self._display.init(self._display.FULL_UPDATE)
self._display.Clear(WHITE) self._display.Clear(WHITE)
self._display.init(self._display.PART_UPDATE) self._display.init(self._display.PART_UPDATE)
self._render_cb = self._waveshare_render self._render_cb = self._waveshare_render
else: else:
core.log("unknown display type %s" % self._display_type) core.log("unknown display type %s" % self._display_type)
@ -179,7 +197,19 @@ class Display(View):
def _waveshare_render(self): def _waveshare_render(self):
buf = self._display.getbuffer(self.canvas) buf = self._display.getbuffer(self.canvas)
self._display.displayPartial(buf) if self._is_waveshare1:
if self.full_refresh_trigger >= 0 and self.full_refresh_count == self.full_refresh_trigger:
self._display.Clear(0x00)
self._display.display(buf)
elif self._is_waveshare2:
if self.full_refresh_trigger >= 0 and self.full_refresh_count == self.full_refresh_trigger:
self._display.Clear(BLACK)
self._display.displayPartial(buf)
self._display.sleep()
if self.full_refresh_trigger >= 0 and self.full_refresh_count == self.full_refresh_trigger:
self.full_refresh_count = 0
elif self.full_refresh_trigger >= 0:
self.full_refresh_count += 1
def _on_view_rendered(self, img): def _on_view_rendered(self, img):
# core.log("display::_on_view_rendered") # core.log("display::_on_view_rendered")

@ -5,7 +5,7 @@ from PIL import Image, ImageDraw
import core import core
import pwnagotchi import pwnagotchi
from pwnagotchi import voice from pwnagotchi.voice import Voice
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
import pwnagotchi.ui.faces as faces import pwnagotchi.ui.faces as faces
@ -31,6 +31,7 @@ def setup_display_specifics(config):
face_pos = (0, int(height / 4)) face_pos = (0, int(height / 4))
name_pos = (int(width / 2) - 15, int(height * .15)) name_pos = (int(width / 2) - 15, int(height * .15))
status_pos = (int(width / 2) - 15, int(height * .30)) status_pos = (int(width / 2) - 15, int(height * .30))
elif config['ui']['display']['type'] in ('papirus', 'papi'): elif config['ui']['display']['type'] in ('papirus', 'papi'):
fonts.setup(10, 8, 10, 23) fonts.setup(10, 8, 10, 23)
@ -39,7 +40,9 @@ def setup_display_specifics(config):
face_pos = (0, int(height / 4)) face_pos = (0, int(height / 4))
name_pos = (int(width / 2) - 15, int(height * .15)) name_pos = (int(width / 2) - 15, int(height * .15))
status_pos = (int(width / 2) - 15, int(height * .30)) status_pos = (int(width / 2) - 15, int(height * .30))
elif config['ui']['display']['type'] in ('ws', 'waveshare'):
elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1',
'ws_2', 'ws2', 'waveshare_2', 'waveshare2'):
fonts.setup(10, 9, 10, 35) fonts.setup(10, 9, 10, 35)
width = 250 width = 250
@ -57,6 +60,7 @@ class View(object):
self._config = config self._config = config
self._canvas = None self._canvas = None
self._lock = Lock() self._lock = Lock()
self._voice = Voice(lang=config['main']['lang'])
self._width, self._height, \ self._width, self._height, \
face_pos, name_pos, status_pos = setup_display_specifics(config) face_pos, name_pos, status_pos = setup_display_specifics(config)
@ -89,7 +93,7 @@ class View(object):
'name': Text(value='%s>' % 'pwnagotchi', position=name_pos, color=BLACK, font=fonts.Bold), 'name': Text(value='%s>' % 'pwnagotchi', position=name_pos, color=BLACK, font=fonts.Bold),
# 'face2': Bitmap( '/root/pwnagotchi/data/images/face_happy.bmp', (0, 20)), # 'face2': Bitmap( '/root/pwnagotchi/data/images/face_happy.bmp', (0, 20)),
'status': Text(value=voice.default(), position=status_pos, color=BLACK, font=fonts.Medium), 'status': Text(value=self._voice.default(), position=status_pos, color=BLACK, font=fonts.Medium),
'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK,
position=(0, self._height - int(self._height * .12) + 1), label_font=fonts.Bold, position=(0, self._height - int(self._height * .12) + 1), label_font=fonts.Bold,
@ -124,19 +128,19 @@ class View(object):
self._state.set(key, value) self._state.set(key, value)
def on_starting(self): def on_starting(self):
self.set('status', voice.on_starting()) self.set('status', self._voice.on_starting())
self.set('face', faces.AWAKE) self.set('face', faces.AWAKE)
def on_ai_ready(self): def on_ai_ready(self):
self.set('mode', '') self.set('mode', '')
self.set('face', faces.HAPPY) self.set('face', faces.HAPPY)
self.set('status', voice.on_ai_ready()) self.set('status', self._voice.on_ai_ready())
self.update() self.update()
def on_manual_mode(self, log): def on_manual_mode(self, log):
self.set('mode', 'MANU') self.set('mode', 'MANU')
self.set('face', faces.SAD if log.handshakes == 0 else faces.HAPPY) self.set('face', faces.SAD if log.handshakes == 0 else faces.HAPPY)
self.set('status', voice.on_log(log)) self.set('status', self._voice.on_log(log))
self.set('epoch', "%04d" % log.epochs) self.set('epoch', "%04d" % log.epochs)
self.set('uptime', log.duration) self.set('uptime', log.duration)
self.set('channel', '-') self.set('channel', '-')
@ -160,7 +164,7 @@ class View(object):
def on_normal(self): def on_normal(self):
self.set('face', faces.AWAKE) self.set('face', faces.AWAKE)
self.set('status', voice.on_normal()) self.set('status', self._voice.on_normal())
self.update() self.update()
def set_closest_peer(self, peer): def set_closest_peer(self, peer):
@ -188,17 +192,17 @@ class View(object):
def on_new_peer(self, peer): def on_new_peer(self, peer):
self.set('face', faces.FRIEND) self.set('face', faces.FRIEND)
self.set('status', voice.on_new_peer(peer)) self.set('status', self._voice.on_new_peer(peer))
self.update() self.update()
def on_lost_peer(self, peer): def on_lost_peer(self, peer):
self.set('face', faces.LONELY) self.set('face', faces.LONELY)
self.set('status', voice.on_lost_peer(peer)) self.set('status', self._voice.on_lost_peer(peer))
self.update() self.update()
def on_free_channel(self, channel): def on_free_channel(self, channel):
self.set('face', faces.SMART) self.set('face', faces.SMART)
self.set('status', voice.on_free_channel(channel)) self.set('status', self._voice.on_free_channel(channel))
self.update() self.update()
def wait(self, secs, sleeping=True): def wait(self, secs, sleeping=True):
@ -214,12 +218,12 @@ class View(object):
if sleeping: if sleeping:
if secs > 1: if secs > 1:
self.set('face', faces.SLEEP) self.set('face', faces.SLEEP)
self.set('status', voice.on_napping(secs)) self.set('status', self._voice.on_napping(secs))
else: else:
self.set('face', faces.SLEEP2) self.set('face', faces.SLEEP2)
self.set('status', voice.on_awakening()) self.set('status', self._voice.on_awakening())
else: else:
self.set('status', voice.on_waiting(secs)) self.set('status', self._voice.on_waiting(secs))
if step % 2 == 0: if step % 2 == 0:
self.set('face', faces.LOOK_R) self.set('face', faces.LOOK_R)
else: else:
@ -232,57 +236,57 @@ class View(object):
def on_bored(self): def on_bored(self):
self.set('face', faces.BORED) self.set('face', faces.BORED)
self.set('status', voice.on_bored()) self.set('status', self._voice.on_bored())
self.update() self.update()
def on_sad(self): def on_sad(self):
self.set('face', faces.SAD) self.set('face', faces.SAD)
self.set('status', voice.on_sad()) self.set('status', self._voice.on_sad())
self.update() self.update()
def on_motivated(self, reward): def on_motivated(self, reward):
self.set('face', faces.MOTIVATED) self.set('face', faces.MOTIVATED)
self.set('status', voice.on_motivated(reward)) self.set('status', self._voice.on_motivated(reward))
self.update() self.update()
def on_demotivated(self, reward): def on_demotivated(self, reward):
self.set('face', faces.DEMOTIVATED) self.set('face', faces.DEMOTIVATED)
self.set('status', voice.on_demotivated(reward)) self.set('status', self._voice.on_demotivated(reward))
self.update() self.update()
def on_excited(self): def on_excited(self):
self.set('face', faces.EXCITED) self.set('face', faces.EXCITED)
self.set('status', voice.on_excited()) self.set('status', self._voice.on_excited())
self.update() self.update()
def on_assoc(self, ap): def on_assoc(self, ap):
self.set('face', faces.INTENSE) self.set('face', faces.INTENSE)
self.set('status', voice.on_assoc(ap)) self.set('status', self._voice.on_assoc(ap))
self.update() self.update()
def on_deauth(self, sta): def on_deauth(self, sta):
self.set('face', faces.COOL) self.set('face', faces.COOL)
self.set('status', voice.on_deauth(sta)) self.set('status', self._voice.on_deauth(sta))
self.update() self.update()
def on_miss(self, who): def on_miss(self, who):
self.set('face', faces.SAD) self.set('face', faces.SAD)
self.set('status', voice.on_miss(who)) self.set('status', self._voice.on_miss(who))
self.update() self.update()
def on_lonely(self): def on_lonely(self):
self.set('face', faces.LONELY) self.set('face', faces.LONELY)
self.set('status', voice.on_lonely()) self.set('status', self._voice.on_lonely())
self.update() self.update()
def on_handshakes(self, new_shakes): def on_handshakes(self, new_shakes):
self.set('face', faces.HAPPY) self.set('face', faces.HAPPY)
self.set('status', voice.on_handshakes(new_shakes)) self.set('status', self._voice.on_handshakes(new_shakes))
self.update() self.update()
def on_rebooting(self): def on_rebooting(self):
self.set('face', faces.BROKEN) self.set('face', faces.BROKEN)
self.set('status', voice.on_rebooting()) self.set('status', self._voice.on_rebooting())
self.update() self.update()
def update(self): def update(self):

@ -0,0 +1,218 @@
# //*****************************************************************************
# * | File : epd2in13.py
# * | Author : Waveshare team
# * | Function : Electronic paper driver
# * | Info :
# *----------------
# * | This version: V3.1
# * | Date : 2019-03-20
# * | Info : python3 demo
# * fix: TurnOnDisplay()
# ******************************************************************************//
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and//or sell
# copies of the Software, and to permit persons to whom the Software is
# furished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# 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 SOFTWARE.
#
from . import epdconfig
from PIL import Image
import RPi.GPIO as GPIO
# import numpy as np
# Display resolution
EPD_WIDTH = 122
EPD_HEIGHT = 250
class EPD:
def __init__(self):
self.reset_pin = epdconfig.RST_PIN
self.dc_pin = epdconfig.DC_PIN
self.busy_pin = epdconfig.BUSY_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
lut_full_update = [
0x22, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x11,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00
]
lut_partial_update = [
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
# Hardware reset
def reset(self):
epdconfig.digital_write(self.reset_pin, GPIO.HIGH)
epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, GPIO.LOW) # module reset
epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, GPIO.HIGH)
epdconfig.delay_ms(200)
def send_command(self, command):
epdconfig.digital_write(self.dc_pin, GPIO.LOW)
epdconfig.spi_writebyte([command])
def send_data(self, data):
epdconfig.digital_write(self.dc_pin, GPIO.HIGH)
epdconfig.spi_writebyte([data])
def wait_until_idle(self):
# print("busy")
while(epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
epdconfig.delay_ms(100)
# print("free busy")
def TurnOnDisplay(self):
self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2
self.send_data(0xC4)
self.send_command(0x20) # MASTER_ACTIVATION
self.send_command(0xFF) # TERMINATE_FRAME_READ_WRITE
self.wait_until_idle()
def init(self, lut):
if (epdconfig.module_init() != 0):
return -1
# EPD hardware init start
self.reset()
self.send_command(0x01) # DRIVER_OUTPUT_CONTROL
self.send_data((EPD_HEIGHT - 1) & 0xFF)
self.send_data(((EPD_HEIGHT - 1) >> 8) & 0xFF)
self.send_data(0x00) # GD = 0 SM = 0 TB = 0
self.send_command(0x0C) # BOOSTER_SOFT_START_CONTROL
self.send_data(0xD7)
self.send_data(0xD6)
self.send_data(0x9D)
self.send_command(0x2C) # WRITE_VCOM_REGISTER
self.send_data(0xA8) # VCOM 7C
self.send_command(0x3A) # SET_DUMMY_LINE_PERIOD
self.send_data(0x1A) # 4 dummy lines per gate
self.send_command(0x3B) # SET_GATE_TIME
self.send_data(0x08) # 2us per line
self.send_command(0X3C) # BORDER_WAVEFORM_CONTROL
self.send_data(0x03)
self.send_command(0X11) # DATA_ENTRY_MODE_SETTING
self.send_data(0x03) # X increment; Y increment
# WRITE_LUT_REGISTER
self.send_command(0x32)
for count in range(30):
self.send_data(lut[count])
return 0
##
# @brief: specify the memory area for data R//W
##
def SetWindows(self, x_start, y_start, x_end, y_end):
self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
self.send_data((x_start >> 3) & 0xFF)
self.send_data((x_end >> 3) & 0xFF)
self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
self.send_data(y_start & 0xFF)
self.send_data((y_start >> 8) & 0xFF)
self.send_data(y_end & 0xFF)
self.send_data((y_end >> 8) & 0xFF)
##
# @brief: specify the start point for data R//W
##
def SetCursor(self, x, y):
self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
# x point must be the multiple of 8 or the last 3 bits will be ignored
self.send_data((x >> 3) & 0xFF)
self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
self.send_data(y & 0xFF)
self.send_data((y >> 8) & 0xFF)
self.wait_until_idle()
def getbuffer(self, image):
if self.width%8 == 0:
linewidth = self.width//8
else:
linewidth = self.width//8 + 1
buf = [0xFF] * (linewidth * self.height)
image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load()
if(imwidth == self.width and imheight == self.height):
# print("Vertical")
for y in range(imheight):
for x in range(imwidth):
if pixels[x, y] == 0:
# x = imwidth - x
buf[x // 8 + y * linewidth] &= ~(0x80 >> (x % 8))
elif(imwidth == self.height and imheight == self.width):
# print("Horizontal")
for y in range(imheight):
for x in range(imwidth):
newx = y
newy = self.height - x - 1
if pixels[x, y] == 0:
# newy = imwidth - newy - 1
buf[newx // 8 + newy*linewidth] &= ~(0x80 >> (y % 8))
return buf
def display(self, image):
if self.width%8 == 0:
linewidth = self.width//8
else:
linewidth = self.width//8 + 1
self.SetWindows(0, 0, EPD_WIDTH, EPD_HEIGHT);
for j in range(0, self.height):
self.SetCursor(0, j);
self.send_command(0x24);
for i in range(0, linewidth):
self.send_data(image[i + j * linewidth])
self.TurnOnDisplay()
def Clear(self, color):
if self.width%8 == 0:
linewidth = self.width//8
else:
linewidth = self.width//8 + 1
self.SetWindows(0, 0, EPD_WIDTH, EPD_HEIGHT);
for j in range(0, self.height):
self.SetCursor(0, j);
self.send_command(0x24);
for i in range(0, linewidth):
self.send_data(color)
self.TurnOnDisplay()
def sleep(self):
self.send_command(0x10) #enter deep sleep
# self.send_data(0x01)
epdconfig.delay_ms(100)
### END OF FILE ###

@ -0,0 +1,73 @@
# /*****************************************************************************
# * | File : EPD_1in54.py
# * | Author : Waveshare team
# * | Function : Hardware underlying interface
# * | Info :
# *----------------
# * | This version: V2.0
# * | Date : 2018-11-01
# * | Info :
# * 1.Remove:
# digital_write(self, pin, value)
# digital_read(self, pin)
# delay_ms(self, delaytime)
# set_lut(self, lut)
# self.lut = self.lut_full_update
# ******************************************************************************/
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# 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 SOFTWARE.
#
import spidev
import RPi.GPIO as GPIO
import time
# Pin definition
RST_PIN = 17
DC_PIN = 25
CS_PIN = 8
BUSY_PIN = 24
# SPI device, bus = 0, device = 0
SPI = spidev.SpiDev(0, 0)
def digital_write(pin, value):
GPIO.output(pin, value)
def digital_read(pin):
return GPIO.input(BUSY_PIN)
def delay_ms(delaytime):
time.sleep(delaytime / 1000.0)
def spi_writebyte(data):
SPI.writebytes(data)
def module_init():
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(RST_PIN, GPIO.OUT)
GPIO.setup(DC_PIN, GPIO.OUT)
GPIO.setup(CS_PIN, GPIO.OUT)
GPIO.setup(BUSY_PIN, GPIO.IN)
SPI.max_speed_hz = 2000000
SPI.mode = 0b00
return 0;
### END OF FILE ###

@ -1,147 +1,160 @@
import random import random
import gettext
import os
def default(): class Voice:
return 'ZzzzZZzzzzZzzz' def __init__(self, lang):
localedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locale')
translation = gettext.translation(
'voice', localedir,
languages=[lang],
fallback=True,
)
translation.install()
self._ = translation.gettext
def default(self):
return self._('ZzzzZZzzzzZzzz')
def on_starting(): def on_starting(self):
return random.choice([ \ return random.choice([ \
'Hi, I\'m Pwnagotchi!\nStarting ...', self._('Hi, I\'m Pwnagotchi!\nStarting ...'),
'New day, new hunt,\nnew pwns!', self._('New day, new hunt,\nnew pwns!'),
'Hack the Planet!']) self._('Hack the Planet!')])
def on_ai_ready(): def on_ai_ready(self):
return random.choice([ return random.choice([
'AI ready.', self._('AI ready.'),
'The neural network\nis ready.']) self._('The neural network\nis ready.')])
def on_normal(): def on_normal(self):
return random.choice([ \ return random.choice([ \
'', '',
'...']) '...'])
def on_free_channel(channel): def on_free_channel(channel):
return 'Hey, channel %d is\nfree! Your AP will\nsay thanks.' % channel return self._('Hey, channel {channel} is\nfree! Your AP will\nsay thanks.').format(channel=channel)
def on_bored(): def on_bored():
return random.choice([ \ return random.choice([ \
'I\'m bored ...', self._('I\'m bored ...'),
'Let\'s go for a walk!']) self._('Let\'s go for a walk!')])
def on_motivated(reward): def on_motivated(self, reward):
return 'This is the best\nday of my life!' return self._('This is the best\nday of my life!')
def on_demotivated(reward): def on_demotivated(self, reward):
return 'Shitty day :/' return self._('Shitty day :/')
def on_sad(): def on_sad(self):
return random.choice([ \ return random.choice([ \
'I\'m extremely bored ...', self._('I\'m extremely bored ...'),
'I\'m very sad ...', self._('I\'m very sad ...'),
'I\'m sad', self._('I\'m sad'),
'...']) '...'])
def on_excited(): def on_excited(self):
return random.choice([ \ return random.choice([ \
'I\'m living the life!', self._('I\'m living the life!'),
'I pwn therefore I am.', self._('I pwn therefore I am.'),
'So many networks!!!', self._('So many networks!!!'),
'I\'m having so much\nfun!', self._('I\'m having so much\nfun!'),
'My crime is that of\ncuriosity ...']) self._('My crime is that of\ncuriosity ...')])
def on_new_peer(peer): def on_new_peer(self, peer):
return random.choice([ \ return random.choice([ \
'Hello\n%s!\nNice to meet you.' % peer.name(), self._('Hello\n{name}!\nNice to meet you. {name}').format(name=peer.name()),
'Unit\n%s\nis nearby!' % peer.name()]) self._('Unit\n{name}\nis nearby! {name}').format(name=peer.name())])
def on_lost_peer(peer): def on_lost_peer(self, peer):
return random.choice([ \ return random.choice([ \
'Uhm ...\ngoodbye\n%s' % peer.name(), self._('Uhm ...\ngoodbye\n{name}').format(name=peer.name()),
'%s\nis gone ...' % peer.name()]) self._('{name}\nis gone ...').format(name=peer.name())])
def on_miss(who): def on_miss(self, who):
return random.choice([ \ return random.choice([ \
'Whoops ...\n%s\nis gone.' % who, self._('Whoops ...\n{name}\nis gone.').format(name=who),
'%s\nmissed!' % who, self._('{name}\nmissed!').format(name=who),
'Missed!']) self._('Missed!')])
def on_lonely(): def on_lonely(self):
return random.choice([ \ return random.choice([ \
'Nobody wants to\nplay with me ...', self._('Nobody wants to\nplay with me ...'),
'I feel so alone ...', self._('I feel so alone ...'),
'Where\'s everybody?!']) self._('Where\'s everybody?!')])
def on_napping(secs): def on_napping(self,secs):
return random.choice([ \ return random.choice([ \
'Napping for %ds ...' % secs, self._('Napping for {secs}s ...').format(secs=secs),
'Zzzzz', self._('Zzzzz'),
'ZzzZzzz (%ds)' % secs]) self._('ZzzZzzz ({secs}s)').format(secs=secs)])
def on_awakening(): def on_awakening(self):
return random.choice(['...', '!']) return random.choice(['...', '!'])
def on_waiting(secs): def on_waiting(self,secs):
return random.choice([ \ return random.choice([ \
'Waiting for %ds ...' % secs, self._('Waiting for {secs}s ...').format(secs=secs),
'...', '...',
'Looking around (%ds)' % secs]) self._('Looking around ({secs}s)').format(secs=secs)])
def on_assoc(ap): def on_assoc(self,ap):
ssid, bssid = ap['hostname'], ap['mac'] ssid, bssid = ap['hostname'], ap['mac']
what = ssid if ssid != '' and ssid != '<hidden>' else bssid what = ssid if ssid != '' and ssid != '<hidden>' else bssid
return random.choice([ \ return random.choice([ \
'Hey\n%s\nlet\'s be friends!' % what, self._('Hey\n{what}\nlet\'s be friends!').format(what=what),
'Associating to\n%s' % what, self._('Associating to\n{what}').format(what=what),
'Yo\n%s!' % what]) self._('Yo\n{what}!').format(what=what)])
def on_deauth(sta): def on_deauth(self,sta):
return random.choice([ \ return random.choice([ \
'Just decided that\n%s\nneeds no WiFi!' % sta['mac'], self._('Just decided that\n{mac}\nneeds no WiFi!').format(mac=sta['mac']),
'Deauthenticating\n%s' % sta['mac'], self._('Deauthenticating\n{mac}').format(mac=sta['mac']),
'Kickbanning\n%s!' % sta['mac']]) self._('Kickbanning\n{mac}!').format(mac=sta['mac'])])
def on_handshakes(new_shakes): def on_handshakes(self,new_shakes):
s = 's' if new_shakes > 1 else '' s = 's' if new_shakes > 1 else ''
return 'Cool, we got %d\nnew handshake%s!' % (new_shakes, s) return self._('Cool, we got {num}\nnew handshake{plural}!').format(num=new_shakes, plural=s)
def on_rebooting(): def on_rebooting(self):
return "Ops, something\nwent wrong ...\nRebooting ..." return self._("Ops, something\nwent wrong ...\nRebooting ...")
def on_log(log): def on_log(self,log):
status = 'Kicked %d stations\n' % log.deauthed status = self._('Kicked {num} stations\n').format(num=log.deauthed)
status += 'Made %d new friends\n' % log.associated status += self._('Made {num} new friends\n').format(num=log.associated)
status += 'Got %d handshakes\n' % log.handshakes status += self._('Got {num} handshakes\n').format(num=log.handshakes)
if log.peers == 1: if log.peers == 1:
status += 'Met 1 peer' status += self._('Met 1 peer')
elif log.peers > 0: elif log.peers > 0:
status += 'Met %d peers' % log.peers status += self._('Met {num} peers').format(num=log.peers)
return status return status
def on_log_tweet(log): def on_log_tweet(self,log):
return 'I\'ve been pwning for %s and kicked %d clients! I\'ve also met %d new friends and ate %d handshakes! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet' % ( \ return self._('I\'ve been pwning for {duration} and kicked {deauthed} clients! I\'ve also met {associated} new friends and ate {handshakes} handshakes! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet').format(
log.duration_human, duration=log.duration_human,
log.deauthed, deauthed=log.deauthed,
log.associated, associated=log.associated,
log.handshakes) handshakes=log.handshakes)