mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-31 21:41:33 +00:00
Switching to key-based APNS cert.
This commit is contained in:
parent
b32201a570
commit
9cd40a71e8
4 changed files with 70 additions and 32 deletions
4
Makefile
4
Makefile
|
@ -77,6 +77,10 @@ lint:
|
||||||
docker exec -it newsblur_web isort --profile black .
|
docker exec -it newsblur_web isort --profile black .
|
||||||
docker exec -it newsblur_web black --line-length 110 .
|
docker exec -it newsblur_web black --line-length 110 .
|
||||||
docker exec -it newsblur_web flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=venv
|
docker exec -it newsblur_web flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=venv
|
||||||
|
|
||||||
|
deps:
|
||||||
|
docker exec -t newsblur_web pip install -U uv
|
||||||
|
docker exec -t newsblur_web uv pip install -r requirements.txt
|
||||||
|
|
||||||
jekyll_build:
|
jekyll_build:
|
||||||
cd blog && JEKYLL_ENV=production bundle exec jekyll build
|
cd blog && JEKYLL_ENV=production bundle exec jekyll build
|
||||||
|
|
|
@ -7,9 +7,8 @@ import urllib.parse
|
||||||
|
|
||||||
import mongoengine as mongo
|
import mongoengine as mongo
|
||||||
import redis
|
import redis
|
||||||
from apns2.client import APNsClient
|
from pyapns_client import APNSClient, IOSPayloadAlert, IOSPayload, IOSNotification
|
||||||
from apns2.errors import BadDeviceToken, DeviceTokenNotForTopic, Unregistered
|
from pyapns_client import APNSDeviceException, APNSServerException, APNSProgrammingException, UnregisteredException
|
||||||
from apns2.payload import Payload
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
@ -306,7 +305,7 @@ class MUserFeedNotification(mongo.Document):
|
||||||
# 4. Save the key file to secrets/certificates/ios/apns_key.p8
|
# 4. Save the key file to secrets/certificates/ios/apns_key.p8
|
||||||
# 5. Note your Team ID and Key ID
|
# 5. Note your Team ID and Key ID
|
||||||
# 6. Deploy: aps -l work -t apns,repo,celery
|
# 6. Deploy: aps -l work -t apns,repo,celery
|
||||||
|
|
||||||
# Legacy certificate method (kept for reference):
|
# Legacy certificate method (kept for reference):
|
||||||
# 0. Upgrade to latest openssl: brew install openssl
|
# 0. Upgrade to latest openssl: brew install openssl
|
||||||
# 1. Create certificate signing request in Keychain Access
|
# 1. Create certificate signing request in Keychain Access
|
||||||
|
@ -321,22 +320,24 @@ class MUserFeedNotification(mongo.Document):
|
||||||
# 7. cat aps.pem aps_key.noenc.pem > aps.p12.pem
|
# 7. cat aps.pem aps_key.noenc.pem > aps.p12.pem
|
||||||
# 8. Verify: openssl s_client -connect gateway.push.apple.com:2195 -cert aps.p12.pem
|
# 8. Verify: openssl s_client -connect gateway.push.apple.com:2195 -cert aps.p12.pem
|
||||||
# 9. Deploy: aps -l work -t apns,repo,celery
|
# 9. Deploy: aps -l work -t apns,repo,celery
|
||||||
|
|
||||||
# Using token-based authentication (modern method)
|
# Using token-based authentication (modern method with pyapns-client)
|
||||||
key_file_path = "/srv/newsblur/config/certificates/apns_key.p8"
|
key_file_path = "/srv/newsblur/config/certificates/apns_key.p8"
|
||||||
apns = APNsClient(
|
|
||||||
team_id=settings.APNS_TEAM_ID,
|
|
||||||
auth_key_id=settings.APNS_KEY_ID,
|
|
||||||
auth_key_path=key_file_path,
|
|
||||||
use_sandbox=tokens.use_sandbox
|
|
||||||
)
|
|
||||||
|
|
||||||
notification_title_only = is_true(user.profile.preference_value("notification_title_only"))
|
notification_title_only = is_true(user.profile.preference_value("notification_title_only"))
|
||||||
title, subtitle, body = self.title_and_body(story, usersub, notification_title_only)
|
title, subtitle, body = self.title_and_body(story, usersub, notification_title_only)
|
||||||
image_url = None
|
image_url = None
|
||||||
if len(story["image_urls"]):
|
if len(story["image_urls"]):
|
||||||
image_url = story["image_urls"][0]
|
image_url = story["image_urls"][0]
|
||||||
# print image_url
|
|
||||||
|
# Create APNS client
|
||||||
|
apns = APNSClient(
|
||||||
|
mode=APNSClient.MODE_DEV if tokens.use_sandbox else APNSClient.MODE_PROD,
|
||||||
|
root_cert_path=None,
|
||||||
|
auth_key_path=key_file_path,
|
||||||
|
auth_key_id=settings.APNS_KEY_ID,
|
||||||
|
team_id=settings.APNS_TEAM_ID
|
||||||
|
)
|
||||||
|
|
||||||
confirmed_ios_tokens = []
|
confirmed_ios_tokens = []
|
||||||
for token in tokens.ios_tokens:
|
for token in tokens.ios_tokens:
|
||||||
|
@ -345,27 +346,44 @@ class MUserFeedNotification(mongo.Document):
|
||||||
"~BMStory notification by iOS: ~FY~SB%s~SN~BM~FY/~SB%s"
|
"~BMStory notification by iOS: ~FY~SB%s~SN~BM~FY/~SB%s"
|
||||||
% (story["story_title"][:50], usersub.feed.feed_title[:50]),
|
% (story["story_title"][:50], usersub.feed.feed_title[:50]),
|
||||||
)
|
)
|
||||||
payload = Payload(
|
|
||||||
alert={"title": title, "subtitle": subtitle, "body": body},
|
# Create payload using helper classes
|
||||||
|
alert = IOSPayloadAlert(title=title, subtitle=subtitle, body=body)
|
||||||
|
custom_data = {
|
||||||
|
"story_hash": story["story_hash"],
|
||||||
|
"story_feed_id": story["story_feed_id"],
|
||||||
|
}
|
||||||
|
if image_url:
|
||||||
|
custom_data["image_url"] = image_url
|
||||||
|
|
||||||
|
payload = IOSPayload(
|
||||||
|
alert=alert,
|
||||||
|
custom=custom_data,
|
||||||
category="STORY_CATEGORY",
|
category="STORY_CATEGORY",
|
||||||
mutable_content=True,
|
mutable_content=image_url is not None
|
||||||
custom={
|
|
||||||
"story_hash": story["story_hash"],
|
|
||||||
"story_feed_id": story["story_feed_id"],
|
|
||||||
"image_url": image_url,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
notification = IOSNotification(payload=payload, topic="com.newsblur.NewsBlur")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
apns.send_notification(token, payload, topic="com.newsblur.NewsBlur")
|
apns.push(notification=notification, device_token=token)
|
||||||
except (BadDeviceToken, Unregistered, DeviceTokenNotForTopic):
|
|
||||||
logging.user(user, "~BMiOS token expired: ~FR~SB%s" % (token[:50]))
|
|
||||||
else:
|
|
||||||
confirmed_ios_tokens.append(token)
|
confirmed_ios_tokens.append(token)
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
logging.user(
|
logging.user(
|
||||||
user,
|
user,
|
||||||
"~BMiOS token good: ~FB~SB%s / %s" % (token[:50], len(confirmed_ios_tokens)),
|
"~BMiOS token good: ~FB~SB%s / %s" % (token[:50], len(confirmed_ios_tokens)),
|
||||||
)
|
)
|
||||||
|
except UnregisteredException as e:
|
||||||
|
logging.user(user, "~BMiOS token unregistered: ~FR~SB%s (since %s)" % (token[:50], e.timestamp_datetime))
|
||||||
|
except APNSDeviceException as e:
|
||||||
|
logging.user(user, "~BMiOS token invalid: ~FR~SB%s" % (token[:50]))
|
||||||
|
except APNSServerException as e:
|
||||||
|
logging.user(user, "~BMiOS notification server error: ~FR~SB%s - %s" % (token[:50], str(e)))
|
||||||
|
except APNSProgrammingException as e:
|
||||||
|
logging.user(user, "~BMiOS notification programming error: ~FR~SB%s - %s" % (token[:50], str(e)))
|
||||||
|
except Exception as e:
|
||||||
|
logging.user(user, "~BMiOS notification error: ~FR~SB%s - %s" % (token[:50], str(e)))
|
||||||
|
finally:
|
||||||
|
apns.close()
|
||||||
|
|
||||||
if len(confirmed_ios_tokens) < len(tokens.ios_tokens):
|
if len(confirmed_ios_tokens) < len(tokens.ios_tokens):
|
||||||
tokens.ios_tokens = confirmed_ios_tokens
|
tokens.ios_tokens = confirmed_ios_tokens
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
amqp==2.6.1
|
amqp==2.6.1
|
||||||
apns2==0.7.2
|
|
||||||
appdirs==1.4.4
|
appdirs==1.4.4
|
||||||
asgiref==3.3.4
|
asgiref==3.3.4
|
||||||
attrs==21.1.0
|
attrs==21.1.0
|
||||||
|
@ -46,13 +45,8 @@ Flask-BasicAuth==0.2.0
|
||||||
future==0.18.2
|
future==0.18.2
|
||||||
gunicorn==21.2.0
|
gunicorn==21.2.0
|
||||||
gevent==22.10.2
|
gevent==22.10.2
|
||||||
h2==2.6.2
|
|
||||||
hiredis==1.1.0
|
hiredis==1.1.0
|
||||||
hpack==3.0.0
|
|
||||||
httplib2==0.18.1
|
httplib2==0.18.1
|
||||||
httpx==0.27.2
|
|
||||||
hyper==0.7.0
|
|
||||||
hyperframe==3.2.0
|
|
||||||
idna==2.10
|
idna==2.10
|
||||||
image==1.5.33
|
image==1.5.33
|
||||||
iniconfig==1.1.1
|
iniconfig==1.1.1
|
||||||
|
@ -81,9 +75,9 @@ pluggy==0.13.1
|
||||||
psutil==5.7.3
|
psutil==5.7.3
|
||||||
psycopg2==2.9.2
|
psycopg2==2.9.2
|
||||||
py==1.10.0
|
py==1.10.0
|
||||||
|
pyapns-client==2.0.6
|
||||||
pyasn1==0.4.8
|
pyasn1==0.4.8
|
||||||
pycparser==2.20
|
pycparser==2.20
|
||||||
PyJWT==1.7.1
|
|
||||||
pymongo>=3,<4
|
pymongo>=3,<4
|
||||||
PyMySQL==0.10.1
|
PyMySQL==0.10.1
|
||||||
pynliner==0.8.0
|
pynliner==0.8.0
|
||||||
|
|
|
@ -61,6 +61,17 @@ class AppDirectoriesFinder(PipelineAppDirectoriesFinder):
|
||||||
"*LICENSE*",
|
"*LICENSE*",
|
||||||
"*README*",
|
"*README*",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def find_files(self, storage, path=None, all=False):
|
||||||
|
"""
|
||||||
|
Override to properly handle wildcard patterns like 'underscore-*.js'
|
||||||
|
"""
|
||||||
|
path = path or ''
|
||||||
|
for pattern in self.find_pattern_matches(path):
|
||||||
|
for path in storage.listdir(pattern[0])[1]:
|
||||||
|
if self.is_ignored(path, pattern[0]):
|
||||||
|
continue
|
||||||
|
yield path, storage
|
||||||
|
|
||||||
|
|
||||||
class FileSystemFinder(PipelineFileSystemFinder):
|
class FileSystemFinder(PipelineFileSystemFinder):
|
||||||
|
@ -115,3 +126,14 @@ class FileSystemFinder(PipelineFileSystemFinder):
|
||||||
# 'Gemfile*',
|
# 'Gemfile*',
|
||||||
"node_modules",
|
"node_modules",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def find_files(self, storage, path=None, all=False):
|
||||||
|
"""
|
||||||
|
Override to properly handle wildcard patterns like 'underscore-*.js'
|
||||||
|
"""
|
||||||
|
path = path or ''
|
||||||
|
for pattern in self.find_pattern_matches(path):
|
||||||
|
for path in storage.listdir(pattern[0])[1]:
|
||||||
|
if self.is_ignored(path, pattern[0]):
|
||||||
|
continue
|
||||||
|
yield path, storage
|
||||||
|
|
Loading…
Add table
Reference in a new issue