CoolFace
Apppublic

lenox-ai/prototype

sourceHugging Faceupdated 3y agoView on Hugging Face
0likes
send_email.py407 linesDownload Raw Back to src
1#!/usr/bin/env python32 3"""Utilities for opening files or URLs in the registered default application4and for sending e-mail using the user's preferred composer.5 6https://stackoverflow.com/a/19779373/32115067 8"""9 10__version__ = "1.1"11__all__ = ["open", "mailto"]12 13import os14import sys15import webbrowser16import subprocess17 18from email.utils import encode_rfc223119 20_controllers = {}21_open = None22 23fileopen = open24 25 26class BaseController(object):27    """Base class for open program controllers."""28 29    def __init__(self, name):30        self.name = name31 32    def open(self, filename):33        raise NotImplementedError34 35 36class Controller(BaseController):37    """Controller for a generic open program."""38 39    def __init__(self, *args):40        super(Controller, self).__init__(os.path.basename(args[0]))41        self.args = list(args)42 43    def _invoke(self, cmdline):44        if sys.platform[:3] == "win":45            closefds = False46            startupinfo = subprocess.STARTUPINFO()47            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW48        else:49            closefds = True50            startupinfo = None51 52        if (53            os.environ.get("DISPLAY")54            or sys.platform[:3] == "win"55            or sys.platform == "darwin"56        ):57            inout = fileopen(os.devnull, "r+")58        else:59            # for TTY programs, we need stdin/out60            inout = None61 62        # if possible, put the child precess in separate process group,63        # so keyboard interrupts don't affect child precess as well as64        # Python65        setsid = getattr(os, "setsid", None)66        if not setsid:67            setsid = getattr(os, "setpgrp", None)68 69        pipe = subprocess.Popen(70            cmdline,71            stdin=inout,72            stdout=inout,73            stderr=inout,74            close_fds=closefds,75            preexec_fn=setsid,76            startupinfo=startupinfo,77        )78 79        # It is assumed that this kind of tools (gnome-open, kfmclient,80        # exo-open, xdg-open and open for OSX) immediately exit after lauching81        # the specific application82        returncode = pipe.wait()83        if hasattr(self, "fixreturncode"):84            returncode = self.fixreturncode(returncode)85        return not returncode86 87    def open(self, filename):88        if isinstance(filename, str):89            cmdline = self.args + [filename]90        else:91            # assume it is a sequence92            cmdline = self.args + filename93        try:94            return self._invoke(cmdline)95        except OSError:96            return False97 98 99# Platform support for Windows100if sys.platform[:3] == "win":101 102    class Start(BaseController):103        """Controller for the win32 start progam through os.startfile."""104 105        def open(self, filename):106            try:107                os.startfile(filename)108            except WindowsError:109                # [Error 22] No application is associated with the specified110                # file for this operation: '<URL>'111                return False112            else:113                return True114 115    _controllers["windows-default"] = Start("start")116    _open = _controllers["windows-default"].open117 118 119# Platform support for MacOS120elif sys.platform == "darwin":121    _controllers["open"] = Controller("open")122    _open = _controllers["open"].open123    124# Platform support for Unix125else:126    import subprocess, stat127 128    # @WARNING: use the private API of the webbrowser module129    # from webbrowser import _iscommand130 131    def _isexecutable(cmd):132        if os.path.isfile(cmd):133            mode = os.stat(cmd)[stat.ST_MODE]134            if mode & stat.S_IXUSR or mode & stat.S_IXGRP or mode & stat.S_IXOTH:135                return True136        return False137 138    def _iscommand(cmd):139        """Return True if cmd is executable or can be found on the executable140        search path."""141        if _isexecutable(cmd):142            return True143 144        path = os.environ.get("PATH")145        if not path:146            return False147        for d in path.split(os.pathsep):148            exe = os.path.join(d, cmd)149            if _isexecutable(exe):150                return True151        return False152 153    class KfmClient(Controller):154        """Controller for the KDE kfmclient program."""155 156        def __init__(self, kfmclient="kfmclient"):157            super(KfmClient, self).__init__(kfmclient, "exec")158            self.kde_version = self.detect_kde_version()159 160        def detect_kde_version(self):161            kde_version = None162            try:163                info = subprocess.getoutput("kde-config --version")164 165                for line in info.splitlines():166                    if line.startswith("KDE"):167                        kde_version = line.split(":")[-1].strip()168                        break169            except (OSError, RuntimeError):170                pass171 172            return kde_version173 174        def fixreturncode(self, returncode):175            if returncode is not None and self.kde_version > "3.5.4":176                return returncode177            else:178                return os.EX_OK179 180    def detect_desktop_environment():181        """Checks for known desktop environments182 183        Return the desktop environments name, lowercase (kde, gnome, xfce)184        or "generic"185 186        """187 188        desktop_environment = "generic"189 190        if os.environ.get("KDE_FULL_SESSION") == "true":191            desktop_environment = "kde"192        elif os.environ.get("GNOME_DESKTOP_SESSION_ID"):193            desktop_environment = "gnome"194        else:195            try:196                info = subprocess.getoutput("xprop -root _DT_SAVE_MODE")197                if ' = "xfce4"' in info:198                    desktop_environment = "xfce"199            except (OSError, RuntimeError):200                pass201 202        return desktop_environment203 204    def register_X_controllers():205        if _iscommand("kfmclient"):206            _controllers["kde-open"] = KfmClient()207 208        for command in ("gnome-open", "exo-open", "xdg-open"):209            if _iscommand(command):210                _controllers[command] = Controller(command)211 212    def get():213        controllers_map = {214            "gnome": "gnome-open",215            "kde": "kde-open",216            "xfce": "exo-open",217        }218 219        desktop_environment = detect_desktop_environment()220 221        try:222            controller_name = controllers_map[desktop_environment]223            return _controllers[controller_name].open224 225        except KeyError:226            if "xdg-open" in _controllers:227                return _controllers["xdg-open"].open228            else:229                return webbrowser.open230 231    if os.environ.get("DISPLAY"):232        register_X_controllers()233    _open = get()234 235 236def open(filename):237    """Open a file or an URL in the registered default application."""238 239    return _open(filename)240 241 242def _fix_addersses(**kwargs):243    for headername in ("address", "to", "cc", "bcc"):244        try:245            headervalue = kwargs[headername]246            if not headervalue:247                del kwargs[headername]248                continue249            elif not isinstance(headervalue, str):250                # assume it is a sequence251                headervalue = ",".join(headervalue)252 253        except KeyError:254            pass255        except TypeError:256            raise TypeError(257                'string or sequence expected for "%s", '258                "%s found" % (headername, type(headervalue).__name__)259            )260        else:261            translation_map = {"%": "%25", "&": "%26", "?": "%3F"}262            for char, replacement in list(translation_map.items()):263                headervalue = headervalue.replace(char, replacement)264            kwargs[headername] = headervalue265 266    return kwargs267 268 269def mailto_format(**kwargs):270    # @TODO: implement utf8 option271 272    kwargs = _fix_addersses(**kwargs)273    parts = []274    for headername in ("to", "cc", "bcc", "subject", "body"):275        if headername in kwargs:276            headervalue = kwargs[headername]277            if not headervalue:278                continue279            if headername in ("address", "to", "cc", "bcc"):280                parts.append("%s=%s" % (headername, headervalue))281            else:282                headervalue = encode_rfc2231(headervalue, charset="utf-8")[283                    7:284                ]  # @TODO: check285                parts.append("%s=%s" % (headername, headervalue))286 287    mailto_string = "mailto:%s" % kwargs.get("address", "")288    if parts:289        mailto_string = "%s?%s" % (mailto_string, "&".join(parts))290 291    return mailto_string292 293 294def wrap_mailto(recipiant_email_summary, subject_email_summary, summary_output):295    return mailto(296        address=recipiant_email_summary,297        subject=subject_email_summary,298        body=summary_output,299    )300 301 302def mailto(address, to=None, cc=None, bcc=None, subject=None, body=None, attach=None):303    """Send an e-mail using the user's preferred composer.304 305    Open the user's preferred e-mail composer in order to send a mail to306    address(es) that must follow the syntax of RFC822. Multiple addresses307    may be provided (for address, cc and bcc parameters) as separate308    arguments.309 310    All parameters provided are used to prefill corresponding fields in311    the user's e-mail composer. The user will have the opportunity to312    change any of this information before actually sending the e-mail.313 314    address - specify the destination recipient315    cc      - specify a recipient to be copied on the e-mail316    bcc     - specify a recipient to be blindly copied on the e-mail317    subject - specify a subject for the e-mail318    body    - specify a body for the e-mail. Since the user will be able319              to make changes before actually sending the e-mail, this320              can be used to provide the user with a template for the321              e-mail text may contain linebreaks322    attach  - specify an attachment for the e-mail. file must point to323              an existing file (UNSUPPORTED)324 325    """326 327    mailto_string = mailto_format(**locals())328    return open(mailto_string)329 330 331if __name__ == "__main__":332    from optparse import OptionParser333 334    version = "%%prog %s" % __version__335    usage = (336        "\n\n%prog FILENAME [FILENAME(s)] -- for opening files"337        "\n\n%prog -m [OPTIONS] ADDRESS [ADDRESS(es)] -- for sending e-mails"338    )339 340    parser = OptionParser(usage=usage, version=version, description=__doc__)341    parser.add_option(342        "-m",343        "--mailto",344        dest="mailto_mode",345        default=False,346        action="store_true",347        help="set mailto mode. " "If not set any other option is ignored",348    )349    parser.add_option(350        "--cc", dest="cc", help="specify a recipient to be " "copied on the e-mail"351    )352    parser.add_option(353        "--bcc",354        dest="bcc",355        help="specify a recipient to be " "blindly copied on the e-mail",356    )357    parser.add_option(358        "--subject", dest="subject", help="specify a subject for the e-mail"359    )360    parser.add_option(361        "--body",362        dest="body",363        help="specify a body for the "364        "e-mail. Since the user will be able to make changes "365        "before actually sending the e-mail, this can be used "366        "to provide the user with a template for the e-mail "367        "text may contain linebreaks",368    )369    parser.add_option(370        "--attach",371        dest="attach",372        help="specify an attachment "373        "for the e-mail. file must point to an existing file",374    )375    # breakpoint()376    (options, args) = parser.parse_args()377 378    if not args:379        parser.print_usage()380        parser.exit(1)381 382    if options.mailto_mode:383        if not mailto(384            args,385            None,386            options.cc,387            options.bcc,388            options.subject,389            options.body,390            options.attach,391        ):392            sys.exit("Unable to open the e-mail client")393    else:394        for name in ("cc", "bcc", "subject", "body", "attach"):395            if getattr(options, name):396                parser.error(397                    'The "cc", "bcc", "subject", "body" and "attach" '398                    "options are only accepten in mailto mode"399                )400        success = False401        for arg in args:402            if not open(arg):403                print('Unable to open "%s"' % arg)404            else:405                success = True406        sys.exit(success)407