()
| 99 | @limit_server(["ol-www0"], scheduler) |
| 100 | @scheduler.scheduled_job("interval", seconds=60) |
| 101 | async def monitor_partner_useragents(): |
| 102 | |
| 103 | def graphite_safe(s: str) -> str: |
| 104 | """Normalize a string for safe use as a Graphite metric name.""" |
| 105 | # Replace dots and spaces with underscores |
| 106 | s = s.replace(".", "_").replace(" ", "_") |
| 107 | # Remove or replace unsafe characters |
| 108 | s = re.sub(r"[^A-Za-z0-9_-]+", "_", s) |
| 109 | # Collapse multiple underscores |
| 110 | s = re.sub(r"_+", "_", s) |
| 111 | # Strip leading/trailing underscores or dots |
| 112 | return s.strip("._") |
| 113 | |
| 114 | def extract_agent_counts(ua_counts, allowed_names=None): |
| 115 | agent_counts = {} |
| 116 | for ua in ua_counts.strip().split("\n"): |
| 117 | count, agent, *_ = ua.strip().split(" ") |
| 118 | count = int(count) |
| 119 | agent_name = graphite_safe(agent.split("/")[0]) |
| 120 | if not allowed_names or agent_name in allowed_names: |
| 121 | agent_counts[agent_name] = count |
| 122 | else: |
| 123 | agent_counts.setdefault("other", 0) |
| 124 | agent_counts["other"] += count |
| 125 | return agent_counts |
| 126 | |
| 127 | known_names = extract_agent_counts(""" |
| 128 | 4307 Bontent/1.0 (https://bontent.app; ***@bontent.app) |
| 129 | 403 Research-Cover-Scraper (***@cornell.edu) |
| 130 | 309 BookshopLT/1.0 (***@gmail.com) |
| 131 | 271 BookReadingTime/2.0 (https://bookreadingtime.com; ***@bookreadingtime.com) |
| 132 | 244 Roolio/1.0 (https://roolio.app; contact: ***@roolio.app) |
| 133 | 182 TooManyBooks/1.0 (***@gmail.com) |
| 134 | 180 CourseworkBot/1.0 (coursework@local) |
| 135 | 180 BookScraper/1.0 (data collection project; contact@example.com) |
| 136 | 180 librimondo-pim/1.0 (https://librimondo.com; ***@fkwt.pl) |
| 137 | 180 WikiNerd (***@wikinerd.com.br) |
| 138 | 179 Blurbit/1.0 (***@blurbit.com) |
| 139 | 179 isbn-book-crawler/1.0 (educational) |
| 140 | 179 GoodreadsEnricher/1.0 (+***@***) |
| 141 | 177 CourseProjectAPI/1.0 (your@email.com) |
| 142 | 177 Whefi/1.0 (***@whefi.com) |
| 143 | 176 Tomeki/1.0 (***@yopmail.com , gzip) |
| 144 | 173 afin-app/1.0 (taste recommendation; ***@gmail.com) |
| 145 | 165 Pinakes-POC/0.1 (SNU Library; mailto:***@library.snu.ac.kr) |
| 146 | 160 BookEnricher/1.0 (your@email.com) |
| 147 | 153 OrelhaDoLivro/1.0 (***@orelhadelivro.com.br; openlibrary-summary) |
| 148 | 129 TimberdoodleReading/0.1 (***@timberdoodle.com) |
| 149 | 126 KiveoAPI/1.0 (***@kiveo.app) |
| 150 | 111 BookInClub/1.0 (https://bookinclub.com; ***@bookinclub.com) node.js/axios |
| 151 | 108 WonderclubBot/1.0 (wonderclub.com) |
| 152 | 107 BookDirectoryBot/1.0 (contact: ***@gmail.com) |
| 153 | 107 Shellf.app (***@shellf.app) |
| 154 | 102 AwarioSmartBot/1.0 (+https://awario.com/bots.html; ***@awario.com) |
| 155 | 85 Bookhives/1.0 (***@gmail.com) |
| 156 | 85 AliyunSecBot/Aliyun (***@service.alibaba.com) |
| 157 | 84 ASCENDCHESS/1.0 (Chess Training Platform; ***@ascendchess.com) |
| 158 | 78 PeoopleEnrichmentBot/1.0 (contact: ***@peoople.app) |
nothing calls this directly
no test coverage detected