Skip to content

Instantly share code, notes, and snippets.

@eneuhauser
Last active October 14, 2024 14:26
Show Gist options
  • Select an option

  • Save eneuhauser/c5d6bb4ce500f702fe3abbcabd0db3d6 to your computer and use it in GitHub Desktop.

Select an option

Save eneuhauser/c5d6bb4ce500f702fe3abbcabd0db3d6 to your computer and use it in GitHub Desktop.
Stravduro

Stravduro

Turn your group activity into an enduro event by selecting which segments to award points to the top finishers to see the total points across those segments. Enjoy the camaraderie of sticking together for most of the event with incentives to push harder for certain segments to be the overall winner. If you're hosting an event, list which segments are worth points ahead of time for athletes to star the segments to show up on their watch / phone / computer.

This currently gives points to the top ten finishers for a segment for the current day. First place gets ten point, second gets nine, etc. Athletes' points will be added up for all the segments to display the rankings of everyone that had a top ten finish for any of the segments.

Installing

  1. Copy the contents of bookmarklet.js to your clipboard
  2. Create a new bookmark and paste the contents as the URL

Using

  1. Go to an activity that has a lot of efforts for today (like a group ride)
  2. If you want to include a hidden segment, click the "Show [x] hidden efforts" link at the bottom of the efforts
  3. Click the newly created bookmark. This will turn the stars, next to the segments, into checkboxes.
  4. Select which segments you would like to include
  5. Click the 🥇 icon at the top of the column
  6. Toggle between men and women to see the overall winner
  7. Select a specific segment to see the top finishers for that segment
  8. Raw data is outputted into the JavaScript console as CSV. Copy the content, create a CSV file, and open the file in Excel to save the results

Tips

  1. At the end of the bookmarklet is 'today'. This filters the results to the current date. You can change it to 'this_week' or 'this_month'
  2. At the end of the bookmarklet is a list of numbers which represents the points for the respective place. You can change this for your own point system. '50,25,10' would only award points to the first 3 places with the given values.
  3. The stravduro.js is the unminified version

Roadmap

  1. Turn into a browser extension and host on the web store
  2. Initiating Stravduro should automatically check starred segments
  3. Manage time range in the pop-up (i.e. today, this_week, this_month)
  4. Manage point values in the pop-up
  5. Manage custom point values per segment
  6. Allow results to be limited to a specific club
  7. Add download CSV button to the pop-up
