newbot.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. import copy
  2. import fastapi
  3. import fastapi.staticfiles as fastapiStaticfiles
  4. import linebot
  5. import linebot.models as linebotModels
  6. import suggests
  7. from GoogleNews import GoogleNews
  8. import dataset
  9. import datetime
  10. from fastapi.responses import HTMLResponse
  11. import matplotlib.dates as mdates
  12. import matplotlib.pyplot as plt
  13. from starlette.responses import StreamingResponse
  14. from fastapi.responses import FileResponse
  15. import numpy as np
  16. from pylab import plt
  17. from scipy.interpolate import make_interp_spline
  18. from matplotlib import dates
  19. import dataset
  20. import matplotlib.dates as mdates
  21. import random
  22. import requests
  23. #
  24. from linebot.models import (
  25. MessageEvent, TextMessage, TextSendMessage,
  26. SourceUser, SourceGroup, SourceRoom,
  27. TemplateSendMessage, ConfirmTemplate, MessageAction,
  28. ButtonsTemplate, ImageCarouselTemplate, ImageCarouselColumn, URIAction,
  29. PostbackAction, DatetimePickerAction,
  30. CameraAction, CameraRollAction, LocationAction,
  31. CarouselTemplate, CarouselColumn, PostbackEvent,
  32. StickerMessage, StickerSendMessage, LocationMessage, LocationSendMessage,
  33. ImageMessage, VideoMessage, AudioMessage, FileMessage,
  34. UnfollowEvent, FollowEvent, JoinEvent, LeaveEvent, BeaconEvent,
  35. MemberJoinedEvent, MemberLeftEvent,
  36. FlexSendMessage, BubbleContainer, ImageComponent, BoxComponent,
  37. TextComponent, IconComponent, ButtonComponent,
  38. SeparatorComponent, QuickReply, QuickReplyButton,
  39. ImageSendMessage)
  40. #uvicorn newbot:app --host 0.0.0.0 --port 5443 --ssl-keyfile=/etc/letsencrypt/live/api.ptt.cx/privkey.pem --ssl-certfile=/etc/letsencrypt/live/api.ptt.cx/fullchain.pem
  41. #uvicorn main:app --host 0.0.0.0 --port 443 --ssl-keyfile=/etc/letsencrypt/live/ptt.cx/privkey.pem --ssl-certfile=/etc/letsencrypt/live/ptt.cx/chain.pem
  42. #uvicorn main:app --host 0.0.0.0 --port 443 --key-file=/etc/letsencrypt/live/ptt.cx/privkey.pem --certfile=/etc/letsencrypt/live/ptt.cx/cert.pem
  43. # --keyfile=./key.pem --certfile=./cert.pem
  44. # --ssl-cert-reqs 1
  45. #
  46. # --ssl-ca-certs=/etc/letsencrypt/live/ptt.cx/fullchain.crt
  47. app = fastapi.FastAPI()
  48. #app.mount(
  49. # '/static', fastapiStaticfiles.StaticFiles(directory='static'), name='static')
  50. from linebot import (
  51. LineBotApi, WebhookHandler
  52. )
  53. from linebot.exceptions import (
  54. InvalidSignatureError
  55. )
  56. from linebot.models import (
  57. MessageEvent, TextMessage,ImageSendMessage, TextSendMessage,FlexSendMessage, TemplateSendMessage,CarouselTemplate,ConfirmTemplate,PostbackAction,MessageAction,CarouselColumn,URIAction
  58. )
  59. import json
  60. import codecs
  61. seo=False
  62. s_news=False
  63. line_bot_api = LineBotApi('Gub58t+8u9z7nTBrZLLvXnCbwDR1Gmyrew3nGFlKRxsmvH/oIMGjjup2DygA4XhV1NenM6dFTO8yvpbtCDezCqP2BAV4eVn2Y63/EYqeTCw1S+oJ+BiLLzC8DdVrWu9jSfp7wXqrVNUxcYdk54eoWAdB04t89/1O/w1cDnyilFU=')
  64. handler = WebhookHandler('f761bc6038c94a3baa815124e33dea50')
  65. #line_bot_api = LineBotApi('YOUR_CHANNEL_ACCESS_TOKEN')
  66. #handler = WebhookHandler('YOUR_CHANNEL_SECRET')
  67. def notify_group(msg):
  68. glist=['1dbtJHbWVbrooXmQqc4r8OyRWDryjD4TMJ6DiDsdgsX']
  69. for gid in glist:
  70. headers = {
  71. "Authorization": "Bearer " + gid,
  72. "Content-Type": "application/x-www-form-urlencoded"
  73. }
  74. params = {"message": msg}
  75. r = requests.post("https://notify-api.line.me/api/notify",headers=headers, params=params)
  76. def get_aws():
  77. result=''
  78. db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/hhh?charset=utf8mb4')
  79. cursor=db.query('SELECT from_unixtime(eventtime) as dt,area FROM hhh.aws_monitor order by eventtime desc limit 4;')
  80. for c in cursor:
  81. result+=str(c['dt'])+"\n"+c['area']+"\n"
  82. return result
  83. def get_idea():
  84. result=''
  85. db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/hhh?charset=utf8mb4')
  86. cursor=db.query('SELECT query FROM hhh.gsc_weekly where clicks > 500 order by rand() limit 10;')
  87. for c in cursor:
  88. result+=str(c['query'])+"\n"
  89. return result
  90. def get_kw_ls(past=False):
  91. db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/yodb?charset=utf8mb4')
  92. now = ''
  93. sql_get_newest_data_date = 'SELECT * FROM trending_searches ORDER BY ts_date DESC'
  94. for row in db.query(sql_get_newest_data_date):
  95. now = row['ts_date']
  96. break
  97. if past:
  98. now =now-datetime.timedelta(days=1)
  99. now_start = now.strftime("%Y-%m-%d 00:00:00")
  100. now_end = now.strftime("%Y-%m-%d 23:59:59")
  101. sqls = 'SELECT * FROM trending_searches WHERE "'+now_end+'">= ts_date and "'+now_start+'"<=ts_date ORDER BY ts_date DESC'
  102. ls = []
  103. for row in db.query(sqls):
  104. ls.append(row['ts_word'])
  105. ls = list(dict.fromkeys(ls))
  106. return ls
  107. def get_kw(past=False):
  108. keys = get_kw_ls(past)
  109. title = '熱門搜尋關鍵字' if past == False else '歷史關鍵字走勢'
  110. carousel_dict = {"type": "carousel","contents": []}
  111. carousel_idx = -1
  112. num = 1
  113. for k in keys:
  114. if (num-1)%12 ==0:
  115. carousel_dict['contents'] = carousel_dict['contents']+[make_bubble(title,k)]
  116. carousel_idx+=1
  117. carousel_dict['contents'][carousel_idx]['body']['contents'] += [make_box(num,k)]
  118. num+=1
  119. return carousel_dict
  120. @app.get("/aws")
  121. async def aws():
  122. result='<html><head><link href="https://getbootstrap.com/docs/4.1/dist/css/bootstrap.min.css" rel="stylesheet"></head><body>'
  123. result+='製表時間: '+str(datetime.datetime.now())+'</br></br>\n\n'
  124. result+='<table class="table table-striped">'
  125. db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/hhh?charset=utf8mb4')
  126. cursor=db.query('SELECT from_unixtime(eventtime) as dt,area FROM hhh.aws_monitor order by eventtime desc limit 30;')
  127. for c in cursor:
  128. result+="<tr><td>"+str(c['dt'])+"</td><td>"+c['area']+"</td></tr>\n"
  129. result+='</table></body></html>'
  130. return HTMLResponse(content=result, status_code=200)
  131. @app.get("/msg/{item_id}")
  132. async def coffee_msg(item_id):
  133. True
  134. line_bot_api.push_message(item_id, TextSendMessage(text='開啟下方完整券樣(密碼9888)至櫃台掃碼兌換。https://txp.rs/v/EvX69b4Xq9'))
  135. return {"code":"ok" }
  136. @app.post('/callback')
  137. async def callback(request: fastapi.Request):
  138. signature = request.headers['X-Line-Signature']
  139. body = await request.body()
  140. handler.handle(body.decode('utf-8'), signature)
  141. return 'OK'
  142. @app.get('/get_trend_image')
  143. async def get_trend_image(kw_id):
  144. #def get_trend_image(kw):
  145. db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/yodb?charset=utf8mb4')
  146. kw = ''
  147. sql_get_newest_data_date = 'SELECT iot_kword FROM interest_over_time WHERE iotid="'+kw_id+'"'
  148. for row in db.query(sql_get_newest_data_date):
  149. kw=row['iot_kword']
  150. sql_get_newest_data_date = 'SELECT * FROM interest_over_time WHERE iot_kword="'+kw+'" ORDER BY iot_date'
  151. x_axis = []
  152. y_axis = []
  153. for row in db.query(sql_get_newest_data_date):
  154. x_axis += [row['iot_date']]
  155. y_axis += [row['iot_value']]
  156. x = np.array(x_axis)
  157. y = np.array(y_axis)
  158. # create an array of numbers for the dates
  159. x_dates = np.array([dates.date2num(i) for i in x])
  160. # create more uniform intervals in x axis and use spline to interpolate data
  161. x_smooth = np.linspace(x_dates.min(), x_dates.max(), 200)
  162. #y_smooth = spline(x_dates, y, x_smooth)
  163. y_smooth = make_interp_spline(x_dates, y)(x_smooth)
  164. # creating a new date array from the new date number array
  165. x_new = np.array([dates.num2date(i) for i in x_smooth])
  166. plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y/%m'))
  167. plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=100)) #座標軸刻度1天
  168. #plt.figure()
  169. plt.plot(x_new, y_smooth)
  170. target_path = 'trend_image/'+kw+'.png'
  171. plt.savefig(target_path)
  172. plt.clf()
  173. return FileResponse(target_path)
  174. def get_news_by_kw(keyword):
  175. googlenews = GoogleNews(lang='zh-TW')
  176. kw=keyword
  177. googlenews.set_lang('zh-TW')
  178. googlenews.search(kw)
  179. resultstr="新聞:"
  180. idx=0
  181. rs=googlenews.results()
  182. for r in rs:
  183. if idx>0:
  184. resultstr+=','
  185. else:
  186. idx+=1
  187. resultstr+=r['title']
  188. return resultstr
  189. # print(r['desc'])
  190. # print(r['link'])
  191. # print(r['datetime'])
  192. def flex_test():
  193. js=json.load(open('test.json','r',encoding='utf-8'))
  194. for i in range(3):
  195. row_dict = {}
  196. row_dict['type'] = 'box'
  197. row_dict['layout'] = 'baseline'
  198. row_dict['contents']= [
  199. {"type": "text","text": "第"+str(i)+"名","size": "sm","color": "#aaaaaa","flex": 2},
  200. {"type": "text","text": "hello, world"+str(1),"flex": 5,"weight": "regular"}
  201. ]
  202. js['body']['contents'] = js['body']['contents'] + [row_dict]
  203. return js
  204. def make_bubble(title,kw):
  205. db = dataset.connect('mysql://choozmo:pAssw0rd@db.ptt.cx:3306/yodb?charset=utf8mb4')
  206. sql_get_newest_data_date = 'SELECT iotid FROM interest_over_time WHERE iot_kword="'+kw+'"'
  207. id=''
  208. for row in db.query(sql_get_newest_data_date):
  209. id=row['iotid']
  210. id = random.randint(1,1050)
  211. box_dict = {
  212. "type": "bubble",
  213. "hero": {
  214. "type": "image",
  215. "url": "https://api.ptt.cx:6443/get_trend_image?kw_id="+str(id),
  216. "size": "full",
  217. "gravity": "top",
  218. "margin": "none",
  219. "aspectRatio": "20:13"
  220. },
  221. "body": {
  222. "type": "box",
  223. "layout": "vertical",
  224. "contents": [
  225. {
  226. "type": "text",
  227. "text": title+"-"+kw,
  228. "size": "xl",
  229. "weight": "bold",
  230. "align": "center"
  231. }
  232. ]
  233. }
  234. }
  235. return box_dict
  236. def make_box(num, content):
  237. row_dict = {}
  238. row_dict['type'] = 'box'
  239. row_dict['layout'] = 'baseline'
  240. #row_dict["action"]= {
  241. # "type": "uri",
  242. # "label": "action",
  243. # "uri": "https://store.line.me"
  244. #}
  245. row_dict['contents']= [
  246. {
  247. "type": "text",
  248. "text": "第"+str(num)+"名",
  249. "size": "sm",
  250. "color": "#aaaaaa",
  251. "flex": 2
  252. },
  253. {
  254. "type": "text",
  255. "text": content,
  256. "flex": 5,
  257. "weight": "regular"
  258. }
  259. ]
  260. return row_dict
  261. def make_carousel():
  262. carousel_dict = {"type": "carousel","contents": []}
  263. carousel_idx = -1
  264. for i in range(26):
  265. if i%12 ==0:
  266. carousel_dict['contents'] = carousel_dict['contents']+[make_bubble()]
  267. carousel_idx+=1
  268. carousel_dict['contents'][carousel_idx]['body']['contents'] += [make_box(i,'data'+str(i))]
  269. return carousel_dict
  270. @handler.add(FollowEvent)
  271. def handle_follow(event):
  272. print(event.source.user_id)
  273. # do something
  274. @handler.add(linebotModels.MessageEvent, message=linebotModels.TextMessage)
  275. def message_text(event):
  276. global seo
  277. global s_news
  278. ## if event.message.text == 'push':
  279. # line_bot_api.push_message(
  280. # event.source.user_id, [
  281. # # TextSendMessage(text='PUSH!'),
  282. # ]
  283. # )
  284. notify_group('User :'+event.message.text)
  285. if event.message.text == 's_news':
  286. s_news=True
  287. line_bot_api.reply_message(
  288. event.reply_token,
  289. TextSendMessage(text='請輸入要搜尋新聞的關鍵字:'))
  290. return
  291. if event.message.text == 'q_aws':
  292. line_bot_api.reply_message(
  293. event.reply_token,[TextSendMessage(text=get_aws()),TextSendMessage(text='完整報告: https://api.ptt.cx:5443/aws')])
  294. return
  295. if event.message.text == 'q_idea':
  296. s_news=True
  297. line_bot_api.reply_message(
  298. event.reply_token,
  299. TextSendMessage(text=get_idea()))
  300. return
  301. ############################
  302. if event.message.text == 'hotkeys':
  303. js = get_kw(False)
  304. line_bot_api.reply_message(event.reply_token, FlexSendMessage('ChoozMo',js))
  305. return
  306. if event.message.text == 'past_hotkeys':
  307. js = get_kw(True)
  308. line_bot_api.reply_message(event.reply_token, FlexSendMessage('ChoozMo',js))
  309. return
  310. if event.message.text == '合作提案':
  311. line_bot_api.reply_message(
  312. event.reply_token,
  313. TextSendMessage(text='合作提案請寄信至 : jared@choozmo.com 信箱'))
  314. return
  315. if event.message.text == '更多功能':
  316. line_bot_api.reply_message(
  317. event.reply_token,
  318. TextSendMessage(
  319. text='更多功能',
  320. quick_reply=QuickReply(
  321. items=[
  322. QuickReplyButton(
  323. action=MessageAction(label="合作提案", text="合作提案")
  324. ),
  325. ])))
  326. return
  327. '''
  328. if event.message.text == 'seo':
  329. seo=True
  330. line_bot_api.reply_message(
  331. event.reply_token,
  332. TextSendMessage(text='請輸入要找的關鍵字:'))
  333. return
  334. '''
  335. if event.message.text=='js test':
  336. FlexMessage = flex_test()
  337. line_bot_api.reply_message(event.reply_token, FlexSendMessage('ChoozMo',FlexMessage))
  338. return
  339. if event.message.text=='熱門關鍵字':
  340. js = get_kw(False)
  341. line_bot_api.reply_message(event.reply_token, FlexSendMessage('ChoozMo',js))
  342. return
  343. if event.message.text=='關鍵字歷史走勢':
  344. js = get_kw(True)
  345. line_bot_api.reply_message(event.reply_token, FlexSendMessage('ChoozMo',js))
  346. return
  347. if event.message.text == '叫':
  348. line_bot_api.reply_message(
  349. event.reply_token, linebotModels.AudioSendMessage(
  350. original_content_url=f'{baseUrl}/static/audio/noot_noot.mp3', duration=1000))
  351. if event.message.text=='ok':
  352. line_bot_api.reply_message(
  353. event.reply_token,
  354. TextSendMessage(text='最欣賞ChoozMo團隊說OK的人!!'))
  355. '''
  356. if event.message.text=='c' or event.message.text=='C' :
  357. line_bot_api.reply_message(
  358. event.reply_token,
  359. TextSendMessage(
  360. text='快速鍵',
  361. quick_reply=QuickReply(
  362. items=[
  363. QuickReplyButton(
  364. action=MessageAction(label="熱門關鍵字", text="hotkeys")
  365. ),
  366. QuickReplyButton(
  367. action=MessageAction(label="關鍵字歷史走勢", text="past_hotkeys")
  368. ),
  369. QuickReplyButton(
  370. action=MessageAction(label="更多功能", text="更多功能")
  371. ),
  372. #QuickReplyButton(
  373. # action=MessageAction(label="幸福空間靈感", text="q_idea")
  374. #),
  375. #QuickReplyButton(
  376. # action=MessageAction(label="查AWS", text="q_aws")
  377. #),
  378. #QuickReplyButton(
  379. # action=MessageAction(label="關聯字", text="seo")
  380. #),
  381. ])))
  382. else:
  383. if s_news:
  384. result=get_news_by_kw(event.message.text)
  385. line_bot_api.reply_message(
  386. event.reply_token,
  387. TextSendMessage(text=result))
  388. s_news=False
  389. return
  390. if seo:
  391. res='相關字:'
  392. s = suggests.suggests.get_suggests(event.message.text, source='google')
  393. idx=0
  394. for sg in s['suggests']:
  395. if idx>0:
  396. res+=','
  397. else:
  398. idx+=1
  399. res+=sg
  400. line_bot_api.reply_message(
  401. event.reply_token,
  402. TextSendMessage(text=res))
  403. seo=False
  404. # print('test')
  405. '''
  406. #if __name__ == "__main__":
  407. # app.run(host='0.0.0.0', port=443,ssl_context=('/etc/letsencrypt/live/ptt.cx/fullchain.pem', '/etc/letsencrypt/live/ptt.cx/privkey.pem'))
  408. # app.run(host='0.0.0.0', port=14404,ssl_context=('/tmp/cert.pem','/tmp/chain.pem' ))