66
77set -euo pipefail
88
9- VERSION=" 1.0 .0"
9+ VERSION=" 1.1 .0"
1010BASE_URL=" ${C0UPONS_API:- https:// c0upons.com/ api} "
11+ # Website root (for login + share links). Defaults to BASE_URL without /api.
12+ WEB_URL=" ${C0UPONS_WEB:- ${BASE_URL%/ api} } "
13+
14+ CONFIG_DIR=" ${XDG_CONFIG_HOME:- $HOME / .config} /c0upons"
15+ TOKEN_FILE=" ${CONFIG_DIR} /token"
1116
1217BOLD=" \033[1m"
1318DIM=" \033[2m"
@@ -27,16 +32,20 @@ usage() {
2732 echo " latest Show latest/trending coupons"
2833 echo " stores List all stores"
2934 echo " store <slug> Show coupons for a store"
35+ echo " login Connect your CoinPay account"
36+ echo " logout Forget saved credentials"
37+ echo " submit Submit a coupon (requires login)"
38+ echo " bounty Post a coupon bounty (requires login)"
3039 echo " upgrade Upgrade to the latest version (alias: update)"
3140 echo " remove Uninstall the CLI (alias: uninstall)"
3241 echo " version Print version"
3342 echo " help Show this help"
3443 echo " "
3544 echo -e " ${BOLD} EXAMPLES${RESET} "
3645 echo " c0upons search nike"
37- echo " c0upons latest "
38- echo " c0upons stores "
39- echo " c0upons store adidas "
46+ echo " c0upons login "
47+ echo " c0upons submit --store Nike --title '20% off sitewide' --code SAVE20 --percent 20 --url https://nike.com "
48+ echo " c0upons bounty -- store Adidas --title 'Need a 30% off code' --reward 1.00 "
4049 echo " "
4150 echo -e " ${DIM} Override API: export C0UPONS_API=http://localhost:3000/api${RESET} "
4251 echo -e " ${DIM} Docs: https://c0upons.com/docs${RESET} "
@@ -52,6 +61,35 @@ require_jq() {
5261 fi
5362}
5463
64+ require_auth () {
65+ if [ ! -f " $TOKEN_FILE " ] || [ ! -s " $TOKEN_FILE " ]; then
66+ echo -e " ${RED} Not logged in.${RESET} Run: ${BOLD} c0upons login${RESET} "
67+ exit 1
68+ fi
69+ TOKEN=$( cat " $TOKEN_FILE " )
70+ }
71+
72+ # api_post <path> <json-payload> — sends an authenticated POST.
73+ # On success, leaves the response body in $API_BODY. On failure, prints the
74+ # error and exits.
75+ api_post () {
76+ local path=" $1 " payload=" $2 " resp code
77+ resp=$( curl -sS -w $' \n %{http_code}' -X POST " ${BASE_URL}${path} " \
78+ -H " Content-Type: application/json" \
79+ -H " Authorization: Bearer ${TOKEN} " \
80+ -d " $payload " ) || { echo -e " ${RED} Network error.${RESET} Could not reach ${BASE_URL} ." ; exit 1; }
81+ code=$( printf ' %s' " $resp " | tail -n1)
82+ API_BODY=$( printf ' %s' " $resp " | sed ' $d' )
83+ if [ " $code " -ge 200 ] && [ " $code " -lt 300 ]; then
84+ return 0
85+ fi
86+ local msg
87+ msg=$( printf ' %s' " $API_BODY " | jq -r ' .error // empty' 2> /dev/null || true)
88+ echo -e " ${RED} Error (${code} ):${RESET} ${msg:- request failed} "
89+ [ " $code " = " 401" ] && echo -e " Run: ${BOLD} c0upons login${RESET} "
90+ exit 1
91+ }
92+
5593urlencode () {
5694 local raw=" $1 "
5795 if command -v python3 & > /dev/null; then
@@ -156,6 +194,126 @@ cmd_store() {
156194 done <<< " $coupons"
157195}
158196
197+ cmd_login () {
198+ require_jq
199+ echo -e " To connect the CLI, open this URL and sign in with CoinPay:"
200+ echo " "
201+ echo -e " ${BOLD}${WEB_URL} /cli-auth${RESET} "
202+ echo " "
203+ printf " Paste the code here: "
204+ local token
205+ read -r token
206+ token=$( printf ' %s' " $token " | tr -d ' [:space:]' )
207+ if [ -z " $token " ]; then
208+ echo -e " ${RED} No code entered.${RESET} "
209+ exit 1
210+ fi
211+ local did
212+ did=$( curl -fsSL -H " Authorization: Bearer ${token} " " ${BASE_URL} /auth/me" 2> /dev/null | jq -r ' .did // empty' ) || true
213+ if [ -z " $did " ]; then
214+ echo -e " ${RED} That code didn't work.${RESET} Make sure you copied the whole thing."
215+ exit 1
216+ fi
217+ mkdir -p " $CONFIG_DIR "
218+ printf ' %s' " $token " > " $TOKEN_FILE "
219+ chmod 600 " $TOKEN_FILE "
220+ echo -e " ${GREEN} ✓${RESET} Logged in as ${BOLD}${did}${RESET} "
221+ }
222+
223+ cmd_logout () {
224+ if [ -f " $TOKEN_FILE " ]; then
225+ rm -f " $TOKEN_FILE "
226+ echo -e " ${GREEN} ✓${RESET} Logged out."
227+ else
228+ echo " Not logged in."
229+ fi
230+ }
231+
232+ cmd_submit () {
233+ require_jq
234+ require_auth
235+ local title=" " store=" " code=" " url=" " website=" " description=" " expiry=" " dtype=" " dvalue=" "
236+ while [ $# -gt 0 ]; do
237+ case " $1 " in
238+ --title) title=" ${2:- } " ; shift 2 ;;
239+ --store) store=" ${2:- } " ; shift 2 ;;
240+ --code) code=" ${2:- } " ; shift 2 ;;
241+ --url) url=" ${2:- } " ; shift 2 ;;
242+ --website) website=" ${2:- } " ; shift 2 ;;
243+ --description|--desc) description=" ${2:- } " ; shift 2 ;;
244+ --expiry) expiry=" ${2:- } " ; shift 2 ;;
245+ --percent) dtype=" percent" ; dvalue=" ${2:- } " ; shift 2 ;;
246+ --off) dtype=" fixed" ; dvalue=" ${2:- } " ; shift 2 ;;
247+ * ) echo -e " ${RED} Unknown option:${RESET} $1 " ; exit 1 ;;
248+ esac
249+ done
250+ if [ -z " $title " ]; then
251+ echo " Usage: c0upons submit --title <title> [--store <name>] [--code <code>]"
252+ echo " [--percent <n> | --off <n>] [--url <url>]"
253+ echo " [--description <d>] [--expiry YYYY-MM-DD]"
254+ exit 1
255+ fi
256+ local payload
257+ payload=$( jq -n \
258+ --arg title " $title " --arg store " $store " --arg code " $code " --arg url " $url " \
259+ --arg website " $website " --arg description " $description " --arg expiry " $expiry " \
260+ --arg dtype " $dtype " --arg dvalue " $dvalue " \
261+ ' {
262+ title: $title,
263+ store_name: (if $store == "" then null else $store end),
264+ code: (if $code == "" then null else $code end),
265+ url: (if $url == "" then null else $url end),
266+ store_website: (if $website == "" then null else $website end),
267+ description: (if $description == "" then null else $description end),
268+ expiry_date: (if $expiry == "" then null else $expiry end),
269+ discount_type: (if $dtype == "" then null else $dtype end),
270+ discount_value: (if $dvalue == "" then null else $dvalue end)
271+ }' )
272+ api_post " /coupons" " $payload "
273+ echo -e " ${GREEN} ✓${RESET} Coupon submitted!"
274+ }
275+
276+ cmd_bounty () {
277+ require_jq
278+ require_auth
279+ local title=" " store=" " reward=" " url=" " description=" "
280+ while [ $# -gt 0 ]; do
281+ case " $1 " in
282+ --title) title=" ${2:- } " ; shift 2 ;;
283+ --store) store=" ${2:- } " ; shift 2 ;;
284+ --reward) reward=" ${2:- } " ; shift 2 ;;
285+ --url) url=" ${2:- } " ; shift 2 ;;
286+ --description|--desc) description=" ${2:- } " ; shift 2 ;;
287+ * ) echo -e " ${RED} Unknown option:${RESET} $1 " ; exit 1 ;;
288+ esac
289+ done
290+ if [ -z " $title " ] || [ -z " $store " ] || [ -z " $reward " ]; then
291+ echo " Usage: c0upons bounty --title <title> --store <name> --reward <usd>"
292+ echo " [--url <url>] [--description <d>]"
293+ exit 1
294+ fi
295+ local payload
296+ payload=$( jq -n \
297+ --arg title " $title " --arg store " $store " --arg reward " $reward " \
298+ --arg url " $url " --arg description " $description " \
299+ ' {
300+ title: $title,
301+ store_name: $store,
302+ reward_usd: $reward,
303+ url: (if $url == "" then null else $url end),
304+ description: (if $description == "" then null else $description end)
305+ }' )
306+ api_post " /bounties" " $payload "
307+ local pubid pay_url
308+ pubid=$( printf ' %s' " $API_BODY " | jq -r ' .public_id // .id // empty' )
309+ pay_url=$( printf ' %s' " $API_BODY " | jq -r ' .pay_url // empty' )
310+ echo -e " ${GREEN} ✓${RESET} Bounty posted!"
311+ [ -n " $pubid " ] && echo -e " ${DIM}${WEB_URL} /bounties/${pubid}${RESET} "
312+ if [ -n " $pay_url " ]; then
313+ echo -e " Fund it to make it live: ${BOLD}${pay_url}${RESET} "
314+ fi
315+ }
316+
159317cmd_upgrade () {
160318 local self
161319 self=" $( command -v c0upons 2> /dev/null || echo " " ) "
@@ -206,6 +364,10 @@ case "${1:-help}" in
206364 latest) cmd_latest ;;
207365 stores) cmd_stores ;;
208366 store) shift ; cmd_store " $@ " ;;
367+ login) cmd_login ;;
368+ logout) cmd_logout ;;
369+ submit) shift ; cmd_submit " $@ " ;;
370+ bounty) shift ; cmd_bounty " $@ " ;;
209371 upgrade|update) cmd_upgrade ;;
210372 remove|uninstall) cmd_remove ;;
211373 version|--version|-v) echo " c0upons v${VERSION} " ;;
0 commit comments