Skip to content

Commit c1a1975

Browse files
committed
Fixing up forecast example
1 parent cb35433 commit c1a1975

File tree

5 files changed

+232
-12
lines changed

5 files changed

+232
-12
lines changed

forecast/hl.json

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
{
2+
"ach": "Luo",
3+
"af": "Afrikaans",
4+
"ak": "Akan",
5+
"am": "Amharic",
6+
"ar": "Arabic",
7+
"az": "Azerbaijani",
8+
"be": "Belarusian",
9+
"bem": "Bemba",
10+
"bg": "Bulgarian",
11+
"bh": "Bihari",
12+
"bn": "Bengali",
13+
"br": "Breton",
14+
"bs": "Bosnian",
15+
"bt": "Bhutanese",
16+
"ca": "Catalan",
17+
"chr": "Cherokee",
18+
"ckb": "Kurdish (Soranî)",
19+
"co": "Corsican",
20+
"crs": "Seychellois Creole",
21+
"cs": "Czech",
22+
"cy": "Welsh",
23+
"da": "Danish",
24+
"de": "German",
25+
"ee": "Ewe",
26+
"el": "Greek",
27+
"en": "English",
28+
"eo": "Esperanto",
29+
"es-419": "Spanish (Latin American)",
30+
"es": "Spanish",
31+
"et": "Estonian",
32+
"eu": "Basque",
33+
"fa": "Persian",
34+
"fi": "Finnish",
35+
"fo": "Faroese",
36+
"fr": "French",
37+
"fy": "Frisian",
38+
"gaa": "Ga",
39+
"ga": "Irish",
40+
"gd": "Scots Gaelic",
41+
"gl": "Galician",
42+
"gn": "Guarani",
43+
"gu": "Gujarati",
44+
"ha": "Hausa",
45+
"haw": "Hawaiian",
46+
"hi": "Hindi",
47+
"hr": "Croatian",
48+
"ht": "Haitian Creole",
49+
"hu": "Hungarian",
50+
"hy": "Armenian",
51+
"ia": "Interlingua",
52+
"id": "Indonesian",
53+
"ig": "Igbo",
54+
"is": "Icelandic",
55+
"it": "Italian",
56+
"iw": "Hebrew",
57+
"ja": "Japanese",
58+
"jw": "Javanese",
59+
"ka": "Georgian",
60+
"kg": "Kongo",
61+
"kk": "Kazakh",
62+
"kl": "Greenlandic",
63+
"km": "Cambodian",
64+
"kn": "Kannada",
65+
"ko": "Korean",
66+
"kri": "Krio (Sierra Leone)",
67+
"ku": "Kurdish",
68+
"ky": "Kyrgyz",
69+
"la": "Latin",
70+
"lg": "Luganda",
71+
"ln": "Lingala",
72+
"lo": "Laothian",
73+
"loz": "Lozi",
74+
"lt": "Lithuanian",
75+
"lua": "Tshiluba",
76+
"lv": "Latvian",
77+
"mfe": "Mauritian Creole",
78+
"mg": "Malagasy",
79+
"mi": "Maori",
80+
"mk": "Macedonian",
81+
"ml": "Malayalam",
82+
"mn": "Mongolian",
83+
"mo": "Moldavian",
84+
"mr": "Marathi",
85+
"ms": "Malay",
86+
"mt": "Maltese",
87+
"mv": "Maldives",
88+
"my": "Myanmar",
89+
"ne": "Nepali",
90+
"nl": "Dutch",
91+
"nn": "Norwegian (Nynorsk)",
92+
"no": "Norwegian",
93+
"nso": "Northern Sotho",
94+
"ny": "Chichewa",
95+
"nyn": "Runyakitara",
96+
"oc": "Occitan",
97+
"om": "Oromo",
98+
"or": "Oriya",
99+
"pa": "Punjabi",
100+
"pcm": "Nigerian Pidgin",
101+
"pl": "Polish",
102+
"ps": "Pashto",
103+
"pt-br": "Portuguese (Brazil)",
104+
"pt": "Portuguese",
105+
"pt-pt": "Portuguese (Portugal)",
106+
"qu": "Quechua",
107+
"rm": "Romansh",
108+
"rn": "Kirundi",
109+
"ro": "Romanian",
110+
"ru": "Russian",
111+
"rw": "Kinyarwanda",
112+
"sd": "Sindhi",
113+
"sh": "Serbo-Croatian",
114+
"si": "Sinhalese",
115+
"sk": "Slovak",
116+
"sl": "Slovenian",
117+
"sn": "Shona",
118+
"so": "Somali",
119+
"sq": "Albanian",
120+
"sr-me": "Montenegrin",
121+
"sr": "Serbian",
122+
"st": "Sesotho",
123+
"su": "Sundanese",
124+
"sv": "Swedish",
125+
"sw": "Swahili",
126+
"ta": "Tamil",
127+
"te": "Telugu",
128+
"tg": "Tajik",
129+
"th": "Thai",
130+
"ti": "Tigrinya",
131+
"tk": "Turkmen",
132+
"tl": "Filipino",
133+
"tn": "Setswana",
134+
"to": "Tonga",
135+
"tr": "Turkish",
136+
"tt": "Tatar",
137+
"tum": "Tumbuka",
138+
"tw": "Twi",
139+
"ug": "Uighur",
140+
"uk": "Ukrainian",
141+
"ur": "Urdu",
142+
"uz": "Uzbek",
143+
"vi": "Vietnamese",
144+
"vu": "Vanuatu",
145+
"wo": "Wolof",
146+
"ws": "Samoa",
147+
"xh": "Xhosa",
148+
"xx-bork": "Bork, bork, bork!",
149+
"xx-elmer": "Elmer Fudd",
150+
"xx-hacker": "Hacker",
151+
"xx-klingon": "Klingon",
152+
"xx-pirate": "Pirate",
153+
"yi": "Yiddish",
154+
"yo": "Yoruba",
155+
"zh": "Chinese",
156+
"zh-cn": "Chinese (Simplified)",
157+
"zh-tw": "Chinese (Traditional)",
158+
"zu": "Zulu"
159+
}

