diff --git a/app.yml b/app.yml index e7f0966..d529c1b 100644 --- a/app.yml +++ b/app.yml @@ -11,3 +11,6 @@ modals: ["MLDW3CH/A","MLDV3CH/A"] #推送url 需要下载Bark app 在ios可得到 https://api.day.app/xxx notifyUrl: ["https://api.day.app/xxx"] + +#消息是否根据取货地点合并通知 +notifyMergedByStore: false \ No newline at end of file diff --git a/apple.go b/apple.go index 083006e..35f6023 100644 --- a/apple.go +++ b/apple.go @@ -50,9 +50,13 @@ func (apple *Apple) Serve() { timer = time.NewTimer(time.Duration(apple.configOption.SearchInterval) * time.Second) } } -func (apple *Apple) ReqSearch() { +func (apple *Apple) ReqSearch() error { log.Printf("[I] 开始查询苹果接口. 查询位置:%v", apple.configOption.Location) - appleUrl := apple.makeUrl() + appleUrl, err := apple.makeUrl() + if err != nil { + log.Printf("[E] make url failed. err:%v", err) + return err + } ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() @@ -61,11 +65,12 @@ func (apple *Apple) ReqSearch() { if err != nil { log.Printf("[E] new request failed. err:%v", err) } + req.Header.Add("sec-fetch-site", "same-origin") resp, err := apple.cli.Do(req) if err != nil { log.Printf("[E] do request failed. url:%v, err:%v", appleUrl, err) - return + return nil } defer resp.Body.Close() @@ -73,37 +78,63 @@ func (apple *Apple) ReqSearch() { searchResponse, err := apple.unMarshalResp(resp) if err != nil { log.Printf("[E] unMarshalResp failed. err:%v", err) - return + return nil } - messages := make([]*Message, 0, 10) + pickupQuote2IphoneModels := make(map[string][]string) stores := searchResponse.Body.Content.PickupMessage.Stores for _, store := range stores { - for _, info := range store.PartsAvailability { - iphoneModal := info.StorePickupProductTitle - pickTime := info.PickupSearchQuote + msgTypes := info.MessageTypes + pickupQuote := info.PickupSearchQuote pickStore := store.StoreName + iphoneModal := msgTypes.Expanded.StorePickupProductTitle + if iphoneModal == "" { + iphoneModal = msgTypes.Regular.StorePickupProductTitle + } + log.Printf("[I] 型号:%+v 地点:%v %v", iphoneModal, pickStore, pickupQuote) if !apple.hasStockOffline(info.PickupSearchQuote) { - log.Printf("[E] 型号:%v 地点:%v %v", iphoneModal, pickStore, pickTime) continue } - msg := &Message{ - Title: iphoneModal, - Content: fmt.Sprintf("取货时间:%v 地点:%v", pickTime, pickStore), + pickupContent := fmt.Sprintf("%v %v", pickStore, pickupQuote) + + iphoneModals, ok := pickupQuote2IphoneModels[pickupContent] + if !ok { + iphoneModals = make([]string, 0) } + iphoneModals = append(iphoneModals, iphoneModal) + pickupQuote2IphoneModels[pickupContent] = iphoneModals + } + } - messages = append(messages, msg) + messages := make([]*Message, 0, 10) + for pickupStore, iphoneModels := range pickupQuote2IphoneModels { + if apple.configOption.NotifyMergedByStore { + messages = append(messages, &Message{ + Title: pickupStore, + Content: strings.Join(iphoneModels, ","), + }) + continue + } + + for _, iphoneModel := range iphoneModels { + messages = append(messages, &Message{ + Title: iphoneModel, + Content: pickupStore, + }) } } - apple.sendNotificationToBarkApp(messages) + apple.sendNotificationToBarkApp(messages...) + return nil } -func (apple *Apple) sendNotificationToBarkApp(messages []*Message) { +func (apple *Apple) sendNotificationToBarkApp(messages ...*Message) { + for _, msg := range messages { + for _, notifyUrl := range apple.configOption.NotifyUrl { url := fmt.Sprintf("%v/%v/%v", notifyUrl, msg.Title, msg.Content) _, err := apple.cli.Get(url) @@ -136,12 +167,21 @@ func (apple *Apple) unMarshalResp(resp *http.Response) (*SearchResponse, error) return searchResponse, nil } -func (apple *Apple) makeUrl() string { +func (apple *Apple) makeUrl() (string, error) { values := make(url.Values) - values.Add("mt", "regular") - values.Add("little", "false") - values.Add("pl", "true") - values.Add("location", apple.configOption.Location) + + state, city, district, err := apple.configOption.Location.GetParts() + if err != nil { + return "", err + } + values.Add("state", state) + values.Add("city", city) + values.Add("district", district) + values.Add("geoLocated", "true") + + // values.Add("mt", "regular") + // values.Add("little", "false") + // values.Add("pl", "true") for i, modal := range apple.configOption.Modals { key := fmt.Sprintf("parts.%d", i) @@ -149,5 +189,5 @@ func (apple *Apple) makeUrl() string { } query := values.Encode() - return fmt.Sprintf("%v?%v", SearchUrl, query) + return fmt.Sprintf("%v?%v", SearchUrl, query), nil } diff --git a/proto.go b/proto.go index e01280b..652a925 100644 --- a/proto.go +++ b/proto.go @@ -1,19 +1,40 @@ package main +import ( + "fmt" + "strings" +) + type Message struct { Title string Content string } +type Location string + +func (l Location) GetParts() (state, city, district string, err error) { + parts := strings.Split(string(l), " ") + if len(parts) != 3 { + err = fmt.Errorf("location %v format not supported, it should be like '广东 深圳 南山区'", l) + return + } + + state = parts[0] + city = parts[0] + district = parts[0] + return +} + type ConfigOption struct { - Modals []string `yaml:"modals"` - NotifyUrl []string `yaml:"notifyUrl"` - Location string `yaml:"location"` - SearchInterval int `yaml:"searchInterval"` + Modals []string `yaml:"modals"` + NotifyUrl []string `yaml:"notifyUrl"` + Location Location `yaml:"location"` + SearchInterval int `yaml:"searchInterval"` + NotifyMergedByStore bool `yaml:"notifyMergedByStore"` } type SearchResponse struct { - // Head SearchRespHead `json:"head"` + // Head SearchRespHead `json:"head,omitempty"` Body SearchRespBody `json:"body"` } @@ -31,7 +52,7 @@ type Content struct { } type PickupMessage1 struct { - Stores []Store `json:"stores"` + Stores []*Store `json:"stores"` } type Store struct { @@ -42,8 +63,17 @@ type Store struct { type PartsAvailability map[string]PartsAvailabilityValue //型号 => info type PartsAvailabilityValue struct { - PickupSearchQuote string `json:"pickupSearchQuote"` //可取货 - StorePickupProductTitle string `json:"storePickupProductTitle"` //iPhone 13 512GB 粉色 + PickupSearchQuote string `json:"pickupSearchQuote"` //可取货 + MessageTypes *MessageTypes `json:"messageTypes"` +} + +type MessageTypes struct { + Expanded Expanded `json:"expanded,omitempty"` + Regular Expanded `json:"regular,omitempty"` +} + +type Expanded struct { + StorePickupProductTitle string `json:"storePickupProductTitle"` //iPhone 14 Pro 128GB 暗紫色 } //有货 diff --git a/proto_test.go b/proto_test.go new file mode 100644 index 0000000..d6e0482 --- /dev/null +++ b/proto_test.go @@ -0,0 +1,27 @@ +package main + +import ( + "encoding/json" + "testing" +) + +func TestProto(t *testing.T) { + str := `{"head":{"status":"200","data":{}},"body":{"content":{"pickupMessage":{"stores":[{"storeEmail":"holidayplazashenzhen@apple.com","storeName":"深圳益田假日广场","reservationUrl":"http://www.apple.com.cn/retail/holidayplazashenzhen","makeReservationUrl":"http://www.apple.com.cn/retail/holidayplazashenzhen","state":"广东","storeImageUrl":"https://rtlimages.apple.com/cmc/dieter/store/4_3/R484.png?resize=828:*&output-format=jpg","country":"CN","city":"深圳","storeNumber":"R484","partsAvailability":{"MQ0D3CH/A":{"storePickEligible":true,"pickupSearchQuote":"今天可取货","partNumber":"MQ0D3CH/A","purchaseOption":"","ctoOptions":"","pickupDisplay":"available","pickupType":"店内取货","messageTypes":{"expanded":{"storeSearchEnabled":true,"storePickupLabel":"立即订购。取货 (店内):","storeSelectionEnabled":true,"storePickupQuote":"今天可取货","storePickupLinkText":"查看其他零售店","storePickupProductTitle":"iPhone 14 Pro 128GB 暗紫色"}}}},"phoneNumber":"4006171254","pickupTypeAvailabilityText":"此地点提供店内取货服务。","address":{"address":"Apple 深圳益田假日广场","address3":null,"address2":"深圳市南山区深南大道 9028 号益田假日广场","postalCode":"518000"},"hoursUrl":"http://www.apple.com.cn/retail/holidayplazashenzhen","storeHours":{"storeHoursText":"Store Hours","bopisPickupDays":"Days","bopisPickupHours":"Hours","hours":[{"storeTimings":"10:00 - 22:30","storeDays":"周五-周六:"},{"storeTimings":"10:00 - 22:00","storeDays":"周一-周四, 周日:"}]},"pickupEncodedUpperDateString":"20230203","storelatitude":22.53986,"storelongitude":113.97079,"storedistance":0.0,"storeDistanceVoText":"null from 518000","retailStore":{"storeNumber":"R484","storeUniqueId":"R484","name":"深圳益田假日广场","storeTypeKey":"1","storeSubTypeKey":"0","storeType":"APPLESTORE_DEFAULT","phoneNumber":"4006171254","email":"holidayplazashenzhen@apple.com","carrierCode":null,"locationType":null,"latitude":22.53986,"longitude":113.97079,"address":{"city":"深圳","companyName":"Apple 深圳益田假日广场","countryCode":"CN","county":null,"district":"南山区","geoCode":null,"label":null,"languageCode":"zh-cn","mailStop":null,"postalCode":"518000","province":null,"state":"广东","street":"深圳市南山区深南大道 9028 号益田假日广场","street2":null,"street3":null,"suburb":null,"type":"SHIPPING","addrSourceType":null,"outsideCityFlag":null,"daytimePhoneAreaCode":null,"eveningPhoneAreaCode":null,"daytimePhone":"4006171254","fullPhoneNumber":null,"eveningPhone":null,"emailAddress":null,"firstName":null,"lastName":null,"suffix":null,"lastNamePhonetic":null,"firstNamePhonetic":null,"title":null,"businessAddress":false,"uuid":"4a9fd61c-4cc4-4fa5-896b-e18cf2142089","mobilePhone":null,"mobilePhoneAreaCode":null,"cityStateZip":null,"daytimePhoneSelected":false,"middleName":null,"residenceStatus":null,"moveInDate":null,"subscriberId":null,"locationType":null,"carrierCode":null,"metadata":{},"verificationState":"UN_VERIFIED","expiration":null,"markForDeletion":false,"correctionResult":null,"fullDaytimePhone":"4006171254","fullEveningPhone":null,"twoLineAddress":"深圳市南山区深南大道 9028 号益田假日广场,\n深圳, 广东, 518000","addressVerified":false,"primaryAddress":false},"urlKey":null,"directionsUrl":null,"storeImageUrl":"http://rtlimages.apple.com/cmc/dieter/store/4_3/R484.png?resize=828:*&output-format=jpg","makeReservationUrl":"http://www.apple.com.cn/retail/holidayplazashenzhen","hoursAndInfoUrl":"http://www.apple.com.cn/retail/holidayplazashenzhen","storeHours":[{"storeDays":"周五-周六","voStoreDays":null,"storeTimings":"10:00 - 22:30 "},{"storeDays":"周一-周四, 周日","voStoreDays":null,"storeTimings":"10:00 - 22:00 "}],"storeHolidays":[],"secureStoreImageUrl":"https://rtlimages.apple.com/cmc/dieter/store/4_3/R484.png?resize=828:*&output-format=jpg","distance":0.0,"distanceUnit":"KM","distanceWithUnit":null,"timezone":"Asia/Shanghai","storeIsActive":true,"lastUpdated":0.0,"lastFetched":1675417836438,"dateStamp":"03-Feb-2023","distanceSeparator":".","nextAvailableDate":null,"storeHolidayLookAheadWindow":0,"driveDistanceWithUnit":null,"driveDistanceInMeters":null,"dynamicAttributes":{},"storePickupMethodByType":{"INSTORE":{"type":"INSTORE","typeDirection":{"directionByLocale":null},"typeMeetupLocation":{"meetingLocationByLocale":null},"typeCoordinate":{"lat":22.53986,"lon":113.97079},"services":["APU"]}},"storeTimings":null,"availableNow":true},"storelistnumber":1,"storeListNumber":1,"pickupOptionsDetails":{"whatToExpectAtPickup":"