webPush通知機能 完成!👏.これでブラウザ閉じても通知可能✌.

2024.11.12

Logging

おはようございます.金曜日の休みを使ってwebPush通知機能を追加しました、これでブラウザ閉じても通知されるようになります.Laravel側はお見せできませんがどういう技術を使用したか書いていきます.まずLaravelの拡張ライブラリを使用しました.

下記のリンクを参照しライブラリをインストールしてみてください.
https://laravel-notification-channels.com/webpush/#installation

インストール手順はリンク先に書いていますので、それを参照しその後フロント側を設定します.下記は作りかけのコードですが通知登録が可能でバックエンド側からスケジュールで通知を飛ばすことが出来ます.

フロント側はサービスワーカーのJSファイルとユーザー通知の許可行い、その情報をバックエンドに渡す処理ファイルが存在します.サービスワーカーJSコードはググれば書き方が出ているので検索してみてください.

公開するのは通知許可を行う方のファイルになります💁あとヒントとしてバックエンドも少し公開します.


const vapidPublicKey = import.meta.env.VITE_VAPID_PUBLIC_KEY;

async function setupPushNotifications() {
    if (Notification.permission === 'granted') {
        try {
            // サービスワーカーを登録
            await navigator.serviceWorker.register('/assets/js/npush-service-worker.js').then(async(registration) => {
                if ('serviceWorker' in navigator && 'PushManager' in window) {
                    try {
                            const subscription = await registration.pushManager.subscribe({
                                userVisibleOnly: true,
                                applicationServerKey: urlBase64ToUint8Array(vapidPublicKey),
                            });
            
                            const response = await fetch('/save-subscription', {
                                method: 'POST',
                                headers: {
                                    'Accept': 'application/json',
                                    'Content-Type': 'application/json',
                                    'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content
                                },
                                body: JSON.stringify(subscription),
                            });
            
                            if (!response.ok) throw new Error('サブスクリプションの保存に失敗しました');
            
                            new Notification('ご登録', {
                                body: 'ご登録有難う御座います',
                            });
                        
                    } catch (error) {
                        console.error('Push subscription error:', error);
                    }
                } else if (result !== 'granted') {
                    console.log('通知の権限が拒否されました。');
                }       
            });
            console.log('サービスワーカーが正常に登録されました');
        } catch (error) {
            console.error('サービスワーカーの登録に失敗しました:', error);
        }
    }
}


function urlBase64ToUint8Array(base64String) {
    const padding = '='.repeat((4 - base64String.length % 4) % 4);
    const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
    const rawData = window.atob(base64);
    const outputArray = new Uint8Array(rawData.length);
    for (let i = 0; i < rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
}

document.getElementById('enable-notifications').addEventListener('click', async () => {
    const permission = await Notification.requestPermission();
    if (permission === 'granted') {
        setupPushNotifications();
    } else {
        console.log('通知の権限が拒否されました。');
    }
});

これで通知の識別データが取得し登録が可能になります.登録されたデータを元に個別通知、全員に通知などが可能になります.なお、会員登録されたユーザーのみ通知が飛ぶシステムです.

        $endpoint = $request->endpoint;
        $token = $request->keys['auth'];
        $key = $request->keys['p256dh'];
        $user = $request->user();
        $user->updatePushSubscription($endpoint, $key, $token);

上記のデータがバックエンド側に保存されます.それを使用しユーザーにどのように送信すれば良いか?

use App\Notifications\Reserved;
$user = User::find(1);
$user->notify(new Reserved($reservation));

こんな感じだと思ってください.抜粋しているのであくまでもヒントです.あとはご自身で考えて対応してください.

明日へ続く

著者名  @taoka_toshiaki

※この記事は著者が40代前半に書いたものです.

Profile
高知県在住の@taoka_toshiakiです、記事を読んで頂きありがとうございます.
数十年前から息を吸うように日々記事を書いてます.たまに休んだりする日もありますがほぼ毎日投稿を心掛けています😅.
SNSも使っています、フォロー、いいね、シェア宜しくお願い致します🙇.
SNS::@taoka_toshiaki

OFUSEで応援を送る

タグ

addEventListener, assets, async, catch, document.getElementById, document.querySelector, headers, installation, keys, METHOD, padding, permission, rawData.charCodeAt, rawData.length, registration, repeat, replace, save-subscription, subscription, then,

サービスワーカー、フロント側のコード.スターウォーズみたいだね.

2024.11.09

Logging

おはようございます.サービスワーカーのプッシュ通知で使用するフロント側のコードの一部部分.このコードのregistration.pushManager等からググるとブラウザを閉じても通知できる方法などが記載しているサイトが見つかるかもしれません.尚、このサイトではこれ以上の情報を記載するつもりはないですが、後日、通知の機能の動画などを掲載するつもりではいます.

if ('serviceWorker' in navigator && 'PushManager' in window) {
    navigator.serviceWorker.ready.then(function(registration) {
        registration.pushManager.subscribe({
            userVisibleOnly: true,
            applicationServerKey: urlBase64ToUint8Array('VAPID_PUBLIC_KEY')
        }).then(function(subscription) {
            fetch('/api/save-subscription', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(subscription),
            });
        }).catch(function(error) {
            console.error('Push subscription error:', error);
        });
    });
}

function urlBase64ToUint8Array(base64String) {
    const padding = '='.repeat((4 - base64String.length % 4) % 4);
    const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
    const rawData = window.atob(base64);
    const outputArray = new Uint8Array(rawData.length);
    for (let i = 0; i < rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
}

その時、どのようなライブラリーを使用したかや技術の一部を公開しようと思っています.ただ、全体のコードを全て公開するつもりは今の所はないです.理由は有料な情報でありこれで商売している人がいると思うので全ての技術情報を公開は控えるつもりです.

明日へ続く

著者名  @taoka_toshiaki

※この記事は著者が40代前半に書いたものです.

Profile
高知県在住の@taoka_toshiakiです、記事を読んで頂きありがとうございます.
数十年前から息を吸うように日々記事を書いてます.たまに休んだりする日もありますがほぼ毎日投稿を心掛けています😅.
SNSも使っています、フォロー、いいね、シェア宜しくお願い致します🙇.
SNS::@taoka_toshiaki

OFUSEで応援を送る

タグ

API, application, applicationServerKey, atob, catch, fetch, headers, METHOD, navigator, outputArray, padding, rawData.charCodeAt, rawData.length, registration, repeat, replace, save-subscription, subscription, then, userVisibleOnly,

サイト内の文字をハイライトする一万円の案件は。 #案件

2022.12.18

Logging

おはようございます、笑う門には福来る😆この記事は月曜日の朝に書いたものです💦。

先日、Chromeの拡張機能でサイト内の文字をハイライトする一万円の案件を募集しておりました。この一万円の案件は妥当な金額なのかが“????”。例えば人工知能をゴリゴリと使えるスーパーエンジニアにとっては朝飯前の案件だと思いますが、見習いエンジニアにとっては難しい案件なのかもしれない。

この一万円という金額は人によって高くもなるし安くもなるかもしれないです。要するに見習いエンジニアが3日間かけて納品した場合とスーパーエンジニアがものの数秒で納品した場合を日本の平均時給で考えると一方は黒字でもう一方は赤字になる。

そう考えると今回の文字をハイライトするという案件は適正価格なのかもしれない。

因みにこの文字をハイライトするChromeの拡張機能はもう存在しており無料で公開されている。そう考えると一万円も貰えるというのはラッキーなのかも知れない。

尚、文字をハイライトするコードは下記により参照ください(デモページはこちら)。

let funs = {
    init: { htmlcode: document.getElementById("vals").innerHTML },
    highlight: function (e) {
        document.getElementById("vals").innerHTML = funs.init.htmlcode;
        if (!String(this.value).match(/[a-zA-Z]/) && this.value) {
            document.getElementById("vals").innerHTML = String(funs.init.htmlcode).replace(new RegExp(this.value, 'g'), '<span style="color:red">' + this.value + '</span>');
        }
    },
    inputevent:function(){
        document.getElementById("txt").addEventListener("input", this.highlight);
    }
};
funs.inputevent();

著者名  @taoka_toshiaki

※この記事は著者が40代前半に書いたものです.

Profile
高知県在住の@taoka_toshiakiです、記事を読んで頂きありがとうございます.
数十年前から息を吸うように日々記事を書いてます.たまに休んだりする日もありますがほぼ毎日投稿を心掛けています😅.
SNSも使っています、フォロー、いいね、シェア宜しくお願い致します🙇.
SNS::@taoka_toshiaki

OFUSEで応援を送る

タグ

a-zA-Z, addEventListener, document.getElementById, function, funs.init.htmlcode, getElementById, gt, highlight, htmlcode, init, innerHTML, inputevent, lt, match, quot, replace, string, this.highlight, this.value, 朝飯前,

pythonで画像ダウンロードしたいなら。コレでよし🤔

2022.08.06

Logging

こんにちは、日が暮れて夕方になってしまいましたね?…本日の更新です😌。

今日は機械学習(tensorflow)の為の画像を集めをしていたました。画像を集めるのが面倒くさくて昔、ダウンロード用のソフトを自前で作っていたのだけど、その自前のソフトをいつの間にか消し去っていた為、再度Pythonで作り直しました。

作ったのですが、これは即席なダウンロードソフトなので完璧なものではないです。50枚~300枚の画像をダウンロード出来ます。そのダウンロードした画像を水回しして機械学習の画像分類に使用しているのですが、学習精度があまり良くないのが明日の課題。

人工知能に学習させる方法にはいろいろな方法があります。それらを上手く使えばもっと効率よく学習出来るみたいですが、そもそもPythonも普段から使用しないので手探り状態です。

もっと綺麗なコードを書けると思いますが…。

トイウコトデ、コードを記載しときますね、このコードはbingサイトで画像ダウンロードするために作られたものです。機械学習しているコードではありません。

from functools import cache
import time
import requests
import os
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.options import Options
options = Options()
# download_path = 'C:\python\images\face'
# options.add_experimental_option("prefs", {"download.default_directory": download_path})
# driver = webdriver.Chrome(ChromeDriverManager().install(), options=options)
driver = webdriver.Chrome(ChromeDriverManager().install())
driver.get("https://www.bing.com/images/search?q=顔&form=HDRSC2&first=1&tsc=ImageHoverTitle")
driver.set_window_size(945, 1012)
time.sleep(3)
sl = 700
for i in range(30):
    driver.execute_script("window.scrollTo(0," + str(sl) + ")")
    time.sleep(3)
    sl = sl + 700
img = []
for x in range(10):
    for y in range(100):
        try:
            txt = driver.find_element(By.XPATH,"//*[@id=\"mmComponent_images_2\"]/ul[" + str(int(y +1 )) +"]/li[" + str(int(x +1 )) +"]/div/div[1]/a").get_attribute("m")
            hoge = str(txt).split(",")
            #print(hoge)
            img.append([s for s in hoge if "murl" in s])
        except:
            print("errors not image")
driver.quit()
file_dir = "C:\\python\\images\\face\\"
for imgdata in img:
    url = str(imgdata).split(":")[1] + ":" + str(imgdata).split(":")[2]
    url = url.replace('"',"").replace("']","")
    print(url)
    try:
        urlData = requests.get(url).content
        with open(os.path.join(file_dir,os.path.basename(url)),'wb') as f:
            f.write(urlData)
    except:
        print("errors not Download")

著者名  @taoka_toshiaki

※この記事は著者が40代前半に書いたものです.

Profile
高知県在住の@taoka_toshiakiです、記事を読んで頂きありがとうございます.
数十年前から息を吸うように日々記事を書いてます.たまに休んだりする日もありますがほぼ毎日投稿を心掛けています😅.
SNSも使っています、フォロー、いいね、シェア宜しくお願い致します🙇.
SNS::@taoka_toshiaki

OFUSEで応援を送る

タグ

ChromeDriverManager, driver, driver.get, driver.quit, except, img.append, imgdata, options, print, quot, replace, scrollTo, selenium.webdriver.support.wait, sl, sleep, tensorflow, time.sleep, ul, urlData, トイウコトデ,

Pythonコード:demo

2019.11.05

Logging

#!/usr/local/bin/python3
# coding:utf-8
import os
import sys
import MeCab
import gensim
import markovify
import unicodedata
model = gensim.models.KeyedVectors.load_word2vec_format('/var/www/html/model.vec', binary=False)
f = open('merosu.txt')
tagger = MeCab.Tagger("-Owakati")
tagger.parse('')
text0 = tagger.parse(f.read())
text1 = text0
text0 = text0.replace('\n','')
text0 = text0.replace('\r','')
text1x = text0.split(" ")
text2 = []
try:
    for item in text1x:
        if item.strip():
            results  = model.most_similar(positive=[item],topn=2)
            #"print(results)
            for val1 in results:
                text2.append(val1[0] + "\n")
#
    # print (text1)
    # print (" ".join(text2))
    model_a = markovify.Text(text1 + "\n")
    print(str(model_a.make_sentence()).replace(' ',''))
    model_b = markovify.Text(" ".join(text2))
    print(str(model_b.make_sentence()).replace(' ',''))
    model_combo = markovify.combine([model_a, model_b], [1, 1])
    print(str(model_combo.make_sentence()).replace(' ',''))
except Exception as e:
    print("動作エラー", e.args)
    pass

著者名  @taoka_toshiaki

※この記事は著者が30代前半に書いたものです.

Profile
高知県在住の@taoka_toshiakiです、記事を読んで頂きありがとうございます.
数十年前から息を吸うように日々記事を書いてます.たまに休んだりする日もありますがほぼ毎日投稿を心掛けています😅.
SNSも使っています、フォロー、いいね、シェア宜しくお願い致します🙇.
SNS::@taoka_toshiaki

OFUSEで応援を送る

タグ

-Owakati, 'merosu, 0, , 2, , 39, 8, bin, binary, coding, demo, false, format, gensim, html, import, KeyedVectors, load, local, markovify, Mecab, model, models, open, OS, parse, Python, quot, read, replace, sys, tagger, Text, txt, unicodedata, usr, UTF-, var, Vec, Word, コード,