├── helper ├── qrs │ └── logo3.png ├── main.css ├── app.js └── make_wishlist.py ├── requirements.txt ├── README.md └── wishlist-aas.py /helper/qrs/logo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plowsof/xmr-wishlist-aaS/HEAD/helper/qrs/logo3.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cryptocompare==0.7.5 2 | python_monerorpc==0.6.2 3 | qrcode==7.3 4 | requests==2.22.0 5 | filelock==3.0.12 6 | Pillow==8.3.2 7 | PyGithub==1.55 8 | -------------------------------------------------------------------------------- /helper/main.css: -------------------------------------------------------------------------------- 1 | body[a="dark"] { 2 | filter: invert(1) 3 | } 4 | 5 | body[a="dark"] img { 6 | filter: invert(1) 7 | } 8 | 9 | body[a="dark"] img.ioda { 10 | filter: invert(0) 11 | } 12 | 13 | @media (prefers-color-scheme: dark) { 14 | body[a="auto"] { 15 | filter: invert(1) 16 | } 17 | body[a="auto"] img { 18 | filter: invert(1) 19 | } 20 | body[a="auto"] img.ioda { 21 | filter: invert(0) 22 | } 23 | } 24 | 25 | html, 26 | body { 27 | background: white 28 | } 29 | 30 | html { 31 | height: 100% 32 | } 33 | 34 | body { 35 | color: black ; 36 | font-family: monospace ; 37 | font-size: 16px ; 38 | line-height: 1.4 ; 39 | margin: 0 ; 40 | min-height: 100% ; 41 | overflow-wrap: break-word 42 | } 43 | 44 | .post-meta { 45 | text-align: right 46 | } 47 | 48 | h2, 49 | h3, 50 | h4, 51 | h5, 52 | h6 { 53 | margin-top: 3rem 54 | } 55 | 56 | hr { 57 | margin: 2rem 0 58 | } 59 | 60 | p { 61 | margin: 1rem 0 62 | } 63 | 64 | li { 65 | margin: 0.4rem 0 66 | } 67 | 68 | *:target { 69 | background: yellow 70 | } 71 | 72 | .w { 73 | max-width: 640px ; 74 | margin: 0 auto ; 75 | padding: 4rem 2rem 76 | } 77 | 78 | hr { 79 | text-align: center ; 80 | border: 0 81 | } 82 | 83 | hr:before { 84 | content: "/////" 85 | } 86 | 87 | hr:after { 88 | content: attr(data-content) "/////" 89 | } 90 | 91 | table { 92 | width: 100% 93 | } 94 | 95 | table, 96 | th, 97 | td { 98 | border: thin solid black ; 99 | border-collapse: collapse ; 100 | padding: 0.4rem 101 | } 102 | 103 | code { 104 | color: white ; 105 | background: black 106 | } 107 | 108 | div.highlighter-rouge code { 109 | display: block ; 110 | overflow-x: auto ; 111 | white-space: pre-wrap ; 112 | padding: 1rem 113 | } 114 | 115 | blockquote { 116 | font-style: italic ;wi 117 | border: thin solid black ; 118 | padding: 1rem 119 | } 120 | 121 | blockquote p { 122 | margin: 0 123 | } 124 | 125 | img { 126 | max-width: 100% ; 127 | display: block ; 128 | margin: 0 auto 129 | } 130 | 131 | .subaddress{ 132 | color: yellow ; 133 | font-family: monospace ; 134 | cursor: pointer ; 135 | display: inline-block ; 136 | } 137 | 138 | .wishtitle{ 139 | color: blue ; 140 | } 141 | 142 | #ascii-progress-bar{ 143 | color: brown ; 144 | font: monospace ; 145 | font-size: 10px ; 146 | } 147 | 148 | .qrimage{ 149 | color: blue ; 150 | } 151 | 152 | .qrbtn{ 153 | text-align:center ; 154 | } 155 | 156 | input.accordion { 157 | display: none; 158 | } 159 | 160 | label.accordion { 161 | cursor: zoom-in; 162 | } 163 | 164 | input.accordion:checked ~ .qr-content { 165 | height: auto; 166 | padding: 1rem; 167 | } 168 | 169 | .qr-content { 170 | overflow: hidden; 171 | padding: 0; 172 | height: 0; 173 | -webkit-transition: all .5s ease; 174 | -moz-transition: all .5s ease; 175 | transition: all .5s ease; 176 | } 177 | 178 | .qr img { 179 | border-radius: 5px; 180 | max-width: 100%; 181 | } 182 | 183 | 184 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Live XMR wishlist as a service 2 | 3 | ## FlipStarter 4 | In an experiment to explore other funding alternatives to the CCS, (and extend the outreach of Monero), i have been funded by Flipstarter to make this multi-currency. XMR/BCH/BTC (and recently WOWnero) which is being developed [here](https://github.com/plowsof/flipstarter-waas-wip). I have been somewhat successful with my goals, as my new list is being used by someone else who has had one of their wishes successfully funded @ [Ruckniums Donation page](https://rucknium.me/donate) and can be span up in a few minutes using docker! 5 | 6 | ## About 7 | It will serve / push updates to a Json array file on github for any external websites to fetch and display. 8 | What you need: 9 | ``` 10 | A github account / access token for the script (modify the variables accordingly) 11 | monero-wallet-rpc monitoring a view-only wallet 12 | ``` 13 | Example json data 14 | ``` 15 | https://github.com/plowsof/plowsof.github.io/blob/main/wishlist/wishlist-data.json 16 | ``` 17 | The json array is 'live' and changes will be pushed to your page (adding/removing) 18 | 19 | In your monero-wallet-rpc config file: 20 | ```bash 21 | tx-notify=/usr/bin/python3 /path/to/wishlist-aas.py %s 22 | ``` 23 | 24 | In your .html file: 25 | ```html 26 |
27 | 28 | 29 | ``` 30 | 31 | In your .js file: 32 | ``` 33 | see helpers/app.js 34 | ``` 35 | 36 | ## Examples 37 | https://xmr.radio/funding src @ https://github.com/plowsof/funding-xmr-radio (i had full creative control over the UI for this one, so im especially proud!) 38 | https://funding.monerujo.app/ 39 | https://moneroart.neocities.org/ 40 | 41 | ## Setting up with an unused wallet 42 | - Modify ```helpers/make_wishlist.py``` to create your json wishlist, containing the subaddresses/descriptions of each wish. 43 | - Upload this file to your github and set the correct URL / github token values. (or ignore this step put it on your own server, im just poor ^^) 44 | - Configure your monero-wallet-rpc's 'tx-notify' value to call ```wishlist-aas.py``` with python3 45 | - Make sure that the pyhton script is using the correct ip:port for your rpc-wallet 46 | 47 | ## Setting up with an 'already used wallet' 48 | Now using rpc call ```get_transfers``` to load the wallet history (if any found) (no more wasting time creating a csv file from the GUI). 49 | - After creating a new wishlist, the wallet history will be imported into the json data (+= to contributors and amounts of the matching addresses) 50 | - see ```load_old_txs``` in ```helpers/make_wishlist.py``` 51 | 52 | ## Modifying your list 53 | - An online json editor could be used to add/remove items easily. e.g: 54 | - https://jsoneditoronline.org 55 | 56 | ## Updates 57 | - QRimages generated for each subaddress + a custom logo see ```helpers/make_wishlist.py``` 58 | - USD amounts accepted for each wish - then converted to current XMR value + a % buffer (can be changed on the fly to readjust goals also) 59 | - Generate a sub address for your wish if none is supplied see ```helpers/make_wishlist.py```. 60 | - Creating wishlist is now as simple as: (3rd arg is a wish 'type' to help display things on the front end) 61 | ``` 62 | create_new_wishlist(500,"Something special",None,"work") 63 | create_new_wishlist(5,"buy me a coffee"86aSNJwDYC2AshDDvbGgtQ17RWspmKNwNXAqdFiFF2Db91v9PC26uDxffD9ZYfcMjvJpuKJepsQtELAdmXVk85E1DsuL6rG","gift") 64 | ``` 65 | # Support 66 | 67 | This project has been fully funded by the Monero Outreach team @https://twitter.com/xmroutreach <3 68 | 69 | I enjoyed making this (because of how out of my depth i am with front end stuff), i will support you if you reach out to me with issues/q's reg. this. 70 | And if you want to show support to me, my xmr address is below, much appreciated! :') 71 | ``` 72 | 86aSNJwDYC2AshDDvbGgtQ17RWspmKNwNXAqdFiFF2Db91v9PC26uDxffD9ZYfcMjvJpuKJepsQtELAdmXVk85E1DsuL6rG 73 | ``` 74 | -------------------------------------------------------------------------------- /helper/app.js: -------------------------------------------------------------------------------- 1 | let modified = "0" 2 | 3 | async function getWishlist() { 4 | let ran_int = Math.floor(Math.random() * 100000) 5 | //let url = 'https://raw.githubusercontent.com/plowsof/plowsof.github.io/main/wishlist/wishlist-data.json?uid=' + ran_int; 6 | let url = "https://raw.githubusercontent.com/plowsof/funding-xmr-radio/main/json/wishlist-data.json?uid=" + ran_int; 7 | try { 8 | let res = await fetch(url); 9 | return await res.json(); 10 | } catch (error) { 11 | return null; 12 | } 13 | } 14 | 15 | async function renderWishlist() { 16 | let wishlist = await getWishlist(); 17 | modified_live = wishlist["metadata"]["modified"] 18 | if (modified != modified_live){ 19 | modified = modified_live 20 | let html_equip = ''; 21 | let html_subsc = ''; 22 | let id = 0 23 | let ran_int = Math.floor(Math.random() * 101); 24 | wishlist["wishlist"].forEach(wish => { 25 | let qrcheck = document.getElementById(`qr-${wish.address}`); 26 | let qrchecked = (qrcheck && qrcheck.checked)?" checked":""; 27 | //alert(qrchecked) 28 | wish.percent = wish.total / wish.goal * 100; 29 | let total = wish.total.toFixed(2) 30 | let goal = wish.goal.toFixed(2) 31 | //alert(wish.address) 32 | let ahead1 = wish.address.substr(0,4) 33 | let ahead2 = wish.address.substr(4,4) 34 | let atail1 = wish.address.substr(-8,4) 35 | let atail2 = wish.address.substr(-4,4) 36 | let atail = wish.address.substr(-5,5) 37 | let something = "" 38 | let address = something.concat(ahead1, " ", ahead2," .. ", atail1, " ", atail2) 39 | wish.percent = 50 40 | var current_percent = wish.percent; 41 | let ascii_progress = '' 42 | for (n = 0; n < 20; n++) { 43 | if (current_percent < (n+1)*5) { 44 | ascii_progress = ascii_progress.concat("░"); // alt-176 45 | } 46 | else { 47 | ascii_progress = ascii_progress.concat("▓"); // alt-178 48 | } 49 | } 50 | //alert(ascii_progress) 51 | //let address = wish.address 52 | //${wish.percent}% 53 | let qr =` 54 | 55 | 56 |
57 |

58 |
` 59 | let htmlSegment =`
60 |
  • 61 |

    ${wish.description}

    62 | Raised ${total} of ${goal} XMR [${ascii_progress}] Inputs: ${wish.contributors} 63 |

    ${address}

    ${qr} 64 |
  • `; 65 | 66 | 67 | //if type == equip then add to... 68 | if (wish.type == "equip"){ 69 | html_equip += htmlSegment 70 | } 71 | else{ 72 | html_subsc += htmlSegment 73 | } 74 | id += 1; 75 | }); 76 | //if type == something then add to that 77 | let container = document.querySelector('.equipment'); 78 | container.innerHTML = html_equip; 79 | let container2 = document.querySelector('.recurring'); 80 | container2.innerHTML = html_subsc; 81 | //let total_main = document.querySelector('.total_main'); 82 | //total_main.innerHTML = wishlist["metadata"]["total"]; 83 | } 84 | 85 | } 86 | 87 | function CopyToClipboard(id) 88 | { 89 | var node = document.getElementById(id); 90 | htmlContent = node.innerHTML; 91 | // htmlContent = "Some sample text." 92 | textContent = node.textContent; 93 | // textContent = "Some sample text." 94 | node.focus(); 95 | navigator.clipboard.writeText(id) 96 | .then(() => { alert(`Address Copied!`) }) 97 | .catch((error) => { alert(`Copy failed! ${error}`) }) 98 | } 99 | 100 | 101 | //on page load - render the wishlist. set a 'time updated variable from the json' then loop compare 102 | //infinite loop 103 | 104 | renderWishlist() 105 | setInterval('renderWishlist()',2000) 106 | 107 | 108 | -------------------------------------------------------------------------------- /wishlist-aas.py: -------------------------------------------------------------------------------- 1 | import pprint 2 | from datetime import datetime 3 | import pickle 4 | import time 5 | import os 6 | import sys 7 | import random 8 | import json 9 | import requests 10 | from github import Github 11 | from monerorpc.authproxy import AuthServiceProxy, JSONRPCException 12 | #from matrix_client.api import MatrixHttpApi 13 | #import emoji 14 | from filelock import FileLock 15 | 16 | 17 | os.chdir("/home/your/scripts/dir") 18 | 19 | wishlist = [] 20 | repo_name = "funding-xmr-radio" 21 | repo_dir = "json" 22 | node_url = 'http://eeebox:18086/json_rpc' 23 | #node_url = 'http://localhost:18082/json_rpc' 24 | git_token = "ghp_hunter2********" 25 | json_url = "https://raw.githubusercontent.com/plowsof/funding-xmr-radio/main/json/wishlist-data.json" 26 | matrix_token = "-" 27 | matrix_room = "-" 28 | 29 | def getJson(): 30 | #must get the latest json as it may have been changed 31 | global json_url 32 | try: 33 | x = requests.get(json_url) 34 | return json.loads(x.text) 35 | except Exception as e: 36 | raise e 37 | 38 | def main(tx_id,conf=0,multi=0): 39 | #check height 40 | saved_wishlist = getJson() 41 | if multi == 0: 42 | tx_data = checkHeight(tx_id,conf) 43 | if tx_data: 44 | print(tx_data["address"]) 45 | else: 46 | tx_data = tx_id 47 | if tx_data: 48 | #print(f"we got funds : {tx_data['address']}") 49 | tx_data["amount"] = formatAmount(tx_data["amount"]) 50 | found = 0 51 | main_fund = saved_wishlist["metadata"]["total"] 52 | for i in range(len(saved_wishlist["wishlist"])): 53 | try: 54 | #print(saved_wishlist[i]) 55 | if saved_wishlist["wishlist"][i]["address"] == tx_data["address"]: 56 | found = 1 57 | saved_wishlist["wishlist"][i]["modified_date"] = str(datetime.now()) 58 | #contributor += 1 59 | saved_wishlist["wishlist"][i]["contributors"] += 1 60 | #total += amount 61 | saved_wishlist["wishlist"][i]["total"] += float(tx_data["amount"]) 62 | #print(f"after:{saved_wishlist["wishlist"][i]") 63 | #if total => goal its 100% 64 | if float(saved_wishlist["wishlist"][i]["total"]) >= float(saved_wishlist["wishlist"][i]["goal"]): 65 | #fully funded [ do something special e.g. make a tweet ...] 66 | if saved_wishlist["wishlist"][i]["percent"] != 100: 67 | #We are newly fully funded 68 | print("something special") 69 | #matrixMsg(saved_wishlist["wishlist"][i]) 70 | saved_wishlist["wishlist"][i]["percent"] = 100 71 | else: 72 | saved_wishlist["wishlist"][i]["percent"] = float(saved_wishlist["wishlist"][i]["total"]) / float(saved_wishlist["wishlist"][i]["goal"]) * 100 73 | break 74 | except Exception as e: 75 | raise e 76 | if found == 0: 77 | extra_xmr = tx_data["amount"] 78 | #donation received on invalid wishlist 79 | saved_wishlist["metadata"]["total"] += float(extra_xmr) 80 | saved_wishlist["metadata"]["contributors"] += 1 81 | 82 | #finished. unless "batched" doesn't exist 83 | saved_wishlist["wishlist"] = sorted(saved_wishlist["wishlist"], key=lambda k: k['percent'],reverse=True) 84 | modified = str(datetime.now()) 85 | saved_wishlist["metadata"]["modified"] = modified 86 | print(modified) 87 | #pickle.dump(saved_wishlist, open( "wishlist.p", "wb+" ) ) 88 | dump_json(saved_wishlist) 89 | if not os.path.isfile("batched"): 90 | #create "batched" 91 | with open("batched", "w+") as f: 92 | f.write("1") 93 | time.sleep(30) 94 | uploadtogit("wishlist-data.json","wishlist-data.json") 95 | os.remove("batched") 96 | 97 | def dump_json(wishlist): 98 | with FileLock("wishlist-data.json.lock"): 99 | print("Lock acquired.") 100 | with open('wishlist-data.json', 'w') as f: 101 | json.dump(wishlist, f, indent=6) 102 | 103 | def matrixMsg(data): 104 | global matrix_token 105 | global matrix_room 106 | message = data["desc"] + emoji.emojize(' is 100% FUNDED @twisted_turtle :thumbs_up:') 107 | matrix = MatrixHttpApi("https://matrix.org", token=matrix_token) 108 | response = matrix.send_message(matrix_room, message) 109 | 110 | def checkHeight(tx_id,conf=0): 111 | global pickled_data 112 | global node_url 113 | #loop incase rpc daemon has not started up yet. 114 | while True: 115 | try: 116 | rpc_connection = AuthServiceProxy(service_url=node_url) 117 | params={"account_index":0, 118 | "txid":str(tx_id)} 119 | info = rpc_connection.get_transfer_by_txid(params) 120 | break 121 | except Exception as e: 122 | print("Retrying connection in 5 seconds.") 123 | time.sleep(5) 124 | #pprint.pprint(info) 125 | #print(f"the transfers length is {len(info['transfers'])}") 126 | #pprint.pprint(info) 127 | if len(info['transfers']) == 1: 128 | theData = info["transfer"] 129 | someInfo = theData["address"] 130 | #print(someInfo) 131 | height = info["transfer"]["height"] 132 | #quick zero conf 133 | #print(f"conf = {conf}") 134 | if conf == 0: 135 | if info["transfer"]["height"] == 0: 136 | return info["transfer"] 137 | else: 138 | if info["transfer"]["height"] != 0: 139 | return info["transfer"] 140 | else: 141 | for x in info["transfers"]: 142 | main(x,1,1) 143 | 144 | def formatAmount(amount): 145 | """decode cryptonote amount format to user friendly format. 146 | Based on C++ code: 147 | https://github.com/monero-project/bitmonero/blob/master/src/cryptonote_core/cryptonote_format_utils.cpp#L751 148 | """ 149 | CRYPTONOTE_DISPLAY_DECIMAL_POINT = 12 150 | s = str(amount) 151 | if len(s) < CRYPTONOTE_DISPLAY_DECIMAL_POINT + 1: 152 | # add some trailing zeros, if needed, to have constant width 153 | s = '0' * (CRYPTONOTE_DISPLAY_DECIMAL_POINT + 1 - len(s)) + s 154 | idx = len(s) - CRYPTONOTE_DISPLAY_DECIMAL_POINT 155 | s = s[0:idx] + "." + s[idx:] 156 | 157 | #my own hack to remove trailing 0's, and to fix the 1.1e-5 etc 158 | trailing = 0 159 | while trailing == 0: 160 | if s[-1:] == "0": 161 | s = s[:-1] 162 | else: 163 | trailing = 1 164 | return s 165 | 166 | def uploadtogit(infile,outfile): 167 | global repo_name 168 | global repo_dir 169 | global git_token 170 | g = Github(git_token) 171 | 172 | repo = g.get_user().get_repo(repo_name) 173 | 174 | with open(infile, 'r') as file: 175 | content = file.read() 176 | #print(outfile) 177 | # Upload to github 178 | git_file = repo_dir + "/" + outfile 179 | contents = repo.get_contents(git_file) 180 | repo.update_file(contents.path, "committing files", content, contents.sha, branch="main") 181 | print(git_file + ' UPDATED') 182 | 183 | 184 | if __name__ == '__main__': 185 | tx_id = sys.argv[1] 186 | 187 | #feed a specific txid 188 | #tx_id = "98d754375dffe284504c820bca35f24d0261e50989c4f30561657e5c87982a1f" 189 | main(tx_id,0,0) 190 | 191 | -------------------------------------------------------------------------------- /helper/make_wishlist.py: -------------------------------------------------------------------------------- 1 | 2 | import pprint 3 | import json 4 | from datetime import datetime 5 | import cryptocompare 6 | import qrcode 7 | from PIL import Image 8 | import requests 9 | import os 10 | from github import Github 11 | from monerorpc.authproxy import AuthServiceProxy, JSONRPCException 12 | #Api key of "-" appears to work currently, this may change, 13 | cryptocompare.cryptocompare._set_api_key_parameter("-") 14 | 15 | 16 | git_username = "plowsof" 17 | repo_name = "funding-xmr-radio" 18 | repo_dir = "json" 19 | qrcode_dir = "qr_codes" 20 | git_token = "hunter123secret" 21 | node_url = 'http://eeebox:18086/json_rpc' 22 | json_url = f"https://raw.githubusercontent.com/{git_username}/{repo_name}/main/{repo_dir}/wishlist-data.json" 23 | viewkey = "" 24 | main_address = "" 25 | percent_buffer = 0.05 26 | 27 | usd_goal_address = {} 28 | wishes =[] 29 | 30 | def getPrice(crypto,offset): 31 | data = cryptocompare.get_price(str(crypto), currency='USD', full=0) 32 | #print(f"[{crypto}]:{data[str(crypto)]['USD']}") 33 | value = float(data[str(crypto)]["USD"]) 34 | #print(f"value = {value}") 35 | return(float(value) - (float(value) * float(offset))) 36 | 37 | def formatAmount(amount): 38 | """decode cryptonote amount format to user friendly format. 39 | Based on C++ code: 40 | https://github.com/monero-project/bitmonero/blob/master/src/cryptonote_core/cryptonote_format_utils.cpp#L751 41 | """ 42 | CRYPTONOTE_DISPLAY_DECIMAL_POINT = 12 43 | s = str(amount) 44 | if len(s) < CRYPTONOTE_DISPLAY_DECIMAL_POINT + 1: 45 | # add some trailing zeros, if needed, to have constant width 46 | s = '0' * (CRYPTONOTE_DISPLAY_DECIMAL_POINT + 1 - len(s)) + s 47 | idx = len(s) - CRYPTONOTE_DISPLAY_DECIMAL_POINT 48 | s = s[0:idx] + "." + s[idx:] 49 | 50 | #my own hack to remove trailing 0's, and to fix the 1.1e-5 etc 51 | trailing = 0 52 | while trailing == 0: 53 | if s[-1:] == "0": 54 | s = s[:-1] 55 | else: 56 | trailing = 1 57 | return s 58 | 59 | def put_qr_code(address): 60 | try: 61 | if not os.path.isdir(os.path.join('.','qrs')): 62 | os.mkdir(os.path.join(".","qrs")) 63 | pass 64 | except Exception as e: 65 | raise e 66 | 67 | title = address[0:12] 68 | qr = qrcode.QRCode( 69 | version=None, 70 | error_correction=qrcode.constants.ERROR_CORRECT_M, 71 | box_size=7, 72 | border=4, 73 | ) 74 | 75 | data = f"monero:{address}" 76 | qr.add_data(data) 77 | qr.make(fit=True) 78 | #img = qr.make_image(fill_color="black", back_color=(62,62,62)) 79 | img = qr.make_image(fill_color=(62,62,62), back_color="white") 80 | img.save(f"qrs/{title}.png") 81 | f_logo = os.path.join(".","qrs","logo3.png") 82 | logo = Image.open(f_logo) 83 | logo = logo.convert("RGBA") 84 | print(logo.size) 85 | im = Image.open(f"qrs/{title}.png") 86 | im = im.convert("RGBA") 87 | logo.thumbnail((60, 60)) 88 | im.paste(logo,box=(142,142),mask=logo) 89 | #im.show() 90 | im.save(f"qrs/{title}.png") 91 | #uploadtogit(f"{title}.png",f"{title}.png") 92 | #return("lolok") 93 | 94 | def wishlist_add_new(goal,desc,address,w_type): 95 | global git_username, repo_name, repo_dir, qrcode_dir 96 | global wishes 97 | global percent_buffer 98 | global usd_goal_address 99 | global node_url 100 | #usd_goal_address.append(usd_address_pair) 101 | test = getPrice("XMR",float(percent_buffer)) 102 | #print(f"test = {test}") 103 | xmrgoal = goal / getPrice("XMR",float(percent_buffer)) 104 | 105 | 106 | #Connect and generate a new sub address for our wish 107 | if not address: 108 | print("Make a new address on the fly!") 109 | rpc_connection = AuthServiceProxy(service_url=node_url) 110 | #label could be added 111 | params={ 112 | "account_index":0, 113 | "label": desc 114 | } 115 | info = rpc_connection.create_address(params) 116 | address = info["address"] 117 | usd_goal_address[address] = goal 118 | app_this = { 119 | "goal":xmrgoal, 120 | "total":0, 121 | "contributors":0, 122 | "address":address, 123 | "description": desc, 124 | "percent": 0, 125 | "type": w_type, 126 | "created_date": str(datetime.now()), 127 | "modified_date": str(datetime.now()), 128 | "author_name": "", 129 | "author_email": "", 130 | "id": address[0:12], 131 | "qr_img_url": f"https://raw.githubusercontent.com/{git_username}/{repo_name}/main/{qrcode_dir}/{address[0:12]}.png", 132 | "title": "" 133 | } 134 | wishes.append(app_this) 135 | put_qr_code(address) 136 | 137 | def getJson(): 138 | #must get the latest json as it may have been changed 139 | global json_url 140 | try: 141 | x = requests.get(json_url) 142 | return json.loads(x.text) 143 | except Exception as e: 144 | raise e 145 | 146 | #Re-make goals using the current USD value of XMR + % buffer 147 | def adjust_goals(new_buffer): 148 | #get Json 149 | current_wishlist = getJson() 150 | #print("Before:") 151 | #pprint.pprint(current_wishlist) 152 | #we need the usd amount + address to do this 153 | with open("usd-address-pair.json") as f: 154 | usd_pairings = json.load(f) 155 | #Adjust all goals 156 | for wish in current_wishlist["wishlist"]: 157 | if usd_pairings[wish["address"]]: 158 | #we exist 159 | usd_xmr = getPrice("XMR",float(new_buffer)) 160 | new_goal = usd_pairings[wish["address"]] / usd_xmr 161 | old_goal = wish["goal"] 162 | print(f"old goal was:{old_goal} new goal: {new_goal}") 163 | wish["goal"] = new_goal 164 | wish["percent"] = float(wish["total"]) / float(wish["goal"]) * 100 165 | #Send back to github with only goals/percent adjusted 166 | #Set modified time to JS reloads our new info 167 | current_wishlist["metadata"]["modified"] = str(datetime.now()) 168 | dump_json(current_wishlist) 169 | #upload to git 170 | #uploadtogit("wishlist-data.json","wishlist-data.json") 171 | 172 | def dump_json(wishlist): 173 | with open('wishlist-data.json', 'w') as f: 174 | json.dump(wishlist, f, indent=6) 175 | 176 | def uploadtogit(infile,outfile): 177 | global repo_name 178 | global repo_dir 179 | global git_token 180 | g = Github(git_token) 181 | 182 | repo = g.get_user().get_repo(repo_name) 183 | 184 | with open(infile, 'r') as file: 185 | content = file.read() 186 | #print(outfile) 187 | # Upload to github 188 | git_file = repo_dir + "/" + outfile 189 | contents = repo.get_contents(git_file) 190 | repo.update_file(contents.path, "committing files", content, contents.sha, branch="main") 191 | print(git_file + ' UPDATED') 192 | 193 | def change_all_titles(title): 194 | now_list = getJson() 195 | for wish in now_list["wishlist"]: 196 | wish["title"] = title 197 | #Set modified time to JS re-loads new info 198 | now_list["metadata"]["modified"] = str(datetime.now()) 199 | dump_json(now_list) 200 | #Check that the .json file is 'sane', or live life in the fast lane: 201 | #uploadtogit("wishlist-data.json","wishlist-data.json") 202 | 203 | #get input from a used wallet 204 | def load_old_txs(): 205 | global wishes 206 | rpc_connection = AuthServiceProxy(service_url=node_url) 207 | #label could be added 208 | params={ 209 | "account_index":0, 210 | "in": True 211 | } 212 | old_txs = {} 213 | info = rpc_connection.get_transfers(params) 214 | num = 0 215 | 216 | if info["in"]: 217 | print("Wallet history detected. Importing") 218 | for tx in info["in"]: 219 | old_txs[num] = {tx["address"]: formatAmount(tx["amount"])} 220 | num += 1 221 | #pprint.pprint(old_txs) 222 | for wi in range(len(wishes)): 223 | for hi in range(len(old_txs)): 224 | #add, amount = old_txs[i] 225 | try: 226 | if old_txs[hi][wishes[wi]["address"]]: 227 | print(f"{wishes[wi]['address']} got +1 contributors and {old_txs[hi][wishes[wi]['address']]} XMR") 228 | #pprint.pprint(wishes[wi]) 229 | wishes[wi]["contributors"] += 1 230 | wishes[wi]["total"] += float(old_txs[hi][wishes[wi]["address"]]) 231 | wishes[wi]["percent"] = float(wishes[wi]["total"]) / float(wishes[wi]["goal"]) * 100 232 | #pprint.pprint(wishes[wi]) 233 | except Exception as e: 234 | continue 235 | 236 | 237 | def create_new_wishlist(): 238 | global wishes 239 | global viewkey, main_address 240 | 241 | #Your wishlist 242 | #------------------------------------------------------------- 243 | wishlist_add_new(500,"Do something for the community",None,"work") 244 | wishlist_add_new(5,"buy me a coffee","86aSNJwDYC2AshDDvbGgtQ17RWspmKNwNXAqdFiFF2Db91v9PC26uDxffD9ZYfcMjvJpuKJepsQtELAdmXVk85E1DsuL6rG","gift") 245 | #------------------------------------------------------------- 246 | 247 | thetime = datetime.now() 248 | total = { 249 | "total": 0, 250 | "contributors": 0, 251 | "modified": str(thetime), 252 | "title": "", 253 | "description": "", 254 | "image": "", 255 | "url": "", 256 | "viewkey": viewkey, 257 | "main_address": main_address 258 | } 259 | 260 | #search wallet for 'in' history, then compare addresses to our new list. 261 | #if matching address are found then contributors are +=1'd and amount+=amount. 262 | load_old_txs() 263 | 264 | the_wishlist = {} 265 | the_wishlist["wishlist"] = wishes 266 | the_wishlist["metadata"] = total 267 | 268 | 269 | with open("wishlist-data.json", "w+") as f: 270 | json.dump(the_wishlist,f, indent=4) 271 | 272 | #Original xmr goal + address pairings 273 | with open("usd-address-pair.json", "w+") as f: 274 | json.dump(usd_goal_address,f,indent=4) 275 | 276 | 277 | return 278 | ''' 279 | //----------------------------- 280 | // What / Why 281 | //----------------------------- 282 | 283 | calling create_new_wishlist will do several things: 284 | - convert the usd value to xmr with a buffer (percent_buffer) 285 | - create a new address for the wish if 'None' is supplied 286 | - generate QRimages, and adds a qr_img_url to the json - which expects you to place them in your 'qrimage_dir' @ github 287 | - creates 2 file: 288 | - wishlist-data.json file to uploaded to github (manually) 289 | - usd-address-pair.json which is just a list of your addresses + original usd goal which is later used by adjust_goals() 290 | - load_old_txs() will get / import old tx's that have a matching address to those in your wishlist (++contributors/amounts) 291 | 292 | You can manually adjust the json after the fact by hand or using an online json editor (easier) 293 | 294 | Why github?' 295 | - it's free, and you can also host a website on github pages. 296 | - Did i mention its free? 297 | - Extra accountability as its a public ledger - edits to the json data/qr images are logged 298 | - See Monerujo's adaptation if you want to self host everything. 299 | 300 | //----------------------------- 301 | // How 302 | //----------------------------- 303 | 304 | go to create_new_wishlist() and add each of your wishes using a USD goal amount. 305 | Supply None as the address if you want to make a new one (has to be connected to an rpc wallet) 306 | 307 | ''' 308 | 309 | create_new_wishlist() 310 | 311 | #Recalculate goals based on current USD value (with a buffer also) amd upload new data 312 | #adjust_goals(0.20) 313 | 314 | #change_all_titles("XMR.radio donation") 315 | 316 | --------------------------------------------------------------------------------