prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  SD: legislators have no email addresses
  Example legislator: http://sdlegislature.gov/Legislators/Legislators/MemberDetail.aspx?Session=2017&Member=1125&Cleaned=True
  
  Looking at him in openstates, his phone number is there but his email address is missing: https://openstates.org/api/v1/legislators/SDL000286/
  
  The data is there, the scraper just needs to be updated to capture the email property.
  
  Thanks!
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `openstates/sd/legislators.py`
  Content:
  ```
  1 import re
  2 
  3 from billy.scrape import NoDataForPeriod
  4 from billy.scrape.legislators import LegislatorScraper, Legislator
  5 
  6 import lxml.html
  7 
  8 
  9 class SDLegislatorScraper(LegislatorScraper):
  10     jurisdiction = 'sd'
  11     latest_only = True
  12 
  13     def scrape(self, chamber, term):
  14         url = 'http://www.sdlegislature.gov/Legislators/default.aspx' \
  15               '?CurrentSession=True'
  16 
  17         if chamber == 'upper':
  18             search = 'Senate Members'
  19         else:
  20             search = 'House Members'
  21 
  22         page = self.get(url).text
  23         page = lxml.html.fromstring(page)
  24         page.make_links_absolute(url)
  25 
  26         for link in page.xpath("//h4[text()='{}']/../div/a".format(search)):
  27             name = link.text.strip()
  28 
  29             self.scrape_legislator(name, chamber, term,
  30                                    '{}&Cleaned=True'.format(
  31                                        link.attrib['href']))
  32 
  33     def scrape_legislator(self, name, chamber, term, url):
  34         page = self.get(url).text
  35         page = lxml.html.fromstring(page)
  36         page.make_links_absolute(url)
  37 
  38         party = page.xpath("string(//span[contains(@id, 'Party')])")
  39         party = party.strip()
  40 
  41         if party == 'Democrat':
  42             party = 'Democratic'
  43 
  44         district = page.xpath("string(//span[contains(@id, 'District')])")
  45         district = district.strip().lstrip('0')
  46 
  47         occupation = page.xpath(
  48             "string(//span[contains(@id, 'Occupation')])")
  49         occupation = occupation.strip()
  50 
  51         (photo_url, ) = page.xpath('//img[contains(@id, "_imgMember")]/@src')
  52 
  53         office_phone = page.xpath(
  54             "string(//span[contains(@id, 'CapitolPhone')])").strip()
  55 
  56         email = None
  57 
  58         email_link = page.xpath('//a[@id="lnkMail"]')
  59 
  60         if email_link:
  61             email = email_link[0].attrib['href'].split(":")[1]
  62 
  63         legislator = Legislator(term, chamber, district, name,
  64                                 party=party,
  65                                 occupation=occupation,
  66                                 photo_url=photo_url,
  67                                 url=url)
  68         kwargs = {}
  69         if office_phone.strip() != "":
  70             kwargs['phone'] = office_phone
  71 
  72         if email and email.strip() != "":
  73             # South Dakota protects their email addresses from scraping using
  74             # some JS code that runs on page load
  75             # Until that code is run, all their email addresses are listed as
  76             # *@example.com; so, fix this
  77             kwargs['email'] = re.sub(r'@example\.com$', '@sdlegislature.gov', email)
  78 
  79         if kwargs:
  80             legislator.add_office('capitol', 'Capitol Office', **kwargs)
  81 
  82         home_address = [
  83                 x.strip() for x in
  84                 page.xpath('//td/span[contains(@id, "HomeAddress")]/text()')
  85                 if x.strip()
  86                 ]
  87         if home_address:
  88             home_address = "\n".join(home_address)
  89             home_phone = page.xpath(
  90                 "string(//span[contains(@id, 'HomePhone')])").strip()
  91             legislator.add_office(
  92                     'district',
  93                     'District Office',
  94                     address=home_address,
  95                     phone=home_phone or None
  96                     )
  97 
  98         legislator.add_source(url)
  99 
  100         comm_url = page.xpath("//a[. = 'Committees']")[0].attrib['href']
  101         self.scrape_committees(legislator, comm_url)
  102 
  103         self.save_legislator(legislator)
  104 
  105     def scrape_committees(self, leg, url):
  106         page = self.get(url).text
  107         page = lxml.html.fromstring(page)
  108         leg.add_source(url)
  109 
  110         term = leg['roles'][0]['term']
  111 
  112         for link in page.xpath("//a[contains(@href, 'CommitteeMem')]"):
  113             comm = link.text.strip()
  114 
  115             role = link.xpath('../following-sibling::td')[0]\
  116                 .text_content().lower()
  117 
  118             if comm.startswith('Joint'):
  119                 chamber = 'joint'
  120             else:
  121                 chamber = leg['roles'][0]['chamber']
  122 
  123             leg.add_role('committee member', term=term, chamber=chamber,
  124                          committee=comm, position=role)
  125
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/openstates/sd/legislators.py b/openstates/sd/legislators.py
  --- a/openstates/sd/legislators.py
  +++ b/openstates/sd/legislators.py
  @@ -69,12 +69,14 @@
           if office_phone.strip() != "":
               kwargs['phone'] = office_phone
   
  -        if email and email.strip() != "":
  -            # South Dakota protects their email addresses from scraping using
  -            # some JS code that runs on page load
  -            # Until that code is run, all their email addresses are listed as
  -            # *@example.com; so, fix this
  -            kwargs['email'] = re.sub(r'@example\.com$', '@sdlegislature.gov', email)
  +        # SD is hiding their email addresses entirely in JS now, so
  +        # search through <script> blocks looking for them
  +        for script in page.xpath('//script'):
  +            if script.text:
  +                match = re.search(r'([\w.]+@sdlegislature\.gov)', script.text)
  +                if match:
  +                    kwargs['email'] = match.group(0)
  +                    break
   
           if kwargs:
               legislator.add_office('capitol', 'Capitol Office', **kwargs)
  
verification_info: {"golden_diff": "diff --git a/openstates/sd/legislators.py b/openstates/sd/legislators.py\n--- a/openstates/sd/legislators.py\n+++ b/openstates/sd/legislators.py\n@@ -69,12 +69,14 @@\n         if office_phone.strip() != \"\":\n             kwargs['phone'] = office_phone\n \n-        if email and email.strip() != \"\":\n-            # South Dakota protects their email addresses from scraping using\n-            # some JS code that runs on page load\n-            # Until that code is run, all their email addresses are listed as\n-            # *@example.com; so, fix this\n-            kwargs['email'] = re.sub(r'@example\\.com$', '@sdlegislature.gov', email)\n+        # SD is hiding their email addresses entirely in JS now, so\n+        # search through <script> blocks looking for them\n+        for script in page.xpath('//script'):\n+            if script.text:\n+                match = re.search(r'([\\w.]+@sdlegislature\\.gov)', script.text)\n+                if match:\n+                    kwargs['email'] = match.group(0)\n+                    break\n \n         if kwargs:\n             legislator.add_office('capitol', 'Capitol Office', **kwargs)\n", "issue": "SD: legislators have no email addresses\nExample legislator: http://sdlegislature.gov/Legislators/Legislators/MemberDetail.aspx?Session=2017&Member=1125&Cleaned=True\r\n\r\nLooking at him in openstates, his phone number is there but his email address is missing: https://openstates.org/api/v1/legislators/SDL000286/\r\n\r\nThe data is there, the scraper just needs to be updated to capture the email property.\r\n\r\nThanks!\n", "before_files": [{"content": "import re\n\nfrom billy.scrape import NoDataForPeriod\nfrom billy.scrape.legislators import LegislatorScraper, Legislator\n\nimport lxml.html\n\n\nclass SDLegislatorScraper(LegislatorScraper):\n    jurisdiction = 'sd'\n    latest_only = True\n\n    def scrape(self, chamber, term):\n        url = 'http://www.sdlegislature.gov/Legislators/default.aspx' \\\n              '?CurrentSession=True'\n\n        if chamber == 'upper':\n            search = 'Senate Members'\n        else:\n            search = 'House Members'\n\n        page = self.get(url).text\n        page = lxml.html.fromstring(page)\n        page.make_links_absolute(url)\n\n        for link in page.xpath(\"//h4[text()='{}']/../div/a\".format(search)):\n            name = link.text.strip()\n\n            self.scrape_legislator(name, chamber, term,\n                                   '{}&Cleaned=True'.format(\n                                       link.attrib['href']))\n\n    def scrape_legislator(self, name, chamber, term, url):\n        page = self.get(url).text\n        page = lxml.html.fromstring(page)\n        page.make_links_absolute(url)\n\n        party = page.xpath(\"string(//span[contains(@id, 'Party')])\")\n        party = party.strip()\n\n        if party == 'Democrat':\n            party = 'Democratic'\n\n        district = page.xpath(\"string(//span[contains(@id, 'District')])\")\n        district = district.strip().lstrip('0')\n\n        occupation = page.xpath(\n            \"string(//span[contains(@id, 'Occupation')])\")\n        occupation = occupation.strip()\n\n        (photo_url, ) = page.xpath('//img[contains(@id, \"_imgMember\")]/@src')\n\n        office_phone = page.xpath(\n            \"string(//span[contains(@id, 'CapitolPhone')])\").strip()\n\n        email = None\n\n        email_link = page.xpath('//a[@id=\"lnkMail\"]')\n\n        if email_link:\n            email = email_link[0].attrib['href'].split(\":\")[1]\n\n        legislator = Legislator(term, chamber, district, name,\n                                party=party,\n                                occupation=occupation,\n                                photo_url=photo_url,\n                                url=url)\n        kwargs = {}\n        if office_phone.strip() != \"\":\n            kwargs['phone'] = office_phone\n\n        if email and email.strip() != \"\":\n            # South Dakota protects their email addresses from scraping using\n            # some JS code that runs on page load\n            # Until that code is run, all their email addresses are listed as\n            # *@example.com; so, fix this\n            kwargs['email'] = re.sub(r'@example\\.com$', '@sdlegislature.gov', email)\n\n        if kwargs:\n            legislator.add_office('capitol', 'Capitol Office', **kwargs)\n\n        home_address = [\n                x.strip() for x in\n                page.xpath('//td/span[contains(@id, \"HomeAddress\")]/text()')\n                if x.strip()\n                ]\n        if home_address:\n            home_address = \"\\n\".join(home_address)\n            home_phone = page.xpath(\n                \"string(//span[contains(@id, 'HomePhone')])\").strip()\n            legislator.add_office(\n                    'district',\n                    'District Office',\n                    address=home_address,\n                    phone=home_phone or None\n                    )\n\n        legislator.add_source(url)\n\n        comm_url = page.xpath(\"//a[. = 'Committees']\")[0].attrib['href']\n        self.scrape_committees(legislator, comm_url)\n\n        self.save_legislator(legislator)\n\n    def scrape_committees(self, leg, url):\n        page = self.get(url).text\n        page = lxml.html.fromstring(page)\n        leg.add_source(url)\n\n        term = leg['roles'][0]['term']\n\n        for link in page.xpath(\"//a[contains(@href, 'CommitteeMem')]\"):\n            comm = link.text.strip()\n\n            role = link.xpath('../following-sibling::td')[0]\\\n                .text_content().lower()\n\n            if comm.startswith('Joint'):\n                chamber = 'joint'\n            else:\n                chamber = leg['roles'][0]['chamber']\n\n            leg.add_role('committee member', term=term, chamber=chamber,\n                         committee=comm, position=role)\n", "path": "openstates/sd/legislators.py"}], "after_files": [{"content": "import re\n\nfrom billy.scrape import NoDataForPeriod\nfrom billy.scrape.legislators import LegislatorScraper, Legislator\n\nimport lxml.html\n\n\nclass SDLegislatorScraper(LegislatorScraper):\n    jurisdiction = 'sd'\n    latest_only = True\n\n    def scrape(self, chamber, term):\n        url = 'http://www.sdlegislature.gov/Legislators/default.aspx' \\\n              '?CurrentSession=True'\n\n        if chamber == 'upper':\n            search = 'Senate Members'\n        else:\n            search = 'House Members'\n\n        page = self.get(url).text\n        page = lxml.html.fromstring(page)\n        page.make_links_absolute(url)\n\n        for link in page.xpath(\"//h4[text()='{}']/../div/a\".format(search)):\n            name = link.text.strip()\n\n            self.scrape_legislator(name, chamber, term,\n                                   '{}&Cleaned=True'.format(\n                                       link.attrib['href']))\n\n    def scrape_legislator(self, name, chamber, term, url):\n        page = self.get(url).text\n        page = lxml.html.fromstring(page)\n        page.make_links_absolute(url)\n\n        party = page.xpath(\"string(//span[contains(@id, 'Party')])\")\n        party = party.strip()\n\n        if party == 'Democrat':\n            party = 'Democratic'\n\n        district = page.xpath(\"string(//span[contains(@id, 'District')])\")\n        district = district.strip().lstrip('0')\n\n        occupation = page.xpath(\n            \"string(//span[contains(@id, 'Occupation')])\")\n        occupation = occupation.strip()\n\n        (photo_url, ) = page.xpath('//img[contains(@id, \"_imgMember\")]/@src')\n\n        office_phone = page.xpath(\n            \"string(//span[contains(@id, 'CapitolPhone')])\").strip()\n\n        email = None\n\n        email_link = page.xpath('//a[@id=\"lnkMail\"]')\n\n        if email_link:\n            email = email_link[0].attrib['href'].split(\":\")[1]\n\n        legislator = Legislator(term, chamber, district, name,\n                                party=party,\n                                occupation=occupation,\n                                photo_url=photo_url,\n                                url=url)\n        kwargs = {}\n        if office_phone.strip() != \"\":\n            kwargs['phone'] = office_phone\n\n        # SD is hiding their email addresses entirely in JS now, so\n        # search through <script> blocks looking for them\n        for script in page.xpath('//script'):\n            if script.text:\n                match = re.search(r'([\\w.]+@sdlegislature\\.gov)', script.text)\n                if match:\n                    kwargs['email'] = match.group(0)\n                    break\n\n        if kwargs:\n            legislator.add_office('capitol', 'Capitol Office', **kwargs)\n\n        home_address = [\n                x.strip() for x in\n                page.xpath('//td/span[contains(@id, \"HomeAddress\")]/text()')\n                if x.strip()\n                ]\n        if home_address:\n            home_address = \"\\n\".join(home_address)\n            home_phone = page.xpath(\n                \"string(//span[contains(@id, 'HomePhone')])\").strip()\n            legislator.add_office(\n                    'district',\n                    'District Office',\n                    address=home_address,\n                    phone=home_phone or None\n                    )\n\n        legislator.add_source(url)\n\n        comm_url = page.xpath(\"//a[. = 'Committees']\")[0].attrib['href']\n        self.scrape_committees(legislator, comm_url)\n\n        self.save_legislator(legislator)\n\n    def scrape_committees(self, leg, url):\n        page = self.get(url).text\n        page = lxml.html.fromstring(page)\n        leg.add_source(url)\n\n        term = leg['roles'][0]['term']\n\n        for link in page.xpath(\"//a[contains(@href, 'CommitteeMem')]\"):\n            comm = link.text.strip()\n\n            role = link.xpath('../following-sibling::td')[0]\\\n                .text_content().lower()\n\n            if comm.startswith('Joint'):\n                chamber = 'joint'\n            else:\n                chamber = leg['roles'][0]['chamber']\n\n            leg.add_role('committee member', term=term, chamber=chamber,\n                         committee=comm, position=role)\n", "path": "openstates/sd/legislators.py"}]}