5 Комити afbedc6476 ... d894d35ac5

Аутор SHA1 Порука Датум
  Pi d894d35ac5 add support for sending MMS too пре 2 недеља
  Pi f212351d63 display MMS attachments (if any) for incoming messages пре 2 недеља
  Pi e7fded7c5c show local time instead of UTC пре 2 недеља
  Pi 0dfec77504 store MMS message attachments пре 2 недеља
  Pi 4677d6eca4 theme the form buttons and keep form at top of page пре 3 недеља
8 измењених фајлова са 136 додато и 57 уклоњено
  1. 2 0
      config/sqlpage.yaml
  2. 7 0
      init.sql
  3. 1 0
      site/contacts.sql
  4. 29 32
      site/conversations.sql
  5. 22 5
      site/messages.sql
  6. 19 3
      site/send.sql
  7. 33 16
      sms-sendmail.sh
  8. 23 1
      webhook.py

+ 2 - 0
config/sqlpage.yaml

@@ -2,3 +2,5 @@
 listen_on: "192.168.0.20:8222"
 listen_on: "192.168.0.20:8222"
 database_url: "sqlite:///home/pi/git/smpp-sqlite/sms.db?mode=rwc"
 database_url: "sqlite:///home/pi/git/smpp-sqlite/sms.db?mode=rwc"
 allow_exec: true
 allow_exec: true
+sqlite_extensions:
+  - "./fileio.so"

+ 7 - 0
init.sql

@@ -18,3 +18,10 @@ CREATE TABLE contacts (
     name TEXT PRIMARY KEY NOT NULL,
     name TEXT PRIMARY KEY NOT NULL,
     number TEXT NOT NULL
     number TEXT NOT NULL
 );
 );
+
+CREATE TABLE attachments (
+    id INTEGER PRIMARY KEY,
+    seq INTEGER NOT NULL,
+    hint TEXT NOT NULL,
+    data BLOB NOT NULL
+)

+ 1 - 0
site/contacts.sql

@@ -6,6 +6,7 @@ SELECT * FROM contacts;
 SELECT 'form' AS component
 SELECT 'form' AS component
 , '/add_contact.sql' AS action
 , '/add_contact.sql' AS action
 , 'Add' AS validate
 , 'Add' AS validate
+, 'orange' AS validate_color
 ;
 ;
 
 
 SELECT 'name' AS name
 SELECT 'name' AS name

+ 29 - 32
site/conversations.sql

@@ -1,38 +1,7 @@
-SELECT 'card' as component
-, 1 AS columns
-;
-SELECT COALESCE(c.name, SUBSTR(peer, 3, LENGTH(peer))) AS title
-, max(ts) AS footer
-, group_concat(msg, ' … ' ORDER BY ts ASC) AS description
-, '/conv?me='||:me||'&peer='||SUBSTR("Peer", 3, LENGTH("Peer"))  AS link
-FROM (
-  SELECT "from" AS peer
-  , received AS ts
-  , CASE WHEN received = max(received)OVER(PARTITION BY "to", "from") THEN payload->>'$.payload.text' ELSE NULL END AS msg
-  FROM inbox
-  WHERE "to" = '+1'||:me
-  UNION
-  SELECT "to" AS peer
-  , sent AS ts
-  , CASE WHEN sent = max(sent)OVER(PARTITION BY "to", "from") THEN payload ELSE NULL END AS msg
-  FROM outbox
-  WHERE "from" = '+1'||:me
-
-  ORDER BY ts
-) q
-LEFT JOIN contacts c ON c.number = q.peer
-GROUP BY "Peer"
-ORDER BY footer DESC, title
-;
-
---SELECT 'debug' AS component;
---SELECT :to_options AS options;
-
-
 SELECT 'form' AS component
 SELECT 'form' AS component
 , '/send.sql' AS action
 , '/send.sql' AS action
 , 'Send' AS validate
 , 'Send' AS validate
