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"
 database_url: "sqlite:///home/pi/git/smpp-sqlite/sms.db?mode=rwc"
 allow_exec: true
+sqlite_extensions:
+  - "./fileio.so"

+ 7 - 0
init.sql

@@ -18,3 +18,10 @@ CREATE TABLE contacts (
     name TEXT PRIMARY KEY 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
 , '/add_contact.sql' AS action
 , 'Add' AS validate
+, 'orange' AS validate_color
 ;
 
 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
 , '/send.sql' AS action
 , 'Send' AS validate
-, 'green' AS validate_color
+, 'orange' AS validate_color
 , 'post' AS method
 ;
 SELECT 'from' AS name
@@ -56,3 +25,31 @@ SELECT 'Write a short message...' AS placeholder
 , '' AS label
 , 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
 , '/send.sql' AS action
-, 'green' AS validate_color
 , 'post' AS method
 , 'Send' AS validate
+, 'orange' AS validate_color
 ;
 SELECT 'from' AS name
 , :me AS value
@@ -18,6 +18,11 @@ SELECT 'to' AS name
 , TRUE AS readonly
 , 3 AS width
 ;
+SELECT 'media' AS name
+, 'file' type
+, 'MMS' AS label
+, 6 AS width
+;
 SELECT 'Write a short message...' AS placeholder
 , TRUE AS autofocus
 , 'message' AS name
@@ -29,19 +34,31 @@ SELECT 'Write a short message...' AS placeholder
 SELECT 'card' as component
 , 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
 FROM inbox i
 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"
 UNION
 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
 FROM outbox o
 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"
-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
 #
-# Requires: maildrop, postfix, python3
+# Requires: maildrop, postfix
 #
 
 set -euo pipefail
@@ -8,30 +8,47 @@ set -euo pipefail
 FROM="$1"
 TO="$2"
 MESSAGE="$3"
+FILE_NAME="$4"
 
 . .env
 
 recipient="sms@voip.ms"
 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 \
 		-I"To: $recipient" \
 		-I"From: $SENDER" \
 		-I"Subject: $subject" \
-		-I"Content-Type: text/plain" -d1 \
+		-d1 \
 	| 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 bottle import Bottle, request, response
 from os import environ as env
+from requests import get
 import sqlite3
 import json
 
@@ -12,6 +13,26 @@ for x in ('DB', 'WH_HOST', 'WH_PORT'):
 conn = sqlite3.connect(DB)
 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")
 def inbound_sms():
     data = request.json["data"]
@@ -25,7 +46,8 @@ def inbound_sms():
 INSERT INTO inbox (payload, received, "from", "to")
 VALUES (?, CURRENT_TIMESTAMP, ?, ?)
 """, (payload, from_number, to_number))
-
+    for _seq, a in enumerate(data["payload"]["media"]):
+        insert_attachment(data["id"], _seq, a)
     conn.commit()
 
     response.status = 200