diff --git a/Makefile b/Makefile index dbe1eb7..b24778e 100644 --- a/Makefile +++ b/Makefile @@ -220,3 +220,6 @@ docker-push: docker-run-ssh: docker run -p 2222:22 -v ~/.ssh/demo:/keys --entrypoint cointop -it cointop/cointop server -k /keys/id_rsa + +mp3: + cat <(printf "package notifier\nfunc Mp3() string {\nreturn \`" "") <(xxd -p media/notification.mp3 | tr -d "\n") <(printf "\`\n}" "") > pkg/notifier/mp3.go diff --git a/go.mod b/go.mod index d70d34a..be80829 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,9 @@ require ( github.com/anaskhan96/soup v1.1.1 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/creack/pty v1.1.11 + github.com/faiface/beep v1.0.2 github.com/fatih/color v1.9.0 + github.com/gen2brain/beeep v0.0.0-20200526185328-e9c15c258e28 github.com/gliderlabs/ssh v0.3.0 github.com/maruel/panicparse v1.5.0 github.com/mattn/go-colorable v0.1.7 // indirect diff --git a/go.sum b/go.sum index d96e396..45b2ceb 100644 --- a/go.sum +++ b/go.sum @@ -26,10 +26,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/faiface/beep v1.0.2 h1:UB5DiRNmA4erfUYnHbgU4UB6DlBOrsdEFRtcc8sCkdQ= +github.com/faiface/beep v1.0.2/go.mod h1:1yLb5yRdHMsovYYWVqYLioXkVuziCSITW1oarTeduQM= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell v1.1.1/go.mod h1:K1udHkiR3cOtlpKG5tZPD5XxrF7v2y7lDq7Whcj+xkQ= +github.com/gen2brain/beeep v0.0.0-20200526185328-e9c15c258e28 h1:M2Zt3G2w6Q57GZndOYk42p7RvMeO8izO8yKTfIxGqxA= +github.com/gen2brain/beeep v0.0.0-20200526185328-e9c15c258e28/go.mod h1:ElSskYZe3oM8kThaHGJ+kiN2yyUMVXMZ7WxF9QqLDS8= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.3.0 h1:7GcKy4erEljCE/QeQ2jTVpu+3f3zkpZOxOJjFYkMqYU= github.com/gliderlabs/ssh v0.3.0/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= @@ -37,6 +43,10 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE= +github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10= +github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -48,13 +58,27 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw= +github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherwasm v0.1.1/go.mod h1:kx4n9a+MzHH0BJJhvlsQ65hqLFXDO/m256AsaDPQ+/4= +github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI= +github.com/gopherjs/gopherwasm v1.1.0 h1:fA2uLoctU5+T3OhOn2vYP0DVT6pxc7xhTlBB1paATqQ= +github.com/gopherjs/gopherwasm v1.1.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hajimehoshi/go-mp3 v0.1.1 h1:Y33fAdTma70fkrxnc9u50Uq0lV6eZ+bkAlssdMmCwUc= +github.com/hajimehoshi/go-mp3 v0.1.1/go.mod h1:4i+c5pDNKDrxl1iu9iG90/+fhP37lio6gNhjCx9WBJw= +github.com/hajimehoshi/oto v0.1.1/go.mod h1:hUiLWeBQnbDu4pZsAhOnGqMI1ZGibS6e2qhQdfpwz04= +github.com/hajimehoshi/oto v0.3.1 h1:cpf/uIv4Q0oc5uf9loQn7PIehv+mZerh+0KKma6gzMk= +github.com/hajimehoshi/oto v0.3.1/go.mod h1:e9eTLBB9iZto045HLbzfHJIc+jP3xaKrjZTghvb6fdM= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM= +github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -65,6 +89,7 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/maruel/panicparse v1.5.0 h1:etK4QAf/Spw8eyowKbOHRkOfhblp/kahGUy96RvbMjI= github.com/maruel/panicparse v1.5.0/go.mod h1:aOutY/MUjdj80R0AEVI9qE2zHqig+67t2ffUDDiLzAM= @@ -77,11 +102,13 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mewkiz/flac v1.0.5/go.mod h1:EHZNU32dMF6alpurYyKHDLYpW1lYpBZ5WrXi/VuNIGs= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miguelmota/go-coinmarketcap v0.1.6 h1:YIe+VdFhEgyGESfmkL7BHRDIdf6CUOAjJisml01AFqs= github.com/miguelmota/go-coinmarketcap v0.1.6/go.mod h1:Jdv/kqtKclIElmoNAZMMJn0DSQv+j7p/H1te/GGnxhA= @@ -94,6 +121,8 @@ github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9 github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= @@ -101,6 +130,9 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= @@ -129,6 +161,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk= +github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e h1:Ee+VZw13r9NTOMnwTPs6O5KZ0MJU54hsxu9FpZ4pQ10= github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e/go.mod h1:fSIW/szJHsRts/4U8wlMPhs+YqJC+7NYR+Qqb1uJVpA= @@ -144,8 +178,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd h1:nLIcFw7GiqKXUS7HiChg6OAYWgASB2H97dZKd1GhDSs= +golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20180806140643-507816974b79 h1:t2JRgCWkY7Qaa1J2jal+wqC9OjbyHCHwIA9rVlRUSMo= +golang.org/x/mobile v0.0.0-20180806140643-507816974b79/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/net v0.0.0-20180215212450-dc948dff8834/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -163,12 +203,14 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed h1:WBkVNH1zd9jg/dK4HCM4lNANnmd12EHC9z+LmcCG4ns= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -186,6 +228,7 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/media/notification.mp3 b/media/notification.mp3 new file mode 100644 index 0000000..491cdc2 Binary files /dev/null and b/media/notification.mp3 differ diff --git a/pkg/notifier/mp3.go b/pkg/notifier/mp3.go new file mode 100644 index 0000000..1bb1306 --- /dev/null +++ b/pkg/notifier/mp3.go @@ -0,0 +1,4 @@ +package notifier +func Mp3() string { +return `fffb90c40000000000000000000000000000000000496e666f0000000f000000840000d923000305070b0d0f1315171b1d1f2224262a2c2e3234363a3c3e414345474b4d4f5355575b5d5f6264666a6c6e7274767a7c7e818385878b8d8f9395979b9d9fa2a4a6aaacaeb2b4b6babcbec1c3c5c7cbcdcfd3d5d7dbdddfe2e4e6eaeceef2f4f6fafcfe000000394c414d45332e31303001cd000000002e13000014802406a8420000800000d923ac07a296000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffb90c4000053f9ecef031879caeb419f428c90000210420041d3b68733231edb0f27a9b3eb934d90d332d907be78393d83f4838461e9b4639e1127a6d1e3240617587a7db79f40883eba0e9b102081e9b934ecf269ffae4f5d882989d1021366001400c3c99441564c00a269ef7dce4c2d362100881887876204321076211eec867e7ebeb774db18102248493a8cb640c2087f10e4d0b600207eb427eb9b9ee849c4dcdc208c211a201c4e5033a002111bcb40cd00dbc5110b02228423745a05431ace9d84d12eb7c61a8175b9e626691c5e8e28dccd6ce3dd67ad66e6a54487114a6e18cdb9b916b2ca91d66525850ab3243f544e9a5254ac1f8d9a33a285968aa936440c91a87882523c489ae862b4e49cb74c9b44b36289152cab68e329d45bd8966184ef5b32f141936bcc9d642b3fa47cab963296b770791a4a215dbdc21622488a9924956a893295f4bb9cba461488a1d680b2149ee511a931f23ab5e0b17cb5667a455640888e5d4f5041054010180406040309101010293cc140d00c381ab9581dd8867de307c1132d04e34f81a37995bb78183a2281853161d4c1b0fffb92c418801b018b2199da8002db21a54bbba000b505819982c062d2581f1176061217a92603198140c4e150bde06673b0194c5006343c26a306713313c8595819805c066d0e8183c3e06342c26b741601c0f291aa4620635120220608061ec03504b3ef1c62e33ec90eb0c0420f06f19121de412eaa171c62732e2cfaa06060900a108592490e21c1ffadd4ba996a26cd9632e52598b9bfffeafdcaa59193335199aa967cb9fffeff7b37d24d04de641abdfffffffe1642db959e0008f804c2d510c53f08f0c0bcc030e4880630f40b30743f000f061204a60d01e2802808091103660f8280c00d314c0339cd921033030620cfae33ac4047993b2c11832fb8c8a0e04062684a110d42c779d981eb51ca5cf7c850501c0acd7f695dddc72350f61dcabc66331a8f3bd0f0e866ff099c29bf29567dc32fc7559650583bf516addfffdeb1feef1e7fef2ffffd7759c0d73b87e597d6c71fdfffef9965dfffc6f3bd461083a2ea028043b42d727eb76d6268a901915d90c0d038c4c2c0e718247f3931541030500a30b41530481130101546240738aa020c044c080280000981e039fffb92c4178314e9152a6ec87c83a22be2c5fdadf883c7587098b71a46952b335359c5b2d2cbd32c56c6bf16953f520b54b4922a8f08e0440e076cd885b42bcd6a1982a5b289a78dfa791ff947d232f55d67fa8a91535482ea452019211e1062cd238e8bacd2b74533cca5a975dd02796dd57bd3e3550e30d58f0b31662e1230f3a6765c36cf4a6d84ef956f5a4103022c0f81e02fcc19f6524c5420d04c11b04b8c03102b4c0960208700403015c07122016c42005888009300640103000002d301800413012802e000016603305366763a61650303c61e6c6123c676003c12aed88a268d0cb583151a415505610ac4d919dbc6a1c2c02e1aa500dc1c795d335d5febdabcec51fe853182e233e401b7aff4ff20b11a7a9d4e4fe5ca78d3d4fcbfb721fb18e7349da0ea00070324a58a39390e4acf1f5169862f75f795181b44cdf33cbb9ae5e71d3f19b45cbbbba8b8fba6c5f34ce2e1b3130cbd8ce2ad479244eb615246ab53dc98abf41f193eab98a4d4ce84cca10420016051311707b3530e3538811ff30c202c1607f3007013302301f30060222200e40197795850c0c0240cfffb92c41603189143184f710fc325aee285ee21f814f0401b181c0a59cbc0e633081709a0b6412074b155a0380dd17e44011795f98bc4e81c15f8dd9b3c3cfc98246c25cf4835e6a2f24a96a7a214b4094b3576f50beb0c3fe000b930628eb638e3626a2db94fd8d7d2b183058441e0b89bae95e5ac78e7ee79b5b88c504dd344d6fdd4c7058b9c41db7d6bcd3cd731e8fc8e86f8c60ecc36126cb22ad4e764934a4be1ed8a5f6689b4e61d796040c1880b4c3c4368cb08b3cfb24034c46805cc0f40f4c1500389809cc03802042018bdda096c90c8ac024980181004a60580f27652e05c3e23093d2a52aed9e46e1eb9d64ca52a32bd20ebbb8ddc76a0a8cb3830397cde2262fdbfeba550476f4bead5bcdca7f2b525a48cdc0209848330f4b73bd670cafeff2b3cb7edcc0a1a0a112a7ed150f8ac1d8f91d2755ef8f0637ab316f8aa49dc87e63d9faaaff6eaa7b98d62eaf879e2beee2ad2eae13e576e6a068bda2d6214a176acf2108957a03ec73c58c3ed6de920040c1e021cc4c4890d343318fafc758c4881c4c2741c0c02c000c02c0694cd3a53961f60adc93213081a00fffb92c41583985d0f122f6d6fdb24aba205fe21f9c4006c60c9c628243810d6d2d12ce04811faab0b8d0c003defe37d6ef4fd6eba4fbaca25af111d36181d6aa104a33c2be766c6aae3f729648234046bb5cffef7f0ffd5dbf8f6c18c06807f4a2c5ea1e34b36b363779d5b75ee98bcac85be78be5f1dd325d2b24d7b78cf065007a8a440e43de3961b96b8da737699df39fe568bb607777afb7d923027fc5d947f0fc93bb537f76f2a432ea983029814b303743a230e31e5e3180c3d4302401153015c07e0b00c63a00d1803a00020b94000090cfcc3495f2750426004cd102b4e269a88cb115dc172b95d0341a446e8e486ec6e4319ad8e100b846083e9c081c5000678aa495b3972ed2e522c73977e724a7b018061e1959cbfbacb1d65db3f8eb72a0e1f02cb5298791031c6dceb3507415232a981d1c8ffa70a99e912c51b0cd37156b174b57131df0d3f330bc4d7d71690dda99999610ad15979e251fb5af97c9f59d5fb12e7857d4e87c8d27a41c55fbf07020281d2608e05bc6397b4d866fb06d460ab81ca602e0088601d804e600400389b6600880120a00212ee0b6628fffb92c4160119897b100ff0cfcaf2246259fd21f9c51167a55010cc3a0310075e7779ce95412f34ec71ec91c1af93d50743520965b9cc2cb5f305080e10112f42f05864ab83f9857f8bc5ef5fa7b537949454629254bacf0cb2ab773b9da4c35a7c448116aa3e69cec63a8ff945b6b57adce68ffab5fee65d552acedd8df9bfffbb3d3d79ffe353421f3ef68dcdc777cb6fdebee7669a9399be23f1e55bab53d2e72c9772dbe53d6db5a17810403e2b8ddbdbe50501082403020c02230570000318d40c5268a30c1910038040aa93018e60130000090024200036a8f1d3412b00f0560b00125da0b16ce215e10ecb938c521fa48fc02fccb2532fe5da4dc918188ba1cc46d218a0b025118c4b65304d998cebdffe7dc89880ca2c6f5cbdbc30ef79cfb3499dc616a3056eceea44214dcca20af39126d14904bb54239fefaf5f68effe15d51ba3279bce115675c9bc70b8ffe2eaca441aea38677f7b5d87fdcf7151be351ea9cdaf65ccd58304852ea37aaa3030c0c630518197313f576b3457831b305e40de30248039300e401a300200133007400048861ca2aeb4065ed7597a8efffb92c4180018dd9f0e0fec6fcb62a7e8f5dd0a7e80282c688a77a290147e0c6f60783650ecbdb19a92f8a4ccb756a3348dc4131073e42bfa5abad3ca76e6757bde4ab2bfba1c6b0200068465d4bcdfe35b2e7dcfe6feba95280d3d520b72061508e8a212496bc83ce209d6115e68c2cdb0f45aa9be33665b96f0eebb9a11542aa14c399f739446879c235718ce11a05c41d335d26b0176f980e925d7c4c8833a247165cc2b59822e4ad2ae007becfda0c9624299998c13a61802c2c0e29b8500430001632d0593211333e26093639d4310cab3150ba30a4cd31b44030c8190e0d90a1e91083261e86e652a6464825e6ae87265e164658505170d502d30b1d39afcaa90d01032858ca020a26964ae5f31394ee7525aa4c33943d6a0f2ebb4482ed821d912ec7d5e74ecc339b77e0774dda41c805622f930e34583c39729587c4587c0f0c380e44cbff2f95c823ee7810310ab3a73499f2b95c4cdd764330b963ff413b1e71e6a0d77e21454fae53cb274bf06063885367229ff745398431fac003610d88a000040188001812015004301409531d253f344d51f307601a300b01503fffb92c40e8018dd4125af6c51c2b620a409edc1b803c1810801981001d182f01589021008114c01c005668040000c01242096601c0220200c4891100198b101d1e0249d5c708a9cea2fcc616ccd424481109ec051e53081434bc16d0d1485c10c1c29249e74e644f26168acb64ce5402d60a80662c28ecccbed13756a65bb5946a7aa61ba182df5a677ac5308009bea5d534f5fce330cbfb4bbb356c5365118e3fe66c24a154aaacfd7edf6de9735c0132eca969baa292867b9ec2c8f7310000d4604808260561ac61ec40e7acaaac610a07a600400e603a024300bc5bb1603110817a7b17896342d681811785058b00a62c62620607dbb6200c6f90c4bec220d28276b92c7e5f978c58064d0f0f033fcc965d02c44702d2f2fcd43194b626300068832f24f6772456a829a9b4e1cba66dbaa32a89839f73c17f8784ab44d6a736a0a4eec8a2600311516d4d067536dfd97ad6ce8de6c6416419b093942a3daca8eb1ec259752dcebdf451d15020c1c8194c30c438c97a798e9f0320c7a0094c2b41d0c1e0094c0a402cc0a8024a8054101043401cd611b54761d10869880c00874fffb92c41a831741031a2f6e6dc2cda72305ed45b81418022339af60e5a2d4a5c8f033b2f2b29705e591bed47158e0f00d151d4e4590e23cb0d7620bca9a5d190b831c086aa296fc467f99637b4c2658ddafc344193968f9f2884e232264929231a2b264d547d9b5394c021662a77f5742ea65ad9bb2eb5294c6248746b4bb9e260983c2ad716eb2dbdd0bb1f2eec5bb7ddeda228db1ef63fd8d1387003cc08c410c36fb70eba54c4c51c0ecc110038c0dc05cc05807c0a00eb3d0105c686e24adae6b2830e511601a303801f7120eb02c69b5409c4dce9e9648e552fa48ac694dacc5b39350a20075d8e4edc9557820b05c191a0da96a1b732a6ef6be40e6c35cb679cc4f1d770da44d514a9a17ba9564d54a7025892641dd1a4921b3a914e920a5a6ad74990322614eda2d7b5b529299592a4c66b28f3cc0d892ab479d38c82cf0a4ca9437ccf5d56fd9dba52a00149c9060052ef198b171f0d1e18982318ac12252b40425b442d62a46426018160a03dafb1368c8f68a6d6c4002d44844241721bd478534548ba28a1b7fe8df444f45371e9e9e1b86a4affb385d8ce1f8c66058fffb92c42a0314653f2e6ebcda4390282489ede898097e2a35b1c6b7e44310c1b8260ccc948113bc79de3c2a20529aa3c01838593260301934e1ef609a118f7af77777fc4102104023608108c6f711f1efbba119fb7fbff8c8686befed3100d1ebfca3b50004c1805800182d07898b888719c02f29b58a10824174d8854129a6923a1615165b002083878f7188dd480d69635aa8e3be358b4c6230c3a660c83879877e063672869aefa69c81a13c67480f07060c5ee63461991461d219c262208c09305a58d0d5dcbbd0030d3720b0305012ecb882d0d25c990092831a28c28246a30610b5c98c5a6342bd1d96164336d670ce1d7f6d5f97c66cdec682d635f2dd996cabb2ab306c6a352a94f6fcec46313f8f33fad5af5ebf72d52d98f295afe7da7b3d63de5daf4b6696cd2e729a5cb2efe5bd65731feff3fbbfd7ef1c5a45a115add1dffffff65500261302704230360d33101665337f22a362d628306d063303708230690423049036260490c0275e6020830f2b33f083071050757ab46056251e8fa9131c0d0a8e1d580aa14034be2afab3a90475605059fb7fafbeb09cc880fffb92c42c8315613f224f6e8bc23527e4cded4cb8a939490f5a892fb24029d7da081c44c14ca8a385b0350282ca49e456cb5b22a618d75556372f974a283a4b30322285773218e40d114daad7d2daa711139b21adab32a955af7bcfb21fff93eefb7fecfeafe800026b2cc01c060541d4c0813ecd5011ace8d8048c47004ce19e225a443d05d9c402fa191204c89e575acbbb2fb8c8fae2c3c99272452916b96bcb0553636366a478c543e4648853ac6ab3321803191cc272e5b260963a5f20007330e724b4eaa75a87212aeaa0cfd7b949599aa9d6d32558c1369f4903c745ca2da92dfb75d4f7a92d97affff35fc6747d7f6a55034c02803c3808cc559544eafd160cc75964c164320700952a919d22cbf288d111202460922aee0c21dd7edc141e46004a30247380497aab88d52cfccce50fca7b671a6a65155c11cafaa9f4ef5062350250669b2d8e3843a059c3ccf09846aaf61177875cf661e05c2d344c5c74d34a679a73e3ecec43647565a4c6cfa540cd9d4fb5d92d73117d537a7ff1b7eb5d3428bdaffb8da0c1980ecc2fc240cbb8c40d630c48ffd5460c9f4284f214fffb92c4568392a541202f68edd26a27e345edd0b80dd4d4cc860c3880c141de867a0e0f5daeecfe4d761a8d0e012f6430010511009c90ab3650f77603269050bf26c87b245026944685060a45a816cd448c05c60d61718f2b383582ea8f9a0af0025016948c4daeecaa229eb53a69aa95dd25d6b317cd996dd2e935990652e5237b76aecfffb29bb297f75726687ffedfffafd372ffe94a10ad2301300814007308c70636057933a9854c314e091305d0141e0005382b00a0801f2dc30d1e0036e15dc6811c474e48a011b68ed59258e65a8fdd3c4a1d9f9cbf4d391798b78e4dd17ec4ac7e5dce905abf0f773f9dde1dccdad72b1cbf3cf0c759e515a3fdd4ced0a28e20380057211de7015769ce8866964766e71fbfe4ffe76f56ffc17fcadde9eda9a2b600ad4498307261c861800a9718ab41b1adf3159813837193399c6981b7058113c1a3c60a54257821187e1025104c233701124805111830316a47879375e701132fc54e9be8060b2a98f241dbfd98e68183469b613930f23dbb8050104092662641503e05b10fe3519826bc79fd600c452a88801a1bb9669c583632eefffb92c4850311ad41204f604fc38628244dcf6c084ddfa49b972e856f66af7b588b40952077e5a51a3611e018038fd5ea7234f6b2b008f6adf1153a5816b5393d0964eb5d824fd9fa7de5288d59a7771df67117775cf81a975befe1dc399eb9adeb1effef2bb6353735aee3ddfe58ebff1ffbba9decabfe5bd30028606005980782818559b71aed2009d6b8d0988c2ea99068359684d0bc70d1c24433034146e2e5d02c132734ab31055354a057ece5f887c5996250530e8d1788e017f61c609d85a86f8aa4c8690514e22441903e8975024c31111622c78d85308b9b5cc800430f4cb45965d4133273745d271f877804d00a801dc5d4133575ba2a36be8ad9ead76d152924c7cabff6fffaca9096eb5dfffdb2babb4da3045040307209131f130734171f333a47d633d853c33db0fc3084065741134c6430c68542029359855b91158ca029934ec92ebaeff3a0bbfe252cb540ed98347a11a9f8c47deda4a6eb4685d95ea59a0160ac9179c5a836a7a8b2050b938d30a99ea4ca2b652c6e0e2022202944acc9a2aafedbd6eb5ad052d1b97bacb46bfe9e87ecbf5156f0ff3e9deffffb92c49403938921224f668782612463c9edd1381712543ed754f576550f92feb5008001fe600a00060180a660c29866d8c33871b454a61aab5c628c124600203a880980b3cb482c443cfef18db0e8fc52210cd1cc54aed7bf2b19e34e22e503a47b4caaab11c37d2f9c139a5b38a9bd4a0bd062ecabfd07e52488183e5fffab7d4ea5fb3b6ec4a12659c543178a0e0b34a24443286282a9abf4d814eda99409269adc4982c04d79998a0899a0a63eb98ca241598a78098981ba01f0b950a183287c1311349a043d2560245119d3bb931274a96089f4e1aef4d5cf91a305c4b65208b455924af0d4148c74b7b7963a7ad2dbbabf7daaa395fb1dbc0e0efd63f95fa5e7feee47ac7e7f014c108d1e9b9191480e2bbdcab0dd01fd64aebef586c8d075553fb2b55b676492decce24666b721f60bfa5c378cd051000180043bcac080c1f4094c9c93fcc574bd0e656420cc0b08cce0c49cc1300b07804c1c01606000301702101013b48799ff4da06000cf43f244d57e9a74dcb163dd78f785d92100218b01a4e3d8644218e986846c4d1756693502c92a9b1aa0193898a0b3233002fffb92c4c0028fc4eb24cf6647c266952301bfe81088e6e608a46f6a8cd1d0291702510542955ef7d1a861aebe643c13ba694e12d24cb736fa91512e89f303562ae4ec789fec7e5723e551d773e54d6e7aa4532266ae72a18c9f463e5a93266fa1c12d11e37dfbd694d3a400300c034090c0f81c0c5a1378cd55bd4f9f0460d271d58c75d040ce106b0c0a00e0c454394c4d88c713cc8918d69e0e6ad0e8108cc5bce1d74d7020c0c74c94f4d5524cccacced44d5d54d451ccd4454259918c251b6351a40e01820402e1890632b275d204e1488b1a0706f9f1c07c6e171a44c6706018ba12d05e5ca2e6145997129ac638e1b12e32082062cd85a682660246932692b488aa7396ccbc698eb1d63a603c8ccd22d97da861fc9c4754d86e690e98ebadd3008931214cb2839998ee2231eccd48d4176df6cc0140dbf80d97cffba6b1e16edb96d7e4728861fc8c390b0e98f2fcf3ce9e9edd7b7288739de7e34949becc525efcf0e6fee525887ca043e200f9f89cd1c1039fee802836b93a3fffdc00018301801f30790de30fe41e349163032ff49d32a760435f324037681a2076e870fffb92c4fa8217b99d192f4d170454a5e389ede930e9aa561c70898f318205895e0d9adcf1060cfc40cfb5c084c0125102f1adb198307930205010d1550c98eccd0147158d059c1cd261e2c65e2042583a066bafe736179814188acc19e46c6a5498c9b6608048b04515c04292d8960100108028466312818f448240243a1814080a053d66240787149eb83e552168c8a4e335ea75f2a625e17b61e77aec5e52dcd88aa57529addb70ac3e86010805692301905084b825b55358ab726bda769de8d55a07fb74afc53f2bdec2e4b691dea6c65d76316b96d714cb66ceae1434d1194d351cae96cd7cb3a4954babd2cee566b56cadd4e67aa6fe615f2fb97c5e81254446ab88a79164006d996ad602ab46d420a73499464abc267970e6c8172607010bd0cba6210d49284aa5dc5e677d7243744adcb7abcebf56f3d302575c96cec41ae05688631ca5b531c28f4646e9ae7c9a279d4a303052cc013046489a3d4faacfb3aa75fcc524512040601b0931aa4dfff39feb524b651c385f11a93a606eb5b29d140bc2866a88898e400987dab2d9ab2efabeea0002a3005002301800f30a50fffb92c4d781a29557224f6f87c237a3269ddd5138e4314f1393380178314834530105db32250a131416030c190999601574a3cc16cd4ba09443050e83fee12994ae18fcaf535687ec53bd0c8d00055b5034a04b84aa055313052540d9261c3317519164993526c0493763ece95d4934eb55663ba9e4722a71c206b5187007d9fffe64dfd7fad430dcccb316c5a9082a7515f39c07fff6950638e91801010985c9449a5b1ad199717c98b81fb18d90111ad4813983100c180801501430c18092980bc699e2220e7bc1a584c6b13dae9b3d9ecb97728a535d5e023095886547636deb821a8b4fd9a1a928bf15b9b92385bbdca78bc8b2b128308189746619cf23634673440c0d4b66a7d14e74af7758f941d022e0363438f3474287a9fea2d7db4d34dd6e8ac6082e17730400c0559023335a99906941e558cbe9247d257994550000137080028020ee602881264e668670760b46582af466a5650691e2a060b002a2a03469d4e60a2261604de3e106840337861a4a24c2fd3b8b0d03d8ab23fa9043b32e8cbc45509118199d631d882206bbd57729bd2929144aa6c5c23c4b4e9ab1fffb92c4cc8391e1032c4f6e87d29ca064cdedd1b8012a8b8921ca2b0226e2c0521673a8bc9ae91c2b189a97915a2a353fad894376614c0386e85c8576d3749d35e92544cc907ef64a8b29d49990a0cd5955d4e9b18add25db453a9d6a536f6db4a751b99296a9c48d4ddc366cd615baddc942150242406f31608cb352336035fc7e6339396530535898324f836e342d05326c8b34015a315c981a31c1c0e2412a997f9080467980855634cfd5a215159b9641361231a566e22be770c3b0d4c31af8c730e81a01bf90cd2bd6b11585ed87e252186a3a280643ec0a6a50d01deaf4ea766260769af236972ca9629b93f4b2585bb6e46eb654b56cbcdcb762bc1715a380894013138a94358f5dd65fafc39bd7f39dec1b3b96f1fffe6b7fce7ebf17f236ed2e3826696cf4e8c638045424a4982c5cf0644439a826714d36168bb393dab07103fcc458cea5000195d6603202462f027a65dc72e79b8b1a6add036722214c7cc8158635216a61061a26094460603a12c1503143930b5e892ed30c05025c0c1171e8e4152a91ceadd7ea65a33bf2b8c40e401826299947706c6122b5debffffb92c4f78397e969226f6e89cb5d2062c5eff4803b2d54aefc42e52c2a79a008007298f4b9d55a6eed57fe0230d931a043ae245efeebe37f74346fc4269a6a92cd4a69fcff0df32c63adc884dcc1a77bcf472bb8b5b785acbae54cd578064546d1733b92858c218241479b792181c3e6479d972ab34de1c2f10948b1c243a834e096ead5defeb020bb31044cad030d039437933c638672dd31c6520342be1136081b93654a30e1537bd23242632a0b2ff34d9127fc54b2a4cdcf2d14adfab176f5aa67da1e94e104954203038c65986d862d39ba5eb4d9bcb1c69b1604a6195abb4b521897d2ba63242e25b85732afaffe728edd9a9dfc2dd6cbbffaadbd7674c0c75b5cbfff3ffd61aeeff5fabb9effff0ffe6b563f7dfce1a9dfcff0df7b9e154980883024925905b92b395b4501078c3a0ee071ed021d09351fabfd8a004d1053c3b0c1062968c9c61234c74f2738c5f91e38c86e6a54c9ad0ff4f197836c368e938b349134cb81a08198f020981ebf5508208410ce82da7bf4faedc68d4393d59cbbb59c12a070b4c341e39e01d973f39c85f0570ef514896d4fd2a8098140fffb92c4f28299512b184f70b1c2dba323e5af6c10b49c89cb624d6752c57461a0d249beed76d58a3cb1c69e45334b334b4b5aec4649639cab5e3b5b1d35a3150494fdec7f3d6b7cb986fbfac7fbdfe7eb787fef7cfef7fdc1f76ed9386676f3c0bfec762b6f3cf781ccd535c95faa72402a35ff9a3aedc530e7e7795ed65cf376ef89bf9aa148084ab2b00f3048036317d59e32d54113bd60db31f171b37002793b8501b3120048303a08230011b32a83f980180f20cb7680d92382380643408d3add2621edcc6120d5e7ea975647001e0109689a2c5a334b8c7200d61a801ecd46810090fc8ec6a6a2dcb5503059b34ecc7ef7579bc3fb52b5bb9f7f585ec77fca0eff6300e267933cbfb7b49dc33753d55ad3b4d713c45e00cdb7d73dc5ff5134d433f7abe62afd7e6ef8d55612d46d26328d8401c008c601c383d287b3746e8f77b9b5d5000800031a0c0380a4c0782a8c4144a4d741ec0d295a9cc819264c083f10dfec0ecc5043b0cf94d34c2fc148c4d010cc7b463cc02c3e0c4002a8c1c333349b4c6e06330924cde1730c100c7c12008640a2a333210d0864011a0c203efffb92c4f8021a8d05142d7f808afb2ea359eda238333248d68c03521780a7935baecd0e27314180c9c1a30c030d8c220a8acc6a1b2a048c340c5a8200318703862506abe4e4578fe20d8081660919993c966a0781cba346c0270147e655258388c62619018f08f260a0135d627051903188219022289a6f9e50008136e137c9111067181003c82114c638d2400c83babbe0f87295cb7524a0214b3861040a11a8a55968d0d196439b6e85fc4344df30c30821aa96cd006cbe371ba38619db8f3f108c524a1d89d701d494f6312947b30430706e68841324b050e98eaee34fe45357abd3e710670e250b0c6591d61ed7e7ea5258cf2a952f5786dcb87ec789c781a000049970206367d86eb6247e29a99a979493c0000203ca0428026611e29662cc47e66ca2bc60fc4e06326b9463780b460220a860581a46d0615a603201060e00aa60441da61c411c0d0141d404b20661a8ca22182a40296261c7903048351d390b44530dc39cc4ce0612246aab0a8e24eb5da5895a6ceb0aaf63ebb5dd7063aeebb8df8281c19ac6684a580c31b0862e0c050702a8e29b44683bcb134ff3bd2efffb92c4f4822b615b1b2f732dc385a76571eceda99a7fa5d334b4534ca9633295358aff6b5367525348ef5c954ba9bb6b2a6cb1ca9a555a456e1a8d675798ebbfbb3bc79aad6b1fffd56b5dc6b535c2606b57696cf2ad9c2d7ef54d1ae56955a39d24c6e4f3bb8a8a301f024302c07530174c333ad12c36b22fa31493b632bb23a387006630660a930ae67b31146ca302209732fc06666d380e6d6280430522938bc56938ef41841a0a46946c2345dcda262d2bcf0b53006622e6b5d7ead5553dba631299013320270619070584d0268800c388011670d172b13c3364b24606e78b89e8991b132686c6b41451492742b5a514e0c666aec8d9ea4968b2462666aa7ad074aeab2f56fd546a36050035ccbc7a7ffbbfa12852604200e61dc6a266c430066cc8326212a2661d359a6a3822e607611e63080727c7c23c6348050441cc606401282654885cc9494001586018d4719230775315a63408b2575220fd5aa6860a80424c03ba9aa4ab27a7c7b9cb69afcfc5555c4821cc0447781c296c99e575ac598ecff375ba7625d26f4c5a14b8cdceb6179ef80a846ba8987c6a39b4de34fffb92c49c839599191e0f66cb82a9a2e3c5e5b792e219b5dffeef6dafa87f0666687febf7e6e36b4a1371b154d0df6fa7bbf4d50400e4719a91009183804619c08549a0681c18410f998162f09904855996640184398199597908a460003a89c8f2cfaecaa43522b2dc2512de650429ea9b211489f60c38764b8b41574dea75a64585ac180d00d650166b9062eba1283d265d0569d5fd3a9ed74c6e1267bad25a4b5d06ad46eb4d553d1d7ebb75325c5f8a32eb41ab3d1eb934d1e8fd230803ed52980080a183112c18be045193b145184f16a18d40cd9a4780f980b03e187d85e1cce89d988e8058752838d87835289a8b27a58acaa3704d0569549dba29c5e71b1e57ca75275a852d4ad495ade1ddcd6b7a7f54748888c1750294f24967cd5c9f5f5ab4ec92aebdd36f7493960d755a93a904ee5d494e82d149e8a956d0415afa353c5d912393b36abbd48fa97e81fdbdaa0244402e60240ec6162ea8610e6fe74289666522b4c6a4df5c6df644466482ce61054566a5334c60d6332605a158040471081418002691c94ab5d18199b4c5e32e7aa0b9f210223453359563a38a47fffb92c4b703114919286f76a78250a2e489edd57864c300f7b6cdba9574f9eabd8ca96ec01011602e5639309ec05f3e89521b5cc9cd8d6f3b550f36dac357b88671c87d30de25bafd343f0a2c8e97653191b1ae57a4366ab6e7fdf0cee6b8bf99c5a74544f4b45ac61f15bd362d042924b6095f13307c522015488ad4f435cbe7562ef2ecd4841802401e85809e3052c3523005875c32b0c45d3098c627303951103123822a3030019d30b2035935e7030030ab80373043402c301f8031300f400a08169582468091049150e6c3390f46236fa05c18b3dd268b169c77699e42a805dc86f2ca2b5deea698b505c82666910c5022343630dc5017962803bcb3d452a61d773b95ad51958a9724bc242c072268bdc745c8b0a17309368badcdcc38c4898a5e1f8ebfe79e2693803444540f41d10c5e4c62693e855e9ae80850a91416435e3a36c262e175626b0fa9248751a818000064d00cd240280460303e6389167bf54a6a2cac69f32a743b907d403469f86461cf9c77271660d128210c4a80921dd6d3a7147e6a4b63735c96d261106872e8c4833fede49f56197e74b02c461e8dfffb92c4ee0317fd17162f716dc330a322c5fe21b85133ad12784f2560120a018890ef344eb5a9991b6c583fb7b1020cfc19110c0e09ecc6edd18649a2536c68d80f6498190c67f3fb25c2f0ebb04a15561dbde2f1b30870b196b4c5a83925dc4b9e262a80180ea03818168064983de46398eca0dc19f3808d988a849d18d2a9f5995d41239857e0ee188422d01c0361af987d2094183040419817e02a180d001398dc0018391a118a805943385facf9e49a8720f891303d9708410d59bf85d33f82215b37a57ae44ec32390d16f500c2edbba992000487074c169b3ba90915d9a43718b2f7614f7a7fa7dc7e072436c1c1f2b95955d9fa47d9aea854eb4ee8ed3f7d3a25d513374faa7435d0d63adf14ffa55f50f2328d5dbcfd32e5f5676d9c3398e214df735cbb86eef8dbb7db2fe5cc9fee223a967c3ae6eefed673cbf9414ff239a852280710e526a0202003d3018c0433055087531c705d631d9c63d312643583152942b30d7426930e00057300f0b6f32bb4b2c302941a5301600ae3001c0711400b02e2e89897ec6d5e58a07a998d587e7e46d3a5ca1eb4e91e377e5a401fffb90c4ee8213dd19254ed518839bb7a205fe2dbba4c07abd495e5739aab94393d52615e27487188e659b09134577a6e3d7a7219bd56b64362090526c8b4e39aa463b2a76b5458028dac6c66a38b2e6b8f1f4a283ee8488911bc277b47fcbd559bc3dee5a6debf34d8446536cf4df254295fbed80a53fe57158d96dcfebb081decf127faa1184cc38499ddf09100600000306024810a60cb05aa64428e0e644f8a1461528fde62f90aea65078216612f0186619509126b0e0504616a0202607380d06038004a601b803463c0610044c2134e1cba92311d80a579beafb7c65a14a2f4961d7e4981e9395ecc7643a95d88e6b29a64ef2070c8127cd68417acba5b96a2f10cb0e6c60f1f8393b88ca930f51b723ab741c57c3cefd73d72b2f49cdc354ed14efd63aa3195abdbb72e1b9eddfe7b377f6f43f5dff3b09ab365193fefeeba25126ff3b1e83d2f00d1ef0824cff576eaa080c02006cc08c1b0c5f8b50cd555f8f2b09f8d048aa4c493d00e664738c99c290c14a8f0ec2aabcc370438c14c2240008c2a032281c5d707002001a7b5ca677691fb91daeca5d22000847218bfffb92c4f18299e519122fed0ddb1aa0e295fda1b934ca188c52898cee5d90f7b75e2e6b38098d0f009803991b035d71acdcdc866f767bd18211e80f074e21b5dbe6d9bcb34f30602edba5ba6d261abb568d7e9aeaae5ebab8ffbfba1b14bdd5435fb3d47f8d93e85a8b19fb78bd57bc9dab6ad9fd9912c73ef6621ecf5898f373fff1573ba25bcf6030014a980240159803a03c181282d41842c0bc9820028e9822438a184d26f71858c0f61834e0471851221b1a20e17e98472051181240229808a00218040003808f29163ec05fe95dca94b471a9e694ea4f3ccead342e7ae36062faa5b16aa4ededd6f9dd4ccb9bb3f20c264fd604cfec5e8d7398fe6a6f81f6935bcf020878b4f474710c5cab46a978e888868b9d1fd526ea66279f9b71285c426450461b000d0b1973848ba86b80599b931f03098abcac410937d8e41fdcf545e9421a94c55d0040c80009cc048290c48ddf8dca50e0f87cb98d0e4ea0cd3dbb0f298578c8104a4c32e9ecf2febe0c53c354c0f81f01206a400443206200458eb4db3514959f43effbbf13871f46f4a8070cce4669ab3304e29759c32d4b6afffb92c4ed02180d41122f6d0dcb0221a2e5fd21b8e54d2596d04da8429539e20140196afc7ce92a4f3f3498f6a662871f62a145534c5ee1845536c68b49da037158bf77b29f2ae14c9219d969ae2263f498b6b41dbed32bffdc2d48d7e8fa8eb4fc66a977b4eab6f3bba28ce5e06a3146af35c99d971b379b8e1f77737fc2aa17cae621bf387f6cd8e00a54c05c030c0b8024c49c950e1b0bdcd928fd0c2119dcc8eea64d9b05ecc450324c8391b4fc18f40c78813cc20c0b0c0d0044c06c010200553396a390ffcba4d62188ec9e962f62189a64f0996c46b32066f4b35f4b7e733c2df3f1b923749be2401e1605d9ab15b2fa6aff958c4aba70c7914e9e81d8892a5499c0da00207c5bcc837cc107763bc859dc893e67997a52387653f90a5a6729a18e4b3b7efd9cf22ddd95e8c790c70994494f71e70e157913cf313fd3b2836a5f32850040418076016180f00329840624498836013990bc2f09881c1f29834e958989e61469841a09518250470199a0497982d007e1800e03c8300260a80323800426faaa280352b8d4ef489fe9e933b7199121e32e8dc4a19c1291debf6efffb92c4f30219957d10af6d0dcaf3ada2d9e40f882bf9c625bb9ade376dc3d126842a01b0b0062fa49f94971f9b18e572fb2edb553f4695ff34c72d77adab1bfadaa3462facf5658bbb5b4f45d053af1a1d69ec37d86aba1428e49d9bb7a538f041ee343a5babf4128a3c8738761a318cc6b63dd270b8e6f044425386aa1281d73f1d646d1cbae1b575c486c593c7e53d20c84344d24a12601e03e604c86a65746506cea1ce61ae82c65286526b8a0c260ea1906286694747660e626204860c802e604600c0a01501585e91e7ac8de722b970abd2b9087546f75231c2b154654f6871a06ab6def7bc4591d8218a1569dd21fad406648489447b38543e161702c7670fc54e3054bbdeb22229805cd1f1ac4f67d9e45476bba25f72f3abe9c7f503545d0d443a1750c2026012da5cabc7225e68913500881c03bc71e69d21ebfefffd54aa44012070603a196625cb0e66cb2c262f8da467465dc6badc506f5236464e412a61b6f4e7936eb062aa27260440fc601205e6000034150034dc4af646de51b7389cfd9db0e750b805a58f08b83183b874c384c5053115c61c5bfd6dbd4938fffb92c4f4821b0dab10afb07c8ac721e369e7a1b813417101c64ce0bec1c43cd2be2d1822dbc2ce738bbdde9e3ec535bfb2598aee067d21b9a22a068e88cda6a69dba9d8e7a065076914eb711cff6a465d326f31e1e5dcabcc8bcd7b862436c11f9e79155374faf652db33786c9f99f7d323a87dd7550cda89fe7201002730034058305e82ed321e478e3355048330560771307e53403121829e306581f5309f870735000600309380d330414064301d8043300ec0163008000528002c9800176182c3f1094cbe131f8ae2eed3a8e3b52e998e44e06579725f418bc53dab362bf65f2f7b9c0bce814008941aa9da06e549be6b7314d0acdef29f73a6892fcfd20efcb5407d2b589c0c70ab8ba214d3b2111cf38f04b5f52dc33b870a12179a7984dcc341cc7905360318fbba2a86dc34599ca73d4a9c92e45a958765a8c99763d62ae72fea74bcd888d62b14e95e52e8a7555200c0c80e4c174200c649408cfb0168e1454eccb505ccd9ad918e4e0318c96c05cc329a88f1e991cc4a04b8c1641d4c0800d0c00c054000045e34b8620cddf4a182e332cbd2487352a4f69b89c226bfffb92c4f5835881f7102f3c71cb5dc061c5f60f89268ed470c33c2fc6aa76deb0ca5b023709f758a0102479dba955badab95b9d95de03a5caca684e4c5e36527db581314dd67b6acb48c97732736855548e379440a73fd77a579961a99074e9134fac650c8c9131b362a8bc32cf949113525cad72e19fc2399bee69e168419086b903b9b255b21c17be82800a3003004230114834324dc37f3222c6c6301a0722311f4dff321e01d33093418a30abc63e346545033089c1363039007b301a403c300c001d3846070ad0a5ac1a5adca4714872cdf86a97a87786eec1721a443f683529e7a535a4f6792bb3fba4b3054aa4452e431952e738cc2bd9c2973851c3814a50f3cd5197c19ddbb42640243990e5abb7eda31ed48fa6b1ac268f5dab0d5bc6c2f6929fcbcd5cadd906a9fcaf4bd0f49ed652a2e2cdde74d34b8aadda21fb6b577a421d95ea7bfa9e3be7ae3ebd7c6ccf7755fc35c1304e750044007003980f022187ca331bdf1301cf90c718e780d19856f198cd8d198c00331847a9b9d75ab4189a845982c01f180f80d81001002148453395c396e7738bd046a8e5d9ca1dfffb92c4ee035885c5102f187c5b42bfa1c1fca1b8180e5913fc97cc2b5632de77f1ce9793166b4be6a250c09166a579678bef632cb7c6a1820c68f74126541d563a938296cc37565395e8e6661c67a3aea43b6544b4aed3511ac4dcc5434a3dd7d4d4d166b99957b2a59ecc8abb3e67bcc5aa4cf617457db1a740d1430163539cb354c49a140487802cc0e0008c6c4320c2588d4cb0db04c4dde14d541f74e6a43ccca3833cc9d98e4fde9370c8084fcc2841f8c1400d4c0a4070c02801cb6cb35894cc3545158cff619236584450fb888f5b830c9ba874c3582cf06c9ac476e9d99b198b2140782eb307970b52fbabfcbcc2eb3780cd1e033cd6c67c48958b6854cabdcb737a2c7804122d7c0b0bd33372216d74635dd48c31bc96291f93454628c389b292752c222299c2b5736d73d777667e99258635be54183258faba91e9b454ccdf2081b21bd45579f437b0240a0000a091d059908009cc26d064c670c40de948f0c4c8084c48a1d8d84c338c27c000c3210f8e5292a4c3c00d8e6a132018b7eb51afab81c8fdf98eaf74ac65f2aae29bcadc623f1c84399f0ff11eccf022fffb92c4ea035701bf12af60adc329baa1c9e78e39eb51f4d148c7a1a8af973888b7a85ed89a4ae5bef8d449b72d7c55bdc7a3745891a2432827acb0cb052d4a0640dcf1c81ef2ae35d0358badef81ae3bdffdb58cf74dda296adf9c18f118efb79bb363fd477eff6a54d70836f62b8747a4796fb6900300d40643008810b300b085731c889a9330c868a306989e4308555ba307582aa30ac40d630b6c802346a46f7309a413a3025808530144034300b001f3ab10b654d35954b5dd8269222f244e5d19720bef4af03fb4f3e426944b2dc3b411d98a5d5fd472a5a88cd5e61e890df4d56a7ebab8dadf3d69f3cb46483b266afac8b659586e0d341da2cc354bc2b5dc86ea552845dc3f4266dd4fb6513a9ca16699a41f7d1d9f051db3286322e563f6ac533d655172de7d5512c232f35ba7c1533d51b190ee627b78b28d67a793ca31056de425798465ff8eaa42c8a35f6b5b363e4e147d39b856558011053ecc39958ce3bd580d701774c7e4590cb45d20e598568c1c8284c5fd614f5a58c0c63409cc1400a0c05c01cb400671081c65fd0c3bdfd57756d9329aa1295530180a65fffb92c4ef021628c3152f69ea4b97c1a145fc19b832ac92d5fc1bc38b8936f21cd72f70b43241d0643db45888752db9b270781a9310a48289a1934f44a009089455d8571cc8a03efddcbffaade5eeeef9a57d9af5c2501ca39a8258f545e7234722c95b3387e670929decba762987554cd091966a6298a8910896182abb994f3d74ab45f718c2a8c99820013984381f191808b19f60f61e348ab9885c0299f7e3c9cb60b798ca08c99043849ede37118e8052983f82b181d010980f8071802000a5731c86a0e9a7f666f6562436e238286416fc7d6ac93ea7a6ad4aeaeef615bbabb5e6e6ab5e76438062dc0d3b967334385fbdabff0f7beb638e9bd4d4d2ced944050420a360fd0060d9a99d1b6c58895cd4af01311982208d331d41d896b989290e73d96e4511249bd2888a6140caa9b2293b0ac491760426f0b48623657920589031f6d1642da90488e4d09e28222b5c73c1ae42aa611800030070084d5308656c367355f32d4528327a15531abd1131b31e130bb08431544343b6a457316405c302f0250700a02801d391ff5a90887ef3f7ddeb0c1fc8edc7dac4fdcadc4ff8fffb92c4e9835745f30e2f3c6ddb3abfa181e30f89671abf5374b76d4aabe14b8b993557026009957d35ce5aef7596ccb6937d49d8ff70f49b1fa5f3c02eb9770592a97d272df11b2e09e0c59eec7d38196e7a5b124846ea78b86bfba2f958b6aa62b69622f6352f5746bb4c54e869f99137e57e9032a78b20ff18352606f7759803020800360c2a5d80d4aa190f1a113cc031fa0d5ca51cec14544c5f4650c80df70f82df44c6cc2a0c2c017cc1140c0c09005ce2242969db6c0ec4352582aa4ba3d3586087f52512ef94a3ab7d3d73562e6fb452ea7b5564919a0c5f729aad5b38527296beb3bbb2651fa6372d10f2761a5d4d13b4da8869c3be18caed05ce2e762630bdbd395277c2a20bbd4ae4ed7be468f30d3507797752a1e3bb33d569f993d992f59f9fd3aef99df67679e7bc6deae1dce8c209d655776978c9adef59a8e1b076b1154fdb896d372be6144fbe6c917512032a0000a9b8300201310819186b9c119e314e9ba69ab998082c1a3842b1b0c8970189a4c46cdbcecbcd40c53423cc0f80b0040560a004417608b5c759d0e472bf629db5e21a9c3f0cf3ce147cc1fffb92c4eb8356a535104f187c4b54402141ec19b930479bd708b064d7dbbbf9767dc2b97405eb3ddefae6f4bef2dbbc45de74f339ac7f703253206d1514c53843049753410644a34263607e70cda36e6ef74faa08de96e445911ffc2d7372253ced232373ee964b7292679c38c56fbf4c8c909bd0cb9d94b6a7cbde343e7a1d6cf06c885e00301d00a304a02d31a92f5324918435ad520307c603317bf9a324e210319c163318869f3ad460031400db309f0543040039301f00c0100897ca89a896ad3965cecf9ec48e860a63440b41e7e2d2bf0c715f3cf0f39c4463856760c80f2a4faad925a8d58718a987740b2b22a770bd82f25a72b420a751f32d639c9eb7a8886f9ca23934de31ddcd74f0d732e9ccab56b3c70c4fd4f8b0f4fadb1095ce1d8f37aecc7153b1cbf0d3eca78dc685d6467fcf6c68deee5bf4f772ed8afb3b1e959dddfe65ee9f09c3fa693692192319400c0ac0e0c100234c335884e358b4cec401a8cfb8708d835ee8e0b8348c66426cc4d0e78f868e04c6302c0c1dc0d4c0c00280403e180088fe9d0f045e2384e5598a5bf63b7e14f25265f9a6440d94fffb92c4ec821779f9110f3c6fc338bf2185e799f1b6f4a2231999ab3b857d5684ea79178452273ebb456df7f479f7be5373f3a8fcaf36dfd85cb64e1d56bd03eea2b30ad880695c4a271c88ba0b0721becef92b520c1a69296644c6e719de36444cddbf502b654524555cc535c937e1932b0859b1466af9213113025527517ed94c43cd932165be421e164541b85a81200c480ce60ecc466fc63cc686e9da6132bbc666f3f66df026a6426200631abe872a2a96624a23861040ce6072070602c02a6006008805939d25e9662dab7726b82b5c69bf7cc5b9c284a98accf60b7b0c5dba82e30e04591a428887a5a0f8111591e2da958f5be1b374b5e7f24bba09ee90c191a90b4041946521d222ba0aac2e759c3a94dea3ed1ca81a0e81ee07760cc9b29fc415e8aa15f340c713d16a7d323b98469d296e7e8c690538e40ab0bc6f604509e9b1bc43d98c8481392ac3031b0e45c07372020669004c00001808260d602a6380626624c48c715674466be5aa6665d1261ee35e6208154624e6967b0e77462da0a6610c038606a01a1c0485f34d44d72debcb94b2bdfb2297b4ccc47221fffb92c4ee035916070c2f307a9b28bde185e78df96cadc907c5102f16d1c9b56de3c4d6313e2c7dcee62741ed8293436e96f96cf0448f465694611432040d19f8320d0209902d20c81e0c1c19ad22504c194737b29391b2d2d4296f051ab4d38ab5d9b893ac62dec67c4840408eb7fdc91431cad7cd9d4ba39a5b595f733eddf5c1211a2a66c6670e1f53c9a957b26d510621033db260c0140204804cc4f9308d5adc74e6f8fa0c5d9bd4cf0a14ce0b4434c9a828cc6e99dce601678c55050cc18c1b8c0b80dcc0540600000a8f4ecca9c17e9c990cfd3d7bb5a96917c41d5edcd5e47755b8de99ab249ebb73777bda7976f24da1e006a2dcba4ba9653f6adbf3cc6756bd645171e55d4390714c4d864468477a54a356d1a5d1c6f74df6b3954ce421495153a4e2b19e5153ad2f87b552111d24ba0cb76b873d2cfcd5de0bdf3087869394dbd26539aefbba867b896fa7cbf786fb4753637512baf7aeeb87d90c61f55ba72e8935da0f34ce5482300a02d0084f181224c1979b1819c124498fd13f18a73ee1b31863180b81b989015c1d931c6989d0008082fc140783c026900a5efffb92c4eb025932050ccf3c6f8b60c0a155e41b88c91ea87e5553ecd6ce6e9e72e5f7f2594db994fd1c19beb393eb5ee35231a9f0a81a1f8f44b5762f6fb7b3d8fd9cc5e9ee8ba73a65e7c80dff4a0dd6886db778750d9428913f1091c82412f9ca3876d7a7b77789063714b33e397b553553076d5e9939ce3aea5bb5791e58f333ca65c7446dc14169a706530885d2d9f1a9e16dd5b6f68300c013300f044304d4cc35222bd384107431b75243098bba30ab160315f06631214c6364547330e70ae393b345230852d9359bd097eaa59a3a9f4f7bb5258dc6ed24a31a25650261a131279081f9c4cd38c7a7038e081875b895654b31dde083c763cdb1acd9103db874e44ab6fca2b3791dab3d45c570d8e5c8ff441d6d2976935f14b70b352e6f5436325b75c9e26d2feee3b56a1888d1ef5c4d55afd334c57308bdefcabc9ad0fc5a523f5d220c655a1eab490757099433a2f7a19003021016305600b31951d032f921133a646e31532313215706370e1163043035316f2d13bdd38031710120304a0d0198f00224d368bae9e9a6e9aa6e5d6f752414752250fdb90d0b11563999fffb92c4e08356ddd70e0f2c770af03e61c5eca169f974af3c2e2c8f5983de9a838180f3eaaea4bc9ef0578dd439169cfabb2db29964f1c71abd9b77606f966c5f14446338c21d4243a45dce0a47f41c329d1ba6d8555b0d19a19f1c8308ce247c198852120c3135dca4c2b13c26662ca4a659113f9ee0d029223dba9d6065105bc35438eacb55ce215a5acc5bad18400701518180099884a079bf51a81bbf1d2991527399ebbd89b91058987b063987e2ac9b0f2aa186403f98188191808005980080004746f3f7ce0e4e2f63c79170ac7b93bb119dc290ec3ca0c664a7de350b3b81979776408e938e0b93e8ea4545a1459dfb2da36668d26c1e63da6380570bb8313921328d482b29a3ee2e6c0ba550199054742cf16908ce0a95d6de3e66b50f17561341abae7aeafb1be5c319c8a88a154199134aa67a2c763b73882cc3cd4b812d60e462a3012f117721e93c34892100030050221c093301b5db344f337391f27b316a23c31b7d75301d1ef301f032318c2783cce2cd319c01e304100d260302600b0221271701b88bcf55f5b6aab728a21cf121bb92e2985ecd1ac4793bfdfffb92c4ed8318f9f70c4f3078cb083f2185e78dbbc365b42973174a62815c8c707d7aaee0c662c4773da4a4f7333a54bcaf950f98ab3d8eefd58d3a637c89a26f6eeb6e8a8beccdc853c54c15dd937edf7fc45e356d39ea6d2c4d997bd29adf768635c3e34c2aa5a01a2235a335aa67dd49530eacb62b90a48f36bbd3eeaf75dfe97388ceff188ed735eb7e10ce5f2e4202860560ca623ca62655a398655eac0643e89068c0f1e6eee26e61d2248617ebb86b5eb72613e110609c0766036024600800a89cb1655c8bd59b956133c9adbcf4d25ab775f42b68d0b5cf36b1e59775b5f8daad90b85f7b63343dccdf969d74783cbc21a669e75a9a2753d05028218831a6db95b7ed34e669ec49e41b48dec626e9c2bd55966466dd1b3327ed35371da92a4ef666d445dca16ad274fdcc59d9da694859588333378a68c2e20a4bdebfbfd65976dab75dc9ad374c534a178a689bccc9f33b79b87274b8a83d080480240c08662540a060346ee713424a62ae4e267a0cac6bc21d0613404c626a2a6754228862820a2607c0204c05e2c010b5d5c38ea7563f5a78fdfbdb40c3c492dc7fbbfffb92c4ef03597e070a4f3ccdcb33c0a141e61ad11bc9a566624ff1e0efc46e9678f08f42ba36ef25dfff3eb1032ee3b7f79bd47ccb7e4037a7d2a205930320852836243b1c960c51d77d925327287e08019c28a4cdc9842c21e694ac80a0e3140f093a66f1f30c0ca85444525418ca521448aa9d66aeab6a15dcc4f83ffaa8e76142585a0a40549f2b1de1c3216eb108180f019980c84b18671ff1ba3a971a7fa7998981bc98c8e1318640df18a308298542a79a2b277183305d982881d180b011980280416759740ee4995c302bd7106eb5b57b2a7948a4cbc8aae3163477b58116f5c62b780f1ee9743ad51b9e4a446ddefddeaa16870f1d1235a12e49811dea6181d631c61723a0ec936614a1aef274190f0ced7078d79f5e4d22d169e9ac7a22dd23a440f6e17a6c91efd20db576cc49a2c82c8ee11e8fa1eae30fab61cff39a73b08967435c5fbc2cc2c8f88bb49b7feca3e5e966cfefb9a59b4a81b600b5ba005f5300502a30203d633ba11f34b526830f224632d74da3414068306705730c408237c609730d602a0303b8f010251afc73dfc7627b77ed6a66b123425f95fffb92c4e9035812050c2f3c6fcb3dc1a145e7a1f0e3e96ebea88188f245ccd6544271d2ea1ab5fb01366dcc3ae99b548bf6f1a732aaad4a313d8b0dca363e9e62b6f9cf6edd2b1bcb8bc64b692cf2f3af9799647c4695c178bfa9316f69daa4da436f2fd0fd354734f2a9972364e7f9dafee892cd2ca8af2f991e5c8adb118976d82eecd86ffba90768c0bc048982a0c575138c944f1cde60b50c05ce50c87db74d6403b4c674280c2cd358cbf50f4c174380c0c011cc0340800800a5ee6531c9040d5226ca676acf93d30e30450ddd4edaf451a243abb02c7ebed6eb8721bbeffd64aa650bb5ec6dc8e3a47b0cd6f695769e76bb0eef14efacddf4f1147ce5ceb15ef3adc8fc29d1df6d6f7d716ed3d51a5eaf21ddd9d18a814531d3ea9aab2f374f95abb9cd3e56d51a49d9b6acbd291390bb28b7f3f34a9a776bd7cd777b6697865b3eff778ef75712c65b4bded32d5c192a004980e01318918f79ab72d6193d23998b79c198ed5ae88c4bcc000248c48c184eb84438389b8883707815c88054880097e36e664c4e61756fa1d152a7c8060c471694cdef2e4d7ed9656fac511dfffb92c4e78216d9d710cf3c71cb24c0a141e61a3912c8dcd1f9ca972f4f77cd3188966953e8af06d8ceab4cbf71719feed96d4a9d8c2d3eb8d2fca9ae0c3ac993e71d3793c4fed338626cd3e178fd5193bb31cdf3519a816db8e8137dc8ab38e474d6c80bbd85cec6ad8f6bf88e4fdb6f6ec61a9eb273bfbad4d66ef8e55ef52a547f8b7f6460843a06848a0050141406f306f4053266467381f0f030203f732365fb353f0b5319803930b045931c83f4305a0d1300703c30000170a802232b4476d3241e12874d1e19691eb044431205cd9506d5517f056506aec95a3cd410848dd21943b755af6d9732fb291224a33d084a34a65b1c5b35a27cbf9d395b6e86db4cfd97b832d4fe3b3212de932b199c97d99ab39d025af4da919090ca14c5126a97a642ee0bd42f1e1d23adfdb6abbee72e939e6c6ea7a5ccb4463cea8b655a7db4dc6aa8d85a08a99b70be9b6aa5a3c4a1da00920180b80d1804030980428b99f486119eb1ef18bd19218b63721aae83e1842023989502c1d020ad9896021982e8000d01e8d009216afe720702596515e555fd7baa056d2c4a4d58403a6d2b12fffb92c4ee0258a6050a0f30cf8b223ee155e499f1756889651714a20b538098c50fccf752d0e294918210f78ef18362ac7c3b4265f4e365c6b1858a65b33ed43adedef9a65a8bdb1f52c3b2198e21ba2d1763875a4fb9f76d54f8ce62dba171f284c622936e54d3eb2aa7db5d4b442c56378bd237b81abad1f0976d475424aa92652e5d169c20a267b78774555c981780a989f1b999c200f9ba71aa9889a3418825bf18498af18ad819185ba5718cda5598308570e01b85403909ab04a3ae8387d8c29252151638a569a16ca4fbf60b0fb20510d63b2c74f336bf574ac3bc5b8daf50a6aebdb62d472965fd3ce982d365f5a4cb2a0e1e53df02d0d1aee69a8cf82905a4727cbf787f330c9aa79e6a2551f91e96bf38404bd9eb4b0e45aa6e91a32cff565197370bc7987b9c2ca27d399f3334fbb647ea65c18457aecccce7fc9a6245a05e41b7265bbb633651685231afdcfe64ab7a7b15240003004015302b03030f419e370f11636cd26f31872a131bf50035340da2212f312f061394313a312704403038838078bf2cd9b47fcab11563567e7873b07ae3a554851a9151e944dffffb92c4ed8258b9e10aaf310f0b2ac02101e619f16175142ad7ae64ecaa65130ac7873b9162e8099e77a5f2a71a21bd9865255e503ef01497ff0bf555a5dcc0177f547623788bc6d7f1fe5bbceb762ddd9defef2ed35f6bdc9d94e7b6a1856efd2389c5b33c62e9a597e712a38a4dfe37496d1e5aa275bd3aa18b7f2ed5fcfc632dabccbe734b8c972a48cfad4aaf2a55fb520294980180f183015f18d904018a0931187810e9903a0199c802f983801d981c1569844160180601988c0113d5b92f96952f1fd419184472f9ead1385274512cfc0c3a84584d04690ecff95c7b9cd3aef27088e1f632879cb9c5b1b113d884cd3732cc4d3838761238d099146519c1b8b6550f2054edadafcc51d6534e4e33e60ad8db0fcac75c4cfaf9fec0b31b2a42c19677b4fdff5ef8aebad3c982c1028c9ca9570622eb465040580fd18f241801c0646064054619ec70602681671e80e264506a0628374e3034a1817262fc052765c0ba62d802a0a07e010128600125831e762c47e55370e405434139dab46d86cc7a8a8e969277709924f72bcfe32cceac7a9afdcc59186e8654325259b3d2fffb92c4eb82189e010a4f30cf4ac4a26225e60df9b2ce7a157d1ec4a5ee6596d759759e7fbefb671b7d232be638cb6eddd5b7f4999ea3da67eebae7118d38a3cc94621cca7468e938b392648f92c4ba28d16c6d1e4dcf47ff904f133d87a10c475d2bccd2c0f66b4f4f469cd6288adb4a2650cd53ea866c4d427fa5ade4f48d178268b9e9e5cc995cd6479d4957da63c39e4caac2802e0804430d51d031104be33bb33131d522a3332503350d0ad308709a30233cc001ea8e8291808009834011789a46ec8e99eca68aabdb332519df3143c52f2b5c91a9965a35c9a9a58312247de8ab416e0d6581077a813e214d24d6f9483ca1731b25162720d77bfcb3a4ad6fd334df5ad8f8c72e2ee599edbdd6753136c432d5377a5bbeb37edb09bf2a706f8479f3d575abd8c56b2b331ece6932b18ef2912b6d6449cf66cd56a3ad27d34b672ddef113d8b7cf39d241b97e1fe07cd415230902832a000160910a0006542201f307712333e22c333bf1fa311d23f320c3c63389074300a03430b5096363108d030b79580d0b0052bd68502bbf4528b314b427e5b6f9f75a56eae66482f1c2fffb92c4f7035c0e07062f30dacb0f3fa141e799b9ebd1562fc7e1accc5d80e8062ca01088ce81c6cb462845a66eddbb56bff0732eec2787eca4afb36b3aebbf6d4ceb081f133bd0e79850b4e352a039ee477a5eb118ce6dfce9b428a618ce320d4936d13cecbbb12dc5132366bae9751c38b57dfa4cd4412cdcaec0e41fc2d8ebf2996e88103cc094070c0c8154c33d1dccdd430cda18690c9005d4c7365f0c2e04f8c35029cc010ff4c62cea8c05421cc03c0981002684e2724f917a5cb12bd088a92c456e8d11b581b5f35c795fb3c75edc67b0e34f99dba4ae16468aa77785e0be62855a6ae4e3133d254bda2b474d7351995569489f6e81a9d427a20999919af9f9dac82749c11ba32f22ae593c4591b95da16aa7524d3a85c4bb6937264106b3d5c9dedb49299dea6e509b43152e44fe96f87b0dac7f44e3df9ac54941915744a299b5edd56c8915725eae0d40a2f375ed934b49e7c4948a42000670600a022611081a6678580629864661a069864ee9bc6868100600a0f661902766ecc230103301c0dc2404a2c00c3c4b556489da30e63c271738ef63df0e12bccd67b5e1fffb92c4eb021759990f4f3071cb4f40e101e799b8660ef316b165a40bb872ea36b11de468f0623c57e9bfbd4420a4b0bd4512e7e8bdd44a6eefeda8b77cbaad420fc32155dcc8dc3dfe5c492d6f511e129c9b7f65fd30d4e2ddd6c71f924d6d32dee134710b3f271f2cc779ac6f3d32dd8946c4590d73c9561dedfc9b7243e6b4625f5acfc77c2eadee0b93eebb7ad8778bc4da4a56140166180d811987009c9a5496f9afb89b18d186d1873a4b99dc04c98738280a185193914a98120450800842e010b026124d74e0d3159d9f198f58ccf85c394b37bd9f3f8f86f91fd195170e468ca8920c6932b647bd9ebd9758ac8e0a488814e0983aa5472259f2387db1f42d04e4f106e7ad26979ab369d7ee731c3b3ebb9b5fcea9454cbbf53ad1bac5193aab2f132282365d465b63654aa6397b7ef65a975863f9a9c4e5914e91968a7734bfda2fa0dd377d762b773ee7d769a667ad2a4ccaad74abb461f8d1309a00294000de180580918570f518e58d219c114218029aa985b3db980e86f982783c18608b81b5d881985b8100081d448078380159b36ae3a25ec18fb8d2de24747478fffb90c4ea0318ea030a4f3ccdcb29402149e799b9187f17dcf49ab1695837bc07ce5e7babe139320cd534ebd01c1f3fdc07ab922244c40c50d646c2b6a875271e951d4d604b4ce07ac81c1526e28882cc96dd553067203345d55430eeca4cb60d2086e9909a1cb5410438c8023e9c763214339d8e294190550ae1de92c17b24092038442a9bc360ce7530204b424330ccf4e8776c4ecc6a207216540010681b18181e218d20ac9a2912798aa07a988120899c082f8186f470a6ccc4c950c0ac1e4c56190b3c67f187fedca2e4509381a9eab62216624c39c64a210872207c4c5d4d1c180f45c571f554c2e7d8d9e126da2de47d5106b63a674386b37376f4b66713d2354256554d420db2c6b9f4628c14a839c585a459861290e3084ab8664cd1c94efb9e4452cdc50d3587b444136bbc55f1335c2b9a55b516346143ea9e495299cce6db424e9a32a34781fc8d664b3a3a3d086df3a352c5040078a01e183c97819479511a9a8af984697918ce26219bf831185302498610b99b0306c18580269de709225c660b27bcc12487ea0713216804459e53996368fec0c729e3393520fffb92c4e70258fe090acf3c6f8af7402141ec21518b95a85112aa5cde5da3b88dd9230d9993ef6f673b937c32993237750c7f3623f2d129f6b6f148617edb9b081aed64fbb6b3977e258e0bfdaf0d657c68a5c9a7a2b840fbbacf1876b397a566ff171b2ced365a0d6ba856c93f91eb75040a972cc314ccfad0ff1e9b62f69dea79f3a9bab7dc9d79a520a00e4b930b312035a022f314330a31610fe30de7ec200d62216a30122ba33c82b3303e06230150154505f6aa8e1be81e349f78a04d150f2aa1c36b8a8cd9d2411b07da9a0a1b4e514b3579e49c38b4cd46812c91b53a5a6e90d324b61496152e9ba65338e58fbaac2529a24c8d2c8ee03479172c63595ef252f8fa943178f1f62c89ef1b186014e495b331b66e69eb48bb5b904db20f88af125edec4117cc45e6f5d8aafd29f0566366e22b3fedbd3ccbbdf8f06274795ab7f28c975f4364cbdd1d29929d500400180f80b181901a987694298c98c09ad1074987c15099089b51a07049186e80318758cb1b0c882986382b1c08982120ab0e81754b5b1a8f25348a2b52fd3fd2515bbf953c1b6585a7c1c14fdb4130a45fffb92c4ea825786050a0f64ca4b18c0a115e499e1e8b12729979a4255b04350bc6c45998e56aa0ad87259ac99028e5f83e3687a66b391bafb069972c44a79982d94ef3d7fc4f9df0e5e596e553c8b49dfba4c5679ccb2a0c7ecd850d4d04b10d7470da2bedf30db5f6bee6b2053272fbbfdee9d1b2b6e931c74e4c17f5c35c5e996564f6bf986a285a8b64acb06a8e80888906cc9c0e38cbdcac4c4781bcc9e54fccf981bcc13806cc0c4a0cceecb08c12c09c140408a6bb1c78444c8c4ac9815a89956d83eb88d568d953885b82c9309b5855a6d65d4654e231c6728965b8169075560a2caae89a815df1cd5ae60f8c22e0731b75286a8124cce593c313793b5e99d8a311a818d075635f12baa2fe9f15b4fade18cdd75b66c3fc20595295a0ce7f3d28bbba72cc625dbde927e8a14bdcb2c5cfb78b4d31e627b1998536bf8da728433b6d73ae598c1785d59cda7d451852e75d002a020600e0ae6158516641e7c2688252061324046128dce616e2062427a61ac3366afc2b4617406e60260085a15366bb0d4652ef3a4cd171c9cc83292555660e2e9a1e324f08a1b2cb3d44685fffb92c4ef82d9460708af64cb4b19c12115e499e9c07bc8a9946708175166d2e7cd324d169b1f22f4fb920bc9598f2d8405c1b9e40c790fe1036554f2e76118d87739340e79513a40af2d524a9cc0f173ab5a1ac8950ca989974b7731d44dce6772b65bdb2948b229d1138facb828a252b56572adf97871df330fc477659e9c83ec5345320618d73b5986336f7f2ce5d003d6936614810667463ac69f82e861ae0ba650464a682a0f2603e08860904a0672452260b40305df4547ed227141e2a7b96e192c9245ce28404705da24474a6af06e10560d5377bc154994514e803a43611267124515d13387106225a8916fb6a8b640b30d5a7d387e4a8d41abd2ce45045336ff5dd49c8210573ac917785554984baf5816b366e586a7b873e6fa69cbf90d267a4b319aa8b98e068614c6d1e9a50631283e5d230d3ae6cad3dcbd3a7c4277bbd727425a9361f0d665a58b8bb474fc7c132bda30302130944432aa9a373088359aed308dfd318c4783377098304302c309d1c035051af309b0060700e838009415e68667a455a59220411346a40d553a88c71428d855e9f09930bad88c81fffb92c4ed83595609082f24cf8b143f6101e499b12a2efc6973a85165c783aa0d4d32d481fd965ed5a66c9e6a5690295973968758655869292362e8bf7949ea6945e510a25179a470eb636a71bcb1508f29335c85b113309af0caf0695a5455d3a385617a7208b116524f4ba6af6ae6979874205e7a2a0e9dfd65e2cadd271467433c3c558ec7ac61d7b16ef59289f7888aaa23034300d36a33a701835310a430960d2313b67630de0ce304c04530572033391204305f03546c46f761c39648da3b4f34020805a68561446f12526804abc056c1485a3229db9b712c464bcc26914db5271129c8d97b784b0e2f6ac0c2db2ad34e5062e8ae5a93c72af51dbb627b37ca5b206ad2ca424d4361d02cdd48e926a872a2db6fe44c914ac798c6f6ba7b828a3e0ebc4ba564da253b602d75ed9a75ff18759aab95736182649add459a776b1169ff38889d61755766e26bae6bebe6625846932c54f3d577ccce308726c341c02a330824c305623631b334a338c048302405b30841cf34741bc010788080d83002535a132ae5b9c8bd74cf6828a34283681d0d40086132e5529e1d4a984912fffb92c4ec0258c609080ef8c54324402115e499e967a2340f811390338f3957339886561c4bccef37ebca09614e7536563ba7e34c3e6ef352a39f5adb4855cb93762ca3396c4aad528b1b84db1d92370d29232264cb4c819367d6bc1e63a5735441937ed0bc6de4cfac8d7ed6915a5e5925c3e475a116414b7966a899d6b3f33e8c6e7dee365d2732973f9ce5c1b38401460c00a66280c67629a7a141662896a61a282665d80ea0610e30441ae33d216f30540480b802a94386fa5da2bcf9ca6f112522d9469c386812303480c38a474b2c971045083cbd48e1e4cfb35f87a640f8c86a48910513084b08385959d28d39b58ac3c61d682bc1067d859a4714c64c3c12fbbfcca7aa3a04d4328505629284d23914d2aa04d7675c62bd21786eacbd9f379d1db23ef2db1ade0ba6ba9358f968206fbe59884673fcf2b5a67e6555463f4db11f0b745d045fe3641f3aefa5a914e701d4aa0095493e608e4f2619448867d61b660823ea6088a82611e12260a609860be33c668e2b260aa03c602c0025d24e95d4fccf8f27e4fe3664fdbb3a8cfea7d144054f169eaf4726e7bd19845e31fffb92c4ea83d84607080c78c1cb0fc12101df18a9849985a66110196e0a65e49924e4ecd4c424f78b16c2450ab6227cab0e819951336c6d3189e9a94f9c2ed4d11cbdf196f32f0b29e9f59f9db9b48e946af7b776ab8dfb7b45225e7a29f35fb5ce64772e390ef27e907ef153efd5e453b644778f5516c544fc7cf6a8d3624ecb27185b9dbae9eed39181a0802e071288e6135866ee64a6134f064c08467f58a7798b263e04061ea7e682221c60ae0860a0204c3570d7e0497db98c253ac2925b2abf6ab6e5f9da9f9749459f491c61059602223970754890f6d870faef5ab74c2481106d93b48a2057fe4e477ac34c3be16e498c45b9f0a99f07dba796a4c0da2d1cb679e5bd0bf09c54a3706ad2b8c4397c18f46cc25e084651663ae49eaa8e74a5df504716e5bb9bb8e57ad3b292db2a0db339c974fe4992bdb5a4534d7f64f96f26c6a496452bc219b3bcc8db65c255000019d6501ca4b53119c93c95123e78c93019cc34836e38e44c32002f3129913a213031083930340848961c089f2194f60d4452b8e09c64982f494db2403cd5084c9515d0a58d14176d9d38c87eb113fffb92c4ee0218b2030acf30cf0b173fa105df19314651348309cc9e0960250e2447913e0322220d27a51e063a8d38f90d54e62030f8087ad87351a3cd33263e763f7937b28f2f7f3138aed0e7c3cc615956db2fd5b4f4607754c5b7963be33e16fe5f6f6aa6b217b9b9ee548aa4c3bdb3799dd76a5a30ec9bc94a2b466e950e7078e6e06846bba1b32001800002980c80c185203a18890af19278d2986a809982e250182503f181480d982006319c306d982400a0b011aac81c701b4a8f11152343a61114462215ac2272ec2e6e088952d690b7271e71e57560f9ec460c0b3e6b9119497e5e98394b312307254128bd6972c7a1b745fe8cc205464e9272e937c5e3d466990cd967276f4b7c7d576be876667a461a5333728d360ddb73b4ee7dfda26c94977b5cdb2fbc5137aef4f665c3147ea2f3ae8343c148bcb951fe59930c77e74c666cb9b191f633a7a7d170a7579141f00c8c4ac58f7f9b4f1f7e0c3c7dcdbf928f0e110cbd030c75798e81614c4f1400807a0294a5a5632eb116ad4cf8731e487d849b8849a524802966db1e28d70b721e4d03006d4683a42cfc44b45e12fffb92c4ee821851cf0b2ea4cdcb293fa159e499b98450452f42e4c19edd1158e4968acda8e4ae257166b6cd8cc641380ba329f7334cc98ed881f2db0511248a346a3862b5a1127627b52709279712c8a93cd29e106bd8c9c9dbd4a532d3862786b224a31240d1699aa72b660c620ba30e8823b705d177305d99478805c4177d738cd6b4178e52e34c5004c096bc08121887381b70eb1f8c511a24341b37e69cc429808163164c33f55062b174982d4e26cc373d6a1f3f69d71e74f02f3b1c8f0acc54c570a8b86a24ae3878f62b38df58f0f2b45a59addcb741f4a6c3ac75283f58a01d0d490ed51169b8e9b260aa2db588b3c461a0a4f1e0aa30b94d11c5e772aede4fd3a6acd8bb5264a0ec4d7005ae8dad3e97c5e14ae7f409e20591698340ff8022ed5ec964fd82ce6a247e230871471a5bc32b10e5b45957af663de2547eb472b5b9c64f9a30a3e339a8e40160b9e44eb0100218468e2192b094981a90598318ce18612a198668548608e982f0e998f80d3182201b8a8052c967cdd639183c3944f295ae59325d44e229a1914d28a1893453b32812d3cd2d32e10783d042a3fffb92c4ee8258960d060c74c1c321bfe125d619b90dc9a94c826d48010d421879d54746e287268f6447a41d88a499abc4a33420e4e626ae4ae0519a70754a2c6e1c7948f25cc7b63d0de55a64ee1c9e989170b591b741d0d2cb3628c06250e68f1c4b4f8ab3c9a88a03b1a4e6b7c4fb1c7f3fa491f177889995904e0c3e4c155048f9379c998339e8b5f1c7bd9fba403a25decd2a280c02b4104a63aa7e65aa587c899c67d98e6e02ba75886261400463312c7ca1da105c8d00cd05cf0f579e2e46ee3f55ed96ccd43985b5aa0fd396dc80c9d8e98f6f6d57f9c6c3c585f0b1451fd7832108060607b898ec0d828331046330bcc4858be0c00db1b825ed1e56530c608765ab0a85aec306a09052000fa9a1ec20d5293385341454139b10912f186c5c1d9c607582bda41708e418254e38a0a38e8546098a081647040d05adc15a1dc4503048e2c2e2b90214191c5d670e603430edb824a11d00a00018981294f99ac0f099790cf183d0bd98672031928837195c2c641dd1e2f8662d0824aa8f35980adfc31dc2dcb2fee6ecdf87f3e1b8412289891a46960c6845a291c4d8d87721fffb92c4ee825a220b060f24cf8b0740e119d60db92c5238fc59e48eda4749b236726a19d1eb922c883d96428a5c21107e1d807d0e4f4816492ff9e9a0ca0dad552430f45970c9aef34cb36727ce51a9a23910484c9c9c4634c6b9a7ac3a49a322d1c2b0faaca030d1713597cd45338201d8a797490462a0bc2cc993499c84442f5e3bd7a216512dd1922a85864ec8d43d5398858a4440101000cc058924ca9c200c83865cc308408c3fd048c428208c054044c1741bccd34268682bd024b8a0e0b0a57de8630150a06e59f50ab30550b580945028594a1ac93ca30a489304b0075c92f624d24810459aee433a2457dce720411f16829f0dc36b220a4ebd15274ca519a8208629ef49a6a40ab687cc6e2d5bad3ab7ae30e42cd95ff3b14cf64d16fb185ca6d14715acddaf17491b733ae9dc6230d4f2e6a107eec2d3db6afcfdec7a105c1cca8c7d864ffe9ff5cd83de2c8c5828a7007b025c8282232292730b9703d146732054633bd2e39b00e30882b30696f345978010428acd761a549ae66243e17044e1965350b71193a8d24099b309992032fb9c60a3d95549acbc908daeb1fffb92c4eb82592603060f70c78303bfa119e499a1f4246f4508a3d13933828f4965bbbf6fa2cd4c1a8fc55cbdc1806411c3cef125173289b6718629f353cc4a0eaee7c92efa53e28b37e6137aa3fe6b52c0a8d9207b095461e4ce7a9333a2f0799e3985c94e5b5f65f9d849459f646938359cebd254f69434560f4cbf99a93b6b5e36cebb16773649dd7bbd31a43c67100e6313046f4c9a6453846269f662ae366d30ba4407989c141e1816989804223b348d1913a8da826236d8328d94c90308e81187f27d6413b22301766c1163dcd48a4c0393a2d07997bb22448966a68a40e30f412c372099fb45aed02c08d81cba9dcca6a4c97630e450cb8df912910266a4638b3f362e00df0ee713d9c309aeb9c91364251b44960bd390777299653cfa7ab39d034e248d168617693f2940b0821d32e31a62615fc25e75a4b8dcebbb41de2b1ca87253e10fb0e9a024dce55fc2b10214015401c900b8084a4625248c66d823e612c2a26078872620a0e6608a05c601a2fc61722c605014300400141e5e21b861a11b30287847cf8ba3c683802879328c9151b8284a551b4844c54f32b3dafffb92c4ed0258c20908aea4cdcb0f41a115d499984448b49c20a0687024e6096ab3533dfa527872b4d347e289191c8a881c69a9441c566437101ca42b9e1a2ad282212c0e8abb23b179886f8638d3ecd8bb55e4bea638ca38822c5f2acc30a56ea0531d4f7b259b251d79b6ced0c69c8559e6712eca1ac9c9f9d47186a5352ebcdcc45a2933750830b27c9a99284900243811a9acb032f38408251d058c11818e0417ce5b4a4125f1990869c1606982409187e091d9e0898760291010c5a0e13962350808110fa014119759864304cf319307e3509379a2c8e168204658d939747a231cab4d19314a475115766e5a4ea8a5a76581a8ad41039c9a2a2959313677849831c6590a6fac99069cb4912eb10a5da56465f7db297f4f315148bc54e190fd445fce497579f5d2a48554c19944e5e9fefb76c2eb7054ae4a887542c4f6de5a9e4d73af2d8c25dd05ecd63b41a73367cc5c25682d2555100bb44c0619226a1a4a4e9bd6db18fe8e19e59f1b3a2418c0099810a59832841004209001f560402a827276e5a6925ede8d755692b24244c4c4705de3a79ed948971239920d3a484cfffb92c4ee825a660106af24cd8b00c0e115d49999466778611268cb35695ae935e79c55209324fc901eb24a5d023dfc2cad4940c83bb24e4d2248617344503c816309b473e30f416ab2c9ee27bbca2d184c869a90923a5951a8a0e94ce24412445948d11a724929af56791731cf8bc252cec71f485bee9ae2cd56b1bb25dd44a0782ab0c5937ea083809038bbb9e72881c55cacd412945e0ec90469a84048a2e6986a819c38211491a04194c1f9b0c860f8aa0898381a9be00b0f078934bd61784356a116d82d328915e649ba46888342cf5094ea092b25098e89674a6e7542a9324538278dacec9a4c174115986a0849afa1863068863d74087c166d1c04a90b6631025428261c055c1d3796858ec56934961f45861c8c887aafb060632692431544f07e5b7442aa74464c6b69d8cc4aa415ad82643fceb9683426c98173929cdf3dd48c24d4721ce82586323000004a41001070280b981b139a76489a42cf98c8679a62681bf407091ac01468ce035caa1022eaf2b8351e9610464f2769734c958471e0b437d0496463ea613205c46585c4a612465530a644eb2ab2a6e2cbae74fffb92c4eb0219ca0b062ea4cdcacec121a5d48df9f0694504f88052bdb787892e2a2368f409db5f11aef85932ef2b98cdb6db914977eb564728490c91c35747642a329aabc6a37aa3752b47194994aa3383f51229ad38c65aff6aeb9a71a4abce76f66f5861792b269aef8b0911209b1514d4d41aa21d723b9a26a9b91bcec4e1396209af788d2b6989d239a09ad53c6264f383e52660c8d526102ff50200ad497e6202f665298870a990001e8c978a4d100ccc1d038c2b180de304c0c210602abf9f90282364e5cea9ce6a16c029e9333220f8ab109870fa21530650e108293020906a0331bbef48f74ebfa7cef472051cfa67b000818773096d149d344db19caf38ac52e1451753a9fbb6ddb3ee7fe6dcf4749c732d8f6422d1b5ca05d3e9f36a65c59c96d3228664a1f9ef283d417169eb22ba535d91d93f6cbcecf16bf481907b6b95a56b6b325f1e8e6d87f67c431d8000d1e004900cb2058060081e63800c628bd674b24c60b91465064e65b8b662000028719a7e670540f4003a788554194cf4042aaeaae991269205d193898a8813224689b12a12eb887b2664f88a856afffb92c4f0821b5a05072eb12d0af53de159d499a9e15928c08d066b0b9461230153742c248b8e46ccc24b2cf0e825d34916e81c62cbd3c0037d110e649c76c1a0e42a29d2342f6eea5a9da4cfc9b3027b1526a96a7e4d120c59c51452aad4e4dfd5b5d82c147f083d0740b5a7ab334d79d4caf54ca7a9b3b722c6819b6655f828d77a0b2f2aa96c471c5d17367c6272841a3ec1444095da40908405b50a82861426470aaec69129a61a94667a11a0ecac206530c87337503230980b4527165c251732c0cb2d21218138fe40406c515a6a1964245a913f502fa398634eea70493a290ec8913a600e4d24a20129e5afd5c18e5d211f700e6e21d27fa84346579c98833c190422b58bb51cc87471b35a4c02ad9f1b0e8415587d2da5ae0bc2ad2c7fed5125c5151b7e6990c593b3d4746b69ec65dbbeebe6d769787943e51ed89e09f7fc03e2dd3dc7298d6a47cd55f3b9aab597a8131402101845201bfc201e420d982a5598e9801aee2615822083a8d864141c08200da452891109030230f090825209a0150e869600e1a2e80681542d8194d828ab08444b188a90a4034a10ac3512fffb92c4eb0219b205062ea4cdcae6412115d49999526404d69ac2716120439cf4418a225c434b35b90928a537bc0a0c681d69c24e60464090d2d328f8d29b9183a8bd7e673a9494a2f2592209ad71465a94826154c4ec2c2e54c4d37288b69a67460ea6d94d72a2d23dc26cb9dab0248811a311335334dc21851a40116927b088202ea651e42b50a344dee169663d0d2e18b3cb5252068bd87b0262aa64500298dac719b6a79980a918c46a195f6e198422140ce6159006e18be614800b55debc12463848d2e6a62a679528a21326fb736717543ee9ca4d36153933646c4af4989b21754831fa887a85dd0da49b64951c624841c0e5fa348ebd7351adb6ba48c3b1cc4e1e66e1d3d44f72099c812bed6c718dc8923b321d4a2f2e148c27d10c5a1078149b9964033b268d98ea20723730c9ee544904a790b3cb39ea66f7410b0e572c20f7e5623ae573528ac8225b49a8a0d0f1008baa4516b70755727d0b75bda40702863a90273737074c1f66001a86668f87020183204982a679ada7a0b03ca08edbf86ec366455322f271f12158f2977970c63a98df8972742dccc5bc285905fffb92c4ee035ace09042ea4cdcb1141a0c1d499b0d986a0a31621c39cdacadafd0212623470617469d3d13059b8ab331d9f779237649356e78e69344d31514995584081a5a2f442c9fb613a966ced77f5d25c9538e1250526aa196ae5b5c826aa9db5182d93710bb10ab24d0a4db5e737235e283c945eb5f2575441263195947b4d28f74d4275a6863ba425e259583df16d55d19a41f534a06329cd3692b3b6eb56c8c13491cd384cc18400530cc44cfcc33cea54c7e9330df6cdb0252e89860ca72a300d085b696c688526270459349546e11f813a4bf4e4cc2a2aa85dea3784448a63075b4a9993ef29991c469944c69043271c804a2e0c30bcb4871a2a11080c4ae6e909f04e806440909541c81f864661cc825095128e4150c3140f049a53107847351760907ee43cd6d20786880854d8949fcd4ad5a8e321274184d6e87850e884ccbb84a8638972e1ab9987e71e0be868c50030021f1203e6034426a48e869eac06101c66175de6738b60d061849587565e89091b9de86cb0220b9208d35669b93274284f933646a86604e2b148c1dd26243e59026296d185965565bc1a61fffb92c4e7025a760b040eb12d0ab3c02155c48db9e58d0a535594f50a4dc113d02028c1a2643b421669c863db2ad3085b4e7706e09ac8fcde46d424d2937659e618995e811205956892fa2faad94239daf528d2abbcfc4dbda71221f98caff75f77150b2d060e6309630b8563385abde94d54f51901cc5b598b09b928d225e6c2ea2c48f9b9d061b44e823baabc82ac2bd646dc2dbbac6b2134b1589091ee3ecf8039a210198c19669a4c82fa062e371998c20b2d92800c0664380984980ed46d4e25036211c0ca2160da64d238271d649d343202e4791c7419dcc92082d066b9145352a50cd3f7524ed188b4abd79baa779da0e59ec63dbc2bdd72fe6dcdbab483226ddf6df9fefc3169ee3c7aec51c9565778ecfb2562a4d79de714b6ceb696a4adca7fcdf086e3b378b92f274b75aa98a738c9986e520d96feaee669a5d8bcc8eff722114fdb1cbfcda24040a69500002179001604b746230b872e980677a566279386464246848601010980e309a162d0181f66101fc9e51763318b75dba4a5d469cb541909090e48ca561c170d101644a8e4e158f22d664cc713d7cbe7cf12fffb92c4ed025b0a0d04aef12682bc3ea159c499a1d0e18d7be77d016f8eeb02258a8965b4963ca5905d3f5ee726ba7abbaed9f490aed72397adfadff955cef4af5bd545a5974d0e9b8432be90e85d6cb3cde0acb9cdb206ee199661a7000e7b35062ea0e8c33499c0498c3d91391e7a4a69263dc8bc9e6294749243c01a27052568426ee6c24867de8029c8d98b248c985614e98205a0488d0d898b83360e84418054a592316428cfc3e3afa18c6a51300f28cbc240400400513691192e51c99acf993f3548df4f6257060ea532784fb61f19617ecfbf246d075418704069455b809271a65e5ca653953b485ecf8ee67998aca22954461cfebe7d4d27931acac733667e31d38cc919edae0a3eaace716f8515f364aeebc2e357754667bed976e794d0b6f9146e341ae6238ef69f9f91df79eed312857f4b366fa318d3137b8f2f7aa44b2ee2e7337f373af6718f09002e00009a89d861ac8063ab167391a46530e66889e46e301061e80620188d5412cc0c0113ada0ca987d83a0738ba5d372d1a924f07d3df391214959240b84f31259c072413b387c1c36cae6e64b655a2ab3d9fffb92c4ef825ba60b072eb0d3c2bbc0e159c499a14a485513bc69815cdb2a37486db0dcca92a7064cb3a854e927cbe3050ea064bafed23e8cd41857068f3084a1ae206b60d2e46e6e84a847c9576283169152489ad2ef44bc78ad0469121cd25c7e23c744846193dd64103c8d320d8f64bb7739a18662aa19e817a1c4d6e487e587979a6ddc225e5279876aa5965ecaaa30ab264f1a7d9f365a957a78a149c5b8c101321ecaa02d2d15571080146455c1b0b0068c8798a0f467fe99bf83e62101182cc06d91b981c00a6ae952eae4e4c96938e0ad6d4d599132a11c50d01e860b09561b92305358e5be2037c3b23b64d32f0380dcd84fb19ea666c89c418fa5af5224b78cc3c63acdde9859334fb5c629ca68a7cd393a0d486310628b4f1bb75b9e796ee799121f6317592f5e19a591d41a60c2eb409140939ba66b446db1d09865c380af087b3d8fda02c4e30c6e5d668a32fdba0fa8e9381c6e120e70d3cb39726d968eca4ce4d382ca20157a6e986cbe750629efc6c0255196bbe6451298080023159b84589f6da3bd201b3e4502842c413e891cf49ecd49847f97e898b448d8fffb92c4f0025d0a0d02ceb12d4b01c060c1c499a984da25270f4f14a357c1f7239f72c253603145169e4bbd4ccc0b7a8515a6a08a90d456fbb45ef39987c33bc2ad526b7b93d54f5b70e71a535cb1a8eafbdcad7f3b61873d3c171b152caa4eec572e7994479035d728a1ad8578b7828af0c647772cbe763c37c78d861e52eddccbd97e62e4d2aab271e97baa30f832cc405816ada10411181816617909af48a6cc6108d0267c469bdc06503130213ccfc26040058140b2c83abd2c35179f95a8493a242d6078981b4c664985c38f72e0d970a2e40320e13118ac4e0e0641334dc22896b46cb8c5db059b26c1967532e215c4f4a32c88531f4c98d8b99029434a246f54321041b0c7464b3164da4d40718522dc28afa1d3762aad22053aed83841e25e1b3ac84e4bc63278c20b6864daba6ff4ddd86975fb85569c7a912da492769452746aef18a24d56b391c8976ded13b7a690db49b169b79a335d6a9736464356a5e29a261aad9c50fc77e5e841e4c558e36e879e1301088df03d4290cf59a2475f312aa981a2d2d2c20a593f2c155acc9a03a18e660120a3cd2489c4a886448fffb92c4e202169a03082e24cd8b3e3fe125c49a396e4c06b0e0603d2d901b3c106de402f3bf426c9da618e937ca45e143d12622cf36c22a821d5cdf5b299e597048824e7f7c735a9f62c0926b244b4e47c90997b30db3934c3f83a5b72ac4ca387810a1e88552ddce0e7c14b2617b007889b26250c757ab429ab020b21378841b728c7d54d0413e776183a4113c2ab513dcbf44e831e5115938533cc4823cdd91ccde51ccc0b2d8c438a0c580b899518fd07e6f8085373d6652c5a81c81e1e6c945133c6620b1565cca1421b3f151564c2e615724a8a0a9f491ba2a4d55256a3bda685cbad70550513a192ee62ce8d7a4089ec4963106e9235519d5a05231f54a465cb4aa04afc5f1de69dca6db92627443392134ee875340349a042d471b5519892f4688c894993e6c4b1d60de2558a5b886e8fb7b4aa3609a1afb257d22efb6d19a34f5cf42446e9c5a86aacce309b3733b0496a8e176d99a4cb2b1437249162292062b75a7239d2200ac6550c6bacc67f6463620686c41f889f601113cf3016028ac4a68e30a449162f501de77a713902f713a81c8413409626b73d406e69d88fffb92c4e603d85a07060e30cb4b3fc1a081dd24d0d103725acc7409da5de56e6c18416295779632cc6a36dcc2dd23fc51e48a8d44e2f224eaa35328e431592fe4c462b0f524f2ad6bb45044e298bb6f66e45c3b9ab8743a2626d6f39126526f8628d7f9cd5943ddbd3d1b7d91b6ccca41cd272db4527b10d25b5620f65a6a37ceb3652d1410e69f0c6248bb12ca1f4f60b600c9ce13f00b4c6093323eacdac1d451001a4cc4764459e964c4bedd2437044aabf5fcdcf8f181854522b2c8cf351b6dee9e326ce1a5c3d1489555d3d26242347931a7a56aabb3d5911e2ee407a6590bda4deb2065ce7b2c21456ddd91c3cc62692894987bacfb1ea0b1c160f0746dae1f12c28b37445280f05498811225d2a4446a61e70c325039e9f2b18d97ce8932fc60482a0e897758931862cb4162ce357605681c50f16ad8475d8b2cf0a5162fca6402058315aeaa00a27932052745d8b18015916a2fd1691f8940690028c3ef2345c40dcab930208409a333087d881820286d6108f0161158edf42c832ed3095c1ab5913a5245126131060310401af34f4543d4b9536c6921984e926cbd32c2fffb92c4e303d63a070807ecc14b39416081c49a78a7db4114e88bb8acb10649f142f6bc525facab5282c5dcbaf1eb368dccdb4b4e29e410218a6766a204f2059cfd694b92edb3966e481442dda52b7369651569684d49a5e9673902d06d45189460aef6f32f5369456d46364bc06d55ae0e4934592558c2b35a38c3dfe9f067c0e2256d28175188e3f2ed2451b85268b6d2c524282a50da987c8c780469c7d3c60f281998966f100a1f084a865732b9c19501d0f1aa6ac1f1872acc4903ee06cf2860b01120938f80512046051ed8e48cc3ee0691b19656661c2b50383c269774ba2941a52525af7e70a2e0b442e200b7bc399cc483931fa9a91392e0644f0d089e4c8661cef2c5d11abcc2451b2a20be8206aa258ec2f0ae410645251feffd6582492d431b0e9d2724ba50696c6b5b276375457d20a288954cad5932ba05bc9ba6cbf3d329ce453a0f235a06a1ea545ef9110871345eaa003802038b2a6415899a4f274a2605440654a41a6c1cf698140a06c50702c90e8c152123657589cb0b4911a58741023559d1546d0346ad1b8aa1432c480eb49634c1aa3ce83190036129fffb90c4e982d8ba0b060e192bcb09c1a0d5c499609238591856f4031b169cca57749a8e51d9cc0ac7f041222c9bad8e8960bbde48e39373c991d5207d2e3189815e9489cec4b4b2572564d33155c5dbe1b76b318198818b3ed7103d18bda1de8ecf64350852d5050519b244f51fba4b9b3888c22bd373b9fd1b4124f970b6ad6dcf2bc3081f89bf1db4914c63261f40bb8b2cb13b36c368c88c106094c2b743290888006284b32b129acc3d159ec69ad22f7b2e66b9b39b517b4c860acac745c4cbe02a8342dbab5db33d779c318ecdd8f59828e6b0f285a6a7679977eece49d3705e20fae8b2220bb4cd379c3b52c23860ad4693d9b7729a4e4330959ea6b3748ad98f9520701cc3b9b87e9845c08d3cf1ae6ec722f7f4cd02295a9b59a04fd5575fa42c96dfbc2b0b0babbb85c1e8153149831ad31db10472ea34b48d4fd094503102acc6db8a0386aa2af00cd086e91113351e40ca4c5d1b37dc4cfc63c20029a3d201e240706c0006077b41c0465ef260221c2e64545448180ce82ca32e45870511263ee19648d92091d80bd232c71a3b39132a656b07d336724e00f0718afffb92c4eb02587e0906ae24cbcb1340e0c9c619f90e5b288408aac99b3705d12a658388ec561fb5c880e228b2403e4e89668d244c8e64a4ac9d778870dc1614ab3950fa7ae6cf9dd4128b6d97bc6ce0a932563d71d28c24b596563b3924be98832acd24dc9d2e92a2a9aee489f49bb1a9b3d226aaeeedbf73147a1c692a64bc23d2a609d57313962443162b6a2cb76b30e8b860d2385a4b9022369512d97234cfa7a0b422dcdd630a073912c3805c020a18ed981ae52487470c843de242819a99036588c9d95a8c288ca4d32511b6caf34fb2dfcc49a51ef920b568eb1e0ebb5065bc04a14001021616b8a88130687c8b11dd8a2e63adb083f0f2a505b78c4c43ba87714614399394ea933d1c81b2892ab42fd19ddeee434ba1f21aac261624914430856e2c2d4ca1a2be887d4508144bb9006a8f166ac4521b1e45bb90ce9ed2e0c39aa492003402a41c0032ae03501f38e27cbae62ac698bc3aa5098e6bb0229312d100400ca16e0d8f085881a04c56198854151336884e10b7a14e085e8c802eba146404c44613a597d9b3192182344ca2b6d414552144885e153406264b4662fffb92c4ed025b4e0f020c7121428d3fa1a5b48d797261e526b23a17b3ee51021627da8324935ce63109a480d31680a6d39b5956e4b0fc114a0e7f49a25949d14d64f3d490b5a888164d145cda26533ec2ed4ef0da68a0551a5879555b93ebcdf19b136505f618f4a9254a946934f5e9a64689611193d07de4d1948ea067204b2694328958a4ad310d219a3d9eab92c00a2eb1846fe376930e823f30c89cca4730cb18f020082f30d049455dea18613b94e52faba26938544d68d8baa8c46e3f1cfbad7b2c1e22c337b090a7d7b553aadcf30b94890e5c69602c71afa320394bc353458beb5a0bc4a3268eb72479b8eb430b394a1e64da09d687108ec169ae52a4b0fd34896a2a6cce787f2237d21490c449bd7be591cd3c4477946f17589ba549f26182b4f983cf39cad28e41fcb91b89df944074b9815cc539bd09ec6a249f5e59958e95f2d933a95659b41ded3b4113750028017190d0c32ce39802c28b42e218b2306a7022b92e01ce04a448241503c4184241c56141d722261d1123425254212b823149313b6883c3c69b272f0d46b3cd1249bd36c8915899466d8a7ce2f5a4fffb92c4f4835ac60b04adf122cb0940a0c9c619b96234439d2c59424d7c6b164f4568e728411b4b496d566dc45368d09f6a468fa928e4948266ce2f53510e4616e041a2073551dd55a6304b8d11536a348d1310a852174e2bc08755657b7c26e60cc99d28867b8c256b1314af38aca96d92169bd6d8e46b3dee1dc71aa5626944f1154e139455229611ad35b506b31ee793a25e29cbad18310a488a462c359c686e61a20184e6061a080f00c42261087e1e01d30fea258424869768aa15edb12d45b232744590ce28e8fa558312a284f33b68a25123d841d2b1f04a4c59cc993d4af4fa8f498a6926c7bea568cf51a817665a499bb892874da30c595a9e922132413939221ce042aa510d4c969008d62506954f0a72536a311582f71148e589441d16ed0801a8a888745f126d3f698d5f708934e8b492246212a33d1ac6a979f928b2cb7edb768dbde9aa4ab95a422ca9393a6db5ce44461260fc26c1b87bf021c1c67ad4035c4e357e70a0c9242c090a5b4442b05cd2ed51e426ca858f8d4d803d824e1b8e5696e690942d25912329e6679597c32860d379a6582b35690e107869fffb92c4ee825a520d04ae6d2642f9c120c1c499790c0662521355195645481868ed269a081c610a9df88086e392deab9f04e89ed85751e3db205da68feb69ae84c3106cd54148aaf6d7234b4e527346369c8aa0dc5ef74da351b82c92ec4d259469223660da9af74ded6dbda14a047358d172282071ba356853ad4d0f5349e251a8288db2e731551229d1eadd1a4467f2edf13e4e70f1961f2503302bf34aaf39f78119e991e59b900b5818a40b55810934446941ec3ae4f8b2e5ce132046f30db0a130d24e204898d363c3abe08d7015545f66b0a101554d118a0e2cacf08143e4f4de1392456d86ce0bcb28ac10205352418ab36fb6a2b365a65d3517658dc6d8ba6e6d66369dc197453a6d15c676a77a72556d6e4b15beebf7084a2e5dd1822af286223b93373c9a536ae3030e9c59f9bf7ab771622d75a49cfbe08f45f372f6f2a71eaf259af2dad9b231aaebb79ed85c7355617a41092a08ea50010138e604f67ba387dc34023514fd31b0961860002459aaf63f9505dd4edc9143175b2c096a8e53284ac42f7248d089ad1e9ed4a5a5f7a1448eae5979bb257523a66267befffb92c4ec8359b205040ded22cb0b4160c9bca4c91bb90dcad6790685c0c4b4485e5550657c81640cafe8d57fb098ab559d3203285f1c42cdf27c95cb3486e189ba4aa63d3214c6f64c93c62150f9183b729f9863d924cb245a19e5223b27e52074f53273f9c7838b299214b65a486e81be62435173701e8bb74809383dce0e452d97c35063894a41c813bc41234d0b42d0ea27a56a40a9f17d43208a0ce8c9090cf92840e2012a33a3d090f650423c608532188a90232e4a40acccb68ca09e54a5a82a2a0b9b8c2c898e2888f05ca48ed59f653a702112cfe52cd67b39cf309d907073e5e314bc4e40cdc2f50366c982c28d4cacac1c5ad0fcca1a0a4e4835520636a68f441144fa4ef147ca1e67482a0643f5a3350a23278cc208b35abe20f0665f92ae5df25e9a6f98d2b519cb975ce6422fb88ea646f53366af0c336c694520e0a8ce6237b05958f234b3866289e231071174d5404ac1021946f7299c2460344231e360ce6088611e8ce6054e32fae4b71d4b5e3416892578ce8b87aa1754785555d54b42eaff5c89d6fb8c2b55835efcc1d012b311674829884a5b078c1010fffb92c4ea8059ca0906cdb0d1cae7c160c5b49968d87aa482a917e349e00fc101d6693be3adb640895207a48133a8675518923944d1b893107a8276787d002596927448987a093892914930651b01a988a3261af8cb4a048e667d1896a17a608d459344d37a0d0915d520f0040b64e520a31cb5f9302041af548a2145c8d369c417bba6a6bd26692559f63154b3f99483ab0d01390c06c8e2188ea938c288cc6ff0cc811658e93814a22f2ebd3e84241f092464a21b8493bb403b3d4517da9c1e150c91884bdaa9cc066477fca8c32b49c862a100ba344457259fa727995008cab7c1801d0ac2187b54980672210f40c91a5020716a5e9a0a985a2493797932e3a0660a39aa132903cd2903a19d1f708dc92ce1acfb03744d17a9a0705112a810262b58a4073492a51a5c5e03e9586b986a29c56150d9b79b7e5044f3feb2ced22fc5b1f6a3d004c6d4c34902886968a74b5d9e91f6364d43b9adbcc945404b46319643884339263011c1a39a0786239a429ec00c505660b69309c54a6ab35099219d23214636bc0c30482c23a07def6c4ece368996966622aabb3a820c238a13e8fffb92c4ec8258f20b040e30cb8335412099b619b85bb8c5e809a1344c450b1e78bf2a6d65a0d63493ca2f68604ee4513a5884e5a3a49951b81df277a2360ba1368d5b2375492c5503bccdebaa9561ba666d41778d218d7675049364c6d20b275db6f0cb0b2e735149a32418b93496652492262abf4452105f1556e58dec60ac0d6a1c649d47ce3159cb2d120e83487b05209238dd7458c21c4fd2daa419bbf2622a255663d70d30b0a317a5348015744a1c140953a94c6ae32f3079645090fae2828da4a105c12ed66e46e988f36cc082cf0e393602923b446ca892f714934cfce74a2b2444e22f544cfce7999326eb931308fa4ae10316c2e211f367275426d882312bce44aebecbe408d229b059a939b86ac8a0d0f03b636b189434136640a5c2189f6f833daa0f533bd14b5a8954b882d169bde44bcc3f7311774f010bc1970b2d2ae619f60d2b7091d0b7363907e6946074f39b4a47c4df00e49f25398c4c0e8a98421663103a7623d073bd9bafdbb102a4470120a85546132a0924570f210254549c2e6722d9d74136919b3654e5239b2dae8da0f942b03cda81f5c9d62a51fffb92c4e883d98a0d040de92642e4c1a0c1b499b82c2f43b66a41b9e10c92656ed912324465a0e82c0918711b4c26aa8b2055c8717dd6e1abb976cc644ca048b9648866569f5dae9cd52d44922b8dda04d77af86690a48d54e5e2c24f728c924a958cdb60ccb49d32664a22ee0fa2497ca6276934d9021db3c81146b070bb104055555ea223ebb465c81e44ba99a2ba38e6576a4221e8d24a10a77842a48b2118920fa8a4293939ce0e96a4c48a44a9d8a10009709e93e558c7cf028c3cf0958661036f33343d0cf47a295338f6357295936562ce35a8eab0214f48c5b44129354c669770cc522531cbeec06f6baa4a26886259db292c4b31532797b36fa7ed99e0c789c329a4c2a4cd4a9ad6a6381a59de1e79a12c62ab13c8738e2ea5ca4e5eb32cd4a2eca7b48af2da4709c1714f0434f67f8f630b55d1d64b409d1cc325ae2aca6779ec5c4902a4fd625675d50350d9cc996428ec75ac660e1c645846de009825fd0cb571671e2878c13a837360342c14404220141d48f41aa470811c933e85612b122c3af4e44e70c345665a56424eb90344123261e60480b24a11d1b20c7efffb92c4ec03dade0b020df124cab6412101b4997014689a49a9254990d20911bceb3a895911aaa9726373454d81252cc30c530d952ca3ee0ec275d3c6958a36d24dad318a51b6a1b8c54b4e40fbd14d45904d3a65b5745326d86662360951ebcb1e1552d4f5d0b1b05d4759b89f428165d6aa82cdb6aa855f49951d61522a5a718a35258f6c98dd6bb93da908d1290eb249d226f3c4178402e009a8610cc72f066e6c03a1405d6308047d89408c080e508cecf8809d86cd50133b07b2711552a18c9d2b42f9b6bd46c8524cee168e98ad6d42c754c64fad3823790ca51a3a892202144ed7462d325fb49b0b39aa6a98de5cc9e561b0a3d15d16b91a3a32aa230883f379bdc827175a13f334da6731bbe4dd8416ae086063126d03d268dfa4708c91acb238a32abec882689ad9bda6f18447d1664673b6faf67982255963c23320526937329d01b9206a391f68a898e36814a75535d950f25a44c14559aad8b048a2edb6087403b290838e86266852382c0a62e543d28a4d990b25434790c1a213b5174c032cca93791884bed12e9563519ce70b2a42027b1816708c93a4b3889f23fffb92c4f0035ab60b02086d214b3140a085b6256948c63cba55a8da145a3881aea2cc0f67d262139b24796f29efa839d0686765427917a549ca644f59eb7011c7b618d48d5a573acfb3aaf199fea7da509bc92a8ba34e99840ede821b1d1c0f7b066de15ced320833956e996cc974d1b5655f9b692dfdd99afad132556ddfdb7762a1a2fe5277e0dd20201d7518cd10786999b39694c7e7cd98062a421a622296c3ab2cb29223889740a0e1f6de423ae3da2c4a5f583bb8996074b8aaba059c5d8159088508515738481e176181fe2b242c7d65539e33a4e6cba6649e2a08deb9a8b6d8684670ba58803ee844b209a33817383cb740c1736c9874542a5e1059b1a4d93ddb327211448ed246ebf45cc5ce119488d451a465c9584cd90abaa2cda02b33649a8c9575047a236c90b4d64ca9d61f32a6a071053314749102e9dc59b8232382ac3465254cd3328fc5526648135136024d2c554635e86c89368a2e8a92b9b4d6b73a676ff980850f1b9cc932818825c0c07154c5263490e16e31ec1f9b1a6b43047c646789b1c4b53365e5b1b1669874c9f3e8d7624dfcea1e6fa149e4c2fffb92c4e58056420d08ada4cb837241a045b625705ed82122bae5c34b97819d1231d51092782668c8459752ddb3cf4566a2662153480765b3ed13279a89d3041adca90ceb53187bad2f21b21c524448a4edbb57d91d26bbab3c02b227124e0f64b501a7a7ed9ccc97d1659aed0edd68965958cb489b92c03dc4082526c23073b186690a23478e7c4ca51bea4f55e1500c852150730f773ea133ba4051a32e4f12df754441462023152be83693a302a0f67a7e39a93b3e0b8c0964526ba64c3056116336a4df426668130cf20c50a965e20b1325371b360d235f944881be1a713b5a99193b040a3c650c62b1020400caa65346b981120d429952ed36e42ba325d271a149128acdeb90a4c5ac5c3a71461b2cb200f451350103894ac0b1f20e2c4e864d205970a1c54a2e86f508e20278e139b7134ce13a17d1392e591a68e475e4c4562bf23aa1b3eba25290b0b309296f1960eac7898872092ac13b2804a6b92a9031659126ecc34f24a14228d9d0aaeb29d29295401afb5320d66a499ae673c62ae0d62bcc480cae0fd9b5a36283b8604ba7a0c9c28c318caeacc8e85cc2ab3d08fffb92c4e483d6fe09060db0cdcb8e416001b62569a243b103e9b34696eb29776827bce6224934891a84c05353924ea481e774923836ea0cc5b1c8e4441e26491a740b366050f830cd814542a20fc3bd9ea590708d693a7139268045b5d7747bd9db885acfa84397127279885a6e559f74b7374db8ca067c48df87a14c9c739d3a8e5e61886e015d6a8c57cb2adcc564336f288324bd68dc834153067c45005d8946045d3194a13375633018e2e430b2507311048b34bc72a9f1d1a864e3aa49e0d0b4c890ace0660c84403c1285a6ed61d9075a59a23166eb0f9c8183f7d15a749a98dae4ca06c8492c9dfd160e2725d62498b220344153a7d7b54757a3504cabcc6768d2eb102706b75ec37294e72c8aad2cda49a1555946d68ec9c4133c9a8ab28b1054d543340fbb4976489745ecca8fc2ef5a6bde196db5d0ac71695f493449a8703c1a6daf69ad8dc218b4fa07c539ab343792520ba7b04e2d31b68d4de6b11a71c734ba7bed0469f2522a6200044a65901ac3b680002ea622cfc18251361d6e21c449a9eb5c9ccb513bbbdf2ef732cab299b612115906371c61d6751af479fffb92c4dd00166603062d24cb8b4e41a099b6257899263c8c9d372906f3c9363631b56507ad7972bb619a9b3932144abd7b339ba608d8825261536abe56814cba61a410d32325b851091bc2a340868856b3183308c6b02a6d4b49273442f330c68cc92c6ed189df7d1b8b48f28f5f20ccd59f8529d75a3d712841a9ea0f682e68ee243edb8e12819eaf0042c1c5ac1bc0da120c2dcca1d69125020759408a7e21ab84427426e381c28725a3e33312da2407c8102f3f287d7606b248c43cc1893c8530a1707c8a94571225854460586511224bb481b0388d91d0a1861023402ba0a6c6110aa1247b6d30b60d262b284eb22796742deca0d61740e9a350a69d622a36a4169aac1731b35de749881692c8d85201656cc466bf2a88711b69204d4ba1832da861834d4f9db51a513c69ccb46cb10a035c77d8c3240c83d949dc51a84f11492b8da3b68ab7419f272c80b4461651e7d22d979af252d697c34bd06b502b7d0048037ac2072d09983a25cb5e04ea100ed2ca072f9752c9e29f852c88d03029252a1326c0f4d93e1a9acd1d1a328513aea7d0cdb58c636da054571553ce5625fffb92c4e002147de10d2ca473cb6f412055b625686046311df8412822e6a38cb123cf9c2394ed2c467755898119a4cd28fc4ad057e95c6e5104b453b95ed0eba7670032c83dea289ac203be9973b926137d89649a50a44532b570ad20e7c96c63381f6039e7143618ba3512aea9938fc8798c1f3c270eea7da2ba7ac76908a413a4301f41f0fe7411c1899130a42d887588b5604c832e8cd8b82a03320646c1b4c5b22459a5da943898d151598d50d70588be5981c1f5c59a681d52436365c94844890a70f16159d0299329b24a65582343a89076d46cbb028b5b573a9c34577a208b7124930ac175d5e5526746a691844d621e01c7c104952070244900744f4899a046934c0cd4a5241395a517d04d5a6d33a1f83032777a3285206d920241038a28ab694e96b21a3b96ee4d2c4b6d5ad86329edeb00839c22d5a3a9029408e44b30e92d4683c63990b69bc230480fa2838c229b5e18a922b4754ca68061a3674c6c36a85d030c80074939887126548fa5a95c383d1ca85865697521d2755cda1193572c9b9044a3b2aa27d66980eaa399271c63081edb96222826a203c23478b1fffb92c4e68257520906ad24cfcb2d416095a49a381a021a3f012b02750f9889c0b3281049b488d61010c0a99670b2be10140a6294f7142212431289612af6b3db7c4e91ccda84cc8ae6d9f48aa776a2e8c2066da031688f206dc4aa402e624da0c36c970f0ad415b04cd91943afd1e32866cc276a61c18993a6e643eda88c40c15d3c5110eb2ce49b68b34c9d5d533db2dac2191c1050c3da081adb223a9a13ab978140d1345a4524e1693512674114a065850bb930c1cc8a17685692b64bdba234cfd3e751b7175d069659725112f180c1e58a11250485b1eb38e39eab5da6912b538206103e030f6cd4ba0e774c743cc161b987c7ab4f7fa38acc6b883488ec24aaa5d9330d74191d3132ab24f99abc61e6f7cc292399cc1a107749f4861d571dd222824cb6e4670fe92661de7547f88d5e1c85247f8b52bbf73d348d3715d21b3076a3115de4a4ed7795198c69e5c01ba66f2b1cc77f18ff9f08cc9a18d20470aa00db458ab640d28eb8e9a30a608a83f6d68ac4bd537bb74d3f72964576989f44e67017de325a888418be380c59e6d52781b69854d3c92da98fd3ef370ccad3fffb92c4ea025c220b000db12b8ac2c06115a499b9741dab174a7af2e450eb0b9536bbab2c3289a75e1dda3c203589fb051eb256e4d8bf564dd56f082a1a51de515856c209d997c7df2bb35b0f291430e2bd6a933f0ad7353336e64a4962840a2505e42455b62065a19528f2daa3189968a355852091b329742dcc9ccd61ff494239fb7ad2f5d29e56964d29452f27b186ae894210c80003aaed1825e17427a8e23e9858e1955de6e2624148a197febd8a6bd72237e935d8c5b84dac2a46c2e98c8882080eb0f2127497168c185841ab1746c2130ac12554b6de464f2693661cd92308d230590e0a514d112144a1d945d04d738a2f08ea42d07a37448ef3e10c994cb343074e11d34aecd646e49b4dc650775758f4d5721059bc295a236080a92cb053ac1b07c05521a596e8986000b4d7a84b9e56804c4124d63cbc5207ae9bb5c0e9a40e7a4cc23c9c306a7c6ee8c1870c38bc7c84479ec7234e79c4834995d9ea00105b68e01d8731100f518ca66a8a7eb9e42faacd99ad72be76355f1a3c6274b8d4b8d3e335a142b0b1d476c9f21662809e64a86f82e16bd152e4c5041044c1fffb92c4e78218320b06cd30d1cb3340a099a49ab93120ab0918559f2f08d249271debb00e931e221872a24102d3e4d2b36b5b64b0688df254f2ac1e1f736e8208d77a48979475334cd2b7fa591554cfdca7ab6ccd2253ed31e81cc24e86a5b5a05c3e4c45b15ddf765a9ef1afe47931bb6eff929d3a9516dafb1f5ca83f6d4cd2f76c8b3da65ecba14834f28b1336c0dd58701241881c27387839904e518e26cc1a4b5d88d2d76c4a3c3a3fa282b640789d6bcb562f25a7394866d38584a1a89cba07a029c2cb28bdec39239147633104c4c227234824c342911085c48e0f87a28c9b0bd994e29148715c771a4e48a2da8ff269b2e710ce249f0f986d8a6cbe4105e9d39420569582333ea2890aebd0a6499835899f5119a6a26d1cd191cda566689230511c8fa013b972d19ad6da37aa81970dc4e29d5959e2215ad4c4c91a8b64afa2eae5e9944caf18c1f26d98cdb5ad117349ce2b2e8196ccc5849e9b49b647a9a261ec559b87057e10b669251204483d56740fbbb226348c910121c2102884b2ae9aec0790f28a451b7589195db8ff040c90091977dc5bed146b13d7983cb2fffb92c4e6801736030b2ca4d58b6041a045a625b8ed779ac9394840d4620a684ed294114b6318ec31ad0e86be18ba9a250b2456a164f1b4aa15ce51820a094273f8127d4a685529dd2dc1ec464c7bf8ef85c67a6922e20820049d5f4a91fb91a4ca51d751705142c797a962294532c8a2e7d0fb24048ec169325ed69a1e58b2592dfc3b9c7ceb1ca6862a049f01346e647113af011da5f0edc4d8fdd141b0745229b93688f06c2e2324190697356d2119204616328cf11b47da4475ecb6a5b5d12aeb9a16d321262f08ec94554ed1c7a08267ad1eb6423054c1cf2a4cd345350930a7689c80fec9626a69b71a4533921d305092cd13cd06121988ec4c1a524912a12c48d122a245b226c52f211506089323530e9d42244515533e65233350985e38c08eb55490914188d9449380a6447ac20b5d2a8d22bb4f54226cb1b69cd3e116d69c156c70573c467f4b0ad245db4d6c70789ed7ec4db55120432841442d62800460a870847019a578b520b90352362b028c15d5150ac1c9c69eab78a2d50eceaaf9814d188531231869a2e697a15b4ca8c0894cf247bb14963087c9a36835cdfffb92c4e403d60605060ca4cbcb5a41e0419d2458ac945015666c955c3f264fa4557920394a2c9a149ab9ae790286dcd5122e9b586bce29531ad363cd29142e404087ff574ac8b3e5906a5ae13cac960ddc513a5242b5c6685e5b1e310d9916411b0ba345e6934562bbdccd2337a8b5a6c9d32e429b2ca16cbccb6b641dcb296caed2a8a2b198b7ee904760ae1f5e15294232469c97c99d8422ba4c5823b914e3b116f96ea642eb22894320c095547160f932e9356380b236e989304a071da6941c13440826b45c8ac2c1c9a400b498e491269b92d921695a12551741ea40caf2e9ea25d8135236f093a7d90e7a4bc33709b36c9e8a90e67c34d68eead6276d657775a7b2f86d94c9bce4e611731cf7cecb6731b5057b9bf99acf4bb5639aa9a297851a7a640ef8d328b9052f4e92ca043ff9999689de5526d531d8e6b1a5b9572f45976cc9d2d3156391b147a2d607cca6a8827b0f7c57305e0d0d0f4b27a823c2954847fc9178e8068ece17974d008e714ef730850422c235c1f6c60486550d2e9983e44518588c4ea1991e51c830cc4ba1428c982886d391c2c7162055423213fffb92c4e702591e09042cb12782ae4021158498e921571f82e2a991c1932933915112da563470a2c7c9973aa324a42cd9080b82623120c7273634548e4b8ba26e63a33836a50514845128133aaa2549d136c50ec139a6ba44884f30c26b901e8f525acaaa2a883a8e1a462b71002c55442bd4d73cb2a53a446c198f4890e69bd71fa54cb96d25ed13878f398362d87cf156d9175e44caacac250900a06c4d7886fe6d4c582851adc7a81cf8604042dd2b8745112430c868bc03e48552710fa200f1e4c22079b079c5c89b0f30893689163085916235d6610e41a61776b338202a9a1304ed214103f217474d5b54aacada9ad92a6e49a9e3dcda0f0b34f8b534f62cdf4fa4f47094d37d6cd45f0bc8e2174904e12570db731532ec36da536a1d5510b28583cd205cdc544b2dce69e8df65db402749c46896444aabd9c60adaea104d11a5ae0b5a936884851a715f60b4a4cc9995ea1698b95abfa095a39a538a8aa7c0058c3d02241090e609705a8512b32fa4ab6e97b2f8172b17e19d21431406dcb2cb422268e94206e489a42d079f13acfc4e074a2734ed5a25dbb611f6c9a4b3fffb92c4f3025bc20b000d31294b2640a0999c2459d29cdaa538cb865d07dde6ecfc91b1ae83f6591381eb6e4b9b59141375949a7fd0be81a91a7b2656ca1891d7c974659e4659c73da61f27a720a452cc65276049e86376c820676d8772e352bb27269510e8f17a5d04bee7a836f68f8353ded55947ee484b41a28cb6fa2998b6aa9b45f1712a4aa68329040f8449c0601f8218ca612e842303bc471d44a02b01bb4593a601b192df6022f149308cacd6018a157060509129944c8d4172b14099a4c9d34d0d82880e13159d2488999056d255161c7c4464246465539bc2a2736809e58855650caad03058c8fa16dcb2c546484cc9c464594271f5d9ad3e46262d0ec91b9b622639741224324525ada98914b9698151321140a542028a96cd3ed1683320fc1616b4d62eb1824275dd86da9f2b25d23e162a8fb09e391a33cb02c847d12182325895490756d9560648543e84ab4cb2294684813663ca37ecb2cca5924cf1e4a9017f82db7204620ba07077cbec3c0b73e46d24c68442c7904e544d323371549e4e272647389a05db452254cc118a3c4c3b89a3e5440f6966b2300651fffb92c4e583d6420b060c24d1cb7341e001a62558e83a272b010803425dee4ed5991492d02f6e098162b7db317268d96d2c995cc8af4aef724f7550cec56212fdf262352f0ab64fbae1f7c25ebdd20c53d4674dea55150e5ff27ed5fc8b6cb6c78578d59771929d9d6a9a9c97a757b23a7e35d641cf2aaef2d7fcec5190f258e0281c8ccb580e745b935b0aa31e3b59908d02ae6e242f4061b0e9f1c8a64e5c3a98bc6a228013590a0a0d08d0064e08094f365145942648819686048608dbb45e83a454254c7d1a9340e2d46d53c6c688e08a42b15c4ec09c342b14db3737f42f616264e4a932a42912948b48644248f58c2308526f10bcda129b6dd68d2884a2b0502040b9278e2136bb7ed82079b6155f48a00c6c4a93c28c90419339162057c0e302349b239d25150bb306e13916a274fac940f364273a6dc95662866861354b1a872555955c9d1a7a3d28f458696d2adce71bf4a2214a4598e8a9e5bb338a704455c3e623eac2d182a5833434e6866b1498a4318b4a6e9a9d86c9c8871c9ca993918bd8205cf58bc1955349edc0c0a50c5074e0ac1a72c81369fb24d4d2d875fffb92c4e48254d9ff0aac24c9cb62c16055a625595a21555d5e4a3ee3f10c0da2275c9903288bcc8c9534ea2a152eb5dcd8199a929a4dba6288b482507c9560f7d520c373826c5bd37c18851c1432b9d7cb5fada6de4e2a44b215eb357a4d05d3678e49482158f5649582c8553aa2e7d26a946da413aea4cf630520d26dc87d884507722c4e6892bd48c32aad1729d690a8cbf23149680dc25c811061c1980a95055c6a058a81b60de061955b055e444691834daaa84cc21091e984b82031c4ce7d36ca4da90b5844d03ac28272a90e493604358658181d0c48e3705c243cb630d1336d266d930e2c9a66a69eaa98894b1869e498614ec40d4b030e49141c922af0414047fc012f5d6475017972ee587268933c51172ccac6442b020632920759846c81c19c164c3356490353cf66249a6c7c6b1a494728e5b9ba08209949d1230faeba93893740c11824385a02924d05030bc422021c0404e98817128c34b77c5042b065efc6715749e29417564ad1d58a10089c408490d4865ed08c804c442c688081821250d2c7864a61058fabc8abc1804cd1370b2f92172366c62724a531a0fffb90c4eb03d8b20b040cb1274b0d41a0819498f8e9a92286b1212511b04c9707a1e9c16596e8acfa4460c516417380f0f125a44488dbc2629454e62faaea4d02060191d620e74971b8741bb45ad46db1409239305a8b405061bf8bf24f531140107e491879a2dd3c9a2b567c257576390313d84e090d722d72e8a284b319e394b4e8a38e187da644e65081db1a08813ec2f0c08354153da9e17cf07a1e0ecf4de3472841f95225872e3a7e0f27abc625f711a325dec88e6c858951855040590b6ba022348833566ac891d4ce9697f114209345558233668f536936d14c79736612ecacd4a72b36ca3c8aa4c8e9f6b69bd38b2e473d62525f57d554a81012ea0468e4be2d23cd64a08857a468a1a39283f923ba493334d45149d45522823f19198ce65729f272714a34851225d8f34428b9cb16ea2d07ce64c46c426ab319114548a6a4bf49aa8dbe169430d07903d2347ada80252d4040d2c70935d5425204a32e5c55eb8cd04ab562210dc371392e1729253665143f2d2050c972541cd33381aa408da9b08cc93a182248d942ed996f166c9cea34485683d1a344db25183ca4fffb92c4ec80585a0b04aca4cd8b214160958624f9d42a9593184339e7472764b597f58b199a0ee26d751feab4895513675940e3b453544c6e4c921544da1627174a13374edc0e7249a14530a1a6278084b4e03b4f5193c95100342e4b92ecd508710701189ba246e4c3cffc49b58d8d2e7d9889cd249428e11a84e5559a48bd956d175a89a66262a161f4d828942190661560e110a1f809d15aab8aa1b96e9a253613d8411a0c878260e100c931192495902802a84636c963210907e646713dd22912953532c8817446504504acb88c40b1d974e6448fc09c810a5e8744c8895c8631a5d11e80851e8790a7a11a21200248c844501b22e2cb18159f362b458a95999292653223aabda5d2ba5110e28cc8e102dcfa8771487120f0528bb098c212599e44810b1915f5c9aadf22448813b468551926328179956834988d611d30b2c89f85585ad1af16c735744469cd19330203081c46697e87925fbc38b0da1810231f6e35440897b4d500440067bc450942c4e853e80ba84420542c14ef535ac4b49c1b2f691ac652292100a594218409e4a484ad1c28b0da5b9f149f72cadaa2a2fffb92c4ed8359020b042ca4d5cb68c16001962518342e6ee0efbca20199b30e46e21d1618e7472adf10eb03d49a14cfa78d4672e9ed9f55a6dd14cbd4b48c31126cf56649ba73b44f22eecfa7cb42575b2959a572a59e8ac7839b34cb4d8d2f689591f351f0f6356ca333529b5afb1c7b23739a8c455a48c7cdc4b48252897932f4b3b05a9125f9ac5c112248925d42d38a9f3304c42747c39309346ac7ebcf179e9faa804b2dd8f52033b2014864102a0310908a54444a54eb894d221100a58987520ae094902a2828b6202444162434c72a9c1a1d5c062bba439712744d07e8db53611902a9c0ecf9932d09de44810ac8b252424f499f281221e348cb2d093038d1a64f0bec194d97cd069030cb4b243e8cc690aebb418a444f3c234671a424c5d82e7d19846d9462ce911109f72caa07949972430d2c447605248927886c877287c80e8ad12272681a8749169f982332571f2c68ccd6e70e37deca02c8986d041500328000488dd09296c246ae642c68438292fa5a99284cb9d60e5e45cd4b90b4865db81e5c70c9524b4993a65074af12ba268b0625a66c404734d49325d9d0fffb92c4e28254f60908a7a4c74b76c1a0058624f8599d87762b235a24f247ddbb98511d434c45d10ca4f902282ea569b2ade5a748c16191830250d308136dbbab6630e74b40f59c8a68ed51a4e91b42f13073b4d2dc6990a20efef250e44ac65934693238920c67d376b4dd65463779982c6234739082765926a470e28d9489d1d9ac55326a32b01d6ac4a22cb5a8c24303fc48c104798ca430b4e1e682698926c6a26510b17a987056683830f307078f91182a30716263ea93a6ba87652386909092a14c8c9cbb171e8e0bc9e750a35565d460c3ca8d18296c54cd1f726849a89936976d0c1dacb229458c90ea4df28dcd344b4a0cae247b12b657b57b726d484e9665b9ac89269782375b46d3b56d3b7a15554736185b48fa156bae6947371bf6db0e7410ed19f06101855882cb5ea894a98c55b8cba0d690a6d2a90d2696e4e787d16e14ad2edba37859399d66642ddb6b50c5010c8000c15e2692512428811c2629177527e40d342c1dcb454115562d271ed604a6074e960752394521cd49b41dc823a31e1d9f5da2e9ec018a1295123ed75090a4c1e111232f9267dd059e3bfffb92c4e60256ee0706c7b0c9cb1cc12094f624d19278a8c4b63a7ad58a259a3f902e6cc2f060ea114399b2a2f02af6e1d741a55d8d084a30f90f9298120d5b468ea315a48101550444b125e1a2568e72ecdb4a2aa3d884df48d136376d53cc9045038e8c10929332456a43622a9124eb4ab64b322a696c773f046b2c689d44f3c68a9ba6b08708a39462d12e659314f697585281455c952f12160b16d75a5815e4b114648ac664cd08a61521303e54f88850b06d4401e1a8c4e268090ea902b17cb052ea0249a29d9a9119b48a481589117a606d250a3b60123634cab4131a4cf9c937962df4e8a48525052024d0710c42317260ab358d9fb04e92c35046d585a3c14fa7eab2e4b9a0955f8a9ca73ce68c9905e1dd77ebc39c968b54a3b169ec158672e4811e5fc7936e9069c298c6bc2a309107b836ce2c85a2f67a73f6e5df24f5ac56424b962cad5d8b6942e0c783502661a9ac20a1df96b6cacf8251fc6989e19c6a572d36258e2b632af0c9392c8859608274564a76c90bb49e6367898bc8111f7995d746f1505cb153b09a32d146251319a4227232240b94697c42b30891fffb92c4ed025afe0d02cc312bc2b3bfe0c0f498e9d2eaacd23426e08b5ac447715e951ed45257a061475aa22141c92093544e94f532cb35442b2ed32da1131dc2220d5d76501444245db568c205129c2715a2829f00da6a8d2caabaa280eae72a12209aac9220489a68f1b614671b96181c545bafd228d13a3d2069a744f9e3f1e9abac2c5979b0b173301c4512226670d1d5a7ac392c0e925c102096096a441270909d97894182a274004ae264deb152678ae6cd3477583a54c36731212a701195f3d36822bb2c986da55ca30c11314a330b22242adbd632899d2361021f726594d13125d57b716096a0c5f32f7e393d249121e58e235f70b144d98c891b935ab189a4c6eab89d6ab2253d6d346ce5a26db8a6e4a25dea464b7a8af4cb479621a411ec23ced26d4871edcb668e9b4adfada691935ce4584905da1a408884a324eea828ac1b5d1c64f5103db9fc9a32142c42255b40472653d52e0024014225c4985843415c371548a8882a643602aa27275c42a2425b624846c4001c4b25c79c8172e19821467a47580c986c9056403af2668a4245621623e26ecb22a83676287bfffb92c4f103dae20d020c3129c310412080f624601246c162c4078cca4910214d5d448cc5612953ccab92692e4cd20cc83cca13e7d34ba0959f2abfc5d436e41042812aa471471c227da2196976324851ab227b5d8424debb722eb0dc68b53d61449b49746ae2267103464ea4d54cab69cd84d661ac630df2630ab046d9f844ebcf46cde90a64aac1511a0331b6ee28eb7c0a49017fb885960997070082e06d81b863aa435a805981b2c2442c016722d2c36ca6393c6503312623148814539b9bd45a4442e401844a4a2295c5cbb0b2c2491558a25024969d991b495d53ba336b200d26b429aeb9f9b42c60fbdd73509e1988e9962480ca12368809188a26a1b733b4842e85b56ec8d7732c2d4480c5ada994f26c4c4f8bc69362752368a0d1a8aac6c15556453660c956e4c1562332ac6eb149e97406d3dd922722d27ec32deaad36bc9c8b1b468575d0c26285e985a6c9c0f4f5a79f4890c2ab4945dd0ebd393d66f5e0244a02846ea8b2751d149d20d725d7ddab992b0e57ee1b75d4d3dded5750e1b039766bb0406dbbdaed7eae2d8fad58efcb97784490ab308b5d9b325a3afffb92c4ea0259c20702a7bd208b1e412040c7a4105131bf0394f4d3fc81a5b80c06c9a3609d8d92d9d14a9b5b0cf0e7b92a23709aed2227f26a5b1b52641d57030fd83ee4bc849257633d30b41e92a811a25e4891198f45976ea474c6bd89cd38c54d63dac93ae987ba10561053764f4151baaea2124b5d6ebd667a596592c4d994fc7137544ba4c5202412249494efe64706401fcc70c03950f402db9e9c67d46c2c30c1626b80b97956a5d17f8ce94e26130d9083522c84a5232791d78022c744e2952cc8a9150b3246782e7dc70115cb13893551e202322443ebc9b204714b587943ada8d742558de8331f022520daf384d5c6503fc9330b65393ed512db91da182025e8a68ee03ad39e3526617698098504ab3c1638123a1920c90138c1e8e0ad214443b161a329d9c0e143443e1dc7ee4ddd59b613da76c726750b93f3e0631122003c812a4f81544086c9443837f46214b4b01e70a63922585f30243c27b4d1209c1d0ac4168403d23174842509deb87f27105695571f2d1d0b87a7f60a84d8db3de56a50512116e0343a338ce9b8f53a85a99e483d8923f164ccb83d0f26fffb92c4e580167e030627993b0ae3406170f48eb907be72ba9c49332b5912e4d761a89b28d5c47199544b60b0c9e5aed347e86b5d7db54a9b7d8836eb97d204c7a7a60e129c2abe62c17d3fadbc6672ea3317e250927b55bd539995eed0d8f9763f75d55ade1ce57cc59e382debf67d7b494ca85e4ec58e58249c36f369dc71654f99793d0be74eac590b8aaa84d7f54b0d69e9d911fe7dd4a92eacc56209fb7a016003f51c348b18ce673952ce90a501d367ebc7e8a208c8fa4656881a06c122c41645c18952db7b2344081a7992c234fa17a291d56f6490798e4514da5349a04b52dd8d867eb8ba944c7d6a33196f88fa5695d0f10895b392962ad88e9da4bdd986e9b48d38ac5e581cd3195ac1ad2796491959c1427a5252132c4b6bbde207788c5dff169b740f4a2486d157b2ff7aa666bb2c05ec3d64672e0969e268b35dd0da9dada18832f22590000024910b25bcb01faccbe2dae2b2913a95a8c933239af35359bead6e50ba677d0cbe1be31d0c4c1fa71992c22281a1b240acd466d889cc2cd20b3b01d30d8e70c112038ac9ba68e932a466c5d86ceae4f64d1642ba42fffb92c4f5821d3e0efea7e18402a340a0e0f4999980f01e5950f1af335a802ef3c8e04ed0a4aaa1655b212c4858951bfcc92cdaaf6cd1726364d6cc1b427f12131de94caaa61185644243cd328c2a4b1b92a4c72c9f1f4aac460d2c69526c73a7a46ca9034aac22605246609d4811984689bd72455c51929121496e1426fe66d3523026308cf913dc7787e693009a02ca912d358eb32584664bfd661090171f1b08a2219018469a870c0e27eac964b3e1b9306165db3908b5090e99265979d1143b9957b539ac8c8a28110a95c623297592c479ef1812fc5f1b567101094d19440c86c234b41351bb31b693c049d890e3a029f513cf96b842a21c1b57684513446a92137d210f8424992a3c04a79d2b09d8ddcbc2c981bfe099166bf724495a8ccbb949749c0ef76648090c87376c48a36521e74d87b4028e2062266da245ea0bd0e7be5c46fd352fa91652d4926acc621b89c2faf5adf9d919272a39368e50322a2d9b9d030a2a4c76483bb40554e24089476058392f2a202c594ea9b991624e4ed7c2907d570ade7212c2e88c5264271231a55e221f30760ac50c456c36c26608fffb92c4f2835c0a0afe47b1360acdc0a040c499f904d505951e6090b55a25051dd36961065b705d35cf11183ec346d09ec691cda7d9494e5083c6946d57ae64ab64f26da98685d091ce48043023361a058c9d1955a2771bae8193028158af1727269c527205a52364a3c7152424924d1d9112489b826bc8f46488910a868d52596a07acae245111f628a32466d620920106a8c2e9188638c112046dc00288626008119391aa2040cac4c7b5522c4265fab346e26d28c43e534bd13b0000a490661b332c79c04e3f51d4743a7a1c819498fda4c91d64426db096ca3ccdfd2d48f83496635a051a6ba0d884082532893311a4cb2325444a263c887366708a65ae50e39418d3211a48bbcde61017b11098a4ca2da55554c72d31c84f65da65742bf252af53aa6dabb8654a9df2118c9126ae26c49d8877ee5994d966a7a7dbe4d2fa89b49862ea7292ea6b4d2c887315031d582152657d05bf4da3c603e5876581140b9e607931503c40d1e13493099d0f1ad8c50222513dbde4e20402d2d78c1f32da330479a4098b53514d41105605e8891b88530614d68984b32450a32407d00843fffb92c4ef03db1a0efe07b12dc2c6c0a0409326f94cc6c846838a90a4219f550a2284a2d16ca2a3c487b3df41ba3691e234680da3b99846301148b28bbe070e865099df4d45369c4288c8f221a68f95284cf93cc13b61e512e89a254881116a720bb0b189cd76c8d305a64468a607918ec0987da54e6af13ca199102cd0a0f0f959964e5645173d9635c617471add450210821516557159b138c9a991e652d04b601f5ca2212b050cb854c79a871010b00f9c15db09ccb1e105173562b347d591446d4155d8525ce233c428694129b384e2469b516233c8da28b89ba8715d6d4690ae2db0a6a9fad562d33e2ec14159642f6b4566e4c42346f05d0122145e0dbd0b96560c2239d1468946a2f4308bec36f8a4d2f6b3566717edccdcac88bd34c20a29840ab8e7cd258b05246d6438590e3d0cd549928d9fc99f9abab85a7d992c55c8c544baf497236a506d646cae9bdb4899d28b49762d674d88b127afbc9b20aa60bc78491183a5447450096bca712d2d12482abcc939b2f4fe43616257943ac670e59bfa5996da91460613666daab2048856621346ba47cc0bbd26a260851288fffb92c4f003db460efc0c31278314415fc0f62401938a9331a7f12204308eac5dcab122b2161b55cc9f4cad2459da82d11fc3f64f024844fc0a144c42898d947c0c9faa8346ef524e0d768fbc9e8f10d632a6b9124453379dc2776a14919193212340f5d689e8b5c9da64fd41273689aa693d8f49e8533a9c54ea1891d14129a8358ae12288a2c8b9598a94633a440e0c4dd52552bb4e1464b3cd2b62a0e3074b21caf34218a234c587046ba753184c7c5b462c0b360992b64eed846047140a170c1845050a976168a526a10c409ab6bd34599c6199128d287653a3302532f2e5a50814a2cd28913b4a65134c3eb8f198c979306b46a041af8eb3d12372e81a448503231513ee40da3290689d1987a9268a9f89471b5173a5956e107ad6591424476d9a234329ca3856c79a5f16952cad217c35a2e856719a60dd97c59f7a466962928308f9d239c748c7997c9861b2aba3575593d7657a90acba7472280d1d3cdc2a400190823006c54e205cd0520387994c4a4c38090bb4c0a741e1679b72f1684c80548d04c20b23422e60a7d542eba23ee6c91e4053b2cd104d0e0bac70b145fffb92c4e683d8ee0efe0619340317c11fc0f62408cd55144ee6a07d70fd8fb4a205d56959a39a8c46928b68088cb3165032d212b34caa04691251111d969219dc4cea260d202627468fd2f689b616430d8969e421717f5a9926592c5ca991cb2042428de88bc2a4868bdc3126f5546a209ad19744650c75982a2d384d19fbcd69f3b4099d5a32b9c13524f852445d1b5e65902684b4d38391d48f1f892ee4403d57ccaa70e91159b56cad1624413d64d5728bd1c3fdcd7d887272a7a5a93cc5d04e18648d9504c7c91010493eda532ee92efc4872996d33a8dc812591368318c589ee1ee3548608ceaaaad070b4146125e0b2af777ca972c5f525527fcda6169b9a740f35880e2c80ed6e9320f4a530a5e2aca77b924134ed8cecd3dcba57e68e47c8cc1dfa853d4db7b2e6a47aa3b694981c64b73c85a44025296be55f1d90f72c7d3c96f3cb2099b9ede93a25c361264830d8ca02820e755261e4ae43326bc308a12a19191a2b302a07e75a4c1b86502340c919040990212440424c4ada8a80c7d4c5b29b32f1f3c70f100645e085c5580a07e5a7c4bce1f2f3a69ca3c5724b9dfffb92c4e603591e0afe27a5200ad7c16004c49b393cc2135084563889194644e60ca25044d2a5d43b8e5e691d69e615462a489f0a04458045e6cb45565d70b09d72e9960c1a5919c133d0eaaa0f17347c620215c367508b39054a86facb12a244a0a165498a92488e716e4c1d44ad321e691ac7c46d6bc9583326f98ef8b725c1c2f3524c8bcc46dc0b204902ea06523449882a2a22403a4492ebaee46697c0f03822602cd827e4c181f44692405a8e152a7c158880c94734d54e2d2179c5d8ed2c69f24d743c8d454cb95404423531c2a1669940a406fd02126228a4b2c35228681be01b966a251323b6895cec638a75db0bc4dc6b2054c9c893d419adb0e9adbb3e4edac98ac30c9281ce752704566f5a48a92c735a26b748391b03fe3a24905165cda1d33d1ad3d8343decca849cf72f69d9ce4692ca2caf3c79e972d7a9248216099157e9598625728d7e5ed5216619d2b64d106abb2e512ca84d51e700ba1298ea90cb4b83d05ef979708ec094d199a9dafa92959600fbeb97935028b101397284104c802288bb451a42514b1b65188ea0c3838ca242a9380c850c450884abfffb92c4ed03db1208fa07b123cab6c1a000941b4002e80eac5d92e58f07c5c8ca0e75c2030891222201a6b9714aec80c168aec080fd2f42b3c4a5cab042a849d39497585670d901e1209593678501f261489cd0404a11e5debae32b9889c393b241815a6252ca3426045b99922514b243a2a405940db7cf32f04d82ab1b46711b48e42bc2d8428db59a6a421462b38543709ae0b8683c4a221402c464f0158ac80f83d39b976667c8a69a88040d26489917361e28da2212713cdf008b07132eb5b4b0f052662481a6a529130ce5610452b285604e10117568045401bae12ab4d2212d66160cd08915239d0a169361480267bb208cd46a3f3473257eb9e34a8d794047c73724c3ba1f4bf489584e9691547341a724d740ea8afab2b0c28d948ea2e9c5a148228a67bb1cf2c87eb20ecdf676b005720e795a7ed4e5b1eda3355dc12725eecfb9e9904f5ca514b4d49969819e10c4ad4182d52007d266380690046268e7542848a911c855909e79186cc08d913964429231c16a0b22d9098b0c6ae2960844d45d19336caab2689846affc344a2b388ea5324416b1a9436684549a2264fffb92c4f003dd660af807b12d4aa8c0a000c498310812556495506044c341640a91da6da6f4c9261623960c122c9b454c11984710e164d0a38b926c5b49593881d9e4b8f54e69cd959268c58b0e22e698652659dd4d86d01128611cd19a20208285848a0845106cb231a69b787c928aa52a92b46869ecf30ab58d52caa4428e4246b452c196c8e34e61034b181e62726e4144261b2340dd326c864e40fa5820004f54a1207d056952462a71324e9a2b6d408c06ee12222a623368fd265d41584dcbc469ed4206dfcda77345a4fb8e82f56cb41e255cc95b9b09922aae902a9c750117080f0143a587ec4b73f4a3d389ab6399bc49793879222dbcee47360aff1d07ae8ecf2bc3e3ea9f19f57adf9e9d46d7bdef5cc9a8e75f9835c8db20fff7f95b3116cdda61e328afd27ddd76d3d3fe46e4bfffe2a6d7748247bdd19509d276608c2217c1006a21210b03f7571e95cfae82a2fac0f4b39a3f38576326225a2432be928c6b13ddb2756b6e80f24b295dcc99ac4cb4ecfce983a58f40ad618266c9e4c7122e6686066816671742c207af2b72a6d80fdc186866c7c46f38f327625afffb92c4eb801a760afa26192c028941a0a4649a68f04e89b524408488d022ac84ac2c80e1b45230808e071628e98648d0a0204d5506d1411215595c2c8cb449a135495003ed484c0412ad20d8a18643229214472733c7c0691960a20c449b8ba7244608c506e00ab265e4143cf668782eb26c1005d71e350647d03da36334892d2221d5510d992c4260e080560a06a26bb3202abc884a34a9d160e55b942152179710e1828997501a1e11213e0b929f6dc214de6cb9736cce47cc218964260a8a9247030348d35c22d1796a236e6f33893446d1a42a30dc137237124d6d28c1d5aca0798c6413d2f15a4bd28f9a4717122c9ba4b9c91b4e4d2f79b2223a5d2a445df4189144d161499d936816240b0d3cc615950986e4d97b64fa29becaa0424e40a0aa167561b81e0d453d3e0eb53d9b6be0f53c759504134978b48d684891a16e4e8a1b67d0c97d2024273ed0ece08d6741b28f45d8592464c44d195770592a100b4a801bd0405a62e33a718b7794ba5c3e44d94d7a9f8bdf5cb9852f60366d95a64fd93c4e944483446ca34019b68629632150c2e151b444457932a3174eeb2afffb92c4f703dbd20cf806312fc338419f40c4a5b89978a6471806e4b72019e4b23434bf28a1940b4552c898863cc35228764e78c280a84520470dc275d2eda5c666922ab0d0a3c9a27933d4c9fab5bb1d0284e977109c6222245df3d4392a97c2588041a778640b1fba50187ad080f722a1f8f28c665205c41d3acb7f6802256ae1d6cb3db879cb872fa952525b294810134c4d8e0b21285167da210a443646741a31a54e974cf30c305e4c4c89b7bc9d04c95d84670c9a7ac7adac9b0dae91491504498a2a2083584735d86545085a2ecd329a85cbb50405113c950bd4597227cd13e42bd0fa1d34ace687ba70838a8d904148a4b3cb92943fac8cc481772a4cd4c79942b2aa23713a2474aa72644e8dd8299232ce5e7ac4df4c902f643f594e7964870db1523e4994d4d665b9ae59a3260dca9c37474a922382c9d3cfa24d5644abc525595dcc94b9901d469b7242c9c44c5964322782d5040004c482a424e85a407c3ce610fc4e4ca9cdb7c88568af51b44d47c8bcd0b0e93629713a2ea3d4930489340c02924d0a3bc148227628d44680ac1eba6e82daf4b07b21034fb9393afffb92c4e70117760cfec624d3c323409f40c324903d9d6431df10830b59e3134ce410441a912d3939ba612e790b664b45598d2525d759b2c8099740e24a3a30a22eae13a84189f2356228a6cc410a29d4765240ae1c99aa923438ba134858d6e246daa622b9037b5143a4a889cea858a1e623247d9934daf58b129f48ca434f51a47246f3882b69882a9923d26a61a590195557963933f34281b5c97e44dfc5556a1a6e514eca5b0d4dd2989ad78a1f64a7531c7b4484481468685cf1210102101c801d2010b2a179e0d08592ebae60ba36cb34167e6dca66dd9c848c44c23a3ab203586a0191504d8132090e0b139e7930bf90669d7ccae5133478908803930b8b351095b41fc916c61dde620dd6ce532307b22a488421271716eb0d48274939fa5cbedbbf7b6a4d1d9974e20832e3123313d8205c6ad3c4b341d0445526414049220b0b3dacb04015951d5338b4711dd593cb05d743db2e2e174bf688948b9592ec85a74734a2f8cffd19ef4892b0e959c38987d897af191d97d41710c49510164016c28405900dbc81992336275308488640b10fa03817172cc2ec9b05909559fffb92c4eb0118520afca4993acaef40dfc8949a790228b55db20588cda64629223c889894943c48862446684c14b11e388e0a9b42b118502a6c4459a15a47f18881c70e489a83cce8d850686c2890f97205213a46f9e1f32c744a1f597731f4486a225f96e242d2755f3b982b022ba75341ae03b4a6476cfa126f29888a1334a0a898e5d6633c5eed4a6a839425c992d13ae3a4dd098c4fddd5eeb7858756943a054590288d942aaf85588a28a41f598f22e6226c130c692b294014621f0fb8977b5955cd40f2680498dedcdef2c65991031650c34fc465db54b2d0c5539c9f9d7c32f27574b54af034ab116e6ba2dc3e49a4c46a203554c2294f612841d76dbe135254e73d552d1a76dc90fc9cdbd9ae9cda4a0cde4975164aa52428f7b92f2a6a75593aad7e5be3a9b081d23e9aaa2d191a4e6ba8550b72a4a3737b128c4c7bada8cd1ad6936a1872087823a9413d6e4398a5058da88c26e65b381edc9aa24084ac20887ab1cc69645a6cd0733a10403cf55311248cecc4c49382ac901b79941c3d04548914acd28db92214681559bd8992f4fcd9b6d0aa26069136c50ac9482dfffb92c4f2031c420af606259b4ace415fc49325e071d0fa3312404c796676d034a2048a6ab42882364a2249a244467c63184d582690a4dace352509b145d53cf2d320658598b6ab2f519b20688e0f549d66d764d92ca63423589b569cbb555264eb6b7245a324e71446498909cd5a0550a04d95975156b5bb9b0c36b90983b6b0aa75e0d3d12b64ad93b59cdae12a0028395eaaa7cf97d234e2c3b3280fa1cea61fae5e60b0b486f9d16dd71fbbead862af5bd858560bca8bd18b850770a112911a11b78ed34388700da68c3c64671b2e5d636909900c87c98bb3024a44660b602a7dcb670ca5c9e8f693132a48dc9b775e4d912ef510afdbeca21af4d520dd431e8e69032c325455158dac85bf0b34a5202eab51a47dc9ac632564934eb6df7a2a8c6d06bc55b64bd1f56327916b2eb9ae520d0acae17968d6b6af7c48522eb324c8cd1944def8192035161cc291523b8215d1b09c55580460b2374c6494d1d1303a7a5d0192678ed8323cc6d2af42fae98597b4711b45248f9d18c11499ae7ba60412c7a62b32064254232281b99dc84f1806c568e6b10bc3c34308888522510dfffb92c4ed8318ce0cfa04992d0323c11f48c62578c974121c51ea51da5ae29212731ab21307db5fbe708b28cb463091c9952aaacfde7cafad18c48b983cda4ad1b04e0f4f6eb4c9a36451408c8d314a0e44829668ecc98c074e36de1378a8ac1612628bda3142d474c36c3f67898ca07c08521ee6cd9b79d694429613a912649522466524709209b346a059948e101d261e2ef14c0572d430b4b0108191616c0855a180c8256f026347d5d5d749b619ad42551b5ba3681f8c89df8b2dcc06d08222a88f8000c092641724117895be53b84ca45212a32866732b030a205289db9b5c890527a888dc31091bcd429b66dca3a49d13a8a459792b1314a4abe0ace5241701fb64cb76d88e32a550185d1a0275182d045af3d884d0a20f252c565140e74cbb2c983f34e0ba9c3c2466c8d0a24a440851149a25499a9913448d48c5722474d1c6d44d0244da9ce4c4326932d221b792cd95d684e2564dac54e3d54369452c9107c34c3402a226333361874e2d2c47a42cae1ced9a0c048f15598559228e382ec3819a9a87161092d53ae0070a0e1f231d2e2b6db4459195710393443a9032fffb92c4ec015a1a0cf824b12dc320415f58932461809d6366d359e70c196cf1b6475eba44eaa925d153d940935485848a8d3619225915d4527a74c32c13931697d426d72096b5ecda3ab159946866805c9e952d1e442929181b38f9235e0d1d60282b23928e4b30848e28532b173650d13c585d1aa467e71fa8894c21b108a6d02142b207d58bb6498c48fb6490b112b322e0b63533d9c36d4452a6b2651a8e65b9879b0998a1282a454127b2b2cfeab6424d6c640b7112ad1bfd257331e875e3b680e172b0217bca247e61f56251b3ed9640a76a2b2d45b94158104dc848694cec2298294b83b102666ea0a541ec408830600019fa129dcaf4cc4e57eb6ead9b9327545d22f66a06468fd92a0f812fa2eaea4b2976f9773cfa296b8424a767888242aa1ecb03329dd046b502e407bdcf8d3cec68aad56b4946d153f688c8d740d792c921fcee90c82505caea1002ad8e75c94a0f223ada33880e221068f405019419436d2e0417253d24d90cf09c32a06bd9e2a5d6d482be9e2e0d8981644daa30a93200d2dbd0a6abdf732f4c9c104880a14227aa296934644570daaa411ab81afffb92c4e60319a60af8049932caa5415fc4649a9969387619711710ae7d13664e3135e9821411743050d186c57281b14a89902709bd45e6ca29b5dd068844916d113a85cc158530a49620ef2c4454157d3674964db64b8f10b3438d34aa3de65192332344212250764874993a6870b1d267220f9c9a44c9c996291141f5d62e8183085113cdcca3388de6569cccbe9904eaad37248923a553e50de4669588bea6caf1276257cb4df7f439e986396b46125f34f284286cf324d128e5b4750969a64d86995c81813a53178101464a48c38a1c12180d4ba6aafa47d57a135d74289b3444267d92baa084d7f18ef5196c1b34aa46dad1851cf29eb9ac723fa07b325916e4eada4ac6d352bda4935c236eca730aefac65b158212f59def11c13f1249a0dac6c897c016b5507a74da08205661b02caa00a0b05f87de91a40d0bcdb2171a3282445d0069ea28aa01f30da88a64275b070cb671b12899c2a13990ab40536a0180f2750d35936cb31d87721c8254b4af2ac98c91d0d6ad2f39ea12fac1293882c128a4463844a4aa6e5680fe34eb92a1255a3b166635ac62129447704612386fffb90c4f1001a020af82499360a80342128649b215118d8ae9569ba68c86e168f0ccf4d5aab6ce1f191d2ecc4363d62c7161bca35e8261ef1256d56a4ab05b5a9a33d56f2d8757b712142587579c34e283844e921638a1cbe4b957bb992ab0894375887896df5e5b4fad67b69218a4f4e9050d5b6f225c60b2c94e0a911fba8c8cd24d84e1d3913ad1a58f432891b060a1a8215ca2bac928b2c272354b524659279121210b42041a74f4cd596820b91489438203fb0422cab26484c3d39418f44ad6448347ca4994b5344c90ae474427931b492444e64813e72d97490b088f28751a24db55443b059576fee11a0193294a5670969aaf99c7ca952ca45a9607709c415b75a7326fda8e02a3ea91574fbda2c263d75ccc41db276ac544aa1a79ab58d121f42d1f1d96ad8cbeb10d24d57387ab0f0c0f793a0c283493d328da5ab9e8102a609ce128fae3d1b4fd0dcf49a5954b365cd3860eca2932ada883e20082020c1c0c81f40d4e83038a38d7b621230e838d782d64481e8134084db0d3480fdac1f158ad75cc2a89edd325c138a159b42aa11bd4f9b5616a0814226d86da6c8fffb92c4ff001d1a0ef8c4bd9a434841dec094b2783565ee725a0737f65564aeaa61a294bced77b2a2489a516242154d4a0e35e2aa42445082f929215e417263fa454b3246b9c4f57da84e9811976e44cb6b28b93b026cb92d2da47a94565095cd2259149498a9815b09f8214d049c5f6d6930b2885574a04e99b933382ed162ac2ca45d344c2a40c2ca11b8a0915665246d345b166501252a02348650740c40eb7a1054e4493c0eaf89b1687471c5a73a0c0e44c86837a0f250ab4c8ac785cca00fa42924a109941a3c3a3048e4cb244e4260866fa6956d489707582230cb260aaa59259447a8159ac25941cb9288c8545dc28ef62927ad2725681cb4a5b548b3099b30b521528ca713caea6cb954e4af4440dda567ae53c562b2063123ab2f18b7d6ecd348a6e4e6bdedf63c5a3aa64613f525914d06e424b228d10529313395d562bc57685cd4d9486d65588252da4fae8a7b110b553292a8e20da94b280a844444b2043679e8d6687cf37a809b90246e981b04d0a174d678ba11724908cd99c3aa1825122c66cac56289130a09031358e9b452589a0f65049a1393ac426a2888fffb92c4e801188e0cfa231925c300419f9893253894117203444c93c82875832428d824227e59a6c4d4b17316a8d8908f486dca965d460a1c952643ccda30aeae91530aa2b2c244ce2e40589da82ab94600753dc34846cc176c5a6d28ca985a728197985a0809120daf13c813c47342a73eab898bc2689225b6d2e2a262389a82996b1330771092aabb48e6081e3536b6744ea198a3215575d724e69a4056a73212014c248043e02a0c4d09ba200f09088c9e3c48bba90c5254c286a715b4465880d24a41130a324b2246a283534dc65caa37c647d840b1f150830f5b46179e13141d52665942bc61a46d543d1ce60d4e91c1964aa17a24a64870aa247164b0fae2234865878cb0367d82440dc4f46d2c3893321534d26a2cf2595c9792cd1b15ba671e81e8495540c75902d36934d01430803aa1320c2259521344a4e88104110b08b3c240910cabf3618081d8e4a766164c04d47ae77230b0847312d0879080050376ce365556a0860274988e28b13b87a0bc1e4c28728a2cd2868c965de84eb47d02689bb25cef1288d15a341a465218c31650c9003729965469724d13ae99a8fffb92c4ec03da020af80489280b18401f00949b3954e5318c6a28a298581c527f1b48c32d48fd1428a89ececc91119412e8de50c1979430694d23450593a7156c9ce98936b9d6be36aa79a8ad097c249aad24b9fd8a6d21659818c6902f251a9216f6edb62659a7222a472414a4871ec9e4cd92ac4125d090227f2f3698996219414aca9a2895a3e75823776e290566bea232a1bb509b0b890310221d12a4d246e15a40188b5a49a0243c44baadaea50d22231e599ee2e12341a5dc263816486c909ee2c040d15848a9f651879087a7ae6de3fb33cf385d52792b0129a14132118446a099161c85848f16cd0f290a202d8235888b6b44a891167cd4b611cde3d06c9899538d457ba514345dfa8a7506e6ba3936eb815aa51556344ab87981b0b100e206c81ad81423ed5a47f18589e6928ba1963e28183459acd63068bb2c3488554a0eb50945d0286c9541a44a4b58191d699d36483c78d2359861b18c6020ca2482afc615402000809414fa47462372a12c0a9a8d62c831ba1dd04943e4f73a28c311756bd68aa38787356c12b1231247395866ed072e9fb11374e22cc6d8486bfffb92c4e78158a608faa4993e0b34c11f20632469dc6ec96c54b9dced94e496c7b40e74ce77417b88e2088c2ecbd22804503eea7865a682cd2f524933b1c81912c6b81a678875134a913a41a1147518ae8e90b4c793a07219075e220bac79d434e5518b33dd3eb9e7125f0750ec2133db90930836da1c56208188a17166232a07408a171052b13489c7c285b95a89904060f243e8c94468241b27a21263e47d4710099b48fcdb446fedcda1d3286623932176d33a985f5568e3922852062b4a964d0900a099040ee21282d3464e2c23260ca0f2205b114d2149480e908dab3175104f49c3ec9e4455882631cb710c1028da515b64940791b64726da59960d33642aa35903ce4c8562c913a14d88b12075724328176db5d01f30cb48cc2daf5db8411236184025d594243868a214450f90372272fde8d7171c282c4e952c425cd07cb9f3266ef05ee092ea848aa6da6b2221232424a558599a08000349c07945d62e444e282aa94e615490ac8099122590edae4ea9234c1b5cf23422032c94d81c0c4e082196614e3458b2d92987cb2af5c89b506524c634a45b4a9cc102889a265dfffb92c4e48155ba0efd0320d8034941dec09324688689aeb16a255e4595204481abe8953e1e58d91a055a2335ed29aca7548dc940d1981f6ad0ae42f14523c9aedcd72c40837c896d365141ca9c551a1162a61b65ac13b450946397277a6a42a4a1155c1646c9302c9aa7945174947100a8e4dc189d5acbae5528192204fbaa29b1036542726df24e783cf54d8a1cab9c2530a2c913eb3222858128a9449a292574604121574488c4174278cebfa8c3335901a6bb3327278344258ea35cbd328c2c29140d9927c59b6f03e8936e2586d8e5cea225367c462a3a48233c08954c196872894161789532069908212d359546513560456f5495e7a429765196b59c43a8110590cd12d4d3536526d94ae6dcc5071228a227a6a26b424ed2e5a6ba6d27cc214b9e44646e96795db28deaeda582713179408d19f67157674074d8b901c9ec651e61451945f5960e48f0bb240212018925f20298c585130b593927220aa37a010085284000012b2ec8955451dcba65c41272806048d6a4435094544bc10d28f48a3d674e9287a04a85dcdf40901f94eb1451b24f275f3588966311b81e34a3fffb92c4eb0019820cf8a499380337c15f14949b3913de453569f7cbcb4a1222898c636a8c2cd7c1f08c68bd04409945165be73c8e5004d92e149490b28c5845270e9a2c8519a04c4490d2524e457d28d2262ec1324e3c81aa1c432686c1b8c62051a959d564c9e12a72dcbe3be6134da4a51cb5c020712d56631d00501256936d6a301859d764aa6950155558b2a1650b2b6c7884330d890b884972a5718a442cd8a5468a92d2a29252a089959a56529b2b0a89954254112582215134c8516c62ab3d542b1122449b3044895cd2125890854008a854d759a4488542933e38b224c8454bb2b22265c11751d0cc1126292514ee5e22452f5d34294d0d5c108a4962850a14854850a1ad8ca78a8a496ae3d564a8227888991222204992125eaa1c59165c08812049ab428889b0a82229662a9122d550d113c8516a416048d1514b01504411148a4969600c1961149a4c95090c888030242a15132244ce554c414d45332e313030555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555fffb92c4e481d5f20afca318d10b4ec1dd4011a420555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555554c414d45332e313030555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555fffb92c43903c00001a4000000200000348000000455555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555` +} \ No newline at end of file diff --git a/pkg/notifier/notiifer.go b/pkg/notifier/notiifer.go new file mode 100644 index 0000000..c987f7d --- /dev/null +++ b/pkg/notifier/notiifer.go @@ -0,0 +1,68 @@ +package notifier + +import ( + "bytes" + "encoding/hex" + "io" + "io/ioutil" + "strings" + "time" + + "github.com/faiface/beep" + "github.com/faiface/beep/mp3" + "github.com/faiface/beep/speaker" + notifylib "github.com/gen2brain/beeep" +) + +// Notify ... +func Notify(title string, msg string) error { + return notifylib.Notify(title, msg, "") +} + +// NotifyWithSound ... +func NotifyWithSound(title string, msg string) error { + err := Notify(title, msg) + if err != nil { + return err + } + + err = PlaySound() + if err != nil { + return err + } + + return nil +} + +// PlaySound ... +func PlaySound() error { + f, err := mp3File() + if err != nil { + return err + } + streamer, format, err := mp3.Decode(f) + if err != nil { + return err + } + defer streamer.Close() + + speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10)) + done := make(chan bool) + speaker.Play(beep.Seq(streamer, beep.Callback(func() { + done <- true + }))) + + <-done + return nil +} + +func mp3File() (io.ReadCloser, error) { + r := strings.TrimRight(strings.TrimLeft(Mp3(), "\r\n"), "\r\n") + mp3Bytes, err := hex.DecodeString(r) + if err != nil { + return nil, err + } + + f := ioutil.NopCloser(bytes.NewReader(mp3Bytes)) + return f, nil +} diff --git a/vendor/github.com/faiface/beep/LICENSE b/vendor/github.com/faiface/beep/LICENSE new file mode 100644 index 0000000..b497e83 --- /dev/null +++ b/vendor/github.com/faiface/beep/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Michal Štrba + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/faiface/beep/README.md b/vendor/github.com/faiface/beep/README.md new file mode 100644 index 0000000..1624c22 --- /dev/null +++ b/vendor/github.com/faiface/beep/README.md @@ -0,0 +1,44 @@ +# Beep [![GoDoc](https://godoc.org/github.com/faiface/beep?status.svg)](https://godoc.org/github.com/faiface/beep) [![Go Report Card](https://goreportcard.com/badge/github.com/faiface/beep)](https://goreportcard.com/report/github.com/faiface/beep) + +A little package that brings sound to any Go application. Suitable for playback and audio-processing. + +``` +go get -u github.com/faiface/beep +``` + +## Features + +Beep is built on top of its [Streamer](https://godoc.org/github.com/faiface/beep#Streamer) interface, which is like [io.Reader](https://golang.org/pkg/io/#Reader), but for audio. It was one of the best design decisions I've ever made and it enabled all the rest of the features to naturally come together with not much code. + +- **Decode and play WAV, MP3, OGG, and FLAC.** +- **Encode and save WAV.** +- **Very simple API.** Limiting the support to stereo (two channel) audio made it possible to simplify the architecture and the API. +- **Rich library of compositors and effects.** Loop, pause/resume, change volume, mix, sequence, change playback speed, and more. +- **Easily create new effects.** With the `Streamer` interface, creating new effects is very easy. +- **Generate completely own artificial sounds.** Again, the `Streamer` interface enables easy sound generation. +- **Very small codebase.** The core is just ~1K LOC. + +## Tutorial + +The [Wiki](https://github.com/faiface/beep/wiki) contains a handful of tutorials for you to get started. They teach the fundamentals and advanced topics alike. **Read them especially if you call `speaker.Init` every time you play something.** + +- [Hello, Beep!](https://github.com/faiface/beep/wiki/Hello,-Beep!) +- [Composing and controlling](https://github.com/faiface/beep/wiki/Composing-and-controlling) +- [To buffer, or not to buffer, that is the question](https://github.com/faiface/beep/wiki/To-buffer,-or-not-to-buffer,-that-is-the-question) +- [Making own streamers](https://github.com/faiface/beep/wiki/Making-own-streamers) + +## Examples + +| [Speedy Player](https://github.com/faiface/beep/tree/master/examples/speedy-player) | [Doppler Stereo Room](https://github.com/faiface/beep/tree/master/examples/doppler-stereo-room) | +| --- | --- | +| ![Speedy Player](https://github.com/faiface/beep/blob/master/examples/speedy-player/screenshot.png) | ![Doppler Stereo Room](https://github.com/faiface/beep/blob/master/examples/doppler-stereo-room/screenshot.png) | + +## Dependencies + +For playback, Beep uses [Oto](https://github.com/hajimehoshi/oto) under the hood. Check its requirements to see what you need to install for building your application. + +Running an already built application should work with no extra dependencies. + +## Licence + +[MIT](https://github.com/faiface/beep/blob/master/LICENSE) diff --git a/vendor/github.com/faiface/beep/buffer.go b/vendor/github.com/faiface/beep/buffer.go new file mode 100644 index 0000000..c20b993 --- /dev/null +++ b/vendor/github.com/faiface/beep/buffer.go @@ -0,0 +1,262 @@ +package beep + +import ( + "fmt" + "math" + "time" +) + +// SampleRate is the number of samples per second. +type SampleRate int + +// D returns the duration of n samples. +func (sr SampleRate) D(n int) time.Duration { + return time.Second * time.Duration(n) / time.Duration(sr) +} + +// N returns the number of samples that last for d duration. +func (sr SampleRate) N(d time.Duration) int { + return int(d * time.Duration(sr) / time.Second) +} + +// Format is the format of a Buffer or another audio source. +type Format struct { + // SampleRate is the number of samples per second. + SampleRate SampleRate + + // NumChannels is the number of channels. The value of 1 is mono, the value of 2 is stereo. + // The samples should always be interleaved. + NumChannels int + + // Precision is the number of bytes used to encode a single sample. Only values up to 6 work + // well, higher values loose precision due to floating point numbers. + Precision int +} + +// Width returns the number of bytes per one frame (samples in all channels). +// +// This is equal to f.NumChannels * f.Precision. +func (f Format) Width() int { + return f.NumChannels * f.Precision +} + +// EncodeSigned encodes a single sample in f.Width() bytes to p in signed format. +func (f Format) EncodeSigned(p []byte, sample [2]float64) (n int) { + return f.encode(true, p, sample) +} + +// EncodeUnsigned encodes a single sample in f.Width() bytes to p in unsigned format. +func (f Format) EncodeUnsigned(p []byte, sample [2]float64) (n int) { + return f.encode(false, p, sample) +} + +// DecodeSigned decodes a single sample encoded in f.Width() bytes from p in signed format. +func (f Format) DecodeSigned(p []byte) (sample [2]float64, n int) { + return f.decode(true, p) +} + +// DecodeUnsigned decodes a single sample encoded in f.Width() bytes from p in unsigned format. +func (f Format) DecodeUnsigned(p []byte) (sample [2]float64, n int) { + return f.decode(false, p) +} + +func (f Format) encode(signed bool, p []byte, sample [2]float64) (n int) { + switch { + case f.NumChannels == 1: + x := norm((sample[0] + sample[1]) / 2) + p = p[encodeFloat(signed, f.Precision, p, x):] + case f.NumChannels >= 2: + for c := range sample { + x := norm(sample[c]) + p = p[encodeFloat(signed, f.Precision, p, x):] + } + for c := len(sample); c < f.NumChannels; c++ { + p = p[encodeFloat(signed, f.Precision, p, 0):] + } + default: + panic(fmt.Errorf("format: encode: invalid number of channels: %d", f.NumChannels)) + } + return f.Width() +} + +func (f Format) decode(signed bool, p []byte) (sample [2]float64, n int) { + switch { + case f.NumChannels == 1: + x, _ := decodeFloat(signed, f.Precision, p) + return [2]float64{x, x}, f.Width() + case f.NumChannels >= 2: + for c := range sample { + x, n := decodeFloat(signed, f.Precision, p) + sample[c] = x + p = p[n:] + } + for c := len(sample); c < f.NumChannels; c++ { + _, n := decodeFloat(signed, f.Precision, p) + p = p[n:] + } + return sample, f.Width() + default: + panic(fmt.Errorf("format: decode: invalid number of channels: %d", f.NumChannels)) + } +} + +func encodeFloat(signed bool, precision int, p []byte, x float64) (n int) { + var xUint64 uint64 + if signed { + xUint64 = floatToSigned(precision, x) + } else { + xUint64 = floatToUnsigned(precision, x) + } + for i := 0; i < precision; i++ { + p[i] = byte(xUint64) + xUint64 >>= 8 + } + return precision +} + +func decodeFloat(signed bool, precision int, p []byte) (x float64, n int) { + var xUint64 uint64 + for i := precision - 1; i >= 0; i-- { + xUint64 <<= 8 + xUint64 += uint64(p[i]) + } + if signed { + return signedToFloat(precision, xUint64), precision + } + return unsignedToFloat(precision, xUint64), precision +} + +func floatToSigned(precision int, x float64) uint64 { + if x < 0 { + compl := uint64(-x * (math.Exp2(float64(precision)*8-1) - 1)) + return uint64(1<= 1< +1 { + return +1 + } + return x +} + +// Buffer is a storage for audio data. You can think of it as a bytes.Buffer for audio samples. +type Buffer struct { + f Format + data []byte + tmp []byte +} + +// NewBuffer creates a new empty Buffer which stores samples in the provided format. +func NewBuffer(f Format) *Buffer { + return &Buffer{f: f, tmp: make([]byte, f.Width())} +} + +// Format returns the format of the Buffer. +func (b *Buffer) Format() Format { + return b.f +} + +// Len returns the number of samples currently in the Buffer. +func (b *Buffer) Len() int { + return len(b.data) / b.f.Width() +} + +// Pop removes n samples from the beginning of the Buffer. +// +// Existing Streamers are not affected. +func (b *Buffer) Pop(n int) { + b.data = b.data[n*b.f.Width():] +} + +// Append adds all audio data from the given Streamer to the end of the Buffer. +// +// The Streamer will be drained when this method finishes. +func (b *Buffer) Append(s Streamer) { + var samples [512][2]float64 + for { + n, ok := s.Stream(samples[:]) + if !ok { + break + } + for _, sample := range samples[:n] { + b.f.EncodeSigned(b.tmp, sample) + b.data = append(b.data, b.tmp...) + } + } +} + +// Streamer returns a StreamSeeker which streams samples in the given interval (including from, +// excluding to). If from<0 or to>b.Len() or to= len(bs.data) { + return 0, false + } + for i := range samples { + if bs.pos >= len(bs.data) { + break + } + sample, advance := bs.f.DecodeSigned(bs.data[bs.pos:]) + samples[i] = sample + bs.pos += advance + n++ + } + return n, true +} + +func (bs *bufferStreamer) Err() error { + return nil +} + +func (bs *bufferStreamer) Len() int { + return len(bs.data) / bs.f.Width() +} + +func (bs *bufferStreamer) Position() int { + return bs.pos / bs.f.Width() +} + +func (bs *bufferStreamer) Seek(p int) error { + if p < 0 || bs.Len() < p { + return fmt.Errorf("buffer: seek position %v out of range [%v, %v]", p, 0, bs.Len()) + } + bs.pos = p * bs.f.Width() + return nil +} diff --git a/vendor/github.com/faiface/beep/compositors.go b/vendor/github.com/faiface/beep/compositors.go new file mode 100644 index 0000000..d874d9f --- /dev/null +++ b/vendor/github.com/faiface/beep/compositors.go @@ -0,0 +1,173 @@ +package beep + +// Take returns a Streamer which streams at most num samples from s. +// +// The returned Streamer propagates s's errors through Err. +func Take(num int, s Streamer) Streamer { + return &take{ + s: s, + remains: num, + } +} + +type take struct { + s Streamer + remains int +} + +func (t *take) Stream(samples [][2]float64) (n int, ok bool) { + if t.remains <= 0 { + return 0, false + } + toStream := t.remains + if len(samples) < toStream { + toStream = len(samples) + } + n, ok = t.s.Stream(samples[:toStream]) + t.remains -= n + return n, ok +} + +func (t *take) Err() error { + return t.s.Err() +} + +// Loop takes a StreamSeeker and plays it count times. If count is negative, s is looped infinitely. +// +// The returned Streamer propagates s's errors. +func Loop(count int, s StreamSeeker) Streamer { + return &loop{ + s: s, + remains: count, + } +} + +type loop struct { + s StreamSeeker + remains int +} + +func (l *loop) Stream(samples [][2]float64) (n int, ok bool) { + if l.remains == 0 || l.s.Err() != nil { + return 0, false + } + for len(samples) > 0 { + sn, sok := l.s.Stream(samples) + if !sok { + if l.remains > 0 { + l.remains-- + } + if l.remains == 0 { + break + } + err := l.s.Seek(0) + if err != nil { + return n, true + } + continue + } + samples = samples[sn:] + n += sn + } + return n, true +} + +func (l *loop) Err() error { + return l.s.Err() +} + +// Seq takes zero or more Streamers and returns a Streamer which streams them one by one without pauses. +// +// Seq does not propagate errors from the Streamers. +func Seq(s ...Streamer) Streamer { + i := 0 + return StreamerFunc(func(samples [][2]float64) (n int, ok bool) { + for i < len(s) && len(samples) > 0 { + sn, sok := s[i].Stream(samples) + samples = samples[sn:] + n, ok = n+sn, ok || sok + if !sok { + i++ + } + } + return n, ok + }) +} + +// Mix takes zero or more Streamers and returns a Streamer which streams them mixed together. +// +// Mix does not propagate errors from the Streamers. +func Mix(s ...Streamer) Streamer { + return StreamerFunc(func(samples [][2]float64) (n int, ok bool) { + var tmp [512][2]float64 + + for len(samples) > 0 { + toStream := len(tmp) + if toStream > len(samples) { + toStream = len(samples) + } + + // clear the samples + for i := range samples[:toStream] { + samples[i] = [2]float64{} + } + + snMax := 0 // max number of streamed samples in this iteration + for _, st := range s { + // mix the stream + sn, sok := st.Stream(tmp[:toStream]) + if sn > snMax { + snMax = sn + } + ok = ok || sok + + for i := range tmp[:sn] { + samples[i][0] += tmp[i][0] + samples[i][1] += tmp[i][1] + } + } + + n += snMax + if snMax < len(tmp) { + break + } + samples = samples[snMax:] + } + + return n, ok + }) +} + +// Dup returns two Streamers which both stream the same data as the original s. The two Streamers +// can't be used concurrently without synchronization. +func Dup(s Streamer) (t, u Streamer) { + var tBuf, uBuf [][2]float64 + return &dup{&tBuf, &uBuf, s}, &dup{&uBuf, &tBuf, s} +} + +type dup struct { + myBuf, itsBuf *[][2]float64 + s Streamer +} + +func (d *dup) Stream(samples [][2]float64) (n int, ok bool) { + buf := *d.myBuf + n = copy(samples, buf) + ok = len(buf) > 0 + buf = buf[n:] + samples = samples[n:] + *d.myBuf = buf + + if len(samples) > 0 { + sn, sok := d.s.Stream(samples) + n += sn + ok = ok || sok + *d.itsBuf = append(*d.itsBuf, samples[:sn]...) + } + + return n, ok +} + +func (d *dup) Err() error { + return d.s.Err() +} diff --git a/vendor/github.com/faiface/beep/ctrl.go b/vendor/github.com/faiface/beep/ctrl.go new file mode 100644 index 0000000..88fd8bc --- /dev/null +++ b/vendor/github.com/faiface/beep/ctrl.go @@ -0,0 +1,52 @@ +package beep + +// Ctrl allows for pausing a Streamer. +// +// Wrap a Streamer in a Ctrl. +// +// ctrl := &beep.Ctrl{Streamer: s} +// +// Then, we can pause the streaming (this will cause Ctrl to stream silence). +// +// ctrl.Paused = true +// +// To completely stop a Ctrl before the wrapped Streamer is drained, just set the wrapped Streamer +// to nil. +// +// ctrl.Streamer = nil +// +// If you're playing a Streamer wrapped in a Ctrl through the speaker, you need to lock and unlock +// the speaker when modifying the Ctrl to avoid race conditions. +// +// speaker.Play(ctrl) +// // ... +// speaker.Lock() +// ctrl.Paused = true +// speaker.Unlock() +type Ctrl struct { + Streamer Streamer + Paused bool +} + +// Stream streams the wrapped Streamer, if not nil. If the Streamer is nil, Ctrl acts as drained. +// When paused, Ctrl streams silence. +func (c *Ctrl) Stream(samples [][2]float64) (n int, ok bool) { + if c.Streamer == nil { + return 0, false + } + if c.Paused { + for i := range samples { + samples[i] = [2]float64{} + } + return len(samples), true + } + return c.Streamer.Stream(samples) +} + +// Err returns the error of the wrapped Streamer, if not nil. +func (c *Ctrl) Err() error { + if c.Streamer == nil { + return nil + } + return c.Streamer.Err() +} diff --git a/vendor/github.com/faiface/beep/go.mod b/vendor/github.com/faiface/beep/go.mod new file mode 100644 index 0000000..3612300 --- /dev/null +++ b/vendor/github.com/faiface/beep/go.mod @@ -0,0 +1,11 @@ +module github.com/faiface/beep + +require ( + github.com/gdamore/tcell v1.1.1 + github.com/hajimehoshi/go-mp3 v0.1.1 + github.com/hajimehoshi/oto v0.3.1 + github.com/jfreymuth/oggvorbis v1.0.0 + github.com/jfreymuth/vorbis v1.0.0 // indirect + github.com/mewkiz/flac v1.0.5 + github.com/pkg/errors v0.8.1 +) diff --git a/vendor/github.com/faiface/beep/go.sum b/vendor/github.com/faiface/beep/go.sum new file mode 100644 index 0000000..d9b3a61 --- /dev/null +++ b/vendor/github.com/faiface/beep/go.sum @@ -0,0 +1,39 @@ +github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell v1.1.1 h1:U73YL+jMem2XfhvaIUfPO6MpJawaG92B2funXVb9qLs= +github.com/gdamore/tcell v1.1.1/go.mod h1:K1udHkiR3cOtlpKG5tZPD5XxrF7v2y7lDq7Whcj+xkQ= +github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw= +github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherwasm v0.1.1/go.mod h1:kx4n9a+MzHH0BJJhvlsQ65hqLFXDO/m256AsaDPQ+/4= +github.com/gopherjs/gopherwasm v1.0.0 h1:32nge/RlujS1Im4HNCJPp0NbBOAeBXFuT1KonUuLl+Y= +github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI= +github.com/hajimehoshi/go-mp3 v0.1.1 h1:Y33fAdTma70fkrxnc9u50Uq0lV6eZ+bkAlssdMmCwUc= +github.com/hajimehoshi/go-mp3 v0.1.1/go.mod h1:4i+c5pDNKDrxl1iu9iG90/+fhP37lio6gNhjCx9WBJw= +github.com/hajimehoshi/oto v0.1.1/go.mod h1:hUiLWeBQnbDu4pZsAhOnGqMI1ZGibS6e2qhQdfpwz04= +github.com/hajimehoshi/oto v0.3.1 h1:cpf/uIv4Q0oc5uf9loQn7PIehv+mZerh+0KKma6gzMk= +github.com/hajimehoshi/oto v0.3.1/go.mod h1:e9eTLBB9iZto045HLbzfHJIc+jP3xaKrjZTghvb6fdM= +github.com/jfreymuth/oggvorbis v1.0.0 h1:aOpiihGrFLXpsh2osOlEvTcg5/aluzGQeC7m3uYWOZ0= +github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM= +github.com/jfreymuth/vorbis v1.0.0 h1:SmDf783s82lIjGZi8EGUUaS7YxPHgRj4ZXW/h7rUi7U= +github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0= +github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08 h1:5MnxBC15uMxFv5FY/J/8vzyaBiArCOkMdFT9Jsw78iY= +github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4= +github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mewkiz/flac v1.0.5 h1:dHGW/2kf+/KZ2GGqSVayNEhL9pluKn/rr/h/QqD9Ogc= +github.com/mewkiz/flac v1.0.5/go.mod h1:EHZNU32dMF6alpurYyKHDLYpW1lYpBZ5WrXi/VuNIGs= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd h1:nLIcFw7GiqKXUS7HiChg6OAYWgASB2H97dZKd1GhDSs= +golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/mobile v0.0.0-20180806140643-507816974b79 h1:t2JRgCWkY7Qaa1J2jal+wqC9OjbyHCHwIA9rVlRUSMo= +golang.org/x/mobile v0.0.0-20180806140643-507816974b79/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb h1:pf3XwC90UUdNPYWZdFjhGBE7DUFuK3Ct1zWmZ65QN30= +golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY= +gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw= diff --git a/vendor/github.com/faiface/beep/interface.go b/vendor/github.com/faiface/beep/interface.go new file mode 100644 index 0000000..8e90919 --- /dev/null +++ b/vendor/github.com/faiface/beep/interface.go @@ -0,0 +1,106 @@ +package beep + +// Streamer is able to stream a finite or infinite sequence of audio samples. +type Streamer interface { + // Stream copies at most len(samples) next audio samples to the samples slice. + // + // The sample rate of the samples is unspecified in general, but should be specified for + // each concrete Streamer. + // + // The value at samples[i][0] is the value of the left channel of the i-th sample. + // Similarly, samples[i][1] is the value of the right channel of the i-th sample. + // + // Stream returns the number of streamed samples. If the Streamer is drained and no more + // samples will be produced, it returns 0 and false. Stream must not touch any samples + // outside samples[:n]. + // + // There are 3 valid return pattterns of the Stream method: + // + // 1. n == len(samples) && ok + // + // Stream streamed all of the requested samples. Cases 1, 2 and 3 may occur in the following + // calls. + // + // 2. 0 < n && n < len(samples) && ok + // + // Stream streamed n samples and drained the Streamer. Only case 3 may occur in the + // following calls. + // + // 3. n == 0 && !ok + // + // The Streamer is drained and no more samples will come. If Err returns a non-nil error, only + // this case is valid. Only this case may occur in the following calls. + Stream(samples [][2]float64) (n int, ok bool) + + // Err returns an error which occurred during streaming. If no error occurred, nil is + // returned. + // + // When an error occurs, Streamer must become drained and Stream must return 0, false + // forever. + // + // The reason why Stream doesn't return an error is that it dramatically simplifies + // programming with Streamer. It's not very important to catch the error right when it + // happens. + Err() error +} + +// StreamSeeker is a finite duration Streamer which supports seeking to an arbitrary position. +type StreamSeeker interface { + Streamer + + // Duration returns the total number of samples of the Streamer. + Len() int + + // Position returns the current position of the Streamer. This value is between 0 and the + // total length. + Position() int + + // Seek sets the position of the Streamer to the provided value. + // + // If an error occurs during seeking, the position remains unchanged. This error will not be + // returned through the Streamer's Err method. + Seek(p int) error +} + +// StreamCloser is a Streamer streaming from a resource which needs to be released, such as a file +// or a network connection. +type StreamCloser interface { + Streamer + + // Close closes the Streamer and releases it's resources. Streamer will no longer stream any + // samples. + Close() error +} + +// StreamSeekCloser is a union of StreamSeeker and StreamCloser. +type StreamSeekCloser interface { + Streamer + Len() int + Position() int + Seek(p int) error + Close() error +} + +// StreamerFunc is a Streamer created by simply wrapping a streaming function (usually a closure, +// which encloses a time tracking variable). This sometimes simplifies creating new streamers. +// +// Example: +// +// noise := StreamerFunc(func(samples [][2]float64) (n int, ok bool) { +// for i := range samples { +// samples[i][0] = rand.Float64()*2 - 1 +// samples[i][1] = rand.Float64()*2 - 1 +// } +// return len(samples), true +// }) +type StreamerFunc func(samples [][2]float64) (n int, ok bool) + +// Stream calls the wrapped streaming function. +func (sf StreamerFunc) Stream(samples [][2]float64) (n int, ok bool) { + return sf(samples) +} + +// Err always returns nil. +func (sf StreamerFunc) Err() error { + return nil +} diff --git a/vendor/github.com/faiface/beep/mixer.go b/vendor/github.com/faiface/beep/mixer.go new file mode 100644 index 0000000..9f0dd19 --- /dev/null +++ b/vendor/github.com/faiface/beep/mixer.go @@ -0,0 +1,70 @@ +package beep + +// Mixer allows for dynamic mixing of arbitrary number of Streamers. Mixer automatically removes +// drained Streamers. Mixer's stream never drains, when empty, Mixer streams silence. +type Mixer struct { + streamers []Streamer +} + +// Len returns the number of Streamers currently playing in the Mixer. +func (m *Mixer) Len() int { + return len(m.streamers) +} + +// Add adds Streamers to the Mixer. +func (m *Mixer) Add(s ...Streamer) { + m.streamers = append(m.streamers, s...) +} + +// Clear removes all Streamers from the mixer. +func (m *Mixer) Clear() { + m.streamers = m.streamers[:0] +} + +// Stream streams all Streamers currently in the Mixer mixed together. This method always returns +// len(samples), true. If there are no Streamers available, this methods streams silence. +func (m *Mixer) Stream(samples [][2]float64) (n int, ok bool) { + var tmp [512][2]float64 + + for len(samples) > 0 { + toStream := len(tmp) + if toStream > len(samples) { + toStream = len(samples) + } + + // clear the samples + for i := range samples[:toStream] { + samples[i] = [2]float64{} + } + + for si := 0; si < len(m.streamers); si++ { + // mix the stream + sn, sok := m.streamers[si].Stream(tmp[:toStream]) + for i := range tmp[:sn] { + samples[i][0] += tmp[i][0] + samples[i][1] += tmp[i][1] + } + if !sok { + // remove drained streamer + sj := len(m.streamers) - 1 + m.streamers[si], m.streamers[sj] = m.streamers[sj], m.streamers[si] + m.streamers = m.streamers[:sj] + si-- + } + } + + samples = samples[toStream:] + n += toStream + } + + return n, true +} + +// Err always returns nil for Mixer. +// +// There are two reasons. The first one is that erroring Streamers are immediately drained and +// removed from the Mixer. The second one is that one Streamer shouldn't break the whole Mixer and +// you should handle the errors right where they can happen. +func (m *Mixer) Err() error { + return nil +} diff --git a/vendor/github.com/faiface/beep/mp3/decode.go b/vendor/github.com/faiface/beep/mp3/decode.go new file mode 100644 index 0000000..4ddcf8f --- /dev/null +++ b/vendor/github.com/faiface/beep/mp3/decode.go @@ -0,0 +1,104 @@ +// Package mp3 implements audio data decoding in MP3 format. +package mp3 + +import ( + "fmt" + "io" + + "github.com/faiface/beep" + gomp3 "github.com/hajimehoshi/go-mp3" + "github.com/pkg/errors" +) + +const ( + gomp3NumChannels = 2 + gomp3Precision = 2 + gomp3BytesPerFrame = gomp3NumChannels * gomp3Precision +) + +// Decode takes a ReadCloser containing audio data in MP3 format and returns a StreamSeekCloser, +// which streams that audio. The Seek method will panic if rc is not io.Seeker. +// +// Do not close the supplied ReadSeekCloser, instead, use the Close method of the returned +// StreamSeekCloser when you want to release the resources. +func Decode(rc io.ReadCloser) (s beep.StreamSeekCloser, format beep.Format, err error) { + defer func() { + if err != nil { + err = errors.Wrap(err, "mp3") + } + }() + d, err := gomp3.NewDecoder(rc) + if err != nil { + return nil, beep.Format{}, err + } + format = beep.Format{ + SampleRate: beep.SampleRate(d.SampleRate()), + NumChannels: gomp3NumChannels, + Precision: gomp3Precision, + } + return &decoder{rc, d, format, 0, nil}, format, nil +} + +type decoder struct { + closer io.Closer + d *gomp3.Decoder + f beep.Format + pos int + err error +} + +func (d *decoder) Stream(samples [][2]float64) (n int, ok bool) { + if d.err != nil { + return 0, false + } + var tmp [gomp3BytesPerFrame]byte + for i := range samples { + dn, err := d.d.Read(tmp[:]) + if dn == len(tmp) { + samples[i], _ = d.f.DecodeSigned(tmp[:]) + d.pos += dn + n++ + ok = true + } + if err == io.EOF { + break + } + if err != nil { + d.err = errors.Wrap(err, "mp3") + break + } + } + return n, ok +} + +func (d *decoder) Err() error { + return d.err +} + +func (d *decoder) Len() int { + return int(d.d.Length()) / gomp3BytesPerFrame +} + +func (d *decoder) Position() int { + return d.pos / gomp3BytesPerFrame +} + +func (d *decoder) Seek(p int) error { + if p < 0 || d.Len() < p { + return fmt.Errorf("mp3: seek position %v out of range [%v, %v]", p, 0, d.Len()) + } + _, err := d.d.Seek(int64(p)*gomp3BytesPerFrame, io.SeekStart) + if err != nil { + return errors.Wrap(err, "mp3") + } + d.pos = p * gomp3BytesPerFrame + return nil +} + +func (d *decoder) Close() error { + err := d.closer.Close() + if err != nil { + return errors.Wrap(err, "mp3") + } + return nil +} diff --git a/vendor/github.com/faiface/beep/resample.go b/vendor/github.com/faiface/beep/resample.go new file mode 100644 index 0000000..902a123 --- /dev/null +++ b/vendor/github.com/faiface/beep/resample.go @@ -0,0 +1,174 @@ +package beep + +import "fmt" + +// Resample takes a Streamer which is assumed to stream at the old sample rate and returns a +// Streamer, which streams the data from the original Streamer resampled to the new sample rate. +// +// This is, for example, useful when mixing multiple Streamer with different sample rates, either +// through a beep.Mixer, or through a speaker. Speaker has a constant sample rate. Thus, playing +// Streamer which stream at a different sample rate will lead to a changed speed and pitch of the +// playback. +// +// sr := beep.SampleRate(48000) +// speaker.Init(sr, sr.N(time.Second/2)) +// speaker.Play(beep.Resample(3, format.SampleRate, sr, s)) +// +// In the example, the original sample rate of the source if format.SampleRate. We want to play it +// at the speaker's native sample rate and thus we need to resample. +// +// The quality argument specifies the quality of the resampling process. Higher quality implies +// worse performance. Values below 1 or above 64 are invalid and Resample will panic. Here's a table +// for deciding which quality to pick. +// +// quality | use case +// --------|--------- +// 1 | very high performance, on-the-fly resampling, low quality +// 3-4 | good performance, on-the-fly resampling, good quality +// 6 | higher CPU usage, usually not suitable for on-the-fly resampling, very good quality +// >6 | even higher CPU usage, for offline resampling, very good quality +// +// Sane quality values are usually below 16. Higher values will consume too much CPU, giving +// negligible quality improvements. +// +// Resample propagates errors from s. +func Resample(quality int, old, new SampleRate, s Streamer) *Resampler { + return ResampleRatio(quality, float64(old)/float64(new), s) +} + +// ResampleRatio is same as Resample, except it takes the ratio of the old and the new sample rate, +// specifically, the old sample rate divided by the new sample rate. Aside from correcting the +// sample rate, this can be used to change the speed of the audio. For example, resampling at the +// ratio of 2 and playing at the original sample rate will cause doubled speed in playback. +func ResampleRatio(quality int, ratio float64, s Streamer) *Resampler { + if quality < 1 || 64 < quality { + panic(fmt.Errorf("resample: invalid quality: %d", quality)) + } + return &Resampler{ + s: s, + ratio: ratio, + first: true, + buf1: make([][2]float64, 512), + buf2: make([][2]float64, 512), + pts: make([]point, quality*2), + off: 0, + pos: 0, + } +} + +// Resampler is a Streamer created by Resample and ResampleRatio functions. It allows dynamic +// changing of the resampling ratio, which can be useful for dynamically changing the speed of +// streaming. +type Resampler struct { + s Streamer // the orignal streamer + ratio float64 // old sample rate / new sample rate + first bool // true when Stream was not called before + buf1, buf2 [][2]float64 // buf1 contains previous buf2, new data goes into buf2, buf1 is because interpolation might require old samples + pts []point // pts is for points used for interpolation + off int // off is the position of the start of buf2 in the original data + pos int // pos is the current position in the resampled data +} + +// Stream streams the original audio resampled according to the current ratio. +func (r *Resampler) Stream(samples [][2]float64) (n int, ok bool) { + // if it's the first time, we need to fill buf2 with initial data, buf1 remains zeroed + if r.first { + sn, _ := r.s.Stream(r.buf2) + r.buf2 = r.buf2[:sn] + r.first = false + } + // we start resampling, sample by sample + for len(samples) > 0 { + again: + for c := range samples[0] { + // calculate the current position in the original data + j := float64(r.pos) * r.ratio + + // find quality*2 closest samples to j and translate them to points for interpolation + for pi := range r.pts { + // calculate the index of one of the closest samples + k := int(j) + pi - len(r.pts)/2 + 1 + + var y float64 + switch { + // the sample is in buf1 + case k < r.off: + y = r.buf1[len(r.buf1)+k-r.off][c] + // the sample is in buf2 + case k < r.off+len(r.buf2): + y = r.buf2[k-r.off][c] + // the sample is beyond buf2, so we need to load new data + case k >= r.off+len(r.buf2): + // we load into buf1 + sn, _ := r.s.Stream(r.buf1) + // this condition happens when the original Streamer got + // drained and j is after the end of the + // original data + if int(j) >= r.off+len(r.buf2)+sn { + return n, n > 0 + } + // this condition happens when the original Streamer got + // drained and this one of the closest samples is after the + // end of the original data + if k >= r.off+len(r.buf2)+sn { + y = 0 + break + } + // otherwise everything is fine, we swap buffers and start + // calculating the sample again + r.off += len(r.buf2) + r.buf1 = r.buf1[:sn] + r.buf1, r.buf2 = r.buf2, r.buf1 + goto again + } + + r.pts[pi] = point{float64(k), y} + } + + // calculate the resampled sample using polynomial interpolation from the + // quality*2 closest samples + samples[0][c] = lagrange(r.pts, j) + } + samples = samples[1:] + n++ + r.pos++ + } + return n, true +} + +// Err propagates the original Streamer's errors. +func (r *Resampler) Err() error { + return r.s.Err() +} + +// Ratio returns the current resampling ratio. +func (r *Resampler) Ratio() float64 { + return r.ratio +} + +// SetRatio sets the resampling ratio. This does not cause any glitches in the stream. +func (r *Resampler) SetRatio(ratio float64) { + r.pos = int(float64(r.pos) * r.ratio / ratio) + r.ratio = ratio +} + +// lagrange calculates the value at x of a polynomial of order len(pts)+1 which goes through all +// points in pts +func lagrange(pts []point, x float64) (y float64) { + y = 0.0 + for j := range pts { + l := 1.0 + for m := range pts { + if j == m { + continue + } + l *= (x - pts[m].X) / (pts[j].X - pts[m].X) + } + y += pts[j].Y * l + } + return y +} + +type point struct { + X, Y float64 +} diff --git a/vendor/github.com/faiface/beep/speaker/speaker.go b/vendor/github.com/faiface/beep/speaker/speaker.go new file mode 100644 index 0000000..d59389f --- /dev/null +++ b/vendor/github.com/faiface/beep/speaker/speaker.go @@ -0,0 +1,130 @@ +// Package speaker implements playback of beep.Streamer values through physical speakers. +package speaker + +import ( + "sync" + + "github.com/faiface/beep" + "github.com/hajimehoshi/oto" + "github.com/pkg/errors" +) + +var ( + mu sync.Mutex + mixer beep.Mixer + samples [][2]float64 + buf []byte + context *oto.Context + player *oto.Player + done chan struct{} +) + +// Init initializes audio playback through speaker. Must be called before using this package. +// +// The bufferSize argument specifies the number of samples of the speaker's buffer. Bigger +// bufferSize means lower CPU usage and more reliable playback. Lower bufferSize means better +// responsiveness and less delay. +func Init(sampleRate beep.SampleRate, bufferSize int) error { + mu.Lock() + defer mu.Unlock() + + Close() + + mixer = beep.Mixer{} + + numBytes := bufferSize * 4 + samples = make([][2]float64, bufferSize) + buf = make([]byte, numBytes) + + var err error + context, err = oto.NewContext(int(sampleRate), 2, 2, numBytes) + if err != nil { + return errors.Wrap(err, "failed to initialize speaker") + } + player = context.NewPlayer() + + done = make(chan struct{}) + + go func() { + for { + select { + default: + update() + case <-done: + return + } + } + }() + + return nil +} + +// Close closes the playback and the driver. In most cases, there is certainly no need to call Close +// even when the program doesn't play anymore, because in properly set systems, the default mixer +// handles multiple concurrent processes. It's only when the default device is not a virtual but hardware +// device, that you'll probably want to manually manage the device from your application. +func Close() { + if player != nil { + if done != nil { + done <- struct{}{} + done = nil + } + player.Close() + context.Close() + player = nil + } +} + +// Lock locks the speaker. While locked, speaker won't pull new data from the playing Stramers. Lock +// if you want to modify any currently playing Streamers to avoid race conditions. +// +// Always lock speaker for as little time as possible, to avoid playback glitches. +func Lock() { + mu.Lock() +} + +// Unlock unlocks the speaker. Call after modifying any currently playing Streamer. +func Unlock() { + mu.Unlock() +} + +// Play starts playing all provided Streamers through the speaker. +func Play(s ...beep.Streamer) { + mu.Lock() + mixer.Add(s...) + mu.Unlock() +} + +// Clear removes all currently playing Streamers from the speaker. +func Clear() { + mu.Lock() + mixer.Clear() + mu.Unlock() +} + +// update pulls new data from the playing Streamers and sends it to the speaker. Blocks until the +// data is sent and started playing. +func update() { + mu.Lock() + mixer.Stream(samples) + mu.Unlock() + + for i := range samples { + for c := range samples[i] { + val := samples[i][c] + if val < -1 { + val = -1 + } + if val > +1 { + val = +1 + } + valInt16 := int16(val * (1<<15 - 1)) + low := byte(valInt16) + high := byte(valInt16 >> 8) + buf[i*4+c*2+0] = low + buf[i*4+c*2+1] = high + } + } + + player.Write(buf) +} diff --git a/vendor/github.com/faiface/beep/streamers.go b/vendor/github.com/faiface/beep/streamers.go new file mode 100644 index 0000000..39348b6 --- /dev/null +++ b/vendor/github.com/faiface/beep/streamers.go @@ -0,0 +1,65 @@ +package beep + +// Silence returns a Streamer which streams num samples of silence. If num is negative, silence is +// streamed forever. +func Silence(num int) Streamer { + return StreamerFunc(func(samples [][2]float64) (n int, ok bool) { + if num == 0 { + return 0, false + } + if 0 < num && num < len(samples) { + samples = samples[:num] + } + for i := range samples { + samples[i] = [2]float64{} + } + if num > 0 { + num -= len(samples) + } + return len(samples), true + }) +} + +// Callback returns a Streamer, which does not stream any samples, but instead calls f the first +// time its Stream method is called. +func Callback(f func()) Streamer { + return StreamerFunc(func(samples [][2]float64) (n int, ok bool) { + if f != nil { + f() + f = nil + } + return 0, false + }) +} + +// Iterate returns a Streamer which successively streams Streamers obtains by calling the provided g +// function. The streaming stops when g returns nil. +// +// Iterate does not propagate errors from the generated Streamers. +func Iterate(g func() Streamer) Streamer { + var ( + s Streamer + first = true + ) + return StreamerFunc(func(samples [][2]float64) (n int, ok bool) { + if first { + s = g() + first = false + } + if s == nil { + return 0, false + } + for len(samples) > 0 { + if s == nil { + break + } + sn, sok := s.Stream(samples) + if !sok { + s = g() + } + samples = samples[sn:] + n += sn + } + return n, true + }) +} diff --git a/vendor/github.com/gen2brain/beeep/.appveyor.yml b/vendor/github.com/gen2brain/beeep/.appveyor.yml new file mode 100644 index 0000000..7555418 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/.appveyor.yml @@ -0,0 +1,18 @@ +version: "{build}" + +clone_depth: 1 + +clone_folder: c:\gopath\src\github.com\gen2brain\beeep + +environment: + GOPATH: c:\gopath + +install: + - echo %GOPATH% + - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% + - go version + - go env + - go get -u github.com/gen2brain/beeep + +build_script: + - go get -t ./... diff --git a/vendor/github.com/gen2brain/beeep/.travis.yml b/vendor/github.com/gen2brain/beeep/.travis.yml new file mode 100644 index 0000000..fd5ad29 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/.travis.yml @@ -0,0 +1,10 @@ +language: go + +go: + - 1.9.x + +install: + - go get -t ./... + +script: + - go build ./... diff --git a/vendor/github.com/gen2brain/beeep/LICENSE b/vendor/github.com/gen2brain/beeep/LICENSE new file mode 100644 index 0000000..9d4a922 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2017, Milan Nikolic +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/gen2brain/beeep/README.md b/vendor/github.com/gen2brain/beeep/README.md new file mode 100644 index 0000000..3def3fc --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/README.md @@ -0,0 +1,45 @@ +## beeep +[![TravisCI Build Status](https://travis-ci.org/gen2brain/beeep.svg?branch=master)](https://travis-ci.org/gen2brain/beeep) +[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/4u7avrhsdxua2c9b?svg=true)](https://ci.appveyor.com/project/gen2brain/beeep) +[![GoDoc](https://godoc.org/github.com/gen2brain/beeep?status.svg)](https://godoc.org/github.com/gen2brain/beeep) +[![Go Report Card](https://goreportcard.com/badge/github.com/gen2brain/beeep?branch=master)](https://goreportcard.com/report/github.com/gen2brain/beeep) + + +`beeep` provides a cross-platform library for sending desktop notifications, alerts and beeps. + +### Installation + + go get -u github.com/gen2brain/beeep + +### Examples + +```go +err := beeep.Beep(beeep.DefaultFreq, beeep.DefaultDuration) +if err != nil { + panic(err) +} +``` + +```go +err := beeep.Notify("Title", "Message body", "assets/information.png") +if err != nil { + panic(err) +} +``` + +```go +err := beeep.Alert("Title", "Message body", "assets/warning.png") +if err != nil { + panic(err) +} +``` + + +## macOS + +For icons to show up when using Alert() or Notify(), you will need to bundle your application +with a app icon. + +## More + +For cross-platform dialogs and input boxes see [dlgs](https://github.com/gen2brain/dlgs). diff --git a/vendor/github.com/gen2brain/beeep/alert_darwin.go b/vendor/github.com/gen2brain/beeep/alert_darwin.go new file mode 100644 index 0000000..68ea0ef --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/alert_darwin.go @@ -0,0 +1,16 @@ +// +build darwin,!linux,!freebsd,!netbsd,!openbsd,!windows,!js + +package beeep + +import "os/exec" + +// Alert displays a desktop notification and plays a default system sound. +func Alert(title, message, appIcon string) error { + osa, err := exec.LookPath("osascript") + if err != nil { + return err + } + + cmd := exec.Command(osa, "-e", `tell application "System Events" to display notification "`+message+`" with title "`+title+`" sound name "default"`) + return cmd.Run() +} diff --git a/vendor/github.com/gen2brain/beeep/alert_js.go b/vendor/github.com/gen2brain/beeep/alert_js.go new file mode 100644 index 0000000..904418f --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/alert_js.go @@ -0,0 +1,11 @@ +// +build js + +package beeep + +// Alert displays a desktop notification and plays a beep. +func Alert(title, message, appIcon string) error { + if err := Notify(title, message, appIcon); err != nil { + return err + } + return Beep(DefaultFreq, DefaultDuration) +} diff --git a/vendor/github.com/gen2brain/beeep/alert_unix.go b/vendor/github.com/gen2brain/beeep/alert_unix.go new file mode 100644 index 0000000..948b9d7 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/alert_unix.go @@ -0,0 +1,11 @@ +// +build linux freebsd netbsd openbsd + +package beeep + +// Alert displays a desktop notification and plays a beep. +func Alert(title, message, appIcon string) error { + if err := Notify(title, message, appIcon); err != nil { + return err + } + return Beep(DefaultFreq, DefaultDuration) +} diff --git a/vendor/github.com/gen2brain/beeep/alert_unsupported.go b/vendor/github.com/gen2brain/beeep/alert_unsupported.go new file mode 100644 index 0000000..f1d11e2 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/alert_unsupported.go @@ -0,0 +1,8 @@ +// +build !linux,!freebsd,!netbsd,!openbsd,!windows,!darwin,!js + +package beeep + +// Alert displays a desktop notification and plays a beep. +func Alert(title, message, appIcon string) error { + return ErrUnsupported +} diff --git a/vendor/github.com/gen2brain/beeep/alert_windows.go b/vendor/github.com/gen2brain/beeep/alert_windows.go new file mode 100644 index 0000000..5227be9 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/alert_windows.go @@ -0,0 +1,21 @@ +// +build windows,!linux,!freebsd,!netbsd,!openbsd,!darwin,!js + +package beeep + +import ( + toast "github.com/go-toast/toast" +) + +// Alert displays a desktop notification and plays a default system sound. +func Alert(title, message, appIcon string) error { + if isWindows10 { + note := toastNotification(title, message, pathAbs(appIcon)) + note.Audio = toast.Default + return note.Push() + } + + if err := Notify(title, message, appIcon); err != nil { + return err + } + return Beep(DefaultFreq, DefaultDuration) +} diff --git a/vendor/github.com/gen2brain/beeep/beeep.go b/vendor/github.com/gen2brain/beeep/beeep.go new file mode 100644 index 0000000..b0e66d4 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/beeep.go @@ -0,0 +1,27 @@ +// Package beeep provides a cross-platform library for sending desktop notifications and beeps. +package beeep + +import ( + "errors" + "path/filepath" + "runtime" +) + +var ( + // ErrUnsupported is returned when operating system is not supported. + ErrUnsupported = errors.New("beeep: unsupported operating system: " + runtime.GOOS) +) + +func pathAbs(path string) string { + var err error + var abs string + + if path != "" { + abs, err = filepath.Abs(path) + if err != nil { + abs = path + } + } + + return abs +} diff --git a/vendor/github.com/gen2brain/beeep/beep_darwin.go b/vendor/github.com/gen2brain/beeep/beep_darwin.go new file mode 100644 index 0000000..d541669 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/beep_darwin.go @@ -0,0 +1,28 @@ +// +build darwin,!linux,!freebsd,!netbsd,!openbsd,!windows,!js + +package beeep + +import ( + "os" + "os/exec" +) + +var ( + // DefaultFreq - frequency, in Hz, middle A + DefaultFreq = 0.0 + // DefaultDuration - duration in milliseconds + DefaultDuration = 0 +) + +// Beep beeps the PC speaker (https://en.wikipedia.org/wiki/PC_speaker). +func Beep(freq float64, duration int) error { + osa, err := exec.LookPath("osascript") + if err != nil { + // Output the only beep we can + _, err = os.Stdout.Write([]byte{7}) + return err + } + + cmd := exec.Command(osa, "-e", `beep`) + return cmd.Run() +} diff --git a/vendor/github.com/gen2brain/beeep/beep_js.go b/vendor/github.com/gen2brain/beeep/beep_js.go new file mode 100644 index 0000000..31feb39 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/beep_js.go @@ -0,0 +1,37 @@ +// +build js + +package beeep + +import ( + "github.com/gopherjs/gopherwasm/js" +) + +var ( + // DefaultFreq - frequency, in Hz, middle A + DefaultFreq = 0.0 + // DefaultDuration - duration in milliseconds + DefaultDuration = 0 +) + +// Beep beeps the PC speaker (https://en.wikipedia.org/wiki/PC_speaker). +func Beep(freq float64, duration int) (err error) { + defer func() { + e := recover() + + if e == nil { + return + } + + if e, ok := e.(*js.Error); ok { + err = e + } else { + panic(e) + } + }() + + a := js.Global().Get("document").Call("createElement", "audio") + a.Set("src", `data:audio/wav;base64,//uQRAAAAWMSLwUIYAAsYkXgoQwAEaYLWfkWgAI0wWs/ItAAAGDgYtAgAyN+QWaAAihwMWm4G8QQRDiMcCBcH3Cc+CDv/7xA4Tvh9Rz/y8QADBwMWgQAZG/ILNAARQ4GLTcDeIIIhxGOBAuD7hOfBB3/94gcJ3w+o5/5eIAIAAAVwWgQAVQ2ORaIQwEMAJiDg95G4nQL7mQVWI6GwRcfsZAcsKkJvxgxEjzFUgfHoSQ9Qq7KNwqHwuB13MA4a1q/DmBrHgPcmjiGoh//EwC5nGPEmS4RcfkVKOhJf+WOgoxJclFz3kgn//dBA+ya1GhurNn8zb//9NNutNuhz31f////9vt///z+IdAEAAAK4LQIAKobHItEIYCGAExBwe8jcToF9zIKrEdDYIuP2MgOWFSE34wYiR5iqQPj0JIeoVdlG4VD4XA67mAcNa1fhzA1jwHuTRxDUQ//iYBczjHiTJcIuPyKlHQkv/LHQUYkuSi57yQT//uggfZNajQ3Vmz+Zt//+mm3Wm3Q576v////+32///5/EOgAAADVghQAAAAA//uQZAUAB1WI0PZugAAAAAoQwAAAEk3nRd2qAAAAACiDgAAAAAAABCqEEQRLCgwpBGMlJkIz8jKhGvj4k6jzRnqasNKIeoh5gI7BJaC1A1AoNBjJgbyApVS4IDlZgDU5WUAxEKDNmmALHzZp0Fkz1FMTmGFl1FMEyodIavcCAUHDWrKAIA4aa2oCgILEBupZgHvAhEBcZ6joQBxS76AgccrFlczBvKLC0QI2cBoCFvfTDAo7eoOQInqDPBtvrDEZBNYN5xwNwxQRfw8ZQ5wQVLvO8OYU+mHvFLlDh05Mdg7BT6YrRPpCBznMB2r//xKJjyyOh+cImr2/4doscwD6neZjuZR4AgAABYAAAABy1xcdQtxYBYYZdifkUDgzzXaXn98Z0oi9ILU5mBjFANmRwlVJ3/6jYDAmxaiDG3/6xjQQCCKkRb/6kg/wW+kSJ5//rLobkLSiKmqP/0ikJuDaSaSf/6JiLYLEYnW/+kXg1WRVJL/9EmQ1YZIsv/6Qzwy5qk7/+tEU0nkls3/zIUMPKNX/6yZLf+kFgAfgGyLFAUwY//uQZAUABcd5UiNPVXAAAApAAAAAE0VZQKw9ISAAACgAAAAAVQIygIElVrFkBS+Jhi+EAuu+lKAkYUEIsmEAEoMeDmCETMvfSHTGkF5RWH7kz/ESHWPAq/kcCRhqBtMdokPdM7vil7RG98A2sc7zO6ZvTdM7pmOUAZTnJW+NXxqmd41dqJ6mLTXxrPpnV8avaIf5SvL7pndPvPpndJR9Kuu8fePvuiuhorgWjp7Mf/PRjxcFCPDkW31srioCExivv9lcwKEaHsf/7ow2Fl1T/9RkXgEhYElAoCLFtMArxwivDJJ+bR1HTKJdlEoTELCIqgEwVGSQ+hIm0NbK8WXcTEI0UPoa2NbG4y2K00JEWbZavJXkYaqo9CRHS55FcZTjKEk3NKoCYUnSQ0rWxrZbFKbKIhOKPZe1cJKzZSaQrIyULHDZmV5K4xySsDRKWOruanGtjLJXFEmwaIbDLX0hIPBUQPVFVkQkDoUNfSoDgQGKPekoxeGzA4DUvnn4bxzcZrtJyipKfPNy5w+9lnXwgqsiyHNeSVpemw4bWb9psYeq//uQZBoABQt4yMVxYAIAAAkQoAAAHvYpL5m6AAgAACXDAAAAD59jblTirQe9upFsmZbpMudy7Lz1X1DYsxOOSWpfPqNX2WqktK0DMvuGwlbNj44TleLPQ+Gsfb+GOWOKJoIrWb3cIMeeON6lz2umTqMXV8Mj30yWPpjoSa9ujK8SyeJP5y5mOW1D6hvLepeveEAEDo0mgCRClOEgANv3B9a6fikgUSu/DmAMATrGx7nng5p5iimPNZsfQLYB2sDLIkzRKZOHGAaUyDcpFBSLG9MCQALgAIgQs2YunOszLSAyQYPVC2YdGGeHD2dTdJk1pAHGAWDjnkcLKFymS3RQZTInzySoBwMG0QueC3gMsCEYxUqlrcxK6k1LQQcsmyYeQPdC2YfuGPASCBkcVMQQqpVJshui1tkXQJQV0OXGAZMXSOEEBRirXbVRQW7ugq7IM7rPWSZyDlM3IuNEkxzCOJ0ny2ThNkyRai1b6ev//3dzNGzNb//4uAvHT5sURcZCFcuKLhOFs8mLAAEAt4UWAAIABAAAAAB4qbHo0tIjVkUU//uQZAwABfSFz3ZqQAAAAAngwAAAE1HjMp2qAAAAACZDgAAAD5UkTE1UgZEUExqYynN1qZvqIOREEFmBcJQkwdxiFtw0qEOkGYfRDifBui9MQg4QAHAqWtAWHoCxu1Yf4VfWLPIM2mHDFsbQEVGwyqQoQcwnfHeIkNt9YnkiaS1oizycqJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//8elePK/Lf//IInrOF/FvDoADYAGBMGb7FtErm5MXMlmPAJQVgWta7Zx2go+8xJ0UiCb8LHHdftWyLJE0QIAIsI+UbXu67dZMjmgDGCGl1H+vpF4NSDckSIkk7Vd+sxEhBQMRU8j/12UIRhzSaUdQ+rQU5kGeFxm+hb1oh6pWWmv3uvmReDl0UnvtapVaIzo1jZbf/pD6ElLqSX+rUmOQNpJFa/r+sa4e/pBlAABoAAAAA3CUgShLdGIxsY7AUABPRrgCABdDuQ5GC7DqPQCgbbJUAoRSUj+NIEig0YfyWUho1VBBBA//uQZB4ABZx5zfMakeAAAAmwAAAAF5F3P0w9GtAAACfAAAAAwLhMDmAYWMgVEG1U0FIGCBgXBXAtfMH10000EEEEEECUBYln03TTTdNBDZopopYvrTTdNa325mImNg3TTPV9q3pmY0xoO6bv3r00y+IDGid/9aaaZTGMuj9mpu9Mpio1dXrr5HERTZSmqU36A3CumzN/9Robv/Xx4v9ijkSRSNLQhAWumap82WRSBUqXStV/YcS+XVLnSS+WLDroqArFkMEsAS+eWmrUzrO0oEmE40RlMZ5+ODIkAyKAGUwZ3mVKmcamcJnMW26MRPgUw6j+LkhyHGVGYjSUUKNpuJUQoOIAyDvEyG8S5yfK6dhZc0Tx1KI/gviKL6qvvFs1+bWtaz58uUNnryq6kt5RzOCkPWlVqVX2a/EEBUdU1KrXLf40GoiiFXK///qpoiDXrOgqDR38JB0bw7SoL+ZB9o1RCkQjQ2CBYZKd/+VJxZRRZlqSkKiws0WFxUyCwsKiMy7hUVFhIaCrNQsKkTIsLivwKKigsj8XYlwt/WKi2N4d//uQRCSAAjURNIHpMZBGYiaQPSYyAAABLAAAAAAAACWAAAAApUF/Mg+0aohSIRobBAsMlO//Kk4soosy1JSFRYWaLC4qZBYWFRGZdwqKiwkNBVmoWFSJkWFxX4FFRQWR+LsS4W/rFRb/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VEFHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU291bmRib3kuZGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwNGh0dHA6Ly93d3cuc291bmRib3kuZGUAAAAAAAAAACU=`) + a.Call("play") + + return +} diff --git a/vendor/github.com/gen2brain/beeep/beep_unix.go b/vendor/github.com/gen2brain/beeep/beep_unix.go new file mode 100644 index 0000000..58343b4 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/beep_unix.go @@ -0,0 +1,138 @@ +// +build linux freebsd netbsd openbsd + +package beeep + +import ( + "errors" + "os" + "syscall" + "time" + "unsafe" +) + +// Constants +const ( + // This number represents the fixed frequency of the original PC XT's timer chip, which is approximately 1.193 MHz. This number + // is divided with the desired frequency to obtain a counter value, that is subsequently fed into the timer chip, tied to the PC speaker. + clockTickRate = 1193180 + + // linux/kd.h, start sound generation (0 for off) + kiocsound = 0x4B2F + + // linux/input-event-codes.h + evSnd = 0x12 // Event type + sndTone = 0x02 // Sound +) + +var ( + // DefaultFreq - frequency, in Hz, middle A + DefaultFreq = 440.0 + // DefaultDuration - duration in milliseconds + DefaultDuration = 200 +) + +// inputEvent represents linux/input.h event structure. +type inputEvent struct { + Time syscall.Timeval // time in seconds since epoch at which event occurred + Type uint16 // event type + Code uint16 // event code related to the event type + Value int32 // event value related to the event type +} + +// ioctl system call manipulates the underlying device parameters of special files. +func ioctl(fd, name, data uintptr) error { + _, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, name, data) + if e != 0 { + return e + } + + return nil +} + +// Beep beeps the PC speaker (https://en.wikipedia.org/wiki/PC_speaker). +// +// On Linux it needs permission to access `/dev/tty0` or `/dev/input/by-path/platform-pcspkr-event-spkr` files for writing, +// and `pcspkr` module must be loaded. User must be in correct groups, usually `input` and/or `tty`. +// +// If it can not open device files, it will fallback to sending Bell character (https://en.wikipedia.org/wiki/Bell_character). +// For bell character in X11 terminals you can enable bell with `xset b on`. For console check `setterm` and `--blength` or `--bfreq` options. +// +// On macOS this just sends bell character. Enable `Audible bell` in Terminal --> Preferences --> Settings --> Advanced. +// +// On Windows it uses Beep function via syscall. +// +// On Web it plays hard coded beep sound. +func Beep(freq float64, duration int) error { + if freq == 0 { + freq = DefaultFreq + } else if freq > 20000 { + freq = 20000 + } else if freq < 0 { + freq = DefaultFreq + } + + if duration == 0 { + duration = DefaultDuration + } + + period := int(float64(clockTickRate) / freq) + + var evdev bool + + f, err := os.OpenFile("/dev/tty0", os.O_WRONLY, 0644) + if err != nil { + e := err + f, err = os.OpenFile("/dev/input/by-path/platform-pcspkr-event-spkr", os.O_WRONLY, 0644) + if err != nil { + e = errors.New("beeep: " + e.Error() + "; " + err.Error()) + + // Output the only beep we can + _, err = os.Stdout.Write([]byte{7}) + if err != nil { + return errors.New(e.Error() + "; " + err.Error()) + } + + return nil + } + + evdev = true + } + + defer f.Close() + + if evdev { // Use Linux evdev API + ev := inputEvent{} + ev.Type = evSnd + ev.Code = sndTone + ev.Value = int32(freq) + + d := *(*[unsafe.Sizeof(ev)]byte)(unsafe.Pointer(&ev)) + + // Start beep + f.Write(d[:]) + + time.Sleep(time.Duration(duration) * time.Millisecond) + + ev.Value = 0 + d = *(*[unsafe.Sizeof(ev)]byte)(unsafe.Pointer(&ev)) + + // Stop beep + f.Write(d[:]) + } else { // Use ioctl + // Start beep + err = ioctl(f.Fd(), kiocsound, uintptr(period)) + if err != nil { + return err + } + + time.Sleep(time.Duration(duration) * time.Millisecond) + + // Stop beep + err = ioctl(f.Fd(), kiocsound, uintptr(0)) + if err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/gen2brain/beeep/beep_unsupported.go b/vendor/github.com/gen2brain/beeep/beep_unsupported.go new file mode 100644 index 0000000..fb521f6 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/beep_unsupported.go @@ -0,0 +1,15 @@ +// +build !linux,!freebsd,!netbsd,!openbsd,!windows,!darwin,!js + +package beeep + +var ( + // DefaultFreq - frequency, in Hz, middle A + DefaultFreq = 0.0 + // DefaultDuration - duration in milliseconds + DefaultDuration = 0 +) + +// Beep beeps the PC speaker (https://en.wikipedia.org/wiki/PC_speaker). +func Beep(freq float64, duration int) error { + return ErrUnsupported +} diff --git a/vendor/github.com/gen2brain/beeep/beep_windows.go b/vendor/github.com/gen2brain/beeep/beep_windows.go new file mode 100644 index 0000000..35aa49d --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/beep_windows.go @@ -0,0 +1,41 @@ +// +build windows,!linux,!freebsd,!netbsd,!openbsd,!darwin,!js + +package beeep + +import ( + "syscall" +) + +var ( + // DefaultFreq - frequency, in Hz, middle A + DefaultFreq = 587.0 + // DefaultDuration - duration in milliseconds + DefaultDuration = 500 +) + +// Beep beeps the PC speaker (https://en.wikipedia.org/wiki/PC_speaker). +func Beep(freq float64, duration int) error { + if freq == 0 { + freq = DefaultFreq + } else if freq > 32767 { + freq = 32767 + } else if freq < 37 { + freq = DefaultFreq + } + + if duration == 0 { + duration = DefaultDuration + } + + kernel32, _ := syscall.LoadLibrary("kernel32.dll") + beep32, _ := syscall.GetProcAddress(kernel32, "Beep") + + defer syscall.FreeLibrary(kernel32) + + _, _, e := syscall.Syscall(uintptr(beep32), uintptr(2), uintptr(int(freq)), uintptr(duration), 0) + if e != 0 { + return e + } + + return nil +} diff --git a/vendor/github.com/gen2brain/beeep/go.mod b/vendor/github.com/gen2brain/beeep/go.mod new file mode 100644 index 0000000..ba1f357 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/go.mod @@ -0,0 +1,12 @@ +module github.com/gen2brain/beeep + +go 1.14 + +require ( + github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 + github.com/godbus/dbus/v5 v5.0.3 + github.com/gopherjs/gopherwasm v1.1.0 + github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect + github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af + golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 +) diff --git a/vendor/github.com/gen2brain/beeep/go.sum b/vendor/github.com/gen2brain/beeep/go.sum new file mode 100644 index 0000000..fb47196 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/go.sum @@ -0,0 +1,14 @@ +github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 h1:qZNfIGkIANxGv/OqtnntR4DfOY2+BgwR60cAcu/i3SE= +github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4/go.mod h1:kW3HQ4UdaAyrUCSSDR4xUzBKW6O2iA4uHhk7AtyYp10= +github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw= +github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherwasm v1.1.0 h1:fA2uLoctU5+T3OhOn2vYP0DVT6pxc7xhTlBB1paATqQ= +github.com/gopherjs/gopherwasm v1.1.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk= +github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/gen2brain/beeep/notify_darwin.go b/vendor/github.com/gen2brain/beeep/notify_darwin.go new file mode 100644 index 0000000..e8c0aa1 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/notify_darwin.go @@ -0,0 +1,20 @@ +// +build darwin,!linux,!freebsd,!netbsd,!openbsd,!windows,!js + +package beeep + +import ( + "os/exec" +) + +// Notify sends desktop notification. +// +// On macOS this executes AppleScript with `osascript` binary. +func Notify(title, message, appIcon string) error { + osa, err := exec.LookPath("osascript") + if err != nil { + return err + } + + cmd := exec.Command(osa, "-e", `display notification "`+message+`" with title "`+title+`"`) + return cmd.Run() +} diff --git a/vendor/github.com/gen2brain/beeep/notify_js.go b/vendor/github.com/gen2brain/beeep/notify_js.go new file mode 100644 index 0000000..57cd9b1 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/notify_js.go @@ -0,0 +1,49 @@ +// +build js + +package beeep + +import ( + "github.com/gopherjs/gopherwasm/js" +) + +// Notify sends desktop notification. +// +// On Web, in Firefox it just works, in Chrome you must call it from some "user gesture" like `onclick`, +// and you must use TLS certificate, it doesn't work with plain http. +func Notify(title, message, appIcon string) (err error) { + defer func() { + e := recover() + + if e == nil { + return + } + + if e, ok := e.(*js.Error); ok { + err = e + } else { + panic(e) + } + }() + + n := js.Global().Get("Notification") + + opts := js.Global().Get("Object").Invoke() + opts.Set("body", message) + opts.Set("icon", pathAbs(appIcon)) + + if n.Get("permission").String() == "granted" { + n.New(js.ValueOf(title), opts) + } else { + var f js.Callback + f = js.NewCallback(func(args []js.Value) { + if args[0].String() == "granted" { + n.New(js.ValueOf(title), opts) + } + f.Release() + }) + + n.Call("requestPermission", f) + } + + return +} diff --git a/vendor/github.com/gen2brain/beeep/notify_unix.go b/vendor/github.com/gen2brain/beeep/notify_unix.go new file mode 100644 index 0000000..daa9a15 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/notify_unix.go @@ -0,0 +1,58 @@ +// +build linux freebsd netbsd openbsd + +package beeep + +import ( + "errors" + "os/exec" + + "github.com/godbus/dbus/v5" +) + +// Notify sends desktop notification. +// +// On Linux it tries to send notification via D-Bus and it will fallback to `notify-send` binary. +func Notify(title, message, appIcon string) error { + appIcon = pathAbs(appIcon) + + cmd := func() error { + send, err := exec.LookPath("sw-notify-send") + if err != nil { + send, err = exec.LookPath("notify-send") + if err != nil { + return err + } + } + + c := exec.Command(send, title, message, "-i", appIcon) + return c.Run() + } + + knotify := func() error { + send, err := exec.LookPath("kdialog") + if err != nil { + return err + } + c := exec.Command(send, "--title", title, "--passivepopup", message, "10", "--icon", appIcon) + return c.Run() + } + + conn, err := dbus.SessionBus() + if err != nil { + return cmd() + } + obj := conn.Object("org.freedesktop.Notifications", dbus.ObjectPath("/org/freedesktop/Notifications")) + + call := obj.Call("org.freedesktop.Notifications.Notify", 0, "", uint32(0), appIcon, title, message, []string{}, map[string]dbus.Variant{}, int32(-1)) + if call.Err != nil { + e := cmd() + if e != nil { + e := knotify() + if e != nil { + return errors.New("beeep: " + call.Err.Error() + "; " + e.Error()) + } + } + } + + return nil +} diff --git a/vendor/github.com/gen2brain/beeep/notify_unsupported.go b/vendor/github.com/gen2brain/beeep/notify_unsupported.go new file mode 100644 index 0000000..8d31d00 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/notify_unsupported.go @@ -0,0 +1,8 @@ +// +build !linux,!freebsd,!netbsd,!openbsd,!windows,!darwin,!js + +package beeep + +// Notify sends desktop notification. +func Notify(title, message string) error { + return ErrUnsupported +} diff --git a/vendor/github.com/gen2brain/beeep/notify_windows.go b/vendor/github.com/gen2brain/beeep/notify_windows.go new file mode 100644 index 0000000..0a74b54 --- /dev/null +++ b/vendor/github.com/gen2brain/beeep/notify_windows.go @@ -0,0 +1,124 @@ +// +build windows,!linux,!freebsd,!netbsd,!openbsd,!darwin,!js + +package beeep + +import ( + "bufio" + "bytes" + "errors" + "os/exec" + "strings" + "syscall" + "time" + + toast "github.com/go-toast/toast" + "github.com/tadvi/systray" + "golang.org/x/sys/windows/registry" +) + +var isWindows10 bool +var applicationID string + +func init() { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + if err != nil { + return + } + defer k.Close() + + maj, _, err := k.GetIntegerValue("CurrentMajorVersionNumber") + if err != nil { + return + } + + isWindows10 = maj == 10 + + if isWindows10 { + applicationID = appID() + } +} + +// Notify sends desktop notification. +func Notify(title, message, appIcon string) error { + if isWindows10 { + return toastNotify(title, message, appIcon) + } + + err := baloonNotify(title, message, appIcon, false) + if err != nil { + e := msgNotify(title, message) + if e != nil { + return errors.New("beeep: " + err.Error() + "; " + e.Error()) + } + } + + return nil + +} + +func msgNotify(title, message string) error { + msg, err := exec.LookPath("msg") + if err != nil { + return err + } + cmd := exec.Command(msg, "*", "/TIME:3", title+"\n\n"+message) + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + return cmd.Run() +} + +func baloonNotify(title, message, appIcon string, bigIcon bool) error { + tray, err := systray.New() + if err != nil { + return err + } + + err = tray.ShowCustom(pathAbs(appIcon), title) + if err != nil { + return err + } + + go func() { + tray.Run() + time.Sleep(3 * time.Second) + tray.Stop() + }() + + return tray.ShowMessage(title, message, bigIcon) +} + +func toastNotify(title, message, appIcon string) error { + notification := toastNotification(title, message, pathAbs(appIcon)) + return notification.Push() +} + +func toastNotification(title, message, appIcon string) toast.Notification { + return toast.Notification{ + AppID: applicationID, + Title: title, + Message: message, + Icon: appIcon, + } +} + +func appID() string { + defID := "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe" + cmd := exec.Command("powershell", "Get-StartApps") + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + out, err := cmd.Output() + if err != nil { + return defID + } + + scanner := bufio.NewScanner(bytes.NewReader(out)) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if strings.Contains(line, "powershell.exe") { + sp := strings.Split(line, " ") + if len(sp) > 0 { + return sp[len(sp)-1] + } + } + } + + return defID +} diff --git a/vendor/github.com/go-toast/toast/.gitignore b/vendor/github.com/go-toast/toast/.gitignore new file mode 100644 index 0000000..ecdc9e2 --- /dev/null +++ b/vendor/github.com/go-toast/toast/.gitignore @@ -0,0 +1,3 @@ +.idea/ +vendor/* +!vendor/vendor.json diff --git a/vendor/github.com/go-toast/toast/LICENSE b/vendor/github.com/go-toast/toast/LICENSE new file mode 100644 index 0000000..68b7294 --- /dev/null +++ b/vendor/github.com/go-toast/toast/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2016 Jacob Marshall + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/go-toast/toast/readme.md b/vendor/github.com/go-toast/toast/readme.md new file mode 100644 index 0000000..4dbc207 --- /dev/null +++ b/vendor/github.com/go-toast/toast/readme.md @@ -0,0 +1,61 @@ +# Toast + +A go package for Windows 10 toast notifications. + +As seen in [jacobmarshall/pokevision-cli](https://github.com/jacobmarshall/pokevision-cli). + +## CLI + +As well as using go-toast within your Go projects, you can also utilise the CLI - for any of your projects. + +Download [64bit](https://go-toast-downloads.s3.amazonaws.com/v1/toast64.exe) or [32bit](https://go-toast-downloads.s3.amazonaws.com/v1/toast32.exe) + +```cmd +C:\Users\Example\Downloads\toast64.exe \ + --app-id "Example App" \ + --title "Hello World" \ + --message "Lorem ipsum dolor sit amet, consectetur adipiscing elit." \ + --icon "C:\Users\Example\Pictures\icon.png" \ + --audio "default" --loop \ + --duration "long" \ + --activation-arg "https://google.com" \ + --action "Open maps" --action-arg "bingmaps:?q=sushi" \ + --action "Open browser" --action-arg "http://..." +``` + +![CLI](./screenshot-cli.png) + +## Example + +```go +package main + +import ( + "log" + + "gopkg.in/toast.v1" +) + +func main() { + notification := toast.Notification{ + AppID: "Example App", + Title: "My notification", + Message: "Some message about how important something is...", + Icon: "go.png", // This file must exist (remove this line if it doesn't) + Actions: []toast.Action{ + {"protocol", "I'm a button", ""}, + {"protocol", "Me too!", ""}, + }, + } + err := notification.Push() + if err != nil { + log.Fatalln(err) + } +} +``` + +## Screenshots + +![Toast](./screenshot-toast.png) + +![Action centre](./screenshot-action-centre.png) diff --git a/vendor/github.com/go-toast/toast/screenshot-action-centre.png b/vendor/github.com/go-toast/toast/screenshot-action-centre.png new file mode 100644 index 0000000..e63917b Binary files /dev/null and b/vendor/github.com/go-toast/toast/screenshot-action-centre.png differ diff --git a/vendor/github.com/go-toast/toast/screenshot-cli.png b/vendor/github.com/go-toast/toast/screenshot-cli.png new file mode 100644 index 0000000..fc03c37 Binary files /dev/null and b/vendor/github.com/go-toast/toast/screenshot-cli.png differ diff --git a/vendor/github.com/go-toast/toast/screenshot-toast.png b/vendor/github.com/go-toast/toast/screenshot-toast.png new file mode 100644 index 0000000..9390406 Binary files /dev/null and b/vendor/github.com/go-toast/toast/screenshot-toast.png differ diff --git a/vendor/github.com/go-toast/toast/toast.go b/vendor/github.com/go-toast/toast/toast.go new file mode 100644 index 0000000..1bcba4b --- /dev/null +++ b/vendor/github.com/go-toast/toast/toast.go @@ -0,0 +1,359 @@ +package toast + +import ( + "bytes" + "errors" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "text/template" + + "github.com/nu7hatch/gouuid" + "syscall" +) + +var toastTemplate *template.Template + +var ( + ErrorInvalidAudio error = errors.New("toast: invalid audio") + ErrorInvalidDuration = errors.New("toast: invalid duration") +) + +type toastAudio string + +const ( + Default toastAudio = "ms-winsoundevent:Notification.Default" + IM = "ms-winsoundevent:Notification.IM" + Mail = "ms-winsoundevent:Notification.Mail" + Reminder = "ms-winsoundevent:Notification.Reminder" + SMS = "ms-winsoundevent:Notification.SMS" + LoopingAlarm = "ms-winsoundevent:Notification.Looping.Alarm" + LoopingAlarm2 = "ms-winsoundevent:Notification.Looping.Alarm2" + LoopingAlarm3 = "ms-winsoundevent:Notification.Looping.Alarm3" + LoopingAlarm4 = "ms-winsoundevent:Notification.Looping.Alarm4" + LoopingAlarm5 = "ms-winsoundevent:Notification.Looping.Alarm5" + LoopingAlarm6 = "ms-winsoundevent:Notification.Looping.Alarm6" + LoopingAlarm7 = "ms-winsoundevent:Notification.Looping.Alarm7" + LoopingAlarm8 = "ms-winsoundevent:Notification.Looping.Alarm8" + LoopingAlarm9 = "ms-winsoundevent:Notification.Looping.Alarm9" + LoopingAlarm10 = "ms-winsoundevent:Notification.Looping.Alarm10" + LoopingCall = "ms-winsoundevent:Notification.Looping.Call" + LoopingCall2 = "ms-winsoundevent:Notification.Looping.Call2" + LoopingCall3 = "ms-winsoundevent:Notification.Looping.Call3" + LoopingCall4 = "ms-winsoundevent:Notification.Looping.Call4" + LoopingCall5 = "ms-winsoundevent:Notification.Looping.Call5" + LoopingCall6 = "ms-winsoundevent:Notification.Looping.Call6" + LoopingCall7 = "ms-winsoundevent:Notification.Looping.Call7" + LoopingCall8 = "ms-winsoundevent:Notification.Looping.Call8" + LoopingCall9 = "ms-winsoundevent:Notification.Looping.Call9" + LoopingCall10 = "ms-winsoundevent:Notification.Looping.Call10" + Silent = "silent" +) + +type toastDuration string + +const ( + Short toastDuration = "short" + Long = "long" +) + +func init() { + toastTemplate = template.New("toast") + toastTemplate.Parse(` +[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null +[Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null +[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null + +$APP_ID = '{{if .AppID}}{{.AppID}}{{else}}Windows App{{end}}' + +$template = @" + + + + {{if .Icon}} + + {{end}} + {{if .Title}} + + {{end}} + {{if .Message}} + + {{end}} + + + {{if ne .Audio "silent"}} + +"@ + +$xml = New-Object Windows.Data.Xml.Dom.XmlDocument +$xml.LoadXml($template) +$toast = New-Object Windows.UI.Notifications.ToastNotification $xml +[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($APP_ID).Show($toast) + `) +} + +// Notification +// +// The toast notification data. The following fields are strongly recommended; +// - AppID +// - Title +// +// If no toastAudio is provided, then the toast notification will be silent. +// You can set the toast to have a default audio by setting "Audio" to "toast.Default", or if your go app takes +// user-provided input for audio, call the "toast.Audio(name)" func. +// +// The AppID is shown beneath the toast message (in certain cases), and above the notification within the Action +// Center - and is used to group your notifications together. It is recommended that you provide a "pretty" +// name for your app, and not something like "com.example.MyApp". +// +// If no Title is provided, but a Message is, the message will display as the toast notification's title - +// which is a slightly different font style (heavier). +// +// The Icon should be an absolute path to the icon (as the toast is invoked from a temporary path on the user's +// system, not the working directory). +// +// If you would like the toast to call an external process/open a webpage, then you can set ActivationArguments +// to the uri you would like to trigger when the toast is clicked. For example: "https://google.com" would open +// the Google homepage when the user clicks the toast notification. +// By default, clicking the toast just hides/dismisses it. +// +// The following would show a notification to the user letting them know they received an email, and opens +// gmail.com when they click the notification. It also makes the Windows 10 "mail" sound effect. +// +// toast := toast.Notification{ +// AppID: "Google Mail", +// Title: email.Subject, +// Message: email.Preview, +// Icon: "C:/Program Files/Google Mail/icons/logo.png", +// ActivationArguments: "https://gmail.com", +// Audio: toast.Mail, +// } +// +// err := toast.Push() +type Notification struct { + // The name of your app. This value shows up in Windows 10's Action Centre, so make it + // something readable for your users. It can contain spaces, however special characters + // (eg. é) are not supported. + AppID string + + // The main title/heading for the toast notification. + Title string + + // The single/multi line message to display for the toast notification. + Message string + + // An optional path to an image on the OS to display to the left of the title & message. + Icon string + + // The type of notification level action (like toast.Action) + ActivationType string + + // The activation/action arguments (invoked when the user clicks the notification) + ActivationArguments string + + // Optional action buttons to display below the notification title & message. + Actions []Action + + // The audio to play when displaying the toast + Audio toastAudio + + // Whether to loop the audio (default false) + Loop bool + + // How long the toast should show up for (short/long) + Duration toastDuration +} + +// Action +// +// Defines an actionable button. +// See https://msdn.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-adaptive-interactive-toasts for more info. +// +// Only protocol type action buttons are actually useful, as there's no way of receiving feedback from the +// user's choice. Examples of protocol type action buttons include: "bingmaps:?q=sushi" to open up Windows 10's +// maps app with a pre-populated search field set to "sushi". +// +// toast.Action{"protocol", "Open Maps", "bingmaps:?q=sushi"} +type Action struct { + Type string + Label string + Arguments string +} + +func (n *Notification) applyDefaults() { + if n.ActivationType == "" { + n.ActivationType = "protocol" + } + if n.Duration == "" { + n.Duration = Short + } + if n.Audio == "" { + n.Audio = Default + } +} + +func (n *Notification) buildXML() (string, error) { + var out bytes.Buffer + err := toastTemplate.Execute(&out, n) + if err != nil { + return "", err + } + return out.String(), nil +} + +// Builds the Windows PowerShell script & invokes it, causing the toast to display. +// +// Note: Running the PowerShell script is by far the slowest process here, and can take a few +// seconds in some cases. +// +// notification := toast.Notification{ +// AppID: "Example App", +// Title: "My notification", +// Message: "Some message about how important something is...", +// Icon: "go.png", +// Actions: []toast.Action{ +// {"protocol", "I'm a button", ""}, +// {"protocol", "Me too!", ""}, +// }, +// } +// err := notification.Push() +// if err != nil { +// log.Fatalln(err) +// } +func (n *Notification) Push() error { + n.applyDefaults() + xml, err := n.buildXML() + if err != nil { + return err + } + return invokeTemporaryScript(xml) +} + +// Returns a toastAudio given a user-provided input (useful for cli apps). +// +// If the "name" doesn't match, then the default toastAudio is returned, along with ErrorInvalidAudio. +// +// The following names are valid; +// - default +// - im +// - mail +// - reminder +// - sms +// - loopingalarm +// - loopimgalarm[2-10] +// - loopingcall +// - loopingcall[2-10] +// - silent +// +// Handle the error appropriately according to how your app should work. +func Audio(name string) (toastAudio, error) { + switch strings.ToLower(name) { + case "default": + return Default, nil + case "im": + return IM, nil + case "mail": + return Mail, nil + case "reminder": + return Reminder, nil + case "sms": + return SMS, nil + case "loopingalarm": + return LoopingAlarm, nil + case "loopingalarm2": + return LoopingAlarm2, nil + case "loopingalarm3": + return LoopingAlarm3, nil + case "loopingalarm4": + return LoopingAlarm4, nil + case "loopingalarm5": + return LoopingAlarm5, nil + case "loopingalarm6": + return LoopingAlarm6, nil + case "loopingalarm7": + return LoopingAlarm7, nil + case "loopingalarm8": + return LoopingAlarm8, nil + case "loopingalarm9": + return LoopingAlarm9, nil + case "loopingalarm10": + return LoopingAlarm10, nil + case "loopingcall": + return LoopingCall, nil + case "loopingcall2": + return LoopingCall2, nil + case "loopingcall3": + return LoopingCall3, nil + case "loopingcall4": + return LoopingCall4, nil + case "loopingcall5": + return LoopingCall5, nil + case "loopingcall6": + return LoopingCall6, nil + case "loopingcall7": + return LoopingCall7, nil + case "loopingcall8": + return LoopingCall8, nil + case "loopingcall9": + return LoopingCall9, nil + case "loopingcall10": + return LoopingCall10, nil + case "silent": + return Silent, nil + default: + return Default, ErrorInvalidAudio + } +} + +// Returns a toastDuration given a user-provided input (useful for cli apps). +// +// The default duration is short. If the "name" doesn't match, then the default toastDuration is returned, +// along with ErrorInvalidDuration. Most of the time "short" is the most appropriate for a toast notification, +// and Microsoft recommend not using "long", but it can be useful for important dialogs or looping sound toasts. +// +// The following names are valid; +// - short +// - long +// +// Handle the error appropriately according to how your app should work. +func Duration(name string) (toastDuration, error) { + switch strings.ToLower(name) { + case "short": + return Short, nil + case "long": + return Long, nil + default: + return Short, ErrorInvalidDuration + } +} + +func invokeTemporaryScript(content string) error { + id, _ := uuid.NewV4() + file := filepath.Join(os.TempDir(), id.String()+".ps1") + defer os.Remove(file) + bomUtf8 := []byte{0xEF, 0xBB, 0xBF} + out := append(bomUtf8, []byte(content)...) + err := ioutil.WriteFile(file, out, 0600) + if err != nil { + return err + } + cmd := exec.Command("PowerShell", "-ExecutionPolicy", "Bypass", "-File", file) + cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} + if err = cmd.Run(); err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/godbus/dbus/v5/.travis.yml b/vendor/github.com/godbus/dbus/v5/.travis.yml new file mode 100644 index 0000000..dd67672 --- /dev/null +++ b/vendor/github.com/godbus/dbus/v5/.travis.yml @@ -0,0 +1,50 @@ +dist: bionic +language: go +go_import_path: github.com/godbus/dbus + +go: + - 1.11.x + - 1.12.x + - 1.13.x + - tip + +matrix: + fast_finish: true + allow_failures: + - go: tip + +addons: + apt: + packages: + - dbus + - dbus-x11 + +before_install: + - export GO111MODULE=on + +script: + - go test -v -race -mod=readonly ./... # Run all the tests with the race detector enabled + - go vet ./... # go vet is the official Go static analyzer + +jobs: + include: + # The build matrix doesn't cover build stages, so manually expand + # the jobs with anchors + - &multiarch + stage: "Multiarch Test" + go: 1.11.x + env: TARGETS="386 arm arm64 ppc64le" + before_install: + - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + script: + - | + set -e + for target in $TARGETS; do + printf "\e[1mRunning test suite under ${target}.\e[0m\n" + GOARCH="$target" go test -v ./... + printf "\n\n" + done + - <<: *multiarch + go: 1.12.x + - <<: *multiarch + go: 1.13.x diff --git a/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md b/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md new file mode 100644 index 0000000..c88f9b2 --- /dev/null +++ b/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# How to Contribute + +## Getting Started + +- Fork the repository on GitHub +- Read the [README](README.markdown) for build and test instructions +- Play with the project, submit bugs, submit patches! + +## Contribution Flow + +This is a rough outline of what a contributor's workflow looks like: + +- Create a topic branch from where you want to base your work (usually master). +- Make commits of logical units. +- Make sure your commit messages are in the proper format (see below). +- Push your changes to a topic branch in your fork of the repository. +- Make sure the tests pass, and add any new tests as appropriate. +- Submit a pull request to the original repository. + +Thanks for your contributions! + +### Format of the Commit Message + +We follow a rough convention for commit messages that is designed to answer two +questions: what changed and why. The subject line should feature the what and +the body of the commit should describe the why. + +``` +scripts: add the test-cluster command + +this uses tmux to setup a test cluster that you can easily kill and +start for debugging. + +Fixes #38 +``` + +The format can be described more formally as follows: + +``` +: + + + +