-, 'green' AS validate_color
+, 'orange' AS validate_color
 , 'post' AS method
 , 'post' AS method
 ;
 ;
 SELECT 'from' AS name
 SELECT 'from' AS name
@@ -56,3 +25,31 @@ SELECT 'Write a short message...' AS placeholder
 , '' AS label
 , '' AS label
 , 2 AS rows
 , 2 AS rows
 ;
 ;
+
+SELECT 'card' as component
+, 1 AS columns
+;
+SELECT COALESCE(c.name, SUBSTR(peer, 3, LENGTH(peer))) AS title
+, datetime(max(ts), 'localtime') AS footer
+, group_concat(msg, ' … ' ORDER BY ts ASC) AS description
+, '/conv?me='||:me||'&peer='||SUBSTR("Peer", 3, LENGTH("Peer"))  AS link
+FROM (
+  SELECT "from" AS peer
+  , received AS ts
+  , CASE WHEN received = max(received)OVER(PARTITION BY "to", "from") THEN payload->>'$.payload.text' ELSE NULL END AS msg
+  FROM inbox
+  WHERE "to" = '+1'||:me
+  UNION
+  SELECT "to" AS peer
+  , sent AS ts
+  , CASE WHEN sent = max(sent)OVER(PARTITION BY "to", "from") THEN payload ELSE NULL END AS msg
+  FROM outbox
+  WHERE "from" = '+1'||:me
+
+  ORDER BY ts
+) q
+LEFT JOIN contacts c ON c.number = q.peer
+GROUP BY "Peer"
+ORDER BY footer DESC, title
+;
+

+ 22 - 5
site/messages.sql

@@ -1,8 +1,8 @@
 SELECT 'form' AS component
 SELECT 'form' AS component
 , '/send.sql' AS action
 , '/send.sql' AS action
-, 'green' AS validate_color
 , 'post' AS method
 , 'post' AS method
 , 'Send' AS validate
 , 'Send' AS validate
+, 'orange' AS validate_color
 ;
 ;
 SELECT 'from' AS name
 SELECT 'from' AS name
 , :me AS value
 , :me AS value
@@ -18,6 +18,11 @@ SELECT 'to' AS name
 , TRUE AS readonly
 , TRUE AS readonly
 , 3 AS width
 , 3 AS width
 ;
 ;
+SELECT 'media' AS name
+, 'file' type
+, 'MMS' AS label
+, 6 AS width
+;
 SELECT 'Write a short message...' AS placeholder
 SELECT 'Write a short message...' AS placeholder
 , TRUE AS autofocus
 , TRUE AS autofocus
 , 'message' AS name
 , 'message' AS name
@@ -29,19 +34,31 @@ SELECT 'Write a short message...' AS placeholder
 SELECT 'card' as component
 SELECT 'card' as component
 , 1 AS columns
 , 1 AS columns
 ;
 ;
-SELECT COALESCE(c.name, SUBSTR("from", 3, LENGTH("from"))) AS title
-, received AS footer, json(payload)->>'$.payload.text' AS description
+SELECT CASE WHEN COALESCE(a.seq, 0) = 0 AND payload->>'$.payload.text' <> '' THEN COALESCE(c.name, SUBSTR("from", 3, LENGTH("from"))) ELSE NULL END AS title
+, received AS _cardinal
+, a.seq AS _seq
+, CASE WHEN COALESCE(a.seq, 0) = 0 AND payload->>'$.payload.text' <> '' THEN datetime(received, 'localtime') ELSE NULL END AS footer
+, CASE WHEN COALESCE(a.seq, 0) = 0 AND payload->>'$.payload.text' <> '' THEN payload->>'$.payload.text' ELSE NULL END AS description
+-- TODO: add support for more MMS attachment types
+, CASE WHEN a.hint = '/media.jpeg' THEN a.data ELSE NULL END AS top_image
 , 'blue' AS color
 , 'blue' AS color
 FROM inbox i
 FROM inbox i
 LEFT JOIN contacts c ON c.number = i."from"
 LEFT JOIN contacts c ON c.number = i."from"
+LEFT JOIN attachments a ON a.id = payload->>'$.payload.id'
 WHERE '+1'||:peer = "from" AND '+1'||:me = "to"
 WHERE '+1'||:peer = "from" AND '+1'||:me = "to"
 UNION
 UNION
 SELECT COALESCE(c.name, SUBSTR("from", 3, LENGTH("from"))) AS title
 SELECT COALESCE(c.name, SUBSTR("from", 3, LENGTH("from"))) AS title
-, sent AS footer, payload AS description
+, sent AS _cardinal
+, a.seq AS _seq
+, CASE WHEN COALESCE(a.seq, 0) = 0 AND payload <> '' THEN datetime(sent, 'localtime') ELSE NULL END AS footer
+, CASE WHEN COALESCE(a.seq, 0) = 0 AND payload <> '' THEN payload ELSE NULL END AS description
+-- TODO: add support for more MMS attachment types
+, CASE WHEN a.hint = 'image/jpeg' THEN a.data ELSE NULL END AS top_image
 , 'green' AS color
 , 'green' AS color
 FROM outbox o
 FROM outbox o
 LEFT JOIN contacts c ON c.number = o."from"
 LEFT JOIN contacts c ON c.number = o."from"
+LEFT JOIN attachments a ON a.id = o.id
 WHERE '+1'||:peer = "to" AND '+1'||:me = "from"
 WHERE '+1'||:peer = "to" AND '+1'||:me = "from"
-ORDER BY footer DESC
+ORDER BY _cardinal DESC, _seq DESC
 ;
 ;
 
 

+ 19 - 3
site/send.sql

@@ -1,3 +1,19 @@
---SET ":result" = sqlpage.exec('/usr/local/bin/sms-sendmail.sh', :from, :to, :message);
-SET ":result" = sqlpage.exec('./sms-sendmail.sh', :from, :to, :message);
-SELECT 'redirect' AS component, '/conv?me='||:from||'&peer='||:to AS link ;
+--SET ":file_name" = sqlpage.uploaded_file_name('media');
+SET ":mime_type" = sqlpage.uploaded_file_mime_type('media');
+SET ":file_path" = sqlpage.uploaded_file_path('media');
+
+SET ":result" = sqlpage.exec('./sms-sendmail.sh', :from, :to, :message, :file_path);
+
+INSERT INTO outbox (sent, "from", "to", payload)
+VALUES (CURRENT_TIMESTAMP, '+1'||:from, '+1'||:to, :message)
+;
+SELECT 'redirect' AS component, '/conv?me='||:from||'&peer='||:to AS link
+WHERE :mime_type IS NULL OR :mime_type = ''
+;
+SET ":id" = (SELECT id FROM outbox ORDER BY id DESC LIMIT 1);
+INSERT INTO attachments (id, seq, hint, data)
+-- readfile() is provided by fileio sqlite extension
+VALUES (:id, 0, :mime_type, readfile(:file_path))
+;
+SELECT 'redirect' AS component, '/conv?me='||:from||'&peer='||:to AS link
+;

+ 33 - 16
sms-sendmail.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 #!/bin/bash
 #
 #
-# Requires: maildrop, postfix, python3
+# Requires: maildrop, postfix
 #
 #
 
 
 set -euo pipefail
 set -euo pipefail
@@ -8,30 +8,47 @@ set -euo pipefail
 FROM="$1"
 FROM="$1"
 TO="$2"
 TO="$2"
 MESSAGE="$3"
 MESSAGE="$3"
+FILE_NAME="$4"
 
 
 . .env
 . .env
 
 
 recipient="sms@voip.ms"
 recipient="sms@voip.ms"
 subject="$TO.$CODE.$FROM"
 subject="$TO.$CODE.$FROM"
 
 
