newbot.py 14 KB

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