Published:

Last Updated:

๋‚˜๋งŒ์˜ ๊ตฌ๋…์ž ์ „์šฉ ์ฝ˜ํ…์ธ  ๋ธ”๋กœ๊ทธ ๋งŒ๋“ค๊ธฐ

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ, ๊ตฌ๊ธ€ ์•ฑ์Šค ์Šคํฌ๋ฆฝํŠธ, ๊ตฌ๊ธ€์‹œํŠธ๋ฅผ ํ™œ์šฉํ•ด ๋น„์šฉ 0์›์œผ๋กœ ๊ตฌ๋…์ž ์ „์šฉ ์ฝ˜ํ…์ธ  ๋ธ”๋กœ๊ทธ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์„ ๋‹จ๊ณ„๋ณ„๋กœ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ธ”๋กœ๊ทธ๋ฅผ ์šด์˜ํ•˜๋‹ค ๋ณด๋ฉด ์ •๋ง ๊ณต๋“ค์—ฌ ์ž‘์„ฑํ•œ ์–‘์งˆ์˜ ์ •๋ณด๋ฅผ ์˜ค์ง ๋‚ด ์†Œ์ค‘ํ•œ '๊ตฌ๋…์ž'๋ถ„๋“ค์—๊ฒŒ๋งŒ ํŠน๋ณ„ํ•˜๊ฒŒ ์ œ๊ณตํ•˜๊ณ  ์‹ถ์„ ๋•Œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ „๋ฌธ์ ์ธ ์„œ๋ฒ„ ์ง€์‹์ด ์—†๊ฑฐ๋‚˜, ์œ ๋ฃŒ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๊ตฌ์ถ•ํ•˜๊ธฐ์—๋Š” ๋ถ€๋‹ด์Šค๋Ÿฌ์šด ๊ฒƒ์ด ํ˜„์‹ค์ž…๋‹ˆ๋‹ค.

๋ฐ์ผ๋ฆฌํ—ˆ๋ธŒ์—์„œ๋Š” ์ด๋Ÿฌํ•œ ๊ณ ๋ฏผ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ(JavaScript), ๊ตฌ๊ธ€ ์•ฑ์Šค ์Šคํฌ๋ฆฝํŠธ(Google Apps Script, GAS), ๊ทธ๋ฆฌ๊ณ  ๊ตฌ๊ธ€ ์‹œํŠธ(Google Sheets)๋ฅผ ์กฐํ•ฉํ•œ ์ฝ˜ํ…์ธ  ์ž ๊ธˆ ๋ฐฉ๋ฒ•์„ ์ ์šฉํ•ด ๋ดค์Šต๋‹ˆ๋‹ค. ๋น„์šฉ์€ 0์›, ๋ณด์•ˆ์€ ์ฒ ์ €ํ•˜๊ฒŒ, ๊ตฌ๋…์ž ๊ด€๋ฆฌ์™€ ๋ถˆํ•„์š”ํ•œ ๋งคํฌ๋กœ ๋Œ“๊ธ€ ๋ถ„์„๊นŒ์ง€ ํ•ด๊ฒฐํ•ด ๋ธ”๋กœ๊ทธ๋ฅผ ์Šค๋งˆํŠธํ•˜๊ฒŒ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋Š” ํ•ฉ๋ฆฌ์ ์ธ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

1. ์™œ '๊ตฌ๊ธ€ ์‹œํŠธ'๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ์‚ฌ์šฉํ•˜๋‚˜์š”?

์ผ๋ฐ˜์ ์ธ ์›น ๊ฐœ๋ฐœ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋ ค๋ฉด MySQL, MongoDB ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ด๋ฅผ ๊ตฌ๋™ํ•  ์„œ๋ฒ„๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ‹ฐ์Šคํ† ๋ฆฌ๋‚˜ ๊ฐœ์ธ ๋ธ”๋กœ๊ทธ๋ฅผ ์šด์˜ํ•˜๋Š” ๋ถ„๋“ค์—๊ฒŒ๋Š” ๋ฐฐ๋ณด๋‹ค ๋ฐฐ๊ผฝ์ด ๋” ํฐ ์ƒํ™ฉ์ด ๋  ์ˆ˜ ์žˆ์ฃ .

  • ์™„์ „ ๋ฌด๋ฃŒ: ๊ตฌ๊ธ€ ๊ณ„์ •๋งŒ ์žˆ๋‹ค๋ฉด ๋ฌด์ƒ์œผ๋กœ ๊ฐ•๋ ฅํ•œ ํด๋ผ์šฐ๋“œ ์ €์žฅ์†Œ๋ฅผ ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  • ์ง๊ด€์ ์ธ UI: ๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ์Œ“์ด๋Š”์ง€ ์—‘์…€์ฒ˜๋Ÿผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธํ•˜๊ณ  ์ง์ ‘ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์„œ๋ฒ„๋ฆฌ์Šค ์ธํ”„๋ผ: Google Apps Script๊ฐ€ API ์„œ๋ฒ„ ์—ญํ• ์„ ๋Œ€์‹ ํ•ด์ฃผ๋ฏ€๋กœ, ๋ณ„๋„์˜ ํ˜ธ์ŠคํŒ… ๋น„์šฉ์ด ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

2. ์ž‘๋™ ์•„ํ‚คํ…์ฒ˜: 3๋‹จ๊ณ„ ๋ฐ์ดํ„ฐ ๊ณผ์ •

์‚ฌ์šฉ์ž๊ฐ€ ๋‚ด ๋ธ”๋กœ๊ทธ์— ์ ‘์†ํ•ด์„œ ๊ตฌ๋… ์ธ์ฆ์„ ์™„๋ฃŒํ•˜๊ธฐ๊นŒ์ง€, ์ž‘์—…๊ณผ์ •์€ ๋‹ค์Œ์˜ ์„ธ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์น˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

  1. ํ”„๋ก ํŠธ์—”๋“œ (JavaScript): ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋ธ”๋กœ๊ทธ URL์„ ์ˆ˜์ง‘ํ•ฉ๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €์˜ fetch() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ๊ธ€ ์„œ๋ฒ„(GAS)๋กœ ์˜์•„ ์˜ฌ๋ฆฝ๋‹ˆ๋‹ค.
  2. ๋ฏธ๋“ค์›จ์–ด (Google Apps Script): ๋ธŒ๋ผ์šฐ์ €์™€ ๊ตฌ๊ธ€ ์‹œํŠธ ์‚ฌ์ด์˜ '์ค‘๊ฐ„ ๊ด€๋ฆฌ์ž'์ž…๋‹ˆ๋‹ค. ๋ฐ›์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•œ์ง€ ๊ฒ€์‚ฌํ•˜๊ณ , ๋ณด์•ˆ ์ด์Šˆ์ธ CORS(Cross-Origin Resource Sharing) ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์—ฌ ์‹œํŠธ์— ์•ˆ์ „ํ•˜๊ฒŒ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
  3. ๋ฐฑ์—”๋“œ (Google Sheets): ์ „๋‹ฌ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ตœ์ข…์ ์œผ๋กœ ๊ธฐ๋ก๋˜๋Š” ์žฅ์†Œ์ž…๋‹ˆ๋‹ค. ๋“ฑ๋ก๋œ ๊ตฌ๋…์ž ๋ฆฌ์ŠคํŠธ๋ฅผ ํ™•์ธํ•˜๊ฑฐ๋‚˜ ์ƒˆ๋กœ์šด ์ ‘์† ๋กœ๊ทธ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

3. [1๋‹จ๊ณ„] ๊ตฌ๊ธ€ ์‹œํŠธ ์„ค์ • ๋ฐ ์„œ๋ฒ„ ์ฝ”๋“œ(GAS) ์ž‘์„ฑ

