[object Object]

← back to Linkedin Like Helper

initial scaffold: LinkedIn manual like helper (batch-open, group size 1-all, progress in localStorage)

b5303bb9a1c30c0806eb4648b7b7a3f4b1f9be9c · 2026-06-30 11:24:21 -0700 · Steve

Files touched

Diff

commit b5303bb9a1c30c0806eb4648b7b7a3f4b1f9be9c
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Jun 30 11:24:21 2026 -0700

    initial scaffold: LinkedIn manual like helper (batch-open, group size 1-all, progress in localStorage)
---
 .gitignore |   8 +++
 index.html | 204 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 212 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1924158
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..9f8b355
--- /dev/null
+++ b/index.html
@@ -0,0 +1,204 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>LinkedIn Like Helper — manual, TOS-safe</title>
+<style>
+  :root{
+    --bg:#f3f2ef; --card:#fff; --ink:#1d2226; --muted:#56687a;
+    --li:#0a66c2; --li-dark:#004182; --line:#d7dce1; --done:#057642;
+    --shadow:0 1px 3px rgba(0,0,0,.08),0 1px 2px rgba(0,0,0,.04);
+  }
+  *{box-sizing:border-box}
+  body{margin:0;font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
+       background:var(--bg);color:var(--ink);padding:24px}
+  .wrap{max-width:820px;margin:0 auto}
+  header{display:flex;align-items:center;gap:12px;margin-bottom:8px}
+  header .badge{width:34px;height:34px;border-radius:7px;background:var(--li);color:#fff;
+       display:grid;place-items:center;font-weight:800;font-size:18px}
+  h1{font-size:21px;margin:0;font-weight:700}
+  .sub{color:var(--muted);margin:2px 0 20px;font-size:13.5px}
+  .card{background:var(--card);border:1px solid var(--line);border-radius:10px;box-shadow:var(--shadow);
+        padding:18px 20px;margin-bottom:16px}
+  label{font-weight:600;font-size:13px;display:block;margin-bottom:6px}
+  textarea{width:100%;min-height:140px;border:1px solid var(--line);border-radius:8px;padding:11px 12px;
+        font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;resize:vertical;color:var(--ink)}
+  .row{display:flex;flex-wrap:wrap;gap:14px;align-items:flex-end}
+  .grp{flex:1 1 200px}
+  select,input[type=number]{width:100%;padding:9px 11px;border:1px solid var(--line);border-radius:8px;
+        font-size:14px;background:#fff;color:var(--ink)}
+  .btns{display:flex;gap:10px;flex-wrap:wrap;margin-top:4px}
+  button{border:0;border-radius:999px;padding:10px 18px;font-size:14px;font-weight:600;cursor:pointer}
+  .primary{background:var(--li);color:#fff}
+  .primary:hover{background:var(--li-dark)}
+  .ghost{background:#fff;color:var(--li);border:1.5px solid var(--li)}
+  .ghost:hover{background:#eaf3fb}
+  .danger{background:#fff;color:#b11b1b;border:1.5px solid #e3c4c4}
+  .danger:hover{background:#fdeeee}
+  .stat{display:flex;gap:22px;margin:14px 0 4px;font-size:13.5px;color:var(--muted);flex-wrap:wrap}
+  .stat b{color:var(--ink)}
+  .bar{height:8px;background:#e6eaee;border-radius:999px;overflow:hidden;margin:10px 0 2px}
+  .bar>i{display:block;height:100%;background:var(--done);width:0;transition:width .3s}
+  ul.list{list-style:none;margin:8px 0 0;padding:0;max-height:340px;overflow:auto;border-top:1px solid var(--line)}
+  ul.list li{display:flex;align-items:center;gap:10px;padding:9px 4px;border-bottom:1px solid #eef1f4;font-size:13.5px}
+  ul.list li.done{opacity:.5}
+  ul.list li.done .url{text-decoration:line-through}
+  .num{width:26px;color:var(--muted);text-align:right;flex:0 0 auto;font-variant-numeric:tabular-nums}
+  .url{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+  .url a{color:var(--li);text-decoration:none}
+  .pill{flex:0 0 auto;font-size:11px;font-weight:700;color:var(--done);border:1px solid #bfe3cd;
+        background:#eafaf0;border-radius:999px;padding:2px 8px}
+  .mini{flex:0 0 auto;background:#fff;border:1px solid var(--line);border-radius:6px;padding:4px 9px;
+        font-size:12px;font-weight:600;cursor:pointer;color:var(--ink)}
+  .note{font-size:12.5px;color:var(--muted);background:#fffbe9;border:1px solid #f1e3a8;border-radius:8px;
+        padding:10px 12px;margin-top:14px}
+  .tos{font-size:12px;color:var(--muted);margin-top:18px;text-align:center}
+</style>
+</head>
+<body>
+<div class="wrap">
+  <header>
+    <div class="badge">in</div>
+    <div>
+      <h1>LinkedIn Like Helper</h1>
+      <div class="sub">Opens posts in batches so <b>you</b> like them by hand. No automation — fully within LinkedIn's terms.</div>
+    </div>
+  </header>
+
+  <div class="card">
+    <label for="urls">Post / profile links — one per line</label>
+    <textarea id="urls" placeholder="https://www.linkedin.com/feed/update/urn:li:activity:7300000000000000000/&#10;https://www.linkedin.com/posts/somebody_xyz-activity-7300000000000000000-abcd/&#10;…paste as many as you like"></textarea>
+    <div class="btns" style="margin-top:12px">
+      <button class="ghost" onclick="loadList()">Load list ↻</button>
+      <button class="danger" onclick="resetProgress()">Reset progress</button>
+    </div>
+  </div>
+
+  <div class="card">
+    <div class="row">
+      <div class="grp">
+        <label for="size">Group size</label>
+        <select id="size" onchange="syncCustom()">
+          <option value="1">1 at a time</option>
+          <option value="3">3</option>
+          <option value="5" selected>5</option>
+          <option value="10">10</option>
+          <option value="25">25</option>
+          <option value="all">All remaining</option>
+          <option value="custom">Custom…</option>
+        </select>
+      </div>
+      <div class="grp" id="customWrap" style="display:none">
+        <label for="custom">Custom count</label>
+        <input type="number" id="custom" min="1" value="8">
+      </div>
+      <div class="grp" style="flex:0 0 auto">
+        <button class="primary" onclick="openNext()">▶ Open next group</button>
+      </div>
+    </div>
+
+    <div class="stat">
+      <span>Total: <b id="cTotal">0</b></span>
+      <span>Done: <b id="cDone">0</b></span>
+      <span>Remaining: <b id="cLeft">0</b></span>
+    </div>
+    <div class="bar"><i id="barFill"></i></div>
+
+    <ul class="list" id="list"></ul>
+
+    <div class="note">
+      <b>First time:</b> Chrome will ask to <i>“allow multiple pop-ups”</i> — click Allow once.
+      Each opened tab is a real LinkedIn page; click the 👍 Like yourself, then close the tab.
+      A batch auto-marks as done when you open it (or use “done” / “Reset progress” to adjust).
+    </div>
+  </div>
+
+  <div class="tos">Manual assist only · your clicks, your account · honors the dw-linkedin “never auto-act” rule</div>
+</div>
+
+<script>
+const KEY = 'lk_like_helper_v1';
+let items = [];   // {url, done}
+
+function load(){ try{ return JSON.parse(localStorage.getItem(KEY)) || {} }catch(e){ return {} } }
+function save(state){ localStorage.setItem(KEY, JSON.stringify(state)); }
+
+function loadList(){
+  const lines = document.getElementById('urls').value
+    .split(/\n+/).map(s=>s.trim()).filter(Boolean);
+  // de-dupe, preserve order
+  const seen = new Set(); const urls = [];
+  for(const u of lines){ if(!seen.has(u)){ seen.add(u); urls.push(u); } }
+  const prev = load(); const doneMap = prev.done || {};
+  items = urls.map(u => ({url:u, done: !!doneMap[u]}));
+  persist(); render();
+}
+
+function persist(){
+  const doneMap = {}; items.forEach(i=>{ if(i.done) doneMap[i.url]=1; });
+  save({ raw: document.getElementById('urls').value, done: doneMap });
+}
+
+function syncCustom(){
+  document.getElementById('customWrap').style.display =
+    document.getElementById('size').value==='custom' ? '' : 'none';
+}
+
+function groupSize(){
+  const v = document.getElementById('size').value;
+  if(v==='all') return Infinity;
+  if(v==='custom') return Math.max(1, parseInt(document.getElementById('custom').value)||1);
+  return parseInt(v);
+}
+
+function openNext(){
+  if(!items.length){ loadList(); }
+  const pending = items.filter(i=>!i.done);
+  if(!pending.length){ alert('All done 🎉  Use “Reset progress” to go again.'); return; }
+  const n = Math.min(groupSize(), pending.length);
+  const batch = pending.slice(0, n);
+  batch.forEach((it, idx) => {
+    // tiny stagger so the browser doesn't drop simultaneous opens
+    setTimeout(()=>window.open(it.url, '_blank', 'noopener'), idx*120);
+    it.done = true;
+  });
+  persist(); render();
+}
+
+function toggleDone(url){
+  const it = items.find(i=>i.url===url); if(it){ it.done=!it.done; persist(); render(); }
+}
+
+function resetProgress(){
+  items.forEach(i=>i.done=false); persist(); render();
+}
+
+function render(){
+  const total = items.length, done = items.filter(i=>i.done).length;
+  document.getElementById('cTotal').textContent = total;
+  document.getElementById('cDone').textContent  = done;
+  document.getElementById('cLeft').textContent  = total-done;
+  document.getElementById('barFill').style.width = total? (done/total*100)+'%' : '0';
+  const ul = document.getElementById('list'); ul.innerHTML='';
+  items.forEach((it,i)=>{
+    const li=document.createElement('li'); if(it.done) li.className='done';
+    li.innerHTML =
+      '<span class="num">'+(i+1)+'</span>'+
+      '<span class="url"><a href="'+it.url+'" target="_blank" rel="noopener">'+it.url+'</a></span>'+
+      (it.done?'<span class="pill">done</span>':'')+
+      '<button class="mini" data-u="'+encodeURIComponent(it.url)+'">'+(it.done?'undo':'done')+'</button>';
+    ul.appendChild(li);
+  });
+  ul.querySelectorAll('.mini').forEach(b=>b.onclick=()=>toggleDone(decodeURIComponent(b.dataset.u)));
+}
+
+// restore on open
+(function init(){
+  const prev = load();
+  if(prev.raw){ document.getElementById('urls').value = prev.raw; loadList(); }
+  syncCustom();
+})();
+</script>
+</body>
+</html>

(oldest)  ·  back to Linkedin Like Helper  ·  add Feed-pages mode: batch-open curated LinkedIn streams (ho d974f37 →