-printf 'To:\r\nFrom:\r\nContent-Type:\r\n\r\n%s\r\n' "$MESSAGE" \
+function make_mms(){
+	WD=$(mktemp -d)
+	trap 'rm -rf "$WD"' EXIT
+	make_plain > "$WD"/plain.txt
+	makemime -j <(
+		makemime \
+			-m "multipart/mixed" \
+			-a "Mime-Version: 1.0" \
+			"$WD"/plain.txt
+	) <(
+		makemime \
+			-c "image/jpeg" \
+			-a "Content-Disposition: attachment; filename=image.jpg" \
+			"$FILE_NAME"
+	)
+}
+
+function make_plain(){
+	makemime \
+		-c "text/plain; charset=utf-8" \
+		-a "Content-Disposition: inline" \
+		- <<<"$MESSAGE"
+}
+
+if [ -n "$FILE_NAME" ]
+then
+	make_mms
+else
+	make_plain
+fi \
 	| reformail \
 	| reformail \
 		-I"To: $recipient" \
 		-I"To: $recipient" \
 		-I"From: $SENDER" \
 		-I"From: $SENDER" \
 		-I"Subject: $subject" \
 		-I"Subject: $subject" \
-		-I"Content-Type: text/plain" -d1 \
+		-d1 \
 	| sendmail -f "$SENDER" -t
 	| sendmail -f "$SENDER" -t
 
 
-export MESSAGE FROM TO
-python3 <<'EOF'
-import sqlite3
-from os import environ as env
-for x in ('FROM', 'TO', 'MESSAGE', 'DB'):
-    globals()[x] = env.get(x)
-with sqlite3.connect(DB) as conn:
-    cursor = conn.cursor()
-    cursor.execute("""
-INSERT INTO outbox (sent, "from", "to", payload)
-VALUES (CURRENT_TIMESTAMP, '+1'||?, '+1'||?, ?)
-""", [FROM, TO, MESSAGE])
-EOF

+ 23 - 1
webhook.py

@@ -1,6 +1,7 @@
 from datetime import datetime
 from datetime import datetime
 from bottle import Bottle, request, response
 from bottle import Bottle, request, response
 from os import environ as env
 from os import environ as env
+from requests import get
 import sqlite3
 import sqlite3
 import json
 import json
 
 
@@ -12,6 +13,26 @@ for x in ('DB', 'WH_HOST', 'WH_PORT'):
 conn = sqlite3.connect(DB)
 conn = sqlite3.connect(DB)
 cur = conn.cursor()
 cur = conn.cursor()
 
 
+def download_attachment(_id, _seq, attachment):
+    url = attachment["url"]
+    #print(url)
+    *_, hint = url.rsplit('/', 1)
+    #response = urllib.request.urlopen(url)
+    response = get(url)
+    data = response.content
+    return hint, data
+
+def insert_attachment(_id, _seq, attachment):
+    try:
+        hint, data = download_attachment(_id, _seq, attachment)
+        cur.execute("""
+INSERT INTO attachments (id, seq, hint, data)
+VALUES (?, ?, '/'||?, ?)
+""", (_id, _seq, hint, data))
+    except Exception as e:
+        print(e)
+
+
 @app.post("/incoming")
 @app.post("/incoming")
 def inbound_sms():
 def inbound_sms():
     data = request.json["data"]
     data = request.json["data"]
@@ -25,7 +46,8 @@ def inbound_sms():
 INSERT INTO inbox (payload, received, "from", "to")
 INSERT INTO inbox (payload, received, "from", "to")
 VALUES (?, CURRENT_TIMESTAMP, ?, ?)
 VALUES (?, CURRENT_TIMESTAMP, ?, ?)
 """, (payload, from_number, to_number))
 """, (payload, from_number, to_number))
-
+    for _seq, a in enumerate(data["payload"]["media"]):
+        insert_attachment(data["id"], _seq, a)
     conn.commit()
     conn.commit()
 
 
     response.status = 200
     response.status = 200