๋จผ์ € ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด์„ ๊ทธ๋ฆ‡์„ ์ค€๋น„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.๋ฌผ๋Ÿฐ ๊ตฌ๋…์ž ๋ฐ์ดํ„ฐ ์ž…๋‹ˆ๋‹ค.๊ตฌ๋…์ž ๋ฐ์ดํ„ฐ๋Š”์ด ๋งํฌ๋ฅผ ํ†ตํ•ด ์‰ฝ๊ฒŒ ์ž๋™์œผ๋กœ ์ˆ˜์ง‘ํ• ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ๊ตฌ๊ธ€ ์‹œํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ œ๋ชฉ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค (์˜ˆ: ๊ตฌ๋…์ž ์ธ์ฆ ๊ด€๋ฆฌ).
  2. ์ƒ๋‹จ ๋ฉ”๋‰ด์˜ ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ Apps Script๋ฅผ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.
  3. ๊ธฐ์กด ์ฝ”๋“œ๋ฅผ ๋ชจ๋‘ ์ง€์šฐ๊ณ  ์•„๋ž˜์˜ ์ฝ”๋“œ(1๋ฒˆ ์ฝ”๋“œ)๋ฅผ ๋ถ™์—ฌ๋„ฃ์Šต๋‹ˆ๋‹ค.

4. [2๋‹จ๊ณ„] ์›น ์•ฑ์œผ๋กœ ๋ฐฐํฌํ•˜๊ธฐ

์•„๋ž˜ ์ฝ”๋“œ(1๋ฒˆ)๋ฅผ ์ž‘์„ฑํ–ˆ์œผ๋ฉด, ์™ธ๋ถ€(๋ธŒ๋ผ์šฐ์ €)์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก URL์„ ์ƒ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  1. ์Šคํฌ๋ฆฝํŠธ ํŽธ์ง‘๊ธฐ ์šฐ์ธก ์ƒ๋‹จ [๋ฐฐํฌ] [์ƒˆ ๋ฐฐํฌ]๋ฅผ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.
  2. ์œ ํ˜• ์„ ํƒ์—์„œ [์›น ์•ฑ]์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.
  3. ์„ค์ •๊ฐ’ ํ™•์ธ:
    • ๋‹ค์Œ ์‚ฌ์šฉ์ž ๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰: ๋‚˜ (๋ณธ์ธ ๊ณ„์ •)
    • ์•ก์„ธ์Šค ๊ถŒํ•œ์ด ์žˆ๋Š” ์‚ฌ์šฉ์ž: ๋ชจ๋“  ์‚ฌ์šฉ์ž (Anyone) - ์ด ์„ค์ •์„ 'Anyone'์œผ๋กœ ํ•ด์•ผ ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์€ ๋ฐฉ๋ฌธ์ž๋„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  4. ๋ฐฐํฌ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ณ  ์ƒ์„ฑ๋œ ์›น ์•ฑ URL์„ ์•ˆ์ „ํ•œ ๊ณณ์— ๋ณต์‚ฌํ•ด ๋‘ก๋‹ˆ๋‹ค.

5. [3๋‹จ๊ณ„] ๋ธ”๋กœ๊ทธ์— ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ ์šฉ

์•„๋ž˜ ์ฝ”๋“œ(2๋ฒˆ)์„ ๋ธ”๋กœ๊ทธ ์Šคํ‚จ์˜ HTML์ด๋‚˜ ์ฝ˜ํ…์ธ  ์˜์—ญ์— ํŒ์—… ์ œ์–ด์™€ ๋ฐ์ดํ„ฐ ์ „์†ก ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

6. ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋งŒ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ ๋ ๊นŒ?

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ์ง์ ‘ ๊ตฌ๊ธ€ ์‹œํŠธ๋ฅผ ์ˆ˜์ •ํ•˜๋ ค๊ณ  ํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๋Š” ๋นจ๊ฐ„์ƒ‰ ์—๋Ÿฌ๋ฅผ ๋‚ด๋ฑ‰์Šต๋‹ˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” ๋ณด์•ˆ(Security) ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

โ‘  CORS(Cross-Origin Resource Sharing) ์ด์Šˆ

