line.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. import uuid
  2. import fastapi
  3. import pymysql
  4. import base64
  5. pymysql.install_as_MySQLdb()
  6. from linebot.models import (
  7. MessageEvent, TextMessage, TextSendMessage, FollowEvent,
  8. TemplateSendMessage, ButtonsTemplate, URITemplateAction,
  9. )
  10. from linebot.exceptions import LineBotApiError
  11. import dataset
  12. import requests
  13. import json
  14. import qrcode
  15. from fastapi.encoders import jsonable_encoder
  16. from random import randrange
  17. from app.schemas import line
  18. from app.core.config import settings
  19. import datetime as dt
  20. from fastapi import APIRouter, FastAPI, Request, Response, Body,Depends
  21. from fastapi.routing import APIRoute
  22. from starlette.types import Message
  23. from app.api import deps
  24. from app import crud, models, schemas
  25. from typing import Callable, List
  26. from uuid import uuid4
  27. from sqlalchemy.orm import Session
  28. from datetime import datetime
  29. import httpx
  30. import pymysql
  31. pymysql.install_as_MySQLdb()
  32. async def set_body(request: Request, body: bytes):
  33. async def receive() -> Message:
  34. return {"type": "http.request", "body": body}
  35. request._receive = receive
  36. async def get_body(request: Request) -> bytes:
  37. body = await request.body()
  38. await set_body(request, body)
  39. return body
  40. async def request_get(url, headers):
  41. async with httpx.AsyncClient() as client:
  42. return await client.get(url, headers = headers)
  43. class LineRouter(APIRoute):
  44. def get_route_handler(self) -> Callable:
  45. original_route_handler = super().get_route_handler()
  46. async def custom_route_handler(request: Request) -> Response:
  47. db = dataset.connect(
  48. 'mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4'
  49. )
  50. log_table = db['log']
  51. try:
  52. userid = int(json.loads(base64.b64decode(request.headers['authorization'][7:].split('.')[1]+ '=' * (-len(request.headers['authorization'][7:].split('.')[1]) % 4)).decode('utf-8'))['sub'])
  53. except:
  54. if( "/api/v1/line/collection/" or "/api/v1/line/receive/" or "/api/v1/line/shop/" or "/api/v1/line/send" in request.url.path):
  55. lineid = request.url.path.split("/")[-1]
  56. sql = 'SELECT id, userid FROM users where userid =\'' + lineid + '\''
  57. try:
  58. userid = int(db.query(sql).next()['id'])
  59. except:
  60. userid = -1
  61. await set_body(request, await request.body())
  62. body = await get_body(request)
  63. body = str(body).replace("\"","\'")
  64. body = str(body).split('Content-Type: image/')[0]
  65. ip = request.client.host
  66. port = str(request.client.port)
  67. query = request.url.query
  68. headers = str(request.headers).replace("\"","\'")
  69. method = request.method
  70. response: Response = await original_route_handler(request)
  71. response_body = str(response.body).replace("\"","\'")
  72. log_table.insert(
  73. dict(
  74. create_time=str(datetime.now()), url_path=request.url.path,
  75. headers=headers, request=body, userid=userid,
  76. ip=ip+":"+port, method=method, response=response_body,
  77. request_query=query
  78. )
  79. )
  80. return response
  81. return custom_route_handler
  82. router = APIRouter(route_class=LineRouter)
  83. baseUrl = "https://nft-api-staging.joyso.io/api/v1/"
  84. headers = {
  85. 'Authorization':
  86. 'Basic %s' % 'bmZ0OmMxOTEzOWMzYjM3YjdjZWU3ZmY3OTFiZGU3NzdjZWNl'
  87. }
  88. def check_account(event):
  89. real_user_id = event.source.user_id
  90. db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4')
  91. table = db['users']
  92. result1 = table.find_one(userid=real_user_id)
  93. # 都存在db的話
  94. if result1:
  95. print('already in db')
  96. db.close()
  97. settings.line_bot_api.reply_message(
  98. event.reply_token,
  99. TextSendMessage(text='很高興再見到您!'))
  100. # 建立全新使用者
  101. else:
  102. # create user account api
  103. url = 'https://nft-api-staging.joyso.io/api/v1/accounts'
  104. headers = {
  105. 'Authorization':
  106. 'Basic bmZ0OmMxOTEzOWMzYjM3YjdjZWU3ZmY3OTFiZGU3NzdjZWNl'
  107. }
  108. try:
  109. profile = settings.line_bot_api.get_profile(real_user_id)
  110. #print(profile['displayName'])
  111. except LineBotApiError as e:
  112. pass
  113. user_id = event.source.user_id
  114. data = 'uid=' + user_id
  115. r = requests.post(url=url, headers=headers, data=data)
  116. try:
  117. dict_str = json.loads(r.text)
  118. user_account = dict_str['account']
  119. user_address = user_account['address']
  120. except:
  121. get_acc_url = 'https://nft-api-staging.joyso.io/api/v1/accounts/'+user_id
  122. r = requests.get(url=get_acc_url,headers=headers)
  123. dict_str = json.loads(r.text)
  124. user_account = dict_str['account']
  125. user_address = user_account['address']
  126. # generate qr code from user id
  127. qr = qrcode.QRCode(
  128. version=1,
  129. box_size=10,
  130. border=5)
  131. qr.add_data(user_address)
  132. qr.make(fit=True)
  133. img_qr = qr.make_image(fill='black', back_color='white')
  134. filename = "/var/www/ArkCard-Linebot/ArkCard-web/qrcode/" + \
  135. real_user_id + '.png'
  136. img_qr.save(filename)
  137. # add to db
  138. data = dict(userid=real_user_id, useraddress=user_address,is_active=1,created_at=dt.datetime.now(),updated_at=dt.datetime.now(),hashed_password='',email='',account='',is_superuser=0)
  139. table.insert(data)
  140. db.close()
  141. settings.line_bot_api.reply_message(
  142. event.reply_token,
  143. TextSendMessage(text='歡迎加入好友'))
  144. # callback event
  145. @router.post("/callback")
  146. async def callback(request: fastapi.Request):
  147. signature = request.headers['X-Line-Signature']
  148. body = await request.body()
  149. settings.handler.handle(body.decode('utf-8'), signature)
  150. return 'OK'
  151. # follow event
  152. @settings.handler.add(FollowEvent)
  153. def handle_follow(event):
  154. # get user id when follow
  155. check_account(event)
  156. # message handler
  157. @settings.handler.add(MessageEvent, message=TextMessage)
  158. def message(event):
  159. if '我要發送' in event.message.text:
  160. button_template_message = ButtonsTemplate(
  161. title=' ',
  162. text='點擊並打開收藏的NFT,可以選擇想要發送的NFT給對方!',
  163. actions=[
  164. URITemplateAction(
  165. label='打開發送頁',
  166. uri='https://ark.cards/collect.html?'
  167. + event.source.user_id)])
  168. settings.line_bot_api.reply_message(
  169. event.reply_token,
  170. TemplateSendMessage(
  171. alt_text="Receive",
  172. template=button_template_message))
  173. elif '我要接收' in event.message.text:
  174. check_account(event)
  175. button_template_message = ButtonsTemplate(
  176. title=' ',
  177. text='點擊並打開接收頁面,即可分享接收地址給對方!',
  178. actions=[
  179. URITemplateAction(
  180. label='打開接收頁',
  181. uri='https://ark.cards/qr-code.html?' +
  182. event.source.user_id)])
  183. settings.line_bot_api.reply_message(
  184. event.reply_token,
  185. TemplateSendMessage(
  186. alt_text="Receive",
  187. template=button_template_message))
  188. elif 'NFT商店' in event.message.text:
  189. button_template_message = ButtonsTemplate(
  190. title=' ',
  191. text='點擊並打開NFT商品頁,就可以購買您所想要的NFT商品哦!',
  192. actions=[
  193. URITemplateAction(
  194. label='打開NFT商品頁',
  195. uri='https://ark.cards/shop.html?' +
  196. event.source.user_id)])
  197. settings.line_bot_api.reply_message(
  198. event.reply_token,
  199. TemplateSendMessage(
  200. alt_text="Receive",
  201. template=button_template_message))
  202. elif 'NFT收藏' in event.message.text:
  203. button_template_message = ButtonsTemplate(
  204. title=' ',
  205. text='點擊並打開收藏的NFT,可以查看收到的NFT!',
  206. actions=[
  207. URITemplateAction(
  208. label='打開收藏頁',
  209. uri='https://ark.cards/collect.html?' +
  210. event.source.user_id), ])
  211. settings.line_bot_api.reply_message(
  212. event.reply_token,
  213. TemplateSendMessage(
  214. alt_text="Receive",
  215. template=button_template_message))
  216. else:
  217. button_template_message = ButtonsTemplate(
  218. title=' ',
  219. text='更多的服務內容,歡迎請上我們的官網!',
  220. actions=[
  221. URITemplateAction(
  222. label='ArkCard的官網',
  223. uri='https://ark.cards')])
  224. settings.line_bot_api.reply_message(
  225. event.reply_token,
  226. TemplateSendMessage(
  227. alt_text="Receive",
  228. template=button_template_message))
  229. @router.post("/push/")
  230. def push_text(user, message):
  231. settings.line_bot_api.push_message(
  232. user, TextSendMessage(text=message)
  233. )
  234. # nft collection api
  235. @router.get("/collection/{userid}")
  236. async def collection(userid, db: Session = Depends(deps.get_db)):
  237. # db connect
  238. url = 'accounts/' + str(userid) + '/nft_balances'
  239. r = await request_get(baseUrl + url, headers)
  240. nft_all = {}
  241. outcome = r.json()['nft_balances']
  242. ii = 0
  243. for i in range(len(outcome)):
  244. nft_ = crud.nft.get_by_uid(db, uid=outcome[i]['uid'])
  245. if nft_:
  246. nft_all[ii] = jsonable_encoder(nft_)
  247. nft_all[ii]['amount'] = outcome[i]['amount']
  248. ii+=1
  249. return nft_all
  250. db = dataset.connect(
  251. 'mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4'
  252. )
  253. table3 = db['nftdrops']
  254. table2 = db['nft']
  255. nftdrops = {}
  256. nft = {}
  257. nfts_all = {}
  258. i = 0
  259. j = 0
  260. if not table3.find_one(userid=userid) and not table2.find_one(userid=userid):
  261. db.close()
  262. return "error: user don't have any nft"
  263. else:
  264. results1 = table3.find(userid=userid)
  265. for item in results1:
  266. nft_id = item['nftid']
  267. nftdrops[i] = table2.find_one(id=nft_id)
  268. i += 1
  269. results2 = table2.find(userid=userid)
  270. for item in results2:
  271. nft[j] = item
  272. j += 1
  273. nfts_all[0] = nftdrops
  274. nfts_all[1] = nft
  275. return nfts_all
  276. db.close()
  277. # receive handler
  278. @router.get("/receive/{userid}")
  279. def receive(userid):
  280. # db connect
  281. db = dataset.connect(
  282. 'mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4'
  283. )
  284. table = db['users']
  285. table.find_one(userid=userid)
  286. if not table.find_one(userid=userid):
  287. db.close()
  288. return "ERROR: User Not Found"
  289. else:
  290. result = table.find_one(userid=userid)
  291. return {
  292. "userid": result['userid'], "useraddress": result['useraddress']
  293. }
  294. db.close()
  295. # send handler
  296. @router.get("/send/{userid}")
  297. async def send(
  298. userid: str,
  299. to: str,
  300. nftuid: int,
  301. amount: int,
  302. db_: Session = Depends(deps.get_db),
  303. ):
  304. try:
  305. to_userid = crud.user.get_by_address(db_, address=to)
  306. except:
  307. to_userid = "外部"
  308. db = dataset.connect(
  309. 'mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4'
  310. )
  311. transaction_table = db['transaction']
  312. txid = str(uuid.uuid4())
  313. payload = {
  314. "txid": txid,
  315. # "contract": "0xe0d9102c88b09369df99b1c126fb2eebc13804f8",
  316. "contract": "0x8d5a7d8ca33fd9edca4b871cf5fb2a9cb5091505",
  317. "to": to,
  318. "uid": nftuid,
  319. "value": amount
  320. }
  321. url = "https://nft-api-staging.joyso.io/api/v1/accounts/Uba38e8903d243cd1bd15d5c27cc6653e/erc1155/safe_transfer_to"
  322. # r = requests.post(baseUrl + "accounts/" + userid+ "/erc1155/safe_transfer_to" , headers=headers, data=payload)
  323. r = requests.post(baseUrl + "accounts/" + userid+ "/erc1155/transfer_by_admin" , headers=headers, data=payload)
  324. # r = requests.post(url , headers=headers, data=payload)
  325. if r.status_code == 200:
  326. title = crud.nft.get_by_uid(db_, uid=nftuid).__dict__['title']
  327. message = "您的NFT : " + title + ", 已劃轉!"
  328. push_text(userid, message)
  329. transaction_table.insert({'txid':txid,'tfrom':userid,'to':to_userid,'nft':nftuid,'transaction_at':datetime.now()})
  330. from_name = settings.line_bot_api.get_profile(userid).as_json_dict()['displayName']
  331. try:
  332. to_name = settings.line_bot_api.get_profile(to_userid).as_json_dict()['displayName']
  333. except:
  334. to_name = "外部"
  335. fr = "您給"+ to_name +"的NFT(" + title + "), 已發送成功!"
  336. to_message = from_name +"給您的NFT("+title+"), 已收到!"
  337. push_text(userid, fr)
  338. try:
  339. push_text(to_userid, to_message)
  340. except:
  341. pass
  342. else:
  343. # push訊息
  344. message = "交易失敗!如果有疑問,請洽網站的服務信箱!"
  345. push_text(userid, message)
  346. return {'msg': r.content}
  347. return {'msg': 'OK'}
  348. # shop handler
  349. @router.get("/shop/{userid}")
  350. async def shop(userid:str, db: Session = Depends(deps.get_db)):
  351. # db connect
  352. url = 'accounts/U9dc55544ecca3a95b170bdf2a30e3691/nft_balances'
  353. r = await request_get(baseUrl + url, headers)
  354. nft_all = {}
  355. outcome = r.json()['nft_balances']
  356. ii = 0
  357. for i in range(len(outcome)):
  358. nft_ = crud.nft.get_by_uid(db, uid=outcome[i]['uid'])
  359. if nft_:
  360. nft_all[ii] = jsonable_encoder(nft_)
  361. nft_all[ii].pop('userid')
  362. nft_all[ii].pop('is_active')
  363. nft_all[ii].pop('uid')
  364. nft_all[ii].pop('hash')
  365. nft_all[ii].pop('category')
  366. nft_all[ii]['amount'] = outcome[i]['amount']
  367. nft_all[ii]['price'] = '1'
  368. ii += 1
  369. return nft_all
  370. db = dataset.connect(
  371. 'mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4'
  372. )
  373. sql = 'SELECT title, id, imgurl, userid FROM arkcard.nft ' \
  374. 'GROUP BY uid'
  375. result = db.query(sql)
  376. rows = {}
  377. i = 0
  378. for row in result:
  379. rows[i] = row
  380. i += 1
  381. return rows
  382. db.close()
  383. # @router.post("/buy")
  384. # async def buy(userModel: line.BuyNft):
  385. # # db connect
  386. # db = dataset.connect(
  387. # 'mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4'
  388. # )
  389. # table2 = db['nft']
  390. # # input
  391. # nftid = userModel.nftid
  392. # userid = userModel.userid
  393. # amount = userModel.amount
  394. # if not table2.find_one(id=nftid):
  395. # db.close()
  396. # # push訊息
  397. # message = "購買失敗!如果有疑問,請洽網站的服務信箱!"
  398. # push_text(userid, message)
  399. # return "該NFT商品不存在!如果有疑問,請洽網站的服務信箱!"
  400. # else:
  401. # user_obj = table2.find_one(id=nftid)
  402. # user_obj['userid'] = userid
  403. # table2.update(dict(user_obj), ['id'])
  404. # # push訊息
  405. # result3 = table2.find_one(id=nftid)
  406. # title = result3['title']
  407. # message = "您的NFT : " + title + ", 已購買成功!"
  408. # push_text(userid, message)
  409. # db.close()
  410. # return "您已購買成功!"
  411. @router.post("/event")
  412. async def nftdrops(userModel: line.NftDrops):
  413. # db connect
  414. db = dataset.connect(
  415. 'mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4'
  416. )
  417. table3 = db['nftdrops']
  418. # input對應
  419. eventid = '1'
  420. nftid = '1001'
  421. nftid2 = '1002'
  422. userid = userModel.userid
  423. email = userModel.email
  424. now = dt.datetime.now()
  425. # 如果userid不在db, 寫入到一個空值nft
  426. if not table3.find_one(userid=userid):
  427. # 新增資料
  428. table3.insert(
  429. dict(
  430. eventid=eventid, nftid=nftid,
  431. userid=userid, email=email, time=now
  432. ))
  433. table3.insert(
  434. dict(
  435. eventid=eventid, nftid=nftid2,
  436. userid=userid, email=email, time=now
  437. ))
  438. db.close()
  439. # push訊息
  440. message = "已為您登記活動!"
  441. push_text(userid, message)
  442. return "新增成功"
  443. # 如果userid存在,回傳通知
  444. else:
  445. db.close()
  446. # push訊息
  447. message = "您已登記過活動了!"
  448. push_text(userid, message)
  449. return "已有資料"
  450. @router.get("/transactions")
  451. def transactions(
  452. skip: int = 0,
  453. limit: int = 100,
  454. current_user: models.users = Depends(deps.get_current_active_superuser),
  455. ):
  456. # db connect
  457. db = dataset.connect(
  458. 'mysql://choozmo:pAssw0rd@db.ptt.cx:3306/arkcard?charset=utf8mb4'
  459. )
  460. sql = 'SELECT * FROM transaction ORDER BY id Desc ' \
  461. 'limit '+str(skip)+', '+str(limit)+''
  462. nft_table = db['nft']
  463. result = db.query(sql)
  464. rows = []
  465. for row in result:
  466. nft_item = nft_table.find_one(uid=row['nft'])
  467. try:
  468. row['nft'] = nft_item['title']
  469. except:
  470. pass
  471. rows.append(row)
  472. db.close()
  473. return rows