Site Builder
Editing:
tools-keywords-good.php
writable 0666
<?php /***************************************************************** * Promptinator — Ultimate Keyword Generator v1.0 (Aug‑2025) *****************************************************************/ declare(strict_types=1); /* ---------- 0. shared cookie helpers ------------------------ */ define('COOKIE','openai_key'); // same name everywhere define('TTL', 30*24*3600); define('SECRET','openai-shared-v1'); $host = $_SERVER['HTTP_HOST'] ?? ''; $root = (!filter_var($host, FILTER_VALIDATE_IP) && preg_match('/([a-z0-9-]+\.[a-z]{2,})$/i',$host,$m)) ? '.'.$m[1] : ''; function enc(string $v):string{ $m='aes-128-ctr'; $k=substr(hash('sha256',SECRET,true),0,16); $iv=random_bytes(openssl_cipher_iv_length($m)); return base64_encode(openssl_encrypt($v,$m,$k,0,$iv)."::{$iv}"); } function dec(string $c):string{ [$ct,$iv]=explode('::',base64_decode($c),2)+['','']; $m='aes-128-ctr';$k=substr(hash('sha256',SECRET,true),0,16); return openssl_decrypt($ct,$m,$k,0,$iv)?:''; } /* ---------- 1. save / delete key --------------------------- */ if($_SERVER['REQUEST_METHOD']==='POST' && isset($_POST['save_key'])){ $raw=trim($_POST['api_key']??''); $ctx=stream_context_create(['http'=>['method'=>'GET','header'=>"Authorization: Bearer $raw\r\n",'timeout'=>6]]); if(@file_get_contents('https://api.openai.com/v1/models',false,$ctx)){ setcookie(COOKIE,enc($raw),time()+TTL,'/',$root,isset($_SERVER['HTTPS']),true); header('Location: '.$_SERVER['REQUEST_URI']);exit; } http_response_code(400);echo'Bad key';exit; } if(isset($_GET['logout'])){ setcookie(COOKIE,'',time()-3600,'/',$root,isset($_SERVER['HTTPS']),true); header('Location: '.$_SERVER['PHP_SELF']);exit; } /* ---------- 2. AJAX --------------------------------------- */ if($_SERVER['REQUEST_METHOD']==='POST' && ($_GET['ajax']??'')==='1'){ header('Content-Type: application/json;charset=utf-8'); $topic = trim($_POST['topic']??''); $count = max(1,min(30,intval($_POST['qty']??10))); $types = $_POST['types']??[]; $model = 'gpt-4o-mini'; $key = isset($_COOKIE[COOKIE])?dec($_COOKIE[COOKIE]):''; if(!$key){echo json_encode(['error'=>'No API key']);exit;} if(!$topic||empty($types)){echo json_encode(['error'=>'Topic & type required']);exit;} /* build prompt */ $prompt="Generate keyword lists for the topic: {$topic}\n" ."Return ONLY pipe‑separated values for each requested TYPE in this format:\n" ."TYPE: kw1|kw2|kw3...\n" ."Make {$count} unique keywords per TYPE, avoiding duplicates across lists.\n" ."Requested TYPES: ".implode(', ',$types); $payload=['model'=>$model,'messages'=>[['role'=>'user','content'=>$prompt]], 'temperature'=>0.4,'max_tokens'=>800]; $ch=curl_init('https://api.openai.com/v1/chat/completions'); curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>1,CURLOPT_POST=>1, CURLOPT_HTTPHEADER=>['Authorization: Bearer '.$key,'Content-Type: application/json'], CURLOPT_POSTFIELDS=>json_encode($payload),CURLOPT_TIMEOUT=>60]); $raw=curl_exec($ch);curl_close($ch); $txt=$raw?json_decode($raw,true)['choices'][0]['message']['content']:''; if(!$txt){echo json_encode(['error'=>'Empty response']);exit;} /* parse to associative array */ $out=[]; foreach(explode("\n",$txt) as $line){ if(!preg_match('/^\s*([A-Za-z ].*?):\s*(.+)$/',$line,$m))continue; $out[$m[1]]=array_map('trim',explode('|',$m[2])); } echo json_encode(['data'=>$out]);exit; } /* ---------- 3. page --------------------------------------- */ $logged=isset($_COOKIE[COOKIE])&&dec($_COOKIE[COOKIE]); ?> <!doctype html><html lang="en"><head> <meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"> <title>Keyword Generator • Promptinator</title> <style> :root{--bg:#f1f4fb;--card:#fff;--dark:#121720;--brand:#004cff;--brand-d:#0841c4; --green:#5af287;--red:#e24d4b;--radius:26px;--shadow:0 6px 30px rgba(0,0,0,.08); font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;font-size:16px} body{margin:0;min-height:100vh;background:var(--bg);display:flex;flex-direction:column} .breadcrumb{background:#ececec;font-weight:600;padding:.7rem 1.2rem}.breadcrumb a{color:var(--brand);text-decoration:none} main{width:100%;max-width:960px;margin:2.4rem auto;padding:0 1.2rem;flex:1;color:#111} .tool-card{background:var(--card);border-radius:var(--radius);box-shadow:var(--shadow);padding:2rem} #bar{background:var(--dark);color:#fff;padding:.9rem 1.2rem;border-radius:var(--radius);display:flex;gap:1rem;flex-wrap:wrap;align-items:center} #bar select,#bar input{border:none;border-radius:8px;padding:.55rem .85rem;font-size:1rem}#bar select{background:#1e2535;color:#fff} .badge{display:flex;align-items:center;gap:.4ch;font-weight:600;font-size:.95rem}.badge i{width:.55rem;height:.55rem;border-radius:50%;background:var(--green)} .badge button{background:none;border:none;color:var(--red);font-size:1.1rem;cursor:pointer} label{font-weight:600;font-size:.95rem;display:flex;flex-direction:column;gap:.4rem} input,select,textarea{width:100%;padding:.7rem 1rem;border:1px solid #ccd4e8;border-radius:10px;font-size:1rem} textarea{resize:vertical;min-height:90px;font-family:ui-monospace,monospace} input:focus,select:focus,textarea:focus{outline:2px solid var(--brand)} .form-grid{display:grid;gap:1.4rem;margin-top:1.8rem} @media(min-width:700px){.form-grid{grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}} .btn{background:var(--brand);color:#fff;border:none;border-radius:10px;padding:.75rem 1.55rem;font-size:1rem;font-weight:600;cursor:pointer} .btn:hover{background:var(--brand-d)}.copyBtn{margin-top:.7rem} .tokenBox{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:.4rem} .token{background:#e8efff;color:#003c9e;padding:.35rem .9rem;border-radius:18px;font-weight:600;position:relative} .token .x{position:absolute;top:-6px;right:-6px;border-radius:50%;background:#e24d4b;color:#fff;width:16px;height:16px;font-size:12px;line-height:16px;text-align:center;cursor:pointer} #spin{display:none;gap:.6ch;align-items:center;margin-top:.8rem}#spin.show{display:flex}@keyframes rot{to{transform:rotate(360deg)}} #spin svg{width:20px;height:20px;animation:rot 1s linear infinite} .tableWrap{overflow-x:auto;margin-top:1.4rem} table{border-collapse:collapse;width:100%}th,td{border:1px solid #ccd4e8;padding:.6rem .8rem;text-align:left;font-size:.95rem} .micBtn{position:absolute;right:.7rem;top:50%;transform:translateY(-50%);background:none;border:none;font-size:1.2rem;cursor:pointer} </style> </head><body> <!-- Breadcrumb --> <nav class="breadcrumb"> <a href="/members/dashboard.php">Dashboard</a> » <a href="/ai-tools/tools.php">AI Toolbox</a> » Keyword Generator <a href="/<?= $_SESSION['slug']??''?>/" style="float:right">View Site</a> </nav> <main> <!-- API bar --> <div id="bar"> <?php if(!$logged): ?> <form method="post" style="display:flex;gap:.7rem;flex-wrap:wrap" autocomplete="off"> <input type="password" name="api_key" placeholder="sk-…" required> <button class="btn" name="save_key">Save Key</button> </form> <?php else: ?> <span class="badge"><i></i> API key saved <button onclick="location='?logout=1'">×</button></span> <?php endif;?> </div> <?php if(!$logged): ?> <div class="tool-card" style="text-align:center;margin-top:2rem"><h2>Get started</h2><p>Save your API key above.</p></div> <?php else: ?> <div class="tool-card"> <h2 style="margin-top:0">Generate SEO keyword sets</h2> <form id="gen" class="form-grid"> <label class="full" style="grid-column:1/-1">Topic / seed idea <div style="position:relative"> <textarea id="topic" name="topic" placeholder="e.g. Lightweight electric bicycles"></textarea> <button type="button" id="mic" class="micBtn">🎤</button> </div> </label> <label>How many per type? <select id="qty" name="qty"> <?php for($i=3;$i<=30;$i++){ echo"<option>$i</option>"; } ?> </select> </label> <label>Add keyword type <select id="typeSel"> <option disabled selected value>— select type —</option> <option>Short‑tail</option><option>Long‑tail</option> <option>Question</option><option>Synonym / LSI</option> <option>Competitor brand</option><option>Trending</option> <option>Local / geo</option> </select> <div id="typeBox" class="tokenBox"></div> </label> <button class="btn" id="go" style="grid-column:1/-1" disabled>Generate keywords</button> <div id="spin"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"/></svg><span id="s">0</span> s</div> </form> <section id="out" style="display:none"> <div class="tableWrap"><table id="tbl"></table></div> <button class="btn copyBtn" id="copyAll" style="display:none">Copy ALL as CSV</button> </section> </div> <?php endif;?> </main> <script> (()=>{ const $=id=>document.getElementById(id); /* --- mic ---------------------------------------------------- */ if($('mic')&&(window.SpeechRecognition||window.webkitSpeechRecognition)){ const R=new (window.SpeechRecognition||window.webkitSpeechRecognition)();R.lang='en-US'; $('mic').onclick=()=>{try{R.start();}catch{}}; R.onresult=e=>{$('topic').value=e.results[0][0].transcript;toggleGo();}; }else $('mic').style.display='none'; /* --- enable button ----------------------------------------- */ function toggleGo(){ $('go').disabled= !$('topic').value.trim() || !$('typeBox').children.length; } $('topic').addEventListener('input',toggleGo); /* --- type token box ---------------------------------------- */ const typeSel=$('typeSel'), tBox=$('typeBox'); typeSel.onchange=()=>{ const txt=typeSel.value;if(!txt)return; if([...tBox.children].some(t=>t.dataset.val===txt)){typeSel.selectedIndex=0;return;} const n=document.createElement('span');n.className='token';n.dataset.val=txt;n.textContent=txt; n.insertAdjacentHTML('beforeend','<span class="x">×</span>'); n.querySelector('.x').onclick=()=>{n.remove();toggleGo();}; tBox.appendChild(n);typeSel.selectedIndex=0;toggleGo(); }; /* pass tokens into FormData */ function addTokens(fd){[...tBox.children].forEach(t=>fd.append('types[]',t.dataset.val));} /* --- AJAX --------------------------------------------------- */ if($('gen')){ $('gen').onsubmit=e=>{ e.preventDefault(); $('go').disabled=true;let sec=0;$('s').textContent=0;$('spin').classList.add('show'); const tick=setInterval(()=>$('s').textContent=++sec,1000); const fd=new FormData($('gen'));addTokens(fd); fetch('?ajax=1&ajax=1',{method:'POST',body:fd}) .then(r=>r.json()).then(j=>{ clearInterval(tick);$('spin').classList.remove('show');$('go').disabled=false; if(j.error){alert(j.error);return;} renderTable(j.data); }).catch(err=>{clearInterval(tick);$('spin').classList.remove('show');$('go').disabled=false;alert(err);}); }; } /* --- render / copy helpers --------------------------------- */ function renderTable(data){ const tbl=$('tbl');tbl.innerHTML=''; const rows=Object.entries(data); if(!rows.length){alert('No keywords returned');return;} // header const thead=document.createElement('tr'); thead.innerHTML='<th>Type</th><th>Keywords</th><th>Copy CSV</th>';tbl.appendChild(thead); // body rows.forEach(([type,list])=>{ const tr=document.createElement('tr'); const csv=list.join(', '); tr.innerHTML=`<td>${type}</td><td>${list.join('<br>')}</td> <td><button class="copyBtn" data-csv="${csv}">Copy</button></td>`; tbl.appendChild(tr); }); tbl.querySelectorAll('button.copyBtn').forEach(btn=>{ btn.onclick=()=>navigator.clipboard.writeText(btn.dataset.csv).then(()=>flash(btn)); }); // copy all $('copyAll').style.display='block'; $('copyAll').onclick=()=>navigator.clipboard.writeText(rows.map(r=>r[1].join(', ')).join(', ')).then(()=>flash($('copyAll'))); $('out').style.display=''; $('tbl').scrollIntoView({behavior:'smooth'}); } function flash(btn){const t=btn.textContent;btn.textContent='✔ Copied';setTimeout(()=>btn.textContent=t,1200);} })(); </script> </body></html>
Save changes
Create folder
writable 0777
Create
Cancel