javascript:((t,n)=>{const r=["M","F"];async function a(t){const n=document.querySelector("meta[name=csrf-token]").content;try{const r=await fetch(t,{headers:{Accept:"application/javascript","X-CSRF-Token":n,"X-Requested-With":"XMLHttpRequest"}});if(r.ok)return r.json();const a=await r.text();r.status;throw alert(e),e}catch(t){throw t}}function o(t){return t?.stopPropagation(),!1}function d(t){const e="stravduro_show",n=t.elements.segmentId.value,r=t.elements.gender.value;[...document.getElementsByClassName(e)].forEach((t=>t.classList.remove(e))),document.getElementById(`stravduro-${n}-${r}`).classList.add(e)}function s(t){const e=t?.querySelector(".starred-col")??null;if(null===e)return;const n=t.attributes["data-segment-effort-id"]?.value??null;e.innerHTML=n?`<input type="checkbox" name="stravduro-effortId" value="${n}" />`:"",e.querySelector("input").addEventListener("click",o,!1)}function i(t){const e=document.querySelector(t);if(!e)return;const n=e.querySelector("thead .starred-col");n&&(n.innerHTML='<button id="stravduro-go">&#127941;</button>',document.getElementById("stravduro-go").addEventListener("click",h));[...e.querySelectorAll("tbody tr")].forEach(s)}function c(t,e){const n=document.createElement("option");return n.innerHTML=t,n.value=e,n}function l(t){const e=document.getElementById("stravduro-header");e.innerHTML='<form><input type="radio" name="gender" value="M" checked>Men</input><input type="radio" name="gender" value="F">Women</input></form>';const n=e.querySelector("form"),r=function(t){const e=document.createElement("select");return e.setAttribute("name","segmentId"),e.appendChild(c("Overall","all")),t.forEach((t=>e.appendChild(c(t.name,t.id)))),e}(t);return n.insertBefore(r,n.firstChild),n.addEventListener("change",(t=>d(n))),n.addEventListener("submit",(t=>t.preventDefault())),n}function u(t,e){const n=l(e),a=document.getElementById("stravduro-results");r.forEach((e=>function(t,e,n){const r=e.filter((t=>t.gender===n)).map((t=>`<tr><td>${t.activity.rank}</td><td>${t.name}</td><td>${t.activity.points}</td></tr>`)).join(""),a=document.createElement("div");a.setAttribute("id",`stravduro-all-${n}`),a.classList.add("stravduro_results"),a.innerHTML=`<table><thead><tr><th>Rank</th><th>Name</th><th>Points</th></tr></thead><tbody>${r}</tbody></table>`,t.appendChild(a)}(a,t,e))),e.forEach((({efforts:t})=>r.forEach((e=>function(t,e,n){const[r]=e,a=e.filter((t=>t.athlete.gender===n)).sort(((t,e)=>t.effort.rank>e.effort.rank?1:-1)).map((t=>`<tr><td>${t.effort.rank}</td><td>${t.athlete.name}</td><td>${t.effort.date}</td><td>${t.effort.speed}</td><td>${t.effort.hr??""}</td><td>${t.effort.watts??""}</td><td>${t.effort.time}</td><td>${t.effort.points}</td></tr>`)).join(""),o=document.createElement("div");o.setAttribute("id",`stravduro-${r.segment.id}-${n}`),o.classList.add("stravduro_results"),o.innerHTML=`<table><thead><tr><th>Rank</th><th>Name</th><th>Date</th><th>Speed</th><th>HR</th><th>Power</th><th>Time</th><th>Points</th></tr></thead><tbody>${a}</tbody></table>`,t.appendChild(o)}(a,t,e))))),d(n)}function f(t,e){return[...e.reduce(((e,n)=>{const r=n[t],{efforts:a=[]}=e.get(r.id)??{};return e.set(r.id,{...r,efforts:[...a,n]}),e}),new Map).values()]}async function m(t,e,n){const o=await async function(t,e){const n=await a(`/segment_efforts/${t}`);return{id:n.segment_id,name:n.name,points:e}}(t,e),d=await Promise.all(r.map((t=>async function(t,e,n,r){const o=Math.max(e.length,10),d=await a(`/segments/${t.id}/leaderboard?raw=true&page=1&per_page=${o}&viewer_context=false&date_range=${n}&filter=overall&gender=${r}`),s=t=>null===t?null:Math.round(t);return d.top_results.map((n=>({segment:t,athlete:{id:n.athlete_id,name:n.display_name,photo:n.photo,gender:r},effort:{id:n.id,rank:n.rank,date:n.start_date_local,speed:n.avg_speed,hr:s(n.avg_heart_rate),watts:s(n.avg_watts),time:n.elapsed_time,seconds:n.elapsed_time_raw,points:e[n.rank-1]??0}})))}(o,e,n,t))));return d.flat()}async function p(t,e){const n=[...document.getElementsByName("stravduro-effortId")].filter((t=>t.checked)).map((t=>t.value));if(0===n.length)return alert("Select at least 1 segment");return(await Promise.all(n.map((n=>m(n,t,e))))).flat()}async function h(){const e=function(t){const e=new RegExp(/\d+/);return t.replace(/[^\d]+/g,":").split(":").filter((t=>e.test(t))).map((t=>parseInt(t,10)))}(t);document.getElementById("stravduro").showModal();const a=await p(e,n),o=r.map((t=>function(t,e){return[...f("athlete",t.filter((t=>t.athlete.gender===e))).map((t=>function(t){const e=t.efforts.reduce(((t,e)=>t+e.effort.points),0),{activity:n={}}=t;return{...t,activity:{...n,points:e}}}(t))).reduce(((t,e)=>{const{points:n=0}=e.activity??{},r=t.get(n)??[];return t.set(n,[...r,e]),t}),new Map).values()].sort(((t,e)=>t[0].activity.points>e[0].activity.points?-1:1)).reduce(((t,e)=>{const n=t.length+1;return[t,e.map((t=>{const{activity:e={}}=t;return{...t,activity:{...e,rank:n}}}))].flat()}),[])}(a,t))).flat();u(o,f("segment",a)),console.log(function(t){return[["athlete_id","athlete_name","athlete_gender","segment_id","segment_name","effort_id","effort_rank","effort_date","effort_seconds","effort_hr","effort_watts","effort_points","activity_rank","activity_points"],...t.map((t=>t.efforts.map((e=>({...e,activity:t.activity}))))).flat().map((e=>[e.athlete.id,e.athlete.name.replace(/,/g,""),e.athlete.gender,e.segment.id,e.segment.name.replace(/,/g,""),e.effort.id,e.effort.rank,e.effort.date.replace(/,/g,""),e.effort.seconds,e.effort.hr??"",e.effort.watts??"",e.effort.points??0,e.activity.rank??t.length,e.activity.points??0]))].map((t=>t.join(","))).join("\n")}(o))}!function(){const t=document.createElement("style");t.innerHTML="#stravduro { min-width: 350px; }#stravduro form input { margin: 5px; }.stravduro_close {display: block;position: absolute;top: 0;right: 0;background-color: transparent;border: 0;font-size: 1.5rem;}.stravduro_results { display: none; }.stravduro_show { display: block; }",document.head.appendChild(t);const e='<p class="stravduro_results stravduro_show">Loading...</p>',n=document.createElement("div");n.innerHTML=`<dialog id="stravduro"><button class="stravduro_close">x</button><div id="stravduro-header">${e}</div><div id="stravduro-results"></div></dialog>`,document.body.appendChild(n),document.querySelector(".stravduro_close").addEventListener("click",(()=>{document.getElementById("stravduro").close(),document.getElementById("stravduro-header").innerHTML=e,document.getElementById("stravduro-results").innerHTML=""}))}(),i("table.segments"),i("table.hidden-segments")})("10,9,8,7,6,5,4,3,2,1","today");
View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment