Skip to content

Commit

Permalink
timeline weibo
Browse files Browse the repository at this point in the history
  • Loading branch information
Covertness committed May 31, 2020
1 parent 1ea5f38 commit 5719332
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 3 deletions.
173 changes: 170 additions & 3 deletions cmd/bot/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import (
"fmt"
"github.com/Covertness/ally/pkg/account"
"github.com/Covertness/ally/pkg/favorite"
"github.com/Covertness/ally/pkg/scraper"
"github.com/Covertness/ally/pkg/timeline"
"io"
"sort"
"strconv"
"strings"
"time"

Expand All @@ -24,6 +28,7 @@ import (
var (
myFtx *ftx.FTX
myCoinBase *coinbase.CoinBase
myScraper *scraper.Scraper
)

func main() {
Expand All @@ -43,6 +48,7 @@ func main() {
err = db.AutoMigrate(
&marketPkg.Market{},
&account.Account{}, &favorite.Favorite{},
&timeline.Timeline{},
).Error
if err != nil {
log.Fatal(err)
Expand Down Expand Up @@ -71,9 +77,10 @@ func main() {

myFtx = ftx.Init()
myCoinBase = coinbase.Init()
myScraper = scraper.Init()

b.Handle("/start", func(m *tb.Message) {
_, _ = sendResponse(m.Sender, fmt.Sprintf("欢迎 %s %s\n/markets 查看行情", m.Sender.FirstName, m.Sender.LastName))
_, _ = sendResponse(m.Sender, fmt.Sprintf("欢迎 %s %s\n查看行情\n/markets\n查看微博/weibo\n", m.Sender.FirstName, m.Sender.LastName))
})

b.Handle("/markets", func(m *tb.Message) {
Expand Down Expand Up @@ -133,6 +140,63 @@ func main() {
_, _ = sendResponse(m.Sender, fmt.Sprintf("最近成交价:\n%s", price))
})

b.Handle("/weibo_search", func(m *tb.Message) {
searchName := m.Payload
if len(searchName) == 0 {
_, _ = sendResponse(m.Sender, fmt.Sprintf("请输入要搜索的微博账户名称,比如\n`/weibo_search 头条`"), tb.ModeMarkdown)
return
}

items, err := myScraper.WeiBoSearch(searchName)
if err != nil {
log.Error(err)
_, _ = sendResponse(m.Sender, fmt.Sprintf("未找到相关帐号,请再次尝试\n`/weibo_search 头条`"), tb.ModeMarkdown)
return
}

var buf bytes.Buffer
table := newTable(&buf)

for _, item := range items {
table.Append([]string{item.Name, item.ID})
}

table.Render()

markdownStr := fmt.Sprintf("```\n%s\n``` `/weibo id` 查看此帐号最新的微博", buf.String())
_, _ = sendResponse(m.Sender, markdownStr, tb.ModeMarkdown)
})

b.Handle("/weibo", func(m *tb.Message) {
id := m.Payload
if len(id) == 0 {
_, _ = sendResponse(m.Sender, fmt.Sprintf("请输入要查看微博帐号的ID,比如\n`/weibo 1618051664`\n获得帐号ID\n`/weibo_search 关键词`"), tb.ModeMarkdown)
return
}

items, err := myScraper.WeiBoTimeline(id)
if err != nil {
log.Error(err)
_, _ = sendResponse(m.Sender, fmt.Sprintf("未找到此帐号,请输入正确的ID,比如\n`/weibo 1618051664`"), tb.ModeMarkdown)
return
}

var resp string
for _, item := range items {
i, _ := strconv.ParseInt(item.Timestamp, 10, 64)
tm := time.Unix(i/1000, 0)
utm := time.Unix(m.Unixtime, 0)
resp += fmt.Sprintf("*%s前*:\n", utm.Sub(tm))
resp += fmt.Sprintf("%s\n", item.Text)
resp += fmt.Sprintf("[详情](%s)\n", item.Link)
resp += "\n\n"
}

resp += fmt.Sprintf("关注此微博帐号\n`/pin timeline weibo %s`\n", id)

_, _ = sendResponse(m.Sender, resp, tb.ModeMarkdown, tb.NoPreview)
})

b.Handle("/pin", func(m *tb.Message) {
myAccount, err := account.GetOrCreate(fmt.Sprintf("%d", m.Sender.ID))
if err != nil {
Expand Down Expand Up @@ -219,6 +283,7 @@ func main() {
}

var markets []*marketPkg.Market
var timelines []*timeline.Timeline
for _, fav := range favs {
switch fav.ItemType {
case favorite.ItemTypeMarket:
Expand All @@ -228,6 +293,13 @@ func main() {
return
}
markets = append(markets, m)
case favorite.ItemTypeTimeline:
t, err := timeline.GetByID(fav.ItemID)
if err != nil {
log.Error(err)
return
}
timelines = append(timelines, t)
}
}

Expand All @@ -237,8 +309,21 @@ func main() {
return
}

markdownStr := fmt.Sprintf("```\nMarkets:\n%s\n```/unpin 取消关注", data)
_, _ = sendResponse(m.Sender, markdownStr, tb.ModeMarkdown)
timelineData, err := outputTimelines(timelines, time.Unix(m.Unixtime, 0))
if err != nil {
log.Error(err)
return
}

var markdownStr string
if len(data) > 0 {
markdownStr += fmt.Sprintf("```\nMarket:\n%s\n```", data)
}
if len(timelineData) > 0 {
markdownStr += fmt.Sprintf("Timeline:\n\n%s\n\n", timelineData)
}
markdownStr += fmt.Sprintf("/unpin 取消关注")
_, _ = sendResponse(m.Sender, markdownStr, tb.ModeMarkdown, tb.NoPreview)
})

log.Info("bot is working...")
Expand Down Expand Up @@ -282,6 +367,64 @@ func outputMarkets(allMarkets []*marketPkg.Market) (string, error) {
return buf.String(), nil
}

func outputTimelines(allTimelines []*timeline.Timeline, now time.Time) (string, error) {
var allItems []*timelineItem
for _, mTimeline := range allTimelines {
switch mTimeline.Provider {
case timeline.ProviderWeiBo:
switch mTimeline.Type {
case timeline.TypeUser:
items, err := myScraper.WeiBoTimeline(mTimeline.CustomID)
if err != nil {
log.Error(err)
continue
}

for _, item := range items {
i, _ := strconv.ParseInt(item.Timestamp, 10, 64)
tm := time.Unix(i/1000, 0)

allItems = append(allItems, &timelineItem{
Provider: mTimeline.Provider,
Type: mTimeline.Type,
Timestamp: tm,
Name: item.Name,
Text: item.Text,
Link: item.Link,
})
}
}
}
}

sort.Slice(allItems, func(i, j int) bool {
return allItems[i].Timestamp.After(allItems[j].Timestamp)
})

if len(allItems) > 5 {
allItems = allItems[:5]
}

var buf string
for _, item := range allItems {
buf += fmt.Sprintf("*%s %s前 - %s*:\n", item.Name, now.Sub(item.Timestamp), item.Provider)
buf += fmt.Sprintf("%s\n", item.Text)
buf += fmt.Sprintf("[详情](%s)\n", item.Link)
buf += "\n\n"
}

return buf, nil
}

type timelineItem struct {
Provider string
Type string
Timestamp time.Time
Name string
Text string
Link string
}

func findFavoriteItemID(typeValue []string) (uint, error) {
switch typeValue[0] {
case favorite.ItemTypeMarket:
Expand All @@ -294,6 +437,30 @@ func findFavoriteItemID(typeValue []string) (uint, error) {
}

return market.ID, nil
case favorite.ItemTypeTimeline:
if len(typeValue) < 3 {
return 0, nil
}

switch typeValue[1] {
case timeline.ProviderWeiBo:
_, err := myScraper.WeiBoTimeline(typeValue[2])
if err != nil {
log.Error(err)
return 0, nil
}
t, err := timeline.GetOrCreate(&timeline.Timeline{
Provider: timeline.ProviderWeiBo,
Type: timeline.TypeUser,
CustomID: typeValue[2],
})
if err != nil {
return 0, err
}
return t.ID, nil
default:
return 0, nil
}
default:
return 0, nil
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/config/scraper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package config

// GetScraperEndpoint get the Scraper Endpoint
func GetScraperEndpoint() string {
return getenv("SCRAPER_ENDPOINT", "http://127.0.0.1:3000")
}
1 change: 1 addition & 0 deletions pkg/favorite/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ type Favorite struct {
// Favorite item type
const (
ItemTypeMarket = "market"
ItemTypeTimeline = "timeline"
)
48 changes: 48 additions & 0 deletions pkg/scraper/scraper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package scraper

import (
"fmt"
"github.com/Covertness/ally/pkg/config"
"github.com/imroc/req"
)

var ins *Scraper

// Init create the FTX connection
func Init() *Scraper {
ins = New(config.GetScraperEndpoint())
return ins
}

// GetInstance get the Scraper instance
func GetInstance() *Scraper {
return ins
}

// New create a new instance
func New(endpoint string) *Scraper {
return &Scraper{
Endpoint: endpoint,
}
}

func (s *Scraper) get(path string) (*req.Resp, error) {
url := fmt.Sprintf("%s%s",
s.Endpoint, path,
)

r, err := req.Get(url)
if err != nil {
return nil, err
}

if r.Response().StatusCode != 200 {
return nil, fmt.Errorf("http code %d", r.Response().StatusCode)
}
return r, nil
}

// Scraper instance connect to the internal service scraper
type Scraper struct {
Endpoint string
}
46 changes: 46 additions & 0 deletions pkg/scraper/weibo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package scraper

import "fmt"

// WeiBoSearch search keyword on WeiBo
func(s *Scraper) WeiBoSearch(keyword string) ([]*WeiBoSearchItem, error) {
path := fmt.Sprintf("/weibo/search/%s", keyword)
r, err := s.get(path)
if err != nil {
return nil, err
}

var resp []*WeiBoSearchItem
err = r.ToJSON(&resp)

return resp, err
}

// WeiBoSearchItem the item of search result
type WeiBoSearchItem struct {
ID string
Name string
}

// WeiBoTimeline get news of a specified user on WeiBo
func(s *Scraper) WeiBoTimeline(userID string) ([]*WeiBoTimelineItem, error) {
path := fmt.Sprintf("/weibo/%s", userID)
r, err := s.get(path)
if err != nil {
return nil, err
}

var resp []*WeiBoTimelineItem
err = r.ToJSON(&resp)

return resp, err
}

// WeiBoTimelineItem the item of timeline
type WeiBoTimelineItem struct {
Name string
Text string
Timestamp string
ID string
Link string
}
24 changes: 24 additions & 0 deletions pkg/timeline/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package timeline

import "time"

// Timeline timeline item
type Timeline struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time `gorm:"not null"`
UpdatedAt time.Time `gorm:"not null"`

Provider string `gorm:"unique_index:uix_timelines_provider_type_custom_id;not null"`
Type string `gorm:"unique_index:uix_timelines_provider_type_custom_id;not null"`
CustomID string `gorm:"unique_index:uix_timelines_provider_type_custom_id;not null"`
}

// Timeline provider
const (
ProviderWeiBo = "weibo"
)

// Timeline type
const (
TypeUser = "user"
)
Loading

0 comments on commit 5719332

Please sign in to comment.