1
0
mirror of https://github.com/EliasKotlyar/Xiaomi-Dafang-Hacks.git synced 2025-09-02 20:52:41 +02:00

Merge beta into master (#1786)

This commit is contained in:
timstanley1985
2021-10-24 09:57:50 +01:00
committed by GitHub
parent d2fa6d7aac
commit f1cf7dad8b
21 changed files with 884 additions and 141 deletions

View File

@@ -1,18 +1,54 @@
#!/bin/sh
CURL="/system/sdcard/bin/curl --silent"
JQ="/system/sdcard/bin/jq"
what="$1"
shift
data="$@"
CURL="/system/sdcard/bin/curl"
if [ "$what" = "m" ]; then
shift
sendtext="${@//\"/\\\"}"
else
filename="$2"
datafile="$3"
fi
. /system/sdcard/config/matrix.conf
sendMessage() {
text="$(echo "${@}" | sed 's:\\n:\n:g')"
echo "Sending message: $text"
uploadData() {
mimetype="$1"
msgtype="$2"
$CURL -XPOST -d '{"msgtype":"m.text", "body":"'$text'"}' "https://$host:$port/_matrix/client/r0/rooms/$room_id/send/m.room.message?access_token=$access_token"
mxcuri="$($CURL -XPOST -H "Content-Type: $mimetype" --data-binary @"$datafile" "https://$host:$port/_matrix/media/r0/upload?filename=$filename&access_token=$access_token" | $JQ -cMr ".content_uri")"
if [ -n "$mxcuri" ] && [ "$mxcuri" != "null" ]; then
$CURL -XPOST -d '{"msgtype":"'"$msgtype"'", "body":"'"$filename"'", "url":"'"$mxcuri"'"}' "https://$host:$port/_matrix/client/r0/rooms/$room_id/send/m.room.message?access_token=$access_token"
fi
}
[ "$what" = "m" ] && sendMessage $data
[ -z "$what" ] && echo -e "$0 <m|f|p> <data>\n m: message\n f: file\n p: picture"
sendFile() {
echo "Sending file: $datafile"
uploadData "application/octet-stream" "m.file"
}
sendMessage() {
echo "Sending message: $sendtext"
$CURL -XPOST -d '{"msgtype":"m.notice", "body":"'"$sendtext"'"}' "https://$host:$port/_matrix/client/r0/rooms/$room_id/send/m.room.message?access_token=$access_token"
}
sendPicture() {
echo "Sending picture: $datafile"
filename="$filename.jpg"
uploadData "image/jpeg" "m.image"
}
sendVideo() {
echo "Sending video: $datafile"
filename="$filename.mp4"
uploadData "video/mp4" "m.video"
}
[ "$what" = "f" ] && sendFile
[ "$what" = "m" ] && sendMessage
[ "$what" = "p" ] && sendPicture
[ "$what" = "v" ] && sendVideo
[ -z "$what" ] && echo -e "$0 <m|f|p|v> <data|filename>\n m: message\n f: file\n p: picture\n v: video"

View File

@@ -18,45 +18,66 @@ sendMessage() {
}
sendFile() {
echo "Sending file: $1"
$CURL -s \
-X POST \
https://api.telegram.org/bot$apiToken/sendDocument \
-F chat_id="$userChatId" \
-F document=@"$1"
if [ -r $1 ]
then
echo "Sending file: $1"
$CURL -s \
-X POST \
https://api.telegram.org/bot$apiToken/sendDocument \
-F chat_id="$userChatId" \
-F document=@"$1"
else
echo "File not found: $1"
fi
}
sendPhoto() {
caption="$(hostname)-$(date +"%d%m%Y_%H%M%S")"
echo "Sending Photo: $1 $caption" >> /tmp/telegram.log
$CURL -s \
-X POST \
https://api.telegram.org/bot$apiToken/sendPhoto \
-F chat_id="$userChatId" \
-F photo="@${1}" \
-F caption="${caption}"
if [ -r $1 ]
then
caption="$(hostname)-$(date +"%d%m%Y_%H%M%S")"
echo "Sending Photo: $1 $caption" >> /tmp/telegram.log
$CURL -s \
-X POST \
https://api.telegram.org/bot$apiToken/sendPhoto \
-F chat_id="$userChatId" \
-F photo="@${1}" \
-F caption="${caption}"
else
echo "File not found: $1"
fi
}
sendVideo() {
caption="$(hostname)-$(date +"%d%m%Y_%H%M%S")"
echo "Sending Video: $1 $caption" >> /tmp/telegram.log
$CURL -s \
-X POST \
https://api.telegram.org/bot$apiToken/sendVideo \
-F chat_id="$userChatId" \
-F video="@${1}" \
-F caption="${caption}"
if [ -r $1 ]
then
bytes=$(busybox stat -c %s $1)
caption="$(hostname)-$(date +"%d%m%Y_%H%M%S")"
echo "Sending Video: $1 $caption (${bytes}bytes)" >> /tmp/telegram.log
$CURL -s \
-X POST \
https://api.telegram.org/bot$apiToken/sendVideo \
-F chat_id="$userChatId" \
-F video="@${1}" \
-F caption="${caption}"
else
echo "File not found: $1"
fi
}
sendAnimation() {
caption="$(hostname)-$(date +"%d%m%Y_%H%M%S")"
echo "Sending Animation: $1 $caption" >> /tmp/telegram.log
$CURL -s \
-X POST \
https://api.telegram.org/bot$apiToken/sendAnimation \
-F chat_id="$userChatId" \
-F animation="@${1}" \
-F caption="${caption}"
if [ -r $1 ]
then
caption="$(hostname)-$(date +"%d%m%Y_%H%M%S")"
echo "Sending Animation: $1 $caption" >> /tmp/telegram.log
$CURL -s \
-X POST \
https://api.telegram.org/bot$apiToken/sendAnimation \
-F chat_id="$userChatId" \
-F animation="@${1}" \
-F caption="${caption}"
else
echo "File not found: $1"
fi
}
[ "$what" == "m" ] && sendMessage $data

View File

@@ -24,6 +24,7 @@ send_email=false
send_telegram=false
telegram_alert_type=image
send_matrix=false
matrix_alert_type=image
night_mode_event_delay=30
# General
@@ -63,6 +64,7 @@ ftp_videos_dir="motion/videos"
# Configure Dropbox snapshots and videos
dropbox_snapshot=false
dropbox_video=false
dropbox_url="https://content.dropboxapi.com/2/files/upload"
dropbox_token="token"
dropbox_stills_dir="/motion/stills"
dropbox_videos_dir="/motion/videos"

View File

@@ -49,7 +49,7 @@ FRAMERATE_NUM=25
VIDEOFORMAT=2
# AudioFormat
# Can be: OPUS | MP3 | PCM | PCMU
# Can be: OPUS | MP3 | PCM | PCMU | OFF
AUDIOFORMAT=MP3
# Audio sampling rate
AUDIOINBR=16000

View File

@@ -40,24 +40,29 @@ send_snapshot() {
if [ "$telegram_alert_type" = "text" ] ; then
debug_msg "Send telegram text"
/system/sdcard/bin/telegram m "Motion detected"
elif [ "$telegram_alert_type" = "image" -o "$telegram_alert_type" = "video+image" ] ; then
elif [ "$telegram_alert_type" = "image" -o "$telegram_alert_type" = "video+image" ] ; then
debug_msg "Send telegram image"
/system/sdcard/bin/telegram p "$snapshot_tempfile"
fi
) &
fi
# Send a matrix message
# Send a matrix message or image
if [ "$send_matrix" = true ]; then
(
include /system/sdcard/config/matrix.conf
debug_msg "Send matrix message"
/system/sdcard/bin/matrix m "Motion detected"
if [ "$matrix_alert_type" = "text" ] ; then
debug_msg "Send matrix text"
/system/sdcard/bin/matrix m "Motion detected"
elif [ "$matrix_alert_type" = "image" -o "$matrix_alert_type" = "video+image" ] ; then
debug_msg "Send matrix image"
/system/sdcard/bin/matrix p "$filename" "$snapshot_tempfile"
fi
) &
fi
#save FTP snapshot
# Save FTP snapshot
if [ "$ftp_snapshot" = true ]; then
(
ftpput_cmd="/system/sdcard/bin/busybox ftpput"
@@ -78,12 +83,11 @@ send_snapshot() {
) &
fi
#save Dropbox snapshot
# Save Dropbox snapshot
if [ "$dropbox_snapshot" = true ]; then
(
debug_msg "Sending Dropbox snapshot to $dropbox_stills_dir/$filename.jpg"
/system/sdcard/bin/curl -X POST https://content.dropboxapi.com/2/files/upload \
/system/sdcard/bin/curl -X POST "$dropbox_url" \
--header "Authorization: Bearer $dropbox_token" \
--header "Dropbox-API-Arg: {\"path\": \"$dropbox_stills_dir/$filename.jpg\"}" \
--header "Content-Type: application/octet-stream" \
@@ -91,17 +95,15 @@ send_snapshot() {
) &
fi
# Save a snapshot
if [ "$save_snapshot" = true ] ; then
(
debug_msg "Save snapshot to $save_snapshot_dir/$groupname/$filename.jpg"
# Save a snapshot
if [ "$save_snapshot" = true ] ; then
(
debug_msg "Save snapshot to $save_snapshot_dir/$groupname/$filename.jpg"
if [ ! -d "$save_snapshot_dir/$groupname" ]; then
mkdir -p "$save_snapshot_dir/$groupname"
chmod "$save_dirs_attr" "$save_snapshot_dir/$groupname"
fi
if [ ! -d "$save_snapshot_dir/$groupname" ]; then
mkdir -p "$save_snapshot_dir/$groupname"
chmod "$save_dirs_attr" "$save_snapshot_dir/$groupname"
fi
# Limit the number of snapshots
if [ "$(ls "$save_snapshot_dir" | wc -l)" -ge "$max_snapshot_days" ]; then
@@ -113,7 +115,7 @@ send_snapshot() {
) &
fi
## Save SMB snapshot
# Save SMB snapshot
if [ "$smb_snapshot" = true ]; then
(
smbclient_cmd="/system/bin/smbclient $smb_share"
@@ -131,6 +133,7 @@ send_snapshot() {
$smbclient_cmd -D "$smb_stills_path" -c "lcd /tmp; mkdir $groupname; cd $groupname; put $snapshot_tempfilename; rename $snapshot_tempfilename $filename.jpg"
) &
fi
# Wait for all background jobs to finish before existing
debug_msg "Waiting for background jobs to end in send_snapshot function:"
for jobpid in $(jobs -p); do
@@ -141,11 +144,11 @@ send_snapshot() {
}
record_video () {
# We only want one video stream at a time. Try to grab an
# exclusive flock on file descriptor 5. Bail out if another
# process already has it. Touch the flock to update it's mod
# time as a signal to the background process to keep recording
# when motion is repeatedly observed.
# We only want one video stream at a time. Try to grab an exclusive
# flock on file descriptor 5. Bail out if another process already has
# it. Touch the flock to update its mod time as a signal to the
# background process to keep recording when motion is repeatedly
# observed.
touch /run/recording_video.flock
exec 5<> /run/recording_video.flock
if /system/sdcard/bin/busybox flock -n -x 5; then
@@ -159,7 +162,6 @@ record_video () {
else
/system/sdcard/bin/openRTSP -4 -w "$video_rtsp_w" -h "$video_rtsp_h" -f "$video_rtsp_f" -d "$video_duration" -b "$output_buffer_size" rtsp://$USERNAME:$USERPASSWORD@127.0.0.1:$PORT/unicast > "$video_tempfile"
fi
else
# Use avconv to stitch multiple JPEGs into 1fps video.
# I couldn't get it working another way.
@@ -205,18 +207,23 @@ filename=$(date "$filename_pattern")
/system/sdcard/bin/getimage > "$snapshot_tempfile"
debug_msg "Got snapshot_tempfile=$snapshot_tempfile"
#Next send picture alerts in the background
# Next send picture alerts in the background
send_snapshot &
# Then, record video (if necessary)
if [ "$save_video" = true -o "$smb_video" = true -o "$telegram_alert_type" = "video+image" -o "$telegram_alert_type" = "video" -o "$publish_mqtt_video" = true ] ; then
if [ "$save_video" = true ] ||
[ "$smb_video" = true ] ||
[ "$dropbox_video" = true ] ||
([ "$send_telegram" = true ] && ([ "$telegram_alert_type" = video+image ] || [ "$telegram_alert_type" = video ])) ||
([ "$send_matrix" = true ] && ([ "$matrix_alert_type" = video+image ] || [ "$matrix_alert_type" = video ])) ||
[ "$publish_mqtt_video" = true ]
then
record_video
fi
# Next, start background tasks for all configured video notifications
# Save the video
if [ "$save_video" = true ] ; then
(
@@ -252,11 +259,11 @@ if [ "$ftp_video" = true ]; then
fi
ftpput_cmd="$ftpput_cmd $ftp_host"
# We only want one video stream at a time. Try to grab an
# exclusive flock on file descriptor 5. Bail out if another
# process already has it. Touch the flock to update it's mod
# time as a signal to the background process to keep recording
# when motion is repeatedly observed.
# We only want one video stream at a time. Try to grab an exclusive
# flock on file descriptor 5. Bail out if another process already has
# it. Touch the flock to update it's mod time as a signal to the
# background process to keep recording when motion is repeatedly
# observed.
touch /run/ftp_motion_video_stream.flock
exec 5<> /run/ftp_motion_video_stream.flock
if /system/sdcard/bin/busybox flock -n -x 5; then
@@ -265,10 +272,10 @@ if [ "$ftp_video" = true ]; then
# XXX Uses avconv to stitch multiple JPEGs into 1fps video.
# I couldn't get it working another way. /dev/videoX inputs
# fail. Localhost rtsp takes very long (10+ seconds) to
# start streaming and gets flaky when when memory or cpu
# are pegged. This is a clugy method, but works well even
# at high res, fps, cpu, and memory load!
# fail. Localhost rtsp takes very long (10+ seconds) to start
# streaming and gets flaky when when memory or cpu are pegged.
# This is a clugy method, but works well even at high res,
# fps, cpu, and memory load!
( while [ "$(/system/sdcard/bin/busybox date "+%s")" -le "$(/system/sdcard/bin/busybox expr "$(/system/sdcard/bin/busybox stat -c "%X" /run/ftp_motion_video_stream.flock)" + "$video_duration")" ]; do
/system/sdcard/bin/getimage
sleep 1
@@ -285,21 +292,17 @@ if [ "$ftp_video" = true ]; then
) &
fi
#save Dropbox video
if [ "$dropbox_video" = true ]; then
(
debug_msg "Saving Dropbox snapshot to $dropbox_videos_dir/$filename.mp4"
/system/sdcard/bin/curl -X POST https://content.dropboxapi.com/2/files/upload \
--header "Authorization: Bearer $dropbox_token" \
--header "Dropbox-API-Arg: {\"path\": \"$dropbox_videos_dir/$filename.mp4\"}" \
--header "Content-Type: application/octet-stream" \
--data-binary @"$video_tempfile"
) &
fi
# Save Dropbox video
if [ "$dropbox_video" = true ]; then
(
debug_msg "Saving Dropbox snapshot to $dropbox_videos_dir/$filename.mp4"
/system/sdcard/bin/curl -X POST "$dropbox_url" \
--header "Authorization: Bearer $dropbox_token" \
--header "Dropbox-API-Arg: {\"path\": \"$dropbox_videos_dir/$filename.mp4\"}" \
--header "Content-Type: application/octet-stream" \
--data-binary @"$video_tempfile"
) &
fi
# SMB snapshot and video
if [ "$smb_video" = true ]; then
@@ -342,14 +345,20 @@ if [ "$send_telegram" = true ]; then
(
include /system/sdcard/config/telegram.conf
if [ "$telegram_alert_type" = "video" -o "$telegram_alert_type" = "video+image" ] ; then
debug_msg "Send telegram video"
if [ "$telegram_alert_type" = "video" -o "$telegram_alert_type" = "video+image" ] ; then
if [ "$video_use_rtsp" = true ]; then
#Convert file to mp4 and remove audio stream so video plays in telegram app
/system/sdcard/bin/avconv -i "$video_tempfile" -c:v copy -an "$video_tempfile"-telegram.mp4
/system/sdcard/bin/telegram v "$video_tempfile"-telegram.mp4
rm "$video_tempfile"-telegram.mp4
if [ "$AUDIOFORMAT" = "PCMU" ] || [ "$AUDIOFORMAT" = "OFF" ] ; then
# Convert file to mp4 and remove audio stream so video plays in telegram app
debug_msg "Send telegram video"
/system/sdcard/bin/avconv -i "$video_tempfile" -c:v copy -an "$video_tempfile"-telegram.mp4
/system/sdcard/bin/telegram v "$video_tempfile"-telegram.mp4
rm "$video_tempfile"-telegram.mp4
else
# avconv can't strip audio it doesn't understand
debug_msg "Send telegram video (only viable for external playback)"
/system/sdcard/bin/telegram v "$video_tempfile"
fi
else
/system/sdcard/bin/avconv -i "$video_tempfile" "$video_tempfile-lo.mp4"
/system/sdcard/bin/telegram v "$video_tempfile-lo.mp4"
rm "$video_tempfile-lo.mp4"
@@ -358,15 +367,33 @@ if [ "$send_telegram" = true ]; then
) &
fi
# Send a matrix video
if [ "$send_matrix" = true ]; then
(
include /system/sdcard/config/matrix.conf
if [ "$matrix_alert_type" = "video" -o "$matrix_alert_type" = "video+image" ] ; then
debug_msg "Send matrix video"
if [ "$video_use_rtsp" = true ]; then
/system/sdcard/bin/matrix v "$filename" "$video_tempfile"
else
/system/sdcard/bin/avconv -i "$video_tempfile" "$video_tempfile-lo.mp4"
/system/sdcard/bin/matrix v "$filename" "$video_tempfile-lo.mp4"
rm -f "$video_tempfile-lo.mp4"
fi
fi
) &
fi
# Run any user scripts.
for i in /system/sdcard/config/userscripts/motiondetection/*; do
if [ -x "$i" ]; then
debug_msg "Running: $i on $snapshot_tempfile"
$i on "$snapshot_tempfile" "$video_tempfile" &
fi
if [ -x "$i" ]; then
debug_msg "Running: $i on $snapshot_tempfile"
$i on "$snapshot_tempfile" "$video_tempfile" &
fi
done
# Wait for all background jobs to finish before existing and deleting tempfile
# Wait for all background jobs to finish before exiting and deleting tempfile
debug_msg "Waiting for background jobs to end:"
for jobpid in $(jobs -p); do
wait "$jobpid"

View File

@@ -11,6 +11,10 @@ JQ="/system/sdcard/bin/jq"
[ -z $apiToken ] && echo "api token not configured yet" && exit 1
[ -z $userChatId ] && echo "chat id not configured yet" && exit 1
status() {
$TELEGRAM m "Motion detection `motion_detection status`\nNight mode `night_mode status`\nAlert type `get_config /system/sdcard/config/motion.conf telegram_alert_type`"
}
sendShot() {
/system/sdcard/bin/getimage > "/tmp/telegram_image.jpg" &&\
$TELEGRAM p "/tmp/telegram_image.jpg"
@@ -52,10 +56,16 @@ videoAlerts() {
$TELEGRAM m "Video alerts on motion detection enabled"
}
imageThenVideoAlerts() {
rewrite_config /system/sdcard/config/motion.conf telegram_alert_type "video+image"
$TELEGRAM m "Image then video alerts on motion detection enabled"
}
respond() {
cmd=$1
[ $chatId -lt 0 ] && cmd=${1%%@*}
case $cmd in
/status) status;;
/mem) sendMem;;
/shot) sendShot;;
/on) detectionOn;;
@@ -65,7 +75,8 @@ respond() {
/textalerts) textAlerts;;
/imagealerts) imageAlerts;;
/videoalerts) videoAlerts;;
/help | /start) $TELEGRAM m "######### Bot commands #########\n# /mem - show memory information\n# /shot - take a snapshot\n# /on - motion detection on\n# /off - motion detection off\n# /nighton - night mode on\n# /nightoff - night mode off\n# /textalerts - Text alerts on motion detection\n# /imagealerts - Image alerts on motion detection\n# /videoalerts - Video alerts on motion detection";;
/dualalerts) imageThenVideoAlerts;;
/help | /start) $TELEGRAM m "######### Bot commands #########\n# /mem - show memory information\n# /status - show current camera status\n# /shot - take a snapshot\n# /on - motion detection on\n# /off - motion detection off\n# /nighton - night mode on\n# /nightoff - night mode off\n# /textalerts - Text alerts on motion detection\n# /imagealerts - Image alerts on motion detection\n# /videoalerts - Video alerts on motion detection\n# /dualalerts - Image snapshot then video alerts on motion detection";;
/*) $TELEGRAM m "I can't respond to '$cmd' command"
esac
}
@@ -109,7 +120,8 @@ main() {
if [ "$chatId" != "$userChatId" ]; then
username=$(echo "$json" | $JQ -r ".result[0].$messageAttr.from.username // \"\"")
firstName=$(echo "$json" | $JQ -r ".result[0].$messageAttr.from.first_name // \"\"")
$TELEGRAM m "Received message from unauthorized chat id: $chatId\nUser: $username($firstName)\nMessage: $cmd"
# Uncomment to get notified of attempted chat spam
# $TELEGRAM m "Received message from unauthorized chat id: $chatId\nUser: $username($firstName)\nMessage: $cmd"
else
respond $cmd
fi;

View File

@@ -0,0 +1,14 @@
#!/bin/sh
echo "Content-type: text/html"
echo "Pragma: no-cache"
echo "Cache-Control: max-age=0, no-store, no-cache"
echo ""
if [ "${REQUEST_METHOD}" = "POST" ]
then
in_raw=`dd bs=1 count=${CONTENT_LENGTH} 1>/tmp/playback.wav`
sed -i -e '1,/Content-Type:/d' /tmp/playback.wav
echo " CONTENT LENGTH ${CONTENT_LENGTH}"
/system/sdcard/bin/audioplay /tmp/playback.wav 60
fi

View File

@@ -41,7 +41,7 @@ if [ -n "$F_cmd" ]; then
do
if [[ -f $file ]]; then
file_size=$(ls -lh $file | awk '{print $5}')
file_url=$(ls -lh $file | awk '{print $9}' | sed 's/\/system\/sdcard\/DCIM/\/viewer/')
file_url=$(ls -lh $file | awk '{print $9}' | sed 's/\/system\/sdcard\/DCIM/viewer/')
file_date=$(ls -lh $file | awk '{print $6 "-" $7 "-" $8}')
file_name=$(ls -lh $file | awk '{print $9}' | awk -F / '{print $(NF)}')
echo "${file_name}#:#${file_size}#:#${file_date}#:#${file_url}"
@@ -51,12 +51,12 @@ if [ -n "$F_cmd" ]; then
;;
del_config)
F_file=$(echo ${F_file} | sed -e 's/%2F/\//g' | sed -e 's/viewer/system\/sdcard\/DCIM/')
F_file=$(echo ${F_file} | sed -e 's/%2F/\//g' | sed -e 's/viewer/\/system\/sdcard\/DCIM/')
echo "Remove ${F_file}"
rm $F_file
;;
restore_config)
F_file=$(echo ${F_file} | sed -e 's/%2F/\//g' | sed -e 's/viewer/system\/sdcard\/DCIM/')
F_file=$(echo ${F_file} | sed -e 's/%2F/\//g' | sed -e 's/viewer/\/system\/sdcard\/DCIM/')
tar -xf $F_file -C /system/sdcard/config/
echo "Restore done"
/sbin/reboot

View File

@@ -40,6 +40,7 @@ if [ -n "$F_cmd" ]; then
echo "dropboxVideosDir#:#${dropbox_videos_dir}"
echo "dropboxSnapshot#:#${dropbox_snapshot}"
echo "dropboxVideo#:#${dropbox_video}"
echo "dropboxUrl#:#${dropbox_url}"
echo "dropboxToken#:#${dropbox_token}"
echo "smbSnapshot#:#${smb_snapshot}"
echo "smbVideo#:#${smb_video}"
@@ -56,6 +57,7 @@ if [ -n "$F_cmd" ]; then
echo "sendTelegram#:#${send_telegram}"
echo "telegramAlertType#:#${telegram_alert_type}"
echo "sendMatrix#:#${send_matrix}"
echo "matrixAlertType#:#${matrix_alert_type}"
echo "nightModeEventDelay#:#${night_mode_event_delay}"
;;
@@ -197,6 +199,11 @@ if [ -n "$F_cmd" ]; then
rewrite_config /system/sdcard/config/motion.conf dropbox_video $F_dropboxVideo
echo "Save video to dropbox set to $F_dropboxVideo<br/>"
fi
if [ -n "${F_dropboxUrl+x}" ]; then
F_dropboxUrl=$(printf '%b' "${F_dropboxUrl//%/\\x}" | sed 's/\//\\\//g')
rewrite_config /system/sdcard/config/motion.conf dropbox_url "\"$F_dropboxUrl\""
echo "dropbox url set<br/>"
fi
if [ -n "${F_dropboxToken+x}" ]; then
F_dropboxToken=$(printf '%b' "${F_dropboxToken//%/\\x}" | sed 's/\//\\\//g')
rewrite_config /system/sdcard/config/motion.conf dropbox_token "\"$F_dropboxToken\""
@@ -252,6 +259,11 @@ if [ -n "$F_cmd" ]; then
rewrite_config /system/sdcard/config/motion.conf send_matrix $F_sendMatrix
echo "Send Matrix on motion set to $F_sendMatrix<br/>"
fi
if [ -n "${F_matrixAlertType+x}" ]; then
F_matrixAlertType=$(printf '%b' "${F_matrixAlertType//%/\\x}")
rewrite_config /system/sdcard/config/motion.conf matrix_alert_type $F_matrixAlertType
echo "Matrix alert type set to $F_matrixAlertType<br/>"
fi
if [ -n "${F_nightModeEventDelay+x}" ]; then
F_nightModeEventDelay=$(printf '%b' "${F_nightModeEventDelay//%/\\x}")
rewrite_config /system/sdcard/config/motion.conf night_mode_event_delay $F_nightModeEventDelay

View File

@@ -31,7 +31,7 @@ if [ -n "$F_cmd" ]; then
do
if [[ -f $file ]]; then
file_size=$(ls -lh $file | awk '{print $5}')
file_url=$(ls -lh $file | awk '{print $9}' | sed 's/\/system\/sdcard\/DCIM/\/viewer/')
file_url=$(ls -lh $file | awk '{print $9}' | sed 's/\/system\/sdcard\/DCIM/viewer/')
file_date=$(ls -lh $file | awk '{print $6 "-" $7 "-" $8}')
file_name=$(ls -lh $file | awk '{print $9}' | awk -F / '{print $(NF)}')
echo "${file_name}#:#${file_size}#:#${file_date}#:#${file_url}"
@@ -45,7 +45,7 @@ if [ -n "$F_cmd" ]; then
echo "sdcardUsedPercent#:#$(df -h /system/sdcard | awk 'NR==2{print$5}')"
;;
del_file)
F_file=$(echo ${F_file} | sed -e 's/%2F/\//g' | sed -e 's/viewer/system\/sdcard\/DCIM/')
F_file=$(echo ${F_file} | sed -e 's/%2F/\//g' | sed -e 's/viewer/\/system\/sdcard\/DCIM/')
echo "Remove ${F_file}"
rm $F_file
;;
@@ -54,4 +54,4 @@ if [ -n "$F_cmd" ]; then
;;
esac
fi
fi

View File

@@ -17,12 +17,12 @@
<script type="text/javascript" src="lib/datatable/datatables.min.js"></script>
<!-- smoothie-->
<script type="text/javascript" src="lib/smoothie/smoothie.js"></script>
<script src="/lib/d3/d3.min.js"></script>
<script src="/lib/event-drops/index.js"></script>
<script src="lib/d3/d3.min.js"></script>
<script src="lib/event-drops/index.js"></script>
<!-- Dafang hack CSS-->
<link rel="stylesheet" href="css/w3-toggle.css" type="text/css" />
<link rel="stylesheet" href="css/w3-accordion.css" type="text/css" />
<link href="/lib/event-drops/style.css" rel="stylesheet" />
<link href="lib/event-drops/style.css" rel="stylesheet" />
<script type="text/javascript">
//Check if theme configured
var css = localStorage.getItem('theme')

View File

@@ -0,0 +1,85 @@
var gumStream; //stream from getUserMedia()
var rec; //Recorder.js object
var input; //MediaStreamAudioSourceNode we'll be recording
// shim for AudioContext when it's not avb.
var AudioContext = window.AudioContext || window.webkitAudioContext;
var audioContext //audio context to help us record
function startRecording() {
console.log("Recording started");
/*
Simple constraints object, for more advanced audio features see
https://addpipe.com/blog/audio-constraints-getusermedia/
*/
var constraints = {
audio: true,
video: false
}
/*
We're using the standard promise based getUserMedia()
https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
*/
navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
console.log("getUserMedia() success, stream created, initializing Recorder.js ...");
/*
create an audio context after getUserMedia is called
sampleRate might change after getUserMedia is called, like it does on macOS when recording through AirPods
the sampleRate defaults to the one set in your OS for your playback device
*/
audioContext = new AudioContext({
sampleRate: 8000
});
/* assign to gumStream for later use */
gumStream = stream;
/* use the stream */
input = audioContext.createMediaStreamSource(stream);
/*
Create the Recorder object and configure to record mono sound (1 channel)
Recording 2 channels will double the file size
*/
rec = new Recorder(input, {
numChannels: 1
})
//start the recording process
rec.record()
});
}
function stopRecording() {
//tell the recorder to stop the recording
// The Dafang fails to play the last two seconds because some bug on audioplay...
setTimeout(() => {
rec.stop();
//stop microphone access
gumStream.getAudioTracks()[0].stop();
rec.exportWAV(sendAudio);
function sendAudio(blob) {
var xhr = new XMLHttpRequest();
xhr.onload = function (e) {
if (this.readyState === 4) {
console.log("Server returned: ", e.target.responseText);
}
};
var fd = new FormData();
fd.append("audio_data", blob, 'recording.wav');
xhr.open("POST", "cgi-bin/audio_upload.cgi", true);
xhr.send(fd);
}
console.log("Audio sent");
}, 2000);
}

View File

@@ -0,0 +1,357 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Recorder = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
module.exports = require("./recorder").Recorder;
},{"./recorder":2}],2:[function(require,module,exports){
'use strict';
var _createClass = (function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);
}
}return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;
};
})();
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Recorder = undefined;
var _inlineWorker = require('inline-worker');
var _inlineWorker2 = _interopRequireDefault(_inlineWorker);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Recorder = exports.Recorder = (function () {
function Recorder(source, cfg) {
var _this = this;
_classCallCheck(this, Recorder);
this.config = {
bufferLen: 4096,
numChannels: 2,
mimeType: 'audio/wav'
};
this.recording = false;
this.callbacks = {
getBuffer: [],
exportWAV: []
};
Object.assign(this.config, cfg);
this.context = source.context;
this.node = (this.context.createScriptProcessor || this.context.createJavaScriptNode).call(this.context, this.config.bufferLen, this.config.numChannels, this.config.numChannels);
this.node.onaudioprocess = function (e) {
if (!_this.recording) return;
var buffer = [];
for (var channel = 0; channel < _this.config.numChannels; channel++) {
buffer.push(e.inputBuffer.getChannelData(channel));
}
_this.worker.postMessage({
command: 'record',
buffer: buffer
});
};
source.connect(this.node);
this.node.connect(this.context.destination); //this should not be necessary
var self = {};
this.worker = new _inlineWorker2.default(function () {
var recLength = 0,
recBuffers = [],
sampleRate = undefined,
numChannels = undefined;
self.onmessage = function (e) {
switch (e.data.command) {
case 'init':
init(e.data.config);
break;
case 'record':
record(e.data.buffer);
break;
case 'exportWAV':
exportWAV(e.data.type);
break;
case 'getBuffer':
getBuffer();
break;
case 'clear':
clear();
break;
}
};
function init(config) {
sampleRate = config.sampleRate;
numChannels = config.numChannels;
initBuffers();
}
function record(inputBuffer) {
for (var channel = 0; channel < numChannels; channel++) {
recBuffers[channel].push(inputBuffer[channel]);
}
recLength += inputBuffer[0].length;
}
function exportWAV(type) {
var buffers = [];
for (var channel = 0; channel < numChannels; channel++) {
buffers.push(mergeBuffers(recBuffers[channel], recLength));
}
var interleaved = undefined;
if (numChannels === 2) {
interleaved = interleave(buffers[0], buffers[1]);
} else {
interleaved = buffers[0];
}
var dataview = encodeWAV(interleaved);
var audioBlob = new Blob([dataview], { type: type });
self.postMessage({ command: 'exportWAV', data: audioBlob });
}
function getBuffer() {
var buffers = [];
for (var channel = 0; channel < numChannels; channel++) {
buffers.push(mergeBuffers(recBuffers[channel], recLength));
}
self.postMessage({ command: 'getBuffer', data: buffers });
}
function clear() {
recLength = 0;
recBuffers = [];
initBuffers();
}
function initBuffers() {
for (var channel = 0; channel < numChannels; channel++) {
recBuffers[channel] = [];
}
}
function mergeBuffers(recBuffers, recLength) {
var result = new Float32Array(recLength);
var offset = 0;
for (var i = 0; i < recBuffers.length; i++) {
result.set(recBuffers[i], offset);
offset += recBuffers[i].length;
}
return result;
}
function interleave(inputL, inputR) {
var length = inputL.length + inputR.length;
var result = new Float32Array(length);
var index = 0,
inputIndex = 0;
while (index < length) {
result[index++] = inputL[inputIndex];
result[index++] = inputR[inputIndex];
inputIndex++;
}
return result;
}
function floatTo16BitPCM(output, offset, input) {
for (var i = 0; i < input.length; i++, offset += 2) {
var s = Math.max(-1, Math.min(1, input[i]));
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
}
}
function writeString(view, offset, string) {
for (var i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
}
function encodeWAV(samples) {
var buffer = new ArrayBuffer(44 + samples.length * 2);
var view = new DataView(buffer);
/* RIFF identifier */
writeString(view, 0, 'RIFF');
/* RIFF chunk length */
view.setUint32(4, 36 + samples.length * 2, true);
/* RIFF type */
writeString(view, 8, 'WAVE');
/* format chunk identifier */
writeString(view, 12, 'fmt ');
/* format chunk length */
view.setUint32(16, 16, true);
/* sample format (raw) */
view.setUint16(20, 1, true);
/* channel count */
view.setUint16(22, numChannels, true);
/* sample rate */
view.setUint32(24, sampleRate, true);
/* byte rate (sample rate * block align) */
view.setUint32(28, sampleRate * 4, true);
/* block align (channel count * bytes per sample) */
view.setUint16(32, numChannels * 2, true);
/* bits per sample */
view.setUint16(34, 16, true);
/* data chunk identifier */
writeString(view, 36, 'data');
/* data chunk length */
view.setUint32(40, samples.length * 2, true);
floatTo16BitPCM(view, 44, samples);
return view;
}
}, self);
this.worker.postMessage({
command: 'init',
config: {
sampleRate: this.context.sampleRate,
numChannels: this.config.numChannels
}
});
this.worker.onmessage = function (e) {
var cb = _this.callbacks[e.data.command].pop();
if (typeof cb == 'function') {
cb(e.data.data);
}
};
}
_createClass(Recorder, [{
key: 'record',
value: function record() {
this.recording = true;
}
}, {
key: 'stop',
value: function stop() {
this.recording = false;
}
}, {
key: 'clear',
value: function clear() {
this.worker.postMessage({ command: 'clear' });
}
}, {
key: 'getBuffer',
value: function getBuffer(cb) {
cb = cb || this.config.callback;
if (!cb) throw new Error('Callback not set');
this.callbacks.getBuffer.push(cb);
this.worker.postMessage({ command: 'getBuffer' });
}
}, {
key: 'exportWAV',
value: function exportWAV(cb, mimeType) {
mimeType = mimeType || this.config.mimeType;
cb = cb || this.config.callback;
if (!cb) throw new Error('Callback not set');
this.callbacks.exportWAV.push(cb);
this.worker.postMessage({
command: 'exportWAV',
type: mimeType
});
}
}], [{
key: 'forceDownload',
value: function forceDownload(blob, filename) {
var url = (window.URL || window.webkitURL).createObjectURL(blob);
var link = window.document.createElement('a');
link.href = url;
link.download = filename || 'output.wav';
var click = document.createEvent("Event");
click.initEvent("click", true, true);
link.dispatchEvent(click);
}
}]);
return Recorder;
})();
exports.default = Recorder;
},{"inline-worker":3}],3:[function(require,module,exports){
"use strict";
module.exports = require("./inline-worker");
},{"./inline-worker":4}],4:[function(require,module,exports){
(function (global){
"use strict";
var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
var WORKER_ENABLED = !!(global === global.window && global.URL && global.Blob && global.Worker);
var InlineWorker = (function () {
function InlineWorker(func, self) {
var _this = this;
_classCallCheck(this, InlineWorker);
if (WORKER_ENABLED) {
var functionBody = func.toString().trim().match(/^function\s*\w*\s*\([\w\s,]*\)\s*{([\w\W]*?)}$/)[1];
var url = global.URL.createObjectURL(new global.Blob([functionBody], { type: "text/javascript" }));
return new global.Worker(url);
}
this.self = self;
this.self.postMessage = function (data) {
setTimeout(function () {
_this.onmessage({ data: data });
}, 0);
};
setTimeout(function () {
func.call(self);
}, 0);
}
_createClass(InlineWorker, {
postMessage: {
value: function postMessage(data) {
var _this = this;
setTimeout(function () {
_this.self.onmessage({ data: data });
}, 0);
}
}
});
return InlineWorker;
})();
module.exports = InlineWorker;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}]},{},[1])(1)
});

View File

@@ -51,7 +51,7 @@ function accordion() {
function checkSDCard() {
$.get("/cgi-bin/ui_sdcard.cgi", {cmd: "check_sdcard"}, function (result) {
$.get("cgi-bin/ui_sdcard.cgi", {cmd: "check_sdcard"}, function (result) {
if ( result == "nok") {
$('#notifAlarm').attr('style','color:red');
$('#notifContent').html("<p></p>Your sdcard is mounted read-only. Settings can't be saved. \

View File

@@ -4,24 +4,24 @@ var stateSideBar = false;
//Function for flip image
function flip() {
$.get("/cgi-bin/ui_live.cgi", {cmd: "flip"});
$.get("cgi-bin/ui_live.cgi", {cmd: "flip"});
}
//Functions for live stream with images
function refreshLiveImage() {
var ts = new Date().getTime();
$("#liveview").attr("src", "/cgi-bin/currentpic.cgi?" + ts);
$("#liveview").attr("src", "cgi-bin/currentpic.cgi?" + ts);
}
//Function to download a screenshot
function downloadScreenshot() {
var ts = new Date().getTime();
window.location.href = "/cgi-bin/downloadpic.cgi?" + ts;
window.location.href = "cgi-bin/downloadpic.cgi?" + ts;
}
//Function to refresh side bar buttons
function refreshSideBar() {
$.get("/cgi-bin/ui_live.cgi", {cmd: "status_all"}, function (result) {
$.get("cgi-bin/ui_live.cgi", {cmd: "status_all"}, function (result) {
var switches = result.split("\n");
for (var i = 0; i < switches.length-1; i++) {
var switch_info = switches[i].split(":");
@@ -70,6 +70,21 @@ function PTZControl(view) {
}
}
function pushToTalk(action) {
if (action == "on") {
$("#btn-ptt span").attr("style","color:red");
$("#btn-ptt span").removeAttr("onpointerdown");
$("#btn-ptt").attr("onpointerup","pushToTalk('off')");
startRecording();
}
else {
stopRecording();
$("#btn-ptt span").removeAttr("style");
$("#btn-ptt span").removeAttr("onpointerup");
$("#btn-ptt").attr("onpointerdown","pushToTalk('on')");
}
}
function record(action) {
$.get("cgi-bin/ui_live.cgi",{cmd: "recording", action: action},function (result) {
if (action == "on" || result == "ON\n") {

View File

@@ -1,11 +1,12 @@
//Function to delete a file
function deleteFile(fileName,dir,confirm) {
if (confirm)
function deleteFile(fileName,dir,askuser) {
if (askuser) {
var del = confirm("Confirm delete file: "+fileName);
if ( del ) {
$.get("cgi-bin/ui_sdcard.cgi", {cmd: "del_file",file: fileName});
getFiles(dir);
}
}
else {
$.get("cgi-bin/ui_sdcard.cgi", {cmd: "del_file",file: fileName});
getFiles(dir);
@@ -48,7 +49,7 @@ function getFiles(dir) {
<td>"+config_info[2]+"</td> \
<td> \
<a href=\""+config_info[3]+"\" download><i class='fas fa-download' title='Download file'></i></a> \
<span onclick=\"deleteFile('"+config_info[3]+"','"+dir+",true')\"><i class='fas fa-trash' title='Delete file'></i></span>\
<span onclick=\"deleteFile('"+config_info[3]+"','"+dir+"',true)\"><i class='fas fa-trash' title='Delete file'></i></span>\
"+html_photo+"\
</td></tr>");
}

View File

@@ -15,7 +15,7 @@ var iridix = new TimeSeries();
var gain = new TimeSeries();
function readIspInfo(){
$.get("/cgi-bin/ui_softnight.cgi", function(data, status)
$.get("cgi-bin/ui_softnight.cgi", function(data, status)
{
parseIspInfo(data);
});
@@ -78,7 +78,7 @@ function startGraph(){
}
$(document).ready(function () {
$.get("/cgi-bin/action.cgi?cmd=get_sw_night_config", function(data, status)
$.get("cgi-bin/action.cgi?cmd=get_sw_night_config", function(data, status)
{
var list = data.split('-');
var valid=false;
@@ -365,4 +365,4 @@ $('#swNightForm').submit(function (event) {
showResult(res);
});
event.preventDefault();
});
});

View File

@@ -2,7 +2,7 @@
<div class="w3-panel w3-card">
<p id="hostname"> Hostname</p>
<div class="w3-display-container" onmouseover="camControl('show')" onmouseleave="camControl('hide');PTZControl('hide')">
<img id="liveview" src="/cgi-bin/currentpic.cgi" onerror="this.src='img/unable_load.png';" alt="Camera" style="width:100%" onmouseover="camControl('show')" onmouseout="camControl('hide')" >
<img id="liveview" src="cgi-bin/currentpic.cgi" onerror="this.src='img/unable_load.png';" alt="Camera" style="width:100%" onmouseover="camControl('show')" onmouseout="camControl('hide')" >
<div id="btn-ptz-left" class="w3-display-left w3-container w3-hide">
<button class="w3-button w3-circle w3-black" onclick="moveCamera('left')"><i class="fa fa-angle-left"></i></button>
</div>
@@ -23,6 +23,7 @@
<button class="w3-button w3-circle w3-black" onclick="flip()">Flip</button>
</div>
<div id="cam-control" class="w3-display-bottomright w3-container w3-hide">
<button id="btn-ptt" class="w3-button w3-circle w3-black" onpointerdown="pushToTalk('on')" title="Push To Talk"><span><i class="fas fa-microphone"></i></span></button>
<button id="btn-record" class="w3-button w3-circle w3-black" onclick="record('on')" title="Record On/Off"><span><i class="fas fa-video"></i></span></button>
<button id="btn-screenshot" class="w3-button w3-circle w3-black" onclick="downloadScreenshot()" title="Download Screenshot"><span><i class="fas fa-image"></i></span></button>
<button id="btn-ptz" class="w3-button w3-circle w3-black" onclick="PTZControl('show')" title="PTZ Control"> <i class="fa fa-arrows-alt"></i></button>
@@ -33,7 +34,6 @@
<p></p>
</div>
<script type="text/javascript" src="js/live.js"></script>
<script type="text/javascript" src="js/audio_recorder.js"></script>
<script type="text/javascript" src="js/audio_capture.js"></script>
<script type="text/javascript" src="js/live.js"></script>

View File

@@ -215,18 +215,14 @@
<button class="accordion" type='button'>Dropbox storage</button>
<div class="panel">
<p></p>
<div class="w3-row-padding">
<div class="w3-half">
<label>Dropbox Long Lived Token</label>
<input class="w3-input" id="dropboxToken">
</div>
<div class="w3-container">
<label>Dropbox URL</label>
<input class="w3-input" id="dropboxUrl" type="text">
</div>
<p></p>
<div class="w3-row-padding">
<div class="w3-half">
</div>
<div class="w3-half">
</div>
<div class="w3-container">
<label>Dropbox Long Lived Token</label>
<input class="w3-input" id="dropboxToken" type="text">
</div>
<p></p>
<div class="w3-row-padding">
@@ -321,6 +317,15 @@
<option value="true">Activate</option>
</select>
</div>
<div class="w3-container">
<label>Matrix alert type</label>
<select id="matrixAlertType" class="w3-select" name="option">
<option value="text">Text</option>
<option value="image">Image</option>
<option value="video">Video</option>
<option value="video+image">Video and Image</option>
</select>
</div>
<div class="w3-container">
<label>Night mode delay (ignore motion events for 'delay' seconds after switching to night mode)</label>
<input class="w3-input" id="nightModeEventDelay" type="number" size="6">

View File

@@ -0,0 +1,142 @@
<?php
/* A simple Dropbox-like API for your own server
Copyright (C) 2021 Chris Osgood
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
##### CONFIG ###############################################################
# Change this to some random long password.
# This key should match the key under Settings->Motion Settings->Storage->Dropbox storage->Dropbox Long Lived Token.
$APIKEY = "aw4QrDrz5Z94oyaYLx9ojh8ZDARt7tA8hRqosNG305shGD6NJtAcjOC3WS3odsBMdkPFvl0hJSZyIkCJ";
# This is the directory where all images and video will be stored. Missing subdirectories will be created here
# based on the values of "Dropbox snapshots remote directory" and "Dropbox videos remote directory".
# Make sure the web user has access permissions for this directory.
$OUTDIR = "/media/securitycam";
# Allowed file extensions
$ALLOWEDFILETYPES = array('jpg', 'mp4', 'avi');
# Maximum upload file size (50MB shown)
$MAXFILESIZE = 50 * 1024 * 1024;
############################################################################
if (!array_key_exists('HTTP_AUTHORIZATION', $_SERVER) || !array_key_exists('HTTP_DROPBOX_API_ARG', $_SERVER))
{
header("HTTP/1.1 500 Internal Server Error");
error_log("ERROR: Missing authorization or API argument");
exit();
}
if ($_SERVER['HTTP_AUTHORIZATION'] != "Bearer $APIKEY")
{
header("HTTP/1.1 403 Forbidden");
exit();
}
$path = json_decode($_SERVER['HTTP_DROPBOX_API_ARG'], true);
if ($path == null || !array_key_exists('path', $path))
{
header("HTTP/1.1 500 Internal Server Error");
error_log("ERROR: Missing or bad file path `".$_SERVER['HTTP_DROPBOX_API_ARG']."'");
exit();
}
$path = $path['path'];
#----------- PATH SANITIZING -----------------------
# Only allow basic characters and digits
$path = str_replace("\\", "/", $path);
$path = preg_replace("([^a-zA-Z\d \-_./])", "_", $path);
# Clean beginning of path
$path = preg_replace("(^[/.]+)", "", $path);
# No path backwards; NOTE: this is a simplistic approach that will
# disallow any paths with multiple repeating periods.
$path = preg_replace("(\.+)", ".", $path);
# Clean repeated path seperators
$path = preg_replace("(\/+)", "/", $path);
# Remove all "current directory" paths
do {
$oldpath = $path;
$path = str_replace("/./", "/", $path);
} while ($path != $oldpath);
#---------------------------------------------------
$dirname = dirname($path);
$filename = basename($path);
$ext = pathinfo($filename, PATHINFO_EXTENSION);
if (!$ext || strlen($filename) < 1)
{
header("HTTP/1.1 500 Internal Server Error");
error_log("ERROR: Bad file path");
exit();
}
if (!in_array(strtolower($ext), $ALLOWEDFILETYPES))
{
header("HTTP/1.1 403 Forbidden");
error_log("ERROR: File extension not allowed");
exit();
}
$dirname = "$OUTDIR/$dirname";
$filename = "$dirname/$filename";
if (!is_dir($dirname))
{
if (!mkdir($dirname, 0777, true))
{
header("HTTP/1.1 500 Internal Server Error");
error_log("ERROR: Failed to create output directory `$dirname'");
exit();
}
}
if (file_exists($filename))
{
header("HTTP/1.1 500 Internal Server Error");
error_log("ERROR: Output file already exists `$filename'");
exit();
}
if (!($fp = fopen($filename, "wb")))
{
header("HTTP/1.1 500 Internal Server Error");
error_log("ERROR: Failed to create output file `$filename'");
exit();
}
$data = file_get_contents('php://input', false, null, 0, $MAXFILESIZE);
$numbytes = fwrite($fp, $data);
fclose($fp);
if (!$numbytes)
{
header("HTTP/1.1 500 Internal Server Error");
error_log("ERROR: Failed to write output file `$filename'");
exit();
}
?>

View File

@@ -0,0 +1,14 @@
## Web server storage
It's possible to use the Dropbox functionality to store snapshots or video on your own server.
Look under Motion Settings»Motion Detection»Storage»Dropbox storage. There should be an option to change the Dropbox URL. This can be set to an internal or external web server. The default uses the Dropbox server.
Example: https[]()://yourserver/dafangstor.php
[dafangstor.php](dafangstor.php) is an example PHP script that can be used on your own server. Inside this file there are two options:
* APIKEY
* OUTDIR
APIKEY is the Long Lived Token value that is used to authenticate requests. This must match the value entered under Settings»Motion Detection»Storage»Dropbox storage»Dropbox Long Lived Token. The PHP script can be modified to include more fancy features like maybe for example changing the OUTDIR based on the authentication token or something similar.
OUTDIR sets the output directory relative to where PHP is running. The www user (eg. www-data) will need access permissions for this directory so that it can create sub directories and output files or you can manually create directories beforehand. Paths specified by "Dropbox snapshots remote directory" and "Dropbox videos remote directory" will be relative to this OUTDIR directory.