๋ธŒ๋ผ์šฐ์ €๋Š” abc.com์—์„œ ์‹คํ–‰ ์ค‘์ธ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ํ—ˆ๋ฝ ์—†์ด google.com์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฑด๋“œ๋ฆฌ๋Š” ๊ฒƒ์„ ๋ง‰์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ด๊ฒƒ์ด ํ—ˆ์šฉ๋œ๋‹ค๋ฉด, ์•…์„ฑ ์‚ฌ์ดํŠธ๊ฐ€ ์—ฌ๋Ÿฌ๋ถ„์˜ ๊ตฌ๊ธ€ ์‹œํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ๋งˆ์Œ๋Œ€๋กœ ํ›”์ณ๊ฐˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. GAS๋Š” ๊ตฌ๊ธ€ ์„œ๋ฒ„ ๋‚ด๋ถ€์—์„œ ์ž‘๋™ํ•˜๋ฏ€๋กœ ์ด ์ œ์•ฝ์„ ์šฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” ์•ˆ์ „ํ•œ ํ†ต๋กœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

โ‘ก ์ธ์ฆ๊ณผ ๊ถŒํ•œ(OAuth 2.0)

๊ตฌ๊ธ€ ์‹œํŠธ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋น„๊ณต๊ฐœ ์ƒํƒœ์ž…๋‹ˆ๋‹ค. ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ ์•ˆ์— ๊ตฌ๊ธ€ ๊ณ„์ •์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ ์–ด๋‘˜ ์ˆ˜๋Š” ์—†์ฃ . GAS๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด "๋‚ด ๊ณ„์ • ๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰ํ•˜๋˜, ๋ฐ์ดํ„ฐ ์ž…๊ตฌ๋งŒ ์™ธ๋ถ€์— ์—ด์–ด๋‘”๋‹ค"๋Š” ์•ˆ์ „ํ•œ ๋ฐฉ์‹์˜ ๊ถŒํ•œ ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.

โ‘ข ๋ฐ์ดํ„ฐ ํฌ๋งท์˜ ๋ฒˆ์—ญ

๊ตฌ๊ธ€ ์‹œํŠธ๋Š” 'ํ‘œ' ํ˜•ํƒœ์˜ ๋ฐ์ดํ„ฐ์ด๊ณ , ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋Š” JSON ํ˜•ํƒœ๋ฅผ ์ข‹์•„ํ•ฉ๋‹ˆ๋‹ค. GAS๋Š” ํ‘œ ๋ฐ์ดํ„ฐ๋ฅผ JSON์œผ๋กœ, ํ˜น์€ JSON์„ ํ‘œ๋กœ ๋ณ€ํ™˜ํ•ด ์ฃผ๋Š” ํ†ต์—ญ์‚ฌ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

7. ์š”์•ฝ ๋ฐ ์ถ”์ฒœ ์ „๋žต

๋ธ”๋กœ๊ทธ๋‚˜ ์‚ฌ์ดํŠธ์˜ ํ”„๋กœ์ ํŠธ ์„ฑ๊ฒฉ์— ๋”ฐ๋ผ ์ตœ์ ํ™”๋œ ์กฐํ•ฉ์„ ์„ ํƒํ• ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชฉ์  ์ถ”์ฒœ ์กฐํ•ฉ ๋‚œ์ด๋„
๋‹จ์ˆœ ๋ฐ์ดํ„ฐ ์ถœ๋ ฅ ์‹œํŠธ ์›น ๊ฒŒ์‹œ + fetch() ํ•˜(Low)
๊ตฌ๋… ์ธ์ฆ ๋ฐ ์ €์žฅ JS + GAS + ๊ตฌ๊ธ€ ์‹œํŠธ ์ค‘(Mid)
๋Œ€๊ทœ๋ชจ ์„œ๋น„์Šค JS + Firebase / Supabase ์ƒ(High)

๊ตฌ๋…์ž ์ „์šฉ ๋ธ”๋กœ๊ทธ ์šด์˜ ๋ฐฉ๋ฒ• ์†Œ๊ฐœ

