Turn a landing-page PDF (the two-column Canva format, page 1) into a campaign in the Hashbun system, in two monitored steps:
STEP 1 extract -> you add dropdown options -> STEP 2 review & push
Images are not pushed (you add the logo/hero in the UI afterwards); they are still saved locally for reference.
- Open this folder in PyCharm.
- Accept the offer to create a virtual environment from
requirements.txt(manual:python -m venv .venv, activate,pip install -r requirements.txt). - Copy
config.example.pytoconfig.pyand paste your Bearer token.config.pyis gitignored.
- Drop your landing-page PDFs into
pdfs/. - Run
step1_extract.py.
For each PDF you get output/<uri>/ containing:
<uri>.txt— everything extracted, human-readablelogo.png,hero.png— the two images (manual upload)_extraction.json— machine state for step 2 (don't edit)- one empty options file per detected dropdown, e.g.
pays.json
The console lists exactly which dropdown files to fill, with their paths.
Maintain one file at the project root, countries.json, with your
country list:
[
{ "code": 1, "value": "United States" },
{ "code": 2, "value": "France" }
]Any country field (Pays / Country / Land / 国 …) in any campaign uses this list
automatically — you do not create a per-campaign country file. Update it
once per batch when the country list changes. (countries.example.json shows
the format.)
If a country field is found but countries.json is empty or missing, the push
stops with a warning for that campaign, so it can never silently become a
text box.
For non-country dropdowns, step 1 drops an empty <field>.json in the campaign
folder and tells you its name. Paste options in the same format. Leave it empty
([]) and that field stays a text box.
Run step2_push.py. For each extracted campaign it:
- Builds the uri from the title (lower-cased, spaces -> hyphens).
- Checks your live system for that uri. If it already exists, it appends
the current month and year, e.g.
apples-oranges->apples-oranges-jun-26(and-2,-3, ... if that is taken too). - Shows the field mapping, the images/styles that will be attached, and the full payload.
- On
y, it uploads the images, then POSTs the campaign live and prints the new id and URL.
If hero.png / logo.png are present in the campaign folder, they are uploaded
to your backend (POST /image/) on push:
- hero is uploaded with
altText = <uri>(so the Cloudinary file is unique). - logo is uploaded with
altText = logo-<6 random chars>.
The returned ids are attached to the campaign's image / logo. If an image
is missing or the upload fails, the campaign is still created without it.
Every campaign gets the CSS in default_styles.css (project root) in its
styles field. Edit that one file to change the default for all future pushes.
| Field | Becomes |
|---|---|
| label with an options file | DropDown (options from the file) |
| label that looks like email | Email |
| consent checkbox | CheckBox, name = consent HTML with <a target="_blank"> |
| everything else | TextBox |
All fields are marked required (from the * on the label).
hashbun_campaign_tool/
step1_extract.py run first
step2_push.py run after filling dropdown files
config.example.py copy to config.py and add your token
requirements.txt
pdfs/ <- input PDFs
output/ <- per-campaign folders land here
hashbun/
extract.py page-1 PDF -> txt/images/state + dropdown templates
mapping.py state + option files -> campaign payload
api.py uri uniqueness check + POST
util.py uri slug + month/year suffix
cli.py the two-step flow
- Image export needs PyMuPDF (in requirements); if missing, the tool still runs and skips images.
- Text extraction uses pdfplumber — no external binaries.