forecast/main.go

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package main
55
import (
66
"bytes"
77
"context"
8+
_ "embed"
9+
"encoding/json"
810
"flag"
911
"fmt"
1012
"image"
@@ -14,37 +16,72 @@ import (
1416
"log"
1517
"net/url"
1618
"os"
19+
"sort"
1720
"strings"
1821
"time"
1922

2023
"github.com/chromedp/cdproto/cdp"
2124
"github.com/chromedp/cdproto/dom"
2225
"github.com/chromedp/chromedp"
2326
"github.com/kenshaw/rasterm"
27+
"golang.org/x/exp/maps"
2428
)
2529

2630
const dataSel = `div[data-ve-view]`
2731

32+
const svgSel = `#wob_d svg path`
33+
2834
func main() {
2935
verbose := flag.Bool("v", false, "verbose")
3036
timeout := flag.Duration("timeout", 1*time.Minute, "timeout")
31-
query := flag.String("q", "", "query")
32-
lang := flag.String("hl", "en", "language")
37+
query := flag.String("q", "", "weather query")
38+
lang := flag.String("hl", "", "language (see hl.json)")
3339
unit := flag.String("unit", "", "temperature unit (C, F, or blank)")
40+
typ := flag.String("type", "", "selection type (temp, rain, wind)")
41+
day := flag.Int("day", 0, "day (0-7)")
3442
scale := flag.Float64("scale", 1.5, "scale")
3543
padding := flag.Int("padding", 20, "padding")
3644
out := flag.String("out", "", "out file")
3745
flag.Parse()
38-
if err := run(context.Background(), *verbose, *timeout, *query, *lang, *unit, *scale, *padding, *out); err != nil {
46+
if err := run(context.Background(), *verbose, *timeout, *query, *lang, *unit, *typ, *day, *scale, *padding, *out); err != nil {
3947
fmt.Fprintf(os.Stderr, "error: %v\n", err)
48+
if strings.HasPrefix(err.Error(), "invalid lang ") {
49+
fmt.Fprint(os.Stderr, "\nvalid languages:\n")
50+
keys := maps.Keys(langs)
51+
sort.Strings(keys)
52+
for _, key := range keys {
53+
fmt.Fprintf(os.Stderr, " %s:\t%s\n", key, langs[key])
54+
}
55+
}
4056
os.Exit(1)
4157
}
4258
}
4359

44-
func run(ctx context.Context, verbose bool, timeout time.Duration, query, lang, unit string, scale float64, padding int, out string) error {
60+
func run(ctx context.Context, verbose bool, timeout time.Duration, query, lang, unit, typ string, day int, scale float64, padding int, out string) error {
61+
// check
62+
lang = strings.ToLower(lang)
63+
if _, ok := langs[lang]; !ok && lang != "" {
64+
return fmt.Errorf("invalid lang %q", lang)
65+
}
4566
if unit = strings.ToUpper(unit); unit != "F" && unit != "C" && unit != "" {
4667
return fmt.Errorf("invalid unit %q", unit)
4768
}
69+
switch typ = strings.ToLower(typ); typ {
70+
case "temp":
71+
typ = ""
72+
case "", "rain", "wind":
73+
default:
74+
return fmt.Errorf("invalid type %q", typ)
75+
}
76+
if day < 0 || day > 7 {
77+
return fmt.Errorf("invalid day %d", day)
78+
}
79+
if scale <= 0 {
80+
return fmt.Errorf("invalid scale %f", scale)
81+
}
82+
if padding < 0 {
83+
return fmt.Errorf("invalid padding %d", padding)
84+
}
4885

4986
query = "weather " + query
5087

@@ -72,22 +109,32 @@ func run(ctx context.Context, verbose bool, timeout time.Duration, query, lang,
72109
if err := chromedp.Run(ctx,
73110
chromedp.Navigate("https://www.google.com/search?"+v.Encode()),
74111
chromedp.WaitVisible(dataSel, chromedp.ByQuery),
112+
chromedp.WaitVisible(svgSel, chromedp.ByQuery),
75113
chromedp.Nodes(dataSel, &nodes, chromedp.ByQuery, chromedp.NodeVisible),
76114
chromedp.ActionFunc(func(ctx context.Context) error {
77115
return dom.RequestChildNodes(nodes[0].NodeID).WithDepth(-1).Do(ctx)
78116
}),
79-
chromedp.Sleep(50*time.Millisecond),
80117
); err != nil {
81118
return err
82119
}
83120

121+
// click on unit
84122
if unit != "" {
85-
// click on unit button if present
86123
if node := findNode(`°`+unit, nodes); node != nil {
87124
_ = chromedp.Run(ctx, chromedp.MouseClickNode(node))
88125
}
89126
}
90127

128+
// click on type
129+
if typ != "" {
130+
_ = chromedp.Run(ctx, chromedp.Click("wob_"+typ, chromedp.ByID))
131+
}
132+
133+
// click on day
134+
if day != 0 {
135+
_ = chromedp.Run(ctx, chromedp.Click(fmt.Sprintf(`//*[@data-wob-di=%d]`, day)))
136+
}
137+
91138
// capture screenshot
92139
var buf []byte
93140
if err := chromedp.Run(ctx, chromedp.ScreenshotScale(dataSel, scale, &buf, chromedp.ByQuery)); err != nil {
@@ -140,3 +187,14 @@ func findNode(val string, nodes []*cdp.Node) *cdp.Node {
140187
}
141188
return nil
142189
}
190+
191+
var langs map[string]string
192+
193+
func init() {
194+
if err := json.Unmarshal(hlJSON, &langs); err != nil {
195+
panic(err)
196+
}
197+
}
198+
199+
//go:embed hl.json
200+
var hlJSON []byte

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/chromedp/cdproto v0.0.0-20231011050154-1d073bb38998
99
github.com/chromedp/chromedp v0.9.3
1010
github.com/kenshaw/rasterm v0.1.8
11+
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
1112
)
1213

1314
require (

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhA
2424
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
2525
github.com/soniakeys/quant v1.0.0 h1:N1um9ktjbkZVcywBVAAYpZYSHxEfJGzshHCxx/DaI0Y=
2626
github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsImz6Y6zds=
27+
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
28+
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
2729
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2830
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
2931
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

subtree/main.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,20 @@ func main() {
6464
// Users get confused sometimes (why node.Children is empty while node.ChildNodeCount > 0?).
6565
// And some users want to travel a subtree of the DOM more easy.
6666
// So here comes the example.
67-
func travelSubtree(pageUrl, of string, opts ...chromedp.QueryOption) chromedp.Tasks {
67+
func travelSubtree(urlstr string, sel interface{}, opts ...chromedp.QueryOption) chromedp.Tasks {
6868
var nodes []*cdp.Node
6969
return chromedp.Tasks{
70-
chromedp.Navigate(pageUrl),
71-
chromedp.Nodes(of, &nodes, opts...),
70+
chromedp.Navigate(urlstr),
71+
chromedp.Nodes(sel, &nodes, opts...),
7272
// ask chromedp to populate the subtree of a node
73-
chromedp.ActionFunc(func(c context.Context) error {
73+
chromedp.ActionFunc(func(ctx context.Context) error {
7474
// depth -1 for the entire subtree
7575
// do your best to limit the size of the subtree
76-
return dom.RequestChildNodes(nodes[0].NodeID).WithDepth(-1).Do(c)
76+
return dom.RequestChildNodes(nodes[0].NodeID).WithDepth(-1).Do(ctx)
7777
}),
7878
// wait a little while for dom.EventSetChildNodes to be fired and handled
7979
chromedp.Sleep(time.Second),
80-
chromedp.ActionFunc(func(c context.Context) error {
80+
chromedp.ActionFunc(func(ctx context.Context) error {
8181
printNodes(os.Stdout, nodes, "", " ")
8282
return nil
8383
}),

0 commit comments

Comments
 (0)