๊ตฌ๊ธ€ ์‹œํŠธ์™€ GAS๋ฅผ ํ™œ์šฉํ•œ ์‹œ์Šคํ…œ์€ ๋ธ”๋กœ๊ทธ ์šด์˜์ž์—๊ฒŒ ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ๋ณ„๋„์˜ ์„œ๋ฒ„ ์œ ์ง€๋น„ ์—†์ด๋„ ์ „๋ฌธ์ ์ธ ํšŒ์›์ œ ์‹œ์Šคํ…œ๊ณผ ์œ ์‚ฌํ•œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋‹ค๋งŒ ํ‹ฐ์Šคํ† ๋ฆฌ ๋ธ”๋กœ๊ทธ์—์„œ๋Š” ์—ฌ๋Ÿฌ ๋ฒ„์ „์˜ ๋ธ”๋กœ๊ทธ๊ฐ€ ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋ถ€ ์ œ์•ฝ์ด ์žˆ๊ธฐ๋Š” ํ•ฉ๋‹ˆ๋‹ค.

๋ฐ์ผ๋ฆฌ ํ—ˆ๋ธŒ์— ์ ์šฉํ•œ ์ฝ”๋“œ ์˜ˆ์ œ

์•„๋ž˜ ์ฝ”๋“œ๋Š” ๋ฐ์ผ๋ฆฌํ—ˆ๋ธŒ์—์„œ ์ง์ ‘ ์„ค๊ณ„ ํ…Œ์ŠคํŠธํ•œ ๊ตฌ๋…์ž ์ „์šฉ ์ฝ˜ํ…์ธ  ์šด์˜ ์‚ฌ๋ก€๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

[์ฝ”๋“œ1] ๊ตฌ๊ธ€ ์•ฑ์Šค ์Šคํฌ๋ฆฝํŠธ(Google Apps Script, GAS)

์•„๋ž˜ ์ฝ”๋“œ์—์„œ ๊ตฌ๊ธ€ ์‹œํŠธ ์ฃผ์†Œ์™€ ๊ตฌ๋…์ž ๋ฐ์ดํ„ฐ ์‹œํŠธ ์ด๋ฆ„์„ ํ™˜๊ฒฝ์— ๋งž๊ฒŒ ์ˆ˜์ •ํ•˜์„ธ์š”

const SPREADSHEET_ID = '๊ตฌ๊ธ€์‹œํŠธ ์ฃผ์†Œ';
const SUBSCRIBERS_SHEET_NAME = '์˜ˆ:๋‚˜๋ฅผ๊ตฌ๋…';

function doGet(e) {
 const visitorLink = (e && e.parameter && e.parameter.visitorLink) ? e.parameter.visitorLink : null;
 let result = { access: false, message: '' };

 try {
 if (!visitorLink) {
 result.message = "Error: visitorLink parameter is missing.";
 } else {
 const spreadsheet = SpreadsheetApp.openById(SPREADSHEET_ID);
 let subscribersSheet = spreadsheet.getSheetByName(SUBSCRIBERS_SHEET_NAME);
 
 if (!subscribersSheet) {
 subscribersSheet = spreadsheet.insertSheet(SUBSCRIBERS_SHEET_NAME);
 subscribersSheet.appendRow(['๋งํฌ', '์ €์žฅ์ผ์‹œ']);
 }

 const lastRow = subscribersSheet.getLastRow();
 const subscriberLinks = lastRow 1 ? subscribersSheet.getRange(2, 1, lastRow - 1, 1).getValues().flat() : [];
 const normalizedVisitorLink = normalizeUrl(visitorLink);

 const isMySubscriber = subscriberLinks.some(subLink = {
 if (!subLink) return false;
 return normalizedVisitorLink === normalizeUrl(subLink);
 });

 result.access = isMySubscriber;
 result.message = isMySubscriber ? 'Access granted' : 'Access denied';
 result.success = true;
 }
 } catch (error) {
 result.access = false;
 result.message = "Error: " + error.message;
 }

 return ContentService.createTextOutput(JSON.stringify(result))
 .setMimeType(ContentService.MimeType.JSON);
}

function normalizeUrl(url) {
 if (!url) return '';
 let normalized = url.toLowerCase().trim();
 normalized = normalized.replace(/^(https?:\/\/)?(www\.)?/, '');
 if (normalized.endsWith('/')) {
 normalized = normalized.slice(0, -1);
 }
 return normalized;
}

