Compare commits

Author SHA1 Message Date
renovate 0ab30379f0 Update golang.org/x/crypto/x509roots/fallback digest to 7a4a4d6
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-21 08:02:27 +00:00
alazyreader 3ef4219999 Merge pull request 'Update module tailscale.com to v1.102.4' (#128) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #128
2026-09-14 03:42:02 +00:00
renovate 23f5e54973 Update module tailscale.com to v1.102.4
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-10 19:02:12 +00:00
alazyreader 7d3b69cd7e Merge pull request 'Update module golang.org/x/sync to v0.23.0' (#126) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #126
2026-09-08 14:40:57 +00:00
renovate 4a4a89ac9c Update module golang.org/x/sync to v0.23.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-08 13:01:45 +00:00
alazyreader ebaef096fd Merge pull request 'Update golang.org/x/crypto/x509roots/fallback digest to 86efde5' (#125) from renovate/golang.org-x-crypto-x509roots-fallback-digest into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #125
2026-09-07 03:46:43 +00:00
renovate c4ae501402 Update golang.org/x/crypto/x509roots/fallback digest to 86efde5
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-06 20:01:47 +00:00
alazyreader 23a39f07bb Merge pull request 'fix cert issue' (#124) from add-certs into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #124
2026-09-06 19:59:34 +00:00
alazyreader 43158a0661 fix cert issue
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-06 15:58:54 -04:00
alazyreader dce67813d9 Merge pull request 'Update mysql Docker tag to v26' (#118) from renovate/mysql-26.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #118
2026-09-05 20:01:59 +00:00
alazyreader bce3730a91 Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.10.1' (#123) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #123
2026-09-02 19:52:17 +00:00
renovate 65bb611bb5 Update module github.com/go-sql-driver/mysql to v1.10.1
ci/woodpecker/push/woodpecker Pipeline was successful
2026-09-02 10:02:32 +00:00
alazyreader 3c5108c947 Merge pull request 'Update module tailscale.com to v1.102.3' (#121) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #121
2026-08-20 04:28:53 +00:00
renovate 7ec55823e2 Update module tailscale.com to v1.102.3
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-20 04:02:26 +00:00
alazyreader a5c9ff743f Merge pull request 'Update golang Docker tag to v1.27' (#122) from renovate/golang-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #122
2026-08-20 03:58:38 +00:00
alazyreader e51b9235f1 upgrade gomod
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-19 23:50:16 -04:00
renovate 4b0b906462 Update golang Docker tag to v1.27
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-19 20:03:18 +00:00
alazyreader 50e3c6c7d0 Merge pull request 'Update module tailscale.com to v1.102.2' (#120) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #120
2026-08-05 05:20:16 +00:00
renovate 72be469f49 Update module tailscale.com to v1.102.2
ci/woodpecker/push/woodpecker Pipeline was successful
2026-08-04 22:02:04 +00:00
alazyreader 203581f37c Merge pull request 'Update module tailscale.com to v1.102.1' (#119) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #119
2026-08-01 12:17:32 +00:00
renovate 99df476d0f Update module tailscale.com to v1.102.1
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-31 18:02:06 +00:00
renovate 2568f159ff Update mysql Docker tag to v26
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-28 00:01:44 +00:00
alazyreader 648a36b608 Merge pull request 'Update module tailscale.com to v1.102.0' (#117) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #117
2026-07-27 02:22:35 +00:00
renovate b47c7909cf Update module tailscale.com to v1.102.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-23 19:02:01 +00:00
alazyreader cbfc7fe4bf Merge pull request 'Update module golang.org/x/sync to v0.22.0' (#116) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #116
2026-07-08 14:21:22 +00:00
renovate 0144dab073 Update module golang.org/x/sync to v0.22.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-07-08 13:01:56 +00:00
alazyreader 46d82dafd2 Merge pull request 'Update module golang.org/x/sync to v0.21.0' (#115) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #115
2026-06-08 14:27:18 +00:00
renovate 6166f1462f Update module golang.org/x/sync to v0.21.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-06-08 13:01:59 +00:00
alazyreader d8edfeda3c Merge pull request 'Update module tailscale.com to v1.100.0' (#114) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #114
2026-06-04 21:19:58 +00:00
renovate 702ecd21e0 Update module tailscale.com to v1.100.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-06-03 20:02:12 +00:00
alazyreader 5d8abc9bc6 Merge pull request 'Update module tailscale.com to v1.98.5' (#113) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #113
2026-05-31 19:02:33 +00:00
renovate a14f4e975d Update module tailscale.com to v1.98.5
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-29 18:01:55 +00:00
alazyreader 1f182dae3c Merge pull request 'Update module tailscale.com to v1.98.4' (#112) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #112
2026-05-29 15:49:35 +00:00
renovate 0abc527c39 Update module tailscale.com to v1.98.4
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-28 20:02:24 +00:00
alazyreader 5bbaea73b6 Merge pull request 'Update module tailscale.com to v1.98.3' (#111) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #111
2026-05-25 17:59:19 +00:00
renovate ffd4a8b2a5 Update module tailscale.com to v1.98.3
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-21 19:02:16 +00:00
alazyreader db0afda596 Merge pull request 'Update module tailscale.com to v1.98.2' (#110) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #110
2026-05-17 20:10:16 +00:00
renovate 32007ff000 Update module tailscale.com to v1.98.2
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-14 16:03:08 +00:00
alazyreader c45a2e1057 fix a few bugs that claude pointed out
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-12 15:58:45 -04:00
alazyreader a7c5e1708c fix missing pull of edit checkboxes
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-12 15:52:20 -04:00
alazyreader 7f5fcd0943 Merge pull request 'Update module tailscale.com to v1.98.1' (#109) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #109
2026-05-08 04:28:31 +00:00
renovate 3de90072b2 Update module tailscale.com to v1.98.1
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-07 21:02:40 +00:00
alazyreader 437b419df5 Merge pull request 'Update module tailscale.com to v1.98.0' (#108) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #108
2026-05-06 17:56:58 +00:00
renovate c4833c6135 Update module tailscale.com to v1.98.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-05-05 20:01:56 +00:00
alazyreader b204005ab9 Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.10.0' (#107) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #107
2026-04-29 18:49:35 +00:00
renovate 7fab8f8b27 Update module github.com/go-sql-driver/mysql to v1.10.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-29 14:02:37 +00:00
alazyreader 1f760ed33d Merge pull request 'Update mysql Docker tag to v9.7' (#106) from renovate/mysql-9.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #106
2026-04-27 20:31:49 +00:00
renovate f66699b3f9 Update mysql Docker tag to v9.7
ci/woodpecker/push/woodpecker Pipeline was successful
2026-04-23 03:02:27 +00:00
alazyreader fe767e821d Merge pull request 'Update module tailscale.com to v1.96.5' (#105) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #105
2026-03-29 20:59:53 +00:00
renovate c67a62987d Update module tailscale.com to v1.96.5
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-27 19:01:59 +00:00
alazyreader a4c543fb5a Merge pull request 'Update module tailscale.com to v1.96.4' (#104) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #104
2026-03-26 00:46:34 +00:00
renovate b279cd53b6 Update module tailscale.com to v1.96.4
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-25 21:02:09 +00:00
alazyreader 3bfaf8c34a Merge pull request 'Update module tailscale.com to v1.96.3' (#103) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #103
2026-03-20 00:49:38 +00:00
renovate 0efba3074e Update module tailscale.com to v1.96.3
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-19 18:02:30 +00:00
alazyreader 847e363cf0 Merge pull request 'Update module tailscale.com to v1.96.2' (#102) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #102
2026-03-19 16:21:45 +00:00
renovate 463ee8d0e0 Update module tailscale.com to v1.96.2
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-17 23:02:16 +00:00
alazyreader ad8b6cc1a9 Merge pull request 'Update mysql Docker tag to v9.6' (#94) from renovate/mysql-9.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #94
2026-03-16 15:02:25 +00:00
alazyreader 7d58ce6140 Merge pull request 'Update module tailscale.com to v1.96.1' (#101) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #101
2026-03-13 17:10:54 +00:00
renovate 030ec84b1a Update module tailscale.com to v1.96.1
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-10 15:02:25 +00:00
alazyreader 7cc64bbbfb Merge pull request 'Update module golang.org/x/sync to v0.20.0' (#100) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #100
2026-03-09 01:42:30 +00:00
renovate 0778530d7d Update module golang.org/x/sync to v0.20.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-08 12:02:52 +00:00
alazyreader fc972d2baa Merge pull request 'Update module tailscale.com to v1.96.0' (#99) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #99
2026-03-05 21:37:46 +00:00
renovate ab1d3e6e74 Update module tailscale.com to v1.96.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-03-05 21:02:21 +00:00
alazyreader d60db5f67b clean up go file
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-14 16:10:03 -05:00
alazyreader f57a971951 Merge pull request 'more-build-caching' (#98) from more-build-caching into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #98
2026-02-14 20:56:49 +00:00
alazyreader bef3300198 fix build step
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-14 15:54:10 -05:00
alazyreader 873875c33a build in CI, not in docker
ci/woodpecker/push/woodpecker Pipeline failed
2026-02-14 15:53:28 -05:00
alazyreader a679d229f8 Update .woodpecker.yml
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-14 20:44:43 +00:00
alazyreader 89b715ec9a use a build cache
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-14 05:40:18 +00:00
alazyreader a541745127 Merge pull request 'Update module tailscale.com to v1.94.2' (#97) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #97
2026-02-14 04:47:03 +00:00
renovate c2c8e87ea3 Update module tailscale.com to v1.94.2
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-13 21:02:37 +00:00
alazyreader 63d9fc5156 Merge pull request 'Update golang Docker tag to v1.26' (#96) from renovate/golang-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #96
2026-02-13 15:58:59 +00:00
renovate cf0d211fdb Update mysql Docker tag to v9.6
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-13 01:03:02 +00:00
renovate d0865afa9b Update golang Docker tag to v1.26
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-10 22:02:11 +00:00
alazyreader 5a3b036107 Merge pull request 'Update module tailscale.com to v1.94.1' (#95) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #95
2026-01-22 22:12:00 +00:00
renovate bccd669baa Update module tailscale.com to v1.94.1
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-22 20:02:14 +00:00
alazyreader dd05b761b3 Merge pull request 'Update module tailscale.com to v1.94.0' (#93) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #93
2026-01-18 20:03:19 +00:00
renovate 53757133fd Update module tailscale.com to v1.94.0
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-14 22:03:46 +00:00
alazyreader 2fc8ac37eb Merge pull request 'Update module tailscale.com to v1.92.5' (#92) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #92
2026-01-08 14:53:12 +00:00
renovate 212327b7cf Update module tailscale.com to v1.92.5
ci/woodpecker/push/woodpecker Pipeline was successful
2026-01-06 19:03:44 +00:00
alazyreader 56ffcb8453 Merge pull request 'Update module tailscale.com to v1.92.4' (#91) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #91
2025-12-26 18:11:38 +00:00
renovate 59ea015b24 Update module tailscale.com to v1.92.4
ci/woodpecker/push/woodpecker Pipeline was successful
2025-12-18 11:03:20 +00:00
alazyreader d8a3a2bf8f Merge pull request 'Update mysql Docker tag to v9.5' (#82) from renovate/mysql-9.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #82
2025-12-14 21:04:52 +00:00
alazyreader 37f29d675b Merge pull request 'Update module tailscale.com to v1.92.2' (#90) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #90
2025-12-13 19:48:46 +00:00
renovate f3e5772f63 Update module tailscale.com to v1.92.2
ci/woodpecker/push/woodpecker Pipeline was successful
2025-12-10 23:03:11 +00:00
renovate 20f0890b94 Update mysql Docker tag to v9.5
ci/woodpecker/push/woodpecker Pipeline was successful
2025-12-09 14:02:54 +00:00
alazyreader 87472f6674 Merge pull request 'Update module tailscale.com to v1.92.1' (#88) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #88
2025-12-08 16:10:26 +00:00
renovate 53dc114d19 Update module tailscale.com to v1.92.1
ci/woodpecker/push/woodpecker Pipeline was successful
2025-12-08 15:05:32 +00:00
alazyreader edd3d71957 Merge pull request 'Update module golang.org/x/sync to v0.19.0' (#89) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #89
2025-12-08 14:59:31 +00:00
renovate 1b064134f3 Update module golang.org/x/sync to v0.19.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-12-08 13:02:47 +00:00
alazyreader 8e997b769d Merge pull request 'Update module tailscale.com to v1.92.0' (#87) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #87
2025-11-28 20:58:44 +00:00
renovate aee8fedf14 Update module tailscale.com to v1.92.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-11-26 22:04:53 +00:00
alazyreader a5e8c46fd9 Merge pull request 'Update module tailscale.com to v1.90.9' (#86) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #86
2025-11-26 21:51:35 +00:00
renovate 2cf2d44e57 Update module tailscale.com to v1.90.9
ci/woodpecker/push/woodpecker Pipeline was successful
2025-11-25 17:05:21 +00:00
alazyreader f5ce0a4dca Merge pull request 'Update module tailscale.com to v1.90.8' (#85) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #85
2025-11-21 18:06:32 +00:00
renovate cbd1d36516 Update module tailscale.com to v1.90.8
ci/woodpecker/push/woodpecker Pipeline was successful
2025-11-18 21:03:58 +00:00
alazyreader 439e77e095 Merge pull request 'Update module golang.org/x/sync to v0.18.0' (#84) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #84
2025-11-17 03:38:07 +00:00
renovate ab16d26cc3 Update module golang.org/x/sync to v0.18.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-11-08 13:02:49 +00:00
alazyreader 45211f4b89 Merge pull request 'Update module tailscale.com to v1.90.6' (#83) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #83
2025-11-01 20:11:49 +00:00
renovate 069a4d178c Update module tailscale.com to v1.90.6
ci/woodpecker/push/woodpecker Pipeline was successful
2025-10-31 22:02:51 +00:00
alazyreader 0196e63579 Merge pull request 'Update module tailscale.com to v1.90.2' (#81) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #81
2025-10-27 15:36:26 +00:00
renovate 60452be32d Update module tailscale.com to v1.90.2
ci/woodpecker/push/woodpecker Pipeline was successful
2025-10-24 18:03:28 +00:00
alazyreader 78b2afd4fb Merge pull request 'Update module tailscale.com to v1.88.4' (#80) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #80
2025-10-15 16:33:10 +00:00
renovate 88fcada19d Update module tailscale.com to v1.88.4
ci/woodpecker/push/woodpecker Pipeline was successful
2025-10-14 19:02:17 +00:00
alazyreader 16e270371e Merge pull request 'Update module tailscale.com to v1.88.3' (#79) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #79
2025-09-29 13:49:01 +00:00
renovate cc0174ed24 Update module tailscale.com to v1.88.3
ci/woodpecker/push/woodpecker Pipeline was successful
2025-09-25 14:02:41 +00:00
alazyreader 8763bda3f4 upgrade sync
ci/woodpecker/push/woodpecker Pipeline was successful
2025-09-19 21:03:52 -04:00
alazyreader 212fa8be4d Merge pull request 'Update module tailscale.com to v1.88.2' (#77) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #77
2025-09-19 14:13:27 +00:00
alazyreader 249e98d060 upgrade go in test
ci/woodpecker/push/woodpecker Pipeline was successful
2025-09-19 10:05:31 -04:00
alazyreader 76964a6a00 update go
ci/woodpecker/push/woodpecker Pipeline failed
2025-09-19 10:04:44 -04:00
renovate 6978b3dc47 Update module tailscale.com to v1.88.2
ci/woodpecker/push/woodpecker Pipeline failed
2025-09-17 18:02:27 +00:00
alazyreader b8865b41dc Merge pull request 'Update mysql Docker tag to v9.4' (#76) from renovate/mysql-9.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #76
2025-09-14 01:17:25 +00:00
renovate 23fd21dbf5 Update mysql Docker tag to v9.4
ci/woodpecker/push/woodpecker Pipeline was successful
2025-09-12 14:02:04 +00:00
alazyreader 2fcc42597d Merge pull request 'Update module tailscale.com to v1.84.3' (#74) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #74
2025-06-29 18:52:44 +00:00
renovate f3f8995d53 Update module tailscale.com to v1.84.3
ci/woodpecker/push/woodpecker Pipeline was successful
2025-06-26 17:02:02 +00:00
alazyreader 6c9320efae Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.9.3' (#73) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #73
2025-06-13 20:14:18 +00:00
renovate c9131bded8 Update module github.com/go-sql-driver/mysql to v1.9.3
ci/woodpecker/push/woodpecker Pipeline was successful
2025-06-13 07:01:34 +00:00
alazyreader e1fe23bcba Merge pull request 'Update module tailscale.com to v1.84.2' (#72) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #72
2025-06-10 14:07:48 +00:00
renovate d3febf1f6c Update module tailscale.com to v1.84.2
ci/woodpecker/push/woodpecker Pipeline was successful
2025-06-09 22:02:14 +00:00
alazyreader 0fe9679891 Merge pull request 'Update module golang.org/x/sync to v0.15.0' (#71) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #71
2025-06-09 01:24:07 +00:00
renovate 03c835139f Update module golang.org/x/sync to v0.15.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-06-06 19:01:59 +00:00
alazyreader af42df691b Merge pull request 'remove tui, unused' (#70) from remove-tui into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #70
2025-06-06 18:46:59 +00:00
alazyreader 95b4fc1802 remove tui, unused
ci/woodpecker/push/woodpecker Pipeline was successful
2025-06-04 22:04:51 -04:00
alazyreader 3874b2cc9d Merge pull request 'Update module tailscale.com to v1.84.1' (#69) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #69
2025-06-05 01:58:13 +00:00
renovate 049e55c9c9 Update module tailscale.com to v1.84.1
ci/woodpecker/push/woodpecker Pipeline was successful
2025-05-29 18:02:23 +00:00
alazyreader a370265e2e Merge pull request 'Update mysql Docker tag to v9.3' (#44) from renovate/mysql-9.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #44
2025-05-26 04:13:26 +00:00
alazyreader 535dd86aba Merge pull request 'Update module golang.org/x/sync to v0.14.0' (#67) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #67
2025-05-23 02:16:02 +00:00
renovate b0896539c8 Update module golang.org/x/sync to v0.14.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-05-23 01:06:13 +00:00
alazyreader 28d9c9b659 Merge pull request 'Update module tailscale.com to v1.84.0' (#68) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #68
2025-05-23 00:55:32 +00:00
renovate 6f9e1ec589 Update module tailscale.com to v1.84.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-05-21 20:02:45 +00:00
alazyreader 2adbe3004f Merge pull request 'Update module tailscale.com to v1.82.5' (#66) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #66
2025-05-14 02:10:47 +00:00
renovate da9fbaf347 Update module tailscale.com to v1.82.5
ci/woodpecker/push/woodpecker Pipeline was successful
2025-04-17 20:02:47 +00:00
renovate 5bca510e83 Update mysql Docker tag to v9.3
ci/woodpecker/push/woodpecker Pipeline was successful
2025-04-15 04:01:58 +00:00
alazyreader e76c63ccdc Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.9.2' (#65) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #65
2025-04-07 15:21:09 +00:00
renovate 288c345a22 Update module github.com/go-sql-driver/mysql to v1.9.2
ci/woodpecker/push/woodpecker Pipeline was successful
2025-04-07 12:02:07 +00:00
alazyreader 151265cc0e Merge pull request 'Update module golang.org/x/sync to v0.13.0' (#64) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #64
2025-04-06 16:59:49 +00:00
renovate 91cd6a1810 Update module golang.org/x/sync to v0.13.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-04-05 14:02:09 +00:00
alazyreader 64b767d0b5 Merge pull request 'Update module tailscale.com to v1.82.0' (#63) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #63
2025-03-30 20:11:08 +00:00
alazyreader 94de300e16 1.24 upgrade
ci/woodpecker/push/woodpecker Pipeline failed
2025-03-30 15:36:04 -04:00
renovate 813f8a43b2 Update module tailscale.com to v1.82.0
ci/woodpecker/push/woodpecker Pipeline failed
2025-03-26 21:03:02 +00:00
alazyreader d219f2b142 Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.9.1' (#62) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #62
2025-03-25 01:44:01 +00:00
renovate bbc4a5fecf Update module github.com/go-sql-driver/mysql to v1.9.1
ci/woodpecker/push/woodpecker Pipeline was successful
2025-03-21 03:02:13 +00:00
alazyreader 20de1eccbb Merge pull request 'Update module golang.org/x/sync to v0.12.0' (#61) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #61
2025-03-19 20:16:20 +00:00
renovate 7076c40e63 Update module golang.org/x/sync to v0.12.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-03-05 14:03:29 +00:00
alazyreader 7fafd997ca Merge pull request 'Update module tailscale.com to v1.80.3' (#60) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #60
2025-03-04 15:57:19 +00:00
renovate 007c4a5954 Update module tailscale.com to v1.80.3
ci/woodpecker/push/woodpecker Pipeline was successful
2025-03-04 00:02:45 +00:00
alazyreader c39837fa0a Merge pull request 'Update module tailscale.com to v1.80.2' (#57) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #57
2025-02-19 03:02:48 +00:00
alazyreader 956051a8b5 Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.9.0' (#59) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #59
2025-02-18 14:46:15 +00:00
renovate 81bdb3d4d8 Update module github.com/go-sql-driver/mysql to v1.9.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-02-18 04:03:02 +00:00
renovate 33354dc21a Update module tailscale.com to v1.80.2
ci/woodpecker/push/woodpecker Pipeline was successful
2025-02-12 19:03:58 +00:00
alazyreader 37682d66c4 Merge pull request 'Update module tailscale.com to v1.80.1' (#56) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #56
2025-02-09 03:17:38 +00:00
renovate bd6831233e Update module tailscale.com to v1.80.1
ci/woodpecker/push/woodpecker Pipeline was successful
2025-02-06 19:03:20 +00:00
alazyreader dcf5508576 Merge pull request 'Update module golang.org/x/sync to v0.11.0' (#55) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #55
2025-02-06 14:28:14 +00:00
renovate c996dcaef2 Update module golang.org/x/sync to v0.11.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-02-04 15:02:25 +00:00
alazyreader 0e09e47694 Merge pull request 'Update module github.com/gdamore/tcell/v2 to v2.8.1' (#53) from renovate/github.com-gdamore-tcell-v2-2.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #53
2025-02-04 03:46:05 +00:00
alazyreader 3d4d10f11c implement missing mock methods
ci/woodpecker/push/woodpecker Pipeline was successful
2025-02-03 22:41:59 -05:00
renovate ae0eb5f001 Update module github.com/gdamore/tcell/v2 to v2.8.1
ci/woodpecker/push/woodpecker Pipeline failed
2025-02-04 03:03:05 +00:00
alazyreader d36a89f61c Merge pull request 'Update module tailscale.com to v1.80.0' (#54) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #54
2025-02-04 02:23:21 +00:00
renovate 441809573e Update module tailscale.com to v1.80.0
ci/woodpecker/push/woodpecker Pipeline was successful
2025-01-30 22:02:59 +00:00
alazyreader 671bffdb2f update secrets syntax
ci/woodpecker/push/woodpecker Pipeline was successful
2025-01-05 19:08:51 +00:00
alazyreader a3413934b8 Merge pull request 'Update module golang.org/x/sync to v0.10.0' (#50) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/manual/woodpecker Pipeline failed
Reviewed-on: #50
2024-12-18 14:03:03 +00:00
renovate 5fb1786b5a Update module golang.org/x/sync to v0.10.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-12-15 04:08:01 +00:00
alazyreader 8293b7b384 Merge pull request 'Update module tailscale.com to v1.78.3' (#52) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #52
2024-12-15 03:45:04 +00:00
renovate db543392d9 Update module tailscale.com to v1.78.3
ci/woodpecker/push/woodpecker Pipeline was successful
2024-12-11 21:03:17 +00:00
alazyreader 405063d20b Merge pull request 'Update module tailscale.com to v1.78.1' (#51) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #51
2024-12-07 21:34:22 +00:00
renovate 030a403641 Update module tailscale.com to v1.78.1
ci/woodpecker/push/woodpecker Pipeline was successful
2024-12-06 01:03:08 +00:00
alazyreader 935956c7bf Merge pull request 'Update module tailscale.com to v1.76.6' (#47) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #47
2024-11-10 19:21:46 +00:00
renovate d9d57c4b2b Update module tailscale.com to v1.76.6
ci/woodpecker/push/woodpecker Pipeline was successful
2024-11-09 21:02:21 +00:00
alazyreader 155f5a967f Merge pull request 'Update module golang.org/x/sync to v0.9.0' (#49) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #49
2024-11-09 20:30:51 +00:00
renovate de3a121b3d Update module golang.org/x/sync to v0.9.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-11-07 22:02:19 +00:00
alazyreader 7175484edb Merge pull request 'Update module tailscale.com to v1.76.3' (#46) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #46
2024-10-29 15:27:49 +00:00
renovate 9a1974a538 Update module tailscale.com to v1.76.3
ci/woodpecker/push/woodpecker Pipeline was successful
2024-10-22 01:02:47 +00:00
alazyreader 757112837d Merge pull request 'Update module tailscale.com to v1.76.1' (#45) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #45
2024-10-22 00:37:13 +00:00
renovate 990d18bc64 Update module tailscale.com to v1.76.1
ci/woodpecker/push/woodpecker Pipeline was successful
2024-10-17 19:02:48 +00:00
alazyreader cd9c4e72ac Merge pull request 'Update module tailscale.com to v1.76.0' (#43) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #43
2024-10-13 18:23:56 +00:00
renovate 2ee8278652 Update module tailscale.com to v1.76.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-10-10 19:02:34 +00:00
alazyreader d4f021ff89 use proper host
ci/woodpecker/push/woodpecker Pipeline was successful
2024-09-23 14:06:38 +00:00
alazyreader 89291c1c0a Merge pull request 'Update module tailscale.com to v1.74.1' (#41) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #41
2024-09-21 18:06:26 +00:00
renovate bd7cf08fcb Update module tailscale.com to v1.74.1
ci/woodpecker/push/woodpecker Pipeline was successful
2024-09-18 20:02:23 +00:00
alazyreader dc556e210e Merge pull request 'Update module tailscale.com to v1.74.0' (#40) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #40
2024-09-14 17:16:41 +00:00
renovate 734f2d094f Update module tailscale.com to v1.74.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-09-12 21:02:17 +00:00
alazyreader aca9b1f672 Merge pull request 'Update module tailscale.com to v1.72.1' (#39) from renovate/tailscale.com-1.x into master
Reviewed-on: #39
2024-09-03 12:26:35 +00:00
renovate b5f04fb3dc Update module tailscale.com to v1.72.1 2024-09-02 18:02:49 +00:00
alazyreader eef2505285 Merge pull request 'Update golang Docker tag to v1.23' (#38) from renovate/golang-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #38
2024-08-20 00:49:33 +00:00
renovate a6887df550 Update golang Docker tag to v1.23
ci/woodpecker/push/woodpecker Pipeline was successful
2024-08-13 21:01:35 +00:00
alazyreader c5b4464a59 Merge pull request 'Update module golang.org/x/sync to v0.8.0' (#37) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline failed
Reviewed-on: #37
2024-08-08 12:21:46 +00:00
renovate b641ac0ca6 Update module golang.org/x/sync to v0.8.0 2024-08-04 16:04:37 +00:00
alazyreader 29434549ce Merge pull request 'Update mysql Docker tag to v9' (#36) from renovate/mysql-9.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #36
2024-07-12 13:49:12 +00:00
alazyreader c086a0ccb1 upgrade tsnet
ci/woodpecker/push/woodpecker Pipeline was successful
2024-07-08 08:46:16 -04:00
renovate 7bb73b8023 Update mysql Docker tag to v9
ci/woodpecker/push/woodpecker Pipeline was successful
2024-07-02 22:01:32 +00:00
alazyreader cdeb5e2d84 Merge pull request 'Update mysql Docker tag to v8.4' (#20) from renovate/mysql-8.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #20
2024-06-09 14:47:50 +00:00
alazyreader 551f1ef203 Merge pull request 'Update module tailscale.com to v1.66.4' (#35) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline is running
Reviewed-on: #35
2024-06-09 14:47:37 +00:00
renovate 5ef60f70f1 Update module tailscale.com to v1.66.4
ci/woodpecker/push/woodpecker Pipeline was successful
2024-05-21 01:02:06 +00:00
renovate f5d8ad7c8c Update mysql Docker tag to v8.4
ci/woodpecker/push/woodpecker Pipeline was successful
2024-05-01 01:01:28 +00:00
alazyreader b1708ec1a8 Merge pull request 'Update module golang.org/x/sync to v0.7.0' (#32) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #32
2024-04-22 00:47:04 +00:00
renovate 94a9a0b77e Update module golang.org/x/sync to v0.7.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-04-18 18:01:37 +00:00
alazyreader 0ab60f1297 Merge pull request 'Update module tailscale.com to v1.64.2' (#34) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline is running
Reviewed-on: #34
2024-04-18 17:35:51 +00:00
renovate b68ac7f643 Update module tailscale.com to v1.64.2
ci/woodpecker/push/woodpecker Pipeline was successful
2024-04-17 15:01:47 +00:00
alazyreader 5f4da44bf0 clean up go mod file 2024-04-13 13:45:35 -04:00
alazyreader 0a337e42dd Merge pull request 'Update module tailscale.com to v1.64.0' (#33) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #33
2024-04-13 13:59:38 +00:00
renovate 165a913828 Update module tailscale.com to v1.64.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-04-11 20:02:11 +00:00
alazyreader aec3b2dff4 Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.8.1' (#30) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #30
2024-03-29 19:00:45 +00:00
alazyreader cf8b07725b Merge pull request 'Update module tailscale.com to v1.62.1' (#31) from renovate/tailscale.com-1.x into master
Reviewed-on: #31
2024-03-29 18:59:40 +00:00
renovate 467d8202e2 Update module tailscale.com to v1.62.1
ci/woodpecker/push/woodpecker Pipeline was successful
2024-03-26 21:01:40 +00:00
renovate 2b6024e229 Update module github.com/go-sql-driver/mysql to v1.8.1
ci/woodpecker/push/woodpecker Pipeline was successful
2024-03-26 15:01:31 +00:00
alazyreader 62d03849a0 Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.8.0' (#28) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #28
2024-03-18 00:15:25 +00:00
alazyreader 7610034240 Merge pull request 'Update module tailscale.com to v1.62.0' (#29) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #29
2024-03-18 00:12:41 +00:00
renovate 7a6f5740e0 Update module tailscale.com to v1.62.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-03-13 16:01:38 +00:00
renovate 09478cbd5b Update module github.com/go-sql-driver/mysql to v1.8.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-03-09 08:01:26 +00:00
alazyreader 609d6cf166 Merge pull request 'Update module tailscale.com to v1.60.1' (#25) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #25
2024-03-04 01:43:16 +00:00
alazyreader 55b6c0689d Merge pull request 'Update module github.com/gdamore/tcell/v2 to v2.7.4' (#26) from renovate/github.com-gdamore-tcell-v2-2.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #26
2024-03-04 01:40:58 +00:00
renovate 363dc85eb4 Update module github.com/gdamore/tcell/v2 to v2.7.4
ci/woodpecker/push/woodpecker Pipeline was successful
2024-03-04 00:01:29 +00:00
renovate c9b9805cee Update module tailscale.com to v1.60.1
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-29 22:01:32 +00:00
alazyreader bf3c3d1dd7 bump build go
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-25 01:42:16 +00:00
alazyreader e3e8d68c95 upgrade go
ci/woodpecker/push/woodpecker Pipeline failed
2024-02-25 01:39:41 +00:00
alazyreader 960b0b8766 use steps
ci/woodpecker/push/woodpecker Pipeline failed
2024-02-25 01:39:13 +00:00
alazyreader f7b2e32651 Merge pull request 'Update module github.com/gdamore/tcell/v2 to v2.7.1' (#24) from renovate/github.com-gdamore-tcell-v2-2.x into master
Reviewed-on: #24
2024-02-21 01:23:44 +00:00
renovate 1554b151cb Update module github.com/gdamore/tcell/v2 to v2.7.1 2024-02-20 16:01:27 +00:00
alazyreader a191e41453 Merge pull request 'Update module tailscale.com to v1.60.0' (#23) from renovate/tailscale.com-1.x into master
Reviewed-on: #23
2024-02-20 15:22:19 +00:00
renovate 0848406a85 Update module tailscale.com to v1.60.0 2024-02-15 21:01:58 +00:00
alazyreader 0ce38e5453 Merge pull request 'Update module tailscale.com to v1.58.2' (#21) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #21
2024-01-27 18:04:29 +00:00
renovate bd1bf93ea0 Update module tailscale.com to v1.58.2
ci/woodpecker/push/woodpecker Pipeline was successful
2024-01-23 23:01:31 +00:00
alazyreader c26e9513ad Merge pull request 'Update module tailscale.com to v1.58.0' (#19) from renovate/tailscale.com-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #19
2024-01-20 01:55:46 +00:00
renovate cb53a35de6 Update module tailscale.com to v1.58.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-01-19 02:02:36 +00:00
alazyreader d69d416c49 Merge pull request 'Update module golang.org/x/sync to v0.6.0' (#18) from renovate/golang.org-x-sync-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #18
2024-01-07 04:13:01 +00:00
renovate caba03e58a Update module golang.org/x/sync to v0.6.0
ci/woodpecker/push/woodpecker Pipeline was successful
2024-01-07 04:01:32 +00:00
alazyreader 315fb4e9d2 management server listener (#17)
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #17
Co-authored-by: David Ashby <delta.mu.alpha@gmail.com>
Co-committed-by: David Ashby <delta.mu.alpha@gmail.com>
2024-01-06 21:38:13 +00:00
alazyreader dc7218131d Merge pull request 'Update module github.com/gdamore/tcell/v2 to v2.7.0' (#16) from renovate/github.com-gdamore-tcell-v2-2.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #16
2023-12-24 20:06:45 +00:00
alazyreader 878635450c add methods to mockscreen
ci/woodpecker/push/woodpecker Pipeline was successful
2023-12-24 15:05:11 -05:00
renovate 138a4a62c1 Update module github.com/gdamore/tcell/v2 to v2.7.0
ci/woodpecker/push/woodpecker Pipeline failed
2023-12-14 22:01:33 +00:00
alazyreader 6658edfd09 Merge pull request 'Update mysql Docker tag to v8.2' (#15) from renovate/mysql-8.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #15
2023-10-27 14:17:12 +00:00
renovate 4a13dc5e7f Update mysql Docker tag to v8.2
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-27 02:01:19 +00:00
alazyreader 43c3a25758 make root filter work with children's books
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-14 17:08:55 -04:00
alazyreader c30052bac7 rollback and fix
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-14 17:01:22 -04:00
alazyreader e8c3da4ac8 re-render when children's checkbox toggles
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-14 16:57:22 -04:00
alazyreader f282b10c05 hide empty strings in isbns
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-14 16:42:46 -04:00
alazyreader 95b269ca05 display isbn10s as well
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-14 15:37:36 -04:00
alazyreader 3d2c9964dc style label
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-08 15:39:03 -04:00
alazyreader 9d3a6fc876 fix checkbox label
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-08 15:36:58 -04:00
alazyreader 77ddc7ec8e add 'childrens' as a field on books
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-08 15:05:15 -04:00
alazyreader 1069dadd10 add flag for if a book is a children's book
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-08 10:21:52 -04:00
alazyreader 27c3a5c881 Merge pull request 'Update golang Docker tag to v1.21' (#14) from renovate/golang-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #14
2023-08-10 17:52:58 +00:00
renovate 899ad531a4 Update golang Docker tag to v1.21
ci/woodpecker/push/woodpecker Pipeline was successful
2023-08-08 22:01:12 +00:00
alazyreader d2b68c1889 Merge pull request 'Update mysql Docker tag to v8.1' (#13) from renovate/mysql-8.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #13
2023-08-05 01:18:23 +00:00
renovate 727dd7ae6f Update mysql Docker tag to v8.1
ci/woodpecker/push/woodpecker Pipeline was successful
2023-08-03 01:01:07 +00:00
alazyreader c2d9bde962 Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.7.1' (#12) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #12
2023-04-27 00:19:34 +00:00
renovate 905c596491 Update module github.com/go-sql-driver/mysql to v1.7.1
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 00:03:00 +00:00
alazyreader f8dcf14346 add tracking
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-22 00:16:18 +00:00
alazyreader b031bca91f add tracking
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-22 00:15:35 +00:00
alazyreader 5938a693c9 Merge pull request 'Update module github.com/irlndts/go-discogs to v0.3.6' (#11) from renovate/github.com-irlndts-go-discogs-0.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #11
2023-04-13 00:20:00 +00:00
alazyreader 25bc263c62 upstream fixed their notes problem
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-12 20:18:55 -04:00
renovate 25b1ced464 Update module github.com/irlndts/go-discogs to v0.3.6
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-13 00:00:57 +00:00
alazyreader 91eafafd84 Merge pull request 'Update mysql Docker tag to v8' (#9) from renovate/mysql-8.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #9
2023-04-12 00:27:26 +00:00
renovate c55c9116d0 Update mysql Docker tag to v8
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-12 00:27:08 +00:00
alazyreader ef55ec0663 Merge pull request 'Update module github.com/gdamore/tcell to v2' (#8) from renovate/github.com-gdamore-tcell-2.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #8
2023-04-08 02:44:06 +00:00
alazyreader 0a5cea9fb1 finish upgrade
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-07 22:42:54 -04:00
renovate 2118e8b790 Update module github.com/gdamore/tcell to v2
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-08 00:02:44 +00:00
alazyreader 1ab5a20fb2 Merge pull request 'Update golang Docker tag to v1.20' (#6) from renovate/golang-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #6
2023-04-07 02:54:16 +00:00
alazyreader c1085924f7 Merge pull request 'Update module github.com/go-sql-driver/mysql to v1.7.0' (#7) from renovate/github.com-go-sql-driver-mysql-1.x into master
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #7
2023-04-07 02:52:21 +00:00
renovate 3736096531 Update module github.com/go-sql-driver/mysql to v1.7.0
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-07 01:00:58 +00:00
renovate 7dea7afd86 Update golang Docker tag to v1.20
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-07 01:00:43 +00:00
renovate fa2f48a4a3 Add renovate.json
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-07 00:00:47 +00:00
alazyreader 2e3359df7a lazy-load record covers to avoid 429 responses
ci/woodpecker/push/woodpecker Pipeline was successful
2023-03-05 08:48:58 -05:00
alazyreader db3387aac9 do the same for the records view
ci/woodpecker/push/woodpecker Pipeline was successful
2023-02-25 17:27:43 -05:00
alazyreader e6e20a32f8 update two small lighthouse flags
ci/woodpecker/push/woodpecker Pipeline was successful
2023-02-25 17:26:25 -05:00
alazyreader 26b6ce6157 remove fontawesome in favor of raw unicode arrows
ci/woodpecker/push/woodpecker Pipeline was successful
2023-02-25 17:22:57 -05:00
alazyreader 8bdf848cbf finish up rewrite
ci/woodpecker/push/woodpecker Pipeline was successful
2023-02-11 23:00:38 -05:00
alazyreader 3b2e8cc79b unleashing flexbox upon thee 2023-02-11 22:31:46 -05:00
alazyreader 727e4e7867 make cover larger and transparent
ci/woodpecker/push/woodpecker Pipeline was successful
2023-02-04 21:30:18 -05:00
alazyreader b11316abe5 add a book count
ci/woodpecker/push/woodpecker Pipeline was successful
2023-02-04 20:51:40 -05:00
alazyreader f802943316 use parent docker
ci/woodpecker/push/woodpecker Pipeline was successful
2022-10-30 20:30:10 -04:00
alazyreader 20ea787617 try and force client to use tls
ci/woodpecker/push/woodpecker Pipeline failed
2022-10-30 20:24:20 -04:00
alazyreader 06f9f864c4 connect on encrypted port
ci/woodpecker/push/woodpecker Pipeline failed
2022-10-30 20:20:52 -04:00
alazyreader fff582aaf0 try and disable tls
ci/woodpecker/push/woodpecker Pipeline failed
2022-10-30 20:18:21 -04:00
alazyreader b692fac091 bump docker version to try and avoid cgroups issue
ci/woodpecker/push/woodpecker Pipeline failed
2022-10-30 20:14:14 -04:00
alazyreader 3fdc4bf509 remove extra spaces from notes field
ci/woodpecker/push/woodpecker Pipeline failed
2022-10-30 20:10:43 -04:00
alazyreader 0e878dac97 bump to 1.18
ci/woodpecker/push/woodpecker Pipeline was successful
2022-05-07 15:50:14 -04:00
alazyreader 3e34199f3b no need to sleep, the test step takes care of that
ci/woodpecker/push/woodpecker Pipeline was successful
2022-05-07 15:49:37 -04:00
alazyreader d45c3ebf33 correct missing quote
ci/woodpecker/push/woodpecker Pipeline was successful
2022-05-07 15:46:56 -04:00
alazyreader e8388b979c remove onloan column
ci/woodpecker/push/woodpecker Pipeline was successful
2022-05-07 15:35:07 -04:00
alazyreader 2814c5dc68 remove vfs
ci/woodpecker/push/woodpecker Pipeline was successful
2022-04-24 13:01:56 -04:00
alazyreader 98f4c4b0c1 add compose_token to secrets list
ci/woodpecker/push/woodpecker Pipeline was successful
2022-04-20 20:54:01 -04:00
alazyreader 48fc2970ad end-to-end deployment (#4)
ci/woodpecker/push/woodpecker Pipeline was successful
Co-authored-by: David Ashby <delta.mu.alpha@gmail.com>
Reviewed-on: #4
2022-04-21 00:45:58 +00:00
alazyreader f9d1cf744e refactor a bit
ci/woodpecker/push/woodpecker Pipeline was successful
2022-04-03 17:54:59 -04:00
alazyreader 2346f17edd only push builds on master
ci/woodpecker/push/woodpecker Pipeline was successful
2022-04-03 15:33:23 -04:00
alazyreader 886e28f348 correctly link build tag
ci/woodpecker/push/woodpecker Pipeline was successful
2022-04-03 15:29:40 -04:00
alazyreader 6a42420d36 add readme
ci/woodpecker/push/woodpecker Pipeline was successful
2022-04-03 15:28:37 -04:00
alazyreader ce194c1418 add comment
ci/woodpecker/push/woodpecker Pipeline was successful
2022-04-03 15:26:33 -04:00
alazyreader ca27b9f1aa secrets
ci/woodpecker/push/woodpecker Pipeline was successful
2022-04-03 15:19:06 -04:00
alazyreader efd624bd7d try new registry location
ci/woodpecker/push/woodpecker Pipeline failed
2022-04-03 15:07:38 -04:00
alazyreader c27751dd93 test mounting socket
ci/woodpecker/push/woodpecker Pipeline failed
2022-04-03 13:58:28 -04:00
alazyreader e56b6da79d checking docker ps output
ci/woodpecker/push/woodpecker Pipeline was successful
2022-04-03 13:51:22 -04:00
alazyreader 63c334a40f add missing line break 2022-04-03 12:33:15 -04:00
alazyreader a38fdaef91 hide label variant ()
ci/woodpecker/push/woodpecker Pipeline failed
2022-04-03 12:29:24 -04:00
alazyreader 5f766a0efb Merge pull request 'add file system persistence of record API calls to avoid hammering the discogs API' (#3) from persist into master
ci/woodpecker/push/woodpecker Pipeline failed
Reviewed-on: #3
2022-04-03 15:51:17 +00:00
alazyreader 5fed609b13 add file system persistence of record API calls to avoid hammering the discogs API
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
2022-04-03 11:26:26 -04:00
alazyreader 7b7e8d0058 remove tls entirely
ci/woodpecker/push/woodpecker Pipeline failed
2022-04-02 20:37:52 -04:00
alazyreader ab6de21418 try forcing client to use tls
ci/woodpecker/push/woodpecker Pipeline failed
2022-04-02 20:35:29 -04:00
alazyreader 729ed30450 use tls throughout
ci/woodpecker/push/woodpecker Pipeline failed
2022-04-02 20:32:44 -04:00
alazyreader 2116fbb15c no it looks like it really does have to be 2376
ci/woodpecker/push/woodpecker Pipeline failed
2022-04-02 20:28:44 -04:00
alazyreader 4b78201bd1 wait for service to start
ci/woodpecker/push/woodpecker Pipeline failed
2022-04-02 20:27:50 -04:00
alazyreader ad262c5fde update port
ci/woodpecker/push/woodpecker Pipeline failed
2022-04-02 20:21:15 -04:00
alazyreader 79c718153d try dind
ci/woodpecker/push/woodpecker Pipeline failed
2022-04-02 20:20:34 -04:00
alazyreader c492eba657 remove woodpecker until I decide how I want to do docker-in-docker 2022-04-02 20:14:50 -04:00
alazyreader 8a1e5f2b17 give woodpecker a try
ci/woodpecker/push/woodpecker Pipeline failed
2022-04-02 19:59:31 -04:00
alazyreader 83b00b69b1 correct italics for album names 2022-04-02 19:53:16 -04:00
alazyreader 5e101c236a correct from label 2022-04-02 17:31:02 -04:00
alazyreader 759835993e copy cert bundle into scratch 2022-04-02 17:30:25 -04:00
alazyreader 04aadf1d10 add logs for discogs initial load error 2022-04-02 17:26:49 -04:00
alazyreader 88d9c4f2f8 Merge pull request 'discogs' (#2) from discogs into master
Reviewed-on: #2
2022-04-02 21:21:22 +00:00
alazyreader 781d96ca14 wrap up initial discogs work 2022-04-02 17:21:02 -04:00
alazyreader 8cff0ec6ab update favicon 2022-04-02 13:42:09 -04:00
alazyreader 832e2025a0 initial UI, pagination starts at 1 for some reason 2022-04-02 13:38:14 -04:00
alazyreader 474ea9b57c struct name idiomatic 2022-04-02 13:10:16 -04:00
alazyreader 84803f1e3d discogs basic backend 2022-03-13 18:04:09 -04:00
alazyreader 98584bbef6 rename book package to media 2022-03-05 10:58:15 -05:00
alazyreader c9b70f02e7 bump go version 2022-01-15 15:51:06 -05:00
alazyreader 7ee118c1cd fix coverURL usage in the frontend 2022-01-15 15:46:59 -05:00
alazyreader 3e1b06e95a move actual onload trigger back into the html file 2021-10-31 20:07:41 -04:00
alazyreader b52949f3e9 move js file into its own file 2021-10-31 20:00:04 -04:00
alazyreader 4a02014bef make the search function pure instead of calling renderTable 2021-10-31 19:51:19 -04:00
alazyreader 913ec3d05e fix searching 2021-10-31 19:25:44 -04:00
alazyreader dcdff94712 Merge branch 'next' 2021-08-21 20:49:16 -04:00
alazyreader d0376e85e6 update preload on googlefont 2021-08-08 21:23:43 -04:00
alazyreader 9097c5efd8 ignore csv files 2021-08-08 21:20:18 -04:00
alazyreader 2803ce919a don't log full password string 2021-08-08 16:56:45 -04:00
alazyreader a0b2bffcb3 server Dockerfile 2021-08-08 10:01:20 -04:00
alazyreader d0ddad01fa speedup suggestions from Lighthouse 2021-08-04 22:59:23 -04:00
alazyreader f800e4c718 remove jquery 2021-08-04 22:31:48 -04:00
alazyreader b3ccda7885 remove more cruft 2021-08-03 21:08:33 -04:00
alazyreader 3e20c3a51e remove mustache 2021-08-03 20:10:24 -04:00
alazyreader f47f0e233c modernize frontend somewhat 2021-08-02 21:31:54 -04:00
alazyreader 9e4f10a7e0 condense ISBN generator 2021-08-01 19:09:54 -04:00
alazyreader fbcd59c7f0 switch frontend to use new api endpoint instead of google sheet 2021-08-01 19:05:40 -04:00
alazyreader 39fd64ace7 basic importing works 2021-08-01 18:52:21 -04:00
alazyreader ebbdb99f37 add file selector text area 2021-08-01 15:50:11 -04:00
alazyreader d6acfe235b draw cursor in editabletextarea 2021-08-01 15:49:35 -04:00
alazyreader e9b6c95019 build text-entry component 2021-08-01 11:41:02 -04:00
alazyreader bb01987b3b make boxes cover anything behind them 2021-08-01 11:05:51 -04:00
alazyreader 4ec1a85139 improve menus 2021-07-28 21:50:09 -04:00
alazyreader 2ea148bec3 clarity 2021-07-28 21:41:35 -04:00
alazyreader 49b8f675df add missing SetVisible changes 2021-07-28 21:39:37 -04:00
alazyreader 90a9facc44 fully commit to event-driven arch 2021-07-28 21:36:45 -04:00
alazyreader 4f4758f7fe fix a panic and handle cleaning up after a panic 2021-07-28 21:06:49 -04:00
alazyreader c2d2fe25c0 add style management 2021-07-13 18:32:01 -04:00
alazyreader 8c6c9325da it now displays books from the DB! 2021-07-06 21:40:12 -04:00
alazyreader 3bff06aad7 make it possible to switch between panels 2021-07-04 14:49:36 -04:00
alazyreader a6f958ccfb support ratio layouts 2021-07-04 13:49:53 -04:00
alazyreader 78d80aef6c handle uneven disribution of space 2021-07-04 09:41:22 -04:00
alazyreader 40b4cd3668 fix up boxes to actually work when moved around the screen 2021-07-03 21:25:32 -04:00
alazyreader 6cd94df521 doesn't quite work yet 2021-07-03 13:30:08 -04:00
alazyreader de3f9cfadb continue to add more building blocks 2021-07-03 10:58:45 -04:00
alazyreader 117c7e0e41 start to make it a little real... 2021-07-02 21:44:45 -04:00
alazyreader b648c1c0ae initial CLI exploration 2021-07-02 20:46:58 -04:00
alazyreader ca618cc609 environment-ize configuration 2021-07-02 18:47:14 -04:00
alazyreader 04506ed01f get database migrations up and running 2021-07-02 18:13:58 -04:00
alazyreader da239cf9ad initial planning 2021-06-26 14:08:35 -04:00
alazyreader 84a523f2f8 remove old version 2021-06-24 20:43:35 -04:00
45 changed files with 2799 additions and 756 deletions
+6 -2
View File
@@ -1,2 +1,6 @@
node_modules
.credentials
/server
*.properties
.DS_Store
/vendor
.recordsCache
.config
+38
View File
@@ -0,0 +1,38 @@
steps:
test:
image: golang:1.27
commands:
- go test ./...
volumes:
- go_build_cache:/root/.cache/go-build
- go_mod_cache:/go/pkg/mod
build:
image: golang:1.27
environment:
CGO_ENABLED: 0
GOOS: linux
commands:
- go build -o server ./cmd/serve
volumes:
- go_build_cache:/root/.cache/go-build
- go_mod_cache:/go/pkg/mod
container:
image: docker
commands:
- apk add curl
- docker login -u docker -p $DOCKER_PASSWORD registry.yetaga.in
- docker build -t registry.yetaga.in/library:latest .
- docker push registry.yetaga.in/library:latest
- 'curl http://100.113.98.36:4000/api/fetch -H "Authorization: Bearer $COMPOSE_TOKEN"'
- 'curl http://100.113.98.36:4000/api/update -H "Authorization: Bearer $COMPOSE_TOKEN"'
environment:
DOCKER_PASSWORD:
from_secret: docker_password
COMPOSE_TOKEN:
from_secret: compose_token
when:
branch: "master"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- go_build_cache:/root/.cache/go-build
- go_mod_cache:/go/pkg/mod
+3
View File
@@ -0,0 +1,3 @@
FROM scratch
COPY server ./
CMD ["/server"]
+27
View File
@@ -0,0 +1,27 @@
.PHONY: up down run test
GOFILES=$(shell find . -name '*.go' -o -name 'go.*')
STATICFILES=$(shell find . -name '*.js' -o -name '*.css' -o -name '*.html')
ifneq (,$(wildcard ./local.properties))
include local.properties
export
endif
build: server
run: build
./server
server: $(GOFILES) $(STATICFILES)
go build -o server ./cmd/serve
test:
go test ./... -cover
# dev dependencies
up:
docker-compose up -d
down:
docker-compose down
+179
View File
@@ -0,0 +1,179 @@
package main
import (
"encoding/json"
"fmt"
"io"
"io/fs"
"log"
"net/http"
"git.yetaga.in/alazyreader/library/media"
"tailscale.com/client/tailscale"
)
type Router struct {
static fs.FS
lib Library
rcol RecordCollection
query Query
ts *tailscale.LocalClient
isAdmin bool
}
type path map[string]func()
func (h path) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if f, ok := h[r.Method]; ok {
f()
return
}
writeJSONerror(w, "method not supported", http.StatusMethodNotAllowed)
}
func writeJSONerror(w http.ResponseWriter, err string, status int) {
log.Println(err)
writeJSON(w, struct{ Status, Reason string }{Status: "error", Reason: err}, status)
}
func writeJSON(w http.ResponseWriter, b any, status int) {
bytes, err := json.Marshal(b)
if err != nil {
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
w.Write(bytes)
w.Write([]byte("\n"))
}
func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/mode":
path{
http.MethodGet: func() {
writeJSON(w, struct{ Admin bool }{Admin: router.isAdmin}, http.StatusOK)
},
}.ServeHTTP(w, r)
case "/api/whoami":
if !router.isAdmin {
http.NotFoundHandler().ServeHTTP(w, r)
return
}
path{
http.MethodGet: func() { getWhoAmI(router.ts, w, r) },
}.ServeHTTP(w, r)
case "/api/records":
path{
http.MethodGet: func() { getRecords(router.rcol, w, r) },
}.ServeHTTP(w, r)
case "/api/books":
p := path{
http.MethodGet: func() { getBooks(router.lib, w, r) },
}
if router.isAdmin {
p[http.MethodPost] = func() { addBook(router.lib, w, r) }
p[http.MethodDelete] = func() { deleteBook(router.lib, w, r) }
}
p.ServeHTTP(w, r)
case "/api/query":
if !router.isAdmin {
http.NotFoundHandler().ServeHTTP(w, r)
return
}
path{
http.MethodPost: func() { lookupBook(router.query, w, r) },
}.ServeHTTP(w, r)
default:
static(router.static).ServeHTTP(w, r)
}
}
func getBooks(l Library, w http.ResponseWriter, r *http.Request) {
books, err := l.GetAllBooks(r.Context())
if err != nil {
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, books, http.StatusOK)
}
func addBook(l Library, w http.ResponseWriter, r *http.Request) {
book, err := ReadBody[media.Book](r.Body)
if err != nil {
writeJSONerror(w, err.Error(), http.StatusBadRequest)
return
}
if err = l.AddBook(r.Context(), book); err != nil {
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusAccepted)
}
func deleteBook(l Library, w http.ResponseWriter, r *http.Request) {
book, err := ReadBody[media.Book](r.Body)
if err != nil {
writeJSONerror(w, err.Error(), http.StatusBadRequest)
return
}
if err = l.DeleteBook(r.Context(), book); err != nil {
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusAccepted)
}
func getRecords(l RecordCollection, w http.ResponseWriter, r *http.Request) {
records, err := l.GetAllRecords(r.Context())
if err != nil {
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, records, http.StatusOK)
}
func getWhoAmI(ts *tailscale.LocalClient, w http.ResponseWriter, r *http.Request) {
whois, err := ts.WhoIs(r.Context(), r.RemoteAddr)
if err != nil {
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, whois.UserProfile, http.StatusOK)
}
func lookupBook(query Query, w http.ResponseWriter, r *http.Request) {
req, err := ReadBody[media.Book](r.Body)
if err != nil {
writeJSONerror(w, err.Error(), http.StatusBadRequest)
return
}
book, err := query.GetByISBN(req.ISBN13)
if err != nil {
writeJSONerror(w, err.Error(), http.StatusInternalServerError)
return
}
writeJSON(w, book, http.StatusOK)
}
func static(f fs.FS) http.Handler {
return http.FileServer(http.FS(f))
}
func ReadBody[T any](r io.ReadCloser) (*T, error) {
t := new(T)
if r == nil {
return t, fmt.Errorf("no body provided")
}
defer r.Close()
b, err := io.ReadAll(r)
if err != nil {
return t, fmt.Errorf("error reading body: %w", err)
}
err = json.Unmarshal(b, t)
if err != nil {
return t, fmt.Errorf("error reading body: %w", err)
}
return t, nil
}
+171
View File
@@ -0,0 +1,171 @@
package main
import (
"context"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"strings"
"time"
"git.yetaga.in/alazyreader/library/config"
"git.yetaga.in/alazyreader/library/database"
"git.yetaga.in/alazyreader/library/frontend"
"git.yetaga.in/alazyreader/library/media"
"git.yetaga.in/alazyreader/library/query"
"github.com/kelseyhightower/envconfig"
_ "golang.org/x/crypto/x509roots/fallback"
"golang.org/x/sync/errgroup"
"tailscale.com/tsnet"
"tailscale.com/util/must"
)
func obscureStr(in string, l int) string {
return in[0:max(l, len(in))] + strings.Repeat("*", max(0, len(in)-l))
}
type Library interface {
GetAllBooks(context.Context) ([]media.Book, error)
AddBook(context.Context, *media.Book) error
DeleteBook(context.Context, *media.Book) error
}
type RecordCollection interface {
GetAllRecords(context.Context) ([]media.Record, error)
}
type Query interface {
GetByISBN(string) (*media.Book, error)
}
func main() {
var c config.Config
must.Do(envconfig.Process("library", &c))
var lib Library
if c.DBType == "memory" {
lib = &database.Memory{}
} else if c.DBType == "sql" {
sqllib, latest, run, err := setupSQL(c)
if err != nil {
log.Fatalf("sql connection err: %v", err)
}
log.Printf("latest migration: %d; migrations run: %d", latest, run)
lib = sqllib
}
discogsCache := must.Get(database.NewDiscogsCache(
c.DiscogsToken, time.Hour*24, c.DiscogsUser, c.DiscogsPersist, c.DiscogsCacheFile,
))
queryProvider := &query.GoogleBooks{}
staticRoot := must.Get(frontend.Root())
servers := make(chan (*http.Server), 3)
errGroup := errgroup.Group{}
errGroup.Go(func() error {
return start(servers)(publicServer(8080, &Router{
static: staticRoot,
lib: lib,
rcol: discogsCache,
isAdmin: false,
}))
})
errGroup.Go(func() error {
return start(servers)(tailscaleListener("library-admin", &Router{
static: staticRoot,
lib: lib,
rcol: discogsCache,
query: queryProvider,
isAdmin: true,
}))
})
errGroup.Go(func() error {
return shutdown(servers)
})
log.Println(errGroup.Wait())
}
func setupSQL(c config.Config) (Library, int, int, error) {
if c.DBUser == "" || c.DBPass == "" || c.DBHost == "" || c.DBPort == "" || c.DBName == "" {
if c.DBPass != "" {
c.DBPass = obscureStr(c.DBPass, 3)
}
if c.DiscogsToken != "" {
c.DiscogsToken = obscureStr(c.DiscogsToken, 3)
}
return nil, 0, 0, fmt.Errorf("invalid config; vars provided: %+v", c)
}
sql, err := database.NewMySQLConnection(c.DBUser, c.DBPass, c.DBHost, c.DBPort, c.DBName)
if err != nil {
return nil, 0, 0, err
}
err = sql.PrepareDatabase(context.Background())
if err != nil {
return nil, 0, 0, err
}
latest, run, err := sql.RunMigrations(context.Background())
if err != nil {
return nil, 0, 0, err
}
return sql, latest, run, nil
}
func start(servers chan (*http.Server)) func(*http.Server, net.Listener, error) error {
return func(s *http.Server, l net.Listener, err error) error {
if err != nil {
return err
}
servers <- s
return s.Serve(l)
}
}
func shutdown(servers chan (*http.Server)) error {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt)
<-sigint
close(servers)
var err error
for server := range servers {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
if shutdownerr := server.Shutdown(ctx); shutdownerr != nil {
err = shutdownerr
}
cancel()
}
return err
}
func publicServer(port int, handler http.Handler) (*http.Server, net.Listener, error) {
server := &http.Server{Handler: handler}
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
return nil, nil, err
}
log.Printf("public server: http://0.0.0.0:%d/", port)
return server, ln, nil
}
func tailscaleListener(hostname string, handler *Router) (*http.Server, net.Listener, error) {
s := &tsnet.Server{
Dir: ".config/" + hostname,
Hostname: hostname,
Logf: func(s string, a ...any) { // silence most tsnet logs
if strings.HasPrefix(s, "To start this tsnet server") {
log.Printf(s, a...)
}
},
}
ln, err := s.Listen("tcp", ":80")
if err != nil {
return nil, nil, err
}
handler.ts, err = s.LocalClient()
if err != nil {
return nil, nil, err
}
log.Printf("management server: http://%s/", hostname)
return &http.Server{Handler: handler}, ln, nil
}
+15
View File
@@ -0,0 +1,15 @@
package config
type Config struct {
DBType string `default:"sql"`
DBUser string
DBPass string
DBHost string
DBPort string
DBName string
DiscogsToken string
DiscogsUser string
DiscogsPersist bool
DiscogsCacheFile string `default:".recordsCache"`
Debug bool
}
-48
View File
@@ -1,48 +0,0 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
-152
View File
@@ -1,152 +0,0 @@
body {
overflow: hidden;
}
#header {
height: 30px;
width: calc(100vw - 20px);
padding: 4px 10px;
background-color: #f7f3dc;
border-bottom: 2px solid #d8d0a0;
font-family: 'Libre Baskerville', sans-serif;
}
#header h1{
font-size: xx-large;
display: inline;
}
#searchBox {
position: absolute;
right: 10px;
top: 7px;
text-align: right;
width: 400px;
}
#searchBox input {
width: 300px;
font-size: 16px;
background: #f9f8ed;
padding: 2px 5px;
border: none;
border-bottom: 2px solid #d8d0a0;
font-family: 'Libre Baskerville', sans-serif;
}
#searchBox input:focus {
outline: none;
}
#searchBox input::placeholder {
font-family: 'Libre Baskerville', sans-serif;
color: #d8d0a0;
}
#current {
background-color: #f7f3dc;
width: calc(40vw - 40px);
height: calc(100vh - 80px);
padding: 20px;
overflow: auto;
float: left;
}
#books {
width: calc(60vw - 40px);
height: calc(100vh - 80px);
padding: 20px;
overflow: auto;
float: left;
}
.bookTable th {
font-weight: bold;
text-align: left;
font-family: 'Libre Baskerville', sans-serif;
}
.bookTable th[data-sort-by] {
cursor: pointer;
}
.bookTable th[data-sort-by]::after {
content: "\f0dc";
font-family: FontAwesome;
font-size: x-small;
position: relative;
left: 4px;
bottom: 2px;
}
.bookTable th.asc::after {
content: "\f0de";
font-family: FontAwesome;
font-size: x-small;
position: relative;
left: 4px;
bottom: 2px;
}
.bookTable th.desc::after {
content: "\f0dd";
font-family: FontAwesome;
font-size: x-small;
position: relative;
left: 4px;
bottom: 2px;
}
.bookTable td, .bookTable th {
padding: 5px;
min-width: 50px;
}
.tRow:nth-child(odd) {
background: #f9f8ed;
border-bottom: 1px solid #d8d0a0;
}
.bookTable .tRow {
cursor: pointer;
}
.bookTable .onLoan {
color: #bbb;
}
.bookTable .tRow .title {
font-style: italic;
max-width: 600px;
}
#current h1 {
font-size: x-large;
font-weight: bold;
font-style: italic;
padding: 10px 0;
}
#current h2 {
font-size: large;
padding: 7px 0;
}
#current img {
max-height: 400px;
max-width: 100%;
display: block;
margin: 0 auto;
}
#current .description p {
padding: 20px 0;
}
#current h1.onLoan {
color: #bbb;
}
#current h2.onLoan {
font-weight: bold;
}
+79
View File
@@ -0,0 +1,79 @@
package database
import (
"context"
"fmt"
"sync"
"git.yetaga.in/alazyreader/library/media"
)
type Memory struct {
lock sync.Mutex
shelf []media.Book
}
func (m *Memory) GetAllBooks(_ context.Context) ([]media.Book, error) {
m.lock.Lock()
defer m.lock.Unlock()
if m.shelf == nil {
m.shelf = []media.Book{}
}
return m.shelf, nil
}
func (m *Memory) AddBook(_ context.Context, b *media.Book) error {
m.lock.Lock()
defer m.lock.Unlock()
if m.shelf == nil {
m.shelf = []media.Book{}
}
m.shelf = append(m.shelf, *b)
return nil
}
func (m *Memory) UpdateBook(_ context.Context, old, new *media.Book) error {
m.lock.Lock()
defer m.lock.Unlock()
if m.shelf == nil {
m.shelf = []media.Book{}
return fmt.Errorf("book does not exist")
}
if old.ID != new.ID {
return fmt.Errorf("cannot change book ID")
}
for i := range m.shelf {
if m.shelf[i].ID == old.ID {
m.shelf[i] = *new
return nil
}
}
return fmt.Errorf("book does not exist")
}
func (m *Memory) DeleteBook(_ context.Context, b *media.Book) error {
m.lock.Lock()
defer m.lock.Unlock()
if m.shelf == nil {
m.shelf = []media.Book{}
return fmt.Errorf("book does not exist")
}
for i := range m.shelf {
if m.shelf[i].ID == b.ID {
// reorder slice to remove book quickly
m.shelf[i] = m.shelf[len(m.shelf)-1]
m.shelf = m.shelf[:len(m.shelf)-1]
return nil
}
}
return fmt.Errorf("book does not exist")
}
+20
View File
@@ -0,0 +1,20 @@
CREATE TABLE IF NOT EXISTS books(
id INT NOT NULL AUTO_INCREMENT,
title VARCHAR(1024),
authors VARCHAR(1024),
sortauthor VARCHAR(1024),
isbn10 VARCHAR(10),
isbn13 VARCHAR(13),
format VARCHAR(255),
genre VARCHAR(255),
publisher VARCHAR(255),
series VARCHAR(255),
volume VARCHAR(255),
year VARCHAR(10),
signed BOOLEAN,
description TEXT,
notes TEXT,
onloan VARCHAR(255),
coverurl VARCHAR(1024),
PRIMARY KEY (id)
)
@@ -0,0 +1 @@
ALTER TABLE books DROP COLUMN onloan;
@@ -0,0 +1,2 @@
ALTER TABLE `books`
ADD COLUMN `childrens` tinyint(1) NOT NULL DEFAULT 0
+255
View File
@@ -0,0 +1,255 @@
package database
import (
"context"
"database/sql"
"embed"
"fmt"
"io/fs"
"strconv"
"strings"
"time"
"git.yetaga.in/alazyreader/library/media"
_ "github.com/go-sql-driver/mysql"
)
//go:embed migrations/mysql
var migrationsFS embed.FS
type migration struct {
id int
name string
content []byte
}
type MySQL struct {
connection *sql.DB
tableName string
versionTable string
migrationsDirectory string
}
func NewMySQLConnection(user, pass, host, port, db string) (*MySQL, error) {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", user, pass, host, port, db) // what a strange syntax
connection, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
return &MySQL{
connection: connection,
tableName: "books",
versionTable: "migrations",
migrationsDirectory: "migrations/mysql",
}, nil
}
func (m *MySQL) PrepareDatabase(ctx context.Context) error {
if m.connection == nil || m.migrationsDirectory == "" || m.versionTable == "" {
return fmt.Errorf("uninitialized mysql client")
}
tablecheck := fmt.Sprintf(`SELECT count(*) AS count
FROM information_schema.TABLES
WHERE TABLE_NAME = '%s'
AND TABLE_SCHEMA in (SELECT DATABASE());`, m.versionTable)
tableschema := fmt.Sprintf(`CREATE TABLE %s (
id INT NOT NULL,
name VARCHAR(100) NOT NULL,
datetime DATE,
PRIMARY KEY (id))`, m.versionTable)
var versionTableExists int
m.connection.QueryRowContext(ctx, tablecheck).Scan(&versionTableExists)
if versionTableExists != 0 {
return nil
}
_, err := m.connection.ExecContext(ctx, tableschema)
return err
}
func (m *MySQL) GetLatestMigration(ctx context.Context) (int, error) {
if m.connection == nil || m.migrationsDirectory == "" || m.versionTable == "" {
return 0, fmt.Errorf("uninitialized mysql client")
}
migrationCheck := fmt.Sprintf("SELECT COALESCE(MAX(id), 0) FROM %s", m.versionTable)
var latestMigration int
err := m.connection.QueryRowContext(ctx, migrationCheck).Scan(&latestMigration)
return latestMigration, err
}
func (m *MySQL) RunMigrations(ctx context.Context) (int, int, error) {
if m.connection == nil || m.migrationsDirectory == "" || m.versionTable == "" {
return 0, 0, fmt.Errorf("uninitialized mysql client")
}
migrations := map[int]migration{}
dir, err := migrationsFS.ReadDir(m.migrationsDirectory)
if err != nil {
return 0, 0, err
}
for f := range dir {
if dir[f].Type().IsRegular() {
mig := migration{}
id, name, err := parseMigrationFileName(dir[f].Name())
if err != nil {
return 0, 0, err
}
mig.id, mig.name = id, name
mig.content, err = fs.ReadFile(migrationsFS, m.migrationsDirectory+"/"+dir[f].Name())
if err != nil {
return 0, 0, fmt.Errorf("failure loading migration: %w", err)
}
migrations[mig.id] = mig
}
}
latestMigrationRan, err := m.GetLatestMigration(ctx)
if err != nil {
return 0, 0, err
}
// exit if nothing to do (that is, there's no greater migration ID)
if _, ok := migrations[latestMigrationRan+1]; !ok {
return latestMigrationRan, 0, nil
}
// loop over and apply migrations if required
tx, err := m.connection.BeginTx(ctx, nil)
if err != nil {
return latestMigrationRan, 0, err
}
migrationLogSql := fmt.Sprintf("INSERT INTO %s (id, name, datetime) VALUES (?, ?, ?)", m.versionTable)
migrationsRun := 0
for migrationsToRun := true; migrationsToRun; _, migrationsToRun = migrations[latestMigrationRan+1] {
mig := migrations[latestMigrationRan+1]
_, err := tx.ExecContext(ctx, string(mig.content))
if err != nil {
nestederr := tx.Rollback()
if nestederr != nil {
return latestMigrationRan, migrationsRun, nestederr
}
return latestMigrationRan, migrationsRun, err
}
_, err = tx.ExecContext(ctx, migrationLogSql, mig.id, mig.name, time.Now())
if err != nil {
nestederr := tx.Rollback()
if nestederr != nil {
return latestMigrationRan, migrationsRun, nestederr
}
return latestMigrationRan, migrationsRun, err
}
latestMigrationRan = latestMigrationRan + 1
migrationsRun = migrationsRun + 1
}
err = tx.Commit()
return latestMigrationRan, migrationsRun, err
}
func (m *MySQL) GetAllBooks(ctx context.Context) ([]media.Book, error) {
if m.connection == nil {
return nil, fmt.Errorf("uninitialized mysql client")
}
allBooksQuery := fmt.Sprintf(`SELECT
id, title, authors, sortauthor, isbn10, isbn13, format, genre, publisher,
series, volume, year, signed, description, notes, coverurl, childrens
FROM %s`, m.tableName)
books := []media.Book{}
rows, err := m.connection.QueryContext(ctx, allBooksQuery)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
b := media.Book{}
var authors string
err := rows.Scan(
&b.ID, &b.Title, &authors, &b.SortAuthor, &b.ISBN10, &b.ISBN13, &b.Format, &b.Genre, &b.Publisher,
&b.Series, &b.Volume, &b.Year, &b.Signed, &b.Description, &b.Notes, &b.CoverURL, &b.Childrens)
if err != nil {
return nil, err
}
b.Authors = strings.Split(authors, ";")
b.Notes = strings.TrimSpace(b.Notes)
books = append(books, b)
}
return books, nil
}
func (m *MySQL) AddBook(ctx context.Context, b *media.Book) error {
if m.connection == nil {
return fmt.Errorf("uninitialized mysql client")
}
res, err := m.connection.ExecContext(ctx, `
INSERT INTO `+m.tableName+`
(
title, authors, sortauthor, isbn10, isbn13, format, genre, publisher, series,
volume, year, signed, description, notes, coverurl, childrens
)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
b.Title, strings.Join(b.Authors, ";"), b.SortAuthor, b.ISBN10, b.ISBN13, b.Format, b.Genre, b.Publisher, b.Series,
b.Volume, b.Year, b.Signed, b.Description, b.Notes, b.CoverURL, b.Childrens,
)
if err != nil {
return err
}
i, err := res.RowsAffected()
if err != nil {
return err
}
if i != 1 {
return fmt.Errorf("unexpectedly updated more than one row: %d", i)
}
return nil
}
func (m *MySQL) UpdateBook(ctx context.Context, old, new *media.Book) error {
if m.connection == nil {
return fmt.Errorf("uninitialized mysql client")
}
if old.ID != new.ID {
return fmt.Errorf("cannot change book ID")
}
res, err := m.connection.ExecContext(ctx, `
UPDATE `+m.tableName+`
SET
title=?, authors=?, sortauthor=?, isbn10=?, isbn13=?, format=?, genre=?, publisher=?,
series=?, volume=?, year=?, signed=?, description=?, notes=?, coverurl=?, childrens=?
WHERE id=?`,
new.Title, strings.Join(new.Authors, ";"), new.SortAuthor, new.ISBN10, new.ISBN13, new.Format, new.Genre, new.Publisher,
new.Series, new.Volume, new.Year, new.Signed, new.Description, new.Notes, new.CoverURL, new.Childrens, old.ID,
)
if err != nil {
return err
}
i, err := res.RowsAffected()
if err != nil {
return err
}
if i != 1 {
return fmt.Errorf("unexpectedly updated more than one row: %d", i)
}
return nil
}
func (m *MySQL) DeleteBook(_ context.Context, b *media.Book) error {
return nil
}
func parseMigrationFileName(filename string) (int, string, error) {
sp := strings.SplitN(filename, "-", 2)
i, err := strconv.Atoi(sp[0])
if err != nil {
return 0, "", err
}
tr := strings.TrimSuffix(sp[1], ".sql")
return i, tr, nil
}
+184
View File
@@ -0,0 +1,184 @@
package database
import (
"context"
"encoding/gob"
"errors"
"fmt"
"log"
"os"
"strconv"
"sync"
"time"
"git.yetaga.in/alazyreader/library/media"
"github.com/irlndts/go-discogs"
)
type DiscogsCache struct {
authToken string
m sync.Mutex
cache []media.Record
maxCacheAge time.Duration
lastRefresh time.Time
client discogs.Discogs
username string
persistence bool
persistFile string
}
type persistence struct {
CachedRecordSlice []media.Record
LastRefresh time.Time
}
func NewDiscogsCache(token string, maxCacheAge time.Duration, username string, persist bool, persistFile string) (*DiscogsCache, error) {
client, err := discogs.New(&discogs.Options{
UserAgent: "library.yetaga.in personal collection cache",
Token: token,
})
if err != nil {
return nil, err
}
cache := &DiscogsCache{
authToken: token,
client: client,
maxCacheAge: maxCacheAge,
username: username,
persistence: persist,
persistFile: persistFile,
}
if cache.persistence && cache.persistFile != "" {
cache.cache, cache.lastRefresh, err = cache.loadRecordsFromFS(context.Background())
if err != nil {
return nil, fmt.Errorf("cache load failed: %w", err)
}
if time.Now().After(cache.lastRefresh.Add(cache.maxCacheAge)) {
log.Printf("cache expired, running refresh...")
go func() {
err := cache.FlushCache(context.Background())
if err != nil {
log.Printf("error loading discogs content: %v", err)
}
}()
}
}
return cache, nil
}
func (c *DiscogsCache) FlushCache(ctx context.Context) error {
c.m.Lock()
defer c.m.Unlock()
records, err := c.fetchRecords(ctx, nil)
if err != nil {
return err
}
return c.saveRecordsToCache(ctx, records)
}
func (c *DiscogsCache) GetAllRecords(ctx context.Context) ([]media.Record, error) {
c.m.Lock()
defer c.m.Unlock()
if time.Now().After(c.lastRefresh.Add(c.maxCacheAge)) {
records, err := c.fetchRecords(ctx, nil)
if err != nil {
return c.cache, err
}
err = c.saveRecordsToCache(ctx, records)
return c.cache, err
}
return c.cache, nil
}
func (c *DiscogsCache) loadRecordsFromFS(ctx context.Context) ([]media.Record, time.Time, error) {
p := &persistence{}
f, err := os.Open(c.persistFile)
if errors.Is(err, os.ErrNotExist) {
log.Printf("%s not found, skipping file load...", c.persistFile)
return nil, time.Time{}, nil
}
if err != nil {
return nil, time.Time{}, fmt.Errorf("error opening cache file %s: %w", c.persistFile, err)
}
defer f.Close()
err = gob.NewDecoder(f).Decode(p)
if err != nil {
return nil, time.Time{}, fmt.Errorf("error readhing from cache file %s: %w", c.persistFile, err)
}
log.Printf("loaded %d records from %s", len(p.CachedRecordSlice), c.persistFile)
return p.CachedRecordSlice, p.LastRefresh, nil
}
func (c *DiscogsCache) saveRecordsToCache(ctx context.Context, records []media.Record) error {
c.cache = records
c.lastRefresh = time.Now()
if c.persistence && c.persistFile != "" {
p := persistence{
CachedRecordSlice: c.cache,
LastRefresh: c.lastRefresh,
}
f, err := os.OpenFile(c.persistFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
return fmt.Errorf("error opening cache file %s: %w", c.persistFile, err)
}
defer f.Close()
err = gob.NewEncoder(f).Encode(p)
if err != nil {
return fmt.Errorf("error writing to cache file %s: %w", c.persistFile, err)
}
log.Printf("wrote %d records to %s", len(c.cache), c.persistFile)
}
return nil
}
func (c *DiscogsCache) fetchRecords(ctx context.Context, pagination *discogs.Pagination) ([]media.Record, error) {
records := []media.Record{}
if pagination == nil {
pagination = getPagination(1)
}
log.Printf("calling discogs API, page %v", pagination.Page)
coll, err := c.client.CollectionItemsByFolder(c.username, 0, pagination)
if err != nil {
return records, fmt.Errorf("error loading collection: %w", err)
}
log.Printf("length: %v, first item in list: %s", len(coll.Items), coll.Items[0].BasicInformation.Title)
for i := range coll.Items {
records = append(records, collectionItemToRecord(&coll.Items[i]))
}
// recurse down the list
if coll.Pagination.URLs.Next != "" {
coll, err := c.fetchRecords(ctx, getPagination(pagination.Page+1))
if err != nil {
return records, err
}
records = append(records, coll...)
}
return records, nil
}
func getPagination(page int) *discogs.Pagination {
return &discogs.Pagination{Page: page, Sort: "added", SortOrder: "asc", PerPage: 100}
}
func collectionItemToRecord(item *discogs.CollectionItemSource) media.Record {
artists := []string{}
for _, artist := range item.BasicInformation.Artists {
artists = append(artists, artist.Name)
}
year := strconv.Itoa(item.BasicInformation.Year)
if year == "0" {
year = ""
}
return media.Record{
ID: item.ID,
AlbumName: item.BasicInformation.Title,
Artists: artists,
Identifier: item.BasicInformation.Labels[0].Catno,
Format: item.BasicInformation.Formats[0].Name,
Genre: item.BasicInformation.Genres[0],
Label: item.BasicInformation.Labels[0].Name,
Year: year,
CoverURL: item.BasicInformation.CoverImage,
DiscogsURL: fmt.Sprintf("https://www.discogs.com/release/%v", item.ID),
}
}
+8
View File
@@ -0,0 +1,8 @@
version: "3.8"
services:
mysql:
image: mysql:26.7
ports:
- 3306:3306
environment:
- MYSQL_ROOT_PASSWORD=KigYBNCT9IU5XyB3ehzMLFWyI # for dev testing only, obviously.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

+326
View File
@@ -0,0 +1,326 @@
var sortState = {
sortBy: "sortAuthor",
sortOrder: "asc",
};
var admin = false;
var books;
function checkAdminMode() {
fetch("/api/mode")
.then((response) => response.json())
.then((resp) => (admin = resp.Admin))
.then(() => {
if (admin) {
var element = document.getElementById("addBook");
element.addEventListener("click", (e) => {
e.preventDefault();
renderAddBookView();
});
element.classList.remove("hidden");
}
});
}
function loadBookList() {
fetch("/api/books")
.then((response) => response.json())
.then((list) => {
// prepare response
list.forEach(apiResponseParsing);
books = list;
document.getElementById("search").addEventListener("input", rerender);
document.getElementById("childrens").addEventListener("change", rerender);
rerender();
});
}
function rerender() {
var searchValue = document.getElementById("search").value;
var childrens = document.getElementById("childrens").checked;
renderTable(search(searchValue, childrens));
}
function init() {
checkAdminMode();
loadBookList();
}
function renderAddBookView() {
document.getElementById("current").innerHTML = AddBookTemplate();
document.getElementById("lookup").addEventListener("click", (e) => {
e.preventDefault();
if (document.getElementById("isbn-13").value.length === 13) {
getPossibleBooks(document.getElementById("isbn-13").value);
} else {
console.log("no isbn");
}
});
document.getElementById("save").addEventListener("click", (e) => {
e.preventDefault();
saveBook({
title: document.getElementById("title").value,
authors: document.getElementById("authors").value.split(";"),
sortAuthor: document.getElementById("sortAuthor").value,
"isbn-10": document.getElementById("isbn-10").value,
"isbn-13": document.getElementById("isbn-13").value,
publisher: document.getElementById("publisher").value,
format: document.getElementById("format").value,
genre: document.getElementById("genre").value,
series: document.getElementById("series").value,
volume: document.getElementById("volume").value,
year: document.getElementById("year").value,
coverURL: document.getElementById("coverURL").value,
childrens: document.getElementById("childrens").checked,
signed: document.getElementById("signed").checked,
});
});
}
function getPossibleBooks(isbn) {
fetch("/api/query", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ "isbn-13": isbn }),
})
.then((response) => response.json())
.then((json) => {
Object.keys(json).forEach((key) => {
var elem = document.getElementById(key);
if (elem !== null) {
elem.value = json[key];
}
});
});
}
function saveBook(book) {
fetch("/api/books", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(book),
}).then(() => {
clearAddBookForm();
loadBookList();
});
}
function renderTable(bookList, sortField) {
if (sortField) {
sortState.sortOrder =
sortState.sortBy === sortField && sortState.sortOrder === "asc"
? "desc"
: "asc";
sortState.sortBy = sortField;
}
bookList.sort((one, two) =>
(one[sortState.sortBy] + one["sortTitle"]).localeCompare(
two[sortState.sortBy] + two["sortTitle"],
),
);
if (sortState.sortOrder === "desc") {
bookList.reverse();
}
bookList.forEach((e, i) => (e.rowNumber = i)); // re-key
// rendering
var bookElement = document.getElementById("books");
bookElement.innerHTML = TableTemplate(bookList);
document.getElementById("bookCount").innerHTML = `${bookList.length} books`;
// add listeners for selecting book to view
Array.from(bookElement.querySelectorAll("tbody tr"))
.slice(1) // remove header from Array
.forEach((row) => {
row.addEventListener("click", (e) => {
// add listener to swap current book
document.getElementById("current").innerHTML = BookTemplate(
bookList[e.currentTarget.id],
);
});
});
// add sorting callbacks
Array.from(bookElement.querySelectorAll("tbody tr th[data-sort-by]")).forEach(
(row) => {
row.addEventListener("click", function (e) {
// only add callback when there's a sortBy attribute
renderTable(bookList, e.target.dataset.sortBy);
});
},
);
// mark currently active column
bookElement
.querySelector("tbody tr th[data-sort-by=" + sortState.sortBy + "]")
.classList.add(sortState.sortOrder);
}
function apiResponseParsing(book) {
book.sortTitle = titleCleaner(book.title);
if (!book["isbn-10"] && book["isbn-13"]) {
book["isbn-10"] = ISBNfromEAN(book["isbn-13"]);
}
if (!book.coverURL && book["isbn-10"]) {
book.coverURL =
`https://images-na.ssl-images-amazon.com/images/P/` +
book["isbn-10"] +
`.01.LZZ.jpg`;
}
return book;
}
function search(searchBy, includeChildrensBooks) {
searchBy = searchCleaner(searchBy);
return books.filter(
({ title, authors, genre, publisher, series, year, childrens }) => {
var inSearch = true;
if (searchBy !== "") {
inSearch = Object.values({
title,
authors: authors.join(" "),
genre,
publisher,
series,
year,
}).find((field) => searchCleaner(field).indexOf(searchBy) !== -1);
}
if (!includeChildrensBooks) {
return inSearch && !childrens;
}
return inSearch;
},
);
}
function titleCleaner(title) {
return title
.replace('"', "")
.replace(":", "")
.replace(/^(An?|The)\s/i, "");
}
function searchCleaner(str) {
return str
.toLowerCase()
.replaceAll('"', "")
.replaceAll(":", "")
.replaceAll("'", "")
.replaceAll(" ", "");
}
function ISBNfromEAN(EAN) {
ISBN = EAN.slice(3, 12);
var checkdigit =
(11 - (ISBN.split("").reduce((s, n, k) => s + n * (10 - k), 0) % 11)) % 11;
return ISBN + (checkdigit === 10 ? "X" : checkdigit);
}
function clearAddBookForm() {
document
.getElementById("newBookForm")
.childNodes.forEach((node) =>
node.nodeName === "LABEL" ? (node.lastChild.value = "") : null,
);
}
function BookTemplate({
"isbn-13": isbn13,
"isbn-10": isbn10,
authors,
coverURL,
format,
publisher,
series,
signed,
title,
volume,
year,
}) {
return `<img ${coverURL ? `src="${coverURL}"` : ``}/>
<div class="bookDetails">
<h1>${title}</h1>
<h2>${authors}</h2>
<span>${[isbn10, isbn13].filter((v) => v != "").join(" / ")}</span><br/>
<span>${publisher}, ${year}</span><br/>
${
series
? `<span>${series}${volume ? `, Volume ${volume}` : ""}</span><br/>`
: ""
}
${signed ? "<span>Signed by the author ✒</span><br/>" : ""}
<span>${format}</span>
${admin ? `<a href="#">Edit Book</a>` : ""}
</div>`;
}
function TableRowTemplate({
"isbn-13": isbn13,
"isbn-10": isbn10,
authors,
publisher,
rowNumber,
signed,
title,
year,
}) {
return `<tr class="tRow" id="${rowNumber}">
<td class="title">
${title} ${
signed
? '<span class="signed" title="Signed by the author" >✒</span>'
: ""
}
</td>
<td class="author">${authors}</td>
<td class="publisher">${publisher}</td>
<td class="year">${year}</td>
<td class="isbn">${isbn13 ? isbn13 : isbn10}</td>
</tr>`;
}
function TableTemplate(books) {
return `<table class="bookTable">
<tr>
<th data-sort-by="sortTitle" class="tHeader title">Title</th>
<th data-sort-by="sortAuthor" class="tHeader author">Author</th>
<th data-sort-by="publisher" class="tHeader publisher">Publisher</th>
<th data-sort-by="year" class="tHeader year">Year</th>
<th class="tHeader isbn">ISBN</th>
</tr>${books.reduce((acc, book) => {
return acc.concat(TableRowTemplate(book));
}, "")} </table>`;
}
function AddBookTemplate() {
return `<div class="addBookView">
<div id="newBookForm">
${[
{ name: "Title", id: "title", type: "text" },
{ name: "Authors", id: "authors", type: "text" },
{ name: "SortAuthor", id: "sortAuthor", type: "text" },
{ name: "ISBN10", id: "isbn-10", type: "text" },
{ name: "ISBN13", id: "isbn-13", type: "text" },
{ name: "Publisher", id: "publisher", type: "text" },
{ name: "Format", id: "format", type: "text" },
{ name: "Genre", id: "genre", type: "text" },
{ name: "Series", id: "series", type: "text" },
{ name: "Volume", id: "volume", type: "text" },
{ name: "Year", id: "year", type: "text" },
{ name: "CoverURL", id: "coverURL", type: "text" },
{ name: "Signed", id: "signed", type: "checkbox" },
{ name: "Childrens", id: "childrens", type: "checkbox" },
].reduce((acc, field) => {
return acc.concat(
`<label>${field.name} <input
type="${field.type}"
name="${field.name.toLowerCase()}"
id="${field.id}"
/></label><br/>`,
);
}, "")}
<input id="lookup" type="submit" value="look up">
<input id="save" type="submit" value="save">
</div>
</div>`;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

+54
View File
@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Library</title>
<link rel="stylesheet" href="style.css" />
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<link
href="https://fonts.googleapis.com/css?family=Libre+Baskerville:400,700&display=swap"
as="style"
rel="stylesheet preload prefetch"
/>
<script type="text/javascript" src="app.js"></script>
<script type="text/javascript">
window.addEventListener("DOMContentLoaded", init);
</script>
<script
defer
data-domain="library.yetaga.in"
src="https://stats.yetaga.in/js/script.js"
></script>
<meta name="description" content="A personal library record." />
</head>
<body>
<div class="wrapper">
<div id="header">
<h1>Library</h1>
<a href="/records">records</a>
<a
target="_blank"
rel="noreferrer"
href="https://git.yetaga.in/alazyreader/library"
>git</a
>
<a href="#" id="addBook" class="hidden">add book</a>
<div id="searchBox">
<label for="childrens" class="bookCount"
>Include Childrens Books?</label
>
<input id="childrens" type="checkbox" name="childrens" />
<span id="bookCount" class="bookCount">_ books</span>
<input
id="search"
type="text"
name="search"
placeholder="Search..."
/>
</div>
</div>
<div id="current">No Book Selected</div>
<div id="books"></div>
<!-- Table goes here -->
</div>
</body>
</html>
+168
View File
@@ -0,0 +1,168 @@
var sortState = {
sortBy: "sortArtist",
sortOrder: "asc",
};
function init() {
fetch("/api/records")
.then((response) => response.json())
.then((records) => {
// prepare response
records.forEach(apiResponseParsing);
document.getElementById("search").addEventListener("input", (e) => {
renderTable(search(records, e.target.value));
});
renderTable(records);
});
}
function renderTable(records, sortField) {
if (sortField) {
if (sortState.sortBy === sortField && sortState.sortOrder === "asc") {
sortState.sortOrder = "desc";
} else {
sortState.sortOrder = "asc";
}
sortState.sortBy = sortField;
}
records.sort((one, two) =>
(one[sortState.sortBy] + one["sortName"]).localeCompare(
two[sortState.sortBy] + two["sortName"]
)
);
if (sortState.sortOrder === "desc") {
records.reverse();
}
records.forEach((e, i) => (e.rowNumber = i)); // re-key
// rendering
var recordElement = document.getElementById("records");
recordElement.innerHTML = TableTemplate(records);
var recordCount = document.getElementById("recordCount");
recordCount.innerHTML = `${records.length} records`;
// add listeners for selecting record to view
Array.from(recordElement.querySelectorAll("tbody tr"))
.slice(1) // remove header from Array
.forEach((row) => {
row.addEventListener("click", (e) => {
// add listener to swap current record
document.getElementById("current").innerHTML = RecordTemplate(
records[e.currentTarget.id]
);
});
});
// add sorting callbacks
Array.from(
recordElement.querySelectorAll("tbody tr th[data-sort-by]")
).forEach((row) => {
row.addEventListener("click", function (e) {
renderTable(records, e.target.dataset.sortBy); // only add callback when there's a sortBy attribute
});
});
// mark currently active column
recordElement
.querySelector("tbody tr th[data-sort-by=" + sortState.sortBy + "]")
.classList.add(sortState.sortOrder);
}
function apiResponseParsing(record) {
record.sortName = titleCleaner(record.name);
record.artists = record.artists.map((artist) => {
return artist.replace(/ \([0-9]+\)$/, "");
});
record.label = record.label.replace(/ \([0-9]+\)$/, "");
record.sortArtist = record.artists.reduce((acc, curr) => {
return (
acc +
curr
.replace(/^(An?|The)\s/i, "")
.toLowerCase()
.replaceAll('"', "")
.replaceAll(":", "")
.replaceAll("'", "")
.replaceAll(" ", "")
);
}, "");
return record;
}
function search(records, searchBy) {
searchBy = searchCleaner(searchBy);
if (searchBy !== "") {
records = records.filter(({ name, artists, genre, label, year }) => {
return Object.values({
name,
artists: artists.join(" "),
genre,
label,
year,
}).find((field) => searchCleaner(field).indexOf(searchBy) !== -1);
});
}
return records;
}
function titleCleaner(title) {
return title
.replace('"', "")
.replace(":", "")
.replace(/^(An?|The)\s/i, "");
}
function searchCleaner(str) {
return str
.toLowerCase()
.replaceAll('"', "")
.replaceAll(":", "")
.replaceAll("'", "")
.replaceAll(" ", "");
}
function RecordTemplate({
name,
artists,
coverURL,
format,
genre,
identifier,
label,
year,
discogsURL,
}) {
return `${coverURL ? `<img src="${coverURL}" loading="lazy"/>` : ""}
<h1>${name}</h1>
<h2>${artists.join(", ")}</h2>
<span>${identifier}</span><br/>
<span>${genre}, ${label}, ${year}</span><br/>
<span>${format}</span><br/>
<span>
<a
target="_blank"
href="${discogsURL}"
>
Data provided by Discogs.
</a>
</span>`;
}
function TableRowTemplate({ name, coverURL, discogsURL }) {
return `<div class="record">
<img class="cover" src="${coverURL}" loading="lazy"/>
<span class="name">${name}</span>
<a
target="_blank"
href="${discogsURL}"
class="discogsLink"
>
Data provided by Discogs.
</a>
</div>`;
}
function TableTemplate(records) {
return `<div class="flow">${records.reduce((acc, record) => {
return acc.concat(TableRowTemplate(record));
}, "")} </div>`;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

+51
View File
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Library</title>
<link rel="stylesheet" href="style.css" />
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<link
href="https://fonts.googleapis.com/css?family=Libre+Baskerville:400,700&display=swap"
as="style"
rel="stylesheet preload prefetch"
/>
<script type="text/javascript" src="app.js"></script>
<script type="text/javascript">
window.addEventListener("DOMContentLoaded", init);
</script>
<script defer data-domain="library.yetaga.in" src="https://stats.yetaga.in/js/script.js"></script>
<meta
name="description"
content="A scrollable view of all of my records."
/>
</head>
<body>
<div class="wrapper">
<div id="header">
<h1>Records</h1>
<a href="/">books</a>
<a
target="_blank"
rel="noreferrer"
href="https://git.yetaga.in/alazyreader/library"
>git</a
>
<div id="searchBox">
<span id="recordCount" class="recordCount">_ records</span>
<input
id="search"
type="text"
name="search"
placeholder="Search..."
/>
</div>
</div>
<div id="records"></div>
<footer>
This application uses Discogs API but is not affiliated with, sponsored
or endorsed by Discogs. Discogs is a trademark of Zink Media, LLC.
</footer>
<!-- Table goes here -->
</div>
</body>
</html>
+238
View File
@@ -0,0 +1,238 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: "";
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/* site CSS starts here */
body {
overflow: hidden;
}
#header {
height: 30px;
width: calc(100vw - 20px);
padding: 4px 10px;
background-color: #f7f3dc;
border-bottom: 2px solid #d8d0a0;
font-family: "Libre Baskerville", sans-serif;
}
#header h1 {
font-size: xx-large;
display: inline;
}
#header .recordCount {
font-size: small;
color: #a29c77;
}
#searchBox {
position: absolute;
right: 10px;
top: 7px;
text-align: right;
width: 400px;
}
#searchBox input {
width: 300px;
font-size: 16px;
background: #f9f8ed;
padding: 2px 5px;
border: none;
border-bottom: 2px solid #d8d0a0;
font-family: "Libre Baskerville", sans-serif;
}
#searchBox input:focus {
outline: none;
}
#searchBox input::placeholder {
font-family: "Libre Baskerville", sans-serif;
color: #d8d0a0;
}
#records .flow {
height: calc(100vh - 35px - 15px - 20px);
padding-top: 5px;
padding-left: 20px;
padding-right: 20px;
display: flex;
justify-content: space-evenly;
align-items: flex-start;
flex-wrap: wrap;
overflow: auto;
}
#records .flow .record {
display: inline-block;
width: 250px;
padding: 15px;
border-radius: 3px;
}
#records .flow .record:nth-child(odd) {
background: #f9f8ed;
}
#records .flow .record .cover {
border-radius: 3px;
max-width: 250px;
display: block;
margin: 0 auto 3px;
overflow: unset;
}
#records .flow .record .name {
font-style: italic;
}
#records .flow .record a.discogsLink {
display: block;
text-align: right;
font-size: smaller;
padding-top: 10px;
color: #a29c77;
}
footer {
background-color: #f7f3dc;
font-size: smaller;
text-align: center;
vertical-align: bottom;
padding: 5px 0px;
position: absolute;
bottom: 0;
padding-left: 20px;
padding-right: 20px;
width: calc(100% - 40px);
color: #a29c77;
border-top: 2px solid #d8d0a0;
}
+288
View File
@@ -0,0 +1,288 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: "";
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/* site CSS starts here */
body {
overflow: hidden;
}
.hidden {
display: none;
}
#header {
height: 30px;
width: calc(100vw - 20px);
padding: 4px 10px;
background-color: #f7f3dc;
border-bottom: 2px solid #d8d0a0;
font-family: "Libre Baskerville", sans-serif;
}
#header h1 {
font-size: xx-large;
display: inline;
}
#header .bookCount {
font-size: small;
color: #a29c77;
}
#searchBox {
position: absolute;
right: 10px;
top: 7px;
text-align: right;
width: 800px;
}
#searchBox input#search {
width: 300px;
font-size: 16px;
background: #f9f8ed;
padding: 2px 5px;
border: none;
border-bottom: 2px solid #d8d0a0;
font-family: "Libre Baskerville", sans-serif;
}
#searchBox input:focus {
outline: none;
}
#searchBox input::placeholder {
font-family: "Libre Baskerville", sans-serif;
color: #d8d0a0;
}
#current {
background-color: #f7f3dc;
width: calc(40vw - 40px);
height: calc(100vh - 80px);
padding: 20px;
overflow: auto;
float: left;
position: relative;
}
#books {
width: calc(60vw - 40px);
height: calc(100vh - 80px);
padding: 20px;
overflow: auto;
float: left;
}
.bookTable th {
font-weight: bold;
text-align: left;
font-family: "Libre Baskerville", sans-serif;
}
.bookTable th[data-sort-by] {
cursor: pointer;
}
.bookTable th[data-sort-by]::after {
content: "\2195";
position: relative;
left: 4px;
}
.bookTable th.asc::after {
content: "\2191";
font-size: small;
position: relative;
left: 4px;
bottom: 1px;
}
.bookTable th.desc::after {
content: "\2193";
font-size: small;
position: relative;
left: 4px;
bottom: 1px;
}
.bookTable td,
.bookTable th {
padding: 5px;
min-width: 50px;
}
.tRow:nth-child(odd) {
background: #f9f8ed;
border-bottom: 1px solid #d8d0a0;
}
.bookTable .tRow {
cursor: pointer;
}
.bookTable .tRow .title {
font-style: italic;
max-width: 600px;
}
#current h1 {
font-size: x-large;
font-weight: bold;
font-style: italic;
padding: 0 0 5px 0;
}
#current h2 {
font-size: large;
padding: 7px 0;
}
#current img {
opacity: 0.5;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: auto;
}
#current .bookDetails {
position: relative;
background-color: rgba(255, 255, 255, 0.8);
padding: 10px;
margin: 0;
width: 75%;
border-radius: 5px;
}
#current .description p {
padding: 20px 0;
}
+13
View File
@@ -0,0 +1,13 @@
package frontend
import (
"embed"
"io/fs"
)
//go:embed files
var static embed.FS
func Root() (fs.FS, error) {
return fs.Sub(static, "files")
}
+58
View File
@@ -0,0 +1,58 @@
module git.yetaga.in/alazyreader/library
go 1.27.0
require (
git.yetaga.in/alazyreader/go-openlibrary v0.0.1
github.com/go-sql-driver/mysql v1.10.1
github.com/irlndts/go-discogs v0.3.6
github.com/kelseyhightower/envconfig v1.4.0
golang.org/x/crypto/x509roots/fallback v0.0.0-20260921070245-7a4a4d6beae2
golang.org/x/sync v0.23.0
tailscale.com v1.102.4
)
require (
filippo.io/edwards25519 v1.2.0 // indirect
github.com/akutz/memconn v0.1.0 // indirect
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
github.com/coder/websocket v1.8.14 // indirect
github.com/creachadair/msync v0.8.1 // indirect
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gaissmai/bart v0.26.1 // indirect
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 // indirect
github.com/godbus/dbus/v5 v5.2.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/huin/goupnp v1.3.0 // indirect
github.com/jsimonetti/rtnetlink v1.4.1 // indirect
github.com/klauspost/compress v1.19.1 // indirect
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
github.com/mdlayher/socket v0.5.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d // indirect
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd // indirect
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
github.com/tailscale/wireguard-go v0.0.0-20260715223240-2e01ba5b00f0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/term v0.45.0 // indirect
golang.org/x/text v0.40.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 // indirect
)
+254
View File
@@ -0,0 +1,254 @@
9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f h1:1C7nZuxUMNz7eiQALRfiqNOm04+m3edWlRff/BYHf0Q=
9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
git.yetaga.in/alazyreader/go-openlibrary v0.0.1 h1:5juCi8d7YyNxXFvHytQNBww5E6GmPetM7nz3kVUqNQY=
git.yetaga.in/alazyreader/go-openlibrary v0.0.1/go.mod h1:o6zBFJTovdFcpA+As1bRFvk5PDhAs2Lf8iVzUt7dKw8=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/aws/aws-sdk-go-v2 v1.42.1 h1:9eOTgu1z/dVtYpNZ3/8/XbbaX0x/BqE3HUzAzs6K0ek=
github.com/aws/aws-sdk-go-v2 v1.42.1/go.mod h1:5pKeft2eJj+gElQ38Jqg4ibCqh+/AK33/0X3hip7IjM=
github.com/aws/aws-sdk-go-v2/config v1.32.17 h1:FpL4/758/diKwqbytU0prpuiu60fgXKUWCpDJtApclU=
github.com/aws/aws-sdk-go-v2/config v1.32.17/go.mod h1:OXqUMzgXytfoF9JaKkhrOYsyh72t9G+MJH8mMRaexOE=
github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU=
github.com/aws/aws-sdk-go-v2/credentials v1.19.16/go.mod h1:6cx7zqDENJDbBIIWX6P8s0h6hqHC8Avbjh9Dseo27ug=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 h1:UuSfcORqNSz/ey3VPRS8TcVH2Ikf0/sC+Hdj400QI6U=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23/go.mod h1:+G/OSGiOFnSOkYloKj/9M35s74LgVAdJBSD5lsFfqKg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 h1:xM/Is9cKMHa8Jj8zkvWhvrFkZsXJV9E+BB4g0HW0duQ=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30/go.mod h1:WueJeNDZvK1fMYEWJIkcivBfEzUkTpBhzlrUKKY8EuA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30 h1:jn46zC9LdsVR/ZpMIJqMqb8hHv31BlLx3ulVqNspUOk=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30/go.mod h1:1hTMsAgbdS/AtUi4bw8+gUuh1pceo+eXRLfpSuSQj3M=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13 h1:mbRIur/BiHK6SKPjoBIXSE/hJ6g6JGRLuxQy1jGjlN4=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.13/go.mod h1:ITg9em2KbJx1s0y4aqRX5OYWG6HBZ5TVR//OdpEZ2CQ=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30 h1:/Z5jmNrKsSD7EmDjzAPsm/3L9IuOkzaynklJZ1qX7S4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.30/go.mod h1:lEzEZnOosE7zi8Z6royW1cFJTD9fpab4Ul1SBrllewk=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI=
github.com/aws/aws-sdk-go-v2/service/ssm v1.45.0 h1:IOdss+igJDFdic9w3WKwxGCmHqUxydvIhJOm9LJ32Dk=
github.com/aws/aws-sdk-go-v2/service/ssm v1.45.0/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.17/go.mod h1:xNWknVi4Ezm1vg1QsB/5EWpAJURq22uqd38U8qKvOJc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 h1:+1Kl1zx6bWi4X7cKi3VYh29h8BvsCoHQEQ6ST9X8w7w=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21/go.mod h1:4vIRDq+CJB2xFAXZ+YgGUTiEft7oAQlhIs71xcSeuVg=
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 h1:F/M5Y9I3nwr2IEpshZgh1GeHpOItExNM9L1euNuh/fk=
github.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7DCsL+2gjEH+RPEAexAzAio=
github.com/aws/smithy-go v1.27.3 h1:F3Zb497UhhskkfpJmfkXswyo+t0sh9OTBnIHjogWbVY=
github.com/aws/smithy-go v1.27.3/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02 h1:bXAPYSbdYbS5VTy92NIUbeDI1qyggi+JYh5op9IFlcQ=
github.com/axiomhq/hyperloglog v0.0.0-20240319100328-84253e514e02/go.mod h1:k08r+Yj1PRAmuayFiRK6MYuR5Ve4IuZtTfxErMIh0+c=
github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok=
github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/creachadair/mds v0.25.13 h1:PsSUHV6zsfPd29k4kvm1rMoee1YFia7JyNGeMPmDcPM=
github.com/creachadair/mds v0.25.13/go.mod h1:4hatI3hRM+qhzuAmqPRFvaBM8mONkS7nsLxkcuTYUIs=
github.com/creachadair/msync v0.8.1 h1:QRd8si3qZ2Q4TaDL7tS/MG/lFE3YND7U7J9fy42eAFM=
github.com/creachadair/msync v0.8.1/go.mod h1:dt0bscS09J8Ie3AdccK9JpCb7LfStaDGlAmDLukOlY4=
github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc=
github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc h1:8WFBn63wegobsYAX0YjD+8suexZDga5CctH4CCTx2+8=
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gaissmai/bart v0.26.1 h1:+w4rnLGNlA2GDVn382Tfe3jOsK5vOr5n4KmigJ9lbTo=
github.com/gaissmai/bart v0.26.1/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 h1:vymEbVwYFP/L05h5TKQxvkXoKxNvTpjxYKdF1Nlwuao=
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME=
github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/go4org/hashtriemap v0.0.0-20251130024219-545ba229f689 h1:0psnKZ+N2IP43/SZC8SKx6OpFJwLmQb9m9QyV9BC2f8=
github.com/go4org/hashtriemap v0.0.0-20251130024219-545ba229f689/go.mod h1:OGmRfY/9QEK2P5zCRtmqfbCF283xPkU2dvVA4MvbvpI=
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo=
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737/go.mod h1:MIS0jDzbU/vuM9MC4YnBITCv+RYuTRq8dJzmCrFsK9g=
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.4 h1:awZRf9FwOeTunQmHoDYSHJps3ie6f1UlhS1fOdPEt1I=
github.com/google/go-tpm v0.9.4/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk=
github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U=
github.com/insomniacslk/dhcp v0.0.0-20240129002554-15c9b8791914 h1:kD8PseueGeYiid/Mmcv17Q0Qqicc4F46jcX22L/e/Hs=
github.com/insomniacslk/dhcp v0.0.0-20240129002554-15c9b8791914/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
github.com/irlndts/go-discogs v0.3.6 h1:3oIJEkLGQ1ffJcoo6wvtawPI4/SyHoRpnu25Y51U4wg=
github.com/irlndts/go-discogs v0.3.6/go.mod h1:UVQ05FdCzH4P/usnSxQDh77QYE37HvmPnSCgogioljo=
github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g=
github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jsimonetti/rtnetlink v1.4.1 h1:JfD4jthWBqZMEffc5RjgmlzpYttAVw1sdnmiNaPO3hE=
github.com/jsimonetti/rtnetlink v1.4.1/go.mod h1:xJjT7t59UIZ62GLZbv6PLLo8VFrostJMPBAheR6OM8w=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o=
github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ3c=
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.69.0 h1:OA85nJQS/T/MaYh/Q2CcgDKSGWqNIgrBDvDH85CuiNk=
github.com/prometheus/common v0.69.0/go.mod h1:ZzL3f6u94qUxh9p+tJTrF+FvBS1XXbbRAZCQkytAL0Y=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
github.com/studio-b12/gowebdav v0.13.0 h1:OcwSg6IQHOFNdYHn3bPOHwSE8looG8N56Y5xTT1asqQ=
github.com/studio-b12/gowebdav v0.13.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d h1:JcGKBZAL7ePLwOhUdN8qGQZlP5GueEiIZwY7R62pejE=
github.com/tailscale/certstore v0.1.1-0.20260409135935-3638fb84b77d/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
github.com/tailscale/gliderssh v0.3.4-0.20260716005906-1a0f895faf28 h1:Azz5ILxxVsHN/KjIu3wkJPAmmtiijucZw4Ax5Ye8n+s=
github.com/tailscale/gliderssh v0.3.4-0.20260716005906-1a0f895faf28/go.mod h1:wn16Km1EZOX4UEAyaZa3dBwfFGOJ7neck40NcwosJUw=
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
github.com/tailscale/golang-x-crypto v0.0.0-20260720153645-2ba0bf7866ed h1:uyvHhX1FQada0vVk8CSHa4tJT96EEAkTypaYz8Tq5Nc=
github.com/tailscale/golang-x-crypto v0.0.0-20260720153645-2ba0bf7866ed/go.mod h1:NC3xRCu4UR+m4n6ix8b6oLLbHa820Y0StbOQEdWTDo0=
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd h1:Rf9uhF1+VJ7ZHqxrG8pJ6YacmHvVCmByDmGbAWCc/gA=
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo=
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 h1:uFsXVBE9Qr4ZoF094vE6iYTLDl0qCiKzYXlL6UeWObU=
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7/go.mod h1:NzVQi3Mleb+qzq8VmcWpSkcSYxXIg0DkI6XDzpVkhJ0=
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+yfntqhI3oAu9i27nEojcQ4NuBQOo5ZFA=
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc=
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14=
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
github.com/tailscale/wireguard-go v0.0.0-20260715223240-2e01ba5b00f0 h1:CnIEL2n7Xql6Ux1k+Vu5S5ubDHCT/kxFgkKCY8FjefU=
github.com/tailscale/wireguard-go v0.0.0-20260715223240-2e01ba5b00f0/go.mod h1:6SerzcvHWQchKO2BfNdmquA77CHSECZuFl+D9fp4RnI=
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek=
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
github.com/u-root/u-root v0.14.0 h1:Ka4T10EEML7dQ5XDvO9c3MBN8z4nuSnGjcd1jmU2ivg=
github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1WMluqE=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=
go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1 h1:EBHQuS9qI8xJ96+YRgVV2ahFLUYbWpt1rf3wPfXN2wQ=
golang.org/x/crypto/x509roots/fallback v0.0.0-20260113154411-7d0074ccc6f1/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18=
golang.org/x/crypto/x509roots/fallback v0.0.0-20260902180247-86efde54dc70 h1:VwViOGcd7C8/Gs18efVlMxvUCS0un6KRKBPFEBvpUXg=
golang.org/x/crypto/x509roots/fallback v0.0.0-20260902180247-86efde54dc70/go.mod h1:HPze8vhfG6fO06AM+VSvxRm4E3+5Yk375mgrJ5M2z1E=
golang.org/x/crypto/x509roots/fallback v0.0.0-20260921070245-7a4a4d6beae2 h1:eZbx26MSkMP0nFHSVwZplOsFCdjlfBz822hYXljWbUo=
golang.org/x/crypto/x509roots/fallback v0.0.0-20260921070245-7a4a4d6beae2/go.mod h1:HPze8vhfG6fO06AM+VSvxRm4E3+5Yk375mgrJ5M2z1E=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8=
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/image v0.41.0 h1:8wS72eGJMJaBxK6okTzd4WaXumUlTVlb753MlsSvTCo=
golang.org/x/image v0.41.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20260522210424-ecfc5a8d5446 h1:cqHQ3AycTHvM2R7ikgyX57D+XvtcSnGylsLkOVhta/w=
golang.zx2c4.com/wireguard v0.0.0-20260522210424-ecfc5a8d5446/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 h1:Zy8IV/+FMLxy6j6p87vk/vQGKcdnbprwjTxc8UiUtsA=
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU=
honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
tailscale.com v1.102.3 h1:M1czCAtMuIcg+2Z+FBPbJyAk3ZEQGEFKnvHthtE1c6M=
tailscale.com v1.102.3/go.mod h1:47bv91Xbg4K1p5wti7F1dmKvUVWV5BXF78d9EWJ+d6c=
tailscale.com v1.102.4 h1:FcAkb7MgfFFUIUASg18Mv5cAiraxb+eMgOQaOKqKyPo=
tailscale.com v1.102.4/go.mod h1:47bv91Xbg4K1p5wti7F1dmKvUVWV5BXF78d9EWJ+d6c=
+64
View File
@@ -0,0 +1,64 @@
package importer
import (
"encoding/csv"
"io"
"strings"
"git.yetaga.in/alazyreader/library/media"
)
func CSVToBooks(r io.Reader) ([]media.Book, error) {
reader := csv.NewReader(r)
header, err := reader.Read()
if err != nil {
return nil, err
}
hmap := parseHeader(header)
books := []media.Book{}
for {
row, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
return books, err
}
b := media.Book{
Title: row[hmap["title"]],
Authors: parseAuthors(row[hmap["author"]]),
SortAuthor: row[hmap["authorlast"]],
ISBN10: row[hmap["isbn-10"]],
ISBN13: row[hmap["isbn-13"]],
Format: row[hmap["format"]],
Genre: row[hmap["genre"]],
Publisher: row[hmap["publisher"]],
Series: row[hmap["series"]],
Volume: row[hmap["volume"]],
Year: row[hmap["year"]],
Signed: row[hmap["signed"]] == "yes", // convert from known string to bool
Description: row[hmap["description"]],
Notes: row[hmap["notes"]],
CoverURL: row[hmap["coverurl"]],
}
books = append(books, b)
}
return books, nil
}
func parseHeader(header []string) map[string]int {
m := make(map[string]int, len(header)-1)
for i := range header {
m[strings.TrimSpace(strings.ToLower(header[i]))] = i
}
return m
}
func parseAuthors(a string) []string {
as := strings.Split(a, ";")
for i := range as {
as[i] = strings.TrimSpace(as[i])
}
return as
}
-188
View File
@@ -1,188 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Library</title>
<script type="text/javascript" src='js/jquery.js'></script>
<script type="text/javascript" src='js/mustache.js'></script>
<script type="text/javascript" src='js/lodash.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/tabletop.js/1.5.1/tabletop.min.js'></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"></link>
<link rel="stylesheet" href="css/reset.css"></link>
<link rel="stylesheet" href="css/style.css"></link>
<link rel="icon" href="favicon.ico" type="image/x-icon">
<link href="https://fonts.googleapis.com/css?family=Libre+Baskerville:400,700" rel="stylesheet">
<script type='text/javascript'>
var publicSpreadsheetUrl = 'https://docs.google.com/spreadsheets/d/1w5Dc57wV0_rrKFsG7KM-qdPWEpqYk6lFu3JzAA0cSv0/pubhtml';
var sortState = {
sortBy: 'authorLast',
sortOrder: 'asc'
};
function init() {
Tabletop.init({
key: publicSpreadsheetUrl,
callback: showInfo,
simpleSheet: true
});
}
function showInfo(data, tabletop) {
$("#reloadLink").unbind('click');
$("#reloadLink").on("click", function() { init(); });
$("#search").unbind('input');
$("#search").on("input", function(e) { search(data, e.target.value); });
$.each(data, function(key, value) {
value.sortTitle = titleCleaner(value.title);
if (!value['isbn-10'] && value['isbn-13']) {
value['isbn-10'] = generateISBNfromEAN(value['isbn-13']);
}
if (!value.coverurl && value['isbn-10']) {
value.coverurl = generateAmazonCoverUrl(value['isbn-10']);
}
});
renderTable(data);
}
function search(data, searchString) {
searchBy = searchString.toLowerCase().replace('"', '').replace(':', '').replace("'", '').replace(" ", '');
relevantFields = ['title', 'author', 'genre', 'publisher', 'series', 'year'];
if (!searchString) {
renderTable(data);
return false;
}
renderTable(_.filter(data, function(book) {
return _.find(_.pick(book, relevantFields), function(field) {
return field.toLowerCase().replace('"', '').replace(':', '').replace("'", '').replace(" ", '').indexOf(searchBy) !== -1;
});
}));
}
function renderTable(data, sortField) {
if (sortField) {
if (sortState.sortBy === sortField) {
sortState.sortOrder = (sortState.sortOrder === 'asc') ? 'desc' : 'asc'; // swap if we're looping
} else {
sortState.sortOrder = 'asc'; // reset if we've changed columns
}
sortState.sortBy = sortField;
}
data = _.orderBy(data, function(o) {
return o[sortState.sortBy].toLowerCase() + o["sortTitle"].toLowerCase();
}, sortState.sortOrder);
$.each(data, function(key, value) {
value.rowNumber = key; // re-key for new sort
});
var template = $('#Table').html();
var rendered = Mustache.render(template, {books: data});
$('#books').html(rendered);
$("#books tbody tr").not(':first').on("click", function() {
updateCurrentBook(data[$(this)[0].id]); // ignore the headers
});
$("#books tbody tr th[data-sort-by]").on("click", function() {
renderTable(data, $(this).data('sortBy')); // only add callback when there's a sortBy attribute
});
$("#books tbody tr th[data-sort-by=" + sortState.sortBy + ']').addClass(sortState.sortOrder);
}
function updateCurrentBook(book) {
var template = $('#View').html();
var rendered = Mustache.render(template, {book: book});
$('#current').html(rendered);
}
function titleCleaner(title) {
return title
.replace('"', '')
.replace(':', '')
.replace(/^(An?|The)\s/i, '');
}
function generateAmazonCoverUrl(ISBN) {
return "https://images-na.ssl-images-amazon.com/images/P/" + ISBN + ".01.LZZ.jpg"
}
function generateISBNfromEAN(EAN) {
ISBN = EAN.slice(3,12);
var checkdigit = ((11 - (_.reduce(ISBN.split(''), function(sum, num, key) {
return sum + (num * (10 - key));
}, 0) % 11)) % 11);
return ISBN + (checkdigit == 10 ? 'X' : checkdigit);
}
window.addEventListener('DOMContentLoaded', init);
</script>
</head>
<body>
<div class="wrapper">
<div id="header">
<h1>Library</h1>
<a target="_blank" href="https://docs.google.com/spreadsheets/d/1w5Dc57wV0_rrKFsG7KM-qdPWEpqYk6lFu3JzAA0cSv0/edit">spreadsheet</a>
<a target="_blank" href="https://git.yetaga.in/alazyreader/library">git</a>
<a id="reloadLink" href="#">reload</a>
<div id="searchBox">
<input id="search" type="text" name="search" placeholder="Search..."/>
</div>
</div>
<div id="current">No Book Selected</div>
<div id="books"></div> <!-- Table goes here -->
</div>
<script id="Table" type="text/html">
<table class="bookTable">
<tr>
<th data-sort-by="sortTitle" class="tHeader title">Title</th>
<th data-sort-by="authorLast" class="tHeader author">Author</th>
<th data-sort-by="publisher" class="tHeader publisher">Publisher</th>
<th data-sort-by="year" class="tHeader year">Year</th>
<th class="tHeader isbn">ISBN</th>
</tr>
{{#books}}
<tr class="tRow {{#onLoan}}onLoan{{/onLoan}}" id="{{rowNumber}}">
<td class="title">{{title}}
{{#signed}}<span class="signed" title="Signed by the author"></span>{{/signed}}</td>
<td class="author">{{author}}</td>
<td class="publisher">{{publisher}}</td>
<td class="year">{{year}}</td>
<td class="isbn">{{isbn-13}}</td>
</tr>
{{/books}}
</table>
</script>
<script id="View" type="text/html">
{{#book}}
{{#coverurl}}
<img src="{{coverurl}}"/>
{{/coverurl}}
<h1 {{#onLoan}}class="onLoan" {{/onLoan}}>{{title}}</h1>
<h2>{{author}}</h2>
<span>{{isbn-13}}</span><br/>
<span>{{publisher}}, {{year}}</span><br/>
{{#series}}
<span>{{series}}{{#volume}}, Volume {{volume}}</span>{{/volume}}<br/>
{{/series}}
{{#signed}}
<span>Signed by the author </span><br/>
{{/signed}}
<span>{{format}}</span>
{{#onLoan}}
<h2 class="onLoan">On loan to {{onLoan}}</h2>
{{/onLoan}}
<div class="description">
<p>{{description}}</p>
{{#notes}}
<span>Notes:</span>
<p>{{notes}}</p>
{{/notes}}
</div>
{{/book}}
</script>
</body>
</html>
-174
View File
@@ -1,174 +0,0 @@
var fs = require('fs');
var readline = require('readline');
var google = require('googleapis');
var googleAuth = require('google-auth-library');
var books = require('google-books-search');
var _ = require('lodash');
var SCOPES = ['https://www.googleapis.com/auth/spreadsheets'];
var TOKEN_DIR = '.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'sheets.googleapis.com-my-library.json';
var SHEET_ID = '1w5Dc57wV0_rrKFsG7KM-qdPWEpqYk6lFu3JzAA0cSv0';
var SHEET_NAME = 'Sheet1';
// Load client secrets from a local file.
fs.readFile(TOKEN_DIR + 'client_secret.json', function processClientSecrets(err, content) {
if (err) {
console.log('Error loading client secret file: ' + err);
return;
}
// Authorize a client with the loaded credentials, then call the
// Google Sheets API.
authorize(JSON.parse(content), inputLoop);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
*
* @param {Object} credentials The authorization client credentials.
* @param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
var clientSecret = credentials.installed.client_secret;
var clientId = credentials.installed.client_id;
var redirectUrl = credentials.installed.redirect_uris[0];
var auth = new googleAuth();
var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function(err, token) {
if (err) {
getNewToken(oauth2Client, callback);
} else {
oauth2Client.credentials = JSON.parse(token);
callback(oauth2Client);
}
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
*
* @param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
* @param {getEventsCallback} callback The callback to call with the authorized
* client.
*/
function getNewToken(oauth2Client, callback) {
var authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
console.log('Authorize this app by visiting this url: ', authUrl);
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Enter the code from that page here: ', function(code) {
rl.close();
oauth2Client.getToken(code, function(err, token) {
if (err) {
console.log('Error while trying to retrieve access token', err);
return;
}
oauth2Client.credentials = token;
storeToken(token);
callback(oauth2Client);
});
});
}
/**
* Store token to disk be used in later program executions.
*
* @param {Object} token The token to store to disk.
*/
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code != 'EEXIST') {
throw err;
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token));
console.log('Token stored to ' + TOKEN_PATH);
}
function inputLoop(auth) {
insertRow(auth);
}
function insertRow(auth) {
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('ISBN: ', function(isbn) {
rl.close();
books.search(isbn, {
field: 'isbn',
lang: 'en'
}, function(error, results) {
if ( ! error && results.length > 0 ) {
console.log(results[0]);
var book = normalizeGoogleData(results[0]);
console.log(book);
appendToSheet(auth, _.values(book));
} else if ( results.length == 0 ) {
console.log("no book found");
inputLoop(auth);
} else {
console.log(error);
}
});
});
}
function normalizeGoogleData(book) {
return {
title: book.subtitle ? book.title + ': ' + book.subtitle : book.title,
author: _.join(book.authors, ', '),
authorLast: book.authors ? _.lowerCase(_.head(_.reverse(_.split(book.authors[0], ' ')))) : '',
"isbn-10": _.find(book.industryIdentifiers, { type: "ISBN_10" })
? _.find(book.industryIdentifiers, { type: "ISBN_10" }).identifier
: '',
"isbn-13": _.find(book.industryIdentifiers, { type: "ISBN_13" })
? _.find(book.industryIdentifiers, { type: "ISBN_13" }).identifier
: '',
format: '',
genre: book.categories ? book.categories[0] : '',
publisher: _.trim(book.publisher, '"'),
series: '',
volume: '',
publishedDate: book.publishedDate ? book.publishedDate.substring(0, 4) : '',
coverurl: '',
description: book.description,
notes: '',
signed: ''
};
}
function appendToSheet(auth, book) {
var sheets = google.sheets('v4');
sheets.spreadsheets.values.append({
auth: auth,
spreadsheetId: SHEET_ID,
range: SHEET_NAME,
valueInputOption: "RAW",
insertDataOption: "INSERT_ROWS",
resource: {
"range": SHEET_NAME,
"majorDimension": "ROWS",
"values": [
book
]
}
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
}
inputLoop(auth);
});
}
-4
View File
File diff suppressed because one or more lines are too long
-136
View File
@@ -1,136 +0,0 @@
/**
* @license
* Lodash lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
*/
;(function(){function n(n,t){return n.set(t[0],t[1]),n}function t(n,t){return n.add(t),n}function r(n,t,r){switch(r.length){case 0:return n.call(t);case 1:return n.call(t,r[0]);case 2:return n.call(t,r[0],r[1]);case 3:return n.call(t,r[0],r[1],r[2])}return n.apply(t,r)}function e(n,t,r,e){for(var u=-1,i=null==n?0:n.length;++u<i;){var o=n[u];t(e,o,r(o),n)}return e}function u(n,t){for(var r=-1,e=null==n?0:n.length;++r<e&&false!==t(n[r],r,n););return n}function i(n,t){for(var r=null==n?0:n.length;r--&&false!==t(n[r],r,n););
return n}function o(n,t){for(var r=-1,e=null==n?0:n.length;++r<e;)if(!t(n[r],r,n))return false;return true}function f(n,t){for(var r=-1,e=null==n?0:n.length,u=0,i=[];++r<e;){var o=n[r];t(o,r,n)&&(i[u++]=o)}return i}function c(n,t){return!(null==n||!n.length)&&-1<d(n,t,0)}function a(n,t,r){for(var e=-1,u=null==n?0:n.length;++e<u;)if(r(t,n[e]))return true;return false}function l(n,t){for(var r=-1,e=null==n?0:n.length,u=Array(e);++r<e;)u[r]=t(n[r],r,n);return u}function s(n,t){for(var r=-1,e=t.length,u=n.length;++r<e;)n[u+r]=t[r];
return n}function h(n,t,r,e){var u=-1,i=null==n?0:n.length;for(e&&i&&(r=n[++u]);++u<i;)r=t(r,n[u],u,n);return r}function p(n,t,r,e){var u=null==n?0:n.length;for(e&&u&&(r=n[--u]);u--;)r=t(r,n[u],u,n);return r}function _(n,t){for(var r=-1,e=null==n?0:n.length;++r<e;)if(t(n[r],r,n))return true;return false}function v(n,t,r){var e;return r(n,function(n,r,u){if(t(n,r,u))return e=r,false}),e}function g(n,t,r,e){var u=n.length;for(r+=e?1:-1;e?r--:++r<u;)if(t(n[r],r,n))return r;return-1}function d(n,t,r){if(t===t)n:{
--r;for(var e=n.length;++r<e;)if(n[r]===t){n=r;break n}n=-1}else n=g(n,b,r);return n}function y(n,t,r,e){--r;for(var u=n.length;++r<u;)if(e(n[r],t))return r;return-1}function b(n){return n!==n}function x(n,t){var r=null==n?0:n.length;return r?k(n,t)/r:P}function j(n){return function(t){return null==t?F:t[n]}}function w(n){return function(t){return null==n?F:n[t]}}function m(n,t,r,e,u){return u(n,function(n,u,i){r=e?(e=false,n):t(r,n,u,i)}),r}function A(n,t){var r=n.length;for(n.sort(t);r--;)n[r]=n[r].c;
return n}function k(n,t){for(var r,e=-1,u=n.length;++e<u;){var i=t(n[e]);i!==F&&(r=r===F?i:r+i)}return r}function E(n,t){for(var r=-1,e=Array(n);++r<n;)e[r]=t(r);return e}function O(n,t){return l(t,function(t){return[t,n[t]]})}function S(n){return function(t){return n(t)}}function I(n,t){return l(t,function(t){return n[t]})}function R(n,t){return n.has(t)}function z(n,t){for(var r=-1,e=n.length;++r<e&&-1<d(t,n[r],0););return r}function W(n,t){for(var r=n.length;r--&&-1<d(t,n[r],0););return r}function B(n){
return"\\"+Tn[n]}function L(n){var t=-1,r=Array(n.size);return n.forEach(function(n,e){r[++t]=[e,n]}),r}function U(n,t){return function(r){return n(t(r))}}function C(n,t){for(var r=-1,e=n.length,u=0,i=[];++r<e;){var o=n[r];o!==t&&"__lodash_placeholder__"!==o||(n[r]="__lodash_placeholder__",i[u++]=r)}return i}function D(n){var t=-1,r=Array(n.size);return n.forEach(function(n){r[++t]=n}),r}function M(n){var t=-1,r=Array(n.size);return n.forEach(function(n){r[++t]=[n,n]}),r}function T(n){if(Bn.test(n)){
for(var t=zn.lastIndex=0;zn.test(n);)++t;n=t}else n=tt(n);return n}function $(n){return Bn.test(n)?n.match(zn)||[]:n.split("")}var F,N=1/0,P=NaN,Z=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]],q=/\b__p\+='';/g,V=/\b(__p\+=)''\+/g,K=/(__e\(.*?\)|\b__t\))\+'';/g,G=/&(?:amp|lt|gt|quot|#39);/g,H=/[&<>"']/g,J=RegExp(G.source),Y=RegExp(H.source),Q=/<%-([\s\S]+?)%>/g,X=/<%([\s\S]+?)%>/g,nn=/<%=([\s\S]+?)%>/g,tn=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,rn=/^\w*$/,en=/^\./,un=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,on=/[\\^$.*+?()[\]{}|]/g,fn=RegExp(on.source),cn=/^\s+|\s+$/g,an=/^\s+/,ln=/\s+$/,sn=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,hn=/\{\n\/\* \[wrapped with (.+)\] \*/,pn=/,? & /,_n=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,vn=/\\(\\)?/g,gn=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,dn=/\w*$/,yn=/^[-+]0x[0-9a-f]+$/i,bn=/^0b[01]+$/i,xn=/^\[object .+?Constructor\]$/,jn=/^0o[0-7]+$/i,wn=/^(?:0|[1-9]\d*)$/,mn=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,An=/($^)/,kn=/['\n\r\u2028\u2029\\]/g,En="[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?(?:\\u200d(?:[^\\ud800-\\udfff]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?)*",On="(?:[\\u2700-\\u27bf]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])"+En,Sn="(?:[^\\ud800-\\udfff][\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]?|[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff]|[\\ud800-\\udfff])",In=RegExp("['\u2019]","g"),Rn=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g"),zn=RegExp("\\ud83c[\\udffb-\\udfff](?=\\ud83c[\\udffb-\\udfff])|"+Sn+En,"g"),Wn=RegExp(["[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+(?:['\u2019](?:d|ll|m|re|s|t|ve))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde]|$)|(?:[A-Z\\xc0-\\xd6\\xd8-\\xde]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde](?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])|$)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?(?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:d|ll|m|re|s|t|ve))?|[A-Z\\xc0-\\xd6\\xd8-\\xde]+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?|\\d*(?:(?:1ST|2ND|3RD|(?![123])\\dTH)\\b)|\\d*(?:(?:1st|2nd|3rd|(?![123])\\dth)\\b)|\\d+",On].join("|"),"g"),Bn=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]"),Ln=/[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,Un="Array Buffer DataView Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Map Math Object Promise RegExp Set String Symbol TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap _ clearTimeout isFinite parseInt setTimeout".split(" "),Cn={};
Cn["[object Float32Array]"]=Cn["[object Float64Array]"]=Cn["[object Int8Array]"]=Cn["[object Int16Array]"]=Cn["[object Int32Array]"]=Cn["[object Uint8Array]"]=Cn["[object Uint8ClampedArray]"]=Cn["[object Uint16Array]"]=Cn["[object Uint32Array]"]=true,Cn["[object Arguments]"]=Cn["[object Array]"]=Cn["[object ArrayBuffer]"]=Cn["[object Boolean]"]=Cn["[object DataView]"]=Cn["[object Date]"]=Cn["[object Error]"]=Cn["[object Function]"]=Cn["[object Map]"]=Cn["[object Number]"]=Cn["[object Object]"]=Cn["[object RegExp]"]=Cn["[object Set]"]=Cn["[object String]"]=Cn["[object WeakMap]"]=false;
var Dn={};Dn["[object Arguments]"]=Dn["[object Array]"]=Dn["[object ArrayBuffer]"]=Dn["[object DataView]"]=Dn["[object Boolean]"]=Dn["[object Date]"]=Dn["[object Float32Array]"]=Dn["[object Float64Array]"]=Dn["[object Int8Array]"]=Dn["[object Int16Array]"]=Dn["[object Int32Array]"]=Dn["[object Map]"]=Dn["[object Number]"]=Dn["[object Object]"]=Dn["[object RegExp]"]=Dn["[object Set]"]=Dn["[object String]"]=Dn["[object Symbol]"]=Dn["[object Uint8Array]"]=Dn["[object Uint8ClampedArray]"]=Dn["[object Uint16Array]"]=Dn["[object Uint32Array]"]=true,
Dn["[object Error]"]=Dn["[object Function]"]=Dn["[object WeakMap]"]=false;var Mn,Tn={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},$n=parseFloat,Fn=parseInt,Nn=typeof global=="object"&&global&&global.Object===Object&&global,Pn=typeof self=="object"&&self&&self.Object===Object&&self,Zn=Nn||Pn||Function("return this")(),qn=typeof exports=="object"&&exports&&!exports.nodeType&&exports,Vn=qn&&typeof module=="object"&&module&&!module.nodeType&&module,Kn=Vn&&Vn.exports===qn,Gn=Kn&&Nn.process;
n:{try{Mn=Gn&&Gn.binding&&Gn.binding("util");break n}catch(n){}Mn=void 0}var Hn=Mn&&Mn.isArrayBuffer,Jn=Mn&&Mn.isDate,Yn=Mn&&Mn.isMap,Qn=Mn&&Mn.isRegExp,Xn=Mn&&Mn.isSet,nt=Mn&&Mn.isTypedArray,tt=j("length"),rt=w({"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I",
"\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C",
"\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i",
"\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S",
"\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe",
"\u0149":"'n","\u017f":"s"}),et=w({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}),ut=w({"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'"}),it=function w(En){function On(n){if(xu(n)&&!af(n)&&!(n instanceof Mn)){if(n instanceof zn)return n;if(ci.call(n,"__wrapped__"))return Pe(n)}return new zn(n)}function Sn(){}function zn(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=F}function Mn(n){this.__wrapped__=n,this.__actions__=[],this.__dir__=1,
this.__filtered__=false,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}function Tn(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function Nn(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function Pn(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function qn(n){var t=-1,r=null==n?0:n.length;for(this.__data__=new Pn;++t<r;)this.add(n[t])}function Vn(n){
this.size=(this.__data__=new Nn(n)).size}function Gn(n,t){var r,e=af(n),u=!e&&cf(n),i=!e&&!u&&sf(n),o=!e&&!u&&!i&&gf(n),u=(e=e||u||i||o)?E(n.length,ri):[],f=u.length;for(r in n)!t&&!ci.call(n,r)||e&&("length"==r||i&&("offset"==r||"parent"==r)||o&&("buffer"==r||"byteLength"==r||"byteOffset"==r)||Re(r,f))||u.push(r);return u}function tt(n){var t=n.length;return t?n[cr(0,t-1)]:F}function ot(n,t){return Te(Mr(n),gt(t,0,n.length))}function ft(n){return Te(Mr(n))}function ct(n,t,r){(r===F||hu(n[t],r))&&(r!==F||t in n)||_t(n,t,r);
}function at(n,t,r){var e=n[t];ci.call(n,t)&&hu(e,r)&&(r!==F||t in n)||_t(n,t,r)}function lt(n,t){for(var r=n.length;r--;)if(hu(n[r][0],t))return r;return-1}function st(n,t,r,e){return oo(n,function(n,u,i){t(e,n,r(n),i)}),e}function ht(n,t){return n&&Tr(t,Lu(t),n)}function pt(n,t){return n&&Tr(t,Uu(t),n)}function _t(n,t,r){"__proto__"==t&&Ei?Ei(n,t,{configurable:true,enumerable:true,value:r,writable:true}):n[t]=r}function vt(n,t){for(var r=-1,e=t.length,u=Hu(e),i=null==n;++r<e;)u[r]=i?F:Wu(n,t[r]);return u;
}function gt(n,t,r){return n===n&&(r!==F&&(n=n<=r?n:r),t!==F&&(n=n>=t?n:t)),n}function dt(n,t,r,e,i,o){var f,c=1&t,a=2&t,l=4&t;if(r&&(f=i?r(n,e,i,o):r(n)),f!==F)return f;if(!bu(n))return n;if(e=af(n)){if(f=Ee(n),!c)return Mr(n,f)}else{var s=yo(n),h="[object Function]"==s||"[object GeneratorFunction]"==s;if(sf(n))return Wr(n,c);if("[object Object]"==s||"[object Arguments]"==s||h&&!i){if(f=a||h?{}:Oe(n),!c)return a?Fr(n,pt(f,n)):$r(n,ht(f,n))}else{if(!Dn[s])return i?n:{};f=Se(n,s,dt,c)}}if(o||(o=new Vn),
i=o.get(n))return i;o.set(n,f);var a=l?a?ye:de:a?Uu:Lu,p=e?F:a(n);return u(p||n,function(e,u){p&&(u=e,e=n[u]),at(f,u,dt(e,t,r,u,n,o))}),f}function yt(n){var t=Lu(n);return function(r){return bt(r,n,t)}}function bt(n,t,r){var e=r.length;if(null==n)return!e;for(n=ni(n);e--;){var u=r[e],i=t[u],o=n[u];if(o===F&&!(u in n)||!i(o))return false}return true}function xt(n,t,r){if(typeof n!="function")throw new ei("Expected a function");return jo(function(){n.apply(F,r)},t)}function jt(n,t,r,e){var u=-1,i=c,o=true,f=n.length,s=[],h=t.length;
if(!f)return s;r&&(t=l(t,S(r))),e?(i=a,o=false):200<=t.length&&(i=R,o=false,t=new qn(t));n:for(;++u<f;){var p=n[u],_=null==r?p:r(p),p=e||0!==p?p:0;if(o&&_===_){for(var v=h;v--;)if(t[v]===_)continue n;s.push(p)}else i(t,_,e)||s.push(p)}return s}function wt(n,t){var r=true;return oo(n,function(n,e,u){return r=!!t(n,e,u)}),r}function mt(n,t,r){for(var e=-1,u=n.length;++e<u;){var i=n[e],o=t(i);if(null!=o&&(f===F?o===o&&!Au(o):r(o,f)))var f=o,c=i}return c}function At(n,t){var r=[];return oo(n,function(n,e,u){
t(n,e,u)&&r.push(n)}),r}function kt(n,t,r,e,u){var i=-1,o=n.length;for(r||(r=Ie),u||(u=[]);++i<o;){var f=n[i];0<t&&r(f)?1<t?kt(f,t-1,r,e,u):s(u,f):e||(u[u.length]=f)}return u}function Et(n,t){return n&&co(n,t,Lu)}function Ot(n,t){return n&&ao(n,t,Lu)}function St(n,t){return f(t,function(t){return gu(n[t])})}function It(n,t){t=Rr(t,n);for(var r=0,e=t.length;null!=n&&r<e;)n=n[$e(t[r++])];return r&&r==e?n:F}function Rt(n,t,r){return t=t(n),af(n)?t:s(t,r(n))}function zt(n){if(null==n)n=n===F?"[object Undefined]":"[object Null]";else if(ki&&ki in ni(n)){
var t=ci.call(n,ki),r=n[ki];try{n[ki]=F;var e=true}catch(n){}var u=si.call(n);e&&(t?n[ki]=r:delete n[ki]),n=u}else n=si.call(n);return n}function Wt(n,t){return n>t}function Bt(n,t){return null!=n&&ci.call(n,t)}function Lt(n,t){return null!=n&&t in ni(n)}function Ut(n,t,r){for(var e=r?a:c,u=n[0].length,i=n.length,o=i,f=Hu(i),s=1/0,h=[];o--;){var p=n[o];o&&t&&(p=l(p,S(t))),s=Mi(p.length,s),f[o]=!r&&(t||120<=u&&120<=p.length)?new qn(o&&p):F}var p=n[0],_=-1,v=f[0];n:for(;++_<u&&h.length<s;){var g=p[_],d=t?t(g):g,g=r||0!==g?g:0;
if(v?!R(v,d):!e(h,d,r)){for(o=i;--o;){var y=f[o];if(y?!R(y,d):!e(n[o],d,r))continue n}v&&v.push(d),h.push(g)}}return h}function Ct(n,t,r){var e={};return Et(n,function(n,u,i){t(e,r(n),u,i)}),e}function Dt(n,t,e){return t=Rr(t,n),n=2>t.length?n:It(n,vr(t,0,-1)),t=null==n?n:n[$e(Ge(t))],null==t?F:r(t,n,e)}function Mt(n){return xu(n)&&"[object Arguments]"==zt(n)}function Tt(n){return xu(n)&&"[object ArrayBuffer]"==zt(n)}function $t(n){return xu(n)&&"[object Date]"==zt(n)}function Ft(n,t,r,e,u){if(n===t)t=true;else if(null==n||null==t||!xu(n)&&!xu(t))t=n!==n&&t!==t;else n:{
var i=af(n),o=af(t),f=i?"[object Array]":yo(n),c=o?"[object Array]":yo(t),f="[object Arguments]"==f?"[object Object]":f,c="[object Arguments]"==c?"[object Object]":c,a="[object Object]"==f,o="[object Object]"==c;if((c=f==c)&&sf(n)){if(!sf(t)){t=false;break n}i=true,a=false}if(c&&!a)u||(u=new Vn),t=i||gf(n)?_e(n,t,r,e,Ft,u):ve(n,t,f,r,e,Ft,u);else{if(!(1&r)&&(i=a&&ci.call(n,"__wrapped__"),f=o&&ci.call(t,"__wrapped__"),i||f)){n=i?n.value():n,t=f?t.value():t,u||(u=new Vn),t=Ft(n,t,r,e,u);break n}if(c)t:if(u||(u=new Vn),
i=1&r,f=de(n),o=f.length,c=de(t).length,o==c||i){for(a=o;a--;){var l=f[a];if(!(i?l in t:ci.call(t,l))){t=false;break t}}if((c=u.get(n))&&u.get(t))t=c==t;else{c=true,u.set(n,t),u.set(t,n);for(var s=i;++a<o;){var l=f[a],h=n[l],p=t[l];if(e)var _=i?e(p,h,l,t,n,u):e(h,p,l,n,t,u);if(_===F?h!==p&&!Ft(h,p,r,e,u):!_){c=false;break}s||(s="constructor"==l)}c&&!s&&(r=n.constructor,e=t.constructor,r!=e&&"constructor"in n&&"constructor"in t&&!(typeof r=="function"&&r instanceof r&&typeof e=="function"&&e instanceof e)&&(c=false)),
u.delete(n),u.delete(t),t=c}}else t=false;else t=false}}return t}function Nt(n){return xu(n)&&"[object Map]"==yo(n)}function Pt(n,t,r,e){var u=r.length,i=u,o=!e;if(null==n)return!i;for(n=ni(n);u--;){var f=r[u];if(o&&f[2]?f[1]!==n[f[0]]:!(f[0]in n))return false}for(;++u<i;){var f=r[u],c=f[0],a=n[c],l=f[1];if(o&&f[2]){if(a===F&&!(c in n))return false}else{if(f=new Vn,e)var s=e(a,l,c,n,t,f);if(s===F?!Ft(l,a,3,e,f):!s)return false}}return true}function Zt(n){return!(!bu(n)||li&&li in n)&&(gu(n)?_i:xn).test(Fe(n))}function qt(n){
return xu(n)&&"[object RegExp]"==zt(n)}function Vt(n){return xu(n)&&"[object Set]"==yo(n)}function Kt(n){return xu(n)&&yu(n.length)&&!!Cn[zt(n)]}function Gt(n){return typeof n=="function"?n:null==n?Nu:typeof n=="object"?af(n)?Xt(n[0],n[1]):Qt(n):Vu(n)}function Ht(n){if(!Le(n))return Ci(n);var t,r=[];for(t in ni(n))ci.call(n,t)&&"constructor"!=t&&r.push(t);return r}function Jt(n,t){return n<t}function Yt(n,t){var r=-1,e=pu(n)?Hu(n.length):[];return oo(n,function(n,u,i){e[++r]=t(n,u,i)}),e}function Qt(n){
var t=me(n);return 1==t.length&&t[0][2]?Ue(t[0][0],t[0][1]):function(r){return r===n||Pt(r,n,t)}}function Xt(n,t){return We(n)&&t===t&&!bu(t)?Ue($e(n),t):function(r){var e=Wu(r,n);return e===F&&e===t?Bu(r,n):Ft(t,e,3)}}function nr(n,t,r,e,u){n!==t&&co(t,function(i,o){if(bu(i)){u||(u=new Vn);var f=u,c=n[o],a=t[o],l=f.get(a);if(l)ct(n,o,l);else{var l=e?e(c,a,o+"",n,t,f):F,s=l===F;if(s){var h=af(a),p=!h&&sf(a),_=!h&&!p&&gf(a),l=a;h||p||_?af(c)?l=c:_u(c)?l=Mr(c):p?(s=false,l=Wr(a,true)):_?(s=false,l=Lr(a,true)):l=[]:wu(a)||cf(a)?(l=c,
cf(c)?l=Ru(c):(!bu(c)||r&&gu(c))&&(l=Oe(a))):s=false}s&&(f.set(a,l),nr(l,a,r,e,f),f.delete(a)),ct(n,o,l)}}else f=e?e(n[o],i,o+"",n,t,u):F,f===F&&(f=i),ct(n,o,f)},Uu)}function tr(n,t){var r=n.length;if(r)return t+=0>t?r:0,Re(t,r)?n[t]:F}function rr(n,t,r){var e=-1;return t=l(t.length?t:[Nu],S(je())),n=Yt(n,function(n){return{a:l(t,function(t){return t(n)}),b:++e,c:n}}),A(n,function(n,t){var e;n:{e=-1;for(var u=n.a,i=t.a,o=u.length,f=r.length;++e<o;){var c=Ur(u[e],i[e]);if(c){e=e>=f?c:c*("desc"==r[e]?-1:1);
break n}}e=n.b-t.b}return e})}function er(n,t){return ur(n,t,function(t,r){return Bu(n,r)})}function ur(n,t,r){for(var e=-1,u=t.length,i={};++e<u;){var o=t[e],f=It(n,o);r(f,o)&&pr(i,Rr(o,n),f)}return i}function ir(n){return function(t){return It(t,n)}}function or(n,t,r,e){var u=e?y:d,i=-1,o=t.length,f=n;for(n===t&&(t=Mr(t)),r&&(f=l(n,S(r)));++i<o;)for(var c=0,a=t[i],a=r?r(a):a;-1<(c=u(f,a,c,e));)f!==n&&wi.call(f,c,1),wi.call(n,c,1);return n}function fr(n,t){for(var r=n?t.length:0,e=r-1;r--;){var u=t[r];
if(r==e||u!==i){var i=u;Re(u)?wi.call(n,u,1):mr(n,u)}}}function cr(n,t){return n+zi(Fi()*(t-n+1))}function ar(n,t){var r="";if(!n||1>t||9007199254740991<t)return r;do t%2&&(r+=n),(t=zi(t/2))&&(n+=n);while(t);return r}function lr(n,t){return wo(Ce(n,t,Nu),n+"")}function sr(n){return tt(Du(n))}function hr(n,t){var r=Du(n);return Te(r,gt(t,0,r.length))}function pr(n,t,r,e){if(!bu(n))return n;t=Rr(t,n);for(var u=-1,i=t.length,o=i-1,f=n;null!=f&&++u<i;){var c=$e(t[u]),a=r;if(u!=o){var l=f[c],a=e?e(l,c,f):F;
a===F&&(a=bu(l)?l:Re(t[u+1])?[]:{})}at(f,c,a),f=f[c]}return n}function _r(n){return Te(Du(n))}function vr(n,t,r){var e=-1,u=n.length;for(0>t&&(t=-t>u?0:u+t),r=r>u?u:r,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=Hu(u);++e<u;)r[e]=n[e+t];return r}function gr(n,t){var r;return oo(n,function(n,e,u){return r=t(n,e,u),!r}),!!r}function dr(n,t,r){var e=0,u=null==n?e:n.length;if(typeof t=="number"&&t===t&&2147483647>=u){for(;e<u;){var i=e+u>>>1,o=n[i];null!==o&&!Au(o)&&(r?o<=t:o<t)?e=i+1:u=i}return u}return yr(n,t,Nu,r);
}function yr(n,t,r,e){t=r(t);for(var u=0,i=null==n?0:n.length,o=t!==t,f=null===t,c=Au(t),a=t===F;u<i;){var l=zi((u+i)/2),s=r(n[l]),h=s!==F,p=null===s,_=s===s,v=Au(s);(o?e||_:a?_&&(e||h):f?_&&h&&(e||!p):c?_&&h&&!p&&(e||!v):p||v?0:e?s<=t:s<t)?u=l+1:i=l}return Mi(i,4294967294)}function br(n,t){for(var r=-1,e=n.length,u=0,i=[];++r<e;){var o=n[r],f=t?t(o):o;if(!r||!hu(f,c)){var c=f;i[u++]=0===o?0:o}}return i}function xr(n){return typeof n=="number"?n:Au(n)?P:+n}function jr(n){if(typeof n=="string")return n;
if(af(n))return l(n,jr)+"";if(Au(n))return uo?uo.call(n):"";var t=n+"";return"0"==t&&1/n==-N?"-0":t}function wr(n,t,r){var e=-1,u=c,i=n.length,o=true,f=[],l=f;if(r)o=false,u=a;else if(200<=i){if(u=t?null:po(n))return D(u);o=false,u=R,l=new qn}else l=t?[]:f;n:for(;++e<i;){var s=n[e],h=t?t(s):s,s=r||0!==s?s:0;if(o&&h===h){for(var p=l.length;p--;)if(l[p]===h)continue n;t&&l.push(h),f.push(s)}else u(l,h,r)||(l!==f&&l.push(h),f.push(s))}return f}function mr(n,t){return t=Rr(t,n),n=2>t.length?n:It(n,vr(t,0,-1)),
null==n||delete n[$e(Ge(t))]}function Ar(n,t,r,e){for(var u=n.length,i=e?u:-1;(e?i--:++i<u)&&t(n[i],i,n););return r?vr(n,e?0:i,e?i+1:u):vr(n,e?i+1:0,e?u:i)}function kr(n,t){var r=n;return r instanceof Mn&&(r=r.value()),h(t,function(n,t){return t.func.apply(t.thisArg,s([n],t.args))},r)}function Er(n,t,r){var e=n.length;if(2>e)return e?wr(n[0]):[];for(var u=-1,i=Hu(e);++u<e;)for(var o=n[u],f=-1;++f<e;)f!=u&&(i[u]=jt(i[u]||o,n[f],t,r));return wr(kt(i,1),t,r)}function Or(n,t,r){for(var e=-1,u=n.length,i=t.length,o={};++e<u;)r(o,n[e],e<i?t[e]:F);
return o}function Sr(n){return _u(n)?n:[]}function Ir(n){return typeof n=="function"?n:Nu}function Rr(n,t){return af(n)?n:We(n,t)?[n]:mo(zu(n))}function zr(n,t,r){var e=n.length;return r=r===F?e:r,!t&&r>=e?n:vr(n,t,r)}function Wr(n,t){if(t)return n.slice();var r=n.length,r=yi?yi(r):new n.constructor(r);return n.copy(r),r}function Br(n){var t=new n.constructor(n.byteLength);return new di(t).set(new di(n)),t}function Lr(n,t){return new n.constructor(t?Br(n.buffer):n.buffer,n.byteOffset,n.length)}function Ur(n,t){
if(n!==t){var r=n!==F,e=null===n,u=n===n,i=Au(n),o=t!==F,f=null===t,c=t===t,a=Au(t);if(!f&&!a&&!i&&n>t||i&&o&&c&&!f&&!a||e&&o&&c||!r&&c||!u)return 1;if(!e&&!i&&!a&&n<t||a&&r&&u&&!e&&!i||f&&r&&u||!o&&u||!c)return-1}return 0}function Cr(n,t,r,e){var u=-1,i=n.length,o=r.length,f=-1,c=t.length,a=Di(i-o,0),l=Hu(c+a);for(e=!e;++f<c;)l[f]=t[f];for(;++u<o;)(e||u<i)&&(l[r[u]]=n[u]);for(;a--;)l[f++]=n[u++];return l}function Dr(n,t,r,e){var u=-1,i=n.length,o=-1,f=r.length,c=-1,a=t.length,l=Di(i-f,0),s=Hu(l+a);
for(e=!e;++u<l;)s[u]=n[u];for(l=u;++c<a;)s[l+c]=t[c];for(;++o<f;)(e||u<i)&&(s[l+r[o]]=n[u++]);return s}function Mr(n,t){var r=-1,e=n.length;for(t||(t=Hu(e));++r<e;)t[r]=n[r];return t}function Tr(n,t,r,e){var u=!r;r||(r={});for(var i=-1,o=t.length;++i<o;){var f=t[i],c=e?e(r[f],n[f],f,r,n):F;c===F&&(c=n[f]),u?_t(r,f,c):at(r,f,c)}return r}function $r(n,t){return Tr(n,vo(n),t)}function Fr(n,t){return Tr(n,go(n),t)}function Nr(n,t){return function(r,u){var i=af(r)?e:st,o=t?t():{};return i(r,n,je(u,2),o);
}}function Pr(n){return lr(function(t,r){var e=-1,u=r.length,i=1<u?r[u-1]:F,o=2<u?r[2]:F,i=3<n.length&&typeof i=="function"?(u--,i):F;for(o&&ze(r[0],r[1],o)&&(i=3>u?F:i,u=1),t=ni(t);++e<u;)(o=r[e])&&n(t,o,e,i);return t})}function Zr(n,t){return function(r,e){if(null==r)return r;if(!pu(r))return n(r,e);for(var u=r.length,i=t?u:-1,o=ni(r);(t?i--:++i<u)&&false!==e(o[i],i,o););return r}}function qr(n){return function(t,r,e){var u=-1,i=ni(t);e=e(t);for(var o=e.length;o--;){var f=e[n?o:++u];if(false===r(i[f],f,i))break;
}return t}}function Vr(n,t,r){function e(){return(this&&this!==Zn&&this instanceof e?i:n).apply(u?r:this,arguments)}var u=1&t,i=Hr(n);return e}function Kr(n){return function(t){t=zu(t);var r=Bn.test(t)?$(t):F,e=r?r[0]:t.charAt(0);return t=r?zr(r,1).join(""):t.slice(1),e[n]()+t}}function Gr(n){return function(t){return h($u(Tu(t).replace(In,"")),n,"")}}function Hr(n){return function(){var t=arguments;switch(t.length){case 0:return new n;case 1:return new n(t[0]);case 2:return new n(t[0],t[1]);case 3:
return new n(t[0],t[1],t[2]);case 4:return new n(t[0],t[1],t[2],t[3]);case 5:return new n(t[0],t[1],t[2],t[3],t[4]);case 6:return new n(t[0],t[1],t[2],t[3],t[4],t[5]);case 7:return new n(t[0],t[1],t[2],t[3],t[4],t[5],t[6])}var r=io(n.prototype),t=n.apply(r,t);return bu(t)?t:r}}function Jr(n,t,e){function u(){for(var o=arguments.length,f=Hu(o),c=o,a=xe(u);c--;)f[c]=arguments[c];return c=3>o&&f[0]!==a&&f[o-1]!==a?[]:C(f,a),o-=c.length,o<e?fe(n,t,Xr,u.placeholder,F,f,c,F,F,e-o):r(this&&this!==Zn&&this instanceof u?i:n,this,f);
}var i=Hr(n);return u}function Yr(n){return function(t,r,e){var u=ni(t);if(!pu(t)){var i=je(r,3);t=Lu(t),r=function(n){return i(u[n],n,u)}}return r=n(t,r,e),-1<r?u[i?t[r]:r]:F}}function Qr(n){return ge(function(t){var r=t.length,e=r,u=zn.prototype.thru;for(n&&t.reverse();e--;){var i=t[e];if(typeof i!="function")throw new ei("Expected a function");if(u&&!o&&"wrapper"==be(i))var o=new zn([],true)}for(e=o?e:r;++e<r;)var i=t[e],u=be(i),f="wrapper"==u?_o(i):F,o=f&&Be(f[0])&&424==f[1]&&!f[4].length&&1==f[9]?o[be(f[0])].apply(o,f[3]):1==i.length&&Be(i)?o[u]():o.thru(i);
return function(){var n=arguments,e=n[0];if(o&&1==n.length&&af(e))return o.plant(e).value();for(var u=0,n=r?t[u].apply(this,n):e;++u<r;)n=t[u].call(this,n);return n}})}function Xr(n,t,r,e,u,i,o,f,c,a){function l(){for(var d=arguments.length,y=Hu(d),b=d;b--;)y[b]=arguments[b];if(_){var x,j=xe(l),b=y.length;for(x=0;b--;)y[b]===j&&++x}if(e&&(y=Cr(y,e,u,_)),i&&(y=Dr(y,i,o,_)),d-=x,_&&d<a)return j=C(y,j),fe(n,t,Xr,l.placeholder,r,y,j,f,c,a-d);if(j=h?r:this,b=p?j[n]:n,d=y.length,f){x=y.length;for(var w=Mi(f.length,x),m=Mr(y);w--;){
var A=f[w];y[w]=Re(A,x)?m[A]:F}}else v&&1<d&&y.reverse();return s&&c<d&&(y.length=c),this&&this!==Zn&&this instanceof l&&(b=g||Hr(b)),b.apply(j,y)}var s=128&t,h=1&t,p=2&t,_=24&t,v=512&t,g=p?F:Hr(n);return l}function ne(n,t){return function(r,e){return Ct(r,n,t(e))}}function te(n,t){return function(r,e){var u;if(r===F&&e===F)return t;if(r!==F&&(u=r),e!==F){if(u===F)return e;typeof r=="string"||typeof e=="string"?(r=jr(r),e=jr(e)):(r=xr(r),e=xr(e)),u=n(r,e)}return u}}function re(n){return ge(function(t){
return t=l(t,S(je())),lr(function(e){var u=this;return n(t,function(n){return r(n,u,e)})})})}function ee(n,t){t=t===F?" ":jr(t);var r=t.length;return 2>r?r?ar(t,n):t:(r=ar(t,Ri(n/T(t))),Bn.test(t)?zr($(r),0,n).join(""):r.slice(0,n))}function ue(n,t,e,u){function i(){for(var t=-1,c=arguments.length,a=-1,l=u.length,s=Hu(l+c),h=this&&this!==Zn&&this instanceof i?f:n;++a<l;)s[a]=u[a];for(;c--;)s[a++]=arguments[++t];return r(h,o?e:this,s)}var o=1&t,f=Hr(n);return i}function ie(n){return function(t,r,e){
e&&typeof e!="number"&&ze(t,r,e)&&(r=e=F),t=Eu(t),r===F?(r=t,t=0):r=Eu(r),e=e===F?t<r?1:-1:Eu(e);var u=-1;r=Di(Ri((r-t)/(e||1)),0);for(var i=Hu(r);r--;)i[n?r:++u]=t,t+=e;return i}}function oe(n){return function(t,r){return typeof t=="string"&&typeof r=="string"||(t=Iu(t),r=Iu(r)),n(t,r)}}function fe(n,t,r,e,u,i,o,f,c,a){var l=8&t,s=l?o:F;o=l?F:o;var h=l?i:F;return i=l?F:i,t=(t|(l?32:64))&~(l?64:32),4&t||(t&=-4),u=[n,t,u,h,s,i,o,f,c,a],r=r.apply(F,u),Be(n)&&xo(r,u),r.placeholder=e,De(r,n,t)}function ce(n){
var t=Xu[n];return function(n,r){if(n=Iu(n),r=null==r?0:Mi(Ou(r),292)){var e=(zu(n)+"e").split("e"),e=t(e[0]+"e"+(+e[1]+r)),e=(zu(e)+"e").split("e");return+(e[0]+"e"+(+e[1]-r))}return t(n)}}function ae(n){return function(t){var r=yo(t);return"[object Map]"==r?L(t):"[object Set]"==r?M(t):O(t,n(t))}}function le(n,t,r,e,u,i,o,f){var c=2&t;if(!c&&typeof n!="function")throw new ei("Expected a function");var a=e?e.length:0;if(a||(t&=-97,e=u=F),o=o===F?o:Di(Ou(o),0),f=f===F?f:Ou(f),a-=u?u.length:0,64&t){
var l=e,s=u;e=u=F}var h=c?F:_o(n);return i=[n,t,r,e,u,l,s,i,o,f],h&&(r=i[1],n=h[1],t=r|n,e=128==n&&8==r||128==n&&256==r&&i[7].length<=h[8]||384==n&&h[7].length<=h[8]&&8==r,131>t||e)&&(1&n&&(i[2]=h[2],t|=1&r?0:4),(r=h[3])&&(e=i[3],i[3]=e?Cr(e,r,h[4]):r,i[4]=e?C(i[3],"__lodash_placeholder__"):h[4]),(r=h[5])&&(e=i[5],i[5]=e?Dr(e,r,h[6]):r,i[6]=e?C(i[5],"__lodash_placeholder__"):h[6]),(r=h[7])&&(i[7]=r),128&n&&(i[8]=null==i[8]?h[8]:Mi(i[8],h[8])),null==i[9]&&(i[9]=h[9]),i[0]=h[0],i[1]=t),n=i[0],t=i[1],
r=i[2],e=i[3],u=i[4],f=i[9]=i[9]===F?c?0:n.length:Di(i[9]-a,0),!f&&24&t&&(t&=-25),De((h?lo:xo)(t&&1!=t?8==t||16==t?Jr(n,t,f):32!=t&&33!=t||u.length?Xr.apply(F,i):ue(n,t,r,e):Vr(n,t,r),i),n,t)}function se(n,t,r,e){return n===F||hu(n,ii[r])&&!ci.call(e,r)?t:n}function he(n,t,r,e,u,i){return bu(n)&&bu(t)&&(i.set(t,n),nr(n,t,F,he,i),i.delete(t)),n}function pe(n){return wu(n)?F:n}function _e(n,t,r,e,u,i){var o=1&r,f=n.length,c=t.length;if(f!=c&&!(o&&c>f))return false;if((c=i.get(n))&&i.get(t))return c==t;var c=-1,a=true,l=2&r?new qn:F;
for(i.set(n,t),i.set(t,n);++c<f;){var s=n[c],h=t[c];if(e)var p=o?e(h,s,c,t,n,i):e(s,h,c,n,t,i);if(p!==F){if(p)continue;a=false;break}if(l){if(!_(t,function(n,t){if(!R(l,t)&&(s===n||u(s,n,r,e,i)))return l.push(t)})){a=false;break}}else if(s!==h&&!u(s,h,r,e,i)){a=false;break}}return i.delete(n),i.delete(t),a}function ve(n,t,r,e,u,i,o){switch(r){case"[object DataView]":if(n.byteLength!=t.byteLength||n.byteOffset!=t.byteOffset)break;n=n.buffer,t=t.buffer;case"[object ArrayBuffer]":if(n.byteLength!=t.byteLength||!i(new di(n),new di(t)))break;
return true;case"[object Boolean]":case"[object Date]":case"[object Number]":return hu(+n,+t);case"[object Error]":return n.name==t.name&&n.message==t.message;case"[object RegExp]":case"[object String]":return n==t+"";case"[object Map]":var f=L;case"[object Set]":if(f||(f=D),n.size!=t.size&&!(1&e))break;return(r=o.get(n))?r==t:(e|=2,o.set(n,t),t=_e(f(n),f(t),e,u,i,o),o.delete(n),t);case"[object Symbol]":if(eo)return eo.call(n)==eo.call(t)}return false}function ge(n){return wo(Ce(n,F,Ve),n+"")}function de(n){
return Rt(n,Lu,vo)}function ye(n){return Rt(n,Uu,go)}function be(n){for(var t=n.name+"",r=Ji[t],e=ci.call(Ji,t)?r.length:0;e--;){var u=r[e],i=u.func;if(null==i||i==n)return u.name}return t}function xe(n){return(ci.call(On,"placeholder")?On:n).placeholder}function je(){var n=On.iteratee||Pu,n=n===Pu?Gt:n;return arguments.length?n(arguments[0],arguments[1]):n}function we(n,t){var r=n.__data__,e=typeof t;return("string"==e||"number"==e||"symbol"==e||"boolean"==e?"__proto__"!==t:null===t)?r[typeof t=="string"?"string":"hash"]:r.map;
}function me(n){for(var t=Lu(n),r=t.length;r--;){var e=t[r],u=n[e];t[r]=[e,u,u===u&&!bu(u)]}return t}function Ae(n,t){var r=null==n?F:n[t];return Zt(r)?r:F}function ke(n,t,r){t=Rr(t,n);for(var e=-1,u=t.length,i=false;++e<u;){var o=$e(t[e]);if(!(i=null!=n&&r(n,o)))break;n=n[o]}return i||++e!=u?i:(u=null==n?0:n.length,!!u&&yu(u)&&Re(o,u)&&(af(n)||cf(n)))}function Ee(n){var t=n.length,r=n.constructor(t);return t&&"string"==typeof n[0]&&ci.call(n,"index")&&(r.index=n.index,r.input=n.input),r}function Oe(n){
return typeof n.constructor!="function"||Le(n)?{}:io(bi(n))}function Se(r,e,u,i){var o=r.constructor;switch(e){case"[object ArrayBuffer]":return Br(r);case"[object Boolean]":case"[object Date]":return new o(+r);case"[object DataView]":return e=i?Br(r.buffer):r.buffer,new r.constructor(e,r.byteOffset,r.byteLength);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":
case"[object Uint16Array]":case"[object Uint32Array]":return Lr(r,i);case"[object Map]":return e=i?u(L(r),1):L(r),h(e,n,new r.constructor);case"[object Number]":case"[object String]":return new o(r);case"[object RegExp]":return e=new r.constructor(r.source,dn.exec(r)),e.lastIndex=r.lastIndex,e;case"[object Set]":return e=i?u(D(r),1):D(r),h(e,t,new r.constructor);case"[object Symbol]":return eo?ni(eo.call(r)):{}}}function Ie(n){return af(n)||cf(n)||!!(mi&&n&&n[mi])}function Re(n,t){return t=null==t?9007199254740991:t,
!!t&&(typeof n=="number"||wn.test(n))&&-1<n&&0==n%1&&n<t}function ze(n,t,r){if(!bu(r))return false;var e=typeof t;return!!("number"==e?pu(r)&&Re(t,r.length):"string"==e&&t in r)&&hu(r[t],n)}function We(n,t){if(af(n))return false;var r=typeof n;return!("number"!=r&&"symbol"!=r&&"boolean"!=r&&null!=n&&!Au(n))||(rn.test(n)||!tn.test(n)||null!=t&&n in ni(t))}function Be(n){var t=be(n),r=On[t];return typeof r=="function"&&t in Mn.prototype&&(n===r||(t=_o(r),!!t&&n===t[0]))}function Le(n){var t=n&&n.constructor;
return n===(typeof t=="function"&&t.prototype||ii)}function Ue(n,t){return function(r){return null!=r&&(r[n]===t&&(t!==F||n in ni(r)))}}function Ce(n,t,e){return t=Di(t===F?n.length-1:t,0),function(){for(var u=arguments,i=-1,o=Di(u.length-t,0),f=Hu(o);++i<o;)f[i]=u[t+i];for(i=-1,o=Hu(t+1);++i<t;)o[i]=u[i];return o[t]=e(f),r(n,this,o)}}function De(n,t,r){var e=t+"";t=wo;var u,i=Ne;return u=(u=e.match(hn))?u[1].split(pn):[],r=i(u,r),(i=r.length)&&(u=i-1,r[u]=(1<i?"& ":"")+r[u],r=r.join(2<i?", ":" "),
e=e.replace(sn,"{\n/* [wrapped with "+r+"] */\n")),t(n,e)}function Me(n){var t=0,r=0;return function(){var e=Ti(),u=16-(e-r);if(r=e,0<u){if(800<=++t)return arguments[0]}else t=0;return n.apply(F,arguments)}}function Te(n,t){var r=-1,e=n.length,u=e-1;for(t=t===F?e:t;++r<t;){var e=cr(r,u),i=n[e];n[e]=n[r],n[r]=i}return n.length=t,n}function $e(n){if(typeof n=="string"||Au(n))return n;var t=n+"";return"0"==t&&1/n==-N?"-0":t}function Fe(n){if(null!=n){try{return fi.call(n)}catch(n){}return n+""}return"";
}function Ne(n,t){return u(Z,function(r){var e="_."+r[0];t&r[1]&&!c(n,e)&&n.push(e)}),n.sort()}function Pe(n){if(n instanceof Mn)return n.clone();var t=new zn(n.__wrapped__,n.__chain__);return t.__actions__=Mr(n.__actions__),t.__index__=n.__index__,t.__values__=n.__values__,t}function Ze(n,t,r){var e=null==n?0:n.length;return e?(r=null==r?0:Ou(r),0>r&&(r=Di(e+r,0)),g(n,je(t,3),r)):-1}function qe(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e-1;return r!==F&&(u=Ou(r),u=0>r?Di(e+u,0):Mi(u,e-1)),
g(n,je(t,3),u,true)}function Ve(n){return(null==n?0:n.length)?kt(n,1):[]}function Ke(n){return n&&n.length?n[0]:F}function Ge(n){var t=null==n?0:n.length;return t?n[t-1]:F}function He(n,t){return n&&n.length&&t&&t.length?or(n,t):n}function Je(n){return null==n?n:Ni.call(n)}function Ye(n){if(!n||!n.length)return[];var t=0;return n=f(n,function(n){if(_u(n))return t=Di(n.length,t),true}),E(t,function(t){return l(n,j(t))})}function Qe(n,t){if(!n||!n.length)return[];var e=Ye(n);return null==t?e:l(e,function(n){
return r(t,F,n)})}function Xe(n){return n=On(n),n.__chain__=true,n}function nu(n,t){return t(n)}function tu(){return this}function ru(n,t){return(af(n)?u:oo)(n,je(t,3))}function eu(n,t){return(af(n)?i:fo)(n,je(t,3))}function uu(n,t){return(af(n)?l:Yt)(n,je(t,3))}function iu(n,t,r){return t=r?F:t,t=n&&null==t?n.length:t,le(n,128,F,F,F,F,t)}function ou(n,t){var r;if(typeof t!="function")throw new ei("Expected a function");return n=Ou(n),function(){return 0<--n&&(r=t.apply(this,arguments)),1>=n&&(t=F),
r}}function fu(n,t,r){return t=r?F:t,n=le(n,8,F,F,F,F,F,t),n.placeholder=fu.placeholder,n}function cu(n,t,r){return t=r?F:t,n=le(n,16,F,F,F,F,F,t),n.placeholder=cu.placeholder,n}function au(n,t,r){function e(t){var r=c,e=a;return c=a=F,_=t,s=n.apply(e,r)}function u(n){var r=n-p;return n-=_,p===F||r>=t||0>r||g&&n>=l}function i(){var n=Jo();if(u(n))return o(n);var r,e=jo;r=n-_,n=t-(n-p),r=g?Mi(n,l-r):n,h=e(i,r)}function o(n){return h=F,d&&c?e(n):(c=a=F,s)}function f(){var n=Jo(),r=u(n);if(c=arguments,
a=this,p=n,r){if(h===F)return _=n=p,h=jo(i,t),v?e(n):s;if(g)return h=jo(i,t),e(p)}return h===F&&(h=jo(i,t)),s}var c,a,l,s,h,p,_=0,v=false,g=false,d=true;if(typeof n!="function")throw new ei("Expected a function");return t=Iu(t)||0,bu(r)&&(v=!!r.leading,l=(g="maxWait"in r)?Di(Iu(r.maxWait)||0,t):l,d="trailing"in r?!!r.trailing:d),f.cancel=function(){h!==F&&ho(h),_=0,c=p=a=h=F},f.flush=function(){return h===F?s:o(Jo())},f}function lu(n,t){function r(){var e=arguments,u=t?t.apply(this,e):e[0],i=r.cache;return i.has(u)?i.get(u):(e=n.apply(this,e),
r.cache=i.set(u,e)||i,e)}if(typeof n!="function"||null!=t&&typeof t!="function")throw new ei("Expected a function");return r.cache=new(lu.Cache||Pn),r}function su(n){if(typeof n!="function")throw new ei("Expected a function");return function(){var t=arguments;switch(t.length){case 0:return!n.call(this);case 1:return!n.call(this,t[0]);case 2:return!n.call(this,t[0],t[1]);case 3:return!n.call(this,t[0],t[1],t[2])}return!n.apply(this,t)}}function hu(n,t){return n===t||n!==n&&t!==t}function pu(n){return null!=n&&yu(n.length)&&!gu(n);
}function _u(n){return xu(n)&&pu(n)}function vu(n){if(!xu(n))return false;var t=zt(n);return"[object Error]"==t||"[object DOMException]"==t||typeof n.message=="string"&&typeof n.name=="string"&&!wu(n)}function gu(n){return!!bu(n)&&(n=zt(n),"[object Function]"==n||"[object GeneratorFunction]"==n||"[object AsyncFunction]"==n||"[object Proxy]"==n)}function du(n){return typeof n=="number"&&n==Ou(n)}function yu(n){return typeof n=="number"&&-1<n&&0==n%1&&9007199254740991>=n}function bu(n){var t=typeof n;return null!=n&&("object"==t||"function"==t);
}function xu(n){return null!=n&&typeof n=="object"}function ju(n){return typeof n=="number"||xu(n)&&"[object Number]"==zt(n)}function wu(n){return!(!xu(n)||"[object Object]"!=zt(n))&&(n=bi(n),null===n||(n=ci.call(n,"constructor")&&n.constructor,typeof n=="function"&&n instanceof n&&fi.call(n)==hi))}function mu(n){return typeof n=="string"||!af(n)&&xu(n)&&"[object String]"==zt(n)}function Au(n){return typeof n=="symbol"||xu(n)&&"[object Symbol]"==zt(n)}function ku(n){if(!n)return[];if(pu(n))return mu(n)?$(n):Mr(n);
if(Ai&&n[Ai]){n=n[Ai]();for(var t,r=[];!(t=n.next()).done;)r.push(t.value);return r}return t=yo(n),("[object Map]"==t?L:"[object Set]"==t?D:Du)(n)}function Eu(n){return n?(n=Iu(n),n===N||n===-N?1.7976931348623157e308*(0>n?-1:1):n===n?n:0):0===n?n:0}function Ou(n){n=Eu(n);var t=n%1;return n===n?t?n-t:n:0}function Su(n){return n?gt(Ou(n),0,4294967295):0}function Iu(n){if(typeof n=="number")return n;if(Au(n))return P;if(bu(n)&&(n=typeof n.valueOf=="function"?n.valueOf():n,n=bu(n)?n+"":n),typeof n!="string")return 0===n?n:+n;
n=n.replace(cn,"");var t=bn.test(n);return t||jn.test(n)?Fn(n.slice(2),t?2:8):yn.test(n)?P:+n}function Ru(n){return Tr(n,Uu(n))}function zu(n){return null==n?"":jr(n)}function Wu(n,t,r){return n=null==n?F:It(n,t),n===F?r:n}function Bu(n,t){return null!=n&&ke(n,t,Lt)}function Lu(n){return pu(n)?Gn(n):Ht(n)}function Uu(n){if(pu(n))n=Gn(n,true);else if(bu(n)){var t,r=Le(n),e=[];for(t in n)("constructor"!=t||!r&&ci.call(n,t))&&e.push(t);n=e}else{if(t=[],null!=n)for(r in ni(n))t.push(r);n=t}return n}function Cu(n,t){
if(null==n)return{};var r=l(ye(n),function(n){return[n]});return t=je(t),ur(n,r,function(n,r){return t(n,r[0])})}function Du(n){return null==n?[]:I(n,Lu(n))}function Mu(n){return Nf(zu(n).toLowerCase())}function Tu(n){return(n=zu(n))&&n.replace(mn,rt).replace(Rn,"")}function $u(n,t,r){return n=zu(n),t=r?F:t,t===F?Ln.test(n)?n.match(Wn)||[]:n.match(_n)||[]:n.match(t)||[]}function Fu(n){return function(){return n}}function Nu(n){return n}function Pu(n){return Gt(typeof n=="function"?n:dt(n,1))}function Zu(n,t,r){
var e=Lu(t),i=St(t,e);null!=r||bu(t)&&(i.length||!e.length)||(r=t,t=n,n=this,i=St(t,Lu(t)));var o=!(bu(r)&&"chain"in r&&!r.chain),f=gu(n);return u(i,function(r){var e=t[r];n[r]=e,f&&(n.prototype[r]=function(){var t=this.__chain__;if(o||t){var r=n(this.__wrapped__);return(r.__actions__=Mr(this.__actions__)).push({func:e,args:arguments,thisArg:n}),r.__chain__=t,r}return e.apply(n,s([this.value()],arguments))})}),n}function qu(){}function Vu(n){return We(n)?j($e(n)):ir(n)}function Ku(){return[]}function Gu(){
return false}En=null==En?Zn:it.defaults(Zn.Object(),En,it.pick(Zn,Un));var Hu=En.Array,Ju=En.Date,Yu=En.Error,Qu=En.Function,Xu=En.Math,ni=En.Object,ti=En.RegExp,ri=En.String,ei=En.TypeError,ui=Hu.prototype,ii=ni.prototype,oi=En["__core-js_shared__"],fi=Qu.prototype.toString,ci=ii.hasOwnProperty,ai=0,li=function(){var n=/[^.]+$/.exec(oi&&oi.keys&&oi.keys.IE_PROTO||"");return n?"Symbol(src)_1."+n:""}(),si=ii.toString,hi=fi.call(ni),pi=Zn._,_i=ti("^"+fi.call(ci).replace(on,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),vi=Kn?En.Buffer:F,gi=En.Symbol,di=En.Uint8Array,yi=vi?vi.f:F,bi=U(ni.getPrototypeOf,ni),xi=ni.create,ji=ii.propertyIsEnumerable,wi=ui.splice,mi=gi?gi.isConcatSpreadable:F,Ai=gi?gi.iterator:F,ki=gi?gi.toStringTag:F,Ei=function(){
try{var n=Ae(ni,"defineProperty");return n({},"",{}),n}catch(n){}}(),Oi=En.clearTimeout!==Zn.clearTimeout&&En.clearTimeout,Si=Ju&&Ju.now!==Zn.Date.now&&Ju.now,Ii=En.setTimeout!==Zn.setTimeout&&En.setTimeout,Ri=Xu.ceil,zi=Xu.floor,Wi=ni.getOwnPropertySymbols,Bi=vi?vi.isBuffer:F,Li=En.isFinite,Ui=ui.join,Ci=U(ni.keys,ni),Di=Xu.max,Mi=Xu.min,Ti=Ju.now,$i=En.parseInt,Fi=Xu.random,Ni=ui.reverse,Pi=Ae(En,"DataView"),Zi=Ae(En,"Map"),qi=Ae(En,"Promise"),Vi=Ae(En,"Set"),Ki=Ae(En,"WeakMap"),Gi=Ae(ni,"create"),Hi=Ki&&new Ki,Ji={},Yi=Fe(Pi),Qi=Fe(Zi),Xi=Fe(qi),no=Fe(Vi),to=Fe(Ki),ro=gi?gi.prototype:F,eo=ro?ro.valueOf:F,uo=ro?ro.toString:F,io=function(){
function n(){}return function(t){return bu(t)?xi?xi(t):(n.prototype=t,t=new n,n.prototype=F,t):{}}}();On.templateSettings={escape:Q,evaluate:X,interpolate:nn,variable:"",imports:{_:On}},On.prototype=Sn.prototype,On.prototype.constructor=On,zn.prototype=io(Sn.prototype),zn.prototype.constructor=zn,Mn.prototype=io(Sn.prototype),Mn.prototype.constructor=Mn,Tn.prototype.clear=function(){this.__data__=Gi?Gi(null):{},this.size=0},Tn.prototype.delete=function(n){return n=this.has(n)&&delete this.__data__[n],
this.size-=n?1:0,n},Tn.prototype.get=function(n){var t=this.__data__;return Gi?(n=t[n],"__lodash_hash_undefined__"===n?F:n):ci.call(t,n)?t[n]:F},Tn.prototype.has=function(n){var t=this.__data__;return Gi?t[n]!==F:ci.call(t,n)},Tn.prototype.set=function(n,t){var r=this.__data__;return this.size+=this.has(n)?0:1,r[n]=Gi&&t===F?"__lodash_hash_undefined__":t,this},Nn.prototype.clear=function(){this.__data__=[],this.size=0},Nn.prototype.delete=function(n){var t=this.__data__;return n=lt(t,n),!(0>n)&&(n==t.length-1?t.pop():wi.call(t,n,1),
--this.size,true)},Nn.prototype.get=function(n){var t=this.__data__;return n=lt(t,n),0>n?F:t[n][1]},Nn.prototype.has=function(n){return-1<lt(this.__data__,n)},Nn.prototype.set=function(n,t){var r=this.__data__,e=lt(r,n);return 0>e?(++this.size,r.push([n,t])):r[e][1]=t,this},Pn.prototype.clear=function(){this.size=0,this.__data__={hash:new Tn,map:new(Zi||Nn),string:new Tn}},Pn.prototype.delete=function(n){return n=we(this,n).delete(n),this.size-=n?1:0,n},Pn.prototype.get=function(n){return we(this,n).get(n);
},Pn.prototype.has=function(n){return we(this,n).has(n)},Pn.prototype.set=function(n,t){var r=we(this,n),e=r.size;return r.set(n,t),this.size+=r.size==e?0:1,this},qn.prototype.add=qn.prototype.push=function(n){return this.__data__.set(n,"__lodash_hash_undefined__"),this},qn.prototype.has=function(n){return this.__data__.has(n)},Vn.prototype.clear=function(){this.__data__=new Nn,this.size=0},Vn.prototype.delete=function(n){var t=this.__data__;return n=t.delete(n),this.size=t.size,n},Vn.prototype.get=function(n){
return this.__data__.get(n)},Vn.prototype.has=function(n){return this.__data__.has(n)},Vn.prototype.set=function(n,t){var r=this.__data__;if(r instanceof Nn){var e=r.__data__;if(!Zi||199>e.length)return e.push([n,t]),this.size=++r.size,this;r=this.__data__=new Pn(e)}return r.set(n,t),this.size=r.size,this};var oo=Zr(Et),fo=Zr(Ot,true),co=qr(),ao=qr(true),lo=Hi?function(n,t){return Hi.set(n,t),n}:Nu,so=Ei?function(n,t){return Ei(n,"toString",{configurable:true,enumerable:false,value:Fu(t),writable:true})}:Nu,ho=Oi||function(n){
return Zn.clearTimeout(n)},po=Vi&&1/D(new Vi([,-0]))[1]==N?function(n){return new Vi(n)}:qu,_o=Hi?function(n){return Hi.get(n)}:qu,vo=Wi?function(n){return null==n?[]:(n=ni(n),f(Wi(n),function(t){return ji.call(n,t)}))}:Ku,go=Wi?function(n){for(var t=[];n;)s(t,vo(n)),n=bi(n);return t}:Ku,yo=zt;(Pi&&"[object DataView]"!=yo(new Pi(new ArrayBuffer(1)))||Zi&&"[object Map]"!=yo(new Zi)||qi&&"[object Promise]"!=yo(qi.resolve())||Vi&&"[object Set]"!=yo(new Vi)||Ki&&"[object WeakMap]"!=yo(new Ki))&&(yo=function(n){
var t=zt(n);if(n=(n="[object Object]"==t?n.constructor:F)?Fe(n):"")switch(n){case Yi:return"[object DataView]";case Qi:return"[object Map]";case Xi:return"[object Promise]";case no:return"[object Set]";case to:return"[object WeakMap]"}return t});var bo=oi?gu:Gu,xo=Me(lo),jo=Ii||function(n,t){return Zn.setTimeout(n,t)},wo=Me(so),mo=function(n){n=lu(n,function(n){return 500===t.size&&t.clear(),n});var t=n.cache;return n}(function(n){var t=[];return en.test(n)&&t.push(""),n.replace(un,function(n,r,e,u){
t.push(e?u.replace(vn,"$1"):r||n)}),t}),Ao=lr(function(n,t){return _u(n)?jt(n,kt(t,1,_u,true)):[]}),ko=lr(function(n,t){var r=Ge(t);return _u(r)&&(r=F),_u(n)?jt(n,kt(t,1,_u,true),je(r,2)):[]}),Eo=lr(function(n,t){var r=Ge(t);return _u(r)&&(r=F),_u(n)?jt(n,kt(t,1,_u,true),F,r):[]}),Oo=lr(function(n){var t=l(n,Sr);return t.length&&t[0]===n[0]?Ut(t):[]}),So=lr(function(n){var t=Ge(n),r=l(n,Sr);return t===Ge(r)?t=F:r.pop(),r.length&&r[0]===n[0]?Ut(r,je(t,2)):[]}),Io=lr(function(n){var t=Ge(n),r=l(n,Sr);return(t=typeof t=="function"?t:F)&&r.pop(),
r.length&&r[0]===n[0]?Ut(r,F,t):[]}),Ro=lr(He),zo=ge(function(n,t){var r=null==n?0:n.length,e=vt(n,t);return fr(n,l(t,function(n){return Re(n,r)?+n:n}).sort(Ur)),e}),Wo=lr(function(n){return wr(kt(n,1,_u,true))}),Bo=lr(function(n){var t=Ge(n);return _u(t)&&(t=F),wr(kt(n,1,_u,true),je(t,2))}),Lo=lr(function(n){var t=Ge(n),t=typeof t=="function"?t:F;return wr(kt(n,1,_u,true),F,t)}),Uo=lr(function(n,t){return _u(n)?jt(n,t):[]}),Co=lr(function(n){return Er(f(n,_u))}),Do=lr(function(n){var t=Ge(n);return _u(t)&&(t=F),
Er(f(n,_u),je(t,2))}),Mo=lr(function(n){var t=Ge(n),t=typeof t=="function"?t:F;return Er(f(n,_u),F,t)}),To=lr(Ye),$o=lr(function(n){var t=n.length,t=1<t?n[t-1]:F,t=typeof t=="function"?(n.pop(),t):F;return Qe(n,t)}),Fo=ge(function(n){function t(t){return vt(t,n)}var r=n.length,e=r?n[0]:0,u=this.__wrapped__;return!(1<r||this.__actions__.length)&&u instanceof Mn&&Re(e)?(u=u.slice(e,+e+(r?1:0)),u.__actions__.push({func:nu,args:[t],thisArg:F}),new zn(u,this.__chain__).thru(function(n){return r&&!n.length&&n.push(F),
n})):this.thru(t)}),No=Nr(function(n,t,r){ci.call(n,r)?++n[r]:_t(n,r,1)}),Po=Yr(Ze),Zo=Yr(qe),qo=Nr(function(n,t,r){ci.call(n,r)?n[r].push(t):_t(n,r,[t])}),Vo=lr(function(n,t,e){var u=-1,i=typeof t=="function",o=pu(n)?Hu(n.length):[];return oo(n,function(n){o[++u]=i?r(t,n,e):Dt(n,t,e)}),o}),Ko=Nr(function(n,t,r){_t(n,r,t)}),Go=Nr(function(n,t,r){n[r?0:1].push(t)},function(){return[[],[]]}),Ho=lr(function(n,t){if(null==n)return[];var r=t.length;return 1<r&&ze(n,t[0],t[1])?t=[]:2<r&&ze(t[0],t[1],t[2])&&(t=[t[0]]),
rr(n,kt(t,1),[])}),Jo=Si||function(){return Zn.Date.now()},Yo=lr(function(n,t,r){var e=1;if(r.length)var u=C(r,xe(Yo)),e=32|e;return le(n,e,t,r,u)}),Qo=lr(function(n,t,r){var e=3;if(r.length)var u=C(r,xe(Qo)),e=32|e;return le(t,e,n,r,u)}),Xo=lr(function(n,t){return xt(n,1,t)}),nf=lr(function(n,t,r){return xt(n,Iu(t)||0,r)});lu.Cache=Pn;var tf=lr(function(n,t){t=1==t.length&&af(t[0])?l(t[0],S(je())):l(kt(t,1),S(je()));var e=t.length;return lr(function(u){for(var i=-1,o=Mi(u.length,e);++i<o;)u[i]=t[i].call(this,u[i]);
return r(n,this,u)})}),rf=lr(function(n,t){return le(n,32,F,t,C(t,xe(rf)))}),ef=lr(function(n,t){return le(n,64,F,t,C(t,xe(ef)))}),uf=ge(function(n,t){return le(n,256,F,F,F,t)}),of=oe(Wt),ff=oe(function(n,t){return n>=t}),cf=Mt(function(){return arguments}())?Mt:function(n){return xu(n)&&ci.call(n,"callee")&&!ji.call(n,"callee")},af=Hu.isArray,lf=Hn?S(Hn):Tt,sf=Bi||Gu,hf=Jn?S(Jn):$t,pf=Yn?S(Yn):Nt,_f=Qn?S(Qn):qt,vf=Xn?S(Xn):Vt,gf=nt?S(nt):Kt,df=oe(Jt),yf=oe(function(n,t){return n<=t}),bf=Pr(function(n,t){
if(Le(t)||pu(t))Tr(t,Lu(t),n);else for(var r in t)ci.call(t,r)&&at(n,r,t[r])}),xf=Pr(function(n,t){Tr(t,Uu(t),n)}),jf=Pr(function(n,t,r,e){Tr(t,Uu(t),n,e)}),wf=Pr(function(n,t,r,e){Tr(t,Lu(t),n,e)}),mf=ge(vt),Af=lr(function(n){return n.push(F,se),r(jf,F,n)}),kf=lr(function(n){return n.push(F,he),r(Rf,F,n)}),Ef=ne(function(n,t,r){n[t]=r},Fu(Nu)),Of=ne(function(n,t,r){ci.call(n,t)?n[t].push(r):n[t]=[r]},je),Sf=lr(Dt),If=Pr(function(n,t,r){nr(n,t,r)}),Rf=Pr(function(n,t,r,e){nr(n,t,r,e)}),zf=ge(function(n,t){
var r={};if(null==n)return r;var e=false;t=l(t,function(t){return t=Rr(t,n),e||(e=1<t.length),t}),Tr(n,ye(n),r),e&&(r=dt(r,7,pe));for(var u=t.length;u--;)mr(r,t[u]);return r}),Wf=ge(function(n,t){return null==n?{}:er(n,t)}),Bf=ae(Lu),Lf=ae(Uu),Uf=Gr(function(n,t,r){return t=t.toLowerCase(),n+(r?Mu(t):t)}),Cf=Gr(function(n,t,r){return n+(r?"-":"")+t.toLowerCase()}),Df=Gr(function(n,t,r){return n+(r?" ":"")+t.toLowerCase()}),Mf=Kr("toLowerCase"),Tf=Gr(function(n,t,r){return n+(r?"_":"")+t.toLowerCase();
}),$f=Gr(function(n,t,r){return n+(r?" ":"")+Nf(t)}),Ff=Gr(function(n,t,r){return n+(r?" ":"")+t.toUpperCase()}),Nf=Kr("toUpperCase"),Pf=lr(function(n,t){try{return r(n,F,t)}catch(n){return vu(n)?n:new Yu(n)}}),Zf=ge(function(n,t){return u(t,function(t){t=$e(t),_t(n,t,Yo(n[t],n))}),n}),qf=Qr(),Vf=Qr(true),Kf=lr(function(n,t){return function(r){return Dt(r,n,t)}}),Gf=lr(function(n,t){return function(r){return Dt(n,r,t)}}),Hf=re(l),Jf=re(o),Yf=re(_),Qf=ie(),Xf=ie(true),nc=te(function(n,t){return n+t},0),tc=ce("ceil"),rc=te(function(n,t){
return n/t},1),ec=ce("floor"),uc=te(function(n,t){return n*t},1),ic=ce("round"),oc=te(function(n,t){return n-t},0);return On.after=function(n,t){if(typeof t!="function")throw new ei("Expected a function");return n=Ou(n),function(){if(1>--n)return t.apply(this,arguments)}},On.ary=iu,On.assign=bf,On.assignIn=xf,On.assignInWith=jf,On.assignWith=wf,On.at=mf,On.before=ou,On.bind=Yo,On.bindAll=Zf,On.bindKey=Qo,On.castArray=function(){if(!arguments.length)return[];var n=arguments[0];return af(n)?n:[n]},
On.chain=Xe,On.chunk=function(n,t,r){if(t=(r?ze(n,t,r):t===F)?1:Di(Ou(t),0),r=null==n?0:n.length,!r||1>t)return[];for(var e=0,u=0,i=Hu(Ri(r/t));e<r;)i[u++]=vr(n,e,e+=t);return i},On.compact=function(n){for(var t=-1,r=null==n?0:n.length,e=0,u=[];++t<r;){var i=n[t];i&&(u[e++]=i)}return u},On.concat=function(){var n=arguments.length;if(!n)return[];for(var t=Hu(n-1),r=arguments[0];n--;)t[n-1]=arguments[n];return s(af(r)?Mr(r):[r],kt(t,1))},On.cond=function(n){var t=null==n?0:n.length,e=je();return n=t?l(n,function(n){
if("function"!=typeof n[1])throw new ei("Expected a function");return[e(n[0]),n[1]]}):[],lr(function(e){for(var u=-1;++u<t;){var i=n[u];if(r(i[0],this,e))return r(i[1],this,e)}})},On.conforms=function(n){return yt(dt(n,1))},On.constant=Fu,On.countBy=No,On.create=function(n,t){var r=io(n);return null==t?r:ht(r,t)},On.curry=fu,On.curryRight=cu,On.debounce=au,On.defaults=Af,On.defaultsDeep=kf,On.defer=Xo,On.delay=nf,On.difference=Ao,On.differenceBy=ko,On.differenceWith=Eo,On.drop=function(n,t,r){var e=null==n?0:n.length;
return e?(t=r||t===F?1:Ou(t),vr(n,0>t?0:t,e)):[]},On.dropRight=function(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===F?1:Ou(t),t=e-t,vr(n,0,0>t?0:t)):[]},On.dropRightWhile=function(n,t){return n&&n.length?Ar(n,je(t,3),true,true):[]},On.dropWhile=function(n,t){return n&&n.length?Ar(n,je(t,3),true):[]},On.fill=function(n,t,r,e){var u=null==n?0:n.length;if(!u)return[];for(r&&typeof r!="number"&&ze(n,t,r)&&(r=0,e=u),u=n.length,r=Ou(r),0>r&&(r=-r>u?0:u+r),e=e===F||e>u?u:Ou(e),0>e&&(e+=u),e=r>e?0:Su(e);r<e;)n[r++]=t;
return n},On.filter=function(n,t){return(af(n)?f:At)(n,je(t,3))},On.flatMap=function(n,t){return kt(uu(n,t),1)},On.flatMapDeep=function(n,t){return kt(uu(n,t),N)},On.flatMapDepth=function(n,t,r){return r=r===F?1:Ou(r),kt(uu(n,t),r)},On.flatten=Ve,On.flattenDeep=function(n){return(null==n?0:n.length)?kt(n,N):[]},On.flattenDepth=function(n,t){return null!=n&&n.length?(t=t===F?1:Ou(t),kt(n,t)):[]},On.flip=function(n){return le(n,512)},On.flow=qf,On.flowRight=Vf,On.fromPairs=function(n){for(var t=-1,r=null==n?0:n.length,e={};++t<r;){
var u=n[t];e[u[0]]=u[1]}return e},On.functions=function(n){return null==n?[]:St(n,Lu(n))},On.functionsIn=function(n){return null==n?[]:St(n,Uu(n))},On.groupBy=qo,On.initial=function(n){return(null==n?0:n.length)?vr(n,0,-1):[]},On.intersection=Oo,On.intersectionBy=So,On.intersectionWith=Io,On.invert=Ef,On.invertBy=Of,On.invokeMap=Vo,On.iteratee=Pu,On.keyBy=Ko,On.keys=Lu,On.keysIn=Uu,On.map=uu,On.mapKeys=function(n,t){var r={};return t=je(t,3),Et(n,function(n,e,u){_t(r,t(n,e,u),n)}),r},On.mapValues=function(n,t){
var r={};return t=je(t,3),Et(n,function(n,e,u){_t(r,e,t(n,e,u))}),r},On.matches=function(n){return Qt(dt(n,1))},On.matchesProperty=function(n,t){return Xt(n,dt(t,1))},On.memoize=lu,On.merge=If,On.mergeWith=Rf,On.method=Kf,On.methodOf=Gf,On.mixin=Zu,On.negate=su,On.nthArg=function(n){return n=Ou(n),lr(function(t){return tr(t,n)})},On.omit=zf,On.omitBy=function(n,t){return Cu(n,su(je(t)))},On.once=function(n){return ou(2,n)},On.orderBy=function(n,t,r,e){return null==n?[]:(af(t)||(t=null==t?[]:[t]),
r=e?F:r,af(r)||(r=null==r?[]:[r]),rr(n,t,r))},On.over=Hf,On.overArgs=tf,On.overEvery=Jf,On.overSome=Yf,On.partial=rf,On.partialRight=ef,On.partition=Go,On.pick=Wf,On.pickBy=Cu,On.property=Vu,On.propertyOf=function(n){return function(t){return null==n?F:It(n,t)}},On.pull=Ro,On.pullAll=He,On.pullAllBy=function(n,t,r){return n&&n.length&&t&&t.length?or(n,t,je(r,2)):n},On.pullAllWith=function(n,t,r){return n&&n.length&&t&&t.length?or(n,t,F,r):n},On.pullAt=zo,On.range=Qf,On.rangeRight=Xf,On.rearg=uf,On.reject=function(n,t){
return(af(n)?f:At)(n,su(je(t,3)))},On.remove=function(n,t){var r=[];if(!n||!n.length)return r;var e=-1,u=[],i=n.length;for(t=je(t,3);++e<i;){var o=n[e];t(o,e,n)&&(r.push(o),u.push(e))}return fr(n,u),r},On.rest=function(n,t){if(typeof n!="function")throw new ei("Expected a function");return t=t===F?t:Ou(t),lr(n,t)},On.reverse=Je,On.sampleSize=function(n,t,r){return t=(r?ze(n,t,r):t===F)?1:Ou(t),(af(n)?ot:hr)(n,t)},On.set=function(n,t,r){return null==n?n:pr(n,t,r)},On.setWith=function(n,t,r,e){return e=typeof e=="function"?e:F,
null==n?n:pr(n,t,r,e)},On.shuffle=function(n){return(af(n)?ft:_r)(n)},On.slice=function(n,t,r){var e=null==n?0:n.length;return e?(r&&typeof r!="number"&&ze(n,t,r)?(t=0,r=e):(t=null==t?0:Ou(t),r=r===F?e:Ou(r)),vr(n,t,r)):[]},On.sortBy=Ho,On.sortedUniq=function(n){return n&&n.length?br(n):[]},On.sortedUniqBy=function(n,t){return n&&n.length?br(n,je(t,2)):[]},On.split=function(n,t,r){return r&&typeof r!="number"&&ze(n,t,r)&&(t=r=F),r=r===F?4294967295:r>>>0,r?(n=zu(n))&&(typeof t=="string"||null!=t&&!_f(t))&&(t=jr(t),
!t&&Bn.test(n))?zr($(n),0,r):n.split(t,r):[]},On.spread=function(n,t){if(typeof n!="function")throw new ei("Expected a function");return t=null==t?0:Di(Ou(t),0),lr(function(e){var u=e[t];return e=zr(e,0,t),u&&s(e,u),r(n,this,e)})},On.tail=function(n){var t=null==n?0:n.length;return t?vr(n,1,t):[]},On.take=function(n,t,r){return n&&n.length?(t=r||t===F?1:Ou(t),vr(n,0,0>t?0:t)):[]},On.takeRight=function(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===F?1:Ou(t),t=e-t,vr(n,0>t?0:t,e)):[]},On.takeRightWhile=function(n,t){
return n&&n.length?Ar(n,je(t,3),false,true):[]},On.takeWhile=function(n,t){return n&&n.length?Ar(n,je(t,3)):[]},On.tap=function(n,t){return t(n),n},On.throttle=function(n,t,r){var e=true,u=true;if(typeof n!="function")throw new ei("Expected a function");return bu(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),au(n,t,{leading:e,maxWait:t,trailing:u})},On.thru=nu,On.toArray=ku,On.toPairs=Bf,On.toPairsIn=Lf,On.toPath=function(n){return af(n)?l(n,$e):Au(n)?[n]:Mr(mo(zu(n)))},On.toPlainObject=Ru,
On.transform=function(n,t,r){var e=af(n),i=e||sf(n)||gf(n);if(t=je(t,4),null==r){var o=n&&n.constructor;r=i?e?new o:[]:bu(n)&&gu(o)?io(bi(n)):{}}return(i?u:Et)(n,function(n,e,u){return t(r,n,e,u)}),r},On.unary=function(n){return iu(n,1)},On.union=Wo,On.unionBy=Bo,On.unionWith=Lo,On.uniq=function(n){return n&&n.length?wr(n):[]},On.uniqBy=function(n,t){return n&&n.length?wr(n,je(t,2)):[]},On.uniqWith=function(n,t){return t=typeof t=="function"?t:F,n&&n.length?wr(n,F,t):[]},On.unset=function(n,t){return null==n||mr(n,t);
},On.unzip=Ye,On.unzipWith=Qe,On.update=function(n,t,r){return null==n?n:pr(n,t,Ir(r)(It(n,t)),void 0)},On.updateWith=function(n,t,r,e){return e=typeof e=="function"?e:F,null!=n&&(n=pr(n,t,Ir(r)(It(n,t)),e)),n},On.values=Du,On.valuesIn=function(n){return null==n?[]:I(n,Uu(n))},On.without=Uo,On.words=$u,On.wrap=function(n,t){return rf(Ir(t),n)},On.xor=Co,On.xorBy=Do,On.xorWith=Mo,On.zip=To,On.zipObject=function(n,t){return Or(n||[],t||[],at)},On.zipObjectDeep=function(n,t){return Or(n||[],t||[],pr);
},On.zipWith=$o,On.entries=Bf,On.entriesIn=Lf,On.extend=xf,On.extendWith=jf,Zu(On,On),On.add=nc,On.attempt=Pf,On.camelCase=Uf,On.capitalize=Mu,On.ceil=tc,On.clamp=function(n,t,r){return r===F&&(r=t,t=F),r!==F&&(r=Iu(r),r=r===r?r:0),t!==F&&(t=Iu(t),t=t===t?t:0),gt(Iu(n),t,r)},On.clone=function(n){return dt(n,4)},On.cloneDeep=function(n){return dt(n,5)},On.cloneDeepWith=function(n,t){return t=typeof t=="function"?t:F,dt(n,5,t)},On.cloneWith=function(n,t){return t=typeof t=="function"?t:F,dt(n,4,t)},
On.conformsTo=function(n,t){return null==t||bt(n,t,Lu(t))},On.deburr=Tu,On.defaultTo=function(n,t){return null==n||n!==n?t:n},On.divide=rc,On.endsWith=function(n,t,r){n=zu(n),t=jr(t);var e=n.length,e=r=r===F?e:gt(Ou(r),0,e);return r-=t.length,0<=r&&n.slice(r,e)==t},On.eq=hu,On.escape=function(n){return(n=zu(n))&&Y.test(n)?n.replace(H,et):n},On.escapeRegExp=function(n){return(n=zu(n))&&fn.test(n)?n.replace(on,"\\$&"):n},On.every=function(n,t,r){var e=af(n)?o:wt;return r&&ze(n,t,r)&&(t=F),e(n,je(t,3));
},On.find=Po,On.findIndex=Ze,On.findKey=function(n,t){return v(n,je(t,3),Et)},On.findLast=Zo,On.findLastIndex=qe,On.findLastKey=function(n,t){return v(n,je(t,3),Ot)},On.floor=ec,On.forEach=ru,On.forEachRight=eu,On.forIn=function(n,t){return null==n?n:co(n,je(t,3),Uu)},On.forInRight=function(n,t){return null==n?n:ao(n,je(t,3),Uu)},On.forOwn=function(n,t){return n&&Et(n,je(t,3))},On.forOwnRight=function(n,t){return n&&Ot(n,je(t,3))},On.get=Wu,On.gt=of,On.gte=ff,On.has=function(n,t){return null!=n&&ke(n,t,Bt);
},On.hasIn=Bu,On.head=Ke,On.identity=Nu,On.includes=function(n,t,r,e){return n=pu(n)?n:Du(n),r=r&&!e?Ou(r):0,e=n.length,0>r&&(r=Di(e+r,0)),mu(n)?r<=e&&-1<n.indexOf(t,r):!!e&&-1<d(n,t,r)},On.indexOf=function(n,t,r){var e=null==n?0:n.length;return e?(r=null==r?0:Ou(r),0>r&&(r=Di(e+r,0)),d(n,t,r)):-1},On.inRange=function(n,t,r){return t=Eu(t),r===F?(r=t,t=0):r=Eu(r),n=Iu(n),n>=Mi(t,r)&&n<Di(t,r)},On.invoke=Sf,On.isArguments=cf,On.isArray=af,On.isArrayBuffer=lf,On.isArrayLike=pu,On.isArrayLikeObject=_u,
On.isBoolean=function(n){return true===n||false===n||xu(n)&&"[object Boolean]"==zt(n)},On.isBuffer=sf,On.isDate=hf,On.isElement=function(n){return xu(n)&&1===n.nodeType&&!wu(n)},On.isEmpty=function(n){if(null==n)return true;if(pu(n)&&(af(n)||typeof n=="string"||typeof n.splice=="function"||sf(n)||gf(n)||cf(n)))return!n.length;var t=yo(n);if("[object Map]"==t||"[object Set]"==t)return!n.size;if(Le(n))return!Ht(n).length;for(var r in n)if(ci.call(n,r))return false;return true},On.isEqual=function(n,t){return Ft(n,t);
},On.isEqualWith=function(n,t,r){var e=(r=typeof r=="function"?r:F)?r(n,t):F;return e===F?Ft(n,t,F,r):!!e},On.isError=vu,On.isFinite=function(n){return typeof n=="number"&&Li(n)},On.isFunction=gu,On.isInteger=du,On.isLength=yu,On.isMap=pf,On.isMatch=function(n,t){return n===t||Pt(n,t,me(t))},On.isMatchWith=function(n,t,r){return r=typeof r=="function"?r:F,Pt(n,t,me(t),r)},On.isNaN=function(n){return ju(n)&&n!=+n},On.isNative=function(n){if(bo(n))throw new Yu("Unsupported core-js use. Try https://npms.io/search?q=ponyfill.");
return Zt(n)},On.isNil=function(n){return null==n},On.isNull=function(n){return null===n},On.isNumber=ju,On.isObject=bu,On.isObjectLike=xu,On.isPlainObject=wu,On.isRegExp=_f,On.isSafeInteger=function(n){return du(n)&&-9007199254740991<=n&&9007199254740991>=n},On.isSet=vf,On.isString=mu,On.isSymbol=Au,On.isTypedArray=gf,On.isUndefined=function(n){return n===F},On.isWeakMap=function(n){return xu(n)&&"[object WeakMap]"==yo(n)},On.isWeakSet=function(n){return xu(n)&&"[object WeakSet]"==zt(n)},On.join=function(n,t){
return null==n?"":Ui.call(n,t)},On.kebabCase=Cf,On.last=Ge,On.lastIndexOf=function(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e;if(r!==F&&(u=Ou(r),u=0>u?Di(e+u,0):Mi(u,e-1)),t===t){for(r=u+1;r--&&n[r]!==t;);n=r}else n=g(n,b,u,true);return n},On.lowerCase=Df,On.lowerFirst=Mf,On.lt=df,On.lte=yf,On.max=function(n){return n&&n.length?mt(n,Nu,Wt):F},On.maxBy=function(n,t){return n&&n.length?mt(n,je(t,2),Wt):F},On.mean=function(n){return x(n,Nu)},On.meanBy=function(n,t){return x(n,je(t,2))},On.min=function(n){
return n&&n.length?mt(n,Nu,Jt):F},On.minBy=function(n,t){return n&&n.length?mt(n,je(t,2),Jt):F},On.stubArray=Ku,On.stubFalse=Gu,On.stubObject=function(){return{}},On.stubString=function(){return""},On.stubTrue=function(){return true},On.multiply=uc,On.nth=function(n,t){return n&&n.length?tr(n,Ou(t)):F},On.noConflict=function(){return Zn._===this&&(Zn._=pi),this},On.noop=qu,On.now=Jo,On.pad=function(n,t,r){n=zu(n);var e=(t=Ou(t))?T(n):0;return!t||e>=t?n:(t=(t-e)/2,ee(zi(t),r)+n+ee(Ri(t),r))},On.padEnd=function(n,t,r){
n=zu(n);var e=(t=Ou(t))?T(n):0;return t&&e<t?n+ee(t-e,r):n},On.padStart=function(n,t,r){n=zu(n);var e=(t=Ou(t))?T(n):0;return t&&e<t?ee(t-e,r)+n:n},On.parseInt=function(n,t,r){return r||null==t?t=0:t&&(t=+t),$i(zu(n).replace(an,""),t||0)},On.random=function(n,t,r){if(r&&typeof r!="boolean"&&ze(n,t,r)&&(t=r=F),r===F&&(typeof t=="boolean"?(r=t,t=F):typeof n=="boolean"&&(r=n,n=F)),n===F&&t===F?(n=0,t=1):(n=Eu(n),t===F?(t=n,n=0):t=Eu(t)),n>t){var e=n;n=t,t=e}return r||n%1||t%1?(r=Fi(),Mi(n+r*(t-n+$n("1e-"+((r+"").length-1))),t)):cr(n,t);
},On.reduce=function(n,t,r){var e=af(n)?h:m,u=3>arguments.length;return e(n,je(t,4),r,u,oo)},On.reduceRight=function(n,t,r){var e=af(n)?p:m,u=3>arguments.length;return e(n,je(t,4),r,u,fo)},On.repeat=function(n,t,r){return t=(r?ze(n,t,r):t===F)?1:Ou(t),ar(zu(n),t)},On.replace=function(){var n=arguments,t=zu(n[0]);return 3>n.length?t:t.replace(n[1],n[2])},On.result=function(n,t,r){t=Rr(t,n);var e=-1,u=t.length;for(u||(u=1,n=F);++e<u;){var i=null==n?F:n[$e(t[e])];i===F&&(e=u,i=r),n=gu(i)?i.call(n):i;
}return n},On.round=ic,On.runInContext=w,On.sample=function(n){return(af(n)?tt:sr)(n)},On.size=function(n){if(null==n)return 0;if(pu(n))return mu(n)?T(n):n.length;var t=yo(n);return"[object Map]"==t||"[object Set]"==t?n.size:Ht(n).length},On.snakeCase=Tf,On.some=function(n,t,r){var e=af(n)?_:gr;return r&&ze(n,t,r)&&(t=F),e(n,je(t,3))},On.sortedIndex=function(n,t){return dr(n,t)},On.sortedIndexBy=function(n,t,r){return yr(n,t,je(r,2))},On.sortedIndexOf=function(n,t){var r=null==n?0:n.length;if(r){
var e=dr(n,t);if(e<r&&hu(n[e],t))return e}return-1},On.sortedLastIndex=function(n,t){return dr(n,t,true)},On.sortedLastIndexBy=function(n,t,r){return yr(n,t,je(r,2),true)},On.sortedLastIndexOf=function(n,t){if(null==n?0:n.length){var r=dr(n,t,true)-1;if(hu(n[r],t))return r}return-1},On.startCase=$f,On.startsWith=function(n,t,r){return n=zu(n),r=null==r?0:gt(Ou(r),0,n.length),t=jr(t),n.slice(r,r+t.length)==t},On.subtract=oc,On.sum=function(n){return n&&n.length?k(n,Nu):0},On.sumBy=function(n,t){return n&&n.length?k(n,je(t,2)):0;
},On.template=function(n,t,r){var e=On.templateSettings;r&&ze(n,t,r)&&(t=F),n=zu(n),t=jf({},t,e,se),r=jf({},t.imports,e.imports,se);var u,i,o=Lu(r),f=I(r,o),c=0;r=t.interpolate||An;var a="__p+='";r=ti((t.escape||An).source+"|"+r.source+"|"+(r===nn?gn:An).source+"|"+(t.evaluate||An).source+"|$","g");var l="sourceURL"in t?"//# sourceURL="+t.sourceURL+"\n":"";if(n.replace(r,function(t,r,e,o,f,l){return e||(e=o),a+=n.slice(c,l).replace(kn,B),r&&(u=true,a+="'+__e("+r+")+'"),f&&(i=true,a+="';"+f+";\n__p+='"),
e&&(a+="'+((__t=("+e+"))==null?'':__t)+'"),c=l+t.length,t}),a+="';",(t=t.variable)||(a="with(obj){"+a+"}"),a=(i?a.replace(q,""):a).replace(V,"$1").replace(K,"$1;"),a="function("+(t||"obj")+"){"+(t?"":"obj||(obj={});")+"var __t,__p=''"+(u?",__e=_.escape":"")+(i?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+a+"return __p}",t=Pf(function(){return Qu(o,l+"return "+a).apply(F,f)}),t.source=a,vu(t))throw t;return t},On.times=function(n,t){if(n=Ou(n),1>n||9007199254740991<n)return[];
var r=4294967295,e=Mi(n,4294967295);for(t=je(t),n-=4294967295,e=E(e,t);++r<n;)t(r);return e},On.toFinite=Eu,On.toInteger=Ou,On.toLength=Su,On.toLower=function(n){return zu(n).toLowerCase()},On.toNumber=Iu,On.toSafeInteger=function(n){return n?gt(Ou(n),-9007199254740991,9007199254740991):0===n?n:0},On.toString=zu,On.toUpper=function(n){return zu(n).toUpperCase()},On.trim=function(n,t,r){return(n=zu(n))&&(r||t===F)?n.replace(cn,""):n&&(t=jr(t))?(n=$(n),r=$(t),t=z(n,r),r=W(n,r)+1,zr(n,t,r).join("")):n;
},On.trimEnd=function(n,t,r){return(n=zu(n))&&(r||t===F)?n.replace(ln,""):n&&(t=jr(t))?(n=$(n),t=W(n,$(t))+1,zr(n,0,t).join("")):n},On.trimStart=function(n,t,r){return(n=zu(n))&&(r||t===F)?n.replace(an,""):n&&(t=jr(t))?(n=$(n),t=z(n,$(t)),zr(n,t).join("")):n},On.truncate=function(n,t){var r=30,e="...";if(bu(t))var u="separator"in t?t.separator:u,r="length"in t?Ou(t.length):r,e="omission"in t?jr(t.omission):e;n=zu(n);var i=n.length;if(Bn.test(n))var o=$(n),i=o.length;if(r>=i)return n;if(i=r-T(e),1>i)return e;
if(r=o?zr(o,0,i).join(""):n.slice(0,i),u===F)return r+e;if(o&&(i+=r.length-i),_f(u)){if(n.slice(i).search(u)){var f=r;for(u.global||(u=ti(u.source,zu(dn.exec(u))+"g")),u.lastIndex=0;o=u.exec(f);)var c=o.index;r=r.slice(0,c===F?i:c)}}else n.indexOf(jr(u),i)!=i&&(u=r.lastIndexOf(u),-1<u&&(r=r.slice(0,u)));return r+e},On.unescape=function(n){return(n=zu(n))&&J.test(n)?n.replace(G,ut):n},On.uniqueId=function(n){var t=++ai;return zu(n)+t},On.upperCase=Ff,On.upperFirst=Nf,On.each=ru,On.eachRight=eu,On.first=Ke,
Zu(On,function(){var n={};return Et(On,function(t,r){ci.call(On.prototype,r)||(n[r]=t)}),n}(),{chain:false}),On.VERSION="4.17.4",u("bind bindKey curry curryRight partial partialRight".split(" "),function(n){On[n].placeholder=On}),u(["drop","take"],function(n,t){Mn.prototype[n]=function(r){r=r===F?1:Di(Ou(r),0);var e=this.__filtered__&&!t?new Mn(this):this.clone();return e.__filtered__?e.__takeCount__=Mi(r,e.__takeCount__):e.__views__.push({size:Mi(r,4294967295),type:n+(0>e.__dir__?"Right":"")}),e},Mn.prototype[n+"Right"]=function(t){
return this.reverse()[n](t).reverse()}}),u(["filter","map","takeWhile"],function(n,t){var r=t+1,e=1==r||3==r;Mn.prototype[n]=function(n){var t=this.clone();return t.__iteratees__.push({iteratee:je(n,3),type:r}),t.__filtered__=t.__filtered__||e,t}}),u(["head","last"],function(n,t){var r="take"+(t?"Right":"");Mn.prototype[n]=function(){return this[r](1).value()[0]}}),u(["initial","tail"],function(n,t){var r="drop"+(t?"":"Right");Mn.prototype[n]=function(){return this.__filtered__?new Mn(this):this[r](1);
}}),Mn.prototype.compact=function(){return this.filter(Nu)},Mn.prototype.find=function(n){return this.filter(n).head()},Mn.prototype.findLast=function(n){return this.reverse().find(n)},Mn.prototype.invokeMap=lr(function(n,t){return typeof n=="function"?new Mn(this):this.map(function(r){return Dt(r,n,t)})}),Mn.prototype.reject=function(n){return this.filter(su(je(n)))},Mn.prototype.slice=function(n,t){n=Ou(n);var r=this;return r.__filtered__&&(0<n||0>t)?new Mn(r):(0>n?r=r.takeRight(-n):n&&(r=r.drop(n)),
t!==F&&(t=Ou(t),r=0>t?r.dropRight(-t):r.take(t-n)),r)},Mn.prototype.takeRightWhile=function(n){return this.reverse().takeWhile(n).reverse()},Mn.prototype.toArray=function(){return this.take(4294967295)},Et(Mn.prototype,function(n,t){var r=/^(?:filter|find|map|reject)|While$/.test(t),e=/^(?:head|last)$/.test(t),u=On[e?"take"+("last"==t?"Right":""):t],i=e||/^find/.test(t);u&&(On.prototype[t]=function(){function t(n){return n=u.apply(On,s([n],f)),e&&h?n[0]:n}var o=this.__wrapped__,f=e?[1]:arguments,c=o instanceof Mn,a=f[0],l=c||af(o);
l&&r&&typeof a=="function"&&1!=a.length&&(c=l=false);var h=this.__chain__,p=!!this.__actions__.length,a=i&&!h,c=c&&!p;return!i&&l?(o=c?o:new Mn(this),o=n.apply(o,f),o.__actions__.push({func:nu,args:[t],thisArg:F}),new zn(o,h)):a&&c?n.apply(this,f):(o=this.thru(t),a?e?o.value()[0]:o.value():o)})}),u("pop push shift sort splice unshift".split(" "),function(n){var t=ui[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:pop|shift)$/.test(n);On.prototype[n]=function(){var n=arguments;if(e&&!this.__chain__){
var u=this.value();return t.apply(af(u)?u:[],n)}return this[r](function(r){return t.apply(af(r)?r:[],n)})}}),Et(Mn.prototype,function(n,t){var r=On[t];if(r){var e=r.name+"";(Ji[e]||(Ji[e]=[])).push({name:t,func:r})}}),Ji[Xr(F,2).name]=[{name:"wrapper",func:F}],Mn.prototype.clone=function(){var n=new Mn(this.__wrapped__);return n.__actions__=Mr(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=Mr(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=Mr(this.__views__),
n},Mn.prototype.reverse=function(){if(this.__filtered__){var n=new Mn(this);n.__dir__=-1,n.__filtered__=true}else n=this.clone(),n.__dir__*=-1;return n},Mn.prototype.value=function(){var n,t=this.__wrapped__.value(),r=this.__dir__,e=af(t),u=0>r,i=e?t.length:0;n=i;for(var o=this.__views__,f=0,c=-1,a=o.length;++c<a;){var l=o[c],s=l.size;switch(l.type){case"drop":f+=s;break;case"dropRight":n-=s;break;case"take":n=Mi(n,f+s);break;case"takeRight":f=Di(f,n-s)}}if(n={start:f,end:n},o=n.start,f=n.end,n=f-o,
o=u?f:o-1,f=this.__iteratees__,c=f.length,a=0,l=Mi(n,this.__takeCount__),!e||!u&&i==n&&l==n)return kr(t,this.__actions__);e=[];n:for(;n--&&a<l;){for(o+=r,u=-1,i=t[o];++u<c;){var h=f[u],s=h.type,h=(0,h.iteratee)(i);if(2==s)i=h;else if(!h){if(1==s)continue n;break n}}e[a++]=i}return e},On.prototype.at=Fo,On.prototype.chain=function(){return Xe(this)},On.prototype.commit=function(){return new zn(this.value(),this.__chain__)},On.prototype.next=function(){this.__values__===F&&(this.__values__=ku(this.value()));
var n=this.__index__>=this.__values__.length;return{done:n,value:n?F:this.__values__[this.__index__++]}},On.prototype.plant=function(n){for(var t,r=this;r instanceof Sn;){var e=Pe(r);e.__index__=0,e.__values__=F,t?u.__wrapped__=e:t=e;var u=e,r=r.__wrapped__}return u.__wrapped__=n,t},On.prototype.reverse=function(){var n=this.__wrapped__;return n instanceof Mn?(this.__actions__.length&&(n=new Mn(this)),n=n.reverse(),n.__actions__.push({func:nu,args:[Je],thisArg:F}),new zn(n,this.__chain__)):this.thru(Je);
},On.prototype.toJSON=On.prototype.valueOf=On.prototype.value=function(){return kr(this.__wrapped__,this.__actions__)},On.prototype.first=On.prototype.head,Ai&&(On.prototype[Ai]=tu),On}();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(Zn._=it, define(function(){return it})):Vn?((Vn.exports=it)._=it,qn._=it):Zn._=it}).call(this);
-1
View File
File diff suppressed because one or more lines are too long
+36
View File
@@ -0,0 +1,36 @@
package media
type Book struct {
ID int `json:"-"`
Title string `json:"title"`
Authors []string `json:"authors"`
SortAuthor string `json:"sortAuthor"`
ISBN10 string `json:"isbn-10"`
ISBN13 string `json:"isbn-13"`
Format string `json:"format"`
Genre string `json:"genre"`
Publisher string `json:"publisher"`
Series string `json:"series"`
Volume string `json:"volume"`
Year string `json:"year"`
Signed bool `json:"signed"`
Description string `json:"description"`
Notes string `json:"notes"`
CoverURL string `json:"coverURL"`
Childrens bool `json:"childrens"`
}
type Record struct {
ID int `json:"-"`
AlbumName string `json:"name"`
Artists []string `json:"artists"`
SortArtist string `json:"sortArtist"`
Identifier string `json:"identifier"`
Format string `json:"format"`
Genre string `json:"genre"`
Label string `json:"label"`
Year string `json:"year"`
Description string `json:"description"`
CoverURL string `json:"coverURL"`
DiscogsURL string `json:"discogsURL"`
}
-24
View File
@@ -1,24 +0,0 @@
{
"name": "library",
"version": "1.0.0",
"description": "Managing my personal library",
"scripts": {
"start": "node ./index.js"
},
"repository": {
"type": "git",
"url": "https://github.com/deltamualpha/my-library.git"
},
"author": "https://github.com/deltamualpha/",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/deltamualpha/my-library/issues"
},
"homepage": "https://github.com/deltamualpha/my-library",
"dependencies": {
"google-auth-library": "^0.10.0",
"google-books-search": "^0.3.1",
"googleapis": "^19.0.0",
"lodash": "^4.17.4"
}
}
+13
View File
@@ -0,0 +1,13 @@
package query
import (
"fmt"
"git.yetaga.in/alazyreader/library/media"
)
type Amazon struct{}
func (o *Amazon) GetByISBN(isbn string) (*media.Book, error) {
return nil, fmt.Errorf("unimplemented")
}
+28
View File
@@ -0,0 +1,28 @@
package query
import (
"fmt"
"strings"
)
func tryGetFirst(s []string) string {
if len(s) == 0 {
return ""
}
return s[0]
}
func buildTitle(title, subtitle string) string {
if subtitle != "" {
return fmt.Sprintf("%s: %s", title, subtitle)
}
return title
}
func getLastName(author string) string {
names := strings.Split(author, " ")
if len(names) < 2 {
return author
}
return names[len(names)-1]
}
+158
View File
@@ -0,0 +1,158 @@
package query
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"git.yetaga.in/alazyreader/library/media"
)
type GoogleBooks struct{}
type googleBookResult struct {
Kind string `json:"kind"`
TotalItems int `json:"totalItems"`
Items []item `json:"items"`
}
type industryIdentifier struct {
Type string `json:"type"`
Identifier string `json:"identifier"`
}
type readingMode struct {
Text bool `json:"text"`
Image bool `json:"image"`
}
type panelizationSummary struct {
ContainsEpubBubbles bool `json:"containsEpubBubbles"`
ContainsImageBubbles bool `json:"containsImageBubbles"`
}
type imageLink struct {
SmallThumbnail string `json:"smallThumbnail"`
Thumbnail string `json:"thumbnail"`
}
type volumeInfo struct {
Title string `json:"title"`
Subtitle string `json:"subtitle"`
Authors []string `json:"authors"`
Publisher string `json:"publisher"`
PublishedDate string `json:"publishedDate"`
Description string `json:"description"`
IndustryIdentifiers []industryIdentifier `json:"industryIdentifiers"`
ReadingModes readingMode `json:"readingModes"`
PageCount int `json:"pageCount"`
PrintType string `json:"printType"`
Categories []string `json:"categories"`
AverageRating float64 `json:"averageRating"`
RatingsCount int `json:"ratingsCount"`
MaturityRating string `json:"maturityRating"`
AllowAnonLogging bool `json:"allowAnonLogging"`
ContentVersion string `json:"contentVersion"`
PanelizationSummary panelizationSummary `json:"panelizationSummary"`
ImageLinks imageLink `json:"imageLinks"`
Language string `json:"language"`
PreviewLink string `json:"previewLink"`
InfoLink string `json:"infoLink"`
CanonicalVolumeLink string `json:"canonicalVolumeLink"`
}
type saleInfo struct {
Country string `json:"country"`
Saleability string `json:"saleability"`
IsEbook bool `json:"isEbook"`
}
type epub struct {
IsAvailable bool `json:"isAvailable"`
}
type pdf struct {
IsAvailable bool `json:"isAvailable"`
}
type accessInfo struct {
Country string `json:"country"`
Viewability string `json:"viewability"`
Embeddable bool `json:"embeddable"`
PublicDomain bool `json:"publicDomain"`
TextToSpeechPermission string `json:"textToSpeechPermission"`
Epub epub `json:"epub"`
Pdf pdf `json:"pdf"`
WebReaderLink string `json:"webReaderLink"`
AccessViewStatus string `json:"accessViewStatus"`
QuoteSharingAllowed bool `json:"quoteSharingAllowed"`
}
type searchInfo struct {
TextSnippet string `json:"textSnippet"`
}
type item struct {
Kind string `json:"kind"`
ID string `json:"id"`
Etag string `json:"etag"`
SelfLink string `json:"selfLink"`
VolumeInfo volumeInfo `json:"volumeInfo"`
SaleInfo saleInfo `json:"saleInfo"`
AccessInfo accessInfo `json:"accessInfo"`
SearchInfo searchInfo `json:"searchInfo"`
}
func (g *GoogleBooks) GetByISBN(isbn string) (*media.Book, error) {
client := &http.Client{}
resp, err := client.Get(fmt.Sprintf("https://www.googleapis.com/books/v1/volumes?q=isbn:%s", isbn))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("received non-200 status code: %d", resp.StatusCode)
}
var result googleBookResult
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if err = json.Unmarshal(b, &result); err != nil {
return nil, err
}
if len(result.Items) == 0 {
return nil, fmt.Errorf("no book found")
}
return googleToBook(result.Items[0]), nil
}
func googleToBook(i item) *media.Book {
return &media.Book{
Title: buildTitle(i.VolumeInfo.Title, i.VolumeInfo.Subtitle),
Authors: i.VolumeInfo.Authors,
SortAuthor: strings.ToLower(getLastName(tryGetFirst(i.VolumeInfo.Authors))),
ISBN10: getIdentifierType(i.VolumeInfo.IndustryIdentifiers, "ISBN_10"),
ISBN13: getIdentifierType(i.VolumeInfo.IndustryIdentifiers, "ISBN_13"),
Publisher: i.VolumeInfo.Publisher,
Year: strings.Split(i.VolumeInfo.PublishedDate, "-")[0],
Description: i.VolumeInfo.Description,
Genre: tryGetFirst(i.VolumeInfo.Categories),
}
}
func getIdentifierType(iis []industryIdentifier, typename string) string {
for _, ident := range iis {
if ident.Type == typename {
return ident.Identifier
}
}
return ""
}
+11
View File
@@ -0,0 +1,11 @@
package query
import (
"git.yetaga.in/alazyreader/library/media"
)
type Null struct{}
func (o *Null) GetByISBN(isbn string) (*media.Book, error) {
return nil, nil
}
+46
View File
@@ -0,0 +1,46 @@
package query
import (
"strings"
"git.yetaga.in/alazyreader/go-openlibrary/client"
"git.yetaga.in/alazyreader/library/media"
)
type OpenLibrary struct {
client client.Client
}
func (o *OpenLibrary) GetByISBN(isbn string) (*media.Book, error) {
details, err := o.client.GetByISBN(isbn)
if err != nil {
return nil, err
}
return openLibraryToBook(details), nil
}
func openLibraryToBook(details *client.BookDetails) *media.Book {
return &media.Book{
Title: details.Title,
Authors: getAuthors(details.Authors),
SortAuthor: strings.ToLower(getLastName(tryGetFirst(getAuthors(details.Authors)))),
Publisher: getPublisher(details.Publishers),
ISBN10: tryGetFirst(details.Identifiers.ISBN10),
ISBN13: tryGetFirst(details.Identifiers.ISBN13),
}
}
func getPublisher(publishers []client.Publishers) string {
if len(publishers) == 0 {
return ""
}
return publishers[0].Name
}
func getAuthors(authors []client.Authors) []string {
ret := make([]string, len(authors))
for i, author := range authors {
ret[i] = author.Name
}
return ret
}
+2 -27
View File
@@ -1,30 +1,5 @@
# library
set `publicSpreadsheetUrl` in `index.html` to set a sheet
[![build status](https://ci.yetaga.in/api/badges/alazyreader/library/status.svg)](https://ci.yetaga.in/alazyreader/library)
expects the following columns, but easily updated:
* title
* author
* authorLast (used for sorting)
* isbn-10 (not displayed)
* isbn-13
* format
* genre
* publisher
* series
* volume
* year
* coverurl
* description
* notes
* signed (`yes` or blank)
## adding books using the google books API
1. update `SHEET_ID` and `SHEET_NAME` in `index.js`.
2. follow the instructions at <https://developers.google.com/sheets/api/quickstart/nodejs#step_1_turn_on_the_api_name>, and save the resulting json file to `.credentials/client_secret.json`.
3. run `npm install`.
4. run `npm start`.
the google books API is somewhat spotty; searching for a title at https://books.google.com/ will sometimes pull up a book and provide the ISBN they know it by. It also doesn't have comprehensive coverage of various editions of titles, and doesn't return image URLs or format information.
A slowly growing list of most of the media I own.
+3
View File
@@ -0,0 +1,3 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}