Electrum wrapper: fix probability mistake
[tails.git] / config / chroot_local-includes / usr / local / bin / electrum
blobb5b88788123db8f0b0fe9eac10a063d0f6cc037b
1 #! /usr/bin/env python3
2 """
3 Tails electrum wrapper
5 Test with "python3 electrum.py doctest".
6 The tests will start the tor-browser so you probably
7 want to use a tester that handles user interaction or
8 run the tests from the command line and answer prompts as needed.
10 goodcrypto.com converted from bash to python and added basic tests.
12 >>> # run script
13 >>> sh.Command(sys.argv[0])()
14 <BLANKLINE>
15 """
17 import gettext
18 import os
19 import sys
20 import subprocess
21 import random
22 from dataclasses import dataclass
23 from pathlib import Path
25 # Make electrum use the cursor size configured in GNOME
26 # refs: #20206
27 os.environ["XCURSOR_SIZE"] = subprocess.check_output(
28     ["/usr/bin/gsettings", "get", "org.gnome.desktop.interface", "cursor-size"],
29     text=True,
30 ).strip()
32 translation = gettext.translation("tails", "/usr/share/locale", fallback=True)
33 _ = translation.gettext
35 HOME_DIR = os.environ["HOME"]
36 CONF_DIR = Path(HOME_DIR, ".electrum")
39 @dataclass
40 class CampaignOption:
41     text: str  # "donate today to keep Tails thriving"
42     address: str  # bc1asdfoobar
43     label: str  # sth like "Donation to Tails"
44     probability: int  # expressed in percent (ie to set it to 20%, just use 20)
46     @property
47     def full_address(self):
48         return f"{self.address}?label={self.label}"
52 class CampaignOptions:
53     def __init__(self, *options: CampaignOption):
54         self.options = [opt for opt in options if opt.probability]
55         if sum(opt.probability for opt in self.options) != 100:  # noqa: PLR2004
56             raise ValueError("probabilities must sum to 100")
58     def choose(self) -> CampaignOption:
59         num = random.randint(1, 100)
60         for opt in self.options:
61             num -= opt.probability
62             if num <= 0:
63                 return opt
64         assert False, "We should never be there"  # noqa: PT015,B011
67 # We use different addresses to track which version of the incentive leads to
68 # more donations
69 CAMPAIGN = CampaignOptions(
70     CampaignOption(
71         _(
72             "<b>Shape the future of Tails, today.</b>\n\n"
73             'Contribute to <a href="https://tails.net/news/2024-fundraiser/">our ongoing fundraiser</a>!',
74         ),
75         "Tails 2024 fundraiser",
76         "bitcoin:bc1qunww28s9za0jx7n2r49yy5mqsgtgfwszz39kfr",
77         50,
78     ),
79     CampaignOption(
80         _(
81             "<b>Your donations help us innovate and stay independent.</b>\n\n"
82             'Support our <a href="https://tails.net/news/2024-fundraiser/">ongoing fundraiser</a>!',
83         ),
84         "bitcoin:bc1qceyaeuv7pfedshv8wzwwcwknhzjjnwmuv5qck9",
85         "Donate to Tails 2024",
86         50,
87     ),
88     CampaignOption(
89         "Not used yet",
90         "bitcoin:bc1qdptm9qzul00aejye7vfwwlas0xr0srj04573w4",
91         "",
92         0,
93     ),
94     CampaignOption(
95         "Not used yet",
96         "bitcoin:bc1ql9dltq68wvkscj7zlrj2ajtgeupc6whsf62uef",
97         "",
98         0,
99     ),
100 ).choose()
102 TPS_FEATURE_NAME = "Electrum"
105 def main(*args):
106     # If we're called with arguments, don't show any messages or adjust
107     # any config and just start Electrum, to avoid breaking scripts /
108     # command-line usage.
109     if len(args) > 0:
110         exec_electrum(*args)
112     if not electrum_config_is_persistent():
113         if not verify_start():
114             return
116     # Disable update checking for all users (even those who made their config
117     # persistent before we changed this value in the default config), because
118     # users can't easily update to a new version anyway if it's not in Debian.
119     subprocess.run(
120         ["/usr/bin/electrum", "--offline", "setconfig", "check_updates", "false"],
121         check=True,
122         stdout=subprocess.DEVNULL,
123     )
125     if should_show_donation_message():
126         show_donation_message()
127     else:
128         exec_electrum(*args)
131 def exec_electrum(*args):
132     os.execv("/usr/bin/electrum", ["/usr/bin/electrum", *list(args)])
135 def should_show_donation_message() -> bool:
136     # Don't show the message if electrum is not persistent
137     if not electrum_config_is_persistent():
138         return False
140     # Only show the message if the user has a wallet
141     if not any(Path(CONF_DIR, "wallets").iterdir()):
142         return False
144     # Check the environment variable
145     if os.environ.get("ELECTRUM_DONATION_MESSAGE") == "0":
146         return False
147     elif os.environ.get("ELECTRUM_DONATION_MESSAGE") == "1":
148         return True
150     # If the other conditions are met, show the message in 1/10 of the
151     # cases.
152     return random.randint(0, 9) == 0  # noqa: S311
155 def show_donation_message():
156     # To test the donation message, execute `ELECTRUM_DONATION_MESSAGE=1 electrum` with a Persistent Storage and a wallet
158     # results 0 == True; 1 == False; 5 == Timeout
159     p = subprocess.run(
160         [
161             "/usr/bin/zenity",
162             "--question",
163             "--title",
164             "",
165             "--text",
166             CAMPAIGN.text,
167             "--ok-label",
168             _("Donate Now"),
169             "--cancel-label",
170             _("Later"),
171             "--icon-name",
172             "tails-logo-drawing",
173             "--modal",
174             "--ellipsize",
175         ],
176         check=False,
177     )
178     if p.returncode == 0:
179         # Show a GNOME notification to let the user know that we're
180         # opening Electrum with our donation address.
181         subprocess.check_call(
182             [
183                 "/usr/bin/notify-send",
184                 "--",
185                 _("Opening Electrum with our donation address..."),
186                 _("Thank you for making the Internet a safer place!"),
187             ]
188         )
189         exec_electrum(CAMPAIGN.full_address)
190     else:
191         exec_electrum()
194 def electrum_config_is_persistent():
195     """Return True iff electrum config is persistent.
197     >>> electrum_config_is_persistent()
198     False
199     """
201     try:
202         subprocess.run(
203             ["/usr/local/lib/tpscli", "is-active", TPS_FEATURE_NAME],
204             check=True,
205         )
206         return True
207     except subprocess.CalledProcessError as e:
208         if e.returncode != 1:
209             raise
210         return False
213 def verify_start():
214     """Ask user whether to start Electrum.
216     >>> verify_start() in (True, False)
217     True
218     """
220     disabled_text = _("Persistence is disabled for Electrum")
221     warning_text = _(
222         "When you reboot Tails, all of Electrum's data will be lost, including your Bitcoin wallet.\nIt is strongly recommended to only run Electrum when its persistence feature is activated."
223     )
224     question_text = _("Do you want to start Electrum anyway?")
225     dialog_msg = "<b><big>{}</big></b>\n\n{}\n\n{}\n".format(
226         disabled_text, warning_text, question_text
227     )
228     launch_text = _("_Launch")
229     exit_text = _("_Exit")
231     p = subprocess.run(
232         [
233             "/usr/bin/zenity",
234             "--question",
235             "--title",
236             "",
237             "--default-cancel",
238             "--ellipsize",
239             "--ok-label",
240             launch_text,
241             "--cancel-label",
242             exit_text,
243             "--text",
244             dialog_msg,
245         ],
246         check=False,
247     )
248     return p.returncode == 0
251 if __name__ == "__main__":
252     if len(sys.argv) > 1 and sys.argv[1] == "doctest":
253         import doctest
255         doctest.testmod()
256     else:
257         main(*sys.argv[1:])