[์ฝ”๋“œ2] ๋ธ”๋กœ๊ทธ์— ์ ์šฉํ•˜๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ

ํ˜„์žฌ ๋ฐ์ผ๋ฆฌํ—ˆ๋ธŒ ๋ธ”๋กœ๊ทธ์— ์ ์šฉํ•œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ์ž์‹ ์˜ ๋ธ”๋กœ๊ทธ์— ์–ด์šฐ๋ฆฌ๋Š” ํŒ์—…์ฐฝ CSS,์™€ ์•ˆ๋‚ด ๋ฌธ๊ตฌ๋ฅผ ์ ์šฉํ•˜๊ณ , ๊ตฌ๊ธ€ ์•ฑ์Šค ์Šคํฌ๋ฆฝํŠธ ์›น ์ฃผ์†Œ๋ฅผ ๋„ฃ์–ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

script
(function () {
 document.addEventListener('DOMContentLoaded', function () {
 const hasPreview = !!document.querySelector('.prime-preview');
 if (!hasPreview) return;

 /* ===============================
 ํŒ์—… ์ƒ์„ฑ
 =============================== */
 const popup = document.createElement('div');
 popup.id = 'access-popup';
 popup.style.cssText = `
 position: fixed;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 background: rgba(10,10,10,0.85);
 color: white;
 display: flex;
 flex-direction: column;
 justify-content: center;
 align-items: center;
 z-index: 99999;
 padding: 20px;
 text-align: center;
 overflow: hidden;
 backdrop-filter: blur(10px);
 `;

 popup.innerHTML = `
 div style="
 background: rgba(20,20,20,0.95);
 padding: 40px;
 border-radius: 20px;
 border: 1px solid #444;
 box-shadow: 0 20px 60px rgba(0,0,0,0.9);
 max-width: 420px;
 width: 95%;
 box-sizing: border-box;
 z-index: 1;
 "
 h2 style="
 font-size: 1.6rem;
 color: #27ae60;
 margin: 0 0 15px 0;
 font-weight: bold;
 " ๊ตฌ๋…์ž ์ „์šฉ ์ฝ˜ํ…์ธ /h2

 p style="
 font-size: 1rem;
 margin: 0 0 10px 0;
 line-height: 1.6;
 color: #eee;
 "
 ์ด ์ฝ˜ํ…์ธ ๋Š” ๋ณดํ˜ธ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.br
 ๊ตฌ๋… ์ค‘์ธ ๋ธ”๋กœ๊ทธ URL์„ ์ž…๋ ฅํ•˜์„ธ์š”.
 /p

 p style="
 font-size: 0.85rem;
 margin: 0 0 20px 0;
 color: #bbb;
 line-height: 1.5;
 "
 โ€ป ๊ตฌ๋…์ž์ž„์—๋„ ์ ‘์†์ด ๋ถˆ๊ฐ€ํ•  ๊ฒฝ์šฐ ๋˜๋Š” ๋น„๊ตฌ๋…์ž๋„,br
 ๋Œ“๊ธ€๋กœ span style="color:#ffcc00;font-weight:bold;"'์Šน์ธ ์š”์ฒญ'/span์„ ๋‚จ๊ฒจ์ฃผ์„ธ์š”.
 /p

 input
 type="text"
 id="visitor-url"
 placeholder="https://์•„์ด๋””.tistory.com"
 style="
 width: 100%;
 padding: 14px;
 margin-bottom: 15px;
 border-radius: 8px;
 border: none;
 outline: none;
 text-align: center;
 color: #000;
 font-size: 1rem;
 box-sizing: border-box;
 background: #fff;
 "
 

 div style="display:flex;flex-direction:column;gap:10px;"
 button
 id="check-access-btn"
 style="
 width: 100%;
 padding: 14px;
 font-size: 1.1rem;
 cursor: pointer;
 background: #087e3a;
 color: white;
 border: none;
 border-radius: 8px;
 font-weight: bold;
 transition: 0.3s;
 "
 ์ ‘๊ทผ ํ™•์ธ/button

 a
 href="ํ™ˆ.์ด๋™ํ•  ์ฃผ์†Œ"
 style="
 width: 100%;
 padding: 12px;
 font-size: 1rem;
 cursor: pointer;
 background: rgba(255,255,255,0.1);
 color: white;
 border: 1px solid rgba(255,255,255,0.3);
 border-radius: 8px;
 font-weight: bold;
 text-decoration: none;
 display: inline-block;
 box-sizing: border-box;
 transition: 0.3s;
 "
 ํ™ˆ์œผ๋กœ ์ด๋™/a
 /div

 p
 id="access-message"
 style="
 margin-top: 20px;
 font-size: 0.95rem;
 min-height: 24px;
 font-weight: bold;
 "
 /p
 /div
 `;

 document.body.appendChild(popup);
 document.body.style.overflow = 'hidden';

 /* ===============================
 ์‚ฌ์šฉ์ž ํ–‰๋™ ์ œํ•œ
 =============================== */
 const preventActions = (e) = {
 if (e.target.id === 'visitor-url') return;
 e.preventDefault();
 };

 document.addEventListener('contextmenu', preventActions);
 document.addEventListener('selectstart', preventActions);
 document.addEventListener('dragstart', preventActions);

 /* ===============================
 ์ ‘๊ทผ ํ™•์ธ ๋กœ์ง
 =============================== */
 const btn = document.getElementById('check-access-btn');
 const input = document.getElementById('visitor-url');
 const msg = document.getElementById('access-message');

 btn.onclick = async () = {
 const val = input.value.trim();
 if (!val) {
 msg.textContent = "URL์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.";
 return;
 }

 btn.disabled = true;
 btn.textContent = "ํ™•์ธ ์ค‘...";

 try {
 const url = `https://script.google.com/๊ตฌ๊ธ€GAS์ฃผ์†Œ๋กœ ๋ณ€๊ฒฝ?visitorLink=${encodeURIComponent(val)}`;
 const res = await fetch(url);
 const data = await res.json();

 if (data.access) {
 msg.textContent = " ํ™•์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!";
 msg.style.color = "#00ff00";

 document.removeEventListener('contextmenu', preventActions);
 document.removeEventListener('selectstart', preventActions);
 document.removeEventListener('dragstart', preventActions);

 setTimeout(() = {
 popup.remove();
 document.body.style.overflow = 'auto';
 }, 800);
 } else {
 msg.textContent = "โŒ ๊ตฌ๋… ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.";
 msg.style.color = "#ff4444";
 btn.disabled = false;
 btn.textContent = "์ ‘๊ทผ ํ™•์ธ";
 }
 } catch (e) {
 msg.textContent = "โš ์—ฐ๊ฒฐ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.";
 btn.disabled = false;
 btn.textContent = "์ ‘๊ทผ ํ™•์ธ";
 }
 };

 input.onkeypress = (e) = {
 if (e.key === 'Enter') btn.click();
 };
 });
})();
/script

[์ฝ”๋“œ 3] ๊ตฌ๋…์ž ์ „์šฉ ๋ธ”๋กœ๊ทธ ๊ธ€์ž‘์„ฑ์‹œ ์ ์šฉํ•˜๋Š” ์ฝ”๋“œ

๊ตฌ๋…์žฆ ์ „์šฉ ๋ธ”๋กœ๊ทธ ๊ธ€์„ ์ž‘์„ฑํ•  ๋•Œ ๊ธ€ ์ƒ๋‹จ ๋˜๋Š” ํ•˜๋‹จ์— ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์‚ฝ์ž…ํ•˜๋ฉด, ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๊ฐ€ ํ•ด๋‹น ์ฝ”๋“œ๊ฐ€ ํฌํ•จ๋œ ๊ธ€๋งŒ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜์—ฌ ๊ตฌ๋…์ž ์ „์šฉ ์ฝ˜ํ…์ธ ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ  ์ž ๊ธˆ ํŒ์—… ์ฐฝ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

div class="prime-preview"/div