import uvicorn import fastapi from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse from linebot import LineBotApi, WebhookHandler from linebot.models import ( MessageEvent, TextMessage, TextSendMessage, FollowEvent, TemplateSendMessage, ButtonsTemplate, URITemplateAction, ) import dataset import requests import json import qrcode # from PIL import Image # import base64, io from random import randrange import models import datetime as dt app = fastapi.FastAPI() app.add_middleware( CORSMiddleware, allow_origins=['*'], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # bot config line_bot_api = LineBotApi("SJT7VPT4RMQFLcS27jQBy3FcC24gtDrkcwJWZ5Xzqesr5T78LOKudHEJzt0k3b2S7n4KPwf27J7DVz2c8NQ4plSaaQylEeB1cYrfejaE/RPG/lCIQBYe4iBTzo26s4i2PcmT89837per/lTyvhVIKAdB04t89/1O/w1cDnyilFU=") handler = WebhookHandler("411ae3ef7e766739ed2c2c27b249d010") # callback event @app.post("/callback") async def callback(request: fastapi.Request): signature = request.headers['X-Line-Signature'] body = await request.body() handler.handle(body.decode('utf-8'), signature) return 'OK' # follow event @handler.add(FollowEvent) def handle_follow(event): # get user id when follow real_user_id = event.source.user_id # db connect and search db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4') table = db['users'] result1 = table.find_one(userid=real_user_id) # 都存在db的話 if result1: db.close() line_bot_api.reply_message( event.reply_token, TextSendMessage(text='很高興再見到您!')) # 建立全新使用者 else: # create user account api url = 'https://nft-api-staging.joyso.io/api/v1/accounts' headers = {'Authorization': 'Basic bmZ0OmMxOTEzOWMzYjM3YjdjZWU3ZmY3OTFiZGU3NzdjZWNl'} # setup for temp use (unique id) rand_num = str(randrange(99999)) user_id = event.source.user_id + rand_num data = 'uid=' + user_id r = requests.post(url=url, headers=headers, data=data) # extract the account address dict_str = json.loads(r.text) user_account = dict_str['account'] user_address = user_account['address'] # generate qr code from user id qr = qrcode.QRCode( version=1, box_size=10, border=5) qr.add_data(user_address) qr.make(fit=True) img_qr = qr.make_image(fill='black', back_color='white') filename = "/var/www/ArkCard-Linebot/ArkCard-web/qrcode/" + real_user_id + '.png' img_save = img_qr.save(filename) # add to db data = dict(userid=real_user_id, useraddress=user_address) table.insert(data) db.close() line_bot_api.reply_message( event.reply_token, TextSendMessage(text='歡迎加入好友')) # message handler @handler.add(MessageEvent, message=TextMessage) def message(event): if '我要發送' in event.message.text: button_template_message = ButtonsTemplate( title=' ', text='點擊並打開收藏的NFT,可以選擇想要發送的NFT給對方!', actions=[ URITemplateAction( label='打開發送頁', uri='https://ark.cards/collect.html?' + event.source.user_id),]) line_bot_api.reply_message( event.reply_token, TemplateSendMessage( alt_text="Receive", template=button_template_message)) elif '我要接收' in event.message.text: button_template_message = ButtonsTemplate( title=' ', text='點擊並打開接收頁面,即可分享接收地址給對方!', actions=[ URITemplateAction( label='打開接收頁', uri='https://ark.cards/qr-code.html?' + event.source.user_id),]) line_bot_api.reply_message( event.reply_token, TemplateSendMessage( alt_text="Receive", template=button_template_message)) elif 'NFT商店' in event.message.text: button_template_message = ButtonsTemplate( title=' ', text='點擊並打開NFT商品頁,就可以購買您所想要的NFT商品哦!', actions=[ URITemplateAction( label='打開NFT商品頁', uri='https://ark.cards/shop.html?' + event.source.user_id),]) line_bot_api.reply_message( event.reply_token, TemplateSendMessage( alt_text="Receive", template=button_template_message)) elif 'NFT收藏' in event.message.text: button_template_message = ButtonsTemplate( title=' ', text='點擊並打開收藏的NFT,可以查看收到的NFT!', actions=[ URITemplateAction( label='打開收藏頁', uri='https://ark.cards/collect.html?' + event.source.user_id), ]) line_bot_api.reply_message( event.reply_token, TemplateSendMessage( alt_text="Receive", template=button_template_message)) else: button_template_message = ButtonsTemplate( title=' ', text='更多的服務內容,歡迎請上我們的官網!', actions=[ URITemplateAction( label='ArkCard的官網', uri='https://ark.cards'),]) line_bot_api.reply_message( event.reply_token, TemplateSendMessage( alt_text="Receive", template=button_template_message)) # nft collection api @app.get("/collection/{userid}") def collection(userid): # 連到ownership表單去找,並回傳json # db connect db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4') table1 = db['ownership'] nft = {} i = 0 if not table1.find_one(userid=userid): db.close() return "error: user don't have any nft" else: results = db.query("SELECT nftid, amount FROM arkcard.ownership WHERE userid IN (SELECT userid FROM arkcard.ownership WHERE userid = '"+userid+"');") for item in results: nft[i] = item i += 1 db.close() return nft # receive handler @app.get("/receive/{userid}") def receive(userid): # 確定要傳送的對象,到user去找到它 # db connect db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4') table = db['users'] if not table.find_one(userid=userid): db.close() return "ERROR: User Not Found" else: result = table.find_one(userid=userid) db.close() return {"userid": result['userid'], "address": result['address']} # send handler @app.post("/send") async def receive(userModel : models.TransactionNft): # 從網頁上選定好商品,在選定好數量後輸入收方地址;將把該商品轉到收方,並存下交易記錄 # 從ownership找到該userid的擁有人,再把擁有人userid改為收方,並在trans留下記錄 # db connect db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4') table1 = db['users'] table2 = db['ownership'] table3 = db['transactions'] # find the userid from the selected nft fromuser = userModel.fromuser address = userModel.address result1 = table1.find_one(address=address) result2 = table1.find_one(userid=fromuser) # 例: # { # "fromuser": "a01", # "address": "0x4cd0ea8b1bdb5ab9249d96ccf3d8a0d3ada2bc76", # "dic": {"10":"2", "20":"3"} # } # 第一個為nftid, 第二個為amount,可以同時輸入多組nft給同一人 the_list = userModel.dic fr_token = "from token" _hash = "hash" # confirm if the user exist # 如果沒有這個接受者,回傳錯誤 if not result1: db.close() return {'msg': 'user not found'} # 確認發送方的nft夠不夠發放 else: for nftid, amount in the_list.items(): print("nft: "+nftid+",數量:"+ amount) # 直接針對取到的值去做確認數量和更新表單,最後再記錄 # 確認sender的總數 result3 = db.query('SELECT SUM(amount) AS total FROM arkcard.ownership WHERE userid="' + fromuser + '" AND nftid ="' + nftid + '";') # 把總數撈出來,存到total for row in result3: rows = row total = rows['total'] # 如果總數不夠,就報錯回傳 if total == None or total < int(amount): db.close() return {"messge: NFT not enough"} # 總數夠,在ownership刪去數量,並再新增ownership一筆 else: # 今天到這,需要找出怎麼扣除總數的方式,目前找到的總數,會更新到資料欄位,導致原本才100個的,會被更新成499個,因為全加到一個 # 找接收者的userid to_user = result1['userid'] old_address = result2['address'] print(total) new_amount = total - int(amount) # 更新原user總數 result4 = db.query('DELETE FROM arkcard.ownership WHERE userid="' + fromuser + '" AND nftid ="' + nftid + '";') result5 = db.query('UPDATE arkcard.ownership SET amount = amount - "' + amount + '" WHERE userid = "' + fromuser + '" AND nftid = "' + nftid + '" LIMIT 1;') new_data1 = dict(userid=fromuser, address=old_address, nftid=nftid, amount=new_amount) table2.insert(new_data1) # 增加新user數量 new_data2 = dict(userid=to_user, address=address, nftid=nftid, amount=int(amount)) table2.insert(new_data2) # 在transactions記錄from和to, 以及時間等等 now = dt.datetime.now() trans_data = dict(to_token=address, time=now, amount=int(amount), userid=fromuser, from_token=fr_token, hash=_hash) table3.insert(trans_data) db.close() return {"messge: NFT 夠"} # shop handler @app.get("/shop") def shop(): # 為了顯示正確的nft網頁和數量,直接顯示nft表出來 # db connect db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4') result1 = db.query('SELECT nftid, launched, amount, type, title, context FROM arkcard.nft WHERE launched="y" AND nftid NOT IN(SELECT nftid FROM arkcard.nft_to_event);') nfts = {} i = 0 for row in result1: nfts[i] = row i += 1 return nfts db.close() @app.post("/buy") async def buy(userModel : models.BuyNft): # db connect db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4') table1 = db['users'] table2 = db['ownership'] table3 = db['nft'] table4 = db['transactions'] shops = "shop" hash = "hash" # input the_list = userModel.dic address = userModel.address # 例: # { # "address": "d04", # "dic": {"1":"2","2":"3"} # } result1 = table1.find_one(address=address) # 找不到該使用者就回報錯誤 if not result1: db.close() return "該用戶不存在!如果有疑問,請洽網站的服務信箱!" else: for nftid, amount in the_list.items(): # 先user找到該使用者的userid留著備用 userid = result1['userid'] # 更新到ownership owner_data = [dict(userid=userid, address=address, nftid=nftid, amount=int(amount))] table2.insert_many(owner_data) # 更新nft數量 result3 = table3.find_one(nftid=nftid) nft_total = result3['amount'] total = int(nft_total) - int(amount) nft_data = dict(nftid=nftid, amount=int(total)) table3.update(nft_data, ['nftid']) # 最後再來處理transactions now = dt.datetime.now() trans_data = dict(to_token=address, time=now, amount=int(amount), userid=userid, from_token=shops, hash=hash) table4.insert(trans_data) db.close() return "您已購買成功!" @app.post("/event") async def nftdrops(userModel : models.NftDrops): # db connect db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4') table1 = db['nft_drop_list'] # input userid = userModel.userid email = userModel.email the_current_event = eventPeriod() if the_current_event == 0: return "目前沒有任何進行中的活動" else: # 再針對有上架的 event 填寫客戶到 nftdroplist now = dt.datetime.now() result1 = table1.find_one(userid=userid) if not result1: for key, item in the_current_event.items(): table1.insert(dict(eventid=item, userid=userid, email=email, time=now)) db.close() return "完成加入" else: db.close() return "該資料已存在" @app.post("/eventPeriod") def eventPeriod(): # db connect db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4') # 設定event始終期間,與上架與否 table3 = db['event'] now = dt.datetime.now() # 先去找 event 表,確認在期間之內 result = db.query("SELECT * FROM arkcard.event WHERE start < NOW() and NOW() < end and launched='yes';") rows = {} i = 0 for row in result: rows[i] = row['eventid'] i += 1 return rows db.close() if __name__ == '__main__': uvicorn.run("main:app", host="0.0.0.0", port=8228, reload=True,ssl_context=('/etc/letsencrypt/live/ark.cards/fullchain.pem', '/etc/letsencrypt/live/ark.cards/privkey.pem'))