mirror of
https://github.com/Labelbase/Labelbase.git
synced 2026-08-20 13:28:18 +02:00
re-added importer
This commit is contained in:
parent
57f0bc492f
commit
1aed9938cb
13 changed files with 351 additions and 20 deletions
0
django/importer/__init__.py
Normal file
0
django/importer/__init__.py
Normal file
6
django/importer/apps.py
Normal file
6
django/importer/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ImporterConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "importer"
|
||||
17
django/importer/forms.py
Normal file
17
django/importer/forms.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from django import forms
|
||||
|
||||
IMPORTER_CHOICES = (
|
||||
("BIP-0329", "BIP-329 .jsonl"),
|
||||
#("BIP-0329-7z-enc" , "BIP-329 (encrypted) .7z"),
|
||||
("csv-bluewallet", "BlueWallet .csv"),
|
||||
("csv-bitbox", "BitBox .csv"),
|
||||
("pocket-accointing", "Pocket Accointing .csv")
|
||||
)
|
||||
|
||||
|
||||
class UploadFileForm(forms.Form):
|
||||
labelbase_id = forms.IntegerField(widget=forms.HiddenInput())
|
||||
import_type = forms.ChoiceField(
|
||||
choices=IMPORTER_CHOICES #, widget=forms.HiddenInput()
|
||||
)
|
||||
file = forms.FileField()
|
||||
49
django/importer/pocket.py
Normal file
49
django/importer/pocket.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import csv
|
||||
import json
|
||||
from labelbase.models import Label
|
||||
from labelbase.models import Labelbase
|
||||
|
||||
|
||||
def validate_csv_format(csv_file_path):
|
||||
expected_sequence = ['withdraw', 'order', 'deposit']
|
||||
with open(csv_file_path, newline='') as csvfile:
|
||||
csv_reader = csv.reader(csvfile)
|
||||
next(csv_reader) # Skip the header
|
||||
for row in csv_reader:
|
||||
if len(row) != 11 or row[0] not in expected_sequence:
|
||||
return False
|
||||
expected_index = expected_sequence.index(row[0])
|
||||
if expected_index == 0 and expected_index != 0:
|
||||
return False
|
||||
expected_sequence.append(expected_sequence.pop(0))
|
||||
return True
|
||||
|
||||
|
||||
def parse_csv_to_json(csv_file_path):
|
||||
json_list = []
|
||||
with open(csv_file_path, newline='') as csvfile:
|
||||
csv_reader = csv.reader(csvfile)
|
||||
next(csv_reader) # Skip the header
|
||||
group = []
|
||||
for row in csv_reader:
|
||||
if row[0] == 'withdraw':
|
||||
if group:
|
||||
json_list.append(group)
|
||||
group = []
|
||||
data = {
|
||||
'transactionType': row[0],
|
||||
'date': row[1],
|
||||
'inBuyAmount': row[2],
|
||||
'inBuyAsset': row[3],
|
||||
'outSellAmount': row[4],
|
||||
'outSellAsset': row[5],
|
||||
'feeAmount': row[6] if row[6] else "",
|
||||
'feeAsset': row[7] if row[7] else "",
|
||||
'classification': row[8] if row[8] else "",
|
||||
'operationId': row[9] if row[9] else "",
|
||||
'comments': row[10] if row[10] else ""
|
||||
}
|
||||
group.append(data)
|
||||
if group:
|
||||
json_list.append(group)
|
||||
return json_list
|
||||
0
django/importer/templatetags/__init__.py
Normal file
0
django/importer/templatetags/__init__.py
Normal file
34
django/importer/templatetags/importer_tags.py
Normal file
34
django/importer/templatetags/importer_tags.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
from django import template
|
||||
from importer.forms import UploadFileForm
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag
|
||||
def genericlabeluploadform(labelbase_id):
|
||||
form = UploadFileForm()
|
||||
form.fields["labelbase_id"].initial = labelbase_id
|
||||
form.fields["import_type"].initial = "BIP-0329"
|
||||
return form
|
||||
|
||||
@register.simple_tag
|
||||
def bip0329labeluploadform(labelbase_id):
|
||||
form = UploadFileForm()
|
||||
form.fields["labelbase_id"].initial = labelbase_id
|
||||
form.fields["import_type"].initial = "BIP-0329"
|
||||
return form
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def csvBlueWalletlabeluploadform(labelbase_id):
|
||||
form = UploadFileForm()
|
||||
form.fields["labelbase_id"].initial = labelbase_id
|
||||
form.fields["import_type"].initial = "csv-bluewallet"
|
||||
return form
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def csvBitBoxLabeluploadform(labelbase_id):
|
||||
form = UploadFileForm()
|
||||
form.fields["labelbase_id"].initial = labelbase_id
|
||||
form.fields["import_type"].initial = "csv-bitbox"
|
||||
return form
|
||||
184
django/importer/views.py
Normal file
184
django/importer/views.py
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.contrib.auth.decorators import login_required
|
||||
import os
|
||||
import json
|
||||
from labelbase.models import Labelbase
|
||||
from labelbase.serializers import LabelSerializer
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.contrib import messages
|
||||
import decimal
|
||||
|
||||
|
||||
from .forms import UploadFileForm
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
EOLSTOP = [b"", "", None, "\n"]
|
||||
|
||||
|
||||
def handle_uploaded_file(f):
|
||||
fp = NamedTemporaryFile(delete=False)
|
||||
for chunk in f.chunks():
|
||||
fp.write(chunk)
|
||||
return fp
|
||||
|
||||
|
||||
@login_required
|
||||
def upload_labels(request):
|
||||
"""
|
||||
Used to import labels manually using files.
|
||||
"""
|
||||
|
||||
if request.method == "POST":
|
||||
form = UploadFileForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
imported_lables = 0
|
||||
labelbase = get_object_or_404(
|
||||
Labelbase,
|
||||
id=form.cleaned_data.get("labelbase_id", ""),
|
||||
user_id=request.user.id,
|
||||
)
|
||||
fp = handle_uploaded_file(request.FILES["file"])
|
||||
fp.seek(0)
|
||||
# BIP-0329
|
||||
if form.cleaned_data.get("import_type", "") == "BIP-0329":
|
||||
while True:
|
||||
buf = fp.readline()
|
||||
if buf in EOLSTOP:
|
||||
break
|
||||
data = json.loads(buf)
|
||||
data["labelbase"] = labelbase.id
|
||||
serializer = LabelSerializer(data=data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
imported_lables += 1
|
||||
elif form.cleaned_data.get("import_type", "") == "BIP-0329-7z-enc":
|
||||
# TODO: Implement
|
||||
pass
|
||||
# Bitbox App
|
||||
elif form.cleaned_data.get("import_type", "") == "csv-bitbox":
|
||||
while True:
|
||||
buf = fp.readline()
|
||||
if buf in EOLSTOP:
|
||||
break
|
||||
try:
|
||||
buf = str(buf.decode("utf-8"))
|
||||
sbuf = buf.split(",")
|
||||
# Time,Type,Amount,Unit,Fee,Address,Transaction ID,Note
|
||||
for elem in [("tx", 6), ("addr", 5)]:
|
||||
data = {
|
||||
"type": elem[0],
|
||||
"ref": sbuf[elem[1]],
|
||||
"label": " ".join(sbuf[7:]),
|
||||
}
|
||||
data["labelbase"] = labelbase.id
|
||||
serializer = LabelSerializer(data=data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
imported_lables += 1
|
||||
else:
|
||||
messages.add_message(
|
||||
request,
|
||||
messages.ERROR,
|
||||
'Could not process line "{}".'.format(buf),
|
||||
)
|
||||
except Exception as ex:
|
||||
messages.add_message(
|
||||
request,
|
||||
messages.ERROR,
|
||||
'Could not process line "{}", {}.'.format(buf, ex),
|
||||
)
|
||||
# Pocket Accointing
|
||||
elif form.cleaned_data.get("import_type", "") == "pocket-accointing":
|
||||
fp.close()
|
||||
csv_file_path = fp.name
|
||||
mempool_api = labelbase.get_mempool_api()
|
||||
from .pocket import validate_csv_format, parse_csv_to_json
|
||||
if validate_csv_format(csv_file_path):
|
||||
for item in parse_csv_to_json(csv_file_path):
|
||||
#label = "Got {} {} for {} {} in tx=\"{}\" ref=\"{} #Pocket\" ".format(item[0].get('outSellAmount'), item[1].get('inBuyAsset') , item[2].get('inBuyAmount'), item[2].get('inBuyAsset') , item[0].get('operationId'), item[1].get('operationId'))
|
||||
|
||||
label = "Got {} {} for {:.2f} {} with reference: {} #Pocket".format(item[0].get('outSellAmount'), item[1].get('inBuyAsset'), decimal.Decimal(item[2].get('inBuyAmount')), item[2].get('inBuyAsset'), item[1].get('operationId'))
|
||||
txid = item[0].get('operationId')
|
||||
tx = mempool_api.get_transaction(txid)
|
||||
potential_utxos = []
|
||||
vouts = tx.get("vout", [])
|
||||
for i in range(len(vouts)):
|
||||
if vouts[i].get('value', 0) == decimal.Decimal(item[0].get('outSellAmount'))*100000000:
|
||||
potential_utxos.append("{}:{}".format(txid, i ))
|
||||
data = {}
|
||||
if len(potential_utxos) == 1:
|
||||
# label UTXO/output of tx
|
||||
data = {
|
||||
"type": "output",
|
||||
"ref": potential_utxos[0],
|
||||
"label": label,
|
||||
}
|
||||
if len(potential_utxos) > 1:
|
||||
# mark tx, add warning tag
|
||||
data = {
|
||||
"type": "tx",
|
||||
"ref": txid,
|
||||
"label": "{} {}".format(label, "#W001_UTXO_NOT_FOUND"),
|
||||
}
|
||||
if data:
|
||||
data["labelbase"] = labelbase.id
|
||||
serializer = LabelSerializer(data=data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
imported_lables += 1
|
||||
else:
|
||||
messages.add_message(
|
||||
request,
|
||||
messages.ERROR,
|
||||
'Could not process record "{}".'.format(item),
|
||||
)
|
||||
else:
|
||||
print("ERROR") # TODO
|
||||
# BlueWallet
|
||||
elif form.cleaned_data.get("import_type", "") == "csv-bluewallet":
|
||||
while True:
|
||||
buf = fp.readline()
|
||||
if buf in EOLSTOP:
|
||||
break
|
||||
try:
|
||||
buf = str(buf.decode("utf-8"))
|
||||
sbuf = buf.split(",")
|
||||
data = {
|
||||
"type": "tx",
|
||||
"ref": sbuf[1],
|
||||
"label": " ".join(sbuf[3:]),
|
||||
}
|
||||
data["labelbase"] = labelbase.id
|
||||
serializer = LabelSerializer(data=data)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
imported_lables += 1
|
||||
else:
|
||||
messages.add_message(
|
||||
request,
|
||||
messages.ERROR,
|
||||
'Could not process line "{}".'.format(buf),
|
||||
)
|
||||
except Exception as ex:
|
||||
messages.add_message(
|
||||
request,
|
||||
messages.ERROR,
|
||||
'Could not process line "{}", {}.'.format(buf, ex),
|
||||
)
|
||||
else:
|
||||
fp.close()
|
||||
os.unlink(fp.name)
|
||||
return HttpResponseRedirect("/failed/url/")
|
||||
fp.close()
|
||||
os.unlink(fp.name)
|
||||
if imported_lables:
|
||||
messages.add_message(
|
||||
request,
|
||||
messages.INFO,
|
||||
"Processed and imported {} labels.".format(imported_lables),
|
||||
)
|
||||
return HttpResponseRedirect(labelbase.get_absolute_url())
|
||||
else:
|
||||
form = UploadFileForm()
|
||||
return render(request, "upload.html", {"form": form})
|
||||
|
|
@ -386,7 +386,6 @@
|
|||
$(document).ready(function () {
|
||||
|
||||
|
||||
|
||||
// Disable the Origin field by default if not Tx
|
||||
{% if label.type == "tx" %}
|
||||
{% else %}
|
||||
|
|
|
|||
|
|
@ -17,17 +17,15 @@
|
|||
<div id="export-label-modal-body">
|
||||
{% labelbaseform_export as export_form %}
|
||||
{{ export_form|bootstrap }}
|
||||
|
||||
<div id="export_enc_warning" class="alert alert-light" role="alert" style="margin-top:1.2rem;">
|
||||
<small>Labels are encrypted using our <a href="https://github.com/Labelbase/python-bip329">Python library for BIP-329</a>. <br>
|
||||
|
||||
Always keep your passphrase secret and secure! </small>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="successMessage" class="alert alert-success d-none" role="alert" style="margin-top:1.2rem;">
|
||||
<strong>Success!</strong> Your export has been completed.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
|
|
|
|||
|
|
@ -281,6 +281,28 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col d-flex align-items-start">
|
||||
<div>
|
||||
<h3 class="fw-bold mb-0 fs-4 text-body-emphasis">Automated Output Management</h3>
|
||||
<p>We utilize Electrum servers to automatically update your spendable outputs. Labelbase keeps the state of your spendable outputs up to date, helping you keep track of your labels and funds.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col d-flex align-items-start">
|
||||
<div>
|
||||
<h3 class="fw-bold mb-0 fs-4 text-body-emphasis">Automated Fiat Currency Evaluation</h3>
|
||||
<p>Labelbase converts the value of your spendable outputs to fiat currency, assisting you in keeping track of your portfolio value measured in your chosen currency. </p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col d-flex align-items-start">
|
||||
<div>
|
||||
<h3 class="fw-bold mb-0 fs-4 text-body-emphasis"><br> <br> </h3>
|
||||
<p>.. and much more.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -293,10 +315,10 @@
|
|||
<div class="text-center mt-5">
|
||||
<p class="lb-header mx-auto text-center fs-5 text-muted">
|
||||
Ready to optimize your Bitcoin labeling for optimal efficiency?
|
||||
<br><br>
|
||||
Discover the power of streamlined label management with Labelbase.
|
||||
<br><br>
|
||||
Join Labelbase now and unlock the full potential of your Bitcoin experience!
|
||||
<br><br>
|
||||
Discover the power of streamlined label management with Labelbase.
|
||||
<br><br>
|
||||
Join Labelbase now and unlock the full potential of your Bitcoin experience!
|
||||
|
||||
|
||||
</p>
|
||||
|
|
@ -306,12 +328,15 @@ Join Labelbase now and unlock the full potential of your Bitcoin experience!
|
|||
|
||||
{% endif %}
|
||||
<!-- Add: What people are saying -->
|
||||
|
||||
<!-- Add:
|
||||
|
||||
Our supporters
|
||||
Our supporters:
|
||||
Special Thanks! <3 to OpenSats !
|
||||
|
||||
Thank you for your support!
|
||||
|
||||
|
||||
-->
|
||||
-->
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -10,24 +10,32 @@
|
|||
|
||||
<div class=" p-3 pb-md-4 mx-auto text-left">
|
||||
<h2 class="display-8 fw-normal">Privacy Policy</h2>
|
||||
<small>The privacy policy on this website specifically applies to the labelbase.space web application, which is hosted by the Labelbase team.<br>Last updated: July 26<sup>th</sup> 2023.</small>
|
||||
<p>
|
||||
The privacy policy applies to labelbase.space hosted by Labelbase. For self-hosted versions, variations or customizations may exist.
|
||||
|
||||
Users are responsible for reviewing any specific privacy considerations or customizations made to their instance.<br><br>
|
||||
|
||||
Last updated: December 19<sup>th</sup> 2023.</p>
|
||||
|
||||
<p class="fs-5 text-muted">
|
||||
<ol style="line-height:2em; ">
|
||||
<li>We do not use any analytics or tracking tools that collect personal information, but {# offer an optional opt-in to use #} we use Sentry for anonymized error tracking, ensuring users are not associated with reported errors.</li>
|
||||
<li>This website does not use cookies, apart from the login session. However, your IP address may be collected as part of basic webserver logs necessary for systems administration purposes. </li>
|
||||
<li>We do not use any analytics or tracking tools that collect personal information, but {# offer an optional opt-in to use #} we use <a href="https://sentry.io/">Sentry</a> for anonymized error tracking, ensuring users are not associated with reported errors.</li>
|
||||
<li>This website does not use cookies, except for the login session and other essential cookies required for the functionality of the web application. However, your IP address may be collected as part of basic webserver logs necessary for systems administration purposes. </li>
|
||||
<li>We do not collect or store information, expect the information that is attached directly to labels. Deleting your Labelbase removes the labels attached to it. </li>
|
||||
<li>We do not share any information with third parties including advertisers. See 6. for interated APIs and services.</li>
|
||||
<li>We cannot access your funds.</li>
|
||||
<li>We are and act as your data processor. In other words: You are responsible for the labelbases and labels you create on or upload to Labelbase, which we store and process to provide our services to you. </li>
|
||||
<li>This is not a wallet software. We cannot access your funds.</li>
|
||||
<li>We are and act as your data processor. In other words: You are responsible for the labelbases, labels and additional date you create on or upload to Labelbase, which we store and process to provide our services to you. </li>
|
||||
<li>"Labelbase" uses the following 3<sup>rd</sup> party APIs and services:
|
||||
<ul>
|
||||
<li>The addresses and transactions are provided with hyperlinks to mempool.space. These links must be actively clicked.</li>
|
||||
<li>We have no influence over wallets and services that implement our API.</li>
|
||||
<li>We connect and may get data (like exchange rates) from mempool.space</li>
|
||||
<li>We connect and may get data from some "community trusted" Electrum servers.</li>
|
||||
</ul>
|
||||
We have no influence over wallets and services that implement our API.
|
||||
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@
|
|||
|
||||
<div class=" p-3 pb-md-4 mx-auto text-left">
|
||||
<h2 class="display-8 fw-normal">Terms of Service</h2>
|
||||
<small>February 10<sup>th</sup> 2023</small>
|
||||
<small>December 19<sup>th</sup> 2023</small>
|
||||
<p class="fs-5 text-muted">
|
||||
"LABELBASE" (labelbase.space) is provided without any guarantees of anything, not
|
||||
"LABELBASE" (labelbase.space, but also the self-hosted versions) is provided without any guarantees of anything, not
|
||||
even that it will do its job properly. If you have any doubts, please use an
|
||||
alternate service.
|
||||
</p>
|
||||
|
|
@ -107,5 +107,5 @@ FIRE, FLOODS, ACCIDENTS, SERVICE OUTAGES RESULTING FROM EQUIPMENT AND/OR SOFTWAR
|
|||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
11
django/templates/upload.html
Normal file
11
django/templates/upload.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{% extends "_base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap %}
|
||||
{% block content %}
|
||||
<h2 style="padding-top: 1em;">Import labels</h2>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form|bootstrap }}
|
||||
<input type="submit" value="OK">
|
||||
</form>
|
||||
{% endblock %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue