AdSense 新加坡稅務資訊相關申請與設定教學

最近 Google AdSense 開始要台灣的 AdSense 使用者提交新加坡稅務資訊了。這是因為台灣的 AdSense 是透過 Google Asia 付款,而他們的地點設在新加坡。目前新加坡似乎還尚未開始扣稅,但如果之後法律要求預扣稅款,支付的款項可能會扣除較高的扣繳稅額。

先說一下,雖然不是很複雜,但整個過程其實需要點時間,主要是因為需要跟國稅局申請「稅務居住地證明」這個文件,且每年需要重新申請並去Adsense更新。以下就是整個申請與設定流程:

先到財政部稅務入口網,填寫申請表格。可以選要用自然人憑證登入或是以健保卡登入。

閱讀全文 AdSense 新加坡稅務資訊相關申請與設定教學

判斷上傳的圖片格式

當我們想要限制上傳檔案的格式時,最簡單的方式是檢查副檔名或Mime types,不過如果該檔案被更改過副檔名的話,這個檢查就沒意義了。好在我們可以透過各種檔案格式的「魔術數字」來檢查該檔案的內容是否真的符合副檔名所對應的格式。

所謂的「魔術數字」指的是各種不同的檔案開頭的幾個固定字節。比如說 .jpg 或 .jpeg 的檔案,開頭都固定是“ffd8ffe0”,.png 的檔案,開頭都固定是 “89504e47”。我們可以在檔案上傳後存檔前,檢查檔案是否有符合這個規則,若符合再將檔案儲存。

以下是 node.js 檢查上傳圖片格式的程式碼範例:

import express from 'express'
// express middleware for multipart/form parsing
import multer from 'multer'
import fs from 'fs'
import path from 'path'

const app = express()
const port = 5438

// 因為這個範例中,想先檢查副檔名是否符合檔案格式再儲存
// 因此我們使用 multer 的 memoryStorage,這樣我們就可以在 file 物件中使用 buffer 來檢查
// 不過使用 memoryStorage 時,最好限制一下檔案大小,否則上傳大檔時,會很消耗主機記憶體
const storage = multer.memoryStorage()
const upload = multer({
  storage,
  fileFilter (req, file, cb) {
    const ext = path.extname(file.originalname)

    const acceptFormats = ['.webp', '.jpg', '.jpeg', '.png', '.gif', '.svg']
    if (!acceptFormats.includes(ext)) {
      return cb(new Error('Only images are allowed'))
    }

    cb(null, true)
  },
  limits: {
    // 限制上傳檔案大小 (bytes)
    fileSize: 1000000
  }
})

app.post('/upload', upload.array('files'), (req, res, next) => {
  if (req.files && req.files.length) {
    req.files.forEach(file => {
      const filename = new Date().getTime() + '_' + Math.floor(Math.random() * 1E+12)
      file.newfilename = filename
      const ext = path.extname(file.originalname)
      const newpath = 'uploads/' + filename + ext

      // 在這邊檢查圖片格式符不符合副檔名
      const needsCheckFormats = ['.webp', '.jpg', '.jpeg', '.png', '.gif']
      const magicMatch = {
        '.jpg': 'ffd8ffe0',
        '.jpeg': 'ffd8ffe0',
        '.png': '89504e47',
        '.gif': '47494638',
        '.webp': '52494646'
      }

      // 檔案是否需要檢查
      if (needsCheckFormats.includes(ext)) {
        const magic = file.buffer.toString('hex', 0, 4)

        if (magic && magic === magicMatch[ext]) {
          // 檢查完畢,符合格式,存起來
          fs.appendFileSync(newpath, file.buffer)
        } else {
          // 檢查完畢,不符合格式,在 file 物件中做紀錄
          file.err = 'File format does not match file ext'
        }
      } else {
        // 不需檢查的svg直接存
        fs.appendFileSync(newpath, file.buffer)
      }
    })
  }

  console.log(req.files)
  res.send('OK')
})

app.listen(port, () => {
  console.log(`Server for testing image upload is listening on port ${port}`)
})

以上是最常用的圖片上傳的判斷,其他還有很多魔術數字,下面列出一些常用的。其他可以參考 https://gist.github.com/leommoore/f9e57ba2aa4bf197ebc5 或 https://asecuritysite.com/forensics/magic

副檔名魔術數字
.gif47 49 46 38
.jpg / .jpegff d8 ff e0
.png89 50 4e 47
.tif / .tiff4d 4d 00 2a / 49 49 2a 00
.zip50 4b 03 04
.pdf25 50 44 46
.mp400 00 00 18 66 74 79 70 6D 70 34 32
.mp349 44 33
.docD0 CF 11 E0 A1 B1 1A E1
.xlsD0 CF 11 E0 A1 B1 1A E1
.psd38 42 50 53

用 color-mix() 處理CSS原生變數的顏色透明度

以往如果我們要在 css 中,對一個顏色變數做透明度處理,第一個想到的應該是透過 sass 的 color module 來處理。但如果我們是要在 runtime 處理一個 css 的原生變數呢?可能會有人想到把 rgb 拆成三個變數,再透過 rgba(var(–r), var(–g), var(–b), 0.5) 這種形式來改變透明度,相同的原理也能套用到 hsla 等等的 css 色彩函式中。

拆開變數的這個做法雖然可行,且在應用上可以很靈活,但同時也增加了變數的數量、造成閱讀與維護時的難度。

這時候不妨試試看 color-mix 函數來對顏色做透明度處理,且除了透明度以外,color-mix 其實本來是用來混合兩個顏色用的。且目前看起來各瀏覽器已經都普遍支援了(除了IE,但現在還有誰管它),請放心使用。

目前已經普遍支援各瀏覽器
閱讀全文 用 color-mix() 處理CSS原生變數的顏色透明度

用 View Transitions API 快速做出有趣的換頁動態吧

如果有在做 Native-like 的 Web Application 的人,應該都曾為了要做到像是 Native 的頁面轉換動態而絞盡腦汁。現在 View Transitions API 提供一種輕鬆創建不同 DOM 狀態之間的動畫轉換的機制。該 API 目前只能用於單一頁面中的 DOM 狀態轉換,但正在計劃支援於同 Domain 的不同頁面間的轉換。目前(2023/09)雖然僅有 Chrome, Edge, Opera 三個瀏覽器支援,但相信未來勢必會成為做動態時很重要的一環。

目前僅Chrome, Edge, Opera 三個瀏覽器支援
閱讀全文 用 View Transitions API 快速做出有趣的換頁動態吧

自動更新 Linode Object Storage SSL/TLS憑證

前一篇文章,我們了解了如何自訂Linode Object Storage的網域,但因Object Storage需要自己手動上傳SSL憑證,且Let’s Encrypt的憑證90天會到期,因此每到即將過期的時間,勢必就得再手動更新一次。因此,這篇就要繼續來了解,如何自動更新憑證。

閱讀全文 自動更新 Linode Object Storage SSL/TLS憑證

Linode Object Storage 自訂網域設定與大阪region Cyberduck設定

Custom Domain 步驟

開好 Linode 的 Object Storage 之後,常常覺得網址有夠長,查了一下自訂網域的方式。跟 AWS 比起來麻煩多了,還要自己用 Certbot 手動建立 ssl 憑證,而這也意味著會遇到無法自動更新憑證的問題。之後會再介紹用 API 更新憑證的方法,這篇的重點就先放在整個設定的步驟。

閱讀全文 Linode Object Storage 自訂網域設定與大阪region Cyberduck設定

[筆記] Critical Render Path 與 Reflow, Repaint

Critical Render Path

讀取 html => 產生 DOM Tree => 產生 CSSOM Tree => 產生 Render Tree => Layout => Paint => Compositing

Layout做的事情

依照樣式表與元素的可視與否,計算每個元素的尺寸、位置

Paint做的事情

繪製每個Layout的文字、顏色、圖片、透明度、transform。也就是把Layout轉為實際視覺上的結果。

Compositing做的事情

將所有的Layout組合起來

閱讀全文 [筆記] Critical Render Path 與 Reflow, Repaint

Nuxt Hydration Fails – SSR與CSR的比對錯誤

在 Nuxt 中,當我們 refresh 頁面時,寫在 data 或 computed 中的程式碼,在 server 端與 client 端都會各執行一次,並且在前端會進行html tag的比對,若兩者不相符會噴錯,甚至沒有辦法進行接下來的操作。

因此,當我們用了 new Date().getTime(), Math.random() 這類的程式碼在data中,是一定會造成錯誤的。甚至 new Date().getDay() 也是有可能,因為 server time 有很大的可能性,跟 client 端的時區不同。最後我們再來看看如何解決,我們先來看看相同的程式碼,在 Nuxt2 與 Nuxt3 分別會導出怎樣的結果。

閱讀全文 Nuxt Hydration Fails – SSR與CSR的比對錯誤

PM2 watch 造成的無限迴圈

pm2 的 watch 是用於監聽檔案變動,自動重啟服務的一個功能。我們可以透過在 pm2.config.js 檔案中設定 watch: true 來開啟此功能。這樣一旦進了新 code 就會自動重啟,不用手動 pm2 restart {id} 非常方便。

但也就是因為這麼方便,所以常常會忘了他的存在。

前陣子做了一個透過排程,自動到某處去下載json檔案回來存放,再提供一隻API來吐出篩選過的資料的小工具。在開發環境完成後,一樣就很順手的在正式環境用PM2跑起來。

閱讀全文 PM2 watch 造成的無限迴圈

在dockerfiles中使用aws ecr image

這篇文章主要是筆記如何在本機使用AWS ECR上已存在的image,以及所需要的驗證方式。

首先到AWS ECR選擇你的repo,進入映像列表後選擇一個映像,在內頁複製他的URI,像是:735675645655.dkr.ecr.ap-northeast-1.amazonaws.com/my_project:23f4b2a。打開你的Dockerfile,將FROM後面的映像名稱改成這個URI

閱讀全文 在dockerfiles中使用aws ecr image

自行修正Site Kit AdSense錯誤

我部落格的網址是用子網域,先前裝Site Kit的時候,已經驗證過AdSense。但最近不知道何時開始,卻出現以下的畫面,告訴你要完成設定。但不管怎麼點,卻都會顯示錯誤。

以下是出現錯誤的流程

下圖是點選Site Kit > Setting之後出現的畫面。在已連接的服務中,AdSense的區塊右邊顯示了一顆要你完成設定的按鈕 “Complete setup for AdSense”。

閱讀全文 自行修正Site Kit AdSense錯誤

瀏覽器bfcache

bfcache是“back/foward cache”的縮寫。當頁面「符合某些條件」的時候,當使用者按了瀏覽器的上下頁,瀏覽器可以快速回復剛剛瀏覽的頁面。且因為bfcache做cache的方式是對整個頁面做快照(包含了Javascript Heap(用來儲存變數、函式的地方)),因此會包含剛剛所做的改變,例如變更過的DOM內容、Javascript的執行結果等等。同時,因為不需要再次載入資源,所以速度超快。

因為Javascript也被緩存的關係,假設我們是用vue.js開發前端頁面,當離開頁面後再返回時,就勢必不會重新觸發fetch, data, asyncData, mounted, created…等等的Methods與Lifecycle Hooks。

閱讀全文 瀏覽器bfcache

用瀏覽器console批次移除Jenkins建置紀錄

某天公司Jenkins的容量爆了,而那天有權限進那台機器的同事剛好請假。唯一能做的只能從前端一筆一筆刪掉。但這樣刪,不知道要刪到民國幾年。於是看看他刪除是怎麼打的,然後用一個回圈批次把一個branch中的所有舊建置都刪掉。

沒權限進主機以外,懶得進主機,也可以用這個方式來刪除。原始碼如下:

閱讀全文 用瀏覽器console批次移除Jenkins建置紀錄

Line Bot 串接 OpenAI API

最近很紅的ChatGPT,如果也能在Line上面使用的話,是不是感覺更方便呢?我們就來串接ChatGPT的開發公司OpenAI提供的API到Line Bot上吧!

這個範例會用Express起一個web server,建立一個method POST且path為/webhook的route作為line的webhook,用來接收line傳來的events

首先你需要先到 Line developers console 點選「Create a new channel」建立一個新的Channel,類型選擇「Messaging API」。填寫完成送出後進入Channel設定頁,複製最下方的「Channel secret」。回到上方選擇「Messaging API」頁籤,複製最下方的「Channel access token」。

用NPM安裝 openai, @line/bot-sdk, express,並在檔案中 require

閱讀全文 Line Bot 串